Friday, December 12. 2008Autocompletion with Zend Framework and DojoI've fielded several questions about setting up an autocompleter with Zend Framework and Dojo, and decided it was time to create a HOWTO on the subject, particularly as there are some nuances you need to pay attention to. Which dijits perform autocompletion?
Your first task is selecting an appropriate form element capable of
autocompletion. Dijit provides two,
Once you've chose the appropriate form element type, you then need to
specify a Defining the form element
Defining the form element is very straightforward. From your
Providing data to a dojo.data store
We're going to work backwards now, as providing data to the data store is
relatively trivial when using
First, we'll create an action in our controller, and assign the model and
the query parameter to the view. We'll be setting up our
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', ''); }
Now, let's create the view script. First, we'll disable layouts; second,
we'll pass our query to the model; third, we'll instantiate our
<?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;
That's really all there is to it. You can actually automate some of this
using the Using dojox.data.QueryReadStore
We now have an endpoint for our
{ query: { name: "A*" }, queryOptions: { ignoreCase: true }, sort: [{ attribute: "name", descending: false }], start: 0, count: 10 } This is problematic in two ways. First, if you were to use it directly, you'd be limited to POST requests, submitting it as a raw post. Second, and related, this means that requests could not be cached client-side.
Fortunately, there's an easy way to correct the situation: extend
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); } } ); The question now is, where to create this definition? You have two options: you can inline the custom definition (less intuitive) and connect the data store manually to the form element, or you can create an actual javascript class file (slightly more work) and have your form element setup the data store for you. Inlining a custom QueryReadStore class extensionInlining is a bit tricky to accomplish, as you need to declare things in the appropriate order. When using this technique, you need to do the following:
We can do all the above within the same view script in which we spit out our form: <?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> This works well, and is an expedient way to get autocompletion working for your element. However, it breaks the DRY principle as you cannot re-use the custom class in other areas. So, let's look at a better solution Creating a reusable custom QueryReadStore class extensionThe recommendation by the Dojo developers is that you should create this class as a javascript class, with your other javascript code. The reasons for this are numerous: you can re-use the class elsewhere, and you can also include it in custom builds -- which will ensure that it is stripped of whitespace and packed, leading to smaller downloads for your end users. The process isn't as scary as it may initially sound. Assuming that your "public/" directory has the following structure:
what we'll do here is to create a sibling to the "dojo" subdirectory, called "custom", and create our class file there:
We'll use the definition as originally shown above, and simply save it as
"public/js/custom/AutocompleteReadStore.js", with one addition: after the
dojo.require("dojox.data.QueryReadStore");
This is analagous to a
On the framework side of things, we're going to alter our element definition
slightly to include information about the $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', ), ));
If you've been following along closely, you'll notice that the "storeParams"
are exactly the same as what we used to initialize the data store when
inlining. The difference is that now the The view script now becomes greatly simplified; we no longer need to setup any javascript, and can literally simply echo the form: <?= $this->form ?> Hopefully it should now be clear which method is easiest in the long run. Next Steps
SummaryLearning new tools can be difficult, and Dojo and Zend Framework are no exceptions. One compelling reason to learn Dojo if you're using Zend Framework, however, is that its structure and design should be familiar: it uses the same 1:1 class name:filename mapping paradigm. Additionally, because it is written to utilize strong OOP principles, familiar concepts such as extending classes can be used to customize Dojo for your site's needs. Hopefully this tutorial will shed a little light on both the subject of autocompletion in Dojo, as well as class extensions in Dojo, and help get you started creating your own custom Dojo libraries for use with your applications. Trackbacks
Trackback specific URI for this entry
No Trackbacks
Comments
Display comments as
(Linear | Threaded)
Thanks for the how to, Matthew. I have been wanting to write a similar post from a long time. I completed it today.
http://techchorus.net/autocomplete-example-zenddojoformelementfiltringselect-and-zenddojodata Well, before reading your article, I was wondering how Zend will integrate with Dojo...
After reading your nice article, I'm a bit disapointed as I thought that it will be no "javascript" code to do like mentionned in the capture part of your article... Hi Nocolas,
It is possible to use Dojo widgets without writing a single line of JavaScript. I have written two articles on using Zend_Dojo without writing a single line of JavaScript. But I think, as the application grows in size and complexity it requires custom JS coding. This article is a good starting point. @Nicoloas - as Sudheer noted, you *can* accomplish a lot with ZF's Dojo integration without writing any JS. In fact, if you use dojo.data.ItemFileReadStore for your dojo.data store, you can do autocompletes without writing any JS. The problem with that read store, however, is that you can't do any querying -- you get the _full_ list of potential matches. QueryReadStore, on the other hand, allows you to do selective querying -- i.e., if you typed "a", you can do lookups in your DB for 'LIKE 'a%'. This helps you limit the results presented to the user.
At a certain point, you simply _must_ start writing Javascript, and this tutorial was intended to show that it really does not need to be a scary thing. If you go the DRY route, you'll write a very simple, trivial JS file, drop it in your public tree, and you've suddenly got _very_ easy integration from ZF -- no JS on the ZF side whatsoever. Unfortunately I am not able to get this running. The searching and presenting the data by calling the controller action directly works as expected. The output also seems ok.
My AutocompleteReadStore.js looks like this: ----------------------- dojo.provide("custom.AutocompleteReadStore"); dojo.require("dojox.data.QueryReadStore"); dojo.declare( "custom.AutocompleteReadStore", dojox.data.QueryReadStore, { fetch: function(request) { request.serverQuery = {q: request.query.name}; return this.inherited("fetch", arguments); } } ); ----------------------- My form looks like this ----------------------- class Form_Autocomplete extends Zend_Dojo_Form { public function init() { $this->setMethod('post'); $this->setName('ajax_form'); $this->addElement('FilteringSelect', 'gericht', array( 'label' => 'Was möchtest du bestellen?', 'storeId' => 'autocompleter', 'storeType' => 'custom.AutocompleteReadStore', 'storeParams' => array( 'url' => '/index/autocomplete', 'requestMethod' => 'get', ), )); $this->addElement('SubmitButton', 'submit_send', array( 'label' => 'Bestellen', )); } } ----------------------- Whenever I enter anything in the input field I get the message "The value entered is not valid". The possible entries for my input are not shown. When I hit the tab to jump to the submit button, the message disappears and the number of the first entry matching my input is shown in the FilteringSelect field. This is weird! When I use a ComboBox element, nothing seems to happen at all. I checked the http headers with the Live http headers browser plugin and the requests seem to be send. Maybe the output format of the response is wrong? Any idea how to solve this? Thanks, Ralf To add some more information here is my view script:
---------------------------------------- ---------------------------------------- And here some sample output from this view script ---------------------------------------- {"identifier":"dish_id","items":[{"dish_id":"3","dish_name":"Pizza Salami"},{"dish_id":"9","dish_name":"Spagetti Salami"},{"dish_id":"17","dish_name":"Calzone Salami"}],"label":"dish_name"} ---------------------------------------- When I delete the echo new Zend_Dojo_Data() line from my view script and enter the ouput string manually then the behavior of the ComboBox and FilteringSelect changes. When I enter anything I get a list of three options all called "undefined". Could this be a combination of a wrong header and a format problem of the output or anything like this? Ok, its me again, sorry the high posting frequency but I really want to get this done. I found a solution for the "undefined" problem. Dojo does not like any fields with an underscore, so I need to change the output like this:
---------------------------------------- {"identifier":"id","items":[{"id":"3","name":"Pizza Salami"},{"id":"9","name":"Spagetti Salami"},{"id":"17","name":"Calzone Salami"}],"label":"name"} ---------------------------------------- But still the output of the Zend_Dojo_Data in my view script does not work as expected. Here is my view-script: ---------------------------------------- $this->layout()->disableLayout(); $data = new Zend_Dojo_Data('id', $this->data, 'name'); echo $data; ---------------------------------------- When I delete the echo $data row and hardcode any output, it does work, but not when using Zend_Dojo_Data. How could this be? Nice tutorial
i followed it but got the following error : exception 'Zend_Loader_PluginLoader_Exception' with message 'Plugin by name 'ComboBox' was not found in the registry can u attach the example files so we can download and take a look into it running plz ? thanks for the article. I'm using a CDN for the Dojo sources. Using the example above throws the exception:
"uncaught exception: Could not load cross-domain resources: custom.AutocompleteReadStore" How would I implement the example without losing the CDN option? hii friend please help me
i am using zend framework 1.7.1 and Dojo while running my code dojo gives error invalid controller The proper place to ask support questions is on the ZF mailing lists.
Check to see if you are using the recommended apache RewriteRules -- we modified them for 1.6.0 as we discovered that they were too restrictive when developing Dojo applications -- and often led to Dojo resources being intercepted by the MVC layer. hiya - I've followed this tutorial and it's working well for me, so I must say thank you for that
I'm stuck at what seems like the final hurdle: getting the widget to store the relevant value when I click on an entry in the dropdown list. I've posted a more detailed question about it here: http://www.dojotoolkit.org/forum/dijit-dijit-0-9/dijit-support/autocomplete-filteringselect-problem-setting-value Any help would be very much appreciated, cheers! Update: I got the widget to work by duplicating my "tradeName" db column as a new column "name", which confirms that the textbox is looking in the datastore for a value "name".
What I can't work out is how to tell the widget to use the value of "tradeName" instead. Weird that it works ok for the dropdown list... Seems like a solution is close... When you create your dojo.data source, there are, minimally, three top level items: identifier, label, and items. Specify "tradeName" for the identifier property.
If using Zend_Dojo_Data to create your dojo.data payload, the identifier name is the first argument to the constructor: $data = new Zend_Dojo_Data("tradeName", $items); Thanks Matthew for taking the time to reply - yep had that already, but I got a reply over at dojotoolkit which helped me fix it.
When I create the widget I had to set the searchAttr property to "tradeName", and then I had to update my custom read store to pass the value of tradeName: request.serverQuery = {q: request.query.tradeName}; It means I can't re-use my custom read store as I'd like to but I'm relieved it's working, and this has given me a better insight into how all this stuff fits together. Thanks again for your tutorial, all the best dAN hi again Matthew
All is going well locally but when I upload my autocomplete form to the server (A2 hosting) where I get the following error message: Fatal error: Uncaught exception 'Zend_Loader_PluginLoader_Exception' with message 'Plugin by name 'Textbox' was not found in the registry; used paths: Zend_Dojo_Form_Element_: Zend/Dojo/Form/Element/ Zend_Form_Element_: Zend/Form/Element/' in /usr/lib/php/ZendFramework/ZendFramework-1.7.2/library/Zend/Loader/PluginLoader.php:386 Stack trace: #0 /usr/lib/php/ZendFramework/ZendFramework-1.7.2/library/Zend/Form.php(1054): Zend_Loader_PluginLoader->load('textbox') #1 /usr/lib/php/ZendFramework/ZendFramework-1.7.2/library/Zend/Form.php(986): Zend_Form->createElement('textbox', 'peopleName', Array) #2 /home/sqrbrkt/zf/illhomes/application/forms/PeopleForm.php(15): Zend_Form->addElement('textbox', 'peopleName', Array) #3 /usr/lib/php/ZendFramework/ZendFramework-1.7.2/library/Zend/Form.php(223): Form_PeopleForm->init() #4 /usr/lib/php/ZendFramework/ZendFramework-1.7.2/library/Zend/Dojo/Form.php(50): Zend_Form->__construct(NULL) #5 /home/sqrbrkt/zf/illhomes/application/controllers/PeopleController.php(163): Zend_Dojo_Form->__const in /usr/lib/php/ZendFramework/ZendFramework-1.7.2/library/Zend/Loader/PluginLoader.php on line 386 This displays for any Zend_Dojo form that uses a textbox element, including my autocomplete forms. A2 have the Zend framework preinstalled on the server - all I can think is that their version needs updating. I'm following this with them but it's been a couple of days with no progress. If you have any quick insights they be much appreciated Cheers dAN The plugin should be 'TextBox' or 'textBox' -- note the capital 'B'.
I've just found that the reason I can't get this example working is down to a bug in ZF: http://framework.zend.com/issues/browse/ZF-4587.
Thought I'd let anyone else know who might be scratching their heads. yep that's done it
I realised I'm running version 1.6.2 locally, but the server is running version 1.7.2 which I guess is why it was working fine locally. Time to update! Cheers again dAN ...or more likely because I'm testing locally on windows (using WAMP) and the server is unix based
Add Comment
|
Calendar
QuicksearchLinks
CategoriesSyndicate This BlogShow tagged entries |
|||||||||||||||||||||||||||||||||||||||||||||||||




