qooxdoo通用javascript框架手册(六)

来源:互联网 发布:第七封印知乎 编辑:程序博客网 时间:2024/05/22 17:03

2.4数据绑定

数据绑定是一个将两个数据项绑定的概念,更改一个数据另一个也相应的自动发生变化,反之亦然。为了这个目的就需要在程序运行时检测到这种变化。在qooxdoo中类的属性是可以满足这种需求。使用数据绑定允许您在运行时保持两个小部件自动同步,尽管他们可能在空间上是分开的,有截然不同的视觉表示(例如文本字段和微调控制项)。

2.4.1数据绑定的介绍

数据绑定是一种允许从源到目标连接数据的功能。整个一章可分为低级的部分,称为“单一值绑定”,和一些更高级的概念,涉及到stores和controllers。

主要思想

qooxdoo的数据绑定组件的主要思想最好的概括如下图。


正如你所看到的数据绑定,它包括五个主要部分,下面的章节将进行更加详细地描述。
 

Data:数据部分是那些被存储的原始数据,这些数据可以从一个普通的本地文件,一个普通的web服务器,甚至是Web服务检索得到。所有的数据来源都依赖于实际的数据存储。

Store:Store组件负责从Data中获取数据,并将数据转换为一个应用程序的数据模型。

Model:模型是数据绑定的核心。它保存数据并作为Store和Controller的整合点。Store在程序运行时提供一个智能的方法与模型类协同工作。

Controller:控制器组件的主要任务是将模型中数据连接到视图组件。更多详细信息请查阅[控制器]这一章。后面解释的单值绑定是所有控制器的基础层。

View:数据绑定中根据控制器的类型视图几乎可以是任何具体丰富配置的qooxdoo的Widget。qooxdoo数据绑定并不局限于一些预定义的数据绑定控件。请注意,其中最著名的以数据为中心的widget,虚拟表,目前仍然使用自己的基于模型的层,没有被新的数据绑定层覆盖,虽然新的虚拟Widgets将被整合到即将到来的数据绑定层中。

Demos, API 和列表

你现在应该对qooxdoo的数据绑定有了一个基本印象,你还可以从qooxdoo官网的在线Demo及API指南上看看它是如何工作的。如果你现在就想开始编程,也许下在的列表可以在程序设计期间帮助你。

单值绑定

单值绑定的目的是通过绑定将一个属性连接到另一个上。连接总是有方向的,如果需要相反的方向,那必须创建另一个绑定。绑定是通过一个事件处理程序将数据传递给目标属性的。因此,绑定的源必须有一个change或其他类型的数据事件来触发绑定事件的发生。单值绑定主要是更高级数据绑定的基础。

单值绑定最简单的形式是将一个属性绑定到另一个属性上。技术上源属性必须有change事件。如果没有就不能进行绑定。但如果满足绑定要求,绑定本身是很简单。在下面的代码片段中,绑定的是标签值的两个属性:

var  label1  =  new  qx.ui.basic.Label(); 

var  label2  =  new  qx.ui.basic.Label(); 

label1.bind("value",  label2,  "value"); 

label1绑定的源对象,绑定调用以下三个参数:

1。绑定的源的属性的名称。

2。绑定的目标属性的所属对象。

3。绑定目标的属性名称。

这段代码的作用是将Label1的属性value发生变化时,label2的属性value将自动同步更改。

给属性绑定数据事件

如果一些属性作为model来使用,那么它们就只能通过对象的方法或一个事件进行访问。这些属性不能直接绑定,但他们可以通过一定的方法或事件来间接引用它们。一个常见的例子是TextField 控件,不像前一示例中的Label,它没有的value属性。TextField 的value只能通过getter和setter方法或change事件来解决。这样TextField也间接的具有了一个可绑定的value属性,虽然它不是一个直接的属性。在下面的示例代码中使用changeValue事件将value进行了绑定。该AP是本质上与属性绑定的情况相同。

var textField = new qx.ui.form.TextField(); 
var label = new qx.ui.basic.Label(); 

