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: #fff; 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 #fff; 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 mix in dgrid/Selection
to add selection support to our list.
require([ 'dojo/_base/declare', 'dgrid/OnDemandList', 'dgrid/Selection', 'dojo/dom-construct', 'dojo/domReady!' ], function (declare, List, Selection, domConstruct) { var DropDown = declare([ List, Selection ]); var dropDown = new DropDown(); domConstruct.place(dropDown.domNode, 'select'); dropDown.startup(); });We'll also add some styles to give our grid appropriate positioning and height, sizing automatically based on the data up to a certain limit:
.mySelect .dgrid { cursor: default; display: none; position: absolute; top: 17px; left: -1px; height: auto; max-height: 20em; width: 100%; } .mySelect .dgrid-scroller { position: relative; }View Demo
You may have noticed that we have appended the list to the select's node rather than the document body. 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.
Hiding and showing the drop-down
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 { cursor: default; display: none; position: absolute; top: 17px; left: -1px; height: auto; max-height: 20em; width: 100%; } .mySelect .dgrid-scroller { position: relative; } .mySelect .opened { display: block; }
Now all we need to do to show the list is to add the opened
class to its node.
Recall that we added a button
class to both the label and arrow nodes.
We can use dojo/on
's event delegation to easily respond to clicks on
both of these nodes:
require([ 'dojo/_base/declare', 'dojo/on', 'dgrid/OnDemandList', 'dgrid/Selection', 'dojo/dom', 'dojo/dom-construct', 'dojo/dom-class', 'dojo/query', 'dojo/domReady!' ], function (declare, on, List, Selection, dom, domConstruct, domClass) { var DropDown = declare([ List, Selection ]); 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.toggle(dropDown.domNode, 'opened', open); }); });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 collection and a renderRow
function to the list:
require([ 'dojo/_base/declare', 'dojo/on', 'dgrid/OnDemandList', 'dgrid/Selection', 'dstore/Memory', 'dojo/dom', 'dojo/dom-construct', 'dojo/dom-class', 'dojo/query', 'dojo/domReady!' ], function (declare, on, List, Selection, Memory, dom, domConstruct, domClass) { var DropDown = declare([ List, Selection ]); var store = new Memory({ idProperty: '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', collection: store, renderRow: function (item) { var divNode = domConstruct.create('div', { className: item.name }); domConstruct.place(document.createTextNode(item.name), divNode); return divNode; } });View Demo
Unlike dgrid/Grid
, dgrid/List
(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 a CSS class added based on
the item's name
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
dgrid-select
event, hide the list on selection, and update the label node 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 = domConstruct.create('div', { className: item.name }); domConstruct.place(document.createTextNode(item.name), divNode); return divNode; } var dropDown = new DropDown({ selectionMode: 'single', collection: store, renderRow: renderItem }); domConstruct.place(dropDown.domNode, 'select'); dropDown.startup(); var open = false; function toggle(state) { open = typeof state !== 'undefined' ? state : !open; domClass.toggle(dropDown.domNode, 'opened', open); } on(dom.byId('select'), '.button:click', function () { toggle(); }); var label = query('.label', dom.byId('select'))[0]; dropDown.on('dgrid-select', function (event) { var node = renderItem(event.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 here's an example to get you started:
define([ 'dojo/_base/declare', 'dojo/on', 'dojo/dom', 'dojo/dom-construct', 'dojo/dom-class', 'dijit/_WidgetBase', 'dijit/_TemplatedMixin', 'dgrid/OnDemandList', 'dgrid/Selection', 'dojo/text!./Select.html' ], function (declare, on, dom, domConstruct, domClass, _WidgetBase, _TemplatedMixin, List, Selection, template) { var DropDown = declare([ List, Selection ]); return declare([ _WidgetBase, _TemplatedMixin ], { baseClass: 'mySelect', name: '', value: null, templateString: template, buildRendering: function () { this.inherited(arguments); this.list = new DropDown({ selectionMode: 'single', showHeader: false, collection: this.collection, renderRow: this.renderItem }, this.listNode); var self = this; this.on('.button:click', function () { self.toggle(); }); this.list.on('dgrid-select', function (event) { self.set('value', event.rows[0].id); self.toggle(false); }); this.own(this.list); }, startup: function () { if (this._started) { return; } this.inherited(arguments); if (!this.value && this.collection) { this.set('value', this.collection.getIdentity(this.collection.fetch()[0])); } }, _setValueAttr: function (value) { var item = this.collection.get(value); if (item) { domConstruct.place(this.renderItem(item), this.labelNode, 'only'); this._set('value', value); } }, toggle: function (state) { if (typeof state === 'undefined') { state = !this.opened; } this.opened = state; domClass.toggle(this.list.domNode, 'opened', this.opened); if (state && !this.list._started) { this.list.startup(); } }, renderItem: function (item) { var divNode = domConstruct.create('div', { className: item.name }); domConstruct.place(document.createTextNode(item.name), divNode); return divNode; } }); });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.