Autocomplete Example With Zend_Dojo_Form_Element_FilteringSelect And Zend_Dojo_Data

Loading

Zend Framework AutoComplete example with Zend_Dojo_Form_Element_FilteringSelect
Zend Framework brings lot of user interface goodies with Zend_Dojo family of classes. In this article let us explore how to build a form element with autocomplete feature.

As a prerequisite you must be familiar with

  • Zend_Controller_Action
  • Zend_Layout
  • Zend_Form

Would it be nice if I tell you that you don't need any JavaScript knowledge? Zend_Dojo empowers PHP programmers to build dynamic and appealing forms without writing a single line of JavaScript.

This example has been tested with Zend Framework 1.7.0.

In this example, we will build a text element where the visitor can either select the user from the drop down list or type the username. While typing the username, the form element generates a drop down list filtering the data from user input. Take a look at the filteringSelect Dijit example to understand the type of form element we will be building.

FilteringSelect differs from Combobox Dojo widget in that, the value of the form element must be provided in the list. Also, you could display the username on the screen and set the 'user id' as the element value.

We will use the autoCompleteDojo action helper to send JSON data.

Let's start coding.

Step 1: Set up the Dojo environment in our bootstrap file.

<?php
// Create new view object if not already instantiated 
//$view = new Zend_View();
Zend_Dojo::enableView($view);
$view->dojo()
    ->
addStyleSheetModule('dijit.themes.tundra')
    ->
setDjConfigOption('usePlainJson'true)
    ->
disable();

$viewRenderer Zend_Controller_Action_HelperBroker::getStaticHelper(
 
'ViewRenderer'
);
$viewRenderer->setView($view);

?>

We enable Zend_Dojo in the $view object and set it to the view renderer.

In this example, we use the default CDN set in Zend_Dojo. You don't have to add Dojo JavaScript files to your web server. We add the 'tundra' stylesheet.

Step 2: Create the controller. Create the file DemoController.php in your controller directory and add the code below to it.


<?php
class DemoController extends Zend_Controller_Action
{
    public function 
indexAction()
    {
    }

    public function 
userlistAction()
    {
    }
    
    public function 
getForm()
    {
    } 

}

?>

As you can see, it is just a skeleton of our controller. We add the code to it in a moment.

We build our form in the function getForm(). In a real world application you might want to create the form in a different class and file. In indexAction we display the form and process the data submitted by the user. In userlistAction we generate the data in JSON format which is required by the filteringSelect element.

Step 3: Create the form and elements. Put the below code in the getForm() function.

<?php
 $form 
= new Zend_Form;

 
$userId = new Zend_Dojo_Form_Element_FilteringSelect('userId');
 
$userId->setLabel('Select a user')
            ->
setAutoComplete(true)
            ->
setStoreId('userStore')
            ->
setStoreType('dojo.data.ItemFileReadStore')
            ->
setStoreParams(array('url'=>'/demo/userlist'))
            ->
setAttrib("searchAttr""username")
            ->
setRequired(true);

 
$submit $form->createElement('submit''submit');

 
$form->addElements(array($userId$submit));

 return 
$form;

?>

$userId is the form element containing the Dojo filteringSelect widget. We give the name userStore to our data store. We are specifying that we will serve the data from the URL /demo/userlist. Finally, we are setting the searchAttr to 'username'.

Step 4: Prepare the database.

CREATE TABLE `user` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR( 100 ) NOT NULL ,
) ENGINE = InnoDB
 
INSERT INTO `zf`.`user` (
`id` ,
`username`
)
VALUES (
NULL , 'jamey'
), (
NULL , 'hryan'
), (
NULL , 'jennyross'
), (
NULL , 'natebrg'
), (
NULL , 'deric'
);

Create a sample table and add dummy data to it.

Step 5: Fetch the data from the database, convert to JSON format and send. I use the MySQL database 'zf' in this example. Insert below code to userlistAction(). I am connecting to the database from within the action in this example. Practically, you would want to connect to the database in a centrally accessible place like the bootstrap or a front controller plugin.

<?php
       $db 
