Archive for the ‘Javascript’ Category

Prism is fast, generally fast enough to use for a live syntax highlighter. But for large texts (multiple kilocharacters) having the text re-highlighted with every input bogs down, and I can type faster than the text can show up. I remember old word processors that were like that, back in the dark ages of computerdom. But it's unacceptable today.

What I need to do is "debounce" the input events, so they aren't piling up and creating a backlog of highlighting that is going to be redone with the next event anyway. John Hann has a nice little routine that I simplified into:

function debounce (func, threshold){
	if (!threshold) return func; // no debouncing
	var timeout;
	return function(){
		var self = this, args = arguments;
		clearTimeout(timeout);
		timeout = setTimeout(function(){
			func.apply(self, args);
		}, threshold);
	};
}

Basically, it uses setTimeout to delay the application of the function, and resets the timer every time it's called. Use it as var debouncedFunc = debounce(func, 100);

This is a plugin for bililiteRange that implements a simple undo/redo stack. It listens for input events (so to use it in IE<10 you need some kind of polyfill) and records the old text each time, so it's pretty memory-inefficient, but it works for what I want to do.

See the code on github.

See a demo with the Prism editor (control-z to undo, control-y to redo).

Usage is simple:

bililiteRange(element).undo(0); // initializes the event listener without trying to undo anything; safe to call multiple times
bililiteRange(element).undo(); // restores the text from the last undo point
bililiteRange(element).undo(-1); // redo--undoes the last undo

bililiteRange(element).undo(n) for any n works and will undo (for positive n) or redo (for negative n) that many times, though I'm not sure how useful that would be.

There are convenience methods to be used as event handlers:

bililiteRange.undo(event);
bililiteRange.redo(event);

So a simple keyboard shortcut handler would be:

		bililiteRange(editor).undo(0); // initialize
		editor.addEventListener ('keydown', function(evt){
			// control z
			if (evt.ctrlKey && evt.which == 90) bililiteRange.undo(evt);
			// control y
			if (evt.ctrlKey && evt.which == 89) bililiteRange.redo(evt);
		});

or, if you use my hotkeys jQuery plugin,


$(editor).on('keydown', {keys: '^z'}, bililiteRange.undo);
$(editor).on('keydown', {keys: '^y'}, bililiteRange.redo);

Hope that this is useful to someone.

It tries to be sophisticated about typing, and bunches all typing done in a row into one, so that undo undoes the entire line. Backspacing, moving the insertion point, or typing a newline starts a new undo point.

Restoring the insertion point isn't as sophisticated as in a real word processor yet; moving the insertion point then typing then undoing moves the insertion point back to where is was originally, not to the start of typing.

As luck would have it, right after I wrote about synchronous vs. asynchronous event handlers, I found exactly that problem in by bililiteRange code. It uses dispatchEvent to fire an input event when text is inserted, but that fires synchronously, so that the event handlers run before the bililiteRange.text() has fully run. I ended up having to wrap the dispatchEvent in a setTimeout to force it to be asynchronous.

The bililiteRange.js code is now at version 1.7.

As promised, I have updated the status plugin to use Promises. Unfortunately, that required a small change to the parameters. Since I doubt anyone is using it, that will not likely affect anyone but me. The version is now 1.1.

There's a subtlety in using Promises with event handlers or any code that's executed asynchronously.

Promises are supposed to be used for future values, with the reject handler acting like an exception handler, so that the Promise constructor actually catches real exceptions and turns them into rejects (from the polyfill; I assume the browser-implemented code works similarly):

try {
  resolver(resolvePromise, rejectPromise);
} catch(e) {
  rejectPromise(e);
}

But asynchronous code runs outside the execution unit that runs resolver(), so any exceptions there have to be caught explicitly:

<button id=one>Click Me</button>
<button id=two>Don't Click Me</button>
<script>
var promise = new Promise(function(resolve, reject){
	document.querySelector('#one').onclick = function(){
		resolve ('You Clicked!');
	};
	document.querySelector('#two').onclick = function(){
		throw new Error ('You Clicked!');
	};
});

promise.then(function(msg){
	alert('Button clicked: '+msg);
}, function(err){
	alert('Error caught:'+err.message);
});
</script>

Clicking button two gives an uncaught exception error, not the alert from the then(). You have to catch the exceptions:

	document.querySelector('#two').onclick = function(){
		try {
			throw new Error ('You Clicked!');
		} catch (e) {
			reject(e);
		}
	};

or resolve to a new promise that can catch the error:

	document.querySelector('#two').onclick = function(){
		resolve(new Promise(function (){
			throw new Error ('You Clicked!');
		}));
	};

The other subtlety (which is obvious from the definition of a Promise) is that Promises are once-only; in the above code, clicking either button settles the Promise and further clicking does nothing. Event handlers can be called multiple times, so you have to think about what you intend.

Just in time for me to start thinking about Javascript Promises, comes along an official API, along with a polyfill that basically re-implements rsvp.js with the "official" terminology.

Now for some experimenting (and rewriting jquery.status.js to use real Promises).

It seems from all the discussion that jQuery $.Deferred are not and will not be real Promises, but they can be Promise.cast($.Deferred()) into one. Note that to use that, you need to $.Deferred().resolve() on the jQuery object, but attach Promise.then()'s to the Promise.

Prism is a great Javascript syntax highlighter, and it's fast enough to be used on the fly for active editing; Lea Verou (the author) does this with Dabblet. Combined with bililiteRange to handle some of the subtleties of dealing with contenteditable elements, a syntax-highlighting editor is relatively easy.

Simplest usage: bililiteRange.fancyText(element, Prism.highlightelement);.

See the demo.

See the code on github.

There's a fair amount of flakiness; contenteditable elements are still handled inconsistently. Newlines are changed into actual elements (Firefox adds <br>s; Chrome adds a whole new <div>), even though a <pre> doesn't need that; it would interpret newlines the way we want. So we have to intercept those and just insert the newline. Firefox insists on changing newlines into <br>'s even in pasted text, and Chrome's clipboard implementation includes the carriage return character, so we have to intercept paste events and insert the text appropriately mangled.

Continue reading ‘Simple syntax-highlighting editor with Prism’ »

I've been looking at Promise/A+ as a way to abstract future values, or values that depend on something the user will do later, specifically Yehuda Katz's rsvp.js, and came across Domenic Denicola's You're Missing the Point of Promises, which got me looking at Oliver Steele's Minimizing Code Paths in Asychronous Code. Now I have another consideration in my code that I never thought about before.

Continue reading ‘Thinking about synchonicity’ »

Looking back at my line numbering plugin for Prism, I realized that it's working too hard by manipulating the DOM. Prism works by string manipulation anyway, so there's no harm in using that to wrap the lines rather than searching through elements. The code is now only 6 lines long. It still uses CSS and ::before pseudoelements to show the line numbers.

I wanted to be able to wrap arbitrary text with HTML elements, so I added a wrap method to my bililiteRange routines. Now I can do something like bililiteRange(el).find(/foo/).wrap(document.createElement('strong')) and I have instant highlighting!

It obviously can only work if the range is defined on an actual HTML element, not on the text of a <input> or <text> element (it throws an error if you try).

For standards-based browsers, there is a simple surroundContents method that does it in one step. Note that it uses the actual node passed in, so if you need to reuse it, remember to clone it.

IE < 10 doesn't have anything so nice, so I have to hack it by extracting and re-inserting the HTML with htmltext and pasteHTML.

It is possible to attempt to create invalid HTML; for instance wrap a range containing a <p> with a <span> element. Depending on the browser, this may throw an error or create undefined results.