Nested select boxes
A common feature of web-gui-s is the use of select boxes to select a "path" in some hierarchial structure. An example is where one selects a country, another select is populated with the possible states, the third with possible cities etc.
Here is a simple snippet of code that manages such a collection of selects.
var level_selects = new Array(); connect(window, 'onload', function () { var sel = SELECT(); level_selects.push(sel); connect(sel, 'onchange', partial(update_levels_below, 0)); appendChildNodes('theselects', sel); get_level_data(0, []).addCallback(function (data) { if (data) { replaceChildNodes(sel, map(function (row) { return OPTION({"value": row[0]}, row[1]); }, data)); signal(sel, 'onchange'); } }); }); function update_levels_below(level_no) { var path = map(itemgetter('value'), islice(level_selects, level_no+1)); var d = get_level_data(level_no + 1, path); d.addCallback(function (data) { if (data) { var sel = level_selects[level_no+1]; if (sel == undefined) { /* the next level didn't exist, create it */ sel = SELECT(); level_selects.push(sel); connect(sel, 'onchange', partial(update_levels_below, level_no+1)); appendChildNodes('theselects', sel); } replaceChildNodes(sel, map(function (row) { return OPTION({"value": row[0]}, row[1]); }, data)); signal(sel, 'onchange'); } else { /* There is no next level */ if (level_selects.length > level_no+1) { /* remove the select and all levels below */ forEach( islice(level_selects, level_no+1, level_selects.length), function (sel) { sel.parentNode.removeChild(sel); } ); level_selects.length = level_no+1; /* truncate the array */ } } }); }
To use this, one must provide:
- a div with the id 'theselects' (or some other id, change accordingly above)
- a function with the signature get_level_data(level_no, path_above)
The function get_level_data is given the number of a level (level_no) and an array of the currently selected values in the levels above (path_above).
The function must return a deferred that will be called back with the level data. The level data is an array of tuples (two element arrays) containing a value (used for the value attribute in an <option>) and a label (the <option> text).
The function can also return a single "null" to indicate that there is no level with the given level_no.
Ideally, this function just forwards the parameters to a loadJSONDoc which builds the lists on the server side.
Here is an example of get_level_data that returns some nonsense hard-coded test values:
function get_level_data(level_no, path_above) { /* you could call json here */ var retval = null; if (path_above.length) var previous_level_id = path_above.pop(); if (level_no == 0) { retval = [['web','Web'],['db','Database'],['gui','GUI']]; } else if (level_no == 1) { if (previous_level_id == 'web') { retval = [['web-python','Python'], ['web-ruby','Ruby'], ['web-php','PHP']]; } else if (previous_level_id == 'db') { retval = [['db-sqlite','SQLite'], ['db-mysql','MySQL']]; } else if (previous_level_id == 'gui') { retval = [['gui-python','Python'], ['gui-cpp','C++']]; } else { retval = null; } } else if (level_no == 2) { if (previous_level_id == 'web-python') { retval = [['tg','TurboGears'], ['django','Django']]; } else if (previous_level_id == 'web-ruby') { retval = [['rails','Ruby on Rails']]; } else if (previous_level_id == 'web-php') { retval = [['symphony', 'Symphony']]; } else if (previous_level_id == 'gui-python') { retval = [['wxpython', 'wxPython']]; } else if (previous_level_id == 'gui-cpp') { retval = [['wxwidgets', 'wxWidgets'], ['mfc', 'MFC']]; } else { retval = null; } } else { retval = null; } return succeed(retval); }
I would provide a complete working example if I had a good place to put it. Meanwhile you can download the attachment below which is a single HTML file. Beware though that it links to the packed mochikit in svn.
Attachments
- test.html (3.7 kB) -
An example of nested select boxes.
, added by arnarbi@gmail.com on 02/02/07 07:24:40.
