Adding documentation to regexper.js and some minor tweaks

This commit is contained in:
Jeff Avallone 2015-04-12 16:56:49 -04:00
parent 7de958dc25
commit d918f956ef

View File

@ -1,3 +1,6 @@
// The Regexper class manages the top-level behavior for the entire
// application. This includes event handlers for all user interactions.
import util from './util.js'; import util from './util.js';
import Parser from './parser/javascript.js'; import Parser from './parser/javascript.js';
import _ from 'lodash'; import _ from 'lodash';
@ -17,7 +20,9 @@ export default class Regexper {
this.svgContainer = root.querySelector('#regexp-render'); this.svgContainer = root.querySelector('#regexp-render');
} }
// Event handler for key presses in the regular expression form field.
keypressListener(event) { keypressListener(event) {
// Pressing Shift-Enter displays the expression.
if (event.shiftKey && event.keyCode === 13) { if (event.shiftKey && event.keyCode === 13) {
event.returnValue = false; event.returnValue = false;
if (event.preventDefault) { if (event.preventDefault) {
@ -28,12 +33,16 @@ export default class Regexper {
} }
} }
// Event handler for key presses while focused anywhere in the application.
documentKeypressListener(event) { documentKeypressListener(event) {
// Pressing escape will cancel a currently running render.
if (event.keyCode === 27 && this.running) { if (event.keyCode === 27 && this.running) {
this.running.cancel(); this.running.cancel();
} }
} }
// Event handler for submission of the regular expression. Changes the URL
// hash which leads to the expression being rendered.
submitListener(event) { submitListener(event) {
event.returnValue = false; event.returnValue = false;
if (event.preventDefault) { if (event.preventDefault) {
@ -44,13 +53,14 @@ export default class Regexper {
this._setHash(this.field.value); this._setHash(this.field.value);
} }
catch(e) { catch(e) {
// Most likely failed to set the URL has (probably because the expression // Failed to set the URL hash (probably because the expression is too
// is too long). Turn off the permalink and just show the expression // long). Turn off display of the permalink and just show the expression.
this.permalinkEnabled = false; this.permalinkEnabled = false;
this.showExpression(this.field.value); this.showExpression(this.field.value);
} }
} }
// Event handler for URL hash changes. Starts rendering of the expression.
hashchangeListener() { hashchangeListener() {
var expr = this._getHash(); var expr = this._getHash();
@ -64,6 +74,7 @@ export default class Regexper {
} }
} }
// Binds all event listeners.
bindListeners() { bindListeners() {
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));
@ -71,10 +82,15 @@ export default class Regexper {
window.addEventListener('hashchange', this.hashchangeListener.bind(this)); window.addEventListener('hashchange', this.hashchangeListener.bind(this));
} }
// Set the URL hash. This method exists to facilitate automated testing
// (since changing the URL can throw off most JavaScript testing tools).
_setHash(hash) { _setHash(hash) {
location.hash = encodeURIComponent(hash); location.hash = encodeURIComponent(hash);
} }
// Retrieve the current URL hash. This method is also mostly for supporting
// automated testing, but also does some basic error handling for malformed
// URLs.
_getHash() { _getHash() {
try { try {
return decodeURIComponent(location.hash.slice(1)); return decodeURIComponent(location.hash.slice(1));
@ -84,6 +100,11 @@ export default class Regexper {
} }
} }
// Currently state of the application. Useful values are:
// - `''` - State of the application when the page initially loads
// - `'is-loading'` - Displays the loading indicator
// - `'has-error'` - Displays the error message
// - `'has-results'` - Displays rendered results
set state(state) { set state(state) {
this.root.className = state; this.root.className = state;
} }
@ -92,6 +113,9 @@ export default class Regexper {
return this.root.className; return this.root.className;
} }
// Start the rendering of a regular expression.
//
// - __expression__ - Regular expression to display.
showExpression(expression) { showExpression(expression) {
this.field.value = expression; this.field.value = expression;
this.state = ''; this.state = '';
@ -101,25 +125,32 @@ export default class Regexper {
} }
} }
// Creates a blob URL for linking to a rendered regular expression image.
//
// - __content__ - SVG image markup.
buildBlobURL(content) { buildBlobURL(content) {
var blob = new Blob([content], { type: 'image/svg+xml' }); // Blob object has to stick around for IE, so the instance is stored on the
window.blob = blob; // Blob object has to stick around for IE // `window` object.
return URL.createObjectURL(blob); window.blob = new Blob([content], { type: 'image/svg+xml' });
return URL.createObjectURL(window.blob);
} }
// Update the URLs of the 'download' and 'permalink' links.
updateLinks() { updateLinks() {
var classes = _.without(this.links.className.split(' '), ['hide-download', 'hide-permalink']); var classes = _.without(this.links.className.split(' '), ['hide-download', 'hide-permalink']);
// Create the 'download' image URL.
try { try {
this.download.parentNode.style.display = null; this.download.parentNode.style.display = null;
this.download.href = this.buildBlobURL(this.svgContainer.querySelector('.svg').innerHTML); this.download.href = this.buildBlobURL(this.svgContainer.querySelector('.svg').innerHTML);
} }
catch(e) { catch(e) {
// Blobs or URLs created from them don't work here. // Blobs or URLs created from a blob URL don't work in the current
// Giving up on the download link // browser. Giving up on the download link.
classes.push('hide-download'); classes.push('hide-download');
} }
// Create the 'permalink' URL.
if (this.permalinkEnabled) { if (this.permalinkEnabled) {
this.permalink.parentNode.style.display = null; this.permalink.parentNode.style.display = null;
this.permalink.href = location.toString(); this.permalink.href = location.toString();
@ -130,20 +161,28 @@ export default class Regexper {
this.links.className = classes.join(' '); this.links.className = classes.join(' ');
} }
// Display any warnings that were generated while rendering a regular expression.
//
// - __warnings__ - Array of warning messages to display.
displayWarnings(warnings) { displayWarnings(warnings) {
this.warnings.innerHTML = _.map(warnings, warning => { this.warnings.innerHTML = _.map(warnings, warning => {
return `<li class="oi with-text" data-glyph="warning">${warning}</li>`; return `<li class="oi with-text" data-glyph="warning">${warning}</li>`;
}).join(''); }).join('');
} }
// Render regular expression
//
// - __expression__ - Regular expression to render
renderRegexp(expression) { renderRegexp(expression) {
var parseError = false, var parseError = false,
startTime, endTime; startTime, endTime;
// When a render is already in progress, cancel it and try rendering again
// after a short delay (canceling a render is not instantaneous).
if (this.running) { if (this.running) {
this.running.cancel(); this.running.cancel();
util.wait(10).then(() => { return util.wait(10).then(() => {
return this.renderRegexp(expression); return this.renderRegexp(expression);
}); });
} }
@ -155,7 +194,9 @@ export default class Regexper {
this.running = new Parser(this.svgContainer); this.running = new Parser(this.svgContainer);
return this.running return this.running
// Parse the expression.
.parse(expression) .parse(expression)
// Display any error messages from the parser and abort the render.
.catch(message => { .catch(message => {
this.state = 'has-error'; this.state = 'has-error';
this.error.innerHTML = ''; this.error.innerHTML = '';
@ -165,9 +206,14 @@ export default class Regexper {
throw message; throw message;
}) })
// When parsing is successful, render the parsed expression.
.then(parser => { .then(parser => {
return parser.render(); return parser.render();
}) })
// Once rendering is complete:
// - Update links
// - Display any warnings
// - Track the completion of the render and how long it took
.then(() => { .then(() => {
this.state = 'has-results'; this.state = 'has-results';
this.updateLinks(); this.updateLinks();
@ -177,6 +223,9 @@ export default class Regexper {
endTime = new Date().getTime(); endTime = new Date().getTime();
window._gaq.push(['_trackTiming', 'visualization', 'total time', endTime - startTime]); window._gaq.push(['_trackTiming', 'visualization', 'total time', endTime - startTime]);
}) })
// Handle any errors that happened during the rendering pipeline.
// Swallows parse errors and render cancellations. Any other exceptions
// are allowed to continue on to be tracked by the global error handler.
.catch(message => { .catch(message => {
if (message === 'Render cancelled') { if (message === 'Render cancelled') {
window._gaq.push(['_trackEvent', 'visualization', 'cancelled']); window._gaq.push(['_trackEvent', 'visualization', 'cancelled']);
@ -187,6 +236,8 @@ export default class Regexper {
throw message; throw message;
} }
}) })
// Finally, mark rendering as complete (and pass along any exceptions
// that were thrown).
.then( .then(
() => { () => {
this.running = false; this.running = false;