Table of Contents
This chapter describes various methods of tying XML data structures into your LZX application. See Chapter 36, Data, XML, and XPath for discussion of some of the concepts used in this chapter. For a gentle introduction to databinding and manipulation in OpenLaszlo applications, you may start with the tutorial: Chapter 11, Introduction to Databinding
By "databinding" we mean the automatic association of a value in an XML data structure with an element in the LZX view hierarchy of the application. This chapter explores various aspects of databinding and manipulation in OpenLaszlo applications. Before going into specific details, we'll start with a conceptual overview of how data is represented in LZX applications, and the APIs for manipulating it.
A dataset (lz.dataset
) is two things:
Firstly, it is the client side store for XML data. It's where a single XML document lives in an OpenLaszlo application.
Secondly, it's the mechanism by which OpenLaszlo applications make HTTP GET or POST requests.
An lz.DataElement
is the LZX class that represents a single XML data tag in OpenLaszlo applications. lz.DataElements are usually kept in a
dataset, although data-bound views can get pointers to them even if they are not in a dataset. Inside of a dataset, lz.DataElements
are linked in a tree-like structure, but that doesn't mean to say that an lz.DataElement must go inside of a dataset.
lz.DataElement is a subclass of lz.DataNode
, as is lz.DataText.
.
Finally, note that a dataset is a subclass of lz.DataElement, which means that all of the methods which work on lz.DataElements also work on lz.datasets, although the usual method of manipulating datasets is with datapaths and datapointers, as explained below.
As we have said, all data in OpenLaszlo applications is in XML format. There are two related but distinct ways of using and manipulating that data in OpenLaszlo applications; that is, there two API models:
The DOM model—in which the APIs allow you to directly manipulate elements of a Document Object Model using DOM conventions.
The DataPointer model—in which the APIs allow you to position a logical cursor within the dataset using XPATH syntax
These two categories of APIs have similar functionality with large areas of overlap. However, there are some things that can only be done (or can best be done) using one specific approach (and not the other). This means that in many situations there are two logically distinct ways to achieve the same result. Learning to master data manipulation in LZX is a matter of becoming fluent in both approaches and knowing when to use each.
The Document Object Model is, according to the W3C specification, "a platform- and language-neutral interface that will allow programs and scripts to dynamically access and update the content, structure and style of documents." In the LZX context, the "document" is that lz.DataNode.
lz.DataNode
is the base class for the classes that represent LZX's hierarchical data format. An lz.DataNode
comprises lz.DataElement
s.
An lz.DataElement represents a node in a hierarchical dataset. An lz.DataElement can contain other lz.DataElements, or
lz.DataText
, which represents a text node.
More
advanced data manipulation in OpenLaszlo applications employ the various methods on the lz.DataElement class, such as
appendChild()
, getNextSibling()
, and so forth. These classes can only be created in script, not by tags. For tag-based data manipulation, use <dataset>
and the related concepts of datapointers and datapaths.
In addition to lz.DataNodes, which can only be manipulated in script, LZX includes the notions of
<datapath>
and <datapointer>
, which provide a convenient, tag-based
mechanism for typical data manipulation. By using datapointers to move through the data, you control the behavior of views
that are bound to that data.
Data in OpenLaszlo applications can be declared with a tag, or built up using procedural (script) APIs. The script APIs operate
on lz.DataNode
s.
All declaratively-declared data in OpenLaszlo applications is contained within one or more datasets. The content of a dataset is an XML fragment with a single root node, but without the XML declaration. A given dataset usually represents a single conceptual set that may or may not be modified or reloaded during the execution of the application.
You declare a dataset in your application using the <dataset>
tag. The name of the dataset is used in the datapath
attribute of a view, as will be explained below.
Datasets can be embedded directly in applications, constructed at runtime, or procured from remote servers. A dataset may be declared on the canvas, in which case it is visible to the entire application, or it may be declared within a class, in which case it is visible to the members of that class.
To embed a dataset directly
in an OpenLaszlo application, you use the <dataset>
tag as
below. In this example, you can get access to the given dataset by referring to
canvas.shelf
.
Example 37.1. Embedding data in an OpenLaszlo application
<canvas> <dataset name="shelf"> <bookshelf> <book binding="paperback"> <title>Acts of the Apostles</title> <author>John F.X. Sundman </author> <publisher>Rosalita Associates </publisher> <price>15.00</price> <year>1999</year> <category>thriller</category> <rating>4.5 </rating> </book> <book binding="casebound"> <title>Shock</title> <author>Robin Cook </author> <publisher>Putnam </publisher> <price>24.95</price> <year>2001</year> <category>thriller</category> <rating>3.5 </rating> </book> <book binding="paperback"> <title>Cheap Complex Devices</title> <editor>John Compton Sundman </editor> <publisher>Rosalita Associates </publisher> <price>11.00</price> <year>2002</year> <category>metafiction</category> <rating>5.0 </rating> </book> </bookshelf> </dataset> </canvas>
This style of dataset inclusion is called local data in
that the data is included locally in the application, rather than
being retrieved from a remote data source or web service. Data can be
included from a remote source by specifying the
src
attribute as follows:
Example 37.2. Dataset from a remote source
<canvas
width
="100%
" height
="400
">
<dataset
name
="menu
" src
="http://www.w3schools.com/xml/simple.xml
" request
="true
"/>
<simplelayout
axis
="y
"/>
<button
onclick
="t.setAttribute('text', canvas.menu.serialize())
">Show XML data
</button
>
<inputtext
multiline
="true
" width
="${canvas.width}
" bgcolor
="0xa0a0a0
" id
="t
" height
="300
"/>
</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 this example the OpenLaszlo application, when it starts up, makes a
HTTP request for the url,
http://www.w3schools.com/xml/simple.xml
and populates the
dataset named menu
with the XML returned. You can
click the button to see the serialized contents of the dataset.
The src
attribute should be a well-formed
URL that points to the back-end data source that will produce the
data. This may be an absolute or relative URL. (All requests made
for relative URLs are relative to the application's URL.) The URL may
point to a static XML file or a server-side processor (such as JSP, ASP,
PHP, and so on) that produces XML data.
The src
attribute of the <dataset>
element specifies whether the data is compiled into the
application or fetched at runtime:
If the src attribute is a URL, the value of the dataset is the XML data that a request to the URL named by the src attribute returns when the application is run.
If the src attribute is a pathname, the value of the dataset is the content of the XML file that the pathname refers to, and is compiled into the application.
If the src attribute is not present, the value of the
dataset is the content of the <dataset>
element.
The data within a dataset is accessed using a <datapointer>
or a instance of one of its subclasses.
A dataset is an instantiation of the lz.dataset
class. An lz.dataset
is a JavaScript object that provides a Document Object
Model (DOM) API for accessing, manipulating, and creating
XML elements and attributes in memory. These APIs are discussed in
Chapter 37, Data Access and Binding. The dataset also has APIs that
pertain to data transport.
The datapath
of the <text>
tag binds it to the data.
Datapaths use XPath attributes to navigate through the XML data. So the name of the dataset to use goes before
the colon myData:
, followed by the nodes, separated by forward slashes (/). The square brackets provide
a (one-based) space to enter which sibling node we want. [1] is implied, so the above example could be rewritten
without any "[1]"s.
The /text()
path segment is unnecessary with the
datapath
attribute.
So far we've used the <text>
tag in conjunction with a single datapath.
If we wanted to present tabular information, this would mean each text element would need its own
datapath, and would be cumbersome and difficult to write. Instead let's make a quick table, by giving
a <view>
a datapath:
Example 37.3. Assigning a datapath to a view
<canvas
height
="80
" width
="100%
">
<dataset
name
="myData
">
<myXML
>
<person
show
="simpsons
">
<firstName
>Homer
</firstName
>
<lastName
>Simpson
</lastName
>
</person
>
<person
show
="simpsons
">
<firstName
>Marge
</firstName
>
<lastName
>Simpson
</lastName
>
</person
>
<person
show
="simpsons
">
<firstName
>Montgomery
</firstName
>
<lastName
>Burns
</lastName
>
</person
>
</myXML
>
</dataset
>
<view
name
="rowOfData
" datapath
="myData:/myXML[1]/person[1]
">
<simplelayout
axis
="x
"/>
<text
datapath
="firstName/text()
"/>
<text
datapath
="lastName/text()
"/>
<text
datapath
="@show
"/>
</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 ****************************************************** -->
The datapath of the entire rowOfData
view has now become Homer's person
node.
The child elements of rowOfData
inherit this, so their datapaths can be referenced relatively.
In the above example we used a single rowOfData
node. Next, we shall use a range of all of the nodes:
Example 37.4. Range of nodes
<canvas
height
="80
" width
="100%
">
<dataset
name
="myData
">
<myXML
>
<person
show
="simpsons
">
<firstName
>Homer
</firstName
>
<lastName
>Simpson
</lastName
>
</person
>
<person
show
="simpsons
">
<firstName
>Marge
</firstName
>
<lastName
>Simpson
</lastName
>
</person
>
<person
show
="simpsons
">
<firstName
>Montgomery
</firstName
>
<lastName
>Burns
</lastName
>
</person
>
</myXML
>
</dataset
>
<view
name
="myTable
">
<simplelayout
axis
="y
"/>
<view
name
="rowOfData
" datapath
="myData:/myXML[1]/person
">
<simplelayout
axis
="x
"/>
<text
datapath
="firstName/text()
"/>
<text
datapath
="lastName/text()
"/>
<text
datapath
="@show
"/>
</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 ****************************************************** -->
Whichever tag contains the datapath
attribute will get repeated as
often as is necessary.
Remember that datapaths bind themselves to a view, so if the data changes, so will the view.
The source for a dataset may be anything that returns XML, including sources elsewhere on the web. For instance, the source may be a URL for a .jsp or .php program that generates XML data "on the fly." This is a typical architecture for OpenLaszlo applications. The table below highlights ways of categorizing datasets according to where the data comes from and how it is integrated into the application.
How is it included? | When is it loaded? | Syntax |
---|---|---|
Embedded | Compile-time |
<dataset name="myData"> <myXML> <!-- ... other XML tags ... --> </myXML> </dataset> |
Included | Compile-time |
<dataset name="myData" src="myXMLDoc.xml"/> |
HTTP data | Runtime |
<dataset name="myData" request="true" type="http" src="myXMLDoc.xml" /> |
Embedded data is XML between the <dataset>
tags. When the OpenLaszlo Compiler compiles the application, the data is bound into it. The data can still be changed after
the application runs.
Included data is essentially the same as embedded data, except that the XML itself is kept in a separate file. The size of the initial download will be the same as with embedded data.
It is locally referenced via the filesystem, so it can be placed in other directories. Included data is static.
Remote data goes over HTTP, which means it can (but doesn't have to) be dynamic. If it is static, then the only difference
between it and included or embedded data is that it is downloaded after the application loads. The type="http"
attribute tells the OpenLaszlo Server that this is an HTTP request. The requests can be either GET or POST.
There are several points at which the client makes requests for the data:
In the table above, we referenced a file locally (myXMLDoc.xml), but we could have done it absolutely, or we could have hit
a server-side script (PHP, ASP, JSP or some CGI) that returned an XML document. We could add the query string to the <dataset>
tag:
<dataset name="myData"
src="http://www.myServer.com/cgi-bin/myXMLDoc.cgi?return=addresses"/>
The type="http"
attribute gets implied when the src
attribute contains "http://
".
If specified on the canvas, datasets are visible to and accessible by the entire application. Datasets can also be local to a class.
Datasets will automatically name themselves localdata
if a name is not specified
Local datapath syntax is datapath="local:reference.to.dataset.relative.to.parent:/path"
The name of the dataset can be omitted from the datapath if the dataset name is the default 'localdata', e.g. 'local:classroot:/' can be used instead of 'local:classroot.localdata:/' for a dataset named localdata in the classroot
Here is a simple program that illustrates use of local datasets (the file testdata.xml, a sample XML file is included in this directory also.)
Example 37.5. local datasets
<canvas
width
="100%
" height
="300
">
<debug
fontsize
="12
"/>
<include
href
="lztest/xmlequals.lzx
"/>
<view
layout
="axis: y
">
<dataset
name
="ds
" src
="http://...
"/>
<text
datapath
="this.ds:/record/text()
"/>
</view
>
<class
name
="myclass
" layout
="axis: y
">
<dataset
name
="ds
" src
="http://...
"/>
<text
datapath
="this.ds:/record/text()
"/>
</class
>
<myclass
/>
<myclass
/>
<dataset
name
="gdata
" src
="resources/testdata.xml
"/>
<simplelayout
spacing
="2
"/>
<view
name
="nodatanoname
" layout
="axis: y
" bgcolor
="#cccccc
">
<handler
name
="onclick
">
Debug.debug("%w", this.localdata);
</handler
>
<dataset
/>
<text
>empty local dataset with no name
</text
>
</view
>
<view
name
="nodata
" layout
="axis: y
" bgcolor
="#cccccc
">
<handler
name
="onclick
">
Debug.debug("%w", this.lds);
</handler
>
<dataset
name
="lds
"/>
<text
>empty local dataset
</text
>
</view
>
<view
name
="somedata
" layout
="axis: y
" bgcolor
="#cccccc
">
<handler
name
="onclick
">
Debug.debug("%w", this.lds);
</handler
>
<dataset
name
="lds
">
<foo
>bar
</foo
>
</dataset
>
<text
>local dataset
</text
>
<handler
reference
="lds
" name
="oninit
"><![CDATA[
Debug.debug("somedata test data loaded %w", this);
if (this.lds.serialize() != '<lds><foo>bar</foo></lds>') {
Debug.error("somedata serialized data does not match expected value");
}
]]>
</handler
>
</view
>
<view
name
="filedata
" layout
="axis: y
" bgcolor
="#cccccc
">
<handler
name
="onclick
">
Debug.debug("%w", this.lds);
</handler
>
<dataset
name
="lds
" src
="resources/testdata.xml
"/>
<text
>local dataset compiled in from external file
</text
>
<handler
reference
="lds
" name
="oninit
"><![CDATA[
Debug.debug("filedata test data loaded %w", this);
if (! xmlstringequals(this.lds.serialize(), '<lds><persons><person id="1"><firstName>Dan</firstName><lastName>McGowan</lastName><modifyDate>3/25/05</modifyDate><address code="ML" id="1"><line1>2210 North 184th Street</line1><line2/><city>Shoreline</city></address></person><person id="2"><firstName>Barry</firstName><lastName>Bonds</lastName><modifyDate>3/25/05</modifyDate></person><person id="3"><firstName>Jeff</firstName><lastName>Beck</lastName><modifyDate>3/25/05</modifyDate></person></persons></lds>')) {
Debug.error("filedata serialized data does not match expected value");
}
]]>
</handler
>
</view
>
<view
name
="remotedata
" layout
="axis: y
" bgcolor
="#cccccc
">
<handler
name
="onclick
">
Debug.debug("%w", this.lds);
</handler
>
<dataset
name
="lds
" src
="resources/testdata.xml
" type
="http
" request
="true
"/>
<text
>local dataset loaded at runtime
</text
>
<text
datapath
="local:parent.lds:/persons/person/firstName/text()
" onclick
="Debug.debug('%w', this.datapath)
"/>
<handler
reference
="lds
" name
="ondata
"><![CDATA[
Debug.debug("remotedata test data loaded %w", this);
if (! xmlstringequals(this.lds.serialize(), '<lds><persons><person id="1"><firstName>Dan</firstName><lastName>McGowan</lastName><modifyDate>3/25/05</modifyDate><address id="1" code="ML"><line1>2210 North 184th Street</line1><line2/><city>Shoreline</city></address></person><person id="2"><firstName>Barry</firstName><lastName>Bonds</lastName><modifyDate>3/25/05</modifyDate></person><person id="3"><firstName>Jeff</firstName><lastName>Beck</lastName><modifyDate>3/25/05</modifyDate></person></persons></lds>')) {
Debug.error("remotedata serialized data does not match expected value");
}
]]>
</handler
>
</view
>
<view
name
="remotedatarelative
" layout
="axis: y
" bgcolor
="#cccccc
" datapath
="local:lds:/persons/
" visible
="true
">
<handler
name
="onclick
">
Debug.debug("%w", this.lds);
this.datapath.setXPath(this.datapath.xpath);
</handler
>
<dataset
name
="lds
" src
="resources/testdata.xml
" type
="http
" request
="true
"/>
<text
>local dataset loaded at runtime and relative datapath - datapath doesn't resolve because dataset doesn't exist yet. click to reparse xpath
</text
>
<text
datapath
="person/firstName/text()
" onclick
="Debug.debug('%w', this.datapath)
"/>
</view
>
<class
name
="localdatatest
">
<dataset
/>
<view
datapath
="local:classroot:/
">
<simplelayout
/>
<handler
name
="onclick
">
this.datapath.addNode('child', 'Click to remove this node', {});
</handler
>
<text
>Click to add a node to my local dataset
</text
>
<text
x
="10
" datapath
="child/text()
" onclick
="this.datapath.deleteNode();
"/>
</view
>
</class
>
<class
name
="redlocaldatatest
" extends
="localdatatest
" bgcolor
="red
"/>
<localdatatest
/>
<localdatatest
/>
<redlocaldatatest
/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
When the application's lz.dataset
receives the data, the
ondata
event is sent. In the case that an error occured
in communicating with the back-end (which may be proxied by the OpenLaszlo Server in proxied applications, or direct, in SOLO
applications), an onerror
event is
sent instead. And, if there is a timeout (currently hard-coded at 30
seconds) in communicating with the back end, an ontimeout
event is sent. The OpenLaszlo Runtime guarentees that each request generates
exactly one of ondata
, onerror
, or
ontimeout
.
Datasets support both HTTP GET and POST methods for communicating
with the OpenLaszlo Server and back-end servers. The default is GET but this can be
changed with the lz.dataset.setQueryType()
API. In
general, requests with large query parameters should be sent via
POST.
In general, the OpenLaszlo Server proxies HTTP request and response headers to and from the back-end. However, certain headers are specifically omitted or modified.
Note that response headers are not available to SOLO applications.
The OpenLaszlo Server proxies all "Cookie" request headers and all
"Set-Cookie
" response headers. Because of
the domain name restrictions on cookies, the OpenLaszlo Server can only properly
proxy these cookie headers when the back-end host is in the same
domain (or a subdomain) or the OpenLaszlo host. For more on this topic, see Chapter 44, Cookies and Sessions
<XMLHTTPRequest>
implements XMLHttpRequest as specified by the what-wg
consortium. Basically, this class allows you to fetch XML data from a URL, and so it is essentially equivalent to the lz.dataset
API (or the <dataset>
tag.) It is provided as a convenience to developers who are familiar with its syntax from its use in AJAX applications.
Here is an example of the XMLHTTPRequest class.
Example 37.6. XMLHTTPRequest
<canvas
width
="100%
" height
="600
" debug
="true
">
<include
href
="rpc/ajax.lzx
"/>
<script
>
var req = null;
function processReqChange() {
Debug.debug("processReqChange: req.readyState %w", req.readyState);
// only if req shows "loaded"
if (req.readyState == 4) {
// only if "OK"
if (req.status == 200) {
Debug.debug("req.status %w", req.status);
Debug.debug("req.responseText: %w", req.responseText);
Debug.debug("req.responseXML: %w", req.responseXML);
Debug.debug("req.getAllResponseHeaders: %w", req.getAllResponseHeaders());
} else {
Debug.error("There was a problem retrieving the XML data: %w",
req.statusText);
}
}
}
function loadXMLDoc(url) {
// branch for native XMLHttpRequest object
req = new lz.XMLHttpRequest();
req.onreadystatechange = processReqChange;
req.open("GET", url, true);
req.setRequestHeader('X-Test', 'one');
req.setRequestHeader('X-Test', 'two');
req.send(null);
}
</script
>
<simplelayout
spacing
="4
"/>
<edittext
id
="in1
">example.xml
</edittext
>
<button
onclick
="loadXMLDoc(in1.text)
">Load Data
</button
>
<button
onclick
="loadXMLDoc('badurl')
">Test Error Handling, this should fail
</button
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008-2011 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
In SOLO applications, the XMLHTTPRequest
class does provide one capability that is not currently available from datasets; that is, you can get the raw text of the
XML as a string, before it is parsed. You do this using the responseText()
method. This capability is only available in SOLO applications.
Note that by accessing a URL in this way you can fetch data that is not XML, which may come in handy in some situations. However, since LZX is predicated on the XML data model, in general you shouldn't expect to be using this technique very much.
Also, in SOLO deployed applications, the XMLHTTPRequest
class departs from the what-wg specification in these ways:
HTTP headers are not settable
response headers are not accessible
you cannot send raw POST data
you cannot send repeated query args in a POST using LoadVars
Username/password HTTP Auth args to send() are not supported.
We introduced datapaths first, which are extensions of datapointers, but they can become cumbersome. A datapointer points to just one place of the dataset at a time, but can be moved around -- you can have multiple datapointers, each pointing to a different part of a dataset.
Datapointers are not bound to views like datapaths are, but they do have a place in the view hierarchy—that is, they "know about" parents and children.
You will use a datapointer when you need to operate on the data in some way. For example, using the same format of data as in the previous examples, say you wanted to find all the people who were in the South Park show:
Example 37.7. Manipulating datapointers
<canvas
height
="180
" width
="100%
" debug
="true
">
<dataset
name
="myData
" src
="resources/myShowData.xml
"/>
<datapointer
xpath
="myData:/
" ondata
="processData()
">
<method
name
="processData
">
this.selectChild(2);
do {
if (this.xpathQuery( '@show' ) == 'south park') {
Debug.debug("%w", this.xpathQuery('lastName/text()'));
}
} while (this.selectNext());
</method
>
</datapointer
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
For brevity's sake, we are writing to the debugger, and we are including the data from a local file.
The selectChild(2)
method call selects the <myXML>
node, then the South Park <person>
node -- it selects the second-depth node because of the depth argument "2" we passed it (otherwise it would default to 1.
The selectNext
method call returns true
as long as an XML node was successfully selected (i.e. until there aren't any more). We exploit this by using it in a do
loop, so that the same iteration occurs for every …
while<person>
node.
We could also have given the <datapointer>
onerror
and ontimeout
event handlers to capture any problems.
A <dataset>
provides a way to
encapsulate arbitrary XML data in an OpenLaszlo application. Depending on
the source of the data, datasets can be static or dynamic. When a
dataset is explicitly declared with type="http"
, the
value of its src
is interpreted as an URL and
the dataset is populated with data at runtime. If the
src
attribute is absent, the data it represents
is expected to be contained within the <dataset>
tags, and thus also compiled into the application.
When we say that HTTP datasets are dynamic, we mean that you can
repopulate them programmatically by calling the
doRequest()
method of the dataset object, or if the
request
attribute is set to true, by changing
the URL of the dataset when one of the setSrc()
,
setQueryString()
, or setQueryParam()
methods is called.
When a dataset is defined as an immediate child of <canvas>
or <library>
, it can be referenced
anywhere in the code through the datasets
property of canvas, i.e. canvas.datasets['mydset']
, or
simply by its name (it is globally visible):
Example 37.8. Explicitly defined datasets
<canvas
debug
="true
" height
="200
" width
="100%
">
<debug
height
="150
"/>
<dataset
name
="mydset
" src
="http:?lzt=xml
"/>
<dataset
name
="week
">
<day
>Sunday
</day
>
<day
>Monday
</day
>
<day
>Tuesday
</day
>
<day
>Wednesday
</day
>
<day
>Thursday
</day
>
<day
>Friday
</day
>
</dataset
>
<script
>
Debug.debug("%w", mydset);
Debug.debug("%w", canvas.datasets['mydset']);
Debug.debug("%w", week)
</script
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Datasets can also be created at runtime in script by calling the constructor for the lz.dataset
:
var dset = new lz.dataset(null, {name: 'mydset'})
. The first argument to the constructor is
the dataset's parent node, which is the <datasource>
that encloses this dataset; this
parameter is allowed to be null — in this case a datasource will be created implicitly.
The LZX event system allows you to insert custom data
handling into the application as needed. This is typically done by
overriding the applyData()
method of the databound
node, by providing a handler for the ondata
event on
the datapointer or datapath, or by defining a $path
constraint on an expression-type attribute and processing changes to
the attribute's value with the
on
handler.
attribute_name
The applyData()
method is called on any node that is
declared with a datapath that matches a terminal selector, such as
text()
or @
when the data it
matches is changed. The argument passed to the method is the string
the data represents. Use the attribute
ondata
event if the node
is bound to a datapath that matches a data node (see below).
Example 37.9. Overriding applyData
<canvas
height
="150
" width
="100%
">
<dataset
name
="colors
">
<value
>red
</value
>
<value
>green
</value
>
<value
>olive
</value
>
<value
>yellow
</value
>
<value
>blue
</value
>
<value
>teal
</value
>
</dataset
>
<simplelayout
spacing
="10
"/>
<view
name
="swatch
" width
="200
" height
="30
" datapath
="colors:/value[1]/text()
">
<method
name
="applyData
" args
="v
">
acceptAttribute('bgcolor', 'color', v);
display.setAttribute('text', v)
</method
>
</view
>
<text
name
="display
" resize
="true
"/>
<button
text
="Change view color
">
<attribute
name
="ind
" value
="$once{1}
"/>
<handler
name
="onclick
">
if (++ind == 7) ind = 1
swatch.setAttribute('datapath', 'colors:/value[' + ind + ']/text()')
</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 ****************************************************** -->
Attributes of a node can be bound to data explicitly by using the $path{}
constraint syntax. The expression
inside the curly brackets must evaluate to a string, which is interpreted as a relative XPath expression.
If you
need to use an absolute path in the expression, you could instead constrain the attribute to the result of an
xpathQuery()
call: visible="dp.xpathQuery('mydset:/record/row[1]/@visible')"
. A
limitation of the $path{}
constraint is that the expression it contains is evaluated only at the
initialization time, that is, an expression such as $path{'mynode[' + i + ']/@attr'}
will behave like a
$once{}
constraint.
$path bindings are two-way, so calling updateData()
on a node's datapath will store the current value for that attribute back in the dataset.
Example 37.10. $path constratint bindings
<canvas
width
="100%
" height
="150
">
<dataset
name
="sizes
">
<value
>200
</value
>
<value
>150
</value
>
<value
>100
</value
>
</dataset
>
<button
text
="Shrink me
" datapath
="sizes:/value[1]
">
<attribute
name
="width
" value
="$path{'text()'}
"/>
<handler
name
="onclick
">
if (!datapath.selectNext()) this.setAttribute('text', 'Done')
</handler
>
</button
>
<button
y
="40
" text
="Stretch me
" datapath
="sizes:/value[1]
">
<attribute
name
="width
" value
="$path{'text()'}
"/>
<handler
name
="onclick
">
datapath.setNodeText(Number(datapath.getNodeText()) + 20)
</handler
>
</button
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Assigning a node's attribute value by $path
constraint results in replication of the node as many times as necessary to correspond to each item in the dataset
.
In this example, there are three items in the dataset
, so the box
node replicates three times.
Example 37.11. Assigning an attribute value by $path constraint
<canvas
width
="100%
" height
="60
">
<dataset
name
="boxes
">
<boxes
>
<box
color
="0xFF0000
"/>
<box
color
="0x00FF00
"/>
<box
color
="0x0000FF
"/>
</boxes
>
</dataset
>
<simplelayout
axis
="x
" spacing
="10
"/>
<class
name
="coloredbox
" height
="50
" width
="50
" bgcolor
="$path{'@color'}
"/>
<coloredbox
datapath
="boxes:/boxes/box
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
JavaScript provides easy casting from string to numeric data types. The $path{...} syntax is defined to return a string, which can be used to assign a value to an attribute. You can combine attributes to preform calculations based on strings returned from a $path{...} inquiry on a dataset.
Say, for example that you had a dataset that contained temperatures in Fahrenheit that you wished to convert to Centigrade. You would have to create an intermediate attribute that binds to the (possibly replicated) path constraint and then bind your text field to a calculation on that attribute. Something like:
Example 37.12. calculations on $path{} values
<attribute name='intermediate' value="$path{'degf'}" \> <attribute name='text' value="${Number(intermediate) * 5 / 9}" />
The data
property is a shorthand way of accessing data referenced by a datapointer or a datapath.
For convenience, a datamapped node gets its data property set to that of the datapath it is bound to. In the example
below, the color view changes its properties as the data field to which they are constrained follows the "order"
attribute of the nodes in the dataset. Note that the data is a string value of the attribute; this is the case when
the XPath matches an operator. The datapath of the enclosing view, however, refers to entire node in the dataset,
and its data property contains an instance of lz.DataNode
that the XPath references. This is
evident from the debugger output.
Example 37.13. Using the data property
<canvas
height
="155
" width
="100%
">
<debug
x
="50%
" width
="45%
" y
="5%
" height
="95%
"/>
<dataset
name
="onion
">
<layer
order
="1
"><layer
><layer
>core
</layer
></layer
></layer
>
</dataset
>
<view
datapath
="onion:/layer
" layout
="spacing: 5
">
<button
text
="Peel next layer
">
<handler
name
="onclick
">
with (parent.datapath) {
// Go down one layer
selectChild();
// If there are no more layers to go, disable ourself
if (getNodeCount() == 0) this.setAttribute('enabled', false);
// Finally, set the order attribute of the new layer to one more than it's parent
setNodeAttribute('order', String(Number(p.parentNode.attributes['order']) + 1));
}
</handler
>
</button
>
<attribute
name
="order
" value
="$path{'@order'}
" type
="number
"/>
<view
width
="${100 / parent.order}
" height
="${100 / parent.order}
" bgcolor
="0x09d055
" opacity
="${Math.min(1, parent.order / 3)}
" visible
="${parent.order != null}
"/>
<text
text
="${this.escapeText(this.formatToString('order: %w, data: %#w', parent.order, parent.datapath.data))}
"/>
</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 ***************************************************** -->
For a datapointer, a datapath, or a datamapped node, the ondata
event is sent whenever the data it
is bound to changes. This implies that for XPaths that select a data node, ondata event is sent only when the
datapointer is set to point to a different node. If the pointer selects an operator, i.e. "text()"
or "@attr"
it is
also sent when the text or the attribute it matches has changed. The argument sent with the event is the current
value of the data property of the node or the datapointer (see previous section).
The example below makes use of the ondata event sent by a temporary
datapointer to calculate the average of a sequence of accumulated
numbers and display it. Then event is sent when the
setXPath()
method is invoked on the
datapointer. Typically, a problem like this would be easier to program
using JavaScript's built-in arrays, but this version illustrates the
data-driven approach. It also introduces the concept of data
replication, which is explained in more detail later in this
document.
Example 37.14. Ondata event
<canvas
width
="100%
" height
="200
">
<dataset
name
="numbers
"/>
<datapointer
name
="thetop
" xpath
="numbers:/
"/>
<datapointer
name
="numptr
">
<handler
name
="ondata
" args
="d
">
// d is lz.DataElement object
result.update(d.nodeName)
</handler
>
</datapointer
>
<simplelayout
spacing
="5
"/>
<text
>Type in a number and press the button or the Enter key
</text
>
<view
>
<simplelayout
spacing
="10
" axis
="x
"/>
<edittext
name
="input
">
<handler
name
="onkeyup
" args
="k
">
if ( k == 13 ) {
parent.bSend.compute();
}
</handler
>
</edittext
>
<button
name
="bSend
" text
="Add
">
<handler
name
="onclick
" method
="compute
"/>
<method
name
="compute
" args
="ignore=null
"><![CDATA[
if(parent.input.text.length > 0){
thetop.addNode(parent.input.text)
var end = thetop.xpathQuery('*/last()')
numptr.setXPath('numbers:/*[' + end + ']')
parent.input.clearText()
}
]]>
</method
>
</button
>
</view
>
<view
height
="100
">
<text
bgcolor
="0xcecece
" datapath
="numbers:/*/name()
"/>
<wrappinglayout
axis
="y
" spacing
="3
"/>
</view
>
<view
>
<attribute
name
="sum
" value
="$once{0}
"/>
<simplelayout
axis
="x
"/>
<text
><b
>AVG:
</b
></text
>
<text
id
="result
" fgcolor
="blue
" fontstyle
="bold
">
<method
name
="update
" args
="v
">
parent.sum += Number(v)
this.setAttribute('text', parent.sum / thetop.p.childNodes.length)
</method
>
</text
>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008-2011 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Recall that datapointer is an object that represents a pointer to a node in a lz.dataset
. The datapointer can be repositioned using either cursor movements calls such as selectNext()
, or by running an XPath request via setXPath()
.
Datapointers support a subset of the XPath specification, which uses a notation similar to the UNIX file-system to refer to nodes within a dataset. Once a datapointer is bound to a node in a dataset it will keep pointing to that node until it is moved. If the dataset is edited, the behavior of the datapointer will be controlled by its rerunxpath attribute. If this attribute is true (the default value), it will continue pointing to its current node as long as it is valid.
The rerunxpath
property of datapointer determines whether the XPath expression is re-evaluated every time the
contents of the dataset change. The default is false; if set to true, every time the dataset is edited, the XPath
binding is refreshed. In other words, the datapointer is assumed to be "constant" unless the rerunxpath
attribute
is true.
Example 37.15. Using rerunxpath attribute
<canvas
width
="100%
" height
="150
">
<dataset
name
="stack
">
<root
/>
</dataset
>
<datapointer
name
="thetop
" xpath
="stack:/root
"/>
<datapointer
xpath
="stack:/root/*[1]/name()
" rerunxpath
="true
">
<handler
name
="ondata
" args
="d
">
good_result.setAttribute('text', d)
</handler
>
</datapointer
>
<datapointer
xpath
="stack:/root/*[1]/name()
">
<handler
name
="ondata
" args
="d
">
bad_result.setAttribute('text', d)
</handler
>
</datapointer
>
<simplelayout
spacing
="5
"/>
<text
>Type in a string and press the button or the Enter key
</text
>
<view
>
<simplelayout
spacing
="10
" axis
="x
"/>
<edittext
name
="input
">
<handler
name
="onkeyup
" args
="k
">
if ( k == 13 ) {
parent.bAdd.handleclick();
}
</handler
>
</edittext
>
<button
name
="bAdd
" id
="ba
" text
="Push
">
<handler
name
="onclick
" method
="handleclick
"/>
<method
name
="handleclick
" args
="ignore
">
var n = parent.input.text;
if (thetop.p.childNodes.length == 0) {
thetop.addNode(n)
} else {
var ne = new lz.DataElement(n)
thetop.p.insertBefore(ne, thetop.p.getFirstChild())
}
parent.input.clearText()
</method
>
</button
>
<button
name
="bPop
" text
="Pop
">
<handler
name
="onclick
">
var last = thetop.xpathQuery('*[1]')
thetop.p.removeChild(last)
</handler
>
</button
>
</view
>
<view
height
="100
">
<text
bgcolor
="0xcecece
" text
="$path{'name()'}
">
<datapath
xpath
="stack:/root/*/name()
"/>
</text
>
<wrappinglayout
axis
="y
" spacing
="3
"/>
</view
>
<view
>
<simplelayout
axis
="x
" spacing
="5
"/>
<text
><b
>TOP:
</b
></text
>
<text
id
="good_result
" resize
="true
" fgcolor
="green
" fontstyle
="bold
"/>
<text
id
="bad_result
" resize
="true
" fgcolor
="red
" fontstyle
="bold
"/>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008-2011 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
This example illustrates the effect of the rerunxpath
property. The text fields at the bottom are updated when
the ondata event is sent by the datapointers intended to be bound to the first node in the dataset. However, the
first one is declared with rerunxpath="true"
, and therefore it points to the actual first data node, while the
second one remembers the node it was referencing initially, and never gets updated.
By default, a view that is databound will have a visibility
attribute of
collapse
,
which will cause it not to be visible when it has no data, that is, when the xpath does not match
any entries in the dataset.
However, sometimes it is desirable to override this behavior, specifying that a datamapped
view should be visible regardless of whether or not it has data. A typical example of this is a panel
containing placeholder views mapped to dynamically retrieved data records, that need to be visible at
all times. You do this by setting the view's visibility
attribute to visible
.
The following example allows you to explore the effect of different settings of the
visibility
attribute when the databinding of a view does and does not match. There are three 'stoplights',
the red bulb has visibility="hidden"
, the yellow bulb has visibility="collapse"
and the green bulb
has visibility="visible"
. This means the green light will always be on, the yellow light will be
on when there is data and the red light will never be on. When initially loaded, there is only
data for the first stoplight, the second and third stoplights have no matching data. Clicking on
the Add Data button will add another entry to the dataset
causing the second (and third if you
click again) stoplight to have data. The text describes the current state of each bulb's visibility
and visible
attributes. You can see the visible
attribute of the
second and third yellow lights change
when you Add Data. You can also press the Toggle Visibility on each light bulb to cycle through the
different visibility
settings to see how they affect the visible
attribute
depending on the presence or absence of data.
Example 37.16. Visibility of datamapped views
<canvas
width
="100%
">
<dataset
name
="mydata
">
<element
>#1
</element
>
</dataset
>
<class
name
="light
">
<!-- Attributes passed down to bulb -->
<attribute
name
="bulbColor
" type
="color
"/>
<attribute
name
="bulbVisibility
" type
="string
"/>
<attribute
name
="bulbXpath
" type
="string
"/>
<simplelayout
axis
="x
" spacing
="5
"/>
<!-- Button to explore visibility settings -->
<button
onclick
="parent.toggleVisibility()
">Toggle Visibility
</button
>
<!-- Display to show data/visibility/visible -->
<text
name
="display
" font
="Monaco, Courier, fixed
"/>
<method
name
="updateDisplay
"><![CDATA[
display.format('data: %-4#w + visibility: %-11#w -> visible: %-7#w', bulb.data, bulb.visibility, bulb.visible);
]]>
</method
>
<!-- "bulb" that will be visible or not according to data binding and visibility settings -->
<text
name
="bulb
" width
="20
" height
="20
" bgcolor
="${classroot.bulbColor}
" visibility
="${classroot.bulbVisibility}
" datapath
="${classroot.bulbXpath}
" oninit
="classroot.updateDisplay()
" ondata
="classroot.updateDisplay()
"/>
<!-- Method to rotate through visibility choices -->
<method
name
="toggleVisibility
">
switch (bulb.visibility) {
case 'visible':
bulb.setAttribute('visibility', 'collapse');
break;
case 'collapse':
bulb.setAttribute('visibility', 'hidden');
break;
case 'hidden':
bulb.setAttribute('visibility', 'visible');
break;
}
updateDisplay();
</method
>
</class
>
<!-- Demo that shows three different visibility settings for the same xpath -->
<class
name
="demo
" bgcolor
="gray90
">
<simplelayout
axis
="y
" spacing
="2
"/>
<attribute
name
="lightXpath
" type
="string
"/>
<light
bulbColor
="red
" bulbVisibility
="hidden
" bulbXpath
="${classroot.lightXpath}
"/>
<light
bulbColor
="yellow
" bulbVisibility
="collapse
" bulbXpath
="${classroot.lightXpath}
"/>
<light
bulbColor
="green
" bulbVisibility
="visible
" bulbXpath
="${classroot.lightXpath}
"/>
</class
>
<simplelayout
axis
="y
" spacing
="7
"/>
<!-- Demo that shows three xpaths only the first of which matches (and hence has data) -->
<demo
lightXpath
="mydata:/element[1]/text()
"/>
<demo
lightXpath
="mydata:/element[2]/text()
"/>
<demo
lightXpath
="mydata:/element[3]/text()
"/>
<!-- Button to add data so second and third xpaths above will get data -->
<attribute
name
="next
" value
="2
"/>
<button
onclick
="if (canvas.next < 4) {mydata.getPointer().addNode('element', '#' + canvas.next++)}
">
Add Data
</button
>
<button
onclick
="if (canvas.next > 1) {var p = mydata.getPointer(); p.setXPath('element[' + --canvas.next + ']'); p.deleteNode();}
">
Remove Data
</button
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008, 2009 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
Because data contained by static datasets is compiled into the application, it is available immediately.
Therefore any datapointers that have a static dataset as part of their paths will send the ondata event before
any children of the canvas are instantiated by the runtime. This is important to remember if all changes in the
data need to be reflected in the application's interface. In other words, when writing handlers for ondata
, one
should be careful not to reference views that may not have been fully initialized.
In the example below, we attempt to expand a datamapped tree in response to the ondata event, but the tree is
backed by the same data and is not yet fully initialized, since there was not enough time for databinding to occur.
Thus, the openChildren()
call on the first tree fails and the tree is not expanded. The solution is to move the
call to the handler for oninit
, which will be sent when the tree and all of its children (also trees)
have finished initializing. This result of this approach is that the call succeeds and the second tree is expanded.
Example 37.17. Proper handling of data updates
<canvas
height
="200
" width
="100%
">
<include
href
="lz/tree.lzx
"/>
<dataset
name
="filesys
" src
="resources/dirtree.xml
"/>
<class
name
="fs_tree
" extends
="tree
">
<datapath
xpath
="filesys:/entry/@name
"/>
<attribute
name
="autoscroll
" value
="true
"/>
<tree
datapath
="*
" text
="$path{'@name'}
" isleaf
="${this.datapath.xpathQuery('@type') == 'file'}
"/>
</class
>
<simplelayout
axis
="x
" spacing
="20
"/>
<view
width
="200
" height
="200
" clip
="true
">
<fs_tree
id
="myfs
"/>
<scrollbar
visible
="${scrollable}
"/>
</view
>
<view
width
="200
" height
="200
" clip
="true
">
<fs_tree
oninit
="toggleOpenAndFocus()
"/>
<scrollbar
visible
="${scrollable}
"/>
</view
>
<datapointer
xpath
="filesys:/
" ondata
="myfs.openChildren(true)
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
On the other hand, dynamic datasets have to fetch data externally, so they may not deliver it before the visual
elements that depend on it are in a stable state. This is why the logic in the code should only attempt to
use these datamapped elements in response to the ondata event or by overriding the applyData method (see above).
Note that the lz.dataset
object itself sends an ondata event whenever it receives new data; this is a convenient
way of synchronizing UI operations with arrival of data it is tied to. As stated above, static datasets cause ondata
to be sent as soon as they are instantiated.
Often, the lz.datapointer
API provides the most convenient way to traverse the hierarchy of nodes in a dataset. Below
is an example that recursively walks a dataset mapped to a tree by calling iterator methods on a datapointer initially
set to the top of the dataset, and adds a new node at each level.
Example 37.18. Using datapointer's iterator methods
<canvas
width
="100%
" height
="200
">
<include
href
="lz/tree.lzx
"/>
<dataset
name
="filesys
" src
="resources/dirtree.xml
"/>
<simplelayout
axis
="x
" spacing
="20
"/>
<view
width
="200
" height
="200
" clip
="true
">
<tree
name
="fs_tree
" open
="true
" datapath
="filesys:/entry/@name
" autoscroll
="true
">
<tree
datapath
="*
" text
="$path{'@name'}
" open
="true
" isleaf
="${this.datapath.xpathQuery('@type') == 'file'}
"/>
</tree
>
<scrollbar
visible
="${scrollable}
"/>
</view
>
<datapointer
name
="dptr
" xpath
="filesys:/entry
"/>
<button
text
="Add a dot dir
">
<handler
name
="onclick
">
// Allow to do this only once
if (!dptr.xpathQuery('*[@name = "."]'))
traverse(dptr);
</handler
>
<method
name
="traverse
" args
="top
">
var ne = new lz.DataElement('entry', {name: '.', type: 'dir'})
do {
if (top.xpathQuery('@type') == 'dir') {
var root = top.dupePointer();
// If this node has children, insert the dot before the first one
// and descend one level, otherwise, just add below self.
if (root.selectChild()) {
top.p.insertBefore(ne, top.p.getFirstChild())
traverse(root)
}
else top.p.appendChild(ne)
}
} while (top.selectNext())
</method
>
</button
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
As an essential part of their functionality, datapointers (and datapaths) can be set directly to data nodes.
The example below uses the setPointer()
method to set the target view's datapath to the node referenced by the
datapath of the selected view. In effect, this maps the details view to the contact currently selected in the
dataset.
Example 37.19. Using setPointer
<canvas
height
="150
" width
="100%
">
<dataset
name
="phonebook
" src
="resources/phonebook.xml
"/>
<simplelayout
axis
="x
" spacing
="20
"/>
<view
name
="contacts
" height
="150
" width
="120
">
<view
bgcolor
="0xe0e0e0
" datapath
="phonebook:/phonebook/contact
" onmouseover
="setAttribute('bgcolor', 0xc0c0c0)
" onmouseout
="setAttribute('bgcolor', 0xe0e0e0)
" onclick
="details.datapath.setAttribute('p', this.datapath.p)
">
<simplelayout
axis
="x
" spacing
="5
"/>
<text
datapath
="@firstName
" resize
="true
"/>
<text
datapath
="@lastName
" resize
="true
"/>
</view
>
<simplelayout
spacing
="5
"/>
</view
>
<view
id
="details
" width
="150
" height
="150
" bgcolor
="0xe0e0e0
" fgcolor
="blue
">
<datapath
/>
<text
datapath
="@firstName
"/>
<text
datapath
="@lastName
"/>
<text
datapath
="@phone
"/>
<text
datapath
="@email
"/>
<simplelayout
spacing
="5
"/>
</view
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
lz.datapath
is a subclass of lz.datapointer
, and therefore can be used in much the same way to iterate over data.
The example below treats the datapath of each of the replicated view as a pointer to the dataset that backs it.
The datapath of the enclosing view in this context is simply a pointer to the root of the dataset, and thus it
could be used to manipulate and add children nodes.
Example 37.20. Dereferencing datapaths
<canvas
height
="150
" width
="100%
">
<dataset
name
="busy
">
<Monday
order
="1
"/>
<Tuesday
order
="2
"/>
<Wednesday
order
="3
"/>
<Thursday
order
="4
"/>
<Friday
order
="5
"/>
</dataset
>
<dataset
name
="free
"/>
<simplelayout
axis
="x
" spacing
="20
"/>
<class
name
="schedule
" height
="150
" width
="100
">
<attribute
name
="title
" type
="string
"/>
<attribute
name
="target
"/>
<simplelayout
spacing
="5
"/>
<text
bgcolor
="white
" fgcolor
="blue
" text
="$once{parent.title}
"/>
<text
bgcolor
="0xe0e0e0
" onmouseover
="setAttribute('bgcolor', 0xc0c0c0)
" onmouseout
="setAttribute('bgcolor', 0xe0e0e0)
">
<datapath
xpath
="*/name()
" sortpath
="@order
" sortorder
="ascending
"/>
<handler
name
="onclick
">
parent.target.datapath.addNodeFromPointer(this.datapath)
this.datapath.deleteNode()
</handler
>
</text
>
</class
>
<schedule
name
="b
" target
="${f}
" title
="Busy
" bgcolor
="0xd0000a
" datapath
="busy:/
"/>
<schedule
name
="f
" target
="${b}
" title
="Free
" bgcolor
="0x00a000
" datapath
="free:/
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
There is an important restriction on usage of iterator methods with a datapath. If you move it by calling any
of select
or ...
()set
methods, its XPath is then removed, which means that any updates to underlying
data will not notify the datamapped UI element. Consider the following example.
XXX
Pointer()
Example 37.21. Datapath iteration
<canvas
height
="150
" width
="100%
">
<dataset
name
="phonebook
" src
="resources/phonebook.xml
"/>
<button
text
="Previous
" y
="125
" onclick
="details.datapath.selectPrev()
"/>
<view
name
="details
" options
="releasetolayout
" height
="150
">
<datapath
xpath
="phonebook:/phonebook/contact[1]
"/>
<text
datapath
="@firstName
"/>
<text
datapath
="@lastName
"/>
<text
datapath
="@phone
"/>
<text
datapath
="@email
"/>
<button
text
="Delete record
" onclick
="parent.datapath.deleteNode()
"/>
<simplelayout
spacing
="5
"/>
</view
>
<button
text
="Next
" y
="125
" onclick
="details.datapath.selectNext()
"/>
<resizelayout
axis
="x
" spacing
="10
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
You can move the datapath of the contact detail view in either direction, and the text fields will update
correctly. This is because their respective datapaths are unaffected by the scrolling (it only moves the
datapath of the containing view). However, deleting a contact will only result in an automatic update to the
text fields if no navigation has yet occurred. The solution is to reset the datapath's XPath to the proper value
by calling the setXPath()
method:
Example 37.22. Datapath iteration workaround
<canvas
height
="150
" width
="100%
">
<dataset
name
="phonebook
" src
="resources/phonebook.xml
"/>
<button
text
="Previous
" y
="125
">
<handler
name
="onclick
">
with (details.datapath) {
selectPrev()
}
</handler
>
</button
>
<view
name
="details
" options
="releasetolayout
" height
="150
">
<datapath
xpath
="phonebook:/phonebook/contact[1]
" rerunxpath
="false
"/>
<text
datapath
="@firstName
"/>
<text
datapath
="@lastName
"/>
<text
datapath
="@phone
"/>
<text
datapath
="@email
"/>
<button
text
="Delete record
" onclick
="parent.datapath.deleteNode()
"/>
<simplelayout
spacing
="5
"/>
</view
>
<button
text
="Next
" y
="125
">
<handler
name
="onclick
">
with (details.datapath) {
selectNext()
}
</handler
>
</button
>
<resizelayout
axis
="x
" spacing
="10
"/>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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.