From e271115d24b0cc762d8c2507d50bc9cfb2f5123a Mon Sep 17 00:00:00 2001 From: Jeff Avallone Date: Mon, 29 Dec 2014 21:31:36 -0500 Subject: [PATCH] Merging the rendering code from main.js and regexper.js --- spec/parser/javascript_spec.js | 72 +++++++++++++++++++++++++--------- spec/regexper_spec.js | 35 ++++++++--------- spec/setup_spec.js | 14 +++++++ src/js/main.js | 31 +++------------ src/js/parser/javascript.js | 67 ++++++++++++++++++++++++------- src/js/regexper.js | 46 +++++++--------------- template.html | 47 ++++++++++++---------- 7 files changed, 182 insertions(+), 130 deletions(-) create mode 100644 spec/setup_spec.js diff --git a/spec/parser/javascript_spec.js b/spec/parser/javascript_spec.js index 83183c0..e5f91e7 100644 --- a/spec/parser/javascript_spec.js +++ b/spec/parser/javascript_spec.js @@ -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 = ''; - 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(); + }); + }); }); diff --git a/spec/regexper_spec.js b/spec/regexper_spec.js index c8240c0..c3fabc4 100644 --- a/spec/regexper_spec.js +++ b/spec/regexper_spec.js @@ -15,7 +15,6 @@ describe('regexper.js', function() { '
', '
', '
', - '' ].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(); diff --git a/spec/setup_spec.js b/spec/setup_spec.js new file mode 100644 index 0000000..c9342e2 --- /dev/null +++ b/spec/setup_spec.js @@ -0,0 +1,14 @@ +beforeEach(function() { + var template = document.createElement('script'); + template.setAttribute('type', 'text/html'); + template.setAttribute('id', 'svg-container-base'); + template.innerHTML = [ + '
', + '
' + ].join(''); + document.body.appendChild(template); +}); + +afterEach(function() { + document.body.removeChild(document.body.querySelector('#svg-container-base')); +}); diff --git a/src/js/main.js b/src/js/main.js index 40b51dc..fa14a51 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -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 = [ - '
', - '
', - 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(); }); }()); diff --git a/src/js/parser/javascript.js b/src/js/parser/javascript.js index 8f67cd9..dd66920 100644 --- a/src/js/parser/javascript.js +++ b/src/js/parser/javascript.js @@ -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')); }); } diff --git a/src/js/regexper.js b/src/js/regexper.js index 396811b..fade574 100644 --- a/src/js/regexper.js +++ b/src/js/regexper.js @@ -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 = [ - '
', - '
', - ].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; }); } } diff --git a/template.html b/template.html index f0511b4..731a28b 100644 --- a/template.html +++ b/template.html @@ -24,27 +24,32 @@ -