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.

Get Adobe Flash playerPlugin by wpburn.com wordpress themes