Login Register

Widget template bindings with 'native' getters/setters, Observable and Widget.attr()

Update!
(Page was called: "Feedback on widget template bindings à la Coherent library?")
History briefly: Originally I was missing a way to update template (DOM) values painlessly. Coherent did it in an interesting way and I created a sample based on that idea. Then Observable was discussed on devel-list, and I updated my sample. Now that Dojo 1.2 defines the standard interface for setting/getting all widget attributes, I figured that it would be the right time to update this page to include official Dojo-way of binding widget values to template elements: attr().

I bumped into this interesting article Properties and Bindings on Coherent js library pages. The magic in it has a certain appeal on it, so I thought that it'd be nice to take advantage of the technique in Dijit-widgets as well. The idea here is that setting a new value for a widget property is automatically reflected on the UI. The target template nodes are marked with textKeyPath-attribute, and the attribute value is the name of the widget property we want to listen to.

Unfortunately, the implementation suffers from the normal IE javascript getter/setter issues, so we don't get it as smooth as we otherwise could (w1.foo='newFoo').

In order to play with, just copy it on your local box and edit the Dojo-dir accordingly. You can edit values w1.foo and w1.bar with FireBug.

<html>
    <head>
    <title>Widget template bindings</title>
   
    <style type="text/css">
      span { background: yellow; }
    </style>

    <script type="text/javascript" src="dojo/dojo/dojo.js.uncompressed.js"
        djConfig="isDebug:true, parseOnLoad: true">

    </script>

    <script type="text/javascript">
 
    dojo.require("dijit._Widget");
    dojo.require("dijit._Templated");
    dojo.require("dijit.Declaration");

    dojo.declare('_DataBoundWidget', [dijit._Widget, dijit._Templated], {
     
      textKeyPathAttr: 'textKeyPath',
     
      postCreate: function() {
        dojo.query( '[' + this.textKeyPathAttr + ']', this.domNode ).forEach( this.setBindings, this );
      },
     
      setBindings: function( node ) {
        var propName = node.getAttribute( this.textKeyPathAttr );
        this.setSetter( propName )
        this.setListener( node, propName )
      },
     
      setListener: function( node, propName ) {
        dojo.connect( this, propName+'Changed', dojo.partial( this.updateNode, node ) );
      },

      updateNode: function( node, newVal, oldVal ) {
        node.innerHTML=newVal;
      },
     
      capitalize: function( str ) {
        return str.substring(0,1).toUpperCase()+str.substring(1);
      },
     
      setSetter: function( propName ) {
        this[propName+'Changed']=function(){};

        var getter = function(propName) {
          return this[propName+'Value'];
        }

        var setter = function(propName, newVal) {
          this[propName+'Changed']( newVal, this[propName+'Value'] );
          this[propName+'Value']=newVal;
        }
       
        this['get'+this.capitalize(propName)] = dojo.hitch( this, getter, propName );
        this['set'+this.capitalize(propName)] = dojo.hitch( this, setter, propName );

        if(this.__defineGetter__) {
          this.__defineGetter__( propName, dojo.hitch( this, getter, propName ) );
          this.__defineSetter__( propName, dojo.hitch( this, setter, propName ) );
        }
      }
    });
    </script>
  </head>
  <body>
    <div>
    <h1>Widget template bindings</h1>
    The idea is to provide a streamlined way to update the UI (widget) automatically when the underlying data changes.
    <a href="http://dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/writing-your-own-widget-class/template">Dojo templates</a>
    provide two ways of setting widget data 1) ${...} substitution and 2) dojoAttachPoint. This example illustrates yet another way. Setting a new value
    for a widget property is automatically reflected in the UI. With FireBug (Lite) you can set a new value for a widget property as follows (<i>where <code>w1</code> is a shorthand to the widget object.</i>):
    <ul>
      <li><code>w1.foo='newFoo'</code>(Firefox)</li>
      <li><code>w1.setFoo( 'newFoo' )</code>(IE)</li>
    </ul>
    <i>Inspired by: <a href="http://coherentjs.org/docs/overview/properties-and-bindings">Coherent JS: Properties and Bindings</a></i>
   
   
    <!-- Declare a class that implements _DataBoundWidget -->
    <div dojoType="dijit.Declaration" widgetClass="simpleDataBoundWidget" mixins="_DataBoundWidget">
      <div>
        Foo: <span textKeyPath="foo">initial value</span>
      </div>
      <div>
        Bar: <span textKeyPath="bar">initial value</span>
      </div>
    </div>
   
    <!-- instantiate simpleDataBoundWidget -->
    <div dojoType="simpleDataBoundWidget" id="w1" jsId="w1"></div>
    </div>
  </body>
</html>

What's the advantage over dojoAttachPoint

I might be missing something, but what does this give you that "dojoAttachPoint" doesn't?

Using dojoAttachPoint you can do the same w1.foo = "blah" can't you?

Auto-wiring is the key

