basedatacombobox.lzx
<library>
<include href="base/baseformitem.lzx"/>
<include href="base/basedatacombobox_item.lzx"/>
<class name="basedatacombobox" extends="baseformitem">
<attribute name="itemdatapath" value="null
" type="string"/>
<attribute name="textdatapath" value="text()
" type="string"/>
<attribute name="valuedatapath" value="@value
" type="string"/>
<attribute name="itemclassname" value="
" type="string"/>
<attribute name="menuclassname" value="
" type="string"/>
<attribute name="selectfirst" value="true
"/>
<attribute name="statictext" value="$once{null}" type="string"/>
<attribute name="defaulttext" value="$once{null}" type="string"/>
<attribute name="value" value="null
" setter="this.doSetValue(value, false, false)"/>
<attribute name="selected" value="null
"/>
<attribute name="shownitems" value="4
"/>
<attribute name="isopen" value="false
" setter="this.setOpen(isopen)"/>
<attribute name="listwidth" value="null
"/>
<attribute name="ismenu" value="false
"/>
<attribute name="listattach" value="bottom
" type="string"/>
<event name="onselected"/>
<event name="onselect"/>
<attribute name="_cbtext" value="null
"/>
<attribute name="_cblist" value="null
"/>
<attribute name="_selectedIndex" value="-1
"/>
<attribute name="_selectdel" value="null
"/>
<attribute name="_datacatchdel" value="null
"/>
<event name="onisopen"/>
<method name="init">
super.init();
if ( this.value == null && this.defaulttext != null ) {
this._cbtext.setAttribute('text', this.defaulttext);
} else {
this._updateSelectionOnData();
}
if ( this.statictext != null ) {
this._cbtext.setAttribute('text', this.statictext );
}
</method>
<method name="_updateSelectionOnData" args="ignore=null">
var dp = new lz.datapointer(this);
var arr = dp.xpathQuery(this.itemdatapath);
if (arr != null) {
if (this._datacatchdel != null) {
this._datacatchdel.unregisterAll();
}
if (this.value == null && this.selectfirst) {
// set first to be the initvalue
this._updateSelectionByIndex(0, false, true);
} else {
if (this._selectedIndex != -1) {
//fix for LPP-5549
this._updateSelectionByIndex(this._selectedIndex, true, false);
}
}
} else {
if (this._datacatchdel == null) {
//FIXME: [20080412 anba] look for dataset,
//does fail when there is ":/" in a predicate, but it used all over the place,
//we should provide a general solution!
var itmpath = this.itemdatapath;
var iof = itmpath.indexOf( ":/" );
if (iof > -1) {
var dset = dp.xpathQuery( itmpath.substring(0, iof + 2) );
if (dset != null) {
this._datacatchdel = new LzDelegate(this, "_updateSelectionOnData", dset, "ondata");
}
}
}
}
dp.destroy();
</method>
<handler name="ondata">
this._teardowncblist();
if (this.value == null && this.selectfirst) {
// set first to be the initvalue
this._updateSelectionByIndex(0, false, true);
}
</handler>
<method name="doSetChanged" args="isChanged">
super.setChanged( isChanged );
// Be sure to update the selection, because value might have been
// changed programmatically
if (this.changed) {
this._updateSelectionByIndex(this.getItemIndex( this['value'] ), true, false);
}
</method>
<method name="_updateSelectionByIndex" args="index,dontSetValue,isinitvalue">
var dp = new lz.datapointer(this);
var nodes = dp.xpathQuery(this.itemdatapath);
if (! (nodes instanceof Array)) nodes = [nodes];
dp.setPointer(nodes[index]);
var dpValid = dp.isValid();
if (dpValid) {
var t = dp.xpathQuery(this.textdatapath);
} else {
var t = null;
}
// if t is null, use default text (if it exists)
if( ( t == null || t.length == 0 ) && (this.defaulttext && this.defaulttext.length > 0) )
t = this.defaulttext;
if ( this._cbtext && (this.statictext == null) ) {
this._cbtext.setAttribute('text', t);
}
if (this.selected == null || dp['p'] !== this.selected['p']) {
if (! (dontSetValue || this.ismenu)) {
if (dpValid) {
var v = dp.xpathQuery(this.valuedatapath);
} else {
var v = null;
}
this.doSetValue(v, true, true);
}
this.setAttribute('text', t);
if (this.ismenu) {
//FIXME: [20080412 anba] datapointer leaks memory, but we cannot destroy
//it because it is passed to the "onselect"/"onselected" event!
// Clear the selection
this._selectedIndex = -1;
if (this['_cblist'] && this._cblist['_selector']) {
this._cblist._selector.clearSelection();
}
} else {
if (this.selected != null) {
this.selected.destroy();
}
this.selected = dp;
}
if ( this['onselected'] ) this.onselected.sendEvent(dp);
if ( this['onselect'] ) this.onselect.sendEvent(dp);
} else {
dp.destroy();
}
</method>
<method name="_setupcblist" args="force">
if (this._cblist == null) {
if (this.itemclassname == "") {
this.itemclassname = "basedatacombobox_item";
}
var icn = this.itemclassname;
if (!(icn && lz[icn])) {
if ($debug) {
Debug.error("%s.itemclassname invalid: %s", this, icn);
}
return;
}
var flcn = this.menuclassname;
if (!(flcn && lz[flcn])) {
if ($debug) {
Debug.error("%s.menuclassname invalid: %s", this, flcn);
}
return;
}
var cblist = new lz[flcn](this,
{ visible:false,
attach: this.listattach,
attachoffset: -2,
itemclassname: icn
});
// add in a white view to reduce the visual effect of the
// list items appearing as they are created
var tmp = new lz[icn](cblist, { name:'item' });
new lz.datapath(tmp, { pooling: true });
this._cblist = cblist;
// Make sure we deselect if we're acting like a menu
if (this.ismenu) {
cblist._selector.clearSelection();
}
var w = ( this.listwidth != null ? this.listwidth : this.width );
cblist.setAttribute('width', w);
cblist.setAttachTarget(this);
cblist.setAttribute('shownitems', this.shownitems);
var itd = this.itemdatapath;
if (itd.indexOf( "local:" ) == 0) {
itd = "local:" + "parent.owner." + itd.substring( 6 );
}
cblist.item.setAttribute('datapath', itd);
cblist.setAttribute('attach', this.listattach);
if (this._selectdel == null) {
this._selectdel = new LzDelegate( this, "_flistselect" );
}
this._selectdel.register(cblist, 'onselect');
}
if (! this.ismenu) {
// Set the item for _cblist
var item = this._getItemAt(this._selectedIndex);
this._cblist.select(item);
}
</method>
<method name="_teardowncblist">
if (this._selectdel != null) {
this._selectdel.unregisterAll();
this._selectdel = null;
}
if (this._cblist != null) {
var cbl = this._cblist;
this._cblist = null;
cbl.setAttribute("visible", false);
cbl.destroy();
}
</method>
<method name="toggle">
this.setAttribute("isopen", !this.isopen)
</method>
<method name="setOpen" args="open">
if (!this._initcomplete) {
this.isopen = open;
} else {
if (open) { // open combox
if (this.isopen) return; // tends to get called more than once
lz.ModeManager.makeModal( this );
this._setupcblist(false);
this._cblist.bringToFront();
this._cblist.setAttribute('visible', true);
lz.Focus.setFocus( this._cblist, false );
this.isopen = true;
if (this['onisopen']) this.onisopen.sendEvent(true);
} else { // close combox
if (!this.isopen) return;
lz.ModeManager.release( this );
if (!this['isopen']) return;
this._cblist.setAttribute("visible", false);
this.isopen = false;
if (this['onisopen']) this.onisopen.sendEvent(false);
if ( lz.Focus.getFocus() === this._cblist ) {
if ( this.focusable ) {
lz.Focus.setFocus( this, false );
}
}
}
}
</method>
<method name="passModeEvent" args="eventStr,view">
// Once a view has been made modal, this method
// gets called ONLY when a user clicks on a view 'outside'
// the contents of this view, or clicks on a inputtext view anywhere
// on the screen even for a subview within this view.
if ( eventStr == "onmousedown" ){
// first, we only care about the mousedown event.
// if the user has pressed the mouse down on a textfield
// within the component, then we will not know this unless
// we test it to see if it is a subview of this component.
if ( view != null ) { // view is a clickable view
// view is not LITERALLY part of the class hierarchy but
// it maybe part of the floatingview of this component, and if so
// then treat it as if it were a child of the class.
if ( !view.childOf(this._cblist) ) {
// view is outside of combobox so close the combbobox
this.setAttribute("isopen", false);
} else {
// view is a child of _cblist, so don't do anything.
}
} else {
this.setAttribute("isopen", false);
}
}
// if we're inside a modal dialog, need to propagate event manually
// since floating list is a child of the canvas
if (view && view.childOf(this._cblist)) {
if (view[ eventStr ]) {
view[ eventStr ].sendEvent( view );
}
return false;
}
// since a combox is not strictly modal, always return
// true to pass the event to the object (oustide combobox)
// that was clicked on
return true;
</method>
<method name="_flistselect" args="item">
// Just return if item is undefined. baselist will do this sometimes
if ( typeof item == "undefined" ) {
return;
}
// Clear selection and return if there is no item selected.
if ( item == null ) {
this._cblist._selector.clearSelection();
return;
}
if ( item.isinited ) {
if ($debug) {
if (item.value == null) {
Debug.warn("null item value in _flistselect");
}
}
this.doSetValue(item.value, false, false);
if ( item && this.statictext == null ) this._cbtext.setAttribute('text', item.text);
}
this.setAttribute("isopen", false);
</method>
<method name="_getItemAt" args="index">
var item = null;
var sel = this._cblist._selector;
if (sel is lz.datalistselector) {
sel._ensureItemInViewByIndex( index );
}
var svs = sel.immediateparent.subviews;
if (svs) {
var sv = svs[0];
if (sv) {
var cl = sv.cloneManager;
if (cl && cl['clones']) {
var pos = cl.clones[0].datapath.xpathQuery( 'position()' ) - 1;
item = cl.clones[ index - pos ];
} else {
item = sv;
}
}
}
return item;
</method>
<method name="selectItem" args="value">
var i = this.getItemIndex(value);
if (i != -1) this._updateSelectionByIndex(i, false, false);
</method>
<method name="getItemIndex" args="value">
var index = -1;
var dp = new lz.datapointer(this);
var nodes = dp.xpathQuery(this.itemdatapath);
if (nodes != null) {
if (! (nodes instanceof Array)) nodes = [nodes];
for (var i = 0; i < nodes.length; i++) {
dp.setPointer(nodes[i]);
var test_value = dp.xpathQuery(this.valuedatapath);
if (test_value == value) {
index = i;
break;
}
}
}
dp.destroy();
return index;
</method>
<method name="selectItemAt" args="index">
this._updateSelectionByIndex(index, false, false);
this._setupcblist(false);
</method>
<method name="setValue" args="v, isinitvalue=null" override="true">
this.doSetValue(v, isinitvalue, false);
</method>
<method name="doSetValue" args="value, isinitvalue, ignoreselection">
if (this['value'] != value) {
var i = this.getItemIndex(value);
this._selectedIndex = i;
if (! this.ismenu) { // Ignore value if we're a menu
super.setValue(value, isinitvalue);
}
if ( i != -1 && !ignoreselection) {
this._updateSelectionByIndex(i, true, false);
}
}
</method>
<method name="getValue">
return ( this.value == null ? '' : this.value );
</method>
<handler name="onkeydown" method="_dokeydown"/>
<method name="_dokeydown" args="key">
// 38: up-arrow, 40: down-arrow, 32: space, 13: return, 27: escape
if ( (key==38) || (key==40) || (key==32) ) {
if ( ! this.isopen ) this.setAttribute("isopen", true);
}
</method>
<method name="getNextSelection">
// must ensure the floatinglist is closed so that if it is being tabbed from,
// we can change the focus. (focus is normally not allowed to escape from
// a modal view).
this.setAttribute("isopen", false);
return lz.Focus.getNext(this);
</method>
<method name="resolveSelection">
// must ensure the floatinglist is closed so that if it is being tabbed from,
// we can change the focus. (focus is normally not allowed to escape from
// a modal view).
this.setAttribute("isopen", false);
return this;
</method>
<event name="oncombofocus"/>
<event name="oncomboblur"/>
<handler name="onfocus">
// lz.Focus.lastfocus is the blurred view
if ( lz.Focus.lastfocus !== this._cblist ||
(this._cblist != null && this._cblist.attachtarget !== this) ) {
if (this.oncombofocus) {
this.oncombofocus.sendEvent(lz.Focus.lastfocus);
}
}
</handler>
<handler name="onblur" args="focusview">
if (focusview != null && focusview !== this._cblist) {
if (this.oncomboblur) {
this.oncomboblur.sendEvent(focusview);
}
// destroy floating list when we lose focus
if (this._cblist != null &&
lz.Focus.lastfocus !== this) {
this._teardowncblist();
}
}
</handler>
<doc>
<tag name="shortdesc"><text>baseclass for a datacombobox</text></tag>
<text>
An abstract class to create dropdown lists of selectable items. Define
the look in a subclass. Also, a basedatacombobox_text must be declared in
the subclass.
<example>
<canvas height="150">
<include href="lz/floatinglist.lzx" />
<dataset name="items">
<item value="item0">item 0</item>
<item value="item1">item 1</item>
<item value="item2">item 2</item>
<item value="item3">item 3</item>
<item value="item4">item 4</item>
<item value="item5">item 5</item>
<item value="item6">item 6</item>
<item value="item7">item 7</item>
<item value="item8">item 8</item>
<item value="item9">item 9</item>
<item value="item10">item 10</item>
<item value="item11">item 11</item>
</dataset>
<class name="simplecombobox" extends="basedatacombobox" width="100">
<attribute name="_cbtext" value="$once{this._text}"/>
<attribute name="menuclassname" value="floatinglist" type="string"/>
<view width="100%" height="20" focusable="false" bgcolor="#CCCCCC">
<handler name="onclick">
lz.Focus.setFocus(this,false);
classroot.toggle()
</handler>
<handler name="onmouseout">
this.setAttribute('bgcolor', 0xCCCCCC);
</handler>
<handler name="onmouseup">
this.setAttribute('bgcolor', 0xCCCCCC);
</handler>
<handler name="onmouseover">
this.setAttribute('bgcolor', 0xEEEEEE);
</handler>
<handler name="onmousedown">
this.setAttribute('bgcolor', 0xAAAAAA);
</handler>
</view>
<text name="_text" width="${ parent.width - 19 }" x="7" />
</class>
<simplecombobox id="cbox1" width="130" shownitems="6" defaulttext="Choose One..." itemdatapath="items:/item"/>
</canvas>
</example>
** Caveat: Combobox items will not update if the attributes
that are mapped to textdatapath or valuedatapath change.
To force the changes to update, call
<code> node.parentNode.replaceChild(node.cloneNode(true), node) </code>
where node is the dataelement of the list item. Don't forget
to reset the seleciton on the list, as well.
</text>
</doc>
</class>
</library>
Cross References
Includes
Classes