Implementing an enhanced autocomplete behaviour for QxComboBox
Work in progress. ToDo:
- adapt to Qooxdoo transport
- limiting of options in case of a high number of matches (to prevent massive slowdown)
Client-Side:
// autocomplete behaviour property QxComboBox.addProperty({ name : "autocomplete", type : QxConst.TYPEOF_BOOLEAN, defaultValue : false }); // where to get autocomplete data from QxComboBox.addProperty({ name : "autocompleteUrl", type : QxConst.TYPEOF_STRING, defaultValue : '' }); // a separator marks the beginning of a new autocomplete section QxComboBox.addProperty({ name : "separator", type : QxConst.TYPEOF_STRING, defaultValue : '' }); /** * behavior on foo.setAutocomplete() **/ QxComboBox.prototype._modifyAutocomplete = function(propValue, propOldValue, propData) { if ( propValue ) { this.setEditable(true); this.getField().addEventListener ("keydown",this._handleKeyPress, this ); this.getField().addEventListener ("input",this._handleInput, this ); } else if ( propOldValue ) { this.getField().removeEventListener ("keydown", this._handleKeyPress, this ); this.getField().removeEventListener ("input",this._handleInput, this ); } return true; }; /** * handles an textfield input by repopulating the combobox list and proposing * a match * server must return a JSON object with the keys * - options: array of options for the popup list * - data: string of the proposed match */ QxComboBox.prototype._handleInput = function ( e ) { // flag to prevent matching if ( this._preventMatch ) return; var self = this, vTextfield = this.getField(), vOldValue = e.getData(), vOldLength = vOldValue.length, vSeparator = this.getSeparator(), vStartMatchAt = vSeparator ? ( vOldValue.lastIndexOf ( vSeparator ) + 1 ) : 0, vBeforeMatch = vStartMatchAt ? vOldValue.substr ( 0, vStartMatchAt ) : "", vMatch = vOldValue.substr( vStartMatchAt ); // execute with a timeout to prevent requests during rapid typing window.setTimeout ( function() { if ( vOldValue != vTextfield.getComputedValue() ) { // user has typed ahead in the meantime, abort return; } // here you need to put your own transport solution in! yourTransportMethod({ url: this.getAutocompleteUrl(), params: { data: vMatch }, handler: function ( data ){ if ( vOldValue != vTextfield.getComputedValue() ) { // user has typed ahead in the meantime, abort return; } // update options if ( typeof data.options != "undefined" ) { self.getList().removeAll(); for ( var i=0; i < data.options.length; i++ ) { self.getList().add ( new QxListItem ( options[i], null, options[i] ) ); }; }; // update data if ( data.data ) { var vNewValue = vBeforeMatch + data.data, vNewLength = vNewValue.length; vTextfield.getElement().value = vNewValue; // set selection vTextfield.setFocused ( true ); vTextfield.setSelectionStart ( vOldLength ); vTextfield.setSelectionLength ( vNewLength - vOldLength ); }; } }); },200 ); }; QxComboBox.prototype._handleKeyPress = function ( e ) { var vKeyCode = e.getKeyCode(), vTextfield = this.getField(), vContent = vTextfield.getComputedValue(); this._preventMatch = false; switch ( vKeyCode ) { // escape restores previous content when popup is not visible case QxKeyEvent.keys.esc: if (!this.getPopup().getVisible()){ vTextfield.getElement().value = vTextfield._previousData; this._preventMatch = true; return false; } break; // do not update and put focus back into textfield case QxKeyEvent.keys.enter: case QxKeyEvent.keys.down: case QxKeyEvent.keys.up: case QxKeyEvent.keys.pageUp: case QxKeyEvent.keys.pageDown: this._preventMatch = true; vTextfield.setFocused(true); return ; } return true; }
Server-Side:
/** * autocomplete data * @param string $match The string to be matched. You need to get this value * from the request sent by "yourTransportMethod" and pass it to this function * @return array Array with keys data and options that need to be turned into * a JSON array before being sent back to the server. */ function autocomplete ( $match ) { $match = trim ( $match ); $length = strlen( $match ); $index = getAllAvailableOptionsFunction(); // this should return an array with ALL available options $options = array(); $data = ""; // get all matching items foreach ( $index as $item ) { if ( strtolower ( substr( $item, 0, $length ) ) == strtolower ( $match ) ) { $options[] = $item; } } if ( count ( $options ) ) { $data = $options[0]; } return array ( 'data' => $data, 'options' => $options ) ); }
— Christian Boulanger 2006/06/20 09:09
