6
Apr/10
4

Creating multiple dependent filtering selects with Dojo

The problem…

I found the need to create multiple filtering select boxes that were dependent on each other. In this post I use the example of vehicles. I want the ability to sort my vehicle models by make but also by type. That is type as in compact, SUV, truck, etc. We will achieve this using JSON, the Dojo ItemFileReadStore, and the Dijit FilteringSelect.

The Setup

The first thing we need to do is include the Dojo library. I will be using the AOL CDN (Content Deliver Network) to serve the Dojo files.

<!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">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>Automobiles</title>
  <link rel="stylesheet" href="http://o.aolcdn.com/dojo/1.4/dojo/resources/dojo.css" media="screen" />
  <link rel="stylesheet" href="http://o.aolcdn.com/dojo/1.4/dijit/themes/tundra/tundra.css" media="screen" />
  <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.4/dojo/dojo.xd.js"
    djConfig="isDebug:true, parseOnLoad:true"></script>
</head>
<body class="tundra">
</body>
</html>

Okay, now we are ready to start working with Dojo but before we get to that we need to figure out how to structure our data. We will use JSON to store our data. We will also have separate type, make, and model objects. We can create the association to type and make in the model object.

The format accepted by the ItemFileReadStore is pretty flexible but must follow a specific convention. We must specify what our human readable text field will be as well as a distinct identifier. We could get away with not having an identifier if we do not have any repeated values but, as you will see later, we will need them to create the association between the three selects. Here is the structure our objects must follow:

{
  label: "name",
  identifier: "myId",
  items: [
    {myId: 0, name: "object 1"},
    {myId: 1, name: "object 2"}
  ]
}

“myId” is the distinct identifier we have designated for our objects and “name” is the human readable label we have applied to it. We can also include additional properties if we wish to do so.

The Data Structure

As stated earlier, we will have 3 JSON objects which we will create ItemFileReadStores with. The first is the car type., second is the car make, and third is the car model. We will filter our model list depending on what is selected in the first two. In the head of the document, after the script tag, add the following.

<script type="text/javascript">
         var types = {
		label: "name",
		identifier: "typeId",
		items: [
			{typeId: 0, name: "compact"},
			{typeId: 1, name: "full size"},
			{typeId: 2, name: "suv"},
			{typeId: 3, name: "truck"},
			{typeId: 4, name: "van"},
			{typeId: 5, name: "sports"}
		]
        };
        var makes = {
		label: "name",
		identifier: "makeId",
		items: [
			{makeId: 0, name: "Ford"},
			{makeId: 1, name: "Cheverolet"},
			{makeId: 2, name: "Dodge"}
		]
	};
 
	var models = {
		label: "name",
		identifier: "modelId",
		items: [
			{modelId: 0, name: "Edge", makeId: 0, typeId: 2},
			{modelId: 1, name: "Escape", makeId: 0, typeId: 2},
			{modelId: 2, name: "E-Series Van", makeId: 0, typeId: 4},
			{modelId: 3, name: "Expedition", makeId: 0, typeId: 2},
			{modelId: 4, name: "Explorer", makeId: 0, typeId: 2},
			{modelId: 5, name: "F-150", makeId: 0, typeId: 3},
			{modelId: 6, name: "Fiesta", makeId: 0, typeId: 0},
			{modelId: 7, name: "Flex", makeId: 0, typeId: 2},
			{modelId: 8, name: "Focus", makeId: 0, typeId: 0},
			{modelId: 9, name: "Fusion", makeId: 0, typeId: 1},
			{modelId: 10, name: "Mustang", makeId: 0, typeId: 5},
			{modelId: 11, name: "Ranger", makeId: 0, typeId: 3},
			{modelId: 12, name: "Avalanche", makeId: 1, typeId:3},
			{modelId: 13, name: "Aveo", makeId: 1, typeId: 0},
			{modelId: 14, name: "Camaro", makeId: 1, typeId: 5},
			{modelId: 15, name: "Cobalt", makeId: 1, typeId: 0},
			{modelId: 16, name: "Colorado", makeId: 1, typeId: 3},
			{modelId: 17, name: "Corvette", makeId: 1, typeId: 5},
			{modelId: 18, name: "Equinox", makeId: 1, typeId: 2},
			{modelId: 19, name: "HHR", makeId: 1, typeId: 2},
			{modelId: 20, name: "Impala", makeId: 1, typeId: 1},
			{modelId: 21, name: "Malibu", makeId: 1, typeId: 1},
			{modelId: 22, name: "Silverado", makeId: 1, typeId: 3},
			{modelId: 23, name: "Avenger", makeId: 2, typeId: 1},
			{modelId: 24, name: "Caliber", makeId: 2, typeId: 0},
			{modelId: 25, name: "Challenger", makeId: 2, typeId: 1},
			{modelId: 26, name: "Charger", makeId: 2, typeId: 1},
			{modelId: 27, name: "Dakota", makeId: 2, typeId: 3},
			{modelId: 28, name: "Durango", makeId: 2, typeId: 3},
			{modelId: 29, name: "Grand Caravan", makeId: 2, typeId: 4},
			{modelId: 30, name: "Journey", makeId: 2, typeId: 2},
			{modelId: 31, name: "Ram", makeId: 2, typeId: 3},
			{modelId: 32, name: "Viper", makeId: 2, typeId: 5}
		]
	};
