我已经解决了几个关于使用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库以用于您的应用程序。
