There have been a lot of times I have needed some information for a bililiteRange plugin that was associated with the underlying element, rather than a specific range. For instance, in bililiteRange(element).text('foo') then bililiteRange(element).undo() the undo needs to know about the previous text change. jQuery has a data() method that attaches an object to the element and you can add fields to that object. Actually, it only attaches an index that points to the actual object, since at least in some browsers the garbage collector had trouble with Javascript objects attached to DOM elements and you ended up with memory leaks. I'm not sure if that is still a problem, but it's an easy enough pattern to implement so I used it.

I didn't want to be jQuery-dependent and I wanted to be able to some more sophisticated things with my data, so I implemented my own. At its simplest, just use:

var data = bililiteRange(element).data();
data.foo = 'bar';
assert(bililiteRange(element).data().foo == 'bar');

bililiteRange(element).data() returns an object that you can add fields to and they will be saved across multiple calls to bililiteRange.

The sophisticated part is for when I want to add information for every bililiteRange. Then I have to add the field to the prototype of the data object. That uses a "global" bililiteRange method, bililiteRange.data() to define the field:

bililiteRange.data(name [,descriptor])

Defines bililiteRange(element).data()[name] for all bililiteRanges by adding name to the data prototype. Without using the descriptor parameter that's pretty useless, but descriptor lets you add some useful parameters. Options include:

value
Set the default value for the field
enumerable

Sets the enumerable property for the field. If false, then for (key in bililiteRange(element).data()) will not include that property, and JSON.stringify(bililiteRange(element).data()) will not list it. Defaults to true.
monitored
Makes the field "monitored", meaning that whenever it is set, an event is triggered:

bililiteRange(element).dispatch({
  type: 'bililiteRangeData',
  bubbles: true,
  detail: {name: name, value: value}
})

The following fields are reserved (in addition to anything in Object.prototype: values is an object that actually stores the values (since internally I use set/get), sourceRange is the range from which the data object was originally defined, toJSON is the method for making JSON.stringify work, and all returns an object that represents all enumerable values, not just ones set for this data object.

The data object has a custom toJSON method, so JSON.stringify(bililiteRange(element).data()) produces the expected results, even though the fields are not simple values. This will only display the fields that have been explicitly set, not the ones that are defined on the prototype. To get all the enumerable fields, use bililiteRange(element).data().all (either in for (...in) or JSON.stringify).

Internet Explorer 8 does not allow setting custom properties on plain Javascript elements, so the enumerable and monitored fields are ignored, and all is always undefined.

Examples

The bililiteRange utilities redefine text() to allow "autoindenting" with a third parameter. We can implement this in an editable element using the data to hold the option:


bililiteRange.data ('autoindent', {value: false}); // default
var rng = bililiteRange(editor);
document.querySelector('#autoindentCheckbox').addEventListener('change', function() { rng.data().autoindent = this.checked });
rng.listen('keydown', function(evt){
	if (evt.keyCode == 13){
		rng.bounds('selection').text('\n','end', rng.data().autoindent).select();
		evt.preventDefault();
	}
});

And fancyText does exactly that. See the demo.

tab-size is inconsistently implemented, but we could use a data property to monitor for changes and set the style based on that:


bililiteRange.data ('tabSize', {monitored: true});
var rng = bililiteRange(editor);
rng.listen('bililiteRangeData', function(evt){
	if (evt.detail && evt.detail.name == 'tabSize') {
		editor.style.tabSize =
		editor.style.mozTabSize = evt.detail.value;
	}
});
rng.data().tabSize = 4;

Leave a Reply


Warning: Undefined variable $user_ID in /home/public/blog/wp-content/themes/evanescence/comments.php on line 75