Behaviors, simple jQuery extension.

November 14, 2010

Behaviors as an entity is a declarative way to assign/bind scripting methods to DOM elements.

We can think that browsers have following declarations in their default CSS declarations:

input[type=text]   { binding: TextEditorImpl; }
input[type=button] { binding: ButtonImpl; }
select             { binding: SelectImpl; }
...

So when we define <input type="text" /> in our markup we declare that the element will behave as a text editor – it will have set of all needed methods and will generate all associated events.  

It would be nice if in script we would be able to define our own behaviors for classes of DOM elements too.

As an example: blog article may have hyperlinks inside and particular blog engine may require some special behavior/reaction assigned to all hyperlinks in the article. Ideally such declaration should like this:

#content div.article a[href][title] 
{
  color: orange; // ui style
  behavior: LinkWithSmartTooltip; // behavioral style
}

In Sciter [1] that is an embeddable HTML/CSS/TIScript engine I have a luxury to step beyond W3C specifications so I’ve implemented the Behaviors in the way as I think they should be:

Behaviors in the Sciter engine

I have added the prototype attribute to my implementation of CSS:

some-CSS-selector 
{
   prototype: SomeBehaviorClass [ url(of a script file) ];
   ...
}

When the engine assignes CSS styles to elements it also tries to find class named SomeBehaviorClass. If such class is found then the element gets "subclassed" by the class. Technically the subclassing means that for all DOM elements that satisfy some-CSS-selector both these statements are true:

element instanceof SomeBehaviorClass; 
element instanceof Element; // Element is a super class of all DOM elements.

The SomeBehaviorClass looks in TIScript like this:

class SomeBehaviorClass: Behavior
{
   function attached() {} // constructor, sort of
   ...
}

The attached method plays a role of a constructor function in realm of Behaviors. It is called when particular element gets the behavior with this variable referring to the element. All this is I would say is pretty human readable an transparent.

Ok, back to the reality of the Web. Below is my attempt to define similar functionality using jQuery:

Behaviors for conventional browsers,  jQuery extension.

First of all here is my initial implementation of the behavior functionality: jquery-behaviors.js. It is pretty simple – near 75 lines of code.

It allows to declare behaviors on elements by using (a) class DOM attribute like this (purely in markup):

<span class="behavior uix-slider" /> 
<input class="behavior uix-date" />

and/or by (b) declaration of selector rules in script code:

$.behaviors.selector("ul.ext-list > li", MyExtListItem );

Later case allows to add behaviors non-intrusively – to any existing markup.

The Behaviors implementation above introduces three methods:

  1. $.behaviors.add( name, behaviorObj ) – add named behavior that can be used in "a" case above – in class names.
  2. $.behaviors.selector( selectorStr, behaviorObj ) – add "CSS selector -> behavior" association – case (b) above.
  3. $.fn.behaviors() – that is a plug-in that extends jQuery object wrapping set of elements. It used after calls of DOM mutating methods to assign behaviors to DOM elements:
    $("#update-panel").html("....").behaviors();

The behaviorObj above is the behavior implementation per se. It is a plain JavaScript object that defines set of methods and properties that will be mixed into the DOM element property map.

Here is a typical structure of some behavior named "x-checkbox" (see it’s demo here …) :

$.behaviors.add( "x-checkbox", 
{
  $attached: function(params) { ... },
  $value: function(v) { ... },
  $clear: function() { ... }
});

Function $attached here has special meaning – it gets called by the Behaviors engine when the element gets this behavior attached. The params here is a parsed version of the params DOM attribute that allows to parametrize instance of the behavior for the element. For example particular instances of a slider may have different initial settings:

<span id="first" class="behavior uix-slider" 
     params="min:0, max:100, values:[15,50]" />
<span id="second" class="behavior uix-slider" 
     params="min:10, max:200, value:50" />

Input behaviors, concept of the value. The "x-form" behavior.