</script>

Now we have our data. Note the association to the type (typeId) and make (makeId) in the models. We will use these two fields to query and filter for our select.

Creating the FilteringSelect

There are a couple different ways to create a filtering select with Dojo. You can do so in the mark up or you can do it programatically as we will here. Before we start working with the FilteringSelect or the ItemFileReadStore we must tell Dojo we wish to use them. We must “require” them. This is similar to an import in other languages. Add the following two lines to the script block we have just created.

dojo.require("dijit.form.FilteringSelect");
dojo.require("dojo.data.ItemFileReadStore");
Note: Since we are using the CDN the Dojo files will be loaded asynchronously. This means they may not be available for us to start working with as the page loads. Any code that relies on the Dojo files will need to be executed after the page load. Use dojo.addOnLoad() for this.

Before we write the code to create our filtering selects we must add the place holders to the body of the page

<label for="type">
	Car Type:
</label>
<input id="type" />
 
<label for="make">
	Make:
</label>
<input id="make" />
 
<label for="model">
	Model:
</label>
<input id="model" />

It is very simple. Nothing spectacular here. Now let’s get to the javascript. Add the following to your script block.

        dojo.addOnLoad(function() {
 
		new dijit.form.FilteringSelect({
			store: new dojo.data.ItemFileReadStore({
				data: types
			}),
			autoComplete: true,
			style: "width: 150px;",
			required: true,
			id: "type",
			onChange: function(type) {
			}
		}, "type");
 
		new dijit.form.FilteringSelect({
			store: new dojo.data.ItemFileReadStore({
				data: makes								   
			}),
			autocomplete: true,
			style: "width: 150px;",
			required: true,
			id: "make",
			onChange: function(make) {
			}
		}, "make");
 
		new dijit.form.FilteringSelect({
			store: new dojo.data.ItemFileReadStore({
				data: models								   
			}),
			autocomplete: true,
			style: "width: 150px;",
			required: true,
			id: "model"
		}, "model");
 
	});

What we are doing here is creating a new ItemFileReadStore using the JSON objects we created earlier. We are then passing that store to the FilteringSelect in order to populate it.

Go ahead and view this page in your browser. With that little bit of javascript we now have populated, working, filtering selects. Awesome, but we are not done yet. The filtering is great but we need to reduce the clutter in the models list. We may not know exactly what we are looking for and it would be easier to search through a smaller list. How will we do that?

Querying the store to filter results

We know that we need 2 pieces of information to filter our results for the models list. The first is the type and the second the make. We should probably also keep in mind that one may have been populated and not the other. For instance, if someone were to select “truck” as a type we should show all trucks regardless of make. The same goes for showing all models for a make if a type is not selected. We will need to keep track of what is selected. Let’s create carTypeId and carMakeId variables to keep track of this. We will set the initial value to null so we can check against this later.

var carTypeId = null;
var carMakeId = null;

