第二十六章 数据存取与绑定
第五部分 数据绑定
本章将描述将XML数据结构连接到LZX应用程序中的各种方法。见25章 数据,XML和XPath 对在OpenFace 应用中的数据绑定与修改进行简单的讨论,以这些基本概念来跟深入的学习。
目录 |
数据绑定概述
数据绑定,意味着XML数据结构中的一个值,自动的关联LZX应用程序的视图结构中的一个元素。本章将描述在OpenLaszlo应用程序中有关数据绑定和操纵的各个方面。首先,我们概念性的介绍LZX应用程序怎么样表示数据的,以及操纵数据的相关API。
数据对象的类型
数据集<LzDataset> 有两层含义:
首先,客户端用它来存储数据。是OpenFace 应用程序中单个的XML文档存在的地方。
其次,它是OpenFace 应用程序进行HTTP GET或POST请求的机制。
一个LzDataElement是一个类,表现单个XML文档标签。数据元素(lzDataElements) 经常处在一个数据集中,尽管即使它们不在数据集中,数据绑定的视图也可以得到它们的指针。在数据集中,数据元素(LzDataElements)以树结构形式连接起来,但并不意味着数据元素(LzDataElement)就一定要处在一个数据集中。
和LzDataText一样,数据元素(LzDataElement)也是lzDataNode的子类。
而一个数据集(dataset)节点是一个LzDataElement的子类,也就意味着能作用在LzDataElement上所有的方法同样也适用于LzDataSet,尽管操纵数据集(DataSet)的方法通常是和datapath以及datapointer有关的。
数据交互的两种方法
正如前面所说,OpenFace 应用程序中所有的数据都是XML格式的。有两种相关但又截然不同的方法来使用和操纵OpenFace 应用程序中的数据;也就是有两种API模型:
DOM模型:其中的API允许你使用DOM协议,直接操纵文档对象模型(DOM)中的元素;
DataPointer模型:其中的API允许你在数据集中使用XPath语法来定位一个逻辑的游标(cursor)
这两种方法在具有某些相似的功能,但有时你也只能应用其中一种方法来解决。这就意味着在某些情况下,两者方法可以达到相同的效果。要熟练掌握LZX中的数据操纵,也就是要熟练掌握这两种方法,并且还要会选择使用哪种。
LzDataNodes 和DOM API
DOM根据W3C的定义:是一种允许程序和脚本动态的存取和更新文档内容、结构和样式的平台和语言的中立接口。在LZX背景下,文档即指 LzDatanode。
LzDatanode 是所有显示LZX层次性数据格式的类的基类。一个LzDatanode 由多个 LzDataElement组成。每一个LzDataElement 代表层次性数据集中的一个节点。一个LzDataElement可以包含其他的 LzDataElement、或 代表一个文本节点的LaDataText。OpenFace 应用程序中,更高级的数据操纵就要用到LzDataElement 的各种方法,比如 appendChild(),getNextSibling(),等等。这些类只能在脚本里创建,不能通过标签。基于标签的数据操纵,使用<dataset>和datapointer和datapath的相关概念。
数据指针 和数据路径
除了只能在脚本中应用的LzDataNode 外,LZX还包括<datapath>和<datapointer>标签,它们提供了一种传统的,基于标签的机制来对典型的数据进行操作。通过使用datapointer在数据中移动,您可以操纵那些和数据绑定的视图的行为。
OpenFace数据集合与数据节点
OpenFace应用程序中的数据可以通过标签来声明,也可以使用脚本API来建构,这些API对LzDataNode进行操作。
所有在应用程序中显式声明的数据都包含在一个或多个数据集中。数据集的内容是一个具有单根节点,但没有XML声明的的XML块。一个给定的数据集(dataset)通常表示单个的概念上的,在应用程序执行时能或不能被修改或重载的集合。
可以使用<dataset> 来声明一个数据集。后面将会描述,数据集的名称在视图的datapath属性中使用。
数据集(dataset)也可以直接嵌入应用程序中,可以是在运行时建构,也可以从远程服务器上获取。一个数据集可以在canvas中声明,这样它就对整个应用程序都是可见的;也可以在一个类中声明,这样它只对该类的成员可见。
如下面例子所示,将数据集(dataset)也直接嵌入应用程序中,使用<dataset>标签,你可以通过引用canvas.shelf来存取给定的数据集。
Example 26.1. 数据嵌入在OpenFace 应用程序中
<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>
这种包含数据的形式叫本地数据,即应用程序包含本地数据,而不是从远程数据源或web服务上获取。获取远程数据可以指定src属性。
Example 26.2. 远程获取数据
<canvas height="400" > <dataset name="menu" src="http://www.w3schools.com/xml/simple.xml" request="true"/> <simplelayout axis="y"/> <inputtext multiline="true" width="${canvas.width}" bgcolor="0xa0a0a0" id="t" height="300"/> </canvas>
在这个例子里,OpenLaszlo应用程序一启动时,就马上对url http://www.w3schools.com/xml/simple.xml 发出HTTP请求,然后将返回的XML数据移植到数据集(dataset)menu中。
src 属性
src属性必须是指向创建数据的后端数据源的标准形式的URL。可以是相对的URL)也可以是绝对的。(所以的请求都是对相对于应用程序的相对URL作用的)。URL可以指向一个静态的XML文件,也可以指向一个服务端的创建XML数据的处理器(如JSP,ASP,PHP等等)。
dataset>元素的src属性还指定了数据是直接编译进应用程序还是在运行时获取。
如果src属性是URL,则数据集的值是通过对该URL请求,并在应用程序运行时返回的XML数据。
如果src属性是一个路径名,则数据集的值是指该路经名所引用的XML文件的内容,它直接编译进入应用程序。
如果src属性没有设置,则数据集的值是<dataset>元素的内容。
数据集中的数据通过使用<datapointer>或它的子类的实例来存取。
一个数据集(dataset)是一个LzDataset类的实例。一个LzDataset是一个Javascript对象,用来提供在内存中进行存取,操作和创建XML元素和属性的DOM API。数据集也有用来进行数据传输的API。
解析datapath
<text>标签的datapath属性将其与数据绑定。
Datapaths 使用XPath属性来在XML数据中进行巡航。所以,数据集的名称放在冒号前 mydata:,然后是用斜线(/)分开的节点。方括号提供了一个空间来进入目标兄弟节点。默认为[1],所以在上面的例子中也可以加上[1]。
text() 路径段在datapath属性中不是必须的。
到目前为止,我们使用了<text>标签来和一个单一的datapath连接。如果我们需要显示表格信息,这就意味着每个文本(text)元素都需要自己的datapath,这样写起来就会比较麻烦。我们可以通过给定<view>一个datapath来快速建造一个表格。
Example 26.3. 为视图绑定datapath
<canvas height="80" width="500"> <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>
这样,整个rowofData视图的datapath现在变成Homer’s person节点。rowofData 的子元素继承了该节点,从而它们的datapath可以相对地进行引用。
多行数据
上面的例子只使用了单一的 rowofData 节点,下面,我们要使用一组节点:
Example 26.4. 节点排列
<canvas height="80" width="500" > <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>
任何包含datapath属性的标签都进行了重复创建。
注意,由于datapath与视图绑定后,如果数据改变,相应的视图也会跟着改变。
包含数据的方法
数据集的来源可以是任何返回XML的东西,包括网络上任何地方的来源。比如来源可以是一个指向产生XML数据的.jsp或.php程序页面的URL。这是OpenLaszlo应用程序的经典结构体系。下面的表精,根据数据的来源以及整合进应用程序的方式,对数据集进行了分类。
方式 载入时刻 语法
内嵌 编译时 <dataset name="myData"> <myXML> <!-- ... other XML tags ... --> </myXML> </dataset> 包含 编译时 <dataset name="myData" src="myXMLDoc.xml"/> HTTP数据 运行时 <dataset name="myData" request="true" type="http" src="myXMLDoc.xml" />
内嵌数据
内嵌数据是指<dataset>标签内的XML。当OpenLaszlo编译器编译程序时,数据就绑定了。而且在应用程序运行后仍可改变数据。
包含数据
包含数据本质上和内嵌数据是一致的,只不过XML是保存在一个分开的文件里。初始下载量和嵌入数据是相同的。
它是针对文件系统而本地引用的,所以它可以位于其他的目录。包含数据是静态的。
HTTP数据
远程数据通过HTTP传输的,也就意味着它可以(但不一定要)是动态的。如果它是静态的,那它和包含数据及内嵌数据唯一的不同点就是它是在程序运行后才下载的。type=“http”属性告诉OpenLaszlo服务器这是一个HTTP请求。可以是GET或POST。
关于客户端请求数据,有几点要注意:
如果数据集(dataset)的“repuest”属性为true,一旦应用程序加载,客户端马上请求数据;
每当查询字符串或数据集的URL改变了(分别使用LzHTTPDataset 对象的setQueryString() 或 setURL() 方法。)客户端也会请求数据。
当调用数据集的 doRequest()方法时,客户端也会请求数据。
上面的表格中,我们引用了本地一个文件(myXMLDoc.xml),但我们需要绝对引用,或者需要请求可以返回XML文档的服务端(PHP,ASP,JSP或 CGI)。我们也可以在<dataset>标签中加这样的查询语句:
<dataset name="myData" src="http://www.myServer.com/cgi-bin/myXMLDoc.cgi?return=addresses"/>
当src属性中包含http://时,就隐含了”type=”http””了。
注意 在使用OpenLaszlo服务器时,您不必担心在Flash Player对XML解析时的速度。
数据集范围
如果数据集是在canvas下指定的,那么它对整个应用程序都可见。数据集也可以只对本地类可见。
如果数据集没有指定名称,将会自动生成为”localdata”。
本地datapath语法为:datapath=“local:reference.to.dataset.relative.to.parent:/path”。
如果数据集的名称为默认的“localdata” ,那么从data得到的数据的名称可能被忽略。
下面是怎样使用本地数据的例子(用作测试的文件数据,是包含在这个目录下的XML样本数据)。
Example 26.5. 本地数据设置(这个例子要去掉,因为存在元素中的dataset访问不到数据)
<canvas width="100%" height="200"> <view layout="axis: y"> <dataset name="ds" src="http://..."/> <text datapath="this.ds:/record/text()"/> </view> <view name="myclass"> <simplelayout axis="y"/> <dataset name="ds" src="http://..."/> <text datapath="this.ds:/record/text()"/> </view> <dataset name="gdata" src="../testdata.xml"/> <simplelayout spacing="2"/> <view name="nodatanoname" layout="axis: y" bgcolor="#cccccc"> <dataset/> <text>empty local dataset with no name</text> </view> <view name="nodata" layout="axis: y" bgcolor="#cccccc"> <dataset name="lds"/> <text>empty local dataset</text> </view> <view name="somedata" layout="axis: y" bgcolor="#cccccc"> <dataset name="lds"> <foo>bar</foo> </dataset> <text>local dataset</text> <handler reference="lds" name="oninit"> <![CDATA[ Debug.write("somedata test data loaded", 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" bgcolor="#cccccc"> <simplelayout axis="y" /> <dataset name="lds" src="../testdata.xml"/> <text>local dataset compiled in from external file</text> <handler reference="lds" name="oninit"> <![CDATA[ Debug.write("filedata test data loaded", this); if (this.lds.serialize() != '<lds> <persons><person id="1"><firstName>Dan</firstName><lastName>McGowan</lastName></person> <person id="2"><firstName>Barry</firstName><lastName>Bonds</lastName></person> <person id="3"><firstName>Jeff</firstName><lastName>Beck</lastName></person></persons></lds>') { Debug.error("filedata serialized data does not match expected value"); } ]]> </handler> </view> <view name="remotedata" bgcolor="#cccccc"> <simplelayout axis="y" /> <dataset name="lds" src="../testdata.xml" type="http" request="true"/> <text>local dataset loaded at runtime</text> <text datapath="local:parent.lds:/persons/person/firstName/text()" οnclick="Debug.write(this.datapath)"/> <handler reference="lds" name="ondata"> <![CDATA[ Debug.write("remotedata test data loaded", this); if (this.lds.serialize() != '<lds> <persons><person id="1"><firstName>Dan</firstName><lastName>McGowan</lastName></person> <person id="2"><firstName>Barry</firstName><lastName>Bonds</lastName></person> <person id="3"><firstName>Jeff</firstName><lastName>Beck</lastName></person></persons></lds>') { Debug.error("remotedata serialized data does not match expected value"); } ]]> </handler> </view> <handler name="onkeydown" reference="LzKeys" args="k"> var key=k.intValue(); if(key==13) Debug.write(nodatanoname.localdata); else if(key==37) Debug.write(canvas.ondata.lds); else if(key==38) Debug.write(canvas.somedata.lds); else if(key==39) Debug.write(canvas.filedata.lds); else if(key==40) Debug.write(canvas.remote.lds); </handler> <view name="remotedatarelative" bgcolor="#cccccc" datapath="local:lds:/persons/" visible="true"> <simplelayout axis="y" /> <handler name="onkeydown" reference="LzKeys" args="key"> var keycode=key.intValue(); if(keycode==35) { Debug.write(this.lds); this.datapath.setXPath(this.datapath.xpath); } </handler> <dataset name="lds" src="../testdata.xml" type="http" request="true"/> <text>local dataset loaded at runtime and relative datapath.</text> <text datapath="person/firstName/text()" > <handler name="onkeydown" reference="LzKeys" args="key"> var keycode=key.intValue(); Debug.write(this.datapath); </handler> </text> </view> <view name="localdatatest" bgcolor="red"> <dataset/> <view datapath="local:classroot:/"> <simplelayout/> <handler name="onkeydown" reference="LzKeys" args="key"> var keycode=key.intValue(); if(keycode=34) 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()" οnclick="this.datapath.deleteNode();"/> </view> </view> </canvas>
ondata
当应用程序的LzDataset接受到数据时,将发送ondata事件。
Post支持
数据集支持使用HTTP GET和POST方法和OpenLaszlo服务器和后端服务器进行通信。默认为GET,但也可通过LaDataset.setQueryType()API来改变。通常,具有大量参数的请求使用POST来传送。
HTTP请求和应答头
一般来说,OpenLaszlo服务器会代理来自或发向后端的HTTP请求和应答的头。然而,有些头则被特殊地忽略或修正了。
注意:在SOLO应用程序中不支持应答头。
数据源
通过HTTP进行通信的每一个潜在的数据集都是一个LzDatasource对象。该对象抽象了所有的协议细节通信。一般来说,除了极少情形,你不需要自己使用数据源(datasource)对象。
数据指针
Datapath及其方便,但如果你需要更多地控制数据,它就变得笨拙了。(Datapath实际上是datapointer的扩展,但它更容易学习,这就是我们为什么先介绍它的原因)。一个DataPointer是一个指向数据集的指针,可以到处移动。它也可以一次只在数据集的一个地方,但你也可以使用多个datapointer,每个datapointer指向数据集的不同地方。
Datapointer不像datapath那样和视图绑定,但它们的确在视图结构中某个位置,也就是说它们“了解”父亲和孩子视图。
当你需要以某种方式操作数据时,你可能会用到datapointer;比如,利用前面例子中的数据的相同格式,你可能需要找到所有的在South Park 的人。
Example 26.6. 操作数据指针(该例子中ondata函数执行不)
<canvas height="180" width="500"> <dataset name="myData" src="../../guide/myShowData.xml" /> <datapointer xpath="myData:/" ondata="processData()"> <method name="processData"> this.selectChild(2); do { if(this.getNodeAttribute("show") == "South Para") Debug.write("show:"+this.getNodeAttribute("show")); } while (this.selectNext()); </method> </datapointer> </canvas>
为简便起见,我们把结果写到degugger中,使用来自本地的包含数据。
第一个selectNext(2)方法首先选择<myXML>节点,然后是Homer的<person>节点。它选择两个是因为我们设置了参数2(否则默认为1)。
另外一个selectNext方法,一旦选择XML节点成功(直到没有节点为止)就返回true。我们使用了一个do…while循环来达到目的,这样对每个<person>节点都可以进行迭代。
我们也可以使用<datapointer>onerror和ontimeout事件管理器来捕捉任何的问题。
访问数据
数据集(Datasets)
在OpenFace应用程序中,<dataset>就提供了一种封装XML数据的方法。取决于数据源的种类,数据集可以使动态的也可以是静态的。当数据集通过”type=http”显式地声明时,src属性的值就解译为一个URL,此时数据集在运行时装载数据。如果src数据缺省,那么它表达的数据就应该包含在<dataset>标签内,于是数据直接编译进应用程序。
当我们说HTTP数据集是动态的时,即意味着我们可以使用程序方式来重新装载数据。具体方法是:调用数据集对象的doRequest()方法;或如果request属性被设置为true,通过通过调用setSrc(),setQueryString(),或setQueryParam()来改变数据集的URL。
全局数据集
当一个数据集定义为<canvas>或<Library>的一个直接的孩子时,在代码的任何地方都可以通过canvas的datasets属性来引用,比如 canvas.datasets[‘mydset’],或者简单的通过它的名称(它是全局可见的)。
Example 26.7. Explicitly defined datasets
<canvas height="200"> <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.write(mydset); Debug.write(canvas.datasets['mydset']); Debug.write(week) </script> </canvas>
在运行时创建数据集
在运行时,可以在脚本中通过调用LaDataset的构造函数:var dset=new LzDataset(null,{name:’mydset’}) 来创建数据集。构造函数的第一个参数是数据集的父节点,它是装载数据集的<datasource>,这个参数允许为null—此时数据源(datasource)被隐性的创建。
简单绑定
LZX事件系统允许你根据需要向应用程序插入自定义的数据处理器。通常可以通过重载数据绑定节点的applyData()方法,在datapointer或datapath上提供一个ondata事件的处理器,或者在表达式类型的属性上定义一个$path约束并通过onattribute_name处理器来改变属性值来实现。
使用$path进行属性绑定
通过显式地使用$path{}约束语法,可以对节点的属性进行绑定。花括号之间的表达式必须是一个可以解译为相对的XPath表达式的字符串。
绝对路径
$path{}约束的一个限制是包含的表达式只能在初始化时刻求值,也就是说,诸如$path{‘mynode[‘+i+i]/@attr’} 的表达式的行为类似于$once{}约束。
$path绑定是双向的,所以在节点的datapath上调用updateData() 将会把当前值保存回数据集的属性。updateData()不支持,定搞时需要去掉的。
Example 26.8. 约束的例子
<canvas height="150" > <dataset name="sizes"> <value>200</value> <value>150</value> <value>100</value> </dataset> <text text="Shrink me" datapath="sizes:/value[1]" > <attribute name="width" value="$path{'text()'}"/> <handler name="oninit"> Debug.write("width:"+this.width); </handler> </text> </canvas>
使用ondata进行更新
(这部分的内容中ondata不被执行 不支持呀 需要去掉)
对于一个datapointer、datapath、或者datamapped节点,一旦它所绑定的数据发生改变,就发送ondata事件。也就意味着,对于选择一个数据节点的XPath,只有当datapointer被设置指向另外一个节点时,才会发送ondata事件。如果指针选择了一个操作,如”text()”或”@attr”,当所匹配的文本(text)或属性(attribute)发生改变时,也会发送该事件。和事件一起发送的参数为节点或datapointer的数据属性的当前值。
下面的例子使用了一个由临时datapointer发送的ondata事件,来计算并显示累积数字序列的平均值。然后,当datapointer的setXPath()方法被调用时,发送事件。通常,像这样的问题使用Javasript内置的数组编程更方便,但这个例子只是为了描述数据驱动的方法。它也引入了数据复制(后面的章节会有更详细的介绍)的概念。
Example 26.9. Ondata 事件
<canvas height="200" > <dataset name="numbers"/> <datapointer name="top" xpath="numbers:/"/> <datapointer name="numptr"> <handler name="ondata" args="d"> // d is LzDataElement object result.update(d.nodeName) </handler> </datapointer> <simplelayout spacing="5"/> <text>test ondata</text> <view> <simplelayout spacing="10" axis="x"/> <inputtext name="input"> <handler name="onkeyup" args="k"> if ( k == 13 ) { canvas.parent.bSend.compute(); } </handler> </inputtext> <text name="bSend" text="Add"> <handler name="oninit" method="compute"/> <method name="compute"> top.addNode(parent.input.getText()) var end = "updata"; numptr.setXPath('numbers:/*[' + end + ']') parent.input.clearText() </method> </text> </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>AVG: </text> <text id="result" fgcolor="blue" fontstyle="bold"> <method name="update" args="v"> parent.sum += Number(v) this.setText(parent.sum / top.p.childNodes.length) </method> </text> </view> </canvas>
操纵数据指针
前面提到,datapointer是一个表示LzDataset中的节点的指针。,通过调用selectNext()来移动游标,或通过setXPath()来发送XPath请求,Datapointer可以实现重新定位。
Datapointer支持的是XPath标准的一个子集,它使用类似UNIX文件系统的标示来引用数据集中的节点。一旦datapointer和数据集中的一个节点绑定,它将会一直指向这个节点,直到它被移除。如果数据被编辑修改,datapointer的行为就由它的rerunxpath属性所控制。如果该属性值为ture(默认值),只要它是有效的,那么它将继续指向当前值。
rerunxpath
(这一部分也需要去掉) datapointer的rerunxpath 属性决定了,当数据集的内容发生改变时,XPath表达式的值是否重新计算。默认值为false;如果置为true,每次数据集被编辑修改时,XPath绑定就刷新一次。也就是说,如果rerunxpath不为true,datapointer就假设为不变的。
Example 26.10. 使用rerunxpath 属性
<canvas height="200"> <dataset name="stack"> <root/> </dataset> <datapointer name="top" xpath="stack:/root"/> <datapointer xpath="stack:/root/*[1]/name()" rerunxpath="true"> <handler name="ondata" args="d"> good_result.setText(d) </handler> </datapointer> <datapointer xpath="stack:/root/*[1]/name()"> <handler name="ondata" args="d"> bad_result.setText(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"/> <inputtext name="input"> <handler name="onkeydown" args="k" reference="LzKeys"> var key=k.intValue(); if ( key == 13 ) { parent.bAdd.handler(); } </handler> </inputtext> </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>TOP: </text> <text id="good_result" resize="true" fgcolor="green" /> <text id="bad_result" resize="true" fgcolor="red" /> </view> </canvas>
上面的例子展示了rerunxpath属性的作用。当绑定在数据集的第一个节点的datapointer发送ondata事被时,下面的文本(text)字段被更新了。然而,第一个datapointer声明了rerunxpath=”true”,因此它指向真正的第一个节点;而第二个datapointer则还保持着初始时的引用,就不会更新了。
数据映射视图的强制可视化
默认情况下,如果视图的datapath没有匹配任何数据,它就不会被显示出来。然而,有时候需要违背这种行为,即让一个数据映射视图不论有没有数据,都显示出来。一个典型的例子就是一个面板,包含占位符视图,该视图映射到一个动态获取的数据纪录,它就需要在任何时候都可见了。要达到这种效果,可以在定义视图的datapath时,使用dataControlsVisibilty属性并设置为false。
下面的例子展示了该属性的用法。绿色方形视图的datapath初始时是没有数据的,但他的datapath声明时将dataControlsVisibilty属性设置为了false,所以它总是可见的。红色方形视图的datapath初始时没有匹配任何东西,dataControlsVisibilty属性保持默认设置为true,于是它就没有显示出来。但我们对数据集增加一个节点,这样datapath就有了一个匹配值了,于是视图也就可见了。蓝色方形视图通过匹配的datapath来声明的,所以它是可见的。
Example 26.11. Visibility of datamapped views
<canvas height="150"> <dataset name="mydata"> <element>data</element> </dataset> <simplelayout spacing="5"/> <view layout="axis: x"> <view name="cs" width="20" height="20" bgcolor="green"> <datapath xpath="mydata:/element[2]"> <attribute name="dataControlsVisibility" value="false"/> </datapath> </view> <text text="${parent.cs.datapath.xpath}"/> </view> <view layout="axis: x"> <simplelayout axis="x" /> <view name="cs" width="20" height="20" bgcolor="red" datapath="mydata:/element[2]/text()"/> <text text="${parent.cs.datapath.xpath}"/> </view> <view > <simplelayout axis="x" /> <view name="cs" width="20" height="20" bgcolor="blue" datapath="mydata:/element[1]/text()"/> <text text="${parent.cs.datapath.xpath}"/> </view> <text text="Add data node" oninit="mydata.getPointer().addNode('element', 'data')"/> </canvas>
最后要指出的是,dataControlsVisibilty 是只读的,也就是说不能在运行时改变它,而只能声明为一个单独的属性。
更新timing(数据初始化的顺序)
因为静态数据集中包含的数据是被编译进应用程序中的,它可以直接使用。因此,任何将静态数据集作为它路径一部分的datapointer,都会在任何canvas的孩子初始化之前发送ondata事件。如果数据的所有变化都将反映到应用程序界面上时,记住这点是很重要的。换言之,当写操作处理ondata 事件时,你就需要仔细考虑,不要让还没有完全初始化的数据反映到视图上。
另一方面,动态数据集必须从外部取数据,所以可能只有当依赖于它的可视化元素都处于稳定状态时,才会发送事件。这就是为什么代码逻辑应该根据ondata事件的响应、或者通过重载applyData方法来使用这些数据映射元素。注意到LzDataset对象本身一旦接受到新数据时就发送一个ondata事件;通常就利用它来使UI操作和数据的到来达到同步。就如上面所述,静态数据集一旦它被实例化,就造成ondata事件的发送。
数据处理
使用setPointer()来绑定数据
作为其本质性的功能,datapionter和(datapath)可以直接送到数据节点。下面的例子使用了setPointer()方法来把目标视图的datapath送到节点,该节点被所选视图的datapath所引用。于是,这样就完成了细节视图到在当前选择数据集中的条目。
Example 26.12. Using setPointer
<canvas height="150"> <dataset name="phonebook" src="resources/phonebook.xml"/> <simplelayout axis="x" spacing="20"/> <view name="contacts" height="150" width="100"> <view bgcolor="0xe0e0e0" datapath="phonebook:/contacts/contact" oninit="details.datapath.setPointer(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>
控制datapath
LzDatapath 是LzDatapointer的子类,因此它也可以类似的在整个数据内迭代巡航。下面的例子将每个复制视图的datapath当作指向支撑它的数据集的指针。封套视图的datapath在这种环境下就是简单的指向数据集根节点的一个指针,这样,它就被用来操作和增加孩子节点。
Example 26.13. Dereferencing datapaths
<canvas height="150"> <dataset name="busy"> <Monday order="1"/> <Tuesday order="2"/> <Wednesday order="3"/> <Thursday order="4"/> <Friday order="5"/> </dataset> <simplelayout axis="x" spacing="20"/> <class name="schedule"> <attribute name="title" type="string"/> <simplelayout spacing="5"/> <text bgcolor="white" fgcolor="blue" text="${parent.title}"/> </class> <schedule name="b" title="Busy" bgcolor="0xd0000a" datapath="busy:/" height="150" width="100"> <text bgcolor="0xe0e0e0" > <datapath xpath="*/name()"/> <handler name="oninit"> parent.target.datapath.addNodeFromPointer(this.datapath) this.datapath.deleteNode() </handler> </text> </schedule> </canvas>
使用datapath的迭代方法时,有一个重要的限制。如果你通过调用任何的 select…() setXXXPoint()方法来移动它时,它的XPath就会被移除;也就意味着对潜在数据的任何更新都将不会同志数据映射的UI元素。考虑以下例子:
Example 26.14. 多次使用Datapath
<canvas height="150" width="250"> <dataset name="phonebook" src="resources/phonebook.xml" /> <view id="details" height="150" > <datapath xpath="phonebook:/myXML/person[1]" /> <text datapath="@simpsons"/> <text text="Delete record"/> <simplelayout spacing="5"/> </view> <simplelayout axis="x" spacing="10"/> <handler name="onkeydown" reference="LzKeys" args="key"> var keycode = key.intValue(); if(keycode == 13){ details.datapath.deleteNode(); }else if(keycode == 37){ details.datapath.selectPrev(); }else if(keycode == 39){ details.datapath.selectNext(); } </handler> </canvas>
数据复制
上面例子所示,和多个节点匹配的datapath导致了它们的节点被复制了。我们指的被复制的意义是对于每个XPath表达式的匹配,都创建了一个映射视图。这是LZX中数据绑定的最重要的特性。
复制管理器
复制管理器是一个运行时对象,一旦数据复制发生(由datapath拥有多于一个的匹配产生的结果)就会被动态创建。此时,复制视图的name或id属性就被复制管理器接管,从那时候起,对那个名称的引用都会访问复制管理器,而不是视图了。为了引用副本视图,即clones,你应使用 LzReplicationManager API。
replication 属性
如果datapath匹配多个节点,它就会创建一个复制管理器。如果replication 属性的值为 normal(默认值),然后复制管理器就会成为直接的LzReplicationManager 实例;如果是 lazy,相应的它就会创建一个LzLazyReplicationManager 。
克隆(clones)和onclones 属性
如前面所提到的,当一个视图被复制,它的副本就被复制管理器所接管。一旦副本(clones)被创建,复制管理器的实例就在clones属性(它是视图数组)中包含了指向它们的引用。注意到LzReplicationManager 继承自 LaDatapath,一个被克隆的视图和它的datapath就被复制管理器所取代了。掌握了以上知识,我们就可以决定什么时候视图被克隆。下面的例子描述了clones属性的用法:通过声明视图的datapath上的onclones事件处理器,。
Example 26.15. 使用clones and the onclones事件
<canvas width="550" height="200"> <dataset name="tabnames"> <title name="Account Info"/> <title name="Order History"/> <title name="Preferences"/> <title name="Shopping Cart"/> </dataset> <view> <simplelayout axis="y" spacing="10" /> <view datapath="tabnames:/title"> <text text="$path{'@name'}"/> <datapath> <handler name="onclones"> alert("@@@@@@@@222onclones"); </handler> </datapath> </view> </view> </canvas>
因为当设置clones属性时,就会发送onclones属性,所以它只表示视图复制的开始。但在本例中它用力测定复制完成的准确时间。既然复制视图初始化的顺序和他们插入克隆数组的顺序一致,我们就只需要等待列表中最后一个克隆对象的oninit事件就可以了。这是必要的,因为tab元素的初始化需要一定时间;而且,它的容器-tab slider在完成前,尝试对它进行操作会使组件处于矛盾的境地。为了描述这种情况,第二个tabslider就有问题,此时选择第一个tab元素太早,从而使它的父亲不稳定(其他的tabelement没有出来)。
本例也利用了一个事实,默认情况下,当它们消费数据时才可见。按钮被点击之前,在tabslider中有一个tabelment。然而,直到接受数据(也是复制发生,显示克隆对象的时刻)它才可见。
节点和onnodes事件
和 clones 属性类似,LzReplicationManager的nodes属性中,保存着一个匹配的数据节点列表。它是一个映射到复制视图的LaDataElement对象的数组,而且,在当任何克隆对象创建之前是可用的。和处理onclones事件一样,onnodes事件的处理器可以以自定义的方式声明,来响应数据复制。下面的代码限定了每个复制数据节点的name属性的值。
Example 26.16. 使用nodes特性
<canvas height="200"> <dataset name="tabnames"> <title name="Account Info"/> <title name="Order History"/> <title name="Preferences"/> <title name="Shopping Cart"/> </dataset> <view> <simplelayout axis="y" spacing="10" /> <view datapath="tabnames:/title"> <text text="$path{'@name'}"> <datapath> <handler name="onnodes"> alert("@@@@@@@@222onnodes"); </handler> </datapath> </text> </view> </canvas>
$path绑定和复制
只有datapath可以创建复制。尽管看起来$path可以用作隐含的强制复制,但它并不行。$path表达式不只产生一个单独的值。如果它和多个值匹配,就是一个错误,不会匹配任何数值。在下面的例子中,注意到当封套datapath被设置好时,$path约束没有更新。
Example 26.17. $path does not replicate
<canvas height="300" width="400"> <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> <view id="thedata" datapath="ds:/"> <simplelayout axis="y"/> <view> <simplelayout axis="x"/> <text>Datapath:</text> <view> <simplelayout axis="y"/> <text datapath='data/person/@name' /> </view> </view> <view> <simplelayout axis="x"/> <text>$path:</text> <view> <simplelayout axis="y"/> <text text="$path{'data/person/@name'}" /> </view> </view> </view> </canvas>
转载:http://www.openface.org.cn/wiki/index.php?title=Data_Access_and_Binding&diff=cur&oldid=4120