// Name: ListSearch.ListSearchBehavior.debug.js
// Assembly: AjaxControlToolkit
// Version: 4.5.7.1213
// FileVersion: 4.5.7.1213
// (c) 2010 CodePlex Foundation
///
///
///
///
///
///
///
///
(function() {
var scriptName = "ExtendedListSearch";
function execute() {
Type.registerNamespace('Sys.Extended.UI');
Sys.Extended.UI.ListSearchBehavior = function(element) {
///
/// The ListSearchBehavior allows users to search incrementally within a Select
///
///
/// Select associated with the behavior
///
Sys.Extended.UI.ListSearchBehavior.initializeBase(this, [element]);
this._promptCssClass = null;
this._promptText = (Sys.Extended.UI.Resources && Sys.Extended.UI.Resources.ListSearch_DefaultPrompt) || "Type to search";
this._offsetX = 0;
this._offsetY = 0;
this._promptPosition = Sys.Extended.UI.ListSearchPromptPosition.Top;
this._raiseImmediateOnChange = false;
this._queryPattern = Sys.Extended.UI.ListSearchQueryPattern.StartsWith;
this._isSorted = false;
this._popupBehavior = null;
this._onShowJson = null;
this._onHideJson = null;
this._originalIndex = 0; // Index of the selected option when a key is first hit (before it is changed by the browser)
this._newIndex = -1; // New index to which we want to move. We need this because Firefox shifts the selected option even though we preventDefault and preventPropagation in _onKeyPress.
this._showingPromptText = false;
this._searchText = ''; // Actual search text (text displayed in the PromptDiv may be clipped)
this._ellipsis = String.fromCharCode(0x2026);
this._binarySearch = false;
this._applicationLoadDelegate = null;
this._focusIndex = 0; // Selected Index when the list is initially given focus
this._queryTimeout = 0; // Timeout in milliseconds after which search text will be cleared
this._timer = null; // Holds the opaque ID returned by setTimeout function. Needed to correctly clear the timeout reference.
this._matchFound = false; // Set to true means an item was selected after searching. False means no item match search criteria
this._focusHandler = null;
this._blurHandler = null;
this._keyDownHandler = null;
this._keyUpHandler = null;
this._keyPressHandler = null;
}
Sys.Extended.UI.ListSearchBehavior.prototype = {
initialize : function() {
///
/// Initialize the behavior
///
Sys.Extended.UI.ListSearchBehavior.callBaseMethod(this, 'initialize');
var element = this.get_element();
if(element && element.tagName === 'SELECT') {
this._focusHandler = Function.createDelegate(this, this._onFocus);
this._blurHandler = Function.createDelegate(this, this._onBlur);
this._keyDownHandler = Function.createDelegate(this, this._onKeyDown);
this._keyUpHandler = Function.createDelegate(this, this._onKeyUp);
this._keyPressHandler = Function.createDelegate(this, this._onKeyPress);
$addHandler(element, "focus", this._focusHandler);
$addHandler(element, "blur", this._blurHandler);
$addHandler(element, "keydown", this._keyDownHandler);
$addHandler(element, "keyup", this._keyUpHandler);
$addHandler(element, "keypress", this._keyPressHandler);
this._applicationLoadDelegate = Function.createDelegate(this, this._onApplicationLoad);
Sys.Application.add_load(this._applicationLoadDelegate);
}
},
dispose : function() {
///
/// Dispose the behavior
///
var element = this.get_element();
$removeHandler(element, "keypress", this._keyPressHandler);
$removeHandler(element, "keyup", this._keyUpHandler);
$removeHandler(element, "keydown", this._keyDownHandler);
$removeHandler(element, "blur", this._blurHandler);
$removeHandler(element, "focus", this._focusHandler);
this._onShowJson = null;
this._onHideJson = null;
this._disposePopupBehavior();
if(this._applicationLoadDelegate) {
Sys.Application.remove_load(this._applicationLoadDelegate);
this._applicationLoadDelegate = null;
}
if(this._timer) {
this._stopTimer();
}
Sys.Extended.UI.ListSearchBehavior.callBaseMethod(this, 'dispose');
},
_onApplicationLoad : function(sender, applicationLoadEventArgs) {
///
/// Handler called automatically when a all scripts are loaded and controls are initialized
/// Called after all scripts have been loaded and controls initialized. If the current Select is the one that has
/// focus then it shows the prompt text. We cannot do this in the initialize method because the second pass initialization
/// of the popup behavior hides it.
///
///
/// Sender
///
///
/// Event arguments
///
var hasInitialFocus = false;
var clientState = Sys.Extended.UI.ListSearchBehavior.callBaseMethod(this, 'get_ClientState');
if (clientState != null && clientState != "") {
hasInitialFocus = (clientState === "Focused");
Sys.Extended.UI.ListSearchBehavior.callBaseMethod(this, 'set_ClientState', null);
}
if(hasInitialFocus) {
this._handleFocus();
}
},
_checkIfSorted : function(options) {
///
/// Checks to see if the list is sorted to see if we can do the fast binary search or the slower linear search
///
///
/// Collections of options in a SELECT
///
if (this._isSorted) {
return true;
} else {
var previousOptionValue = null;
var optionsLength = options.length;
for(var index = 0; index < optionsLength; index++) {
var optionValue = options[index].text.toLowerCase();
if(previousOptionValue && this._compareStrings(optionValue, previousOptionValue) < 0) {
return false;
}
previousOptionValue = optionValue;
}
return true;
}
},
_onFocus : function(e) {
///
/// Handler for the Select's focus event
///
///
/// Event info
///
this._handleFocus();
},
_handleFocus : function() {
///
/// Utility method called when the form is loaded if the Select has the default focus, or when it is explicitly focused
///
var element = this.get_element();
this._focusIndex = element.selectedIndex;
if(!this._promptDiv) {
this._promptDiv = document.createElement('div');
this._promptDiv.id = element.id + '_promptDiv';
this._promptDiv.innerHTML = this._promptText && this._promptText.length > 0 ? this._promptText : Sys.Extended.UI.Resources.ListSearch_DefaultPrompt;
this._showingPromptText = true;
if(this._promptCssClass) {
this._promptDiv.className = this._promptCssClass;
}
element.parentNode.insertBefore(this._promptDiv, element.nextSibling);
this._promptDiv.style.overflow = 'hidden';
this._promptDiv.style.height = this._promptDiv.offsetHeight + 'px';
this._promptDiv.style.width = element.offsetWidth + 'px';
}
if(!this._popupBehavior) {
this._popupBehavior = $create(Sys.Extended.UI.PopupBehavior, { parentElement : element }, {}, {}, this._promptDiv);
}
if (this._promptPosition && this._promptPosition == Sys.Extended.UI.ListSearchPromptPosition.Bottom) {
this._popupBehavior.set_positioningMode(Sys.Extended.UI.PositioningMode.BottomLeft);
} else {
this._popupBehavior.set_positioningMode(Sys.Extended.UI.PositioningMode.TopLeft);
}
if (this._onShowJson) {
this._popupBehavior.set_onShow(this._onShowJson);
}
if (this._onHideJson) {
this._popupBehavior.set_onHide(this._onHideJson);
}
this._popupBehavior.show();
this._updatePromptDiv(this._promptText);
},
_onBlur : function() {
///
/// Handle the Select's blur event
///
this._disposePopupBehavior();
var promptDiv = this._promptDiv;
var element = this.get_element();
if(promptDiv) {
this._promptDiv = null;
promptDiv.parentNode.removeChild(promptDiv);
}
if(!this._raiseImmediateOnChange && this._focusIndex != element.selectedIndex) {
this._raiseOnChange(element);
}
},
_disposePopupBehavior : function() {
///
/// Utilty function to dispose of the popup behavior, called when the Select loses focus or when the extender is being disposed
///
if (this._popupBehavior) {
this._popupBehavior.dispose();
this._popupBehavior = null;
}
},
_onKeyDown : function(e) {
///
/// Handler for the Select's KeyDown event
///
///
/// Event info
///
var element = this.get_element();
var promptDiv = this._promptDiv;
if(!element || !promptDiv) {
return;
}
this._originalIndex = element.selectedIndex;
if(this._showingPromptText) {
promptDiv.innerHTML = '';
this._searchText = '';
this._showingPromptText = false;
this._binarySearch = this._checkIfSorted(element.options); // Delayed until required
}
if(e.keyCode == Sys.UI.Key.backspace) {
e.preventDefault();
e.stopPropagation();
this._removeCharacterFromPromptDiv();
this._searchForTypedText(element);
if(!this._searchText || this._searchText.length == 0) {
this._stopTimer();
}
} else if(e.keyCode == Sys.UI.Key.esc) {
e.preventDefault();
e.stopPropagation();
promptDiv.innerHTML = '';
this._searchText = '';
this._searchForTypedText(element);
this._stopTimer();
} else if(e.keyCode == Sys.UI.Key.enter && !this._raiseImmediateOnChange && this._focusIndex != element.selectedIndex) {
this._focusIndex = element.selectedIndex; // So that OnChange is not fired again when the list loses focus
this._raiseOnChange(element);
}
},
_onKeyUp : function(e) {
///
/// Handler for the Select's KeyUp event. We need this because Firefox shifts the selected option even though
/// we preventDefault and preventPropagation in _onKeyPress
///
///
/// Event info
///
var element = this.get_element();
var promptDiv = this._promptDiv;
if(!element || !promptDiv) {
return;
}
if(this._newIndex == -1 || !element || !promptDiv || promptDiv.innerHTML == '') {
this._newIndex = -1;
return;
}
element.selectedIndex = this._newIndex;
this._newIndex = -1;
},
_onKeyPress : function(e) {
///
/// Handler for the Select's KeyPress event.
///
///
/// Event info
///
var element = this.get_element();
var promptDiv = this._promptDiv;
if(!element || !promptDiv) {
return;
}
if(!this._isNormalChar(e)) {
if(e.charCode == Sys.UI.Key.backspace) {
e.preventDefault();
e.stopPropagation();
if(this._searchText && this._searchText.length == 0) {
this._stopTimer();
}
}
return;
}
e.preventDefault();
e.stopPropagation();
this._addCharacterToPromptDiv(e.charCode);
this._searchForTypedText(element);
this._stopTimer();
if(this._searchText && this._searchText.length != 0) {
this._startTimer();
}
},
_isNormalChar : function(e) {
///
/// Returns true if the specified charCode is a key rather than a normal (displayable) character
///
///
/// Event info
///
///
if (Sys.Browser.agent == Sys.Browser.Firefox && e.rawEvent.keyCode) {
return false;
}
if (Sys.Browser.agent == Sys.Browser.Opera && e.rawEvent.which == 0) {
return false;
}
if (e.charCode && (e.charCode < Sys.UI.Key.space || e.charCode > 6000)) {
return false;
}
return true;
},
_updatePromptDiv : function(newText) {
///
/// Updates the text in the promptDiv.
///
///
/// The new text to be displayed in the promptDiv. Optional. If not specified then uses the _searchText member.
///
///
var promptDiv = this._promptDiv;
if(!promptDiv || !this.get_element()) {
return;
}
var text = typeof(newText) === 'undefined' ? this._searchText : newText;
var textNode = promptDiv.firstChild;
if(!textNode) {
textNode = document.createTextNode(text);
promptDiv.appendChild(textNode);
} else {
textNode.nodeValue = text;
}
if(promptDiv.scrollWidth <= promptDiv.offsetWidth && promptDiv.scrollHeight <= promptDiv.offsetHeight) {
return; // Already fit
}
for(var maxFit = text.length - 1; maxFit > 0 && (promptDiv.scrollWidth > promptDiv.offsetWidth || promptDiv.scrollHeight > promptDiv.offsetHeight); maxFit--) {
textNode.nodeValue = this._ellipsis + text.substring(text.length - maxFit, text.length);
}
},
_addCharacterToPromptDiv : function (charCode) {
///
/// Adds the specified character to the promptDiv.
///
///
/// The charCode of the character to be added
///
this._searchText += String.fromCharCode(charCode);
this._updatePromptDiv();
},
_removeCharacterFromPromptDiv : function () {
///
/// Removes a character from the end of the promptDiv.
///
if(this._searchText && this._searchText != '') {
this._searchText = this._searchText.substring(0, this._searchText.length - 1);
this._updatePromptDiv();
}
},
_searchForTypedText : function(element) {
///
/// Searches for the text typed so far in the Select
///
///
/// Select associated with the behavior
///
var searchText = this._searchText;
var options = element.options;
var text = searchText ? searchText.toLowerCase() : "";
this._matchFound = false;
if(text.length == 0) { // Probably hit delete -- select the first option
if(options.length > 0) {
element.selectedIndex = 0;
this._newIndex = 0;
}
} else {
var selectedIndex = -1;
if(this._binarySearch && (this._queryPattern == Sys.Extended.UI.ListSearchQueryPattern.StartsWith)) {
selectedIndex = this._doBinarySearch(options, text, 0, options.length - 1);
} else {
selectedIndex = this._doLinearSearch(options, text, 0, options.length - 1);
}
if(selectedIndex == -1) {
this._newIndex = this._originalIndex;
} else { // Otherwise move to the new option
element.selectedIndex = selectedIndex;
this._newIndex = selectedIndex;
this._matchFound = true;
}
}
if(this._raiseImmediateOnChange && this._originalIndex != element.selectedIndex) {
this._raiseOnChange(element);
}
},
_raiseOnChange : function(element) {
///
/// Fires a OnChange event
///
///
/// Select associated with the behavior
///
if (document.createEvent) {
var onchangeEvent = document.createEvent('HTMLEvents');
onchangeEvent.initEvent('change', true, false);
element.dispatchEvent(onchangeEvent);
} else if( document.createEventObject ) {
element.fireEvent('onchange');
}
},
_compareStrings : function(strA, strB) {
///
/// Compare two strings
///
///
/// The first string
///
///
/// The second string
///
///
/// 0 if equal, -1 if strA < strB, 1 otherwise
///
return ((strA == strB) ? 0 : ((strA < strB) ? -1 : 1))
},
_doBinarySearch : function(options, value, left, right) {
///
/// Does a binary search for a value in the Select's options
///
///
/// The collection of options in the Select
///
///
/// The value being searched for
///
///
/// The left bounds of the search
///
///
/// The right bounds of the search
///
while (left <= right) {
var mid = Math.floor((left+right)/2);
var option = options[mid].text.toLowerCase().substring(0, value.length);
var compareResult = this._compareStrings(value, option);
if (compareResult > 0) {
left = mid+1
} else if(compareResult < 0) {
right = mid-1;
} else {
while(mid > 0 && options[mid - 1].text.toLowerCase().startsWith(value)) {
mid--;
}
return mid;
}
}
return -1;
},
_doLinearSearch : function(options, value, left, right) {
///
/// Does a linear search for a value in the Select's options
///
///
/// The collection of options in the Select
///
///
/// The value being searched for
///
///
/// The left bounds of the search
///
///
/// The right bounds of the search
///
if (this._queryPattern == Sys.Extended.UI.ListSearchQueryPattern.Contains) {
for(var i = left; i <= right; i++) {
if(options[i].text.toLowerCase().indexOf(value) >= 0) {
return i;
}
}
} else if (this._queryPattern == Sys.Extended.UI.ListSearchQueryPattern.StartsWith) {
for(var i = left; i <= right; i++) {
if(options[i].text.toLowerCase().startsWith(value)) {
return i;
}
}
}
return -1;
},
_onTimerTick : function() {
///
/// On timer tick since user is not responsive, so reset search text if no match is found.
///
this._stopTimer();
if (!this._matchFound) {
this._searchText = '';
this._updatePromptDiv();
}
},
_startTimer : function() {
///
/// Starts timer to monitor user interaction only if greater than zero.
///
if (this._queryTimeout > 0) {
this._timer = window.setTimeout(Function.createDelegate(this, this._onTimerTick), this._queryTimeout);
}
},
_stopTimer : function() {
///
/// Stops and clears previously created timer.
///
if(this._timer != null) {
window.clearTimeout(this._timer);
}
this._timer = null;
},
get_onShow : function() {
///
/// Generic OnShow Animation's JSON definition
///
return this._popupBehavior ? this._popupBehavior.get_onShow() : this._onShowJson;
},
set_onShow : function(value) {
if (this._popupBehavior) {
this._popupBehavior.set_onShow(value)
} else {
this._onShowJson = value;
}
this.raisePropertyChanged('onShow');
},
get_onShowBehavior : function() {
///
/// Generic OnShow Animation's behavior
///
return this._popupBehavior ? this._popupBehavior.get_onShowBehavior() : null;
},
onShow : function() {
///
/// Play the OnShow animation
///
///
if (this._popupBehavior) {
this._popupBehavior.onShow();
}
},
get_onHide : function() {
///
/// Generic OnHide Animation's JSON definition
///
return this._popupBehavior ? this._popupBehavior.get_onHide() : this._onHideJson;
},
set_onHide : function(value) {
if (this._popupBehavior) {
this._popupBehavior.set_onHide(value)
} else {
this._onHideJson = value;
}
this.raisePropertyChanged('onHide');
},
get_onHideBehavior : function() {
///
/// Generic OnHide Animation's behavior
///
return this._popupBehavior ? this._popupBehavior.get_onHideBehavior() : null;
},
onHide : function() {
///
/// Play the OnHide animation
///
///
if (this._popupBehavior) {
this._popupBehavior.onHide();
}
},
get_promptText : function() {
///
/// The prompt text displayed when user clicks the list
///
///
return this._promptText;
},
set_promptText : function(value) {
if (this._promptText != value) {
this._promptText = value;
this.raisePropertyChanged('promptText');
}
},
get_promptCssClass : function() {
///
/// CSS class applied to prompt when user clicks list.
///
///
return this._promptCssClass;
},
set_promptCssClass : function(value) {
if (this._promptCssClass != value) {
this._promptCssClass = value;
this.raisePropertyChanged('promptCssClass');
}
},
get_promptPosition : function() {
///
/// Where the prompt should be positioned relative to the target control.
/// Can be Top (default) or Bottom
///
return this._promptPosition;
},
set_promptPosition : function(value) {
if (this._promptPosition != value) {
this._promptPosition = value;
this.raisePropertyChanged('promptPosition');
}
},
get_raiseImmediateOnChange : function() {
///
/// Boolean indicating whether an OnChange event should be fired as soon as the selected element
/// is changed, or only when the list loses focus or the user hits enter.
///
///
return this._raiseImmediateOnChange;
},
set_raiseImmediateOnChange : function(value) {
if (this._raiseImmediateOnChange != value) {
this._raiseImmediateOnChange = value;
this.raisePropertyChanged('raiseImmediateOnChange');
}
},
get_queryTimeout : function() {
///
/// Value indicating timeout in milliseconds after which search query will be cleared.
/// Zero means no auto reset at all.
///
///
return this._queryTimeout;
},
set_queryTimeout : function(value) {
if (this._queryTimeout != value) {
this._queryTimeout = value;
this.raisePropertyChanged('queryTimeout');
}
},
get_isSorted : function() {
///
/// When setting this to true, we instruct search routines that
/// all values in List are already sorted on population,
/// so binary search can be used if on StartsWith criteria is set.
///
return this._isSorted;
},
set_isSorted : function(value) {
if (this._isSorted != value) {
this._isSorted = value;
this.raisePropertyChanged('isSorted');
}
},
get_queryPattern : function() {
///
/// Search query pattern to be used to find items.
/// Can be StartsWith (default) or Contains
///
return this._queryPattern;
},
set_queryPattern : function(value) {
if (this._queryPattern != value) {
this._queryPattern = value;
this.raisePropertyChanged('queryPattern');
}
}
}
Sys.Extended.UI.ListSearchBehavior.registerClass('Sys.Extended.UI.ListSearchBehavior', Sys.Extended.UI.BehaviorBase);
Sys.registerComponent(Sys.Extended.UI.ListSearchBehavior, { name: "listSearch" });
Sys.Extended.UI.ListSearchPromptPosition = function() {
throw Error.invalidOperation();
}
Sys.Extended.UI.ListSearchPromptPosition.prototype = {
Top: 0,
Bottom: 1
}
Sys.Extended.UI.ListSearchPromptPosition.registerEnum('Sys.Extended.UI.ListSearchPromptPosition');
Sys.Extended.UI.ListSearchQueryPattern = function() {
///
/// Choose what query pattern to use to search for matching words.
///
///
///
throw Error.invalidOperation();
}
Sys.Extended.UI.ListSearchQueryPattern.prototype = {
StartsWith: 0,
Contains: 1
}
Sys.Extended.UI.ListSearchQueryPattern.registerEnum('Sys.Extended.UI.ListSearchQueryPattern');
} // execute
if (window.Sys && Sys.loader) {
Sys.loader.registerScript(scriptName, ["ExtendedPopupBehavior"], execute);
}
else {
execute();
}
})();