Now we can fill in the bodies of those onChange functions to filter our list. First we need to populate our newly created variables to track the state. Then we will do the query on the model list.

                 new dijit.form.FilteringSelect({
			store: new dojo.data.ItemFileReadStore({
				data: types
			}),
			autoComplete: true,
			style: "width: 150px;",
			required: true,
			id: "type",
			onChange: function(type) {
				carTypeId = type;
				dijit.byId('model').query = {makeId: (carMakeId == null) ? "*" : carMakeId, typeId: type};
			}
		}, "type");
 
		new dijit.form.FilteringSelect({
			store: new dojo.data.ItemFileReadStore({
				data: makes								   
			}),
			autocomplete: true,
			style: "width: 150px;",
			required: true,
			id: "make",
			onChange: function(make) {
				carMakeId = make;
				dijit.byId('model').query = {makeId: make, typeId: (carTypeId == null) ? "*" : carTypeId};
			}
		}, "make");

We are setting the query object for our models to include a make id and a type id. As stated earlier we are checking to be sure that both have been set. If not, we are using the * wildcard and this will match anything in our store.

There you have it. FilteringSelects which are dependent on each other. I hope you find this useful. I know this could have been achieved by other means. I see that relationships can be created in an ItemFileReadStore but found it easier to go this route as the data was already segregated and it made querying for the selects very simple. If you have other suggestions please mention them in the comments.

11
Sep/09
0

Back and ready for more!

After traveling for the past month, up and down the east coast, and even a trip out to Wisconsin, I am back at home. For those of you who have asked for help or clarification on one of my previous posts I will try to take care of that for you right away. I will also try to get the source code for the examples posted.

I am back working on AircraftConnect.com, so look for more Zend, Dojo, and Dijit posts to be coming.

3
Aug/09
3

Dijit Tooltips

Why such poor documentation?

I find it very hard to understand why Zend would choose such a poorly documented Javascript library to include with their framework. I understand Dojo itself has some undergone some serious changes with even minor releases (I am using version 1.3) which is very, very frustrating for developers. I have not had any problems finding documentation for Prototype or jQuery. Why is there such a lack of documentation for such a high profile library? I even attended the Rational Developers Conference and IBM was pushing the dojo framework. It makes me want to bang my head against the wall. Everything I could find is outdated.

Now that my rant is over, it does look like dojotoolkit.org is being updated with current data. Perhaps, if Zend will continue to ship their framework with dojo, they can send some money their way to help resolve this issue. Otherwise, we may see a lot of people going back to Prototype, jQuery, or YUI.

To the subject at hand

Such a simple task, yet all documentation I could find is out of date. Here is how to create a tooltip with dojo and dijit in a Zend page template. In this case I added a tooltip to the search form.

myPage.phtml

<?php 
   echo $this->headLink()->appendStylesheet('/styles/forms.css');
   echo $this->dojo()->enable()->setLocalPath('/scripts/Dojo/dojo/dojo.js')
          ->addStyleSheetModule('dijit.themes.tundra')
          ->setDjConfigOption('parseOnLoad', true)
          ->requireModule('dijit.Tooltip');
?>
<div id="search_form">
	<form id="search" onsubmit="return false">
		Search: <input type="text" id="search_box" onkeypress="search(event)" />
		<div dojoType="dijit.Tooltip" connectId="search_box" label="<div style='text-align:left'><b>To search for $ amount:</b><br /> Enter '<' or '>' and amount. <br /><br /><b>To search a state</b><br /> Enter the state name.<br /><br /><b>Everything else</b><br />Just enter the text to search for.</div>"></div>		
	</form>
</div>

What it does

When the user hovers over the search field a tooltip is shown, giving the user suggestions on how to use the search. Simple enough, right? Just require the correct modules, use “connectId” to assign the tooltip to the correct element, and use the label attribute to set the text of your tooltip. Was that so hard? Perhaps dojotoolkit.org should go to a wiki format so we developers can update it as we figure out how to use it.

18
Jul/09
23

Zend Framework: The Dijit Editor

Introduction

In one of my current projects, AircraftConnection.com, I decided to use the dijit.Editor WYSIWYG editor in my user’s forms. I ran into a few problems and could not find much documentation on the topic.

  1. I wanted the label, description, and field in a different order.
  2. I wanted the data entered into the editor to actually be passed to the server when the form was submitted.
  3. I wanted to pre-populate the form with data the user had previously entered.

I was able to find a few posts related to posting the data to the server via a hidden field but these mostly did so via Ajax, which is fine. It just didn’t suit my needs. I adopted and manipulated their methods to work in the way I wanted.

Prerequisites

  • A web server with PHP installed
  • The Zend Framework
  • Dojo and Dijit Javascript libraries