In principle there are two distinct types of behaviors:

  • input behaviors – behaviors of elements that have a concept of the value. Input elements have at least these two methods:
    • method $value(v) – getter/setter of the value.
    • method $clear() – clear the value – revert it to the initial, markup declared state.
  • UI behaviors – behaviors implementing various UI effects like "click here – expand/collapse there".

The $value and $clear methods are used by the x-form behavior for gathering and setting values of input elements it contains. The x-form element is by itself is an input element (compound one). Its value is a map of name/value pairs – values of standard inputs and elements that have input behaviors attached. Behavior x-form can be assigned to any container that has inputs and call of its method $("#my-form")[0].$value() will give collection of values that are e.g. ready to be send over the AJAX.

The x-form and individual input behaviors may also implement concept of $validate() – that is not implemented yet but planned.

Demos

Here is couple of demonstrations of the approach:

  • jq-ui.htm – demo of jQuery-UI widgets wrapped into input and UI behaviors (implementation: jquery-ui-behaviors.js).  Also demonstrates use of x-form to gather/set/clear form data and dynamic html loading with behavior assignment.
  • std-behaviors.htm – purely declarative sample. Demonstrates x-checkbox behavior – is a input and UI element that can be bound declaratively with show/hide of "buddy" elements. Uses std-behaviors.js – implementation of the x-form and x-checkbox.

References

  1. The Sciter – an embeddable HTML/CSS/Scripting engine;
  2. TIScript language – JavaScript++ if you wish. Used in Sciter.
  3. Behaviors in Sciter, part I, part II and part III

