Showing posts with label Design. Show all posts
Showing posts with label Design. Show all posts

Thursday, November 15, 2007

Styleable Dropdown - Part II

After blogging about the Styleable Dropdown, I decided that it appeared a bit too complicated - so I decided to simplify it a bit (and also reuse more of the built-in functionality of Dojo).

Here is the new class:

dojo.provide("toonetown.dijit.form.DropDownSelect");

dojo.require("dijit.form.Button");
dojo.require("dijit.form.FilteringSelect");
dojo.require("dijit.Menu");
dojo.require("dojo.data.ItemFileReadStore");

dojo.declare("toonetown.dijit.form.DropDownSelect", dijit.form.DropDownButton, {
store: null,
dropDown: null,
hasDownArrow: true,
_isPopulated: false,
_lastValue: "",
onChange: function(){},
_getValueField: function(){ return "value"; },
_getItemByValue: function(value){
var ret = null;
this.store.fetch({query: {value: value}, onComplete: function(i, r){
if (i.length && i[0]) ret = i[0];
}});
return ret;
},
postMixInProperties: function(){
this.inherited(arguments);
dijit.form.ComboBoxMixin.prototype.postMixInProperties.apply(this, arguments);
this.dropDown = new dijit.Menu();
dojo.place(dojo.doc.createElement("span"), this.srcNodeRef, "first");
},
postCreate: function(){
this._menuItemClick(this.value);
this.inherited(arguments);
},
_menuItemClick: function(item){
var str = (typeof item == "string"),
i = str ? this._getItemByValue(item) : item,
val = i ? i.value : null;
if (!val || val == this._lastValue)
return;
this.setValue(val);
this.setLabel(i.name);
this._lastValue = val;
if (!str) this.onChange(val);
},
_toggleDropDown: function(){
var _this = this, dropDown = _this.dropDown;
if (dropDown && !dropDown.isShowingNow && !_this._isPopulated)
{
_this.store.fetch({
onItem:function(i){
dropDown.addChild(new dijit.MenuItem({
label: i.name,
onClick: function(){
_this._menuItemClick(i);
}}));
},
onComplete: function(){
_this._isPopulated = true;
toonetown.dijit.form.DropDownSelect.superclass._toggleDropDown.call(_this);
}
});
return;
}
this.inherited(arguments);
}
});


Isn't code reuse nice? :)

Wednesday, November 14, 2007

Dojo widget - Styleable Dropdown

I guess it's official now - we need to be using dojo for our development at work, so I've been trying to learn it. There are quite a few things that I like about it, and so I thought I'd start posting some of my findings as I come across them.

Today's topic - a Styleable Dropdown.

At the Ajax Experience this year, there was a presentation by Aaron Gustafson from A List Apart where he discussed various techniques for styling forms. Most form elements can be styled just fine using CSS, however, the <select> tag is not selectable. In order to do such a thing, you need to use some kind of javascript-based solution which uses elements *other* than a select.

Dojo is great at creating widgets - it's probably what it does the best, so I thought it would be a great exercise in learning dojo to create a widget that turns something like this:


<select dojoType="toonetown.dijit.form.StyleableDropdown>
<option value="opt1">First Option</option>
<option value="opt2">Second Option</option>
</select>


Into something that can be styled. This should be something fairly easy to do using "Dojo Magic" (Majik? Mojo? Dojic?)

The first thing I tried was to use some existing dojo widgets (dijits). First off, I tried using a dijit.form.FilteringSelect. It did one part of what I wanted by taking a <select> as its input. The problem is that it's not very stylable, and is more geared to the "ComboBox" style of doing things (allows for text entry).

What I really wanted was a dijit.form.DropDownButton - that's styleable just like I would want it, however it didn't take a <select> tag as its input. (It's kind of picky in how its input needs to be structured).

So, I ended up creating a custom widget - extended from dijit.form.DropDownButton. Efforts to make it extended from dijit.form.FilteringSelect proved too complex. The final result follows.


dojo.provide("toonetown.dijit.form.DropDownSelect");
dojo.require("dijit.form.Button");
dojo.require("dijit.Menu");
dojo.require("dojo.data.ItemFileReadStore");
dojo.declare("toonetown.dijit.form.DropDownSelect", dijit.form.DropDownButton, {
store: null,
dropDown: null,
_isPopulated: false,
_lastOption: "",
onChange: function(){},
postMixInProperties: function(){
this.inherited(arguments);
var span = dojo.doc.createElement("span"), selItem = null;
if (!this.store)
{
var items = dojo.query("> option", this.srcNodeRef).map(function(node){
node.style.display="none";
return { value: node.getAttribute("value"), name: String(node.innerHTML) };
});
this.store = new dojo.data.ItemFileReadStore({data: {identifier:"value", items:items}});
if(items && items.length && !this.value)
{
selItem = items[this.srcNodeRef.selectedIndex != -1 ? this.srcNodeRef.selectedIndex : 0];
}
}
this._initSelect(selItem, span);
if (!this.dropDown)
{
this.dropDown = new dijit.Menu();
}
dojo.place(span, this.srcNodeRef, "first");
},
_initSelect: function(item, span){
if (item)
{
this.value = item.value;
span.innerHTML = item.name;
}
else if (this.store)
{
var _this = this;
this.store.fetch({onComplete: function(i, r){
if (i.length && i[0]) _this._initSelect(i[0], span);
}});
}
},
_menuItemClick: function(item){
var val = item.value;
if (this._lastOption != val)
{
this.setValue(val);
this.setLabel(item.name);
this._lastOption = val;
this.onChange(val);
}
},
_loadCallback: function(items, request){
var dropDown = this.dropDown, _this = this;
if (!dropDown) { return; }
dojo.forEach(items, function(item){
dropDown.addChild(new dijit.MenuItem({
label: item.name,
onClick: function(){ _this._menuItemClick(item);}}));
});
_this._isPopulated = true;
toonetown.dijit.form.DropDownSelect.superclass._toggleDropDown.call(this);
},
_toggleDropDown: function(){
var dropDown = this.dropDown;
if (dropDown && !dropDown.isShowingNow && !this._isPopulated)
{
this.store.fetch({onComplete:dojo.hitch(this, "_loadCallback")});
return;
}
this.inherited(arguments);
}
});


This fulfills all my requirements - now I just need to work on styling it - and probably marking it up too... :) I just figured I'd post it to my blog, as was also suggested at the Ajax Experience.