Skip to content

Cht async

guba-j edited this page Jun 15, 2011 · 7 revisions

How can templates consume asynchronous data?

The templates can consume input data in the form of promises (implemented by the dojo.Deferred), automatically updating the resulting HTML as asynchronous operations complete.

Execution model

The template evaluator functions still execute synchronously, always returning a value convertible to string representation of the HTML or to DOM nodes. Dependencies on asynchronously available data are handled by branching the control flow depending on whether a particular promise has already been resolved. Additional execution context is required for repeated evaluations of the same template to work correctly, therefore the template instance provides an additional method update() to repeatedly execute the evaluator against the initially established context that includes the arguments supplied in the original call to the evaluator.

The caller of an asynchronous evaluator can use dojo.when() on the returned value: the progress callback (if provided) will be activated for every resolved promise within the template; the primary callback will be called only when all of the promises depended upon by the template have been resolved. This gives the caller a fine-grained control over decisions when to re-evaluate the template (using update()) and when to modify the DOM (using place()).

A higher level facility that combines handling of asynchronous events with DOM updates is provided by the render() method of the template instance object. The caller can still use dojo.when() on the value returned by render() in order to be notified of the changes in the rendered template but the actual DOM updates are performed automatically.

The template developer can facilitate the performance of repeated updates as well as protect the unrelated parts of DOM built by the same template from incremental changes to other pieces by assigning the marker= attribute to nested templates. Templates thus designated inject invisible DOM elements before and after the content they produce and are thus capable of selectively replacing their content within a larger fragment produced by the parent template. To utilize this capability, the caller of the template should use the updateDom() method of the template instance rather than place(); render() does that automatically for appropriately written templates.

New CHT features

<? when ?>

<? when [Promise] ?>

FinalContent

[

<? else ?>
PlaceholderContent
]
<? /when ?>

The basic building block of asynchronous templates, <?when?> expands to FinalContent when its argument is not a promise or after it has resolved to a value. Otherwise it expands to PlaceholderContent. The current input of FinalContent is the value of the argument after promise resolution; the current input of PlaceholderContent is the same as that of the <?when?> itself.

wait

<? when "a.get()|wait|expr:$.user" ?>
     {{name}}
<? /when ?>

An additional Q+ tag, wait, can be used only within the argument expression of <?when?> element. It is used to explicitly specify the point in the pipeline where a promise has to be resolved to a value. In the example above, a.get() is expected to return a promise resolving to an object that contains a property named user which is to serve as current input for the body of <?when?>. It is possible to chain multiple promise resolutions in one <?when?> by using wait multiple times.

If the argument of <?when?> does not contain an explicit wait, one is added at the end implicitly, resulting in the default behavior described in the section above.

<? template ?>

Generally, asynchronous templates do not have to be specially declared: the compiler determines that property from presence of <?when?> blocks or nested asynchronous templates. Two additional attributes of <?template?> element introduced by CHT 1.1 are required in more complex cases:

marker=

<? template TableRow marker='tr' ?>
  <tr><td>....</td></tr>
<? /template ?>

This attribute is used to designate a template as incrementally updated (using updateDom()) and specify the type of HTML element to be injected as a marker. Presence of this attributed implies async="true" and compiled="true": implementation specifics require the incrementally updated templates to be always compiled separately.

async=

When a template references itself recursively, the compiler may not yet know at the point of reference whether to generate the code for a potentially asynchronous call or for a straightforward function call. Thus recursive asynchronous (not necessarily incrementally updated!) templates have to specify async="true" in their definitions. Presence of this attribute implies compiled="true" which is a requirement for any recursive template.

Template instance API

The new methods described below are provided on the instance objects returned by the asynchronous template evaluators. The instance objects returned by the regular (synchronous) evaluators also provide stub versions of these methods (except updateDom(), which should not be used with templates not capable of incremental DOM update) in order to keep the application code reasonably insulated from the templates it may be using.

render()

 async_template( args ).render( ref_node, pos, options ); 

or

 dojo.when( async_template( args ).render( ref_node, pos, options ), on_rendering_complete ); 

This is a high-level method that encapsulates all details of asynchronous and incremental template rendering. It takes the same parameters as the place() method and does indeed pass them through to place; it may, however, call place() repeatedly for the entire template and/or call it on the instance objects produced by the nested templates. It returns a promise that will resolve once the rendering is fully completed.

It is expected that render() will be used in most cases for evaluating and displaying the CHT templates. The synchronous version of render() is a synonym of place().

isDeferred()

var tpl_instance = async_template( args );
if( tpl_instance.isDeferred() ) {
  // Execute if instance will change
}

Returns true if the result of template evaluation still depends on one or more unresolved promises. Note that even an asynchronous template may produce its final expansion on first evaluation. The synchronous version always returns false.

update()

dojo.when( 
  async_template( args ),
  function( instance ) {
    instance.update();
    instance.place( ref_node, pos, options );
  }
);

Re-evaluates the template and updates the template instance. The example above will render only the final expansion of the template. Note that this code will be suboptimal if the very first evaluation produces the final expansion since it will evaluate the template twice. Synchronous version of this method does nothing.

canUpdateDom()

Returns true if the template instance can update its DOM representation in place. The updateDom() method should never be called if canUpdateDom() returned false. The synchronous version always returns false.

updateDom()

var tpl_instance = async_template( args );
tpl_instance.place( ref_node, 'only', options );

if( tpl_instance.isDeferred() ) {

  function refresh( instance ) {
    if( instance.canUpdateDom() ) instance.updateDom( ref_node, options );
    else {
      instance.update();
      instance.place( ref_node, 'only', options );
    }
  }

  dojo.when( tpl_instance, refresh, on_error, refresh );
}

Method updateDom() updates the DOM representation of a template in place, using the marker elements that it and/or its children have injected into their expansions. If the template expansion has reached its final state (no more promises are pending), the marker elements are removed. This method should not be called unless canUpdateDom() returned true therefore no synchronous version is provided.

The example above is a simplified version of the algorithm implemented by the render() method. The ref_node argument is used by updateDom() only to reduce the area in which to conduct the search for marker elements. The options argument is passed through to place().