Document Information

Last modified:
2007/05/31 18:16 by thron7

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

Information

Last modified:
2007/05/31 18:16 by thron7

Account

Not logged in

 
 

Job Offers

To further improve qooxdoo we are seeking javascript developers. Read more...

Rich Ajax Platform (RAP)

RAP uses qooxdoo, Java and the Eclipse development model to build rich web applications. Read more...

qooxdoo Web Toolkit (QWT)

Similar to GWT this framework allows to create impressive qooxdoo applications just using Java. Read more...

Pustefix

Pustefix is a MVC-based web application framework using Java and XML/XSLT. Read more...

 
SourceForge.net Logo

Bad Behavior has blocked 0 potential spam attempts in the last 7 days.