textField.bind("changeValue", label, "value");

你可以看到,和属性绑定使用了同样的方法。不同的是第一个参数是一个数据事件的名称而不是属性名称。

类似的方法,在TextField的隐性属性value上可以绑定一个控制器:

Var textField = new qx.ui.form.TextField(); 

//create the controller 

var controller = new qx.data.controller.Object(model); 

// connect the name 

controller.addTarget(textfield, "value", "name", true); 

在这种情况下,绑定代码将转换属性“value”为getValue()和setValue()方法。

绑定属性链到另一个属性上

单值绑定的一个更高级的功能是绑定一个具有层次结构的属性到目标属性上。要理解这一点请看下面的代码。使用此代码qooxdoo类需要一种指定的节点,这个节点要有子节点、有名称为name的属性并且可以触发change事件。

// create the object hierarchy 

var a = new Node("a");// set the name to “a

var b = new Node("b");// set the name to “b

a.setChild(b); 

// bind the property to a labels value 

a.bind("child.name", label, "value"); 

现在节点b的name属性的每一个变化都将会改变标签的值。但另一个节点的子节点属性的另一个name发生改变时也会将标签的value改为新的name。这是在一个层次结构中深层的绑定机制造成的。但要记住,每个绑定的属性都需要触发change事件。

将一个数组绑定到一个属性

绑定的下一个功能是绑定数组值的能力。但这种绑定需要一个特殊的数组,因为绑定组件需要知道当数组发生变化时是哪一个原素发生了改变。qx.data.Array类型的数组可以满足这个要求。qx.data.Array包装了本地数组并将数组的每一个变化添加到改变事件上。以下代码示例显示了一个绑定数组。作为一个先决条件是一个对象有一个qx.data.Array数组类型的一个属性。且数组包含字符串。

// bind the first array element to a label’s value 

a.bind("array[0]", labelFirst, "value"); 

// bind the last array element to a label’s value 

a.bind("array[last]", labelFirst, "value"); 

你可以在括号中使用任何数字或字符串,last被映射成length-1,这样你可以很容易映射堆栈顶部上的东西。

2.4.2 Controller 

控制器的总体思路是将存储了一组数据的模型连接到一个视图组件上,这种控制器需要依赖视图组件。目前有四种类型的控制器可用:

•  Object Controller 

•  List Controller 

•  Tree Controller 

•  Form Controller 

在上面你并没有看到表格控制器。目前可用的表不能改变,因为它没有数据绑定功能。目前正在开发中新的虚拟表,它具有数据绑定功能。

在下面的部分中,也会讨论selection,因为它是列表和树控制器的一个共同的功能。delegation机制是这两个控制器的另一个共同的功能,也被会被说明。之后,将详细讨论每一个可用的控制器。

Selection(选择)

通常象树或列表等视图组件的选择是用来对用户选定的树的枝叶或列表项进行处理。作为数据绑定的用户,你不希望将视图转换为模型。因此,系统为你映射出一个控制器。在控制器中有一个选定数组包含了当前选定的模型的项。使用控制器的选择时,不需要处理象列表项这样的视图部件。可以对选定的数据数组进行添加/删除操作,因为它是一个数组,所以可以使用所有数组定义的操作来维护相应的控制器。

这里有一些例子展示如何对选择进行操作:

// select ’modelItem’ 

controller.getSelection().setItem(0, modelItem); // empty the selection 

controller.getSelection().removeAll(); 

// add ’modelItem’ to the selected items 

controller.getSelection().push(modelItem); 

如果你想选择的内容发生变化时得到一个通知,你可以再次从数组中得到好处,它提供了一个当内容发生变化时触发的change事件。

controller.getSelection().addListener("change",  function(e){});

上面的代码增加了一个监听器当选择数组发生变化时触发。其实控制器还提供了一个changeSelection事件,但它并不是像你所期望的,选定项发生变化就会触发这个事件。它只有在选定的数组发生变化时才会被触发。

不提倡对整个选择数据进行操作,但你仍然可以这样做。有两个原因可以解释为什么你不应该这样做。首先,你必须确保附着到它上的监听器已经返回,第二,你要确保老的数组已经进行的适当的销毁。下面是一个示例标明了应该如何做。

var oldSelection = controller.getSelection(); 

oldSelection.removeListener("change", this._onChange); 

oldSelection.dispose(); 

controller.setSelection(newSelection); 

newSelection.addListener("change", this._onChange); 

Delegate 

列表和树控制器负责创建和绑定的视图的子控件。但如果你想用一些列表中的不同的东西或绑定的不只是标签和图标。为了这个目的,Delegate 提供的可能性,Delegate 可以在不使用子类的前提下优化控制器的代码。

总的来说,有三种方法涉及到创建和绑定子视图控件的主题。

configureItem:如果你只是想修改创建的默认插件的有关属性,您可以使用configureitem功能。它给你一个设置标签的机会,在这里你可以丰富的内容或修改子控件任何东西。但这不是你想add/change绑定行为的地方.

bindItem:这里是使用binditem方法的地方。对于所有绑定和绑定的东西不想使用单一的值,为此,控制器提供你一个称为bindproperty的方法,它把源数据的路径,目标属性的名称和选项做单值绑定。另外两个参数会被映射到实际值中。但请记住,如果您使用了这个功能,那么标签和图标的默认绑定就不起作用了。如果你还想有默认的绑定,请使用bindDefaultProperties方法传递两个参数。但是记住,这两个绑定设置方法都是从模型的视图的单向绑定。如果你想从视图到模型绑定,可以使用bindpropertyreverse方法,它的参数与bindproperty方法一样。

createItem:最后一种称createitem方法为用户提供了为视图添加一些不同的子控件的机会。这个方法允许你创造你想在视图中看到的widget 并返回将其做为新条项目返加。但请记住,默认的绑定可能无法在这些widget上正确执行,代码将失效。所以使用bintItem定义自己的绑定是一个好的做法。

下面的代码展示了Delegate 如何使用:

var delegate = 

    configureItem : function(item) 
item.setPadding(3); 

   }, 

    createItem : function() 

        return new qx.ui.form.CheckBox(); 
    }, 

    bindItem : function(controller, item, id) 

      controller.bindProperty("name","label",null,item,id); 

      controller.bindProperty("online","checked",null,item,id); 
    

};

Delegate定义了一个CheckBox来做为视图的子控件使用,CheckBox没有图标,bindItem函数需要重新声明一个绑定,它将数据模型中的name和online两个属性分别绑定到CheckBox的label和checked属性上。

对象控制器

最简单、轻量级的控制器是对象控制器。它可将一个数据模型对象连接到一个或多个视图上。模型中的数据可以被任何一个属性抓取。也就是说原始数据类型如字符串或数字,或像健值映射这样的引用类型,它们都可以和文本框,滑块或原始JavaScript可视化部件等视图的实例进行绑定。但不仅视图可以作为目标使用。目标可以是任何有一个适当属性的控件,下面的代码示例中可以看到对象控制器如何工作的:

// create two sliders 

var slider1 = new qx.ui.form.Slider(); 
var slider2 = new qx.ui.form.Slider(); 

// create a controller and use the first slider as a model 

var controller = new qx.data.controller.Object(slider1); 

// add the second slider as a target 

controller.addTarget(slider2, "value", "value"); 

这段代码确保了slider1的每次设置的值自动设置到第二个slider的值上。你可以看到,对象控制器只包括一个基本单值绑定,并试图使单值绑定的使用更容易一些。

列表控制器

顾名思义列表控制器就是用于像List这样的widgets的控制器。它可以支持的控件有qooxdoo的List,SelectBox和ComboBox,这几个控件都在qx.ui.form包。列表控制器可以将数组作为数据模型,数据模型还可以包含模型对象。这些对象将被显示在列表中,它们可以是一些原始的类型也可以是真正的qooxdoo对象。下面的代码片段显示了如何绑定字符串数组到列表控件上:

// create the model 

var model = new qx.data.Array(["a", "b", "c", "d", "e"]); 

// create a list widget 

var list = new qx.ui.form.List(); 
// create the controller 

var listController = new qx.data.controller.List(model, list); 

现在在数组模型中的每一个变化都会反映到列表部件上。

过滤方法是列表控制器包含的一个独特功能。您可以给控制器指定一个过滤函数,控制器会自动使用您提供的函数对数据进行过滤。

树控制器

当然,树控件也有它自己的控制器。树控件的控制器可以自动从包含的数据的qooxdoo对象中填充数据。做为树节点的模型,qooxdoo对象至少要包含两个属性,一个用于存储它的子节点的数组,第二个保存节点的名称,它将以一个label的形式显示到树的节点上。想象一下,一个称为节点的模型类(继承于qx.cor.Object)要含有两个上面提到的属性,一个保存子节点称为CH和一个保存节点名称为N。下面的代码将一个包含节点对象的数据模型绑定到树控件上:

// create the model 

var rootNode = new Node(); 
rootNode.setN("root"); 
var childNode = new Node(); 
childNode.setN("child"); 

rootNode.getCh().push(childNode); 

// create the tree view 

var tree = new qx.ui.tree.Tree(); 
// create the controller 

var treeController = new qx.data.controller.Tree(rootNode, tree, "ch", "n"); 

使用上面的代码后,任何名称或子节点的变化都会映射到树视图中,在树视图中选择一个树的节点也会将相对应的节点对象添加到控制器的选定数组中。

表单控制器

表单有一个特殊的控制器,它使用qx.ui.form.Form作为目标,和一个对象控制器执行双向绑定。用法和其他控制器的使用一样。它的主要属性就是model和target。表单控制器的一个额外的功能就是根据给定的表单生成数据模型。看下面的使用这一用法的代码。

// a form is available as ’form’ 
// create the controller 

var formController = new qx.data.controller.Form(null, form); 

// create the model 

var model = formController.createModel(); 

执行完这些代码后,控制器和模型都可以使用了,因此控制器也可以设置绑定了。

组合控制器

一种更先进的例子就是将树的选定连接到一个列表上。因此,我们扩展的树控制器部分的示例代码。

// create a list widget 

var list = new qx.ui.form.List(); 
// create the controller 

var listController = new qx.data.controller.List(null, list, "n"); 

// bind the selection of the tree to the list 

treeController.bind("selection", listController, "model"); 

该示例显示了控制器与单值绑定在一起如何完美的工作。诀窍是创建列表控制器时不要设置模型。该模型将由单值绑定设置,并从树控制器选定中提供数据,因为树的选定将做为一个数组被提供。

2.4.3 存储

存储组件的主要目的是从源数据加载并将这些数据转换成一个数据模型。现在加载数据和数据转换这两个任务已经分开,转换工作已经是模型的主要任务。存储本身主要负责加载数据,但也可以委托其建立数据模型或实例化数据封装器。关于数据封装器和模型的更多信息可以查看模型部分。

JSON存储

JSON存储需要一个URL,它从给出的URL中获取数据并将数据封装成JSON形式传递给qooxdoo的模型实例,数据加载完成后,它就有一个可以使用的模型属性。它加载过程的各种状态会映射到state属性中。在存储中将使用qx.io.request.Xhr来加载数据。

下面的代码显示了如何使用JSON数据存储。

 var url = "json/data.json";

 var store = new qx.data.store.Json(url);

在创建过程中设置好URL后,加载将立即执行。一旦数据被加载和转换,你就可以用下面的代码访问数据模型。

store.getModel();

JSONP存储

JSONP存储是基于JSON存储的,但可以对加载数据使用一个脚本标签。回调的名称参数和URL中必须声明。

下面的代码显示了如何使用JSONP数据存储。

var url = "json/data.json";

var store = new qx.data.store.Jsonp(url, null, "CallbackParamName");

在创建过程设置好URL和回调参数名称后,加载过程立即开始。

YQL存储

describes it as "[...] an expressive SQL-like language that lets you query, filter, and join data across Web services." 

YQL是雅虎查询语言的缩写。其描述“YQL类似于SQL的语言,可以让你查询,过滤,并加入跨Web服务加入数据的能力。”基于JSONP Store,qooxdoo提供了YQL存储,你只要指定YQL查询,qooxdoo会为你处理剩下的工作。

下面的代码演示了如何获取一些Twitter消息。

 var query = "select * from twitter.user.timeline where id='wittemann'";

 var store = new qx.data.store.Yql(query);

离线存储

离线存储使用本地HTML或会话在客户端上来存储数据。为了达到在客户端离线存储以及其他储存的目的,你应该使用Environment检查以确保所使用的存储技术在你的代码运行环境中得到支持。

下面的代码演示如何初始化数据存储。

if (qx.core.Environment.get("html.storage.local") {

  var store = new qx.data.store.Offline("my-test-key", "local");

  if (store.getModel() == null) {

    // initialize model ...

  }

}

与控制器相结合

如上节所述,您可以在数据加载后访问模型属性。更好的使用办法是将模型属性和控制器通过单值绑定连接在一起。代码是这个样子。

store.bind("model",  controller,  "model"); 

使用单值绑定,绑定将处理所有与模型数据加载相关的东西。这意味着,只要数据在存储中可用,那么在控制器中也可以使用。

2.4.4 数据模型

数据模型是数据绑定的核心部件。它保存的数据,并作为存储和控制器的集成点。几乎所有的模型都是纯qooxdoo类,它使用简单属性来保存数据,并且配置为每个变化都会触发事伯。这些变化事件是数据模型最重要的部分也是为什么普通的JavaScript对象不能做为数据模型的原因,原生JavaScript数组也是如此。因为当当数组项目改变时不能触发事件,因此为了数据绑定的目的模型增加了一个增强型的数组类型。

但是,有不需要为你要使用的每一个数据源手动编写自己的模型类。封送处理程序提供了一个巧妙的方法在运行时自动创建这些模型类。

在下面的章节中,我们先来看看模型基础知识以及它们是如何工作的。在那之后,我们深入了解数组的作用以及它是如何解决的。最后一节中,因为你不需要手工书写所有的简单模型,所以我们来查看qooxdoo是如何创建这些模型的。

结构:正如介绍这一章已经提到的,数据模型是纯qooxdoo对象。这种模式的主要思想是在属性中保存所有数据,它一旦有新的数据就会触发变更事件。让我们来看一个简单的例子,我们使用JSON数据来演示模型。该示例中的数据如下所示:

{

   s: "string",

   b: true,

   a: []

}

相应的模型现在应该是一个对象,类定义了三个属性,命名为s,b和a。让看看以下qooxdoo代码,我们假设我们有一个合适的模型:

var model = new ExampleModel(); // this returns a fitting model

model.getS(); // return the value of the property 's' which is "string"

model.setB(false); // will fire a change event for the property 'b'

我想现在你清楚模型的结构是什么样了。没有太多的代码或技巧,但他们却是整个绑定方案中最重要的部分。

数据数组:如果我们第二次看一看我们上面所用的例子,我们还增加了一个数组作为一个属性值。这个数组不应该是一个简单的JavaScript数组,它应该是一个qooxdoo数组类型,该数组Class位于qx.data.Array。至于原因应该是相当明显的,一旦所有必须的更新改变了数据内容,数据绑定就需得到一个通知事件。而作为普通数组它不能提供这样的通知,因此在数据绑定层我们增加了我们自己的数组实现。该数组尽可能接近原生数组,但在一些核心的东西上我们需要改变API操作。主要的区别就是数组中的项的访问。下面的示例代码,根据上面的示例,展示了不同之处:

var array = model.getA();

array.setItem(0,"content"); //equals 'array[0]="content"'and fires a change event

array.getItem(0); // equals 'array[0]' and returns "content"

array.length; // like the native API and returns '1'

你看,读/写访问需要使用指定的方法来完成,以确保各项变化对事件的触发。但是,所有的其他的API操作,如push,pop或splice都是一样的,并且也可以触发事件。

事件的重要性:这两个部分上面解释了车型的外观和原因。提及最多的理由是需要改变的事件,这让他们也是在数据绑定了重要的作用。退房的关于绑定数据的事件单独的页面。

释放:如果你熟悉qooxdoo和它的对象你就应该知道,释放处理是很有必要的。这也是真正的模型对象和数据数组也同样如此当创建一个封装器时,模型对象确实有一特殊的东西要做深层释放,,下面我们了解这个知识

JSON封装器

封装器能够将JavaScript对象封装成qooxdoo类和类实例的照顾。你可以每次用一个方法开始两个任务

toClass这种方法给定JavaScript对象转换成模型类。每一个类都定义在qx.data.model命名空间。类的名称将根据其中将要存储的数据自动生成。作为一个可选的参数,您可以传入一个冒泡事件来监听属性的每一个变化。如果给定数据对象模型已经创建, 就不会创建新类。

toModel:这个方法使用需要一个可用的类。所以调用此方法之前一定要先调用toClass方法。该方法的主要目的根据所创建的模型类创建它的实例,并返回对应于给定数据对象的模型。

createModel(静态):这个方法是静态的,使用一次可以依次调用上面的方法。通过这一点,你可以使用一行代码为给定的的JavaScript对象创建数据模型:

var model = qx.data.marshal.Json.createModel({a: {b: {c: "test"}}});

2.4.5 事件

事件是数据绑定的一个关键概念。当更新模型中数据他们负责通知每一个连接的视图(可多个)。你可以事件看数据绑定应用程序系统一种神经系统。一般来说,有两种不同类型的事件。

Change事件

数据绑定中使用的基本事件就是qooxdoo属性的change事件。你可以在文档中找到一些关于属性的change事件的信息属性一旦发生变化这些事件这些事件就会被触发

数组

数据数组也有一个每一次变化都会触发的事件,这个事件被称为change,它还包含了一些数组的附加信息,这些信息做为键值映射保存到数据中。可以使用是键如下:

start:发生改变的元素的起始位置,如索引位置为0它就赋值为0

end:发生改变的元素的结束位置,通常它和start相同。但当操作的元素是一个范围时就不相同了。如sort或者splice

type:改变的类型,这是一个字符串。可以是”add”,”remove”或者”order”

items:发生改变的元素(JavaScript数组)

冒泡事件

普通更改事件有时不能满足每一种使用情况例如我们考虑一个巨大的树数据模型,那么为每一个模型对象添加监听事件来取得视图的更新就需要相当的工作。因此,数据绑定也支持命名changeBubbles冒泡事件。这些事件命名MEventBubblingmixin提供。

启用这些冒泡事件默认禁用的,因为每变化触发一个额外的事件并不总需要。为使用这个事件,可以有两种方法。最容易和首选方法是使用封装器创建模型。第二个方法是自己的类包括mixin

详细信息:这个事件和普通的数组事件一样,在事件发生时也提供了一些附加信息,信息也是一个键值映射的形式,可以使用的键如下:

· Value: 属性的新值.

· Old: 属性原来的值.

· Name: 属性的名称,包括它的父节点,如:baz[3].baz.

· Item: 被改变的模型的条目.

数据数组也提供了相同细节的冒泡事件。但数组没有改变的属性。你可以将数组的索引作为属性。这是一个简单的使用unshift()方法的例子,如下:

var array = new qx.data.Array("a", "b", "c");

array.addListener("changeBubble", function(e) {

  var data = e.getData();

  // do something with the data

});

array.unshift("X");

我们将unshift所操作的元素认做索引为0的元素,因为它在位置0插入元素。其它的每一个元素都移动一个位置。在changeBubble事件中将会处理后面的数据。

value:['X'] (包含在索引位置0的新值的数组)

old:['a'] (包含在索引位置0的原值的数组)

name:0 (改变的的索引名称)

item:<array> (数组本身)

属性值和旧值总是数组,因为有的操作可能改变几个索引如splice。

0 0
原创粉丝点击