Merging the rendering code from main.js and regexper.js

This commit is contained in:
Jeff Avallone 2014-12-29 21:31:36 -05:00
parent d6e81a2932
commit e271115d24
7 changed files with 182 additions and 130 deletions

View File

@ -6,7 +6,30 @@ import Q from 'q';
describe('parser/javascript.js', function() {
beforeEach(function() {
this.parser = new Parser();
this.container = document.createElement('div');
this.parser = new Parser(this.container);
});
describe('container property', function() {
it('sets the content of the element', function() {
var element = document.createElement('div');
this.parser.container = element;
expect(element.innerHTML).not.toEqual('');
});
it('keeps the original content if the keepContent option is set', function() {
var element = document.createElement('div');
element.innerHTML = 'example content';
this.parser.options.keepContent = true;
this.parser.container = element;
expect(element.innerHTML).toContain('example content');
expect(element.innerHTML).not.toEqual('example content');
});
});
describe('#parse', function() {
@ -15,6 +38,12 @@ describe('parser/javascript.js', function() {
spyOn(regexpParser, 'parse');
});
it('adds the "loading" class', function() {
spyOn(this.parser, '_addClass');
this.parser.parse('example expression');
expect(this.parser._addClass).toHaveBeenCalledWith('loading');
});
it('parses the expression', function(done) {
this.parser.parse('example expression')
.then(() => {
@ -59,23 +88,10 @@ describe('parser/javascript.js', function() {
this.renderPromise = Q.defer();
this.parser.parsed = jasmine.createSpyObj('parsed', ['render']);
this.parser.parsed.render.and.returnValue(this.renderPromise.promise);
this.svgBase = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1"></svt>';
this.svgContainer = document.createElement('div');
});
it('creates the SVG element', function() {
var svg;
this.parser.render(this.svgContainer, this.svgBase);
svg = this.svgContainer.querySelector('svg');
expect(svg.getAttribute('xmlns')).toEqual('http://www.w3.org/2000/svg');
expect(svg.getAttribute('version')).toEqual('1.1');
});
it('render the parsed expression', function() {
this.parser.render(this.svgContainer, this.svgBase);
this.parser.render();
expect(this.parser.parsed.render).toHaveBeenCalled();
});
@ -90,12 +106,11 @@ describe('parser/javascript.js', function() {
height: 24
});
this.parser.render(this.svgContainer, this.svgBase);
this.renderPromise.resolve(this.result);
});
it('positions the renderd expression', function(done) {
this.parser.render(this.svgContainer, this.svgBase)
this.parser.render()
.then(() => {
expect(this.result.transform).toHaveBeenCalledWith(Snap.matrix()
.translate(6, 8));
@ -105,9 +120,9 @@ describe('parser/javascript.js', function() {
});
it('sets the dimensions of the image', function(done) {
this.parser.render(this.svgContainer, this.svgBase)
this.parser.render()
.then(() => {
var svg = this.svgContainer.querySelector('svg');
var svg = this.container.querySelector('svg');
expect(svg.getAttribute('width')).toEqual('62');
expect(svg.getAttribute('height')).toEqual('44');
@ -116,6 +131,25 @@ describe('parser/javascript.js', function() {
.done();
});
it('removes the "loading" class', function(done) {
spyOn(this.parser, '_removeClass');
this.parser.render()
.then(() => {
expect(this.parser._removeClass).toHaveBeenCalledWith('loading');
})
.finally(done)
.done();
});
it('removes the progress element', function(done) {
this.parser.render()
.then(() => {
expect(this.container.querySelector('.loading')).toBeNull();
})
.finally(done)
.done();
});
});
});

View File

@ -15,7 +15,6 @@ describe('regexper.js', function() {
'<div><a href="#" data-glyph="link-intact"></a></div>',
'<div><a href="#" data-glyph="data-transfer-download"></a></div>',
'<div id="regexp-render"></div>',
'<script type="text/html" id="svg-base"><svg></svg></script>'
].join('');
this.regexper = new Regexper(this.root);
@ -102,7 +101,7 @@ describe('regexper.js', function() {
beforeEach(function() {
this.event = util.customEvent('keyup');
this.regexper.runningParser = jasmine.createSpyObj('parser', ['cancel']);
this.regexper.running = jasmine.createSpyObj('parser', ['cancel']);
});
describe('when the keyCode is not 27 (Escape)', function() {
@ -113,7 +112,7 @@ describe('regexper.js', function() {
it('does not cancel the parser', function() {
this.regexper.documentKeypressListener(this.event);
expect(this.regexper.runningParser.cancel).not.toHaveBeenCalled();
expect(this.regexper.running.cancel).not.toHaveBeenCalled();
});
});
@ -126,7 +125,7 @@ describe('regexper.js', function() {
it('cancels the parser', function() {
this.regexper.documentKeypressListener(this.event);
expect(this.regexper.runningParser.cancel).toHaveBeenCalled();
expect(this.regexper.running.cancel).toHaveBeenCalled();
});
});
@ -375,14 +374,14 @@ describe('regexper.js', function() {
expect(this.regexper._trackEvent).toHaveBeenCalledWith('visualization', 'start');
});
it('keeps a copy of the running parser', function() {
it('keeps a copy of the running property parser', function() {
this.regexper.renderRegexp('example expression');
expect(this.regexper.runningParser).toBeTruthy();
expect(this.regexper.running).toBeTruthy();
});
it('parses the expression', function() {
this.regexper.renderRegexp('example expression');
expect(this.regexper.runningParser.parse).toHaveBeenCalledWith('example expression');
expect(this.regexper.running.parse).toHaveBeenCalledWith('example expression');
});
describe('when parsing fails', function() {
@ -423,7 +422,7 @@ describe('regexper.js', function() {
describe('when parsing succeeds', function() {
beforeEach(function() {
this.parser = new Parser();
this.parser = new Parser(this.regexper.svgContainer);
this.parsePromise.resolve(this.parser);
this.renderPromise.resolve();
});
@ -431,7 +430,7 @@ describe('regexper.js', function() {
it('renders the expression', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.parser.render).toHaveBeenCalledWith(this.regexper.svgContainer.querySelector('.svg'), this.regexper.svgBase);
expect(this.parser.render).toHaveBeenCalled();
}, fail)
.finally(done)
.done();
@ -442,7 +441,7 @@ describe('regexper.js', function() {
describe('when rendering is complete', function() {
beforeEach(function() {
this.parser = new Parser();
this.parser = new Parser(this.regexper.svgContainer);
this.parsePromise.resolve(this.parser);
this.renderPromise.resolve();
});
@ -483,10 +482,10 @@ describe('regexper.js', function() {
.done();
});
it('sets the runningParser to false', function(done) {
it('sets the running property to false', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.regexper.runningParser).toBeFalsy();
expect(this.regexper.running).toBeFalsy();
}, fail)
.finally(done)
.done();
@ -497,7 +496,7 @@ describe('regexper.js', function() {
describe('when the rendering is cancelled', function() {
beforeEach(function() {
this.parser = new Parser();
this.parser = new Parser(this.regexper.svgContainer);
this.parsePromise.resolve(this.parser);
this.renderPromise.reject('Render cancelled');
});
@ -520,10 +519,10 @@ describe('regexper.js', function() {
.done();
});
it('sets the runningParser to false', function(done) {
it('sets the running property to false', function(done) {
this.regexper.renderRegexp('example expression')
.then(() => {
expect(this.regexper.runningParser).toBeFalsy();
expect(this.regexper.running).toBeFalsy();
}, fail)
.finally(done)
.done();
@ -534,15 +533,15 @@ describe('regexper.js', function() {
describe('when the rendering fails', function() {
beforeEach(function() {
this.parser = new Parser();
this.parser = new Parser(this.regexper.svgContainer);
this.parsePromise.resolve(this.parser);
this.renderPromise.reject('example render failure');
});
it('sets the runningParser to false', function(done) {
it('sets the running property to false', function(done) {
this.regexper.renderRegexp('example expression')
.then(fail, () => {
expect(this.regexper.runningParser).toBeFalsy();
expect(this.regexper.running).toBeFalsy();
})
.finally(done)
.done();

14
spec/setup_spec.js Normal file
View File

@ -0,0 +1,14 @@
beforeEach(function() {
var template = document.createElement('script');
template.setAttribute('type', 'text/html');
template.setAttribute('id', 'svg-container-base');
template.innerHTML = [
'<div class="svg"><svg></svg></div>',
'<div class="progress"><div></div></div>'
].join('');
document.body.appendChild(template);
});
afterEach(function() {
document.body.removeChild(document.body.querySelector('#svg-container-base'));
});

View File

@ -27,31 +27,10 @@ window._gaq = (typeof _gaq !== 'undefined') ? _gaq : {
});
}
_.each(document.querySelectorAll('figure[data-expr]'), element => {
var parser = new Parser(),
svg, percentage;
element.className = _.compact([element.className, 'loading']).join(' ');
element.innerHTML = [
'<div class="svg"></div>',
'<div class="progress"><div style="width: 0;"></div></div>',
element.innerHTML
].join('');
svg = element.querySelector('.svg');
percentage = element.querySelector('.progress div');
setTimeout(() => {
parser.parse(element.getAttribute('data-expr'))
.invoke('render', svg, document.querySelector('#svg-base').innerHTML)
.then(null, null, progress => {
percentage.style.width = progress * 100 + '%';
})
.finally(() => {
element.className = _.without(element.className.split(' '), 'loading').join(' ');
element.removeChild(element.querySelector('.progress'));
})
.done();
}, 1);
_.each(document.querySelectorAll('[data-expr]'), element => {
new Parser(element, { keepContent: true })
.parse(element.getAttribute('data-expr'))
.invoke('render')
.done();
});
}());

View File

@ -1,10 +1,11 @@
import Q from 'q';
import Snap from 'snapsvg';
import _ from 'lodash';
import javascript from './javascript/parser.js';
export default class Parser {
constructor() {
constructor(container, options) {
this.state = {
groupCounter: 1,
renderCounter: 0,
@ -12,11 +13,40 @@ export default class Parser {
cancelRender: false,
warnings: []
};
this.options = options || {};
_.defaults(this.options, {
keepContent: false
});
this.container = container;
}
set container(cont) {
this._container = cont;
this._container.innerHTML = [
document.querySelector('#svg-container-base').innerHTML,
this.options.keepContent ? this.container.innerHTML : ''
].join('');
}
get container() {
return this._container;
}
_addClass(className) {
this.container.className = _.compact([this.container.className, className]).join(' ');
}
_removeClass(className) {
this.container.className = _.without(this.container.className.split(' '), className).join(' ');
}
parse(expression) {
var deferred = Q.defer();
this._addClass('loading');
setTimeout(() => {
try {
javascript.Parser.SyntaxNode.state = this.state;
@ -32,23 +62,30 @@ export default class Parser {
return deferred.promise;
}
render(containerElement, svgBase) {
var svg;
containerElement.innerHTML = svgBase;
svg = Snap(containerElement.querySelector('svg'));
render() {
var svg = Snap(this.container.querySelector('svg')),
progress = this.container.querySelector('.progress div');
return this.parsed.render(svg.group())
.then(result => {
var box = result.getBBox();
.then(
result => {
var box = result.getBBox();
result.transform(Snap.matrix()
.translate(10 - box.x, 10 - box.y));
svg.attr({
width: box.width + 20,
height: box.height + 20
});
result.transform(Snap.matrix()
.translate(10 - box.x, 10 - box.y));
svg.attr({
width: box.width + 20,
height: box.height + 20
});
},
null,
percent => {
progress.style.width = percent * 100 + '%';
}
)
.finally(() => {
this._removeClass('loading');
this.container.removeChild(this.container.querySelector('.progress'));
});
}

View File

@ -13,7 +13,6 @@ export default class Regexper {
this.permalink = root.querySelector('a[data-glyph="link-intact"]');
this.download = root.querySelector('a[data-glyph="data-transfer-download"]');
this.svgContainer = root.querySelector('#regexp-render');
this.svgBase = this.root.querySelector('#svg-base').innerHTML;
}
keypressListener(event) {
@ -28,8 +27,8 @@ export default class Regexper {
}
documentKeypressListener(event) {
if (event.keyCode === 27 && this.runningParser) {
this.runningParser.cancel();
if (event.keyCode === 27 && this.running) {
this.running.cancel();
}
}
@ -123,12 +122,12 @@ export default class Regexper {
}
renderRegexp(expression) {
var svg, percentage;
var parseError = false;
if (this.runningParser) {
if (this.running) {
let deferred = Q.defer();
this.runningParser.cancel();
this.running.cancel();
setTimeout(() => {
deferred.resolve(this.renderRegexp(expression));
@ -140,53 +139,38 @@ export default class Regexper {
this.state = 'is-loading';
this._trackEvent('visualization', 'start');
this.runningParser = new Parser();
this.running = new Parser(this.svgContainer);
this.svgContainer.innerHTML = [
'<div class="svg"></div>',
'<div class="progress"><div style="width: 0;"></div></div>',
].join('');
svg = this.svgContainer.querySelector('.svg');
percentage = this.svgContainer.querySelector('.progress div');
return this.runningParser.parse(expression)
return this.running
.parse(expression)
.then(null, message => {
this.state = 'has-error';
this.error.innerHTML = '';
this.error.appendChild(document.createTextNode(message));
this.parseError = true;
parseError = true;
throw message;
})
.invoke('render', svg, this.svgBase)
.then(
() => {
.invoke('render')
.then(() => {
this.state = 'has-results';
this.updateLinks();
this.displayWarnings(this.runningParser.warnings);
this.displayWarnings(this.running.warnings);
this._trackEvent('visualization', 'complete');
},
null,
progress => {
percentage.style.width = progress * 100 + '%';
}
)
})
.then(null, message => {
if (message === 'Render cancelled') {
this._trackEvent('visualization', 'cancelled');
this.state = '';
} else if (this.parseError) {
} else if (parseError) {
this._trackEvent('visualization', 'parse error');
} else {
throw message;
}
})
.finally(() => {
this.runningParser = false;
this.parseError = false;
this.svgContainer.removeChild(this.svgContainer.querySelector('.progress'));
this.running = false;
});
}
}

View File

@ -24,27 +24,32 @@
</div>
<![endif]-->
<!--[if gt IE 8]> -->
<script type="text/html" id="svg-base">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
version="1.1">
<defs>
<style type="text/css">${svgStyles}</style>
</defs>
<metadata>
<rdf:RDF>
<cc:License rdf:about="http://creativecommons.org/licenses/by/3.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
</svg>
<script type="text/html" id="svg-container-base">
<div class="svg">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
version="1.1">
<defs>
<style type="text/css">${svgStyles}</style>
</defs>
<metadata>
<rdf:RDF>
<cc:License rdf:about="http://creativecommons.org/licenses/by/3.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
</svg>
</div>
<div class="progress">
<div style="width:0;"></div>
</div>
</script>
<header>