In this post I make the assumption that you already have the above installed as well as your ZF project created with the Javascript libraries included.

Note: I have set up my example application to use the “Blog_” namespace. I have also used the autoloader to add namespaces for Form and Element. Please keep this in mind when you see a line of code such as new Blog_Element_Editor().

Getting Started

The application we are going to build will be very simple. We will have a single controller, IndexController, with two actions, indexAction and editAction. We will use the editAction to edit the content that is displayed in the index action. The editAction is where we will display the form with the dijit.Editor.

So, in reality, the only work we are going to do will be in the IndexController::editAction() method. This will handle displaying the form as well as processing the data posted to the server.

Creating the Form Elements

The first thing in my list was that I wanted to display the label, description, and field in a different order. The default is label, field, description, which really doesn’t make all that much sense. So, the first thing we need to do is create our own Element class and extend the dijit.Editor class. We need to override the loadDefaultDecorators() method.

Editor.php

class Blog_Element_Editor extends  Zend_Dojo_Form_Element_Editor {
 
   public function loadDefaultDecorators() {
      if($this->loadDefaultDecoratorsIsDisabled()) {
         return;
      }
 
      $decorators = $this->getDecorators();
      if(empty($decorators)) {
         $this->addDecorator('DijitElement')
                ->addDecorator('Description', array(
                      'tag' 		=> 'p',
		      'class'		=> 'description',
		      'placement'	=> Zend_Form_Decorator_Abstract::PREPEND))
	         ->addDecorator('HtmlTag', array(
		      'tag'		=> 'dd',
		      'id'	        => $this->getName() . '_element'))
	         ->addDecorator('Label', array(
		      'tag'		=> 'dt',
		      'escape'	=> false));
      }
   }
}

Basically all we are doing here is changing the value of “placement” for our description to be prepend instead of append. One item to not is the use of “DijitElement” instead of the normal “ViewHelper” since we extending a Dijit element. Now we can create our form. The form will consist of a single editor and a submit button.

Creating the Form

As stated, we are going to create a form and add one of the Editor elements we just created as well as a submit button. We will do this by extending Zend_Form.

EditForm.php

class Blog_Form_EditForm extends Zend_Form {
 
   public function init() {
      Zend_Dojo::enableForm($this);
   }
 
   public function __construct($options=null) {
      parent::__construct($options);
 
      $this->setName('editForm');
      $this->setMethod('post');
 
      $this->addElement(new Blog_Element_Editor( 'editor', array(
			'label'		=> 'Edit Page:',
                        'description'=> 'This form allows you to edit the index page.',
			//'plugins'	=> array('undo', '|', 'bold', 'italic'), //<- you can choose your plugins here
                        'dojoType'	=> 'dijit.Editor'
      )));
 
      $this->addElement('submit', 'submit', array(
			'ignore'	=> true,
			'label'		=> 'continue'
      ));
   }
}

And the form is done. If you haven’t already, create your editAction function and the edit view.

The Edit View

At this point the view is very simple. We will add some Javacsript to it in just a bit.

edit.phtml

echo $this->form;
if($this->dojo()->isEnabled()) {
   $this->dojo()->setLocalPath('/scripts/Dojo/dojo/dojo.js')
          ->addStyleSheetModule('dijit.themes.tundra');
   echo $this->dojo();
}

Here we are checking to see if dojo is enabled, which we did enable via our form in the init() function. We also set the path to the dojo.js file (change this to wherever this file exists in your project relative to the public directory) and also select a stylesheet to use for the editor. Be sure to echo out the form prior to checking to see if dojo is enabled or it will fail. Now on to the controller.

Note: In the below code I make use of a logger. If you do not wish to do so you can simply echo out any errors, send them to firebug, etc. Your choice.

IndexController.php

class IndexController extends Zend_Controller_Action
{
   private $logger;
 
   public function init() {
      $this->logger = Zend_Registry::get('logger');
   }
 
   /**
    * The default action - show the home page
    */
    public function indexAction()
    {
        // TODO Auto-generated IndexController::indexAction() action
    }
 
    public function editAction() {
    	$form = new Blog_Form_EditForm();
 
    	$this->view->form = $form;
    }
}

All we are doing here is creating an instance of our form, assigning it to the view, and displaying the page. If you bring up your browser and view http://localhost/index/edit/ you will be able to see the form with all of it’s fancy tools.

