Adding support for canceling a parse/render

This commit is contained in:
Jeff Avallone 2014-12-17 19:24:33 -05:00
parent bc351bc9fc
commit c64e48946e
4 changed files with 147 additions and 53 deletions

View File

@ -96,6 +96,41 @@ describe('regexper.js', function() {
}); });
describe('#documentKeypressListener', function() {
beforeEach(function() {
this.event = document.createEvent('Event');
spyOn(parser, 'cancel');
});
describe('when the keyCode is not 27 (Escape)', function() {
beforeEach(function() {
this.event.keyCode = 42;
});
it('does not cancel the parser', function() {
this.regexper.documentKeypressListener(this.event);
expect(parser.cancel).not.toHaveBeenCalled();
});
});
describe('when the keyCode is 27 (Escape)', function() {
beforeEach(function() {
this.event.keyCode = 27;
});
it('cancels the parser', function() {
this.regexper.documentKeypressListener(this.event);
expect(parser.cancel).toHaveBeenCalled();
});
});
});
describe('#submitListener', function() { describe('#submitListener', function() {
beforeEach(function() { beforeEach(function() {
@ -168,9 +203,16 @@ describe('regexper.js', function() {
describe('#bindListeners', function() { describe('#bindListeners', function() {
beforeEach(function() {
spyOn(this.regexper, 'keypressListener');
spyOn(this.regexper, 'submitListener');
spyOn(this.regexper, 'updatePercentage');
spyOn(this.regexper, 'documentKeypressListener');
spyOn(this.regexper, 'hashchangeListener');
});
it('binds #keypressListener to keypress on the text field', function() { it('binds #keypressListener to keypress on the text field', function() {
spyOn(this.regexper.field, 'addEventListener'); spyOn(this.regexper.field, 'addEventListener');
spyOn(this.regexper, 'keypressListener');
this.regexper.bindListeners(); this.regexper.bindListeners();
expect(this.regexper.field.addEventListener).toHaveBeenCalledWith('keypress', jasmine.any(Function)); expect(this.regexper.field.addEventListener).toHaveBeenCalledWith('keypress', jasmine.any(Function));
@ -180,7 +222,6 @@ describe('regexper.js', function() {
it('binds #submitListener to submit on the form', function() { it('binds #submitListener to submit on the form', function() {
spyOn(this.regexper.form, 'addEventListener'); spyOn(this.regexper.form, 'addEventListener');
spyOn(this.regexper, 'submitListener');
this.regexper.bindListeners(); this.regexper.bindListeners();
expect(this.regexper.form.addEventListener).toHaveBeenCalledWith('submit', jasmine.any(Function)); expect(this.regexper.form.addEventListener).toHaveBeenCalledWith('submit', jasmine.any(Function));
@ -190,17 +231,24 @@ describe('regexper.js', function() {
it('binds #updatePercentage to updateStatus on the root', function() { it('binds #updatePercentage to updateStatus on the root', function() {
spyOn(this.regexper.root, 'addEventListener'); spyOn(this.regexper.root, 'addEventListener');
spyOn(this.regexper, 'updatePercentage');
this.regexper.bindListeners(); this.regexper.bindListeners();
expect(this.regexper.root.addEventListener).toHaveBeenCalledWith('updateStatus', jasmine.any(Function)); expect(this.regexper.root.addEventListener).toHaveBeenCalledWith('updateStatus', jasmine.any(Function));
this.regexper.root.addEventListener.calls.mostRecent().args[1](); this.regexper.root.addEventListener.calls.first().args[1]();
expect(this.regexper.updatePercentage).toHaveBeenCalled(); expect(this.regexper.updatePercentage).toHaveBeenCalled();
}); });
it('binds #documentKeypressListener to keyup on the root', function() {
spyOn(this.regexper.root, 'addEventListener');
this.regexper.bindListeners();
expect(this.regexper.root.addEventListener).toHaveBeenCalledWith('keyup', jasmine.any(Function));
this.regexper.root.addEventListener.calls.mostRecent().args[1]();
expect(this.regexper.documentKeypressListener).toHaveBeenCalled();
});
it('binds #hashchangeListener to hashchange on the window', function() { it('binds #hashchangeListener to hashchange on the window', function() {
spyOn(window, 'addEventListener'); spyOn(window, 'addEventListener');
spyOn(this.regexper, 'hashchangeListener');
this.regexper.bindListeners(); this.regexper.bindListeners();
expect(window.addEventListener).toHaveBeenCalledWith('hashchange', jasmine.any(Function)); expect(window.addEventListener).toHaveBeenCalledWith('hashchange', jasmine.any(Function));
@ -262,6 +310,21 @@ describe('regexper.js', function() {
}); });
describe('when the parser is cancelled', function() {
beforeEach(function(done) {
spyOn(this.regexper, 'updateLinks');
this.regexper.showExpression('example expression');
this.renderPromise.reject('Render cancelled');
setTimeout(done, 100);
});
it('clears the state', function() {
expect(this.regexper.state).toEqual('');
});
});
}); });
}); });

View File

@ -41,8 +41,14 @@ parser.Parser.RepeatSpec = { module: RepeatSpec };
parser.parse = (parse => { parser.parse = (parse => {
return function() { return function() {
Subexp.resetCounter(); Subexp.resetCounter();
Node.reset();
return parse.apply(this, arguments); return parse.apply(this, arguments);
}; };
})(parser.parse); })(parser.parse);
parser.cancel = () => {
Node.cancelRender = true;
};
export default parser; export default parser;

View File

@ -1,9 +1,6 @@
import _ from 'lodash'; import _ from 'lodash';
import Q from 'q'; import Q from 'q';
var renderCounter = 0,
maxCounter = 0;
export default class Node { export default class Node {
constructor(textValue, offset, elements, properties) { constructor(textValue, offset, elements, properties) {
this.textValue = textValue; this.textValue = textValue;
@ -66,57 +63,69 @@ export default class Node {
return this.container.transform(matrix); return this.container.transform(matrix);
} }
renderLabel(text) { deferredStep() {
var deferred = Q.defer(), var deferred = Q.defer(),
group = this.container.group() result = arguments;
setTimeout(() => {
if (Node.cancelRender) {
deferred.reject('Render cancelled');
} else {
deferred.resolve.apply(this, result);
}
}, 1);
return deferred.promise;
}
renderLabel(text) {
var group = this.container.group()
.addClass('label'), .addClass('label'),
rect = group.rect(), rect = group.rect(),
text = group.text(0, 0, _.flatten([text])); text = group.text(0, 0, _.flatten([text]));
setTimeout(deferred.resolve.bind(deferred, group)); return this.deferredStep(group)
deferred.promise.then(() => { .then(group => {
var box = text.getBBox(), var box = text.getBBox(),
margin = 5; margin = 5;
text.transform(Snap.matrix() text.transform(Snap.matrix()
.translate(margin, box.height / 2 + 2 * margin)); .translate(margin, box.height / 2 + 2 * margin));
rect.attr({ rect.attr({
width: box.width + 2 * margin, width: box.width + 2 * margin,
height: box.height + 2 * margin height: box.height + 2 * margin
});
return group;
}); });
});
return deferred.promise;
} }
startRender() { startRender() {
renderCounter++; Node.renderCounter++;
} }
doneRender() { doneRender() {
var evt, deferred = Q.defer(); var evt;
if (maxCounter === 0) { if (Node.maxCounter === 0) {
maxCounter = renderCounter; Node.maxCounter = Node.renderCounter;
} }
renderCounter--; Node.renderCounter--;
evt = document.createEvent('Event'); evt = document.createEvent('Event');
evt.initEvent('updateStatus', true, true); evt.initEvent('updateStatus', true, true);
evt.detail = { evt.detail = {
percentage: (maxCounter - renderCounter) / maxCounter percentage: (Node.maxCounter - Node.renderCounter) / Node.maxCounter
}; };
document.body.dispatchEvent(evt); document.body.dispatchEvent(evt);
if (renderCounter === 0) { if (Node.renderCounter === 0) {
maxCounter = 0; Node.maxCounter = 0;
} }
setTimeout(deferred.resolve.bind(deferred), 1); return this.deferredStep();
return deferred.promise;
} }
render(container) { render(container) {
@ -191,8 +200,7 @@ export default class Node {
} }
renderLabeledBox(label, content, options) { renderLabeledBox(label, content, options) {
var deferred = Q.defer(), var label = this.container.text()
label = this.container.text()
.addClass([this.type, 'label'].join('-')) .addClass([this.type, 'label'].join('-'))
.attr({ .attr({
text: label text: label
@ -211,26 +219,30 @@ export default class Node {
this.container.prepend(label); this.container.prepend(label);
this.container.prepend(box); this.container.prepend(box);
setTimeout(deferred.resolve); return this.deferredStep()
deferred.promise.then(() => { .then(() => {
var labelBox = label.getBBox(), var labelBox = label.getBBox(),
contentBox = content.getBBox(); contentBox = content.getBBox();
label.transform(Snap.matrix() label.transform(Snap.matrix()
.translate(0, labelBox.height)); .translate(0, labelBox.height));
box box
.transform(Snap.matrix() .transform(Snap.matrix()
.translate(0, labelBox.height)) .translate(0, labelBox.height))
.attr({ .attr({
width: Math.max(contentBox.width + options.padding * 2, labelBox.width), width: Math.max(contentBox.width + options.padding * 2, labelBox.width),
height: contentBox.height + options.padding * 2 height: contentBox.height + options.padding * 2
}); });
content.transform(Snap.matrix() content.transform(Snap.matrix()
.translate(box.getBBox().cx - contentBox.cx, labelBox.height + options.padding)); .translate(box.getBBox().cx - contentBox.cx, labelBox.height + options.padding));
}); });
return deferred.promise;
} }
}; };
Node.reset = () => {
Node.renderCounter = 0;
Node.maxCounter = 0;
Node.cancelRender = false;
};

View File

@ -32,6 +32,12 @@ export default class Regexper {
} }
} }
documentKeypressListener(event) {
if (event.keyCode === 27) {
parser.cancel();
}
}
submitListener(event) { submitListener(event) {
event.returnValue = false; event.returnValue = false;
if (event.preventDefault) { if (event.preventDefault) {
@ -62,6 +68,7 @@ export default class Regexper {
this.field.addEventListener('keypress', this.keypressListener.bind(this)); this.field.addEventListener('keypress', this.keypressListener.bind(this));
this.form.addEventListener('submit', this.submitListener.bind(this)); this.form.addEventListener('submit', this.submitListener.bind(this));
this.root.addEventListener('updateStatus', this.updatePercentage.bind(this)); this.root.addEventListener('updateStatus', this.updatePercentage.bind(this));
this.root.addEventListener('keyup', this.documentKeypressListener.bind(this));
window.addEventListener('hashchange', this.hashchangeListener.bind(this)); window.addEventListener('hashchange', this.hashchangeListener.bind(this));
} }
@ -92,6 +99,12 @@ export default class Regexper {
.then(() => { .then(() => {
this.state = 'has-results'; this.state = 'has-results';
this.updateLinks(); this.updateLinks();
}, (message) => {
if (message === 'Render cancelled') {
this.state = '';
} else {
throw message;
}
}) })
.done(); .done();
} }