Date picker and bylines for platforms`
[bus.git] / busui / js / jquery.ui.autocomplete.js
blob:a/busui/js/jquery.ui.autocomplete.js -> blob:b/busui/js/jquery.ui.autocomplete.js
  /*
  * jQuery UI Autocomplete @VERSION
  *
  * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
  * Dual licensed under the MIT or GPL Version 2 licenses.
  * http://jquery.org/license
  *
  * http://docs.jquery.com/UI/Autocomplete
  *
  * Depends:
  * jquery.ui.core.js
  * jquery.ui.widget.js
  * jquery.ui.position.js
  * jquery.ui.menu.js
  */
  (function( $, undefined ) {
   
  // used to prevent race conditions with remote data sources
  var requestIndex = 0;
   
  $.widget( "ui.autocomplete", {
  defaultElement: "<input>",
  options: {
  appendTo: "body",
  delay: 300,
  minLength: 1,
  position: {
  my: "left top",
  at: "left bottom",
  collision: "none"
  },
  source: null
  },
   
  pending: 0,
   
  _create: function() {
  var self = this,
  doc = this.element[ 0 ].ownerDocument,
  suppressKeyPress;
   
  this.element
  .addClass( "ui-autocomplete-input" )
  .attr( "autocomplete", "off" )
  // TODO verify these actually work as intended
  .attr({
  role: "textbox",
  "aria-autocomplete": "list",
  "aria-haspopup": "true"
  })
  .bind( "keydown.autocomplete", function( event ) {
  if ( self.options.disabled || self.element.attr( "readonly" ) ) {
  return;
  }
   
  suppressKeyPress = false;
  var keyCode = $.ui.keyCode;
  switch( event.keyCode ) {
  case keyCode.PAGE_UP:
  self._move( "previousPage", event );
  break;
  case keyCode.PAGE_DOWN:
  self._move( "nextPage", event );
  break;
  case keyCode.UP:
  self._move( "previous", event );
  // prevent moving cursor to beginning of text field in some browsers
  event.preventDefault();
  break;
  case keyCode.DOWN:
  self._move( "next", event );
  // prevent moving cursor to end of text field in some browsers
  event.preventDefault();
  break;
  case keyCode.ENTER:
  case keyCode.NUMPAD_ENTER:
  // when menu is open and has focus
  if ( self.menu.active ) {
  // #6055 - Opera still allows the keypress to occur
  // which causes forms to submit
  suppressKeyPress = true;
  event.preventDefault();
  }
  //passthrough - ENTER and TAB both select the current element
  case keyCode.TAB:
  if ( !self.menu.active ) {
  return;
  }
  self.menu.select( event );
  break;
  case keyCode.ESCAPE:
  self.element.val( self.term );
  self.close( event );
  break;
  default:
  // keypress is triggered before the input value is changed
  clearTimeout( self.searching );
  self.searching = setTimeout(function() {
  // only search if the value has changed
  if ( self.term != self.element.val() ) {
  self.selectedItem = null;
  self.search( null, event );
  }
  }, self.options.delay );
  break;
  }
  })
  .bind( "keypress.autocomplete", function( event ) {
  if ( suppressKeyPress ) {
  suppressKeyPress = false;
  event.preventDefault();
  }
  })
  .bind( "focus.autocomplete", function() {
  if ( self.options.disabled ) {
  return;
  }
   
  self.selectedItem = null;
  self.previous = self.element.val();
  })
  .bind( "blur.autocomplete", function( event ) {
  if ( self.options.disabled ) {
  return;
  }
   
  clearTimeout( self.searching );
  // clicks on the menu (or a button to trigger a search) will cause a blur event
  self.closing = setTimeout(function() {
  self.close( event );
  self._change( event );
  }, 150 );
  });
  this._initSource();
  this.response = function() {
  return self._response.apply( self, arguments );
  };
  this.menu = $( "<ul></ul>" )
  .addClass( "ui-autocomplete" )
  .appendTo( $( this.options.appendTo || "body", doc )[0] )
  // prevent the close-on-blur in case of a "slow" click on the menu (long mousedown)
  .mousedown(function( event ) {
  // clicking on the scrollbar causes focus to shift to the body
  // but we can't detect a mouseup or a click immediately afterward
  // so we have to track the next mousedown and close the menu if
  // the user clicks somewhere outside of the autocomplete
  var menuElement = self.menu.element[ 0 ];
  if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
  setTimeout(function() {
  $( document ).one( 'mousedown', function( event ) {
  if ( event.target !== self.element[ 0 ] &&
  event.target !== menuElement &&
  !$.contains( menuElement, event.target ) ) {
  self.close();
  }
  });
  }, 1 );
  }
   
  // use another timeout to make sure the blur-event-handler on the input was already triggered
  setTimeout(function() {
  clearTimeout( self.closing );
  }, 13);
  })
  .menu({
  // custom key handling for now
  input: $(),
  focus: function( event, ui ) {
  var item = ui.item.data( "item.autocomplete" );
  if ( false !== self._trigger( "focus", event, { item: item } ) ) {
  // use value to match what will end up in the input, if it was a key event
  if ( /^key/.test(event.originalEvent.type) ) {
  self.element.val( item.value );
  }
  }
  },
  select: function( event, ui ) {
  var item = ui.item.data( "item.autocomplete" ),
  previous = self.previous;
   
  // only trigger when focus was lost (click on menu)
  if ( self.element[0] !== doc.activeElement ) {
  self.element.focus();
  self.previous = previous;
  // #6109 - IE triggers two focus events and the second
  // is asynchronous, so we need to reset the previous
  // term synchronously and asynchronously :-(
  setTimeout(function() {
  self.previous = previous;
  self.selectedItem = item;
  }, 1);
  }
   
  if ( false !== self._trigger( "select", event, { item: item } ) ) {
  self.element.val( item.value );
  }
  // reset the term after the select event
  // this allows custom select handling to work properly
  self.term = self.element.val();
   
  self.close( event );
  self.selectedItem = item;
  },
  blur: function( event, ui ) {
  // don't set the value of the text field if it's already correct
  // this prevents moving the cursor unnecessarily
  if ( self.menu.element.is(":visible") &&
  ( self.element.val() !== self.term ) ) {
  self.element.val( self.term );
  }
  }
  })
  .zIndex( this.element.zIndex() + 1 )
  .hide()
  .data( "menu" );
  if ( $.fn.bgiframe ) {
  this.menu.element.bgiframe();
  }
  },
   
  _destroy: function() {
  this.element
  .removeClass( "ui-autocomplete-input" )
  .removeAttr( "autocomplete" )
  .removeAttr( "role" )
  .removeAttr( "aria-autocomplete" )
  .removeAttr( "aria-haspopup" );
  this.menu.element.remove();
  },
   
  _setOption: function( key, value ) {
  this._super( "_setOption", key, value );
  if ( key === "source" ) {
  this._initSource();
  }
  if ( key === "appendTo" ) {
  this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )
  }
  if ( key === "disabled" && value && this.xhr ) {
  this.xhr.abort();
  }
  },
   
  _initSource: function() {
  var self = this,
  array,
  url;
  if ( $.isArray(this.options.source) ) {
  array = this.options.source;
  this.source = function( request, response ) {
  response( $.ui.autocomplete.filter(array, request.term) );
  };
  } else if ( typeof this.options.source === "string" ) {
  url = this.options.source;
  this.source = function( request, response ) {
  if ( self.xhr ) {
  self.xhr.abort();
  }
  self.xhr = $.ajax({
  url: url,
  data: request,
  dataType: "json",
  autocompleteRequest: ++requestIndex,
  success: function( data, status ) {
  if ( this.autocompleteRequest === requestIndex ) {
  response( data );
  }
  },
  error: function() {
  if ( this.autocompleteRequest === requestIndex ) {
  response( [] );
  }
  }
  });
  };
  } else {
  this.source = this.options.source;
  }
  },
   
  search: function( value, event ) {
  value = value != null ? value : this.element.val();
   
  // always save the actual value, not the one passed as an argument
  this.term = this.element.val();
   
  if ( value.length < this.options.minLength ) {
  return this.close( event );
  }
   
  clearTimeout( this.closing );
  if ( this._trigger( "search", event ) === false ) {
  return;
  }
   
  return this._search( value );
  },
   
  _search: function( value ) {
  this.pending++;
  this.element.addClass( "ui-autocomplete-loading" );
   
  this.source( { term: value }, this.response );
  },
   
  _response: function( content ) {
  if ( !this.options.disabled && content && content.length ) {
  content = this._normalize( content );
  this._suggest( content );
  this._trigger( "open" );
  } else {
  this.close();
  }
  this.pending--;
  if ( !this.pending ) {
  this.element.removeClass( "ui-autocomplete-loading" );
  }
  },
   
  close: function( event ) {
  clearTimeout( this.closing );
  if ( this.menu.element.is(":visible") ) {
  this.menu.element.hide();
  this.menu.deactivate();
  this._trigger( "close", event );
  }
  },
   
  _change: function( event ) {
  if ( this.previous !== this.element.val() ) {
  this._trigger( "change", event, { item: this.selectedItem } );
  }
  },
   
  _normalize: function( items ) {
  // assume all items have the right format when the first item is complete
  if ( items.length && items[0].label && items[0].value ) {
  return items;
  }
  return $.map( items, function(item) {
  if ( typeof item === "string" ) {
  return {
  label: item,
  value: item
  };
  }
  return $.extend({
  label: item.label || item.value,
  value: item.value || item.label
  }, item );
  });
  },
   
  _suggest: function( items ) {
  var ul = this.menu.element
  .empty()
  .zIndex( this.element.zIndex() + 1 );
  this._renderMenu( ul, items );
  // TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivate
  this.menu.deactivate();
  this.menu.refresh();
   
  // size and position menu
  ul.show();
  this._resizeMenu();
  ul.position( $.extend({
  of: this.element
  }, this.options.position ));
  },
   
  _resizeMenu: function() {
  var ul = this.menu.element;
  ul.outerWidth( Math.max(
  ul.width( "" ).outerWidth(),
  this.element.outerWidth()
  ) );
  },
   
  _renderMenu: function( ul, items ) {
  var self = this;
  $.each( items, function( index, item ) {
  self._renderItem( ul, item );
  });
  },
   
  _renderItem: function( ul, item) {
  return $( "<li></li>" )
  .data( "item.autocomplete", item )
  .append( $( "<a></a>" ).text( item.label ) )
  .appendTo( ul );
  },
   
  _move: function( direction, event ) {
&n