7 Comments

  1. Neat… though I wonder if you should be using a HTML5 “data” attribute like data-behavior=”uix-tabs” and data-behavior-options=”json, data” instead. Or simply pinch the type attribute from inputs for all other elements you want to add behaviors to. I’d also suggest using the term “options” as opposed to params as that seems to be the terminology for most widget/plugin configurations in JQuery et al.

    Anyway back to the point I was trying to make. I feel that separating the UI behavior mapping from the CSS layout/look n feel seems more “correct”. eg There can be multiple buttons all behaving the same but can look different depending on where they are on the page….

    I think CSS is a rather “confused” standard already so I think maybe behaviors would increase that complexity? Also at this stage I don’t see your CSS! style markup (which I really like) arriving in the official standards world too soon to help that… Although that said, a sort of dynamically compiled CSS! stylesheet (much like Less CSS does) is an interesting idea.

    Comment by Kent — November 22, 2010 @ 4:56 am

  2. To Kent:

    To be honest <div data-behavior=”uix-tabs”> looks a bit scary. Why “data”?

    Usually people associate behaviors with classes of elements. I think that <div class=”behavior uix-tabs”> is conceptually better – closer to the nature of this entity. It means: the element belongs to class of elements having some behavior and in particular behavior named “uix-tabs”.

    In contrary data-options="json alike data" looks OK and will make validator happy.

    As of “CSS layout/look-n-feel” … “look” is a visual style and “feel” is a behavioral style.
    I see nothing wrong with that (or I did not get the meaning of that point)

    And I see no problems with: “There can be multiple buttons all behaving the same but can look different depending on where they are on the page…”

    You can define: <div class=”behavior uix-tabs something-else”> and use .something-else class for styling.
    Or you can use

    #content .uix-tabs { ... }
    #sidebar .uix-tabs { ... }
    

    for placement specific styles.

    And about CSS in general. It is surprisingly pretty well extensible.
    I’ve managed to add @set,@const and flex units/flows to it without breaking anything in principle.
    @set, @const are all about “Less CSS”. My @set’s are conceptually more powerful though.

    As of CSSS!… it is abbreviation “CSS Script!” – functional extension of CSS.
    In plain old CSS you are able to define:

    input:checked + div { display:none; }
    

    that is sort of behavioral definition: click on that checkbox will trigger visibility of the next element to it.
    But the problem is that it works only for the next element. CSSS! allows you to extend that binding on arbitrary elements.

    input.bound
    { 
      value-changed!: $(div.bound):collapsed = self:value; /* csss! property */
    }
    div.bound:collapsed
    { 
      display:none;
    }
    

    There are many forms of doing such bindings. For some of us the CSSS! is not acceptable.

    In HTMLayout/Sciter you have an option to use native mechanism of behaviors for that, something like:

    input.bound[type="checkbox"]
    { 
      behavior: bound-toggler; /* toggles visibility of bound element */
      -bound-with: selector(div.bound); /* custom CSS property, behavior specific */
    }
    div.bound:collapsed
    { 
      display:none;
    }
    

    That is more declarative but requires special behavior (native or in script). Whichever is better is a matter of personal taste.

    In any case ability to define behaviors in CSS is a good thing (TM).
    E.g. @media touch-screen {} may require different set of behaviors from what is suitable for @media screen {} or @media tv {}.

    Comment by Andrew — November 22, 2010 @ 8:12 pm

  3. Ok. I get your arguments but I’m not overly convinced.

    From what I’m reading people have pretty much moved away from putting “settings” inside class attributes and I think you are basically doing this. Defining “behavior ui-tab” is pretty much the equivalent of the old work with IE6 days of doing class=”behavior(ui-tab)” or class=”behavior=ui-tab” (You’re just using a space instead of an operator)

    I suggested data-behavior because since you are using jQuery (that is the target of why you wrote it) then it is easy to use the $jQuery.data() function to get information attributed to an element in an HTML5 way.

    $(“div”).data(“behavior”,”ui-tab”); //to dynamically set behavior

    The added benefit is you can bind to the changedData event for cross-browser changes in data tags… You could use another unique attribute like “behavior” but for the benefits of HTML5 compliance and simplicity of access by using jQuery I suggest “data-behavior”

    To me the following looks pretty close (not too scary)

    … HTML5 ready

    vs

    … old school

    vs

    … simplest but not “correct”

    The first way I think you can be clear about what the element is intended for. Using classes there is no clear rule for class sequencing… can I write it as “ui-tab behavior” and still have the same meaning. Originally my main issue for using class was the way your code parses behavior map. eg class=”behavior ux-tabs” . What if I want 2 behaviors for the one element? Ok I can statically set it as “behaviour ux-tabs behavior ux-glow” but not dynamically with jQuery.addClass() Can I simply turn one of these behaviors off? jQuery addClass or removeClass doesn’t allow you to simply set the sequence of your class values nor does it allow multiple class entries (eg behavior twice)

    As far as the argument for CSS layout and look I think using an independent data-behavior can still take part in CSS selectors on modern browsers (barring some poor dynamic parsing issues you mentioned in earlier blogs)

    div[data-behavior] {
    /* anything with a behavior*/
    }
    div[data-behavior~ui-tabs] {
    /* anything with a specific ui-tabs behavior*/
    }

    Did I mention I really like CSSS! I think it would be great to have some rules for manipulation defined in CSS that wouldn’t require the constant binding and unbinding of events (live or delegated) to do some simple rules. I like @set, const and especially how you have flex units. (Just like Less CSS)

    I guess I was wondering what if you could translate your CSSS! rules to be usable in the current array of browsers (if someone isn’t using the brilliant Sciter of course ;-)

    In fact I was pondering the ability to compile your CSSS! stylesheet to a mix of CSS and Javascript. Think of a merger of IE7.js from Dean Edwards or LessCSS.js from cloudhead and CoffeeScript… you could compile your CSSS! right there in the browser.

    I do get your point about having differing behaviors with @media that’d be very cool… again compiled CSSS! would be great or at the very least something like a way to map events to trigger other events…

    @media tv {
    button[data-behavior*=ui-button] {
    -tf-attached: “attached.ui-button”; //raises named event
    -tf-value: “value.ui-button”; //custom js can bind to
    -tf-click: “clear.ui-button click.ui-button”; //or map to many
    -tf-clear: “clear.ui-button”;
    -tf-destroy: “destroy.ui-button”;
    }
    }

    Comment by Kent — November 23, 2010 @ 4:13 am

  4. Sorry lost a little HTML… hope I can use PRE tags… just wanted to say I don’t this data-behavior is that scary…

    … HTML5 ready
    vs
    … old school
    vs
    … simplest but not “correct”

    Comment by Kent — November 23, 2010 @ 4:18 am

  5. I do not see how this $(”div”).data(”behavior”,”ui-tab”); is better than $(”div”).addClass(”behavior ui-tab”); to be honest. In any case there is a second form for adding behaviors:

    $.behaviors.selector("ul.ext-list > li", MyExtListItem );

    It is more suitable if you need to add behaviors dynamically.

    Ideal solution would be to use special property in CSS for behavior bindings.
    Time to time at W3C CSS WG some activity happens around this document: http://www.w3.org/TR/becss/
    Take a look on this by the way:

    DIV.header > H1.TOCtitle 
    {
      color : red ;
      onclick : "fold_or_unfoldTOC(event)"
    }

    That is from version dated 1999: http://www.w3.org/TR/1999/WD-becss-19990804
    Looks familiar, isn’t it? In later version people tried to add XBL there for reasons unknown to me. Why we need XML to link behaviors assigned in CSS with functions/classes defined in scripts? To make XML at least somewhere useful on the Web ? :) But that is another story indeed.

    And yet button[data-behavior=ui-button] selectors do not work in WebKit browsers in all cases. At load time it does something but not in runtime.

    In HTMLayout/Sciter such selectors work and actively used for assigning behaviors. Usually I use “type” attribute for that: widget[type=ui-button] { prototype: UIButton; } />. But that is mostly suitable for custom types of <input>’s and <widget>s.

    Hmmm… and what about

    <div type=tabs >
      ...
    </div>
    

    ? If to treat ‘type’ attribute as something that defines behavior …

    And what is “the changedData event” you have mentioned?

    Comment by Andrew — November 23, 2010 @ 10:03 pm

  6. changedData was included in jQuery 1.4.3 to listen to events where data is changed. http://blog.jquery.com/2010/10/16/jquery-143-released/

    You never really covered what we want 2 behaviors. Dynamically add and/or remove.

    $.behaviors.selector(selector, behavior) doesn’t work (at least in your demos) and there is no “remove”

    And from Quirks mode and the basic tests I’ve run on Chrome, Opera, Safari the [data-behavior*=uix-tabs] works fine when adding, changing and removing values. The main issue is again from what I’ve read is the values are case sensitive and IE7 doesn’t automatically referesh (however you can overcome this by setting the class to the same value)

    Oh and here is a way to “bind” behaviors automatically….(not in a jQuery style though)
    http://devign.me/ohbehave-apply-behavior-to-elements/

    Finally I’ll see if I can rustle up some sort of demo code to explain my thoughts better.

    Comment by Kent — November 24, 2010 @ 3:43 am

  7. $.behaviors.selector(selector, behavior) doesn’t work

    Oh, thanks. Fixed, you can try on: http://terrainformatica.com/behaviors/behaviors-by-select.htm

    and there is no “remove”

    Remove what from what?

    I’ve run on Chrome, Opera, Safari the [data-behavior*=uix-tabs] works fine…

    Try this test: http://terrainformatica.com/temp/test-att-sel.htm in FF and GC. This what I meant when said that they are not quite working.

    Usually element with behavior requires some styles for different states:

    [data-behavior*=uix-tabs]
    [data-behavior*=uix-tabs]:focus
    [data-behavior*=uix-tabs]:disabled
    

    As you see not all browsers support dynamic attributes assignment with attribute selectors. This makes behaviors-by-attribute not quite useful.

    As of that automatic binding implementation. All three methods used are too far from each other and too complex. I think that my 75 LOC version is just enough for that.

    And for the record: As far as I remember Ben Nolan was the first who provided behaviors.js implementation. See: http://bennolan.com/behaviour/ and http://bennolan.com/behaviour/behaviour.js

    Comment by Andrew — November 24, 2010 @ 11:49 pm

RSS feed for comments on this post. TrackBack URI

Sorry, the comment form is closed at this time.