This technique provides an automatic wiring mechanism that listens to the changes and updates fields based on any property. Using dojoAttachPoint alone would require you to write setters/getters for each property you want to listen to. Plus, here we keep the data always synchronized in a standard JS-property and in the corresponding DOM-node.

Additionally, with this technique we can transfer some standard functions from widget-js to template declarations. With dojoAttachPoint you get access to a DOM-node on the JS-side, while with this technique it's vice-versa.

The idea originally stems from the difficulty of changing widget UI text elements on the fly. If you've set them using ${...} substitution you cannot access or reset them after rendering the widget easily without re-creating the UI. While on the other hand, with this technique you could just change the property value and that's all.

I wished Kris would chime in

I wished Kris would chime in here. He actually did some pretty extensive testing on introducing this into widgets, where each property acted as a getter / setter. Turns out the IE performance (as you note) is horrible comparitively, and the additional overhead in creation turned out to be quite significant.

you can find the discussion on the dojo-developers mailing list, though I don't have any links handy ...
]

About performance

What I meant by IE issues is that we're stuck with set*-type setters. When it comes to performance, I made some simple tests for creating widgets. First I created class 'reallySimpleWidget' which is the same as 'simpleDataBoundWidget' except for it inherits "dijit._Widget, dijit._Templated". Then I created a bunch of objects using:

function createWidgets( widgetName ) {
  var start = (new Date()).getTime();
  for(var i=0; i<500; i++){
    new window[ widgetName ]()
  }
  var end = (new Date()).getTime();
  return end - start;
}
console.log( createWidgets( 'reallySimpleWidget' ) )

The results were (on this slow box):
FF2: simpleDataBoundWidget: 7270ms; reallySimpleWidget: 3475ms
IE7: simpleDataBoundWidget: 5939ms; reallySimpleWidget: 2955ms
So, it's clear that the queries and extra functions add some overhead, but this technique might be applicable for certain cases, where convenience is preferred over performance.

I can see it particularly

I can see it particularly useful with _Templated widgets, where you want to just dump a bunch of data to the widget and update all the various attachPoints with matching keys?

Ahhh, gotcha, for some

Ahhh, gotcha, for some reason I thought you could already do...

dijit.byId('dijit_TitlePane_0').titleNode = "something different";

But as you point out, you'd have to have something like:

dijit.byId('dijit_TitlePane_0').setTitleNode = "something different";

And then code a function in your widget that sets the title node value.

That being the case, this seems like a great new idea. :-)

Widget.setAttribute()

So, we already have a Widget.setAttribute() method that could be extended to work for any attribute in the widet, so it's not as bad as writing setters for every widget attribute. (Of course having a separate setter for each widget attribute makes attaching/extending easier, so you need to pick which is more important.)

As per going the extra mile and support actually setting attributes directly, like Dante said we considered but seemed like there were too many disadvantages at this point.

=========
Bill Keese
Project Lead (aka BDFL) of Dijit

Dante here's the link....

Thanks, I'll have a look.

Thanks, I'll have a look.

Lessons learned from "observable"-discussion

That was one interesting conversation! I'm not going to summarize it all, but I'll emphasize some relevant bits.

First, the Observable-class discussed in that Observable/getter/setter thread is a solution to the IE set*-problem. So, it would be possible to update this widget type to take advantage of that for IE. (Plus, it's some pretty creative coding with VBScript as well. We've had to resort to that when working with browser plugins, but the way it's done here... nice! :))

Second, the need for something what I've done here is obviously recognized:

Fundamentally, being able to say something like:
myWidget.f.label = "some text";
and have it instantly set the innerHTML of the widget's labelNode
property would be a HUGE win for widget authors.

http://article.gmane.org/gmane.comp.web.dojo.devel/8385

Third, about the performance cost of the solution:

Of course, in some applications, property access to an object may be
infrequent, or overshadowed by other expenses (like DOM access), and the
"quality" of the interface may be more important. Bottom line, before you
build a tower, you better the count the cost, and the cost of using
getters/setters to "observe" is about 0.028ms in IE...

http://article.gmane.org/gmane.comp.web.dojo.devel/8388

Fourth, different opinions about handling performance issues:

performance implication [...] can be handled by some big fat warning.

and

I am concerned about killing perf in the name of trying to get real getters and setters, particularly in something like Dijit.

and

Therefore I mean to really have it in a separate package which clearly states the "experimental" state of it.

and

I see no reason not to use this capability to improve our APIs.

http://article.gmane.org/gmane.comp.web.dojo.devel/8391

Fifth, there are some additional limitations:

So briefly, the observable/JSdata module can take existing data stores and
make properties accessible with JavaScript for the majority case, existing
primitive values. However, it is slow and has a somewhat more complicated
set of rules for different forms of access. Alternately, data stores can be
developed that have a simpler set of rules and provide faster data access.

http://article.gmane.org/gmane.comp.web.dojo.devel/8404

I'll have to digest all this information, but it still seems that this really can be turned it something useful, like a widget type with "observablePropertiesInTemplate" property to switch on the magic.

I'd like to hear more ideas on the topic.

Widget bindings with experimental 'observable'

I took this experimental 'observable' class on a test drive and I edited my sample to take advantage of that. Now we get "nice" setters (w1.f.foo = 'new value') also for IE.
(In order to test this, you need to fetch observable from the issue mentioned above.)

<html>
    <head>
    <title>Widget template bindings</title>
    <style type="text/css">
      span { background: yellow; }
    </style>
    <script type="text/javascript" src="dojo/dojo/dojo.js.uncompressed.js"
        djConfig="isDebug:true, parseOnLoad: true">

    </script>
    <script type="text/javascript">
    dojo.require("dijit._Widget");
    dojo.require("dijit._Templated");
    dojo.require("dijit.Declaration");
    // note: highly experimental observable class
    dojo.require("dojox.lang.observable");

    // changes in properties will be reflected in DOM
    dojo.declare('_DataBoundWidget', [dijit._Widget, dijit._Templated], {
     
      textKeyPathAttr: 'textKeyPath',
     
      postCreate: function() {
        // pick nodes from the template where there's attribute 'textKeyPath'
        var nodeList = dojo.query( '[' + this.textKeyPathAttr + ']', this.domNode ).map( function(node) {
          return {
            prop: node.getAttribute( this.textKeyPathAttr ),
            value: node.innerHTML,
            node: node
          }
        }, this );
       
        var nodeMap = {};
        var f = {};
        // map nodes to 2 lists
        // - f: properties with their initial values (to be turned into observale)
        // - nodeMap: map properties to nodes (nodes that "listen to" property changes)
        dojo.forEach( nodeList, function( p ) {
          f[ p.prop ] = p.value;
          nodeMap[ p.prop ] = p.node;
        } );
       
        var setter = function( obj, prop, value ){
          obj[ prop ] = value;
          console.debug("setting " + prop );
          nodeMap[ prop ].innerHTML = value;
        }
       
        var getter = function( obj, prop ){
          console.debug("getting " + prop );
          return obj[ prop ];
        }
        // create observable widget.f property
        this.f = new dojox.lang.observable( f, getter, setter );
      }
    });
   
    dojo.addOnLoad( function() {
      // test assigning new value, should update DOM
      w1.f.foo = 'assigned a new value';
    } );
    </script>
  </head>
  <body>
    <div>
    <h1>Widget template bindings</h1>
   
    <!-- Declare a class that implements _DataBoundWidget -->
    <div dojoType="dijit.Declaration" widgetClass="simpleDataBoundWidget" mixins="_DataBoundWidget">
      <div>
        Foo: <span textKeyPath="foo">initial value</span>
      </div>
      <div>
        Bar: <span textKeyPath="bar">initial value</span>
      </div>
    </div>
   
    <!-- instantiate simpleDataBoundWidget -->
    <div dojoType="simpleDataBoundWidget" id="w1" jsId="w1"></div>
    </div>
  </body>
</html>

Widget bindings with Dojo's Widget.attr()

Now that Dojo 1.2 defines the standard interface for setting/getting all widget attributes, I figured that it would be the right time to update this page to include official Dojo-way of binding widget values to template elements: attr(). Instead of updating values via native setters (that's the thing in the samples above w1.foo = 'assigned a new value';), in Dojo 1.2 we do it with the help of attr(): w1.attr('foo', 'assigned a new value');

<html>
    <head>
    <title>Widget template bindings with Dojo 1.2 Widget.attr()</title>
    <style type="text/css">
      span { background: yellow; }
    </style>
    <script type="text/javascript" src="dojo-src/dojo/dojo.js"
        djConfig="isDebug:true, parseOnLoad: true">

    </script>
    <script type="text/javascript">
    dojo.require("dijit._Widget");
    dojo.require("dijit._Templated");

    // changes in properties will be reflected in DOM
    dojo.declare('simpleDataBoundWidget', [dijit._Widget, dijit._Templated], {
     
      foo: 'default value',
      bar: 'default value',
     
      templateString: '<div>\n<div>\Foo: <span  dojoAttachPoint="fooNode">'+
        'template value</span>\n</div>\n<div>\nBar: <span dojoAttachPoint='+
        '
"barNode">
template value</span>\n</div></div>',
     
      attributeMap: dojo.mixin(dojo.clone(dijit._Widget.prototype.attributeMap), {
            foo: {node: "fooNode", type: "innerHTML" },
            bar: {node: "barNode", type: "innerHTML" }
        })
    });
   
    dojo.addOnLoad( function() {
      // test assigning new value, should update DOM
      w1.attr('foo', 'assigned a new value');
    } );
    </script>
  </head>
  <body>
    <div>
    <h1>Widget template bindings with Dojo 1.2 Widget.attr()</h1>
    <!-- instantiate simpleDataBoundWidget -->
    <div dojoType="simpleDataBoundWidget" id="w1" jsId="w1"></div>
    </div>
  </body>
</html>