﻿// Custum implementation of the AJAX autocomplete behavior
// This behavior can be attached to a textbox to enable auto-complete/auto-suggest
// scenarios.

Type.registerNamespace('Sentient');
Type.registerNamespace('AjaxControlToolkit');

Sentient.AutoCompleteBehavior = function(element) {
    /// <param name="element" domElement="true">The DOM element the behavior is associated with.</param>
    Sentient.AutoCompleteBehavior.initializeBase(this, [element]);
        this._servicePath = null;
        this._serviceMethod = null;
        this._minimumPrefixLength = 3;
        this._completionSetCount = 10;
        this._completionInterval = 1000;        
        this._completionListElementID = null;
        this._completionListElement = null;
        this._textColor = 'windowtext';
        this._textBackground = 'window';
        this._popupBehavior = null;
        this._currentPrefix = null;
        this._selectIndex = -1;
        this._focusHandler = null;
        this._blurHandler = null;
        this._keyDownHandler = null;
        this._keyUpHandler = null;
        this._mouseDownHandler = null;
        this._mouseUpHandler = null;
        this._mouseOverHandler = null;
        this._tickHandler = null;
        this._enableCaching = true;
        this._hasFocus = true;
}
Sentient.AutoCompleteBehavior.prototype = {

    initialize: function() {
    
        Sentient.AutoCompleteBehavior.callBaseMethod(this, 'initialize');
        this._lastRequest     = null;
        this._lastRequestText = null;
        this._timer = null;
        this._cache = null;
        this._itemIdCache = {};
        this._cacheId2Match = {};

        this._tickHandler = Function.createDelegate(this, this._onTimerTick);
        this._focusHandler = Function.createDelegate(this, this._onGotFocus);
        this._blurHandler = Function.createDelegate(this, this._onLostFocus);
        this._keyDownHandler = Function.createDelegate(this, this._onKeyDown);
        this._keyUpHandler = Function.createDelegate(this, this._onKeyUp);
        this._mouseDownHandler = Function.createDelegate(this, this._onListMouseDown);
        this._mouseUpHandler = Function.createDelegate(this, this._onListMouseUp);
        this._mouseOverHandler = Function.createDelegate(this, this._onListMouseOver);
        
        this._timer = new Sys.Timer();
        this.initializeTimer(this._timer);
        
        var element = this.get_element();
        this.initializeTextBox(element);
        
        if(this._completionListElementID !== null)
            this._completionListElement = $get(this._completionListElementID);
        if (this._completionListElement == null ) {
            this._completionListElement = document.createElement('DIV');
            this._completionListElement.id = this.get_id() + '_completionListElem';

            // Safari styles the element incorrectly if it's added to the desired location
            if (Sys.Browser.agent === Sys.Browser.Safari) {
                document.body.appendChild(this._completionListElement);
            } else {
                element.parentNode.appendChild(this._completionListElement);
            }
        }
        
        this.initializeCompletionList(this._completionListElement);
        
        this._popupBehavior = $create(AjaxControlToolkit.PopupBehavior, 
                { 'id':this.get_id()+'PopupBehavior', 'parentElement':element, "positioningMode": AjaxControlToolkit.PositioningMode.BottomLeft }, null, null, this._completionListElement);        
    },
    
    get_completionInterval: function() {
        /// <value type="Number">Auto completion timer interval in milliseconds.</value>
        return this._completionInterval;
    },
    set_completionInterval: function(value) {
        this._completionInterval = value;
    },
    
    get_completionList: function() {
        /// <value domElement="true">List dom element.</value>
        return this._completionListElement;
    },
    set_completionList: function(value) {
        this._completionListElement = value;
    },
    
    get_selectIndex: function(){
      if(this._completionListElement.innerHTML.length>0)
				return this._selectIndex;
			return -1;
    },
    
    get_selectedID: function(){
      var id = this.get_element().id + 'id';
      var idElement = document.getElementById(id);
      return idElement.value.length>0 ? idElement.value : null ;
    },
    
    get_completionSetCount: function() {
        /// <value type="Number">Maximum completion set size.</value>
        return this._completionSetCount;
    },
    set_completionSetCount: function(value) {
        this._completionSetCount = value;
    },
    
    get_minimumPrefixLength: function() {
        /// <value type="Number">Minimum text prefix length required to perform behavior.</value>
        return this._minimumPrefixLength;
    },
    set_minimumPrefixLength: function(value) {
        this._minimumPrefixLength = value;
    },
    
    get_serviceMethod: function() {
        /// <value type="String">Web service method.</value>
        return this._serviceMethod;
    },
    set_serviceMethod: function(value) {
        this._serviceMethod = value;
        this._cache = {};
        this._itemIdCache = {};
        this._cacheId2Match = {};
        if(this._timer!=null){
          this._timerSetEnabled(false);
        }          
        this._showProgress(false);        
    },
    
    get_servicePath: function() {
        /// <value type="String">Web service url.</value>
        return this._servicePath;
    },
    set_servicePath: function(value) {
        this._servicePath = value;
    },
    
    get_enableCaching: function() {
        /// <value type="Boolean">Get or sets whether suggestions retrieved from the webservice should be cached.</value>
        return this._enableCaching;
    },
    set_enableCaching: function(value) {
        this._enableCaching = value;
    },
    
    get_completionListElementID: function(){
        /// <value type="String>ID of the completion div element. </value>
        return this._completionListElementID;
    },
    set_completionListElementID: function(value) {
        this._completionListElementID = value;  
    },    
    
    get_htmlButtons : function(bookid){
			var match = this._cacheId2Match[bookid];
			if(match) 
				return match.HtmlIcons;
			return null;
    },
    
    dispose: function() {
        
        if (this._popupBehavior) {
            this._popupBehavior.dispose();
            this._popupBehavior = null;
        }
        if(this._timer) {        
            this._timer.dispose();
            this._timer = null;
        }

        var element = this.get_element();
        if(element) {
            $removeHandler(element, "focus", this._focusHandler);
            $removeHandler(element, "blur", this._blurHandler);
            $removeHandler(element, "keyup", this._keyUpHandler);
            $removeHandler(element, "keydown", this._keyDownHandler);
            $removeHandler(this._completionListElement, 'mousedown', this._mouseDownHandler);
            $removeHandler(this._completionListElement, 'mouseup', this._mouseUpHandler);
            $removeHandler(this._completionListElement, 'mouseover', this._mouseOverHandler);
        }
        
        this._tickHandler = null;
        this._focusHandler = null;
        this._blurHandler = null;
        this._keyDownHandler = null;
        this._keyUpHandler   = null;
        this._mouseDownHandler = null;
        this._mouseUpHandler = null;
        this._mouseOverHandler = null;
        

        Sentient.AutoCompleteBehavior.callBaseMethod(this, 'dispose');
    },
    
    initializeTimer: function(timer) {
        timer.set_interval(this._completionInterval);
        timer.add_tick(this._tickHandler);
    },
    
    initializeTextBox: function(element) {
        element.autocomplete = "off";
        $addHandler(element, "focus", this._focusHandler);
        $addHandler(element, "blur", this._blurHandler);
        $addHandler(element, "keyup", this._keyUpHandler);
        $addHandler(element, "keydown", this._keyDownHandler);
        
    },
    
    initializeCompletionList: function(element) {
        var completionListStyle = element.style;
        completionListStyle.visibility = 'hidden';
        completionListStyle.backgroundColor = this._textBackground;
        completionListStyle.color = this._textColor;
        completionListStyle.border = 'solid 1px buttonshadow';
        completionListStyle.cursor = 'default';
        completionListStyle.unselectable = 'unselectable';
        completionListStyle.overflow = 'hidden';
        
        $addHandler(element, "mousedown", this._mouseDownHandler);
        $addHandler(element, "mouseup", this._mouseUpHandler);
        $addHandler(element, "mouseover", this._mouseOverHandler);
    },
    
    _hideCompletionList: function(transferFocusToTextBox) {
        this._popupBehavior.hide();
        this._completionListElement.innerHTML = '';
        this._selectIndex = -1;

        if(transferFocusToTextBox)
					this.get_element().focus;
    },
    
    _highlightItem: function(item) {
        /// <param name="item">DOM element.</param>
        var children = this._completionListElement.childNodes;
        
        for (var i = 0; i < children.length; i++) {
            var child = children[i];
            if (child !== item) {
                child.style.backgroundColor = this._textBackground;
                child.style.color = this._textColor;
            }
        }

        // Only on Safari set the background color to be the same as "DropDown"
        // hightlighted item otherwise the text and the background are the same
        // color and the item hides itself.
        if (Sys.Browser.agent === Sys.Browser.Safari) {
          item.style.backgroundColor = '#FFF3DB';        
          item.style.color = 'black';
        }else{
          item.style.backgroundColor = 'highlight';
          item.style.color = 'highlighttext';

        }
    },
    
    _onListMouseDown: function(ev) {
      if (ev.target !== this._completionListElement) {
        this._setSelectedItem(ev.target);
      }
    },
    
    _onListMouseUp: function(ev) {
        this.get_element().focus();
    },
    
    _onListMouseOver: function(ev) {
        var item = ev.target;
        this._selectIndex = -1;
        this._highlightItem(item);
    },

    _onGotFocus: function(ev) {
        this._hasFocus = true;
        //this._timerSetEnabled(true);
        var id = this.get_element().id + 'id';
	      var idElement = document.getElementById(id);
	      if(idElement.value.length==0)
					this._timerSetEnabled(true);

    },

    _timerGetEnabled: function(){
      return this._timer.get_enabled();
    },

    
    _timerSetEnabled: function(bool){
			var text = this._getElementValue();
      // aborting the request does not kill the sql query at the server
      if(bool && this._lastRequest && this._lastRequestText == text ){
        this._showProgress(true);
      }else{
	      this._showProgress(false);
        if(this._lastRequest){
          //this._lastRequest.get_executor().abort();
          this._lastRequest = null;
          this._lastRequestText = null;
        }
        if(!bool)
					this._timer.set_enabled(false);
				else{
					if (this._cache && this._cache[text]) {
							this._update(text, this._cache[text], /* cacheResults */ false);
					} else {
						this._timer.set_enabled(false);
						this._timer.set_enabled(true);
					}
				}				
      }
    },
    
    _getElementValue: function(){
			return this.get_element().value.trim().toLowerCase();    
    },

  // When changing this code keep in mind the behavior in 
  // nblcEventHandlers . showOrHideClearButton(evt)  :
  // fired when the text in the stimulus textboxes is changed
  // can start the call back to the server if the enter button is pressed
  // since we can not distinguish between events in the floating div (autocomplete) and the text box itself
  //   , we check if the timer of the associated autocomplete object is enabled   
  // see also http://www.asp.net/ajax/documentation/live/ClientReference/Sys.UI/KeyEnum/default.aspx
    _onKeyDown: function(ev) {
      var startTimer = false;
      var k = ev.keyCode ? ev.keyCode : ev.rawEvent.keyCode;        
      
      switch(k){
        case Sys.UI.Key.up:
          ev.preventDefault();        
          if (this._selectIndex > 0) {
              this._selectIndex--;
              this._highlightItem(this._completionListElement.childNodes[this._selectIndex]);
          }else if(this._selectIndex==0){
            this._selectIndex = (this._completionListElement.childNodes.length - 1);
            this._highlightItem(this._completionListElement.childNodes[this._selectIndex]);
          }
		      return false;
        case Sys.UI.Key.down:
          ev.preventDefault();
          if (this._selectIndex < (this._completionListElement.childNodes.length - 1)) {
              this._selectIndex++;
              this._highlightItem(this._completionListElement.childNodes[this._selectIndex]);
          }else if(this._selectIndex == (this._completionListElement.childNodes.length - 1)){
            this._selectIndex = 0;
            this._highlightItem(this._completionListElement.childNodes[this._selectIndex]);
          }
		      return false;
      }
      return true;
    },
 
  // When changing this code keep in mind the behavior in 
  // nblcEventHandlers . showOrHideClearButton(evt)  :
  // fired when the text in the stimulus textboxes is changed
  // can start the call back to the server if the enter button is pressed
  // since we can not distinguish between events in the floating div (autocomplete) and the text box itself
  //   , we check if the timer of the associated autocomplete object is enabled   
  // see also http://www.asp.net/ajax/documentation/live/ClientReference/Sys.UI/KeyEnum/default.aspx
    _onKeyUp: function(ev) {			
      var startTimer = false;
      var k = ev.keyCode ? ev.keyCode : ev.rawEvent.keyCode;        
      var setText = false;
      var clearID = true;
      var returnValue = true;
      
      switch(k){
        case Sys.UI.Key.esc:
          clearID = false;
          this._hideCompletionList(true);
          ev.preventDefault();
          break;
        case Sys.UI.Key.up:
          clearID = false;
          returnValue = false;
          break;
        case Sys.UI.Key.down:
          clearID = false;
          returnValue = false;
          break;
        case Sys.UI.Key.enter:
          clearID = false;
          if( this._completionListElement.innerHTML.length > 0 ){
            // be careful when changing this code!
            if(this._selectIndex != -1 )
              this._setSelectedItem(this._completionListElement.childNodes[this._selectIndex]);
            else
              this._setSelectedItem(this._completionListElement.childNodes[0]);
            ev.preventDefault();
            ev.srcElement = this._completionListElement;
            returnValue = false;
          }else{
            ev.preventDefault();
            startTimer = true; // start timer even though the page might start advising, because it might not start advising
            returnValue = true;
          }
          break;
        case Sys.UI.Key.tab:
            clearID = false;
            if(this._selectIndex != -1)
              this._setSelectedItem(this._completionListElement.childNodes[this._selectIndex]);
            this._showProgress(false);
          break;
        case Sys.UI.Key.right:
        case Sys.UI.Key.left:
        case Sys.UI.Key.home:
        case Sys.UI.Key.end:
        case Sys.UI.Key.pageUp:
        case Sys.UI.Key.pageDown:
        case Sys.UI.Key.shift:
        case Sys.UI.Key.control:
          clearID = false;
          startTimer = true;          
          break;
        default:          
          startTimer = true;          
      }
      if( startTimer ){
         this._hideCompletionList(true);
         this._timerSetEnabled(true);
      }else{
        this._timerSetEnabled(false);            
      }
      if(clearID)
				this._clearCurrentId();        

      // element has focus
			this.get_element().focus;
				
      return returnValue;

    },
    
    _clearCurrentId: function(){
      var id = this.get_element().id + 'id';
      var idElement = document.getElementById(id);
      var itemid = idElement.value;
			if(this._itemIdCache[itemid]==this.get_element().value){
			  // nop
  			this._debug(this.get_element().value + " itemid " + itemid);
			}else{
  			this._debug("Clear: " + this.get_element().value + " itemid " + itemid);
				idElement.value = '';
			}
			//TODO add code to clear MOM and other
      this.setInfoTextAndVisibility(false,'');
    },
    
    setInfoTextAndVisibility: function(visible, text){
      var infoElement = document.getElementById(this.get_element().id + '_info');
      var element = this.get_element();
      
      if(visible){
        infoElement.innerHTML = text;
        infoElement.style.display='block';
				debug('Old width: ' + element.style.width);
				element.style.width='335px';
				debug('New width: ' + element.style.width);
      }else{
        infoElement.style.display='none';
				debug('Old width: ' + element.style.width);
				element.style.width='380px';
				debug('New width: ' + element.style.width);
      }
    },

    
    _setSelectedItem: function(selectedElement) {    
      this._setText(selectedElement.firstChild.nodeValue, selectedElement.attributes.getNamedItem("itemid").nodeValue, selectedElement.attributes.getNamedItem("htmlbuttons").nodeValue);
      this._hideCompletionList(true);    
    },
    
    _onLostFocus: function() {
        this._timerSetEnabled(false);
        this._hideCompletionList(false);
        this._hasFocus = false;        
    },
    
    _onMethodComplete: function(result, context, methodName) {
      this._lastRequest = null;   
      this._lastRequestText = null;
      this._update(context, result, /* cacheResults */ true);
    },
    _onMethodFailed: function(err, response, context) {
        // no op
        this._showProgress(false);
    },
    
    _showProgress : function ( start ){
          var element = this.get_element();
          var stopElement = document.getElementById( element.id + "_stopped" );
          var startElement = document.getElementById( element.id + "_loading" );
          if(stopElement==null || startElement==null)
            return;
          stopElement.style.display= start ? "none":"block";
          startElement.style.display=start ? "block":"none"; 
    },
    
    // call back function. Initiates the search for matching authors/titles
    _onTimerTick: function(sender, eventArgs) {
    //alert('Trying to send');
      if (this._servicePath && this._serviceMethod) {
        var text = this._getElementValue();
        
        if (text.length < this._minimumPrefixLength) {
          this._currentPrefix = null;
          this._update('', null, /* cacheResults */ false);
          return;
        }
        
        this._currentPrefix = text;
        if (this._cache && this._cache[text]) {
            this._update(text, this._cache[text], /* cacheResults */ false);
            return;
        }
        if(this._lastRequest && this._lastRequestText == text)
          return;
        else if(this._lastRequest){
          //this._lastRequest.get_executor().abort();
        }
        this._showProgress(true);
        this._lastRequestText = text;
        this._lastRequest = Sys.Net.WebServiceProxy.invoke(this.get_servicePath()
              , fnGetAutocompletionMethod() //this.get_serviceMethod()
              , false
              , { prefixText : text, count: this._completionSetCount }
              , Function.createDelegate(this, this._onMethodComplete)
              , Function.createDelegate(this, this._onMethodFailed)
              , text);
      }
    },

    _setText: function(text) {
      this._setText(text,-1, '');
    },
    
    _setText: function(text, itemid, htmlButtons) {
			this._debug(text + " itemid " + itemid + htmlButtons);
      this._timerSetEnabled(false);
      this._currentPrefix = text;
      var element = this.get_element();
      var control = element.control;
      // todo: should check for 'derives from' too and should somehow manually cause TB to raise property changed event
      if (control && control.set_text) {
        control.set_text(text);
      }else {
        element.value = text;
      }
      var id = this.get_element().id + 'id';
      document.getElementById(id).value = itemid;
			if(itemid>0)
			  this._itemIdCache[itemid]=text;
      this._hideCompletionList(true);
      
      this.setInfoTextAndVisibility(htmlButtons.length>0,htmlButtons);
    },
    
    _debug: function(text){
			debug(text,false);
    },
    
    _debug: function(text, clear){
			debug(text,clear);
    },
       
    _update: function(prefixText, completionItems, cacheResults) {
        var element = this.get_element();
        this._hideCompletionList(true);

        if(!completionItems || completionItems.length <= 0)
          return;
  
        if (cacheResults && this.get_enableCaching()) {
            if (!this._cache)
                this._cache = {};
            this._cache[completionItems[0].Query] = completionItems;
        }

        if(completionItems[0].Query != prefixText || completionItems[0].SearchMethodName != fnGetAutocompletionMethod()) //this.get_serviceMethod()
          return;
        if( !element || this._getElementValue() != prefixText )
          return;
          
        this._timerSetEnabled(false);
        
        if( !this._hasFocus ){
          return;
        }
        //alert(prefixText + '|' + this.get_element().value + ' [' + cacheResults + '] ' + this.get_element().id + ' ' + completionItems[0].Name);

        this._completionListElement.innerHTML = '';
        this._selectIndex = -1;
        for (var i = 0; i < completionItems.length; i++) {
            var itemElement = document.createElement('div');
            itemElement.appendChild(document.createTextNode(completionItems[i].Name));
            itemElement.__item = '';
            itemElement.setAttribute( "itemid", completionItems[i].ID );
            itemElement.setAttribute( "htmlbuttons", completionItems[i].HtmlIcons );
            //itemElement.itemid=completionItems[i].ID;
            
            var itemElementStyle = itemElement.style;
            itemElementStyle.padding = '1px';
            itemElementStyle.textAlign = 'left';
            itemElementStyle.textOverflow = 'ellipsis';
            itemElementStyle.backgroundColor = this._textBackground;
            itemElementStyle.color = this._textColor;
            //itemElementStyle.zIndex = 2; // force to show on top
            this._completionListElement.appendChild(itemElement);
            
            if(cacheResults)
              this._cacheId2Match[completionItems[i].ID] = completionItems[i];
        }
        var elementBounds = CommonToolkitScripts.getBounds(this.get_element());        
        this._completionListElement.style.width = Math.max(1, elementBounds.width - 2) + 'px';
        this._popupBehavior.show();
    }    
}

Sentient.AutoCompleteBehavior.descriptor = {
    properties: [   {name: 'completionInterval', type: Number},
                    {name: 'completionList', isDomElement: true},
                    {name: 'completionListElementID', type: String},
                    {name: 'completionSetCount', type: Number},
                    {name: 'minimumPrefixLength', type: Number},
                    {name: 'serviceMethod', type: String},
                    {name: 'servicePath', type: String},
                    {name: 'enableCaching', type: Boolean},
                    {name: 'searchingImageID', type: String},
                    {name: 'stoppedImageID', type: String},
                    {name: 'hasFocus', type: Boolean}
                    ]
}

Sentient.AutoCompleteBehavior.registerClass('Sentient.AutoCompleteBehavior', Sys.UI.Behavior);