= new Zend_Db_Adapter_Pdo_Mysql(array(
            
'host'     => '127.0.0.1',
            
'username' => 'zf',
            
'password' => 'password',
            
'dbname'   => 'zf'
        
));

        
$result $db->fetchAll("SELECT * FROM user");
        
$data = new Zend_Dojo_Data('id'$result);
        
$this->_helper->autoCompleteDojo($data);
?>

It takes only three lines to fetch the data and send it in the format Dojo requires. We pass two parameters - id and $result the Zend_Dojo_Data constructor. 'id' is the unique identifier in our database table. The autoCompleteDojo action helper is convenient to use. It disables the layout and viewrenderer. It sets the appropriate headers and sends the data using the Zend_Dojo_Data object.

Point your browser to demo/userlist, you should receive the data in a file. Dojo uses this file via dojo.data.ItemFileReadStore.

Step 6:Set up indexAction(). Add the below code to the indexAction() function.

<?php
 $form 
$this->getForm();

        if (
$this->_request->isPost()) {

            if (
$form->isValid($_POST)) {
                
/* 
                 * Process data
                 */
                
$userId $this->_getParam('userId');
                
//$userId contains the userId input by the user 
            
} else {
               
$form->populate($_POST);

               
$this->view->form $form;
            }

        } else {

           
$this->view->form $form;
        }

?>

Step 7:Set up the view and layout
In the view script index.phtml write:

<?php
<h1>Demo</h1>
<?
php
    
echo $this->form;
?>

In your layout file output the Dojo files and set the body tag to tundra class. When you echo $this->dojo(), links to Dojo JavaScript source files are printed. It also prints the JavaScript code that Zend Framework generates for you.

<?php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<
html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<
head>
    <
meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    
    <?
php 
          
echo $this->dojo()->addStylesheetModule('dijit.themes.tundra');
    
?>

</head>
<body class="tundra">
            <div id="content">
                <?php echo $this->layout()->content ?>
            </div>
</body>
</html>

?>

Initially it may appear that you have to write a lot of code to get this work. Once you understand the whole process it becomes trivial to build form elements with autocomplete feature.

I have attached the files to this post for your reference.

You might also want to take a look at:

Add A Cool Zend Dojo Date Picker Form Element Without Writing A Single Line Of JavaScript

AttachmentSize
zf-filteringSelect-example.tar_.gz11.6 KB
About the author

Sudheer is an entrepreneur and software developer. Get more from Sudheer on Twitter.


Translated in French on itanea blog

Hello Sudheer,

This is our second collaboration, I hope there will be others.
In any case, your articles are always qualities.

For the French public, you can find the translation of this article to the following address:
http://www.itanea.com/blog/index.php/2008/12/15/zend-framework-tutoriel-...

----
Hello Sudheer,

C'est notre deuxi

Re: Translated in French on itanea blog

Thanks for the translation, Fred.

Can't get it to work in the form.

First, thanks for the nice example and explanation. I've setup the code, but can't get the dropdown to display in the form. The data isn't being passed to the form for some reason. I'm on current 1.7.2 ZF code. Thanks for your advice.

The userlist action produces this output from my user table:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">{"identifier":"UserID","items":[{"UserID":"1","UserName":"peach"},{"UserID":"2","UserName":"jennifer"},{"UserID":"3","UserName":"scottie"},{"UserID":"4","UserName":"brother"}]

But my form doesn't show the datastore items in the select. The form code is from your example, with my datanames:

 	 $form = new Zend_Form;
 
	 $UserID = new Zend_Dojo_Form_Element_FilteringSelect('UserID');
	 $UserID->setLabel('Select a user')
	            ->setAutoComplete(true)
	            ->setStoreId('userStore')
	            ->setStoreType('dojo.data.ItemFileReadStore')
	            ->setStoreParams(array('url'=>'/demo/userlist'))
	            ->setAttrib("searchAttr", "UserName")
	            ->setRequired(true);
 
	 $submit = $form->createElement('submit', 'submit');
	 $form->addElements(array($UserID, $submit));
	 return $form;