Populating the Editor

The next thing we need to do is populate the editor with the data we want to edit. In this example we are going to pull the contents of our index page into the editor and save the edited content back to the file. The first thing we need to do is read the content from the file. Let’s update our editAction() function to the following.

IndexController.php

public function editAction() {
   $form = new Blog_Form_EditForm();
   $this->view->form = $form;
 
    // get the contents of the index file
    $file = APPLICATION_PATH . '/views/scripts/index/index.phtml';
    $contents = trim(file_get_contents($file, FILE_TEXT));
    // remove all line breaks because they will cause problems with the Javascript
    $contents = str_replace("\r\n", "", $contents);
 
   // pass the file contents to our view
   $this->view->fileContents = $contents;
}

Our view is now aware of the contents of the index.phtml file but it is still not available to our editor yet. Here is where a little Javascript trickery happens. Essentially what we are going to do is populate a Javascript variable with the contents of our file. We will then use some Javascript to populate the editor. Since the dijit editor is not a textarea but is actually an iframe we will need to get the body of the iframe and add the contents via dijit’s setValue() function. Let’s go back to the edit.phtml file.

Note: WordPress keeps stripping out my Javascript. So wherever you see — script tag — use a real script tag

edit.phtml

-- script tag --
   var fileContents = '<?php echo $this->fileContents ?>';
-- end script tag --
<?php
   echo $this->form;
   if($this->dojo()->isEnabled()) {
      $this->dojo()->setLocalPath('/scripts/Dojo/dojo/dojo.js')
                        ->addStyleSheetModule('dijit.themes.tundra');
      $this->dojo();
   }
?>
-- script tag --
   dojo.addOnLoad(function() {
      dijit.byId('editor-Editor').setValue(fileContents);
   }
-- end script tag --

Now you can reload the page in your browser and you should see the contents of your index.phtml file there. If you have Firebug, or a similar tool, you can walk through the DOM and take a look at how dijit creates the editor. Notice that there is a hidden field with the name “editor” which just happens to be the name of the form element we created. Dijit creates this hidden field for us to use to pass the contents of the form back to the server.

Now our data is being passed back to the server. We need to update our editAction() function to handle the post data and update the file.

IndexController.php

public function editAction() {
   $form = new Blog_Form_EditForm();
   $this->view-form = $form;
 
   // the file we are editing
   $file = APPLICATION_PATH . '/views/scripts/index/index.phtml';
 
   // check to see if the form has been submitted
   $request = $this->getRequest();
   if($request->isPost()) {
      $editorContents = $request->getParam('editor');
      file_put_contents($file, $editorContents);
      // send the user to the index page
      $this->_helper->redirector('index', 'index');
 
   } else {
      // form has not been submitted
      $contents = trim(file_get_contents($file, FILE_TEXT));
      // remove all line breaks because they will cause problems with the Javascript
      $contents = str_replace("\r\n", "", $contents);
      $this->view->fileContents = $contents;
   }
}

After you submit the form you will be redirected to your newly updated index page. There you have it. The dijit.Editor in action and we achieved the three goals listed at the beginning. It took me some experimenting to get it working so I hope this helps someone out there!

Conclusion

You can extend the Zend_Dojo_Form_Element_* or Zend_Form_Element_* classes to alter the order of your decorators, add new ones, or override functionality. Inheritance in action!

Getting the editor to do what you want takes a little Javascript magic but the functionality and usability it adds is well worth it. Some users still seem to be pleasantly surprised by having this functionality but it is starting to become common place and in the near future all of our textareas will be replaced by WYSIWYG editors.

18
Jul/09
0

The New Blog!

I decided to go ahead and give WordPress a try. I haven’t really used it other than helping a friend install it on their web server. My old blog definitely needed an update and since I don’t really have the time to re-write the software I went with WordPress. So far I am very impressed. I wish I would have done it earlier.

I have been very busy lately and it does not seem to be coming to an end any time soon. The projects I am currently working on are TheWeddingVendor.comOurWeddingCircle.com, SoutheastReglazing.com, and AircraftConnection.com.

AircraftConnection.com is a rather large project which I decided to write using the Zend Framework. I will be blogging about my experiences with the Zend Framework over the next couple weeks as I work on the site. Be sure to come back and check it out.

Get Adobe Flash playerPlugin by wpburn.com wordpress themes