Notice: This is a tutorial for a previous version of dgrid and may be out of date.

Drop-down Selects with dgrid

In this tutorial, we will show you how to use one of dgrid's lightweight base classes to create a drop-down select component that will handle thousands of options.

Getting started

The first thing we will need is some HTML to represent the <select> element. We'll use the following HTML:

<div id="select" class="mySelect">
	<div class="label button">Label</div>
	<div class="arrow button"></div>
</div>

And we'll style it with the following CSS:

.mySelect {
	border: 1px solid #b5bcc7;
	background-color: #ffffff;

	width: 200px;
	height: 17px;

	/* Make this position: relative so our arrow is positioned within */
	position: relative;
	padding: 0;
}
.mySelect .label {
	line-height: 17px;
	vertical-align: middle;
}
.mySelect .arrow {
	/* Position the arrow on the right-hand side */
	position: absolute;
	top: 0;
	right: 0;

	/* Use claro's arrow image */
	background-image: url("path/to/dijit/themes/claro/form/images/commonFormArrows.png");
	background-position: -35px 70%;
	background-repeat: no-repeat;

	/* 16x16 with a white border and a gray background */
	width: 16px;
	height: 16px;
	border: 1px solid #ffffff;
	border-top: none;
	background-color: #efefef;
}
View Demo

Adding a drop-down

The next task will be to add the drop-down. For our needs, dgrid/OnDemandList will work well because it doesn't have a header and is only concerned with rendering a row — it has no concept of columns. We'll also use dgrid/Selection and dgrid/Keyboard to add selection and keyboard navigation to our list.

require([
	"dojo/_base/declare",
	"dgrid/OnDemandList",
	"dgrid/Selection",
	"dgrid/Keyboard",
	"dojo/dom-construct",
	"dojo/domReady!"
], function(declare, List, Selection, Keyboard, domConstruct){
	var DropDown = declare([List, Selection, Keyboard]);

	var dropDown = new DropDown();
	domConstruct.place(dropDown.domNode, "select");
	dropDown.startup();
});
.mySelect .dgrid {
	position: absolute;
	top: 17px;
	left: -1px;
	width: 100%;
}
View Demo

Hiding and showing the drop-down

You may have noticed that we have appended the list to the select's node rather than the document body. The reason for this is so we don't have to position and size the list manually when it is opened; instead, we're doing it with CSS. Let's modify our CSS to make the dgrid initially hidden and add a class that will show it when applied to the parent node:

.mySelect .dgrid {
	position: absolute;
	top: 17px;
	left: -1px;
	width: 100%;
	display: none;
}
.mySelect .opened {
	display: block;
}

Now all we need to do to show the list is to add the opened class to its node. This can easily be accomplished by using dojo/on and event delegation:

require([
	"dojo/_base/declare",
	"dojo/on",
	"dgrid/OnDemandList",
	"dgrid/Selection",
	"dgrid/Keyboard",
	"dojo/dom",
	"dojo/dom-construct",
	"dojo/dom-class",
	"dojo/query",
	"dojo/domReady!"
], function(declare, on, List, Selection, Keyboard,
			dom, domConstruct, domClass){
	var DropDown = declare([List, Selection, Keyboard]);

	var dropDown = new DropDown();
	domConstruct.place(dropDown.domNode, "select");
	dropDown.startup();

	var open = false;
	on(dom.byId("select"), ".button:click", function(e){
		open = !open;
		domClass[open ? "add" : "remove"](dropDown.domNode, "opened");
	});
});
View Demo

Adding items to the drop-down

Now that we have the basic behavior working, it's time that we add some items to our drop-down; this is accomplished by assigning a store and a renderRow function to the list:

require([
	"dojo/_base/declare",
	"dojo/on",
	"dgrid/OnDemandList",
	"dgrid/Selection",
	"dgrid/Keyboard",
	"dojo/store/Memory",
	"dojo/dom",
	"dojo/dom-construct",
	"dojo/dom-class",
	"dojo/query",
	"dojo/domReady!"
], function(declare, on, List, Selection, Keyboard,
			Memory, dom, domConstruct, domClass){
	var DropDown = declare([List, Selection, Keyboard]);

	var store = new Memory({
		identifier: "id",
		data: [
			{ id: 0, name: "One", value: 1 },
			{ id: 1, name: "Two", value: 2 },
			{ id: 2, name: "Three", value: 3 },
			{ id: 3, name: "Four", value: 4 }
		]
	});

	var dropDown = new DropDown({
		selectionMode: "single",
		store: store,
		renderRow: function(item){
			var divNode, textNode;
			textNode = document.createTextNode(item.name);
			divNode = domConstruct.create("div", {
				"class": item.name
			});
			domConstruct.place(textNode, divNode);
			return divNode;
		}
	});
View Demo

Unlike a dgrid/Grid, a dgrid/List instance (and by inheritance, dgrid/OnDemandList) has no concept of "columns" and only cares about rendering rows. The renderRow function receives a store item and needs to return a DOM node; in our case, it's simply a <div> with the name of the item as its contents and the color of the text set to the item's color property. You can get as creative as you'd like with the rendering of your items.

Selecting items

We have items rendering, but nothing happens when we select them. The next step is to hook up the list's selection event, hide the list on selection, and replace the text of the label with the text from the item. In our case, we want the text in the label to match what is in the drop-down. We'll need to break out some of the functionality we've written into separate functions so we can re-use it:

function renderItem(item){
	var divNode, textNode;
	textNode = document.createTextNode(item.name);
	divNode = domConstruct.create("div", {
		"class": item.name
	});
	domConstruct.place(textNode, divNode);
	return divNode;
}

var dropDown = new DropDown({
	selectionMode: "single",
	store: store,
	renderRow: renderItem
});
domConstruct.place(dropDown.domNode, "select");
dropDown.startup();

var open = false;
function toggle(state){
	open = state;
	domClass[open ? "add" : "remove"](dropDown.domNode, "opened");
}
on(dom.byId("select"), ".button:click", function(e){
	toggle(!open);
});

var label = query(".label", dom.byId("select"))[0];
dropDown.on("dgrid-select", function(e){
	var node = renderItem(e.rows[0].data);
	domConstruct.place(node, label, "only");
	toggle(false);
});
View Demo

To be notified when a row is selected, we must listen for the dgrid-select event; the event object will have a rows property which is an array of the row objects that are currently selected. Since the list is set up in single selection mode, there will only ever be one item in the array. We use the data property from the row object to render a node and then replace the contents of the label node with that node. Finally, the list is toggled closed.

One more exercise that can be undertaken is to widgetize this component: as it stands, it's not very reusable. This can be done many different ways, but an example method has been provided to get you started:

View Demo

Conclusion

As you can see, using dgrid/OnDemandList as a drop-down list is quite simple. The modular nature of dgrid allows us to pick and choose which features to include in our application without adding any more than we absolutely need.

Resources