basecombobox.lzx
<library>
<include href="base/baseformitem.lzx"/>
<include href="lz/list.lzx"/>
<include href="lz/floatinglist.lzx"/>
<class name="basecombobox" extends="baseformitem" focusable="false" width="100">
<attribute name="defaultplacement" value="cblist
" type="string"/>
<attribute name="value" value="${cblist.value}"/>
<attribute name="isopen" value="false
" setter="this.setOpen(isopen)"/>
<attribute name="bordersize" value="1
"/>
<attribute name="spacing" value="0
"/>
<attribute name="defaulttext" value="
" type="string"/>
<attribute name="defaultselection" value="null
" type="number" setter="this.setDefaultSelection( defaultselection )"/>
<event name="ondefaultselection"/>
<attribute name="editable" value="true
" setter="this.setEditable(editable)"/>
<attribute name="itemclassname" setter="this.setItemclassname(itemclassname)" value="
" type="string"/>
<attribute name="shownitems" value="-1
"/>
<attribute name="mousedownintext" value="false
"/>
<attribute name="initcomplete" value="0
"/>
<attribute name="autoscrollbar" value="true
"/>
<attribute name="selected" value="null
"/>
<event name="onselect"/>
<attribute name="dataoption" value="none
" type="string"/>
<attribute name="attachoffset" value="-1
" type="number"/>
<attribute name="text_x" value="2
" type="number"/>
<attribute name="text_y" value="2
" type="number"/>
<attribute name="text_width" value="${this.width - 19}" type="number"/>
<attribute name="min_width" value="50
" type="number"/>
<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.setOpen(false);
if (!editable) {
return lz.Focus.getNext(bkgnd);
} else {
return lz.Focus.getNext(interior.cbtext);
}
</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.setOpen(false);
if (!editable) {
return bkgnd;
} else {
return interior.cbtext;
}
</method>
<view name="bkgnd" width="100%" focusable="false">
</view>
<view name="interior" x="$once{classroot.text_x}" y="$once{classroot.text_y}" width="${classroot.text_width}" height="${classroot.height - 2*classroot.bordersize - 2}" focusable="false">
<attribute name="_dsblfield" value="null
"/>
<method name="setupText">
var txt = classroot.cblist.getText();
if ( !txt ) {
if ( classroot.defaultselection ) {
if ( classroot.cblist._contentview != null ) {
classroot.cblist.selectItemAt( classroot.defaultselection );
txt = classroot.cblist.getText();
}
} else {
txt = classroot.defaulttext;
}
}
if (this.cbtext) this.cbtext.setAttribute('text', txt );
if (this._dsblfield) this._dsblfield.setAttribute('text', txt );
parent._applystyle( parent.style );
</method>
<state name="editable_state">
<view name="editbkgnd" bgcolor="white" width="100%" height="100%"/>
<inputtext x="2" y="1" name="cbtext" width="$once{parent.width - 4}">
<handler name="onkeydown" args="key">
// 38 is up-arrow
// 40 is down-arrow
// 32 is space
// 13 is return
if ((key==38) || (key==40)) {
if (!classroot.isopen) {
classroot.setOpen(true, true); // open, withKey
}
// pass the key event on to the floatinglist
classroot.cblist.onkeydown.sendEvent(key);
} else if (key > 31 && key != 8) {
// user is entering a value manually - so clear selection
// don't clear selection for non-printing keys like shift/ctrl/etc.
classroot.cblist.clearSelection();
}
</handler>
<handler name="onfocus" args="v">
if ( classroot['onfocus'] ) classroot.onfocus.sendEvent(v);
</handler>
<handler name="onblur" args="v">
classroot.setAttribute('text', this.text );
if ( classroot['onblur'] ) classroot.onblur.sendEvent(v);
</handler>
<handler name="onkeyup" args="kc">
if ( classroot['onkeyup'] ) classroot.onkeyup.sendEvent(kc);
</handler>
<handler name="onkeydown" args="kc">
if ( classroot['onkeydown'] ) classroot.onkeydown.sendEvent(kc);
</handler>
<method name="getFocusRect">
return classroot.getFocusRect();
</method>
</inputtext>
</state>
<state name="non_editable_state">
<text x="2" y="1" name="cbtext" width="${parent.width - 4}" clickable="true" focusable="true">
<handler name="onkeydown" args="key">
// 38 is up-arrow
// 40 is down-arrow
// 32 is space
// 13 is return
if ((key==38) || (key==40) || (key==32)) {
if (!classroot.isopen) {
classroot.setOpen(true, true); // open, withkey
}
if (key!=32) {
// pass the key event on to the floatinglist
classroot.cblist.onkeydown.sendEvent(key);
}
}
</handler>
<handler name="onmouseup">
classroot.toggle();
</handler>
<handler name="onfocus" args="v">
if ( classroot['onfocus'] ) classroot.onfocus.sendEvent(v);
</handler>
<handler name="onblur" args="v">
if ( classroot['onblur'] ) classroot.onblur.sendEvent(v);
</handler>
<handler name="onkeyup" args="kc">
if ( classroot['onkeyup'] ) classroot.onkeyup.sendEvent(kc);
</handler>
<handler name="onkeydown" args="kc">
if ( classroot['onkeydown'] ) classroot.onkeydown.sendEvent(kc);
</handler>
<method name="getPrevSelection">
return lz.Focus.getPrev(parent);
</method>
<method name="getFocusRect">
return classroot.getFocusRect();
</method>
</text>
</state>
</view>
<floatinglist name="cblist" width="${owner.width - 1}" bordersize="${this.owner.bordersize}" spacing="${this.owner.spacing}" visible="false" shownitems="${this.owner.shownitems}" attach="bottom" attachoffset="${this.owner.attachoffset}" multiselect="false" autoscrollbar="${owner.autoscrollbar}" defaultselection="${owner.defaultselection ? owner.defaultselection : (owner.defaulttext == '' ? 0 : null) }">
<datapath>
<attribute name="datacontrolsvisibility" value="false
"/>
</datapath>
<handler name="onconstruct">
this.dataoption = owner.dataoption;
</handler>
<handler name="oninit">
// Clips to the canvas, so it doesn't spill off down into
// forever and subsequently look all goofy.
if(this.owner.shownitems == -1){
var ih = Math.floor((canvas.height - owner.y) / owner.height);
this.setAttribute('shownitems', ih);
}
</handler>
<handler name="onkeyup" method="_dokeyup"/>
<method name="_dokeyup" args="kc">
if (kc == 27) { // escape needs to close floating list
this.owner.setOpen(false);
}
</method>
</floatinglist>
<method name="setDefaultSelection" args="ds">
this.defaultselection = ds;
if ( ds == null ) return;
if ( defaulttext != "" ) {
if ($debug){
Debug.warn("%s.defaultselection (%s) overrides defaulttext (%s)",
this, ds, defaulttext);
}
this.defaulttext = "";
}
if ( this.ondefaultselection ) this.ondefaultselection.sendEvent();
</method>
<handler name="ondata" args="d">
if(d is lz.DataNodeMixin){
this.cblist.datapath.setPointer( d );
}
</handler>
<event name="onitemclassname"/>
<method name="setItemclassname" args="icn">
this.itemclassname = icn; // before anything is inited capture value
if ( this.isinited ) { // view is now inited so check classname with cblist
if (icn != "") {
// then force this classname as the classname for cblist
cblist.setAttribute('itemclassname',this.itemclassname);
} else {
//use the default subview as the classname
this.itemclassname = cblist.itemclassname;
}
}
if (this.onitemclassname) this.onitemclassname.sendEvent();
</method>
<event name="oneditable"/>
<method name="setEditable" args="isEditable">
this.editable = isEditable;
if (this._initcomplete) {
if (isEditable) {
this.interior.non_editable_state.setAttribute('applied', false);
this.interior.editable_state.setAttribute('applied', true);
} else {
this.interior.editable_state.setAttribute('applied', false);
this.interior.non_editable_state.setAttribute('applied', true);
}
this._showEnabled();
this.interior.setupText();
if (this.oneditable) this.oneditable.sendEvent();
}
</method>
<event name="ontext"/>
<method name="determinePlacement" args="newsub, placement, iargs">
if (placement == 'cblist')
return this.cblist.determinePlacement(newsub, placement, iargs);
else
return super.determinePlacement(newsub, placement, iargs);
</method>
<method name="init">
this._initcomplete = true;
// setEditable wants to be called before _applystyle
// but after _initcomplete, so it knows it can apply child states
this.setEditable(this.editable);
super.init();
// reassign classname now that the instance is inited
this.setItemclassname(this.itemclassname);
cblist.setAttribute('visible', false);
</method>
<handler name="onblur">
if ( lz.Focus.getFocus() != this.interior.cbtext )
this.setOpen(false);
</handler>
<method name="getFocusRect">
var fx = this.getAttributeRelative('x',canvas);
var fy = this.getAttributeRelative('y',canvas);
var fw = this.getAttributeRelative('width',canvas);
var fh = this.getAttributeRelative('height',canvas);
return [fx,fy,fw,fh];
</method>
<method name="select" args="item">
this.cblist.select(item);
</method>
<attribute name="_fixseldel" value="$once{new LzDelegate(this, 'fixSelection')}"/>
<attribute name="_fixselstart" type="number"/>
<attribute name="_fixselend" type="number"/>
<method name="fixSelection" args="ignore">
this.interior.cbtext.setSelection(this._fixselstart, this._fixselend);
</method>
<handler name="onselect" reference="this.cblist" args="v">
this.setOpen(false);
// anItem has been selected so update the input text field
this.selected = v;
if (v) this.setText(v.text);
if (this.editable && lz.Focus.getFocus() == this.interior.cbtext) {
this._fixselstart = 0;
this._fixselend = v.text.length;
lz.Idle.callOnIdle( _fixseldel );
}
// resend the event so that developers can write thier own
// onselect methods without having to know the internals of
// this class.
if ( this.onselect ) this.onselect.sendEvent(v);
</handler>
<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 heirarchy 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.setOpen(false);
} else {
// view is a child of cblist, so don't do anything.
}
} else {
this.setOpen(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="toggle" args="withkey=null">
this.setOpen(!this.isopen, withkey)
</method>
<method name="setOpen" args="open, withkey=null">
if (!this.isinited) {
this.isopen = open;
return;
}
if (open) { // open combox
if (this.isopen) return; // tends to get called more than once, esp when
lz.ModeManager.makeModal( this );
this.cblist.bringToFront();
this.cblist.setAttribute('visible', true);
lz.Focus.setFocus(this.cblist, withkey);
this.isopen = true;
if (this['onisopen']) this.onisopen.sendEvent(true);
} else { // close combox
if (!this['isopen']) return;
this.isopen = false;
lz.ModeManager.release( this );
this.cblist.setAttribute('visible', false);
if (this['onisopen']) this.onisopen.sendEvent(false);
if ( lz.Focus.getFocus() == this.cblist ) {
if (!editable) {
lz.Focus.setFocus(bkgnd, withkey);
} else {
lz.Focus.setFocus(interior.cbtext, withkey);
}
}
}
</method>
<method name="acceptValue" args="data, type=null">
this.setText( data );
</method>
<method name="getValue">
var rv;
var ra = this.cblist.getValue(); // an array of values
if (ra==null) {
rv = this.interior.cbtext.text;
} else {
rv = ra;
}
return rv;
</method>
<method name="setText" args="t">
this.text = t;
this.interior.cbtext.setAttribute('text', t );
if (!this._enabled) interior._dsblfield.setAttribute('text', t );
if ( this.ontext ) this.ontext.sendEvent( t );
</method>
<method name="getText">
return this.interior.cbtext.text;
</method>
<method name="getSelection">
return this.cblist.getSelection();
</method>
<method name="addItem" args="txt, val=null">
this.cblist.addItem(txt,val);
</method>
<method name="getItem" args="value">
return this.cblist.getItem(value);
</method>
<method name="getItemAt" args="index">
return this.cblist.getItemAt(index);
</method>
<method name="removeItem" args="value">
this.cblist.removeItem(value);
</method>
<method name="removeItemAt" args="index">
this.cblist.removeItemAt(index);
</method>
<method name="selectItem" args="value">
this.cblist.selectItem(value);
</method>
<method name="selectItemAt" args="index">
this.cblist.selectItemAt(index);
</method>
<method name="clearSelection">
this.cblist.clearSelection();
this.setText("");
</method>
<method name="_applystyle" args="s">
if (this.style != null) {
if (editable) {
interior.editbkgnd.setAttribute("bgcolor", s.textfieldcolor);
interior.cbtext.setAttribute('bgcolor',s.textfieldcolor);
} else {
interior.cbtext.setAttribute('bgcolor',null);
interior.cbtext.setAttribute('fgcolor',s.textcolor);
}
setTint(bkgnd, s.basecolor);
}
</method>
<method name="_showEnabled">
interior.cbtext.setAttribute('visible', this._enabled);
if (!this._enabled) {
if (interior._dsblfield == null) {
var t = new lz.text(interior,
{ name: '_dsblfield', x: 2, y: 1,
width:interior.width, height:interior.height,
fgcolor:this['style'] ? this.style.textdisabledcolor : null});
} else {
interior._dsblfield.setAttribute('visible', true);
}
interior._dsblfield.setAttribute('text', this.getText());
} else {
if (interior._dsblfield) interior._dsblfield.setAttribute('visible', false);
}
</method>
<setter name="width" args="w">
super.setAttribute('width', (Math.max(w,this.min_width)));
</setter>
<doc>
<tag name="shortdesc"><text>baseclass for a combobox</text></tag>
<text>
<p>Extend this base class to create a combobox with a custom popup
control, and to change the size of the font.</p>
<p>The number of items shown in the combobox can be set using the
<attribute>shownitems</attribute> attribute. If there are more items
available than are shown, a scrollbar will be created
automatically.</p>
<p>The list of items in a combobox can be created explicity using the
<tagname>textlistitem</tagname> tags with assigned text and value
attributes.</p>
<example title="custom appearance and font for a combobox">
<canvas height="100">
<font name="smallfont" src="verity/verity9.ttf"/>
<class name="minicombo" extends="basecombobox" font="smallfont"
bgcolor="0xeaeaea" editable="false" height="14">
<attribute name="text_y" value="-2"/>
<view bgcolor="green" x="${parent.width-10}"
y="3" height="8" width="8"
placement="bkgnd" onclick="parent.toggle()" />
</class>
<class name="minilistitem" extends="textlistitem"
font="smallfont" text_y="-2" height="12"/>
<minicombo x="20" y="20" >
<minilistitem value="1">one</minilistitem>
<minilistitem value="2">two</minilistitem>
<minilistitem value="3">three</minilistitem>
</minicombo>
</canvas>
</example>
<p>For more use cases of <tagname>basecombobox</tagname>, see examples
in <a href="lz.combobox.html" target="_self"><code class="tagname"><combobox></code></a>.</p>
</text>
</doc>
</class>
</library>
Cross References
Includes
Classes