Here's an excerpt from the html of just the form:

<form name="" method="post" action="" enctype="application/x-www-form-urlencoded">
 <dl class="zend_form">
  <dt><label class="required" for="UserID" id="UserID_label">Select a user</label></dt>
  <dd>
   <div tabindex="-1" wairole="combobox" dojoattachpoint="comboNode" dojoattachevent="onmouseenter:_onMouse,onmouseleave:_onMouse,onmousedown:_onMouse" id="widget_UserID" class="dijit dijitReset dijitInlineTable dijitLeft dijitComboBox dijitComboBoxHover dijitHover" role="combobox" widgetid="UserID" aria-labelledby="UserID_label">
   <div style="overflow: hidden;">
    <div dojoattachevent="onmousedown:_onArrowMouseDown,onmouseup:_onMouse,onmouseenter:_onMouse,onmouseleave:_onMouse" wairole="presentation" dojoattachpoint="downArrowNode" class="dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton" role="presentation">
     <div class="dijitArrowButtonInner">?</div>
     <div class="dijitArrowButtonChar">?</div>
    </div>
     <div class="dijitReset dijitValidationIcon"><br/></div>
     <div class="dijitReset dijitValidationIconText">?</div>
     <div class="dijitReset dijitInputField">
      <input type="text" waistate="haspopup-true,autocomplete-list" wairole="textbox" dojoattachpoint="textbox,focusNode" dojoattachevent="onkeypress:_onKeyPress, onfocus:_update, compositionend" class="dijitReset" autocomplete="off" role="textbox" aria-haspopup="true" aria-autocomplete="list" id="UserID" tabindex="0" aria-required="true" aria-invalid="false" value=""/>
      <input type="text" style="display: none;" name="UserID"/>
     </div>
    </div>
   </div>
  </dd>
  <dt></dt>
  <dd><input type="submit" value="submit" id="submit" name="submit"/></dd>
 </dl>
 
</form>

Problem in userlistAction

Hi Patricia,

There is a problem with your userlistAction. The output must be in JSON format and it should not print

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

Everything else seems all right.

Thanks for you response. Do

Thanks for you response. Do you happen to know why the header is being added to the output? I'm using _helper->autoCompleteDojo to return the data.

See where you are printing the doctype

Check your view and layout script. Disable layout and view for the userlistAction.

thanks for the nice example

thanks for the nice example and explanation
I have used this tutorial and it works perfect.

Plugin by name 'Textbox' was not found in the registry

Thanks for this tutorial which has helped me get FilteringSelect inputs working on my local server. When I uploaded to my web server (with A2 hosting) 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

The only difference with my local version is that the Zend framework is already installed on the server - does this sound like their version of the framework needs updating?

I'm following this up with A2 support too but they are taking their time getting back to me

Thanks for your time
dAN

Same query on Matthew's blog?

Hi,

I noticed you posted the exact same question on Matthew's blog. I'm curious to see your form code.

Yep, he helped me solve it

Yep, he helped me solve it too (see below!)

Thanks for looking anyway,
All the best
Dan

An excellent tutorial

Hi sudheer,

That was really, great. Thanks for that.

I suppose in the same way you can use it for server validation on forms as well?

Hi Antonios, The controller

Hi Antonios,

The controller action code I have included in the post calls the ->isValid() method of the form object. If the validation fails, the form is loaded with the user supplied values.

I hope this what you asked for.

Solved: Plugin by name 'Textbox' was not found in the registry

Thanks to Matthew Weier O'Phinney who pointed out that my code (which I didn't post here...) used "textbox" rather than "textBox".

This didn't cause a problem locally as I've been running zend 1.6.2, but the server is running 1.7.2

So now I can move on to the next brick wall :)

I have to say though I'm enjoying learning Zend and looking forward to the day when I feel like I have a clue what's going on!

Dan

a clue

Dan - you have been at it two years since this comment - and I have just started.
Do you feel like you have a clue, yet?

- looking for a light at the end of the tunnel ;-)

Problem in 1.7.6?

Great tutorial. It helped me understand Zend and Dojo integration much better.

