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.

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.

Get Adobe Flash playerPlugin by wpburn.com wordpress themes