我已经解决了几个关于使用ZendFramework和Dojo设置自动完成器的问题,并决定是时候创建一个关于这个主题的HOWTO了,特别是因为您需要注意一些细微差别。
哪些dijit执行自动完成?
您的首要任务是选择能够自动完成的适当表单元素。Dijit提供了两个,ComboBox
和FilteringSelect
。但是,它们具有不同的功能:
ComboBox
允许您输入任意文本;如果它与关联列表不匹配,它仍然被认为是有效的。提交的文本输入—不是选项值。(这不同于普通的下拉选择。)FilteringSelect
也允许您输入任意文本,但只有当它与提供给它的选项匹配时才会被视为有效。optionvalue已提交,就像普通的下拉选择一样。
一旦您选择了合适的表单元素类型,您就需要指定一个dojo.data
存储。dojo.data
为dijits和其他dojo组件使用的数据结构提供一致的API。从本质上讲,它只是一个任意JSON结构的数组,每个结构都包含一个公共标识符字段,每个项目包含一个唯一值。在内部,ComboBox
和FilteringSelect
都可以利用dojo.data
存储来填充它们的选项和/或提供匹配项。Dojo为此类目的提供了多种dojo.data
存储。
定义表单元素
定义表单元素非常简单。从您的Zend_Dojo_Form
实例(或您扩展该类的表单),像往常一样简单地调用addElement()
。在本教程的后面,根据您使用的方法,您可能需要向元素定义添加一些信息,但现在,所需要的只是最基本的元素定义:
$form->addElement('ComboBox', 'myAutoCompleteField', array( 'label' => 'My autocomplete field:', ));
向dojo.data存储提供数据
我们现在要逆向工作,因为在使用Zend_Dojo_Data
时向数据存储提供数据相对微不足道。
首先,我们将在我们的控制器中创建一个动作,并将模型和查询参数分配给视图。我们将设置我们的dojo.data
存储以通过GET参数q
发送查询字符串,这就是我们将分配给视图的内容。
public function autocompleteAction() { // First, get the model somehow $this->view->model = $this->getModel(); // Then get the query, defaulting to an empty string $this->view->query = $this->_getParam('q', ''); }
现在,让我们创建视图脚本。首先,我们将禁用布局;其次,我们将查询传递给模型;第三,我们将用我们的查询结果实例化我们的Zend_Dojo_Data
对象;最后,我们将回显Zend_Dojo_Data
实例。
<?php // Disable layouts $this->layout()->disableLayout(); // Fetch results from the model; again, merely illustrative $results = $this->model->query($this->params); // Now, create a Zend_Dojo_Data object. // The first parameter is the name of the field that has a // unique identifier. The second is the dataset. The third // should be specified for autocompletion, and should be the // name of the field representing the data to display in the // dropdown. Note how it corresponds to \"name\" in the // AutocompleteReadStore. $data = new Zend_Dojo_Data('id', $results, 'name'); // Send our output echo $data;
这就是它的全部内容。实际上,您可以使用AjaxContext
操作帮助程序自动执行其中的一些操作,从而使其更加简单。
使用dojox.data.QueryReadStore
我们现在有一个用于我们的dojo.data
数据存储的端点,所以现在我们需要确定要使用的存储类型。
dojox.data.QueryReadStore
是一个很棒的dojo.data
存储,允许您对数据创建任意查询。它将查询创建为JSON对象:
{ "query": { "name": "A*" }, "queryOptions": { "ignoreCase": true }, "sort": [{ "attribute": "name", "descending": false }], "start": 0, "count": 10 }
这在两个方面存在问题。首先,如果您要直接使用它,您将仅限于POST请求,将其作为原始帖子提交。其次,相关的是,这意味着请求无法缓存在客户端。
幸运的是,有一种简单的方法可以纠正这种情况:扩展dojox.data.QueryReadStore
并重写fetch
方法,将查询重写为一个简单的GET查询,使用单个参数。
dojo.provide("custom.AutocompleteReadStore"); dojo.declare( "custom.AutocompleteReadStore", // our class name dojox.data.QueryReadStore, // what we're extending { fetch: function(request) { // the fetch method // set the serverQuery, which sets query string parameters request.serverQuery = {q: request.query.name}; // and then operate as normal: return this.inherited("fetch", arguments); } } );
现在的问题是,在哪里创建这个定义?
您有两个选择:您可以内联自定义定义(不太直观)并手动将数据存储连接到表单元素,或者您可以创建一个实际的javascript类文件(稍微多一些工作)并让您的表单元素设置数据存储你。
内联自定义QueryReadStore类扩展
完成内联有点棘手,因为您需要以适当的顺序声明事物。使用此技术时,您需要执行以下操作:
- 需要
dojox.data.QueryReadStore
类 - 定义一个全局JS变量,用于标识您的商店
- 使用
dojo.provide
和dojo.declare
创建您的自定义数据存储扩展 - 定义一个onLoad事件来实例化数据存储并将其附加到表单元素
我们可以在输出表单的同一个视图脚本中执行上述所有操作:
<?php $this->dojo()->requireModule("dojox.data.QueryReadStore"); // Define a new data store class, and // setup our autocompleter data store $this->dojo()->javascriptCaptureStart() ?> dojo.provide("custom.AutocompleteReadStore"); dojo.declare( "custom.AutocompleteReadStore", dojox.data.QueryReadStore, { fetch: function(request) { request.serverQuery = {q: request.query.name}; return this.inherited("fetch", arguments); } } ); var autocompleter; <?php $this->dojo()->javascriptCaptureEnd(); // Once dijits have been created and all classes defined, // instantiate the autocompleter and attach it to the element. $this->dojo()->onLoadCaptureStart() ?> function() { autocompleter = new custom.AutocompleteReadStore({ url: "/test/autocomplete", requestMethod: "get" }); dijit.byId("myAutoCompleteField").attr({ store: autocompleter }); } <?php $this->dojo()->onLoadCaptureEnd() ?> <h1>Autocompletion Example</h1> <div class="tundra"> <?php echo $this->form ?> </div>
这很有效,并且是让自动完成为您的元素工作的权宜之计。但是,它违反了DRY原则,因为您不能在其他区域重复使用自定义类。那么,让我们看看更好的解决方案
创建可重用的自定义QueryReadStore类扩展
Dojo开发人员的建议是,您应该将此类创建为javascript类,并使用其他javascript代码。这样做的原因有很多:您可以在其他地方重复使用该类,也可以将其包含在自定义构建中——这将确保它被去除空格并打包,从而减少最终用户的下载量。
这个过程并不像最初听起来那么可怕。假设您的public/
目录具有以下结构:
public/ js/ dojo/ dojo.js dijit/ dojox/
我们在这里要做的是为dojo
子目录创建一个兄弟目录,称为custom"
并在那里创建我们的类文件:
public/ js/ dojo/ dojo.js dijit/ dojox/ custom/ AutocompleteReadStore.js
我们将使用上面最初显示的定义,并将其简单地保存为public/js/custom/AutocompleteReadStore.js
,并添加一个:在dojo.provide调用,添加这个:
dojo.require("dojox.data.QueryReadStore");
这类似于PHP中的require_once
调用,并确保该类在声明自身之前具有所有依赖项。稍后,当我们在ComboBox
元素中提示要使用的数据存储类型时,我们将利用这一事实。
在框架方面,我们将稍微更改我们的元素定义,以包含有关它将使用的dojo.data
存储的信息:
$form->addElement('ComboBox', 'myAutoCompleteField', array( 'label' => 'My autocomplete field:', // The javascript identifier for the data store: 'storeId' => 'autocompleter', // The class type for the data store: 'storeType' => 'custom.AutocompleteReadStore', // Parameters to use when initializint the data store: 'storeParams' => array( 'url' => '/foo/autocomplete', 'requestMethod' => 'get', ), ));
如果您一直密切关注,您会注意到“storeParams”与我们在内联时用于初始化数据存储的完全相同。不同之处在于,现在ComboBox
视图助手将为您创建所有必要的Javascript。
视图脚本现在变得非常简单;我们不再需要设置任何javascript,并且可以从字面上简单地回应表单:
<?= $this->form ?>
希望现在应该清楚从长远来看哪种方法最简单。
后续步骤
dojox.data.QueryReadStore
提供的不仅仅是简单地指定查询字符串。正如在介绍该组件时所指出的那样,它创建了一个JSON结构,该结构还包括用于排序的键、选择要显示的结果数量以及提取结果时的偏移量。这些也可以添加到您的查询字符串中,以允许更细粒度地选择结果——例如,您可以确保返回的结果不超过3或5个,以允许更易于管理的匹配列表,或指定排序顺序使得对用户更有意义。
总结
学习新工具可能很困难,Dojo和ZendFramework也不例外。但是,如果您使用ZendFramework,学习Dojo的一个令人信服的理由是它的结构和设计应该是熟悉的:它使用相同的1:1类名:文件名映射范例。此外,因为它是为利用强大的OOP原则而编写的,所以可以使用扩展类等熟悉的概念来自定义Dojo,以满足您站点的需求。
希望本教程能够对Dojo中的自动完成主题以及Dojo中的类扩展有所启发,并帮助您开始创建自己的自定义Dojo库以用于您的应用程序。