Table of Contents
There are two types of data replication, implicitand explicit.
Implicit replication is the earlier form, and while it works well, it has some problems:
It can make the code harder to read.
It can be inefficient.
It can sometimes be fragile.
The newly introduced explicit replication solves these problems by mirroring the (final) runtime view structure in the replicating declaration. We recommend that you use explicit replication in all new code that you write. You can change from implicit to explicit replication by changing this:
<view ... datapath="..."> ... </view>
to this:
<replicator datapath="..."> <view ...> ... </view> </replicator>
As shown in some of the examples above, datapaths that match multiple nodes cause their nodes to be replicated. By "replicated", we mean that for each match of the XPath expression one instance of the mapped view is created. This is one of the most important features of the databinding facilities in LZX.
A
replication manageris a runtime object
that is created automatically whenever data replication
occurs as a result of a datapath matching more than once.
When that happens, the
name
or
id
attribute of the
replicated view (if the view is named) is taken over by the
replication manager, and from then on referring to that name
will access the replication manager object, and not the view.
In order to reference the replicated views, known as
clones, you should use the
lz.ReplicationManager
API.
If a datapath matches multiple nodes, it will create
a replication manager. If
replication
is
normal
(the default), then the
replication manager will be a direct instance of
lz.ReplicationManager
. If it is
lazy
, it will instead create a
lz.LazyReplicationManager
.
As mentioned above, when a view is replicated, its
copies are managed by the replication manager object. Once
clones are created, the instance of the replication manager
contains references to them in the
clones
property, which
is an array of views. Note that
lz.ReplicationManager
extends
lz.datapath
, and a cloned view along
with its datapath is replaced with the replication manager
object. Armed with this knowledge, we have a technique for
determining when a view is cloned. The example below
demonstrates the use of the clones property by declaring a
handler for the
onclones
event on the view's
datapath.
Example 38.1. Using clones and the onclones event
<canvas
width
="100%
" height
="200
">
<dataset
name
="tabnames
">
<title
name
="Account Info
"/>
<title
name
="Order History
"/>
<title
name
="Preferences
"/>
<title
name
="Shopping Cart
"/>
</dataset
>
<simplelayout
axis
="x
" spacing
="25
"/>
<button
text
="Create tabs
">
<handler
name
="onclick
">
gs.pane.setAttribute('datapath', 'tabnames:/title')
bs.pane.setAttribute('datapath', 'tabnames:/title')
</handler
>
</button
>
<class
name
="repltabelt
" extends
="tabelement
" text
="$path{'@name'}
" visible
="true
"/>
<tabslider
width
="150
" name
="gs
" height
="150
" spacing
="2
">
<repltabelt
name
="pane
">
<datapath
>
<handler
name
="onclones
">
if (!this['doneDel']) {
this.doneDel = new LzDelegate(this, 'openOH')
this.doneDel.register(clones[clones.length - 1], 'oninit')
}
</handler
>
<method
name
="openOH
">
parent.select(this.getCloneNumber(0))
</method
>
</datapath
>
</repltabelt
>
</tabslider
>
<tabslider
width
="150
" name
="bs
" height
="150
" spacing
="2
">
<repltabelt
name
="pane
">
<datapath
>
<handler
name
="onclones
">
parent.select(this.getCloneNumber(0))
</handler
>
</datapath
>
</repltabelt
>
</tabslider
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Because the
onclones
event is sent when the
clones
attribute is
set, it only signals the start of view replication, but in
this example it is used to determine the exact moment when
replication is finished. Since replicated views are
initialized in the same order they are in inserted in the
clones array, we only need to wait for the oninit event for
the last clone in the list. This is necessary because
initialization of the
tab
elements takes a non-zero amount
of time, and an attempt to perform an operation on their
container — tab slider
— before it is completed will leave the
component in an inconsistent state. For illustration
purposes, the second
tabslider
has this problem, whereby
selecting the first
tab
element too soon renders its
parent unusable (the other
tabelement
s are gone).
This example also takes advantage of the fact that,
by default, views become visible when they consume data
(see section on visibility of datamapped views above).
Before the button is clicked, there is a single
tabelement
object within the
tabslider. However, it is kept invisible until it receives
data, at which point its replication occurs, and its clones
are displayed.
Similarly to the
clones
property,
lz.ReplicationManager
maintains a list
of matched data nodes in the
nodes
property. It is
an array of
lz.DataElement
objects that are mapped
to the replicated views, and is available before any clones
are created. And as with the
onclones
event, a handler for
onnodes
may be declared to respond to
data replication in a custom way. The code below qualifies
the value of
name
attribute of each
replicated data node with the value of the text field, if
any.
Example 38.2. Using the nodes property
<canvas
height
="200
" width
="100%
">
<dataset
name
="tabnames
">
<title
name
="Account Info
"/>
<title
name
="Order History
"/>
<title
name
="Preferences
"/>
<title
name
="Shopping Cart
"/>
</dataset
>
<simplelayout
axis
="x
" spacing
="25
"/>
<button
text
="Create tabs for user:
">
<handler
name
="onclick
">
nav.pane.setDatapath('tabnames:/title')
</handler
>
</button
>
<edittext
name
="user
" width
="120
" options
="ignorelayout
" y
="25
"/>
<tabslider
width
="150
" name
="nav
" height
="150
" spacing
="2
">
<tabelement
name
="pane
" text
="$path{'@name'}
" visible
="true
">
<datapath
>
<handler
name
="onnodes
"><![CDATA[
if (user.text.length)
for (var i = 0; i < nodes.length ; i++) {
var title = nodes[i].getAttr('name')
pos = title.indexOf(':')
if (pos != -1)
title = user.text + title.substr(pos)
else
title = user.text + ': ' + title
nodes[i].setAttr('name', title)
}
]]>
</handler
>
</datapath
>
</tabelement
>
</tabslider
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
If your application uses data replication and the data backing replicated views changes at runtime, by default the replication manager destroys and re-creates the replicated views whose data has changed. The typical scenarios when this will occur are a change in the datapath of the replicated view, or deletion/addition of rows to the dataset. Because the dataset may contain many data elements, this adjustment is often an expensive operation that results in a noticeable flicker of the user interface while view removal/creation takes place.
In order to make updates to datamapped elements more
efficient, you can declare the datapath that will match
multiple nodes with the
pooling
attribute set to
true
. The effect of this is that the views
that have already been created as a result of replication
will be reused internally, instead of re-created. Since the
replication manager only needs to remap the changed data to
the existing clones, data updates are reflected in UI much
faster than they would be if the runtime had to create new
views. Consider the following example.
Example 38.3. Using pooling to optimize data updates
<canvas
height
="300
" width
="100%
">
<dataset
name
="phonebook
" src
="resources/phonebook.xml
"/>
<simplelayout
axis
="y
" spacing
="3
"/>
<view
>
<view
name
="newContact
" datapath
="new:/contact
">
<text
>First Name:
</text
>
<edittext
name
="firstName
" datapath
="@firstName
" x
="80
"/>
<text
y
="25
">Last Name:
</text
>
<edittext
name
="lastname
" datapath
="@lastName
" x
="80
" y
="25
"/>
<text
y
="50
">Phone:
</text
>
<edittext
name
="phone
" datapath
="@phone
" x
="80
" y
="50
"/>
<text
y
="75
">Email:
</text
>
<edittext
name
="email
" datapath
="@email
" x
="80
" y
="75
"/>
<button
width
="80
" x
="200
">Add
<handler
name
="onclick
">
parent.datapath.updateData();
var dp = phonebook.getPointer();
dp.selectChild();
dp.addNodeFromPointer(parent.datapath);
parent.setAttribute("datapath", "new:/contact");
</handler
>
</button
>
</view
>
</view
>
<button
text
="Delete selected
">
<handler
name
="onclick
"><![CDATA[
for (var c = 0; c < all.nodes.length;) {
var clone = all.clones[c];
if (clone.datapath.xpathQuery('@checked') == 'true') {
clone.datapath.deleteNode();
} else {
c += 1;
}
}
]]>
</handler
>
</button
>
<view
name
="all
">
<datapath
xpath
="phonebook:/phonebook/contact
" pooling
="true
"/>
<view
>
<simplelayout
axis
="x
"/>
<checkbox
width
="30
" datapath
="@checked
">
<handler
name
="onvalue
">
datapath.updateData();
</handler
>
<method
name
="updateData
">
return String(this.value);
</method
>
<method
name
="applyData
" args
="d
">
this.setValue(d);
</method
>
</checkbox
>
<text
datapath
="@firstName
"/>
<text
datapath
="@lastName
"/>
<text
datapath
="@phone
"/>
<text
datapath
="@email
"/>
</view
>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007-2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
In the code above, we handle data removal by going
through the list of data nodes, and deleting the nodes whose
checked
attribute is set
to "true". Note how this attribute is controlled by and
mapped to the value of the corresponding checkbox. Any change
in the state of the checkbox results in an update to the data
node attribute, and vice versa — when
views are created or reused (due to deletion), the appearance
of their checkboxes is unchecked because initially the
attribute is not set.
This kind of syncing to the underlying data is generally required when pooling is in effect and the state of the visual elements can be changed as a result of a user interaction. In a simpler case, the UI would not be modifiable by the user, so the data flow is one way only and the views are completely data-driven, and therefore consistency of data with its presentation would be maintained automatically.
Pooling is generally a good optimization in cases
where the data completely informs the state of a replicated
view. If the view has additional state which can change
through user interaction or depends on setting attributes
at init time, then this option cannot usually be used. The
default value for the
pooling
on
<dataset>
is
"false", except when replication is set to
lazy
, in which case it must be true, as
described below.
If a datapath's
replication
attribute is
set to
lazy
, then a match to multiple nodes will
create an
lz.LazyReplicationManager
instead of an
lz.ReplicationManager
. This kind of
replication manager is called "lazy" because it doesn't do
the work of creating a view until it has to, and it does the
bare minimum of work. The lazy replication manager creates
only enough replicated views necessary to display the data,
so there is not a view for each data node. This enables the
display of very large datasets.
Because the
lz.LazyReplicationManager
is relatively
specialized, there are several restrictions on its
use:
The replicated views should be contained in a view which is not the view that clips. The replicated views can be positioned by moving this container. This container will be sized to the size of the replicated list.
The parent of the container must be a view that
clips (that is, its
clip
attribute is set
to "true".
The replicated view cannot change its size in the replication axis, and the size cannot be a constraint. If the replicated view is sized by its contents, then lazy replication may not work in all cases.
The data should completely inform the display of the view. Any attributes that are changed through interaction with a replicated view should be stored in the dataset.
Selection within the replicated views should be
controlled by a
lz.dataselectionmanager
.
This example shows use of the lazy replication manager to display a large dataset. The replication does not create a view for each node in the dataset; rather it only creates enough views to fill the clipping view that contains it. As you click the "Make it bigger" button, you will see that more items from the list are shown. Notice also that these views are actually being created when you press the button, as you can see by then "number of subviews" value at the top of the canvas.
Example 38.4. Using a lazyreplicationmanager to display a large dataset
<canvas
height
="350
" width
="100%
">
<dataset
name
="vegetables
">
<celery
/> <celeriac
/> <carrot
/> <florence_fennel
/> <parsnip
/>
<parsley
/> <winter_endive
/> <witloof_chicory
/> <cardoon
/>
<artichoke
/> <head_lettuce
/> <cos_lettuce
/> <black_salsify
/>
<swedish_turnip
/> <cauliflower
/> <cabbage
/> <brussels_sprouts
/>
<kohlrabi
/> <broccoli
/> <savoy_cabbage
/> <turnip
/> <radish
/>
<water_cress
/> <garden_cress
/> <foliage_beet
/> <spinach
/>
<sweet_potato
/> <watermelon
/> <melon
/> <cucumber
/> <winter_squash
/>
<marrow
/> <chickpea
/> <lentil
/> <runner_bean
/> <common_bean
/>
<pea
/> <faba_bean
/> <leek
/> <shallot
/> <onion
/> <salsify
/>
<welsh_onion
/> <garlic
/> <chives
/> <asparagus
/> <ladyfinger
/>
<sweet_corn
/> <rhubarb
/> <capsicum_pepper
/> <tomato
/> <eggplant
/>
</dataset
>
<simplelayout
spacing
="10
"/>
<text
width
="200
">
<handler
name
="onaddsubview
" reference
="replicationParent
">
this.setAttribute("text", 'number of subviews: ' +
replicationParent.subviews.length);
</handler
>
</text
>
<view
clip
="true
" width
="100
" height
="100
" id
="clipper
" bgcolor
="silver
">
<view
id
="replicationParent
">
<text
>
<datapath
xpath
="vegetables:/*/name()
" replication
="lazy
"/>
</text
>
</view
>
<scrollbar
/>
</view
>
<button
>Make it bigger
<handler
name
="onclick
">
clipper.setAttribute('height', clipper.height + 50);
</handler
>
</button
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
See the paging.lzx examplefor another example of lazy replication.
Only a datapath can cause replication. Although it might seem that $path might be used to implicitly force replication, it will not. A $path expression will only yield a single value. If it matches multiple values, it is an error and it will act as if it matched none. In the example below, note that The $path constraint does not update when the enclosing datapath is set.
Example 38.5. $path does not replicate
<canvas
height
="300
" width
="100%
" debug
="true
">
<debug
y
="100
"/>
<dataset
name
="ds
">
<data
>
<person
name
="a assdfasfva asdf sad
" surname
="a surname
"/>
<person
name
="b
" surname
="b surname
"/>
<person
name
="c
" surname
="c surname
"/>
</data
>
</dataset
>
<simplelayout
axis
="y
"/>
<button
onclick
="thedata.setAttribute('datapath', 'ds:/')
">Set datapath
</button
>
<view
id
="thedata
" ondata
="Debug.warn('data %#w', arguments[0])
">
<simplelayout
axis
="y
"/>
<view
>
<simplelayout
axis
="x
"/>
<text
>Datapath:
</text
>
<view
>
<simplelayout
axis
="y
"/>
<text
datapath
="data/person/@name
" resize
="true
" ondata
="Debug.warn('datapath.ondata')
"/>
</view
>
</view
>
<view
>
<simplelayout
axis
="x
"/>
<text
>$path:
</text
>
<view
>
<simplelayout
axis
="y
"/>
<text
text
="$path{'data/person/@name'}
" resize
="true
" ondata
="Debug.warn('$path.ondata')
"/>
</view
>
</view
>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Sometimes you want to know the position of a view; for
example, say you wanted to alternate background colors. You
might think of checking for the position of the view in its
oninit()
method.
However, if you're using datapath pooling (you'll
probably want to for long lists), the
oninit
events for views created by data
replication don't necessarily fire because the views may be
reused. In that case, the
ondata
event will fire, so you might
consider using the
ondata()
handler. However,
incrementing a counter isn't the most reliable way to
determine order because views may not instantiate in linear
order.
That's why it's better to use a datapath expression. Add attribute like this inside your replicated node:
<attribute name="pos" value="$path{'position()'}"/>
This will tie the pos attribute to the physical position in the data. You can then then tie the background color like so:
<attribute name="bgcolor" value="${this.pos % 2 == 0 ? 0x00EEEE : 0x00DDDD}"/>
Views that you create procedurally are not the same as "clones" created by data replication. In fact, data replication overrides procedurally created views. For example:
Declare a view.
Add subviews to it (procedurally), and alter its properties.
Set a datapath on the view (from step 1) that would make it replicate.
Changes made in step 2 will be ignored after replication.
Explicit replication improves upon implicit replication
by adding 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:
lz.view { … } lz.ReplicationManager { datapath: { ... }, ... } lz.view { name: 'Bob' } ... lz.view { 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 one-to-one 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 (Section 2.2, “Pooling”) control in explicit replication.
Copyright © 2002-2010 Laszlo Systems, Inc. All Rights Reserved. Unauthorized use, duplication or distribution is strictly prohibited. This is the proprietary information of Laszlo Systems, Inc. Use is subject to license terms.