// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.
// This behavior can be attached to a textbox to enable auto-complete/auto-suggest
// scenarios.
Microsoft.Mtps.Rendering.Behaviors.Ajax.AutoCompleteBehavior = function(element) {
/// The DOM element the behavior is associated with.
Microsoft.Mtps.Rendering.Behaviors.Ajax.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._timer = null;
this._cache = null;
this._currentPrefix = null;
this._selectIndex = -1;
this._focusHandler = null;
this._blurHandler = null;
this._keyDownHandler = null;
this._mouseDownHandler = null;
this._mouseUpHandler = null;
this._mouseOverHandler = null;
this._tickHandler = null;
this._enableCaching = true;
}
Microsoft.Mtps.Rendering.Behaviors.Ajax.AutoCompleteBehavior.prototype = {
initialize: function() {
Microsoft.Mtps.Rendering.Behaviors.Ajax.AutoCompleteBehavior.callBaseMethod(this, 'initialize');
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._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(Microsoft.Mtps.Rendering.Behaviors.Ajax.PopupBehavior,
{ 'id':this.get_id()+'PopupBehavior', 'parentElement':element, "positioningMode": Microsoft.Mtps.Rendering.Behaviors.Ajax.PositioningMode.BottomLeft }, null, null, this._completionListElement);
},
get_completionInterval: function() {
/// Auto completion timer interval in milliseconds.
return this._completionInterval;
},
set_completionInterval: function(value) {
this._completionInterval = value;
},
get_completionList: function() {
/// List dom element.
return this._completionListElement;
},
set_completionList: function(value) {
this._completionListElement = value;
},
get_completionSetCount: function() {
/// Maximum completion set size.
return this._completionSetCount;
},
set_completionSetCount: function(value) {
this._completionSetCount = value;
},
get_minimumPrefixLength: function() {
/// Minimum text prefix length required to perform behavior.
return this._minimumPrefixLength;
},
set_minimumPrefixLength: function(value) {
this._minimumPrefixLength = value;
},
get_serviceMethod: function() {
/// Web service method.
return this._serviceMethod;
},
set_serviceMethod: function(value) {
this._serviceMethod = value;
},
get_servicePath: function() {
/// Web service url.
return this._servicePath;
},
set_servicePath: function(value) {
this._servicePath = value;
},
get_enableCaching: function() {
/// Get or sets whether suggestions retrieved from the webservice should be cached.
return this._enableCaching;
},
set_enableCaching: function(value) {
this._enableCaching = value;
},
get_completionListElementID: function(){
/// DOM element.
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._setText(ev.target.firstChild.nodeValue);
}
},
_onListMouseUp: function(ev) {
this.get_element().focus();
},
_onListMouseOver: function(ev) {
var item = ev.target;
this._selectIndex = -1;
this._highlightItem(item);
},
_onGotFocus: function(ev) {
this._timer.set_enabled(true);
},
_onKeyDown: function(ev) {
var k = ev.keyCode ? ev.keyCode : ev.rawEvent.keyCode;
if (k === Sys.UI.Key.esc) {
this._hideCompletionList();
ev.preventDefault();
}
else if (k === Sys.UI.Key.up) {
if (this._selectIndex > 0) {
this._selectIndex--;
this._highlightItem(this._completionListElement.childNodes[this._selectIndex]);
ev.preventDefault();
}
}
else if (k === Sys.UI.Key.down) {
if (this._selectIndex < (this._completionListElement.childNodes.length - 1)) {
this._selectIndex++;
this._highlightItem(this._completionListElement.childNodes[this._selectIndex]);
ev.preventDefault();
}
}
else if (k === Sys.UI.Key.enter) {
if (this._selectIndex !== -1) {
this._setText(this._completionListElement.childNodes[this._selectIndex].firstChild.nodeValue);
ev.preventDefault();
}
}
else if (k === Sys.UI.Key.tab) {
if (this._selectIndex !== -1) {
this._setText(this._completionListElement.childNodes[this._selectIndex].firstChild.nodeValue);
}
}
else {
this._timer.set_enabled(true);
}
},
_onLostFocus: function() {
this._timer.set_enabled(false);
this._hideCompletionList();
},
_onMethodComplete: function(result, context, methodName) {
this._update(context, result, /* cacheResults */ true);
},
_onMethodFailed: function(err, response, context) {
// no op
},
_onTimerTick: function(sender, eventArgs) {
if (this._servicePath && this._serviceMethod) {
var text = this.get_element().value;
if (text.trim().length < this._minimumPrefixLength) {
this._currentPrefix = null;
this._update('', null, /* cacheResults */ false);
return;
}
if (this._currentPrefix !== text) {
this._currentPrefix = text;
if (this._cache && this._cache[text]) {
this._update(text, this._cache[text], /* cacheResults */ false);
return;
}
Sys.Net.WebServiceProxy.invoke(this.get_servicePath(), this.get_serviceMethod(), false,
{ prefixText : this._currentPrefix, count: this._completionSetCount },
Function.createDelegate(this, this._onMethodComplete),
Function.createDelegate(this, this._onMethodFailed),
text);
}
}
},
_setText: function(text) {
this._timer.set_enabled(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;
}
this._hideCompletionList();
},
_update: function(prefixText, completionItems, cacheResults) {
if (cacheResults && this.get_enableCaching()) {
if (!this._cache) {
this._cache = {};
}
this._cache[prefixText] = completionItems;
}
this._completionListElement.innerHTML = '';
this._selectIndex = -1;
if (completionItems && completionItems.length) {
for (var i = 0; i < completionItems.length; i++) {
var itemElement = document.createElement('div');
itemElement.appendChild(document.createTextNode(completionItems[i]));
itemElement.__item = '';
var itemElementStyle = itemElement.style;
itemElementStyle.padding = '1px';
itemElementStyle.textAlign = 'left';
itemElementStyle.textOverflow = 'ellipsis';
itemElementStyle.backgroundColor = this._textBackground;
itemElementStyle.color = this._textColor;
this._completionListElement.appendChild(itemElement);
}
var elementBounds = CommonToolkitScripts.getBounds(this.get_element());
this._completionListElement.style.width = Math.max(1, elementBounds.width - 2) + 'px';
this._popupBehavior.show();
}
else {
this._popupBehavior.hide();
}
}
}
Microsoft.Mtps.Rendering.Behaviors.Ajax.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} ]
}
Microsoft.Mtps.Rendering.Behaviors.Ajax.AutoCompleteBehavior.registerClass('Microsoft.Mtps.Rendering.Behaviors.Ajax.AutoCompleteBehavior', Microsoft.Mtps.Rendering.Behaviors.Ajax.BehaviorBase);
if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();