JavaScript Behavior Sheets: an experiment
Here’s a little experiment. I’m really after feedback on this one as I’m trying to decide whether this is a good idea. It’s also entirely possible somebody else did this before. That would be good feedback too. Anyway, here it is.
Despite its shortcomings, CSS has a number of features that make it very compelling. First, it decouples styling from markup. Second, its selector syntax is simple, yet reasonably powerful.
So we have semantic markup on the one hand, and styles on the other hand, and the only coupling between the two is the selectors in the stylesheet.
In Ajax applications, there is a third kind of entity in the mix, JavaScript behavior. There are of course ways to decouple the script behavior from the markup, which are usually referred to as unobtrusive JavaScript. jQuery also introduced back in 2005 a way to associate script behavior with the DOM using the same selector syntax that CSS uses.
But way before jQuery, Internet Explorer 5 enabled developers to specify behavior in stylesheets. You could do this for example:
.hilite { behavior:url(hilite.htc) }
This was a pretty neat idea at the time despite the challenges that came with it (in terms of performance for example), but it was never adopted by the other browsers despite having been submitted to W3C. The feature was never very widely used. There *is* a Firefox implementation of HTC behaviors but it doesn’t seem to have helped much in terms of adoption of this feature.
And one thing that bothers me with that idea is that while it does decouple behavior from markup, it also couples behavior to styling at least in location. Putting styles and behavior in the same file, in retrospect, looks like one step forward, and one step backwards. Sure, you could always use two separate files but the system did nothing to encourage you to do that.
What I’m trying to do here is separate styling, markup *and* behavior, leverage selectors in a nice, declarative way à la CSS and still work on all modern browsers. The coupling mechanism between style and markup (CSS selectors), is reasonably good and is already known by all Web developers so it remains a natural choice as jQuery showed clearly. Let’s see if one can express behavior in a way that is close to the already well-known CSS pattern.
In this implementation, it is possible to add event handlers and Microsoft Ajax behaviors from a “JavaScript Behavior Sheet” which can be embedded in the page or be loaded from a separate file using a modified script tag:
<script type="text/behavior" src="BehaviorSheet.jss"></script>
The contents of the tag or file is in the simple JSON notation for an object, omitting the curly braces:
"input[type=text].nomorethanfive": { click: function(e) { alert("You clicked input #" + e.target.id); }, "Bleroy.Sample.CharCount": { maxLength: 5, overflow: function(source, args) { $(source.get_element()).jFade({ property: 'background', start: 'FFFF00', end: 'FFFFFF', steps: 25, duration: 30 }); } } }
The top-level entities that can be found in there are the CSS selectors (note the quotes that are a departure from CSS notation but made the prototyping so much simpler). Each selector is associated with an object that contains event and object definitions.
The event definitions consist in the event name and the handler to associate with it:
click: function(e) { alert("You clicked input #" + e.target.id); },
The implementation of this feature uses the new live events from jQuery. The result of that definition is that clicking on any input of type text with the class “nomorethanfive” will display an alert giving the id of the input that was clicked. That is a pretty efficient way to hook up events to multiple elements…
The behavior instantiation specifies the class to instantiate, “Bleroy.Sample.CharCount” and lists the properties, fields and events to set (here, the maxLength property is set to 5 and the overflow event is hooked to a function that flashes the element’s background yellow):
"Bleroy.Sample.CharCount": { maxLength: 5, overflow: function(source, args) { $(source.get_element()).jFade({ property: 'background', start: 'FFFF00', end: 'FFFFFF', steps: 25, duration: 30 }); } }
Here’s what the page looks like:
I should point out that while the events will handle DOM mutations (such as adding new elements that match the selector) just fine, component instantiation won’t in this implementation, which is a limitation that is quite hard to work around in current browsers.
So here it is. All the code for this is available from the link below (contains jFade, code licensed under MIT: jQuery, and code under MS-PL: Microsoft Ajax and my own code), with some tests written with RST.js. So what do you think?
http://weblogs.asp.net/blogs/bleroy/Samples/BehaviorSheet.zip
UPDATE: interestingly, the intent behind Reglib by Greg Reimer is pretty close to this and he even uses the words “Behavior Sheets”.
UPDATE: Stuart Langridge did something very close to declarative events as they are done here back in November 2003. Thanks to Clayton for pointing that out. One should note that the limitations of the time are no longer preventing the technique from reaching its full potential.
UPDATE: Brian mentions in comment this proposal back from 1998 that looked pretty much like this... http://www.w3.org/TR/NOTE-AS