Consider the following fragment of HTML code:
<div id="myCarInfo"> <h1 title="data:lastUpdated"> <span>data:make</span> <span>data:model</span> <span>data:year</span> </h1> </div>
Consider the following JavaScript Data Object (JSON used for illustrative purposes)
var carInfoObject = { make: "BMW", model: "325i", year: "2002", lastUpdated: new Date(481516234200) }
IBDOM, thru the custom-defined function $e(), allows you to do this:
$e("myCarInfo").populate(carInfoObject);
See it all in action on the demo page.
This part needs polishing. As shown on the demo page, IBDOM supports some number-related data types:
type:number performs basic U.S. number formatting with comma and decimal.
It also assumes you'll be okay with only two digits after the decimal point.
type:number_rounded_no_zeroes uses Math.round() and removes the
decimal part.
Over-simplistic, you might say? We agree. This doesn't even begin to address international formatting. This is where IBDOM.DataTypes.Defs comes-in. What comes after type: matches the properties defined in caps:
NUMBER_ROUNDED_NO_ZEROES: { id: 3, handler: function() { return IBDOM.Utils.getFormattedNumber(arguments[0],false,true); } }//NUMBER_ROUNDED_NO_ZEROES
arguments[0] is the value being inserted in the HTML Element or Attribute. The handler() function mechanism can be used to format any sort of data type. handler() should always return a String or a primitive that can ultimately be resolved to a String.
This might be a good area to start patching.
src="about:data:vehicleImageUrl"
href="about:data:vehicleDetailUrl"
"about" gets ignored. All IBDOM cares about, is whatever comes after "data:".
One design idea behind IBDOM is to more easily enable "hybrid documents": accessible *and* dynamic.
Consider this HTML snippet:
.... somewhere in the head ... <style type="text/css"> .IB_POPULATE { display:none; } </style> ... somewhere in the body ... in a table ... <tbody id="vehicleListings"> <!-- the IB_POPULATE class gets unset at population time --> <!-- this template will be used during dynamic population --> <!-- but will be kept hidden during initial page load, due to the above CSS directive --> <tr class="template:repeat IB_POPULATE"> <td>data:year</td> <td>data:make</td> <td>data:model</td> </tr> <tr class="template:empty_collection IB_POPULATE"> <td colspan="3">No listings were found. Please try again with a broader search.</td> </tr> <!-- start actual data rows streamed out at initial page load --> <!-- stuff that's accessible to all user agents, and indexable by engines == good --> <tr> <td>2002</td> <td>BMW</td> <td>325i</td> </tr> <tr> <td>2003</td> <td>Jeep</td> <td>Liberty</td> </tr> </tbody>
Upon user-interaction, an asynchronous call might retrieve updated listings data in a javascript array of listing objects. At which point developers can:
$e("vehicleListings").populate(arrayOfVehicleListings);
... multiple times. All template information and data mappings are retained in a cache for subsequent usage.
Element.populate() detects whether the passed argument is an Array, or just an Object. It delegates the work to two different methods to handle ether case.
Templates can also be "stashed-away" in any element with a *class* value set to "templates". Developers should ensure all templates have at least one class value that uniquely identifies them across all stashes.
As a result, here's another possible implementation of the above example:
<tbody id="vehicleListings" class="use_template:repeat|listing_row use_template:empty_collection|no_listing_found"> <!-- no template embedded inside keeps your table clean --> <!-- start actual data rows streamed out at initial page load --> <!-- stuff that's accessible to all user agents, and indexable by engines == good --> <tr> <td>2002</td> <td>BMW</td> <td>325i</td> </tr> <tr> <td>2003</td> <td>Jeep</td> <td>Liberty</td> </tr> </tbody>
... somewhere else in the document, one or more stashes might be present with template definitions:
<div class="templates"> <!-- this time, we add a className to our templates that uniquely identifies them: listing_row no_listing_found --> <tr class="listing_row"> <td>data:year</td> <td>data:make</td> <td>data:model</td> </tr> <tr class="no_listing_found"> <td colspan="3">No listings were found. Please try again with a broader search.</td> </tr> </div>
To further insulate an HTML document, we're thinking of enabling an optional pattern by which developers could store all templates in a separate XHTML file, that could get loaded at some initialization process in an XmlHttpRequest, and accessed via the DOM.
It should be easy enough to implement. Any thoughts?
While the data-mapping and templating mechanisms take care of a brunt of grunt work in injecting data into HTML elements, we can't possibly accomodate and anticipate all needs.
This is where a Processor comes-in. A processor is a function, whose this keyword represents the HTML Element being processed, and sole argument, aka arguments[0] is the data object being mapped.
Important Rule: A processor should always be set/defined BEFORE invoking populate() on an Element
//First, define the processor, by passing a function to setProcessor $e("someElement").setProcessor( function() { /* set HTML Element's class based on the value of the year property */ dataObject = arguments[0]; if (dataObject.year == 2002) { this.setClassValue("YEAR_2002"); } else if (dataObject.year == 2003) { this.setClassValue("YEAR_2003"); } } ); //Then, call populate, by passing your bean $e("someElement").populate(theDataObject);
In this instance, you will have an array of objects, each of which is going to be mapped to a repeating instance of a given template.
This time, we will pass two arguments to populate(): The first argument is the mandatory array of objects. The second, optional, argument is the processor function that will get applied to each template processing iteration:
$e("vehicleListings").populate( arrayOfListingObjects, function() { /* this function gets passed as an argument to every template iteration's .setProcessor() */ } );
This feature enables developers to alter the class of any HTML element within an element being populated, based on the value of any field:
<td class="setclass:YEAR_2002{if}((this.year==2002)) someOtherClass yetAnotherClass">data:year</td>
Processing the above setclass directive should yield the following table cell in instances where this.year's value is 2002:
<td class="YEAR_2002 someOtherClass yetAnotherClass">2002</td>
... and the following table cell in all other instances:
<td class="someOtherClass yetAnotherClass">2003</td>
Where "this.year" refers to the "year" property of the vehicle listing object being used to populate the current row instance. What comes after the {if} is any valid javascript expression evaluated in the context of the data bean.
Despite the fact that it might look unwieldy, developers might find themselves in need of specifying mulitple setclass directives for different field values. Any thoughts?. This type of advanced conditional handling of an element's class value might be better handled in a processor. See "Setting a Processor" above.
When populate() is used to inject large collections of objects, most browsers will, by default, not "repaint" the HTML Element being modified until the processing has completed. This may unnecessarily keep useful data away from the eyes of your users.
By default, IBDOM uses "forked loop execution" to force browsers to repaint the HTML Element being processed on each array iteration. To disable this behavior, set IBDOM.Config.USED_FORKED_LOOP_EXECUTION to false