I've been trying to use your code as guide for my own application and was having trouble. I've narrowed the problem down to Zend's use of dojo.addOnLoad to create the ItemFileReadStore.

dojo.addOnLoad(function() {
    strainStore = new dojo.data.ItemFileReadStore({"url":"\/strain\/list"});
});

It seems that is isn't happening in time. I can fix this one of two ways:

Add a line to the onLoad function above to ensure the datastore is attached to the FilteringSelect:

dijit.byId('form_id').store = strainStore;

But since that isn't really doable inside Zend, I can add this to the view to create the store in markup (which seems to happen in time)

<div dojoType="dojo.data.ItemFileReadStore" url="/strain/list" jsId="strainStore"></div>

Am I missing something or is something broken in Zend 1.7.6? Thanks.

Something definitely has

Something definitely has changed in 1.7.6. I think it is the View Helper. I will look at the problem in detail and post the updates soon.

Any updates on 1.7.6?

Hi Sudheer,
Can you post the updates/code to make this example work with Zend 1.7.6?

Much Appreciated,
Amit

Issue in ZF issue tracker

Hi,

There's an issue with 1.7.6 which is causing this. Matthew told us on the mailing list that he will look into it.

http://framework.zend.com/issues/browse/ZF-6018

Looks like the issue will be resolved in 1.7.7 release.

Thanks for the quick response

Thanks for the quick response Sudheer.
Any idea approximately when 1.7.7 should be released by?

cheers,
Amit

It was released yesterday.

It was released yesterday. The issue has been fixed in ZF 1.7.7.

For Future Reference for others

This example was verified with Version 1.7.0, 1.7.5.
It does NOT work with 1.7.2 because of the format of JSON data created.
Hope this saves the readers some time. :)

cheers,
Amit

Number of requests

Hi,

Thank you for the tutorial! it works great.
Only I have a question about the performace. When typing in a username it make a xmlhttp request for retrieving al the availaible usernames. Because my list is not so big, I return all the available usernames. But now this script is making a request for each letter in type which is not neccesary because the received list is already complete.

How can I change the interval of requests?

Not working for big data

THanks for the tutorial.

It is working nic efor small data but not working for big data ( not even for records > 200 and < 300 )

Can you please guide on this?

I will test the script with

I will test the script with large data set as soon as I get some time.

For large data set I recommend implementing queryReadStore instead of itemFileReadStore.

dojo autocomplete working with large data

Ok, it was my mistake, data was duplicated in the table. I selected distinct keys and it works with large data as well.

I am now actually stuck with another proble. I want user either to select option from given autocomplete options or enter his own new data.

1. Filteringselect allows you only to select from the options provided. It does not accept new value

2. If I use dojo combobox then it allows to enter new data but then when you try to type in something in combobox, it shows all data in dropdown. It does not filter with keys as we type in. It jsut highlightes the typed in keys

I am finding solution for either of it.

PLease help.

THanks in advance.

Use another field

You will have to implement a custom solution to address this problem.

In your situation, I would do something like this.

Provide the filteringSelect form widget for the user to select an item from the data store. If the item is not in the data store and needs to be created by the user, I would provide a 'create new button'. Upon clicking this 'create new' button, I would programatically generate a new form field client side. When the form is submitted, I would check if an existing item was selected or a new one was entered in the generated form field.

A simpler solution is to provide two fields -
a) filteringSelect widget to select an item from the data store
b) another form field to submit a new value.

Problem to implement this cOol code.

First thanks to share your work.
I've test your application with this archive zf-filteringSelect-example.tar_.gz and it works fine. But when I try to implement it in my multilanguage modular MVC application the data doesn't seems to be load in the form which is skinned with dijit.

The following url http://localhost/fr/admin/demo/userlist give me the correct JSON code.

I'm using this kind of route

new Zend_Controller_Router_Route(
        "/:language/:module/:controller/:action/*",

I don't understand what's going on.
Can you help me please ? Thanks in advance.

Correct URI?

Have you double checked the URI you specify in

<?php
   
->setStoreParams(array('url'=>'http://localhost/fr/admin/demo/userlist'))
?>

Works fine now ;- )

Everything works fine now.
Why i didn't see it before .... I even know myself.
Thanks a lot.

Thanks Bonaparte!

Cheers for this, and all the help in IRC. A very nicely written tutorial that I will be pointing out to anyone who asks me about learning Zend_Dojo.

You're welcome. Look forward

You're welcome. Look forward for more Zend_Dojo posts here on Tech Chorus.

Implementing dijit.tree and zend_dojo_data

Hi everyone,

just trying to implement zend_dojo_data and dijit.tree. It works if the results have only parents, but I'm struggling to get it to work with children.

I'm running an sql that checks on 2 tables using INNER JOIN, the returned result is an array.

Array ( [1] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) [2] => Array ( [manufacturer] => Chevrolet [vehicle_id] => 7 [manufacturer_id] => 2 [vehicle] => Chevette [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) [3] => Array ( [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) [4] => Array ( [manufacturer] => Ford [vehicle_id] => 10 [manufacturer_id] => 4 [vehicle] => Escortt [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) [5] => Array ( [manufacturer] => Fiat [vehicle_id] => 12 [manufacturer_id] => 5 [vehicle] => Marea [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) [6] => Array ( [_children] => Array ( [0] => Array ( [manufacturer] => Chevrolet [vehicle_id] => 7 [manufacturer_id] => 2 [vehicle] => Chevette [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) ) ) [7] => Array ( [_children] => Array ( [0] => Array ( [manufacturer] => Chevrolet [vehicle_id] => 7 [manufacturer_id] => 2 [vehicle] => Chevette [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) ) ) [8] => Array ( [_children] => Array ( [0] => Array ( [manufacturer] => Ford [vehicle_id] => 10 [manufacturer_id] => 4 [vehicle] => Escortt [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) ) ) [9] => Array ( [_children] => Array ( [0] => Array ( [manufacturer] => Ford [vehicle_id] => 10 [manufacturer_id] => 4 [vehicle] => Escortt [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) ) ) [10] => Array ( [_children] => Array ( [0] => Array ( [manufacturer] => Ford [vehicle_id] => 10 [manufacturer_id] => 4 [vehicle] => Escortt [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) ) ) [11] => Array ( [_children] => Array ( [0] => Array ( [manufacturer] => Fiat [vehicle_id] => 12 [manufacturer_id] => 5 [vehicle] => Marea [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) ) ) [12] => Array ( [_children] => Array ( [0] => Array ( [manufacturer] => Fiat [vehicle_id] => 12 [manufacturer_id] => 5 [vehicle] => Marea [_children] => Array ( [0] => Array ( [manufacturer] => Wolksvagen [vehicle_id] => 5 [manufacturer_id] => 1 [vehicle] => Saveiro ) ) ) ) ) )

but I'm trying to get the right array format so hopefully when I convert it to JSON it will be like an valid JSON format with children.

does anybody know any tuto or have any examples for this?

Cheers

Tree and nested arrays

Trees are by nature hierarchical. If you have a nested array, you can convert it to JSON or Zend_Dojo_Data.

At last

I have had major problems with ZFW and Dojo. At last I found this - clean example not too old. At last understood what I did wrong. Thank You.

broken link

the link to "filteringSelect Dijit example"
should be
http://api.dojotoolkit.org/jsdoc/1.3/dijit.form.FilteringSelect ??

Re: broken link

Thanks for reporting the broken link.

I have updated the blog post with the correct link.

Help

Hi, can i have some help please? I've done everything as described in the tutorial, but when i start to type something in the textbox, i get a message saying the value i enter is invalid. That happens even when i start to type the first letter. Any help will be great.

Visit the store URL and make

Visit the store URL and make sure the items are loaded.

Thanks, it works now. I have

Thanks, it works now. I have another question though. Is it possible to set the searchAttrib to more than one field (like username in your example)? Would it be possible to have like, username, id and maybe more fields set as the searchAttrib?

Not that I'm aware of

Not that I'm aware of

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>. The supported tag styles are: <foo>, [foo].

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.