Personal tools

Explicit Replication

From OpenLaszlo

Implemented in 4.0.5.

This proposal is to expose data-driven replication as an explicit syntax tag in LZX. Currently a databound node is implicitly replicated if its datapath matches more than one node in the XML tree; with this change, the decision whether to replicate or not is pushed up into the source code and made explicit.

Contents

Introduction

Current Design

The current syntax for replication is implicit in the sense that a view is replicated if it meets certain criteria. Hence whether a view will replicate or not is actually decided at runtime. For example, consider the following simple example.

<view name="a">
  <view name="b">
    <datapath xpath="..." replication="normal" pooled="false"/>
  </view>
</view>

At construct time, this declaration initially has the following structure:

LzView { name: 'a', … }
  LzView { name: 'b', datapath: { xpath: '…', replication: 'normal', pooled: false, … }, … }

However, if during view b's initialization the datapath resolves to multiple data nodes, then b's initialization is halted and the view structure is modified:

LzView { name: 'a', … }
  LzReplicationManager 
     { cloneClass: 'view', cloneAttrs: { … }, cloneContent: […], xpath: '…', … }

Here the essential information about view b has been sucked out and put into three attributes, @cloneClass, @cloneAttrs, and @cloneContent. The replication manager is itself a datapath, so it copies all relevant state from b's datapath into itself.

Finally, the replication manager uses the clone attributes to create clone views corresponding to the matched data nodes. (In certain cases, the replication manager will have reused view b and b's datapath in the list of replicated clones, saving an object allocation and construction.)

LzView { name: 'a', … }
  LzReplicationManager { datapath: { xpath: '…', ...}, clones: […], … }
  LzView { datapath: { … }, … }
  …
  LzView { datapath: { … }, … }

The replication manager stays in the hierarchy to respond to changes in the datapath or its matched datanodes.

The Problem

The problems with this design are fairly obvious:

  • Replication only kicks in if, at runtime, the datapath returns more than one data node. This reduces readability of the code, because it is difficult to tell by eye whether a given view tag will result in replication -- and impossible to tell for sure without actually running the code against a particular data binding. In practice, there are very few cases where a view hierarchy is meant to be replicated in some cases and not in others, so the flexibility is not really necessary.
  • The "clone prototype" is partially instantiated before a decision can be made whether to replicate. This is inefficient, but can also lead to subtle bugs and consequently more complex code elsewhere in a program.
  • The process by which the replication manager takes over the databinding process is fragile. LzReplicationManager copies all essential state from the original LzDataPath into itself, but that means that whenever changes are made to LzDataPath there is the potential for parallel changes to be made in the LzReplicationManager constructor function.

Proposal

The proposal is to make replication explicit and to mirror the (final) runtime view structure in the replicating declaration.

Language Changes

Add replicator tag

Add a replicator tag representing a replication manager:

<view>
  <replicator datapath="ds:/people/person/">
    <view name="$path{'@name'}"/>
  </replicator>
</view>

The replicator tag has the following legal tag attributes (in addition to those inherited from datapath):

  • sortpath -- xpath to the sort key
  • sortorder -- comparator function for sorting
  • datapath -- dataset:xpath

`datapath` is a shorthand for `data="${dataset.p.xpathQuery(xpath)}"` It constrains the `data` attribute of the replicator to the result of the xpath query on the dataset. Note that this meaning is different from the meaning of `datapath` on a view, which implies old-style implicit replication.

The single lexical subnode of the replicator tag is a view that will be used as a template to be replicated zero or more times, depending on the number of nodes the datapath attribute matches. The replicator tag only permits a single child node.

Note that inside a replicator tag, `${path}` constraints are used to bind views to the data (`datapath` is not used).

The replicator will be a child node of its parent view, appearing in the parent's subnodes array (but not in the parents subviews array). If named or given an identifier, it can be queried and controlled via that name or identifier.

For example, the code above might lead to the following DOM structure at runtime:

LzView { … }
  LzReplicationManager { datapath: { … }, … }
  LzView { name: 'Bob' }
  …
  LzView { name: 'Edna' }
  

The replicator has additional attributes and methods that are accessible to Javascript

  • data -- array of dataelements the datapath matches (possibly empty)
  • views -- array of views bound to the nodes (possibly empty)

There is a 1-1 correspondence between data elements and views.

Replicator variants

replicator can be subclassed. Two subclasses are pre-defined:

lazyreplicator -- only creates views that will be visible within the parent view.

resizereplicator -- a lazy replicator where each view may be of a different size.

lazy replication implies that the views will be `pooled`. There is no separate pooling control in explicit replication

Implementation Notes

Implicit replication will continue to be supported for the immediate future, but conversion to explicit replication will be encouraged. It is straightforward to transform

   <view ... datapath="...">
     ...
   </view>

to

   <replicator datapath="...">
     <view ...>
       ...
     </view>
   </replicator>

Examples

History

  • ptw 10:49, 26 July 2007 (PDT) Updated to reflect design discussion
  • ptw 12:56, 26 July 2007 (PDT) Respond to David's comments