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() {
beforeEach(function() {
@ -168,9 +203,16 @@ describe('regexper.js', 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() {
spyOn(this.regexper.field, 'addEventListener');
spyOn(this.regexper, 'keypressListener');
this.regexper.bindListeners();
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() {
spyOn(this.regexper.form, 'addEventListener');
spyOn(this.regexper, 'submitListener');
this.regexper.bindListeners();
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() {
spyOn(this.regexper.root, 'addEventListener');
spyOn(this.regexper, 'updatePercentage');
this.regexper.bindListeners();
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();
});
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() {
spyOn(window, 'addEventListener');
spyOn(this.regexper, 'hashchangeListener');
this.regexper.bindListeners();
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 => {
return function() {
Subexp.resetCounter();
Node.reset();
return parse.apply(this, arguments);
};
})(parser.parse);
parser.cancel = () => {
Node.cancelRender = true;
};
export default parser;

View File

@ -1,9 +1,6 @@
import _ from 'lodash';
import Q from 'q';
var renderCounter = 0,
maxCounter = 0;
export default class Node {
constructor(textValue, offset, elements, properties) {
this.textValue = textValue;
@ -66,57 +63,69 @@ export default class Node {
return this.container.transform(matrix);
}
renderLabel(text) {
deferredStep() {
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'),
rect = group.rect(),
text = group.text(0, 0, _.flatten([text]));
setTimeout(deferred.resolve.bind(deferred, group));
deferred.promise.then(() => {
var box = text.getBBox(),
margin = 5;
return this.deferredStep(group)
.then(group => {
var box = text.getBBox(),
margin = 5;
text.transform(Snap.matrix()
.translate(margin, box.height / 2 + 2 * margin));
text.transform(Snap.matrix()
.translate(margin, box.height / 2 + 2 * margin));
rect.attr({
width: box.width + 2 * margin,
height: box.height + 2 * margin
rect.attr({
width: box.width + 2 * margin,
height: box.height + 2 * margin
});
return group;
});
});
return deferred.promise;
}
startRender() {
renderCounter++;
Node.renderCounter++;
}
doneRender() {
var evt, deferred = Q.defer();
var evt;
if (maxCounter === 0) {
maxCounter = renderCounter;
if (Node.maxCounter === 0) {
Node.maxCounter = Node.renderCounter;
}
renderCounter--;
Node.renderCounter--;
evt = document.createEvent('Event');
evt.initEvent('updateStatus', true, true);
evt.detail = {
percentage: (maxCounter - renderCounter) / maxCounter
percentage: (Node.maxCounter - Node.renderCounter) / Node.maxCounter
};
document.body.dispatchEvent(evt);
if (renderCounter === 0) {
maxCounter = 0;
if (Node.renderCounter === 0) {
Node.maxCounter = 0;
}
setTimeout(deferred.resolve.bind(deferred), 1);
return deferred.promise;
return this.deferredStep();
}
render(container) {
@ -191,8 +200,7 @@ export default class Node {
}
renderLabeledBox(label, content, options) {
var deferred = Q.defer(),
label = this.container.text()
var label = this.container.text()
.addClass([this.type, 'label'].join('-'))
.attr({
text: label
@ -211,26 +219,30 @@ export default class Node {
this.container.prepend(label);
this.container.prepend(box);
setTimeout(deferred.resolve);
deferred.promise.then(() => {
var labelBox = label.getBBox(),
contentBox = content.getBBox();
return this.deferredStep()
.then(() => {
var labelBox = label.getBBox(),
contentBox = content.getBBox();
label.transform(Snap.matrix()
.translate(0, labelBox.height));
label.transform(Snap.matrix()
.translate(0, labelBox.height));
box
.transform(Snap.matrix()
.translate(0, labelBox.height))
.attr({
width: Math.max(contentBox.width + options.padding * 2, labelBox.width),
height: contentBox.height + options.padding * 2
});
box
.transform(Snap.matrix()
.translate(0, labelBox.height))
.attr({
width: Math.max(contentBox.width + options.padding * 2, labelBox.width),
height: contentBox.height + options.padding * 2
});
content.transform(Snap.matrix()
.translate(box.getBBox().cx - contentBox.cx, labelBox.height + options.padding));
});
return deferred.promise;
content.transform(Snap.matrix()
.translate(box.getBBox().cx - contentBox.cx, labelBox.height + options.padding));
});
}
};
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) {
event.returnValue = false;
if (event.preventDefault) {
@ -62,6 +68,7 @@ export default class Regexper {
this.field.addEventListener('keypress', this.keypressListener.bind(this));
this.form.addEventListener('submit', this.submitListener.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));
}
@ -92,6 +99,12 @@ export default class Regexper {
.then(() => {
this.state = 'has-results';
this.updateLinks();
}, (message) => {
if (message === 'Render cancelled') {
this.state = '';
} else {
throw message;
}
})
.done();
}