summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwei <>2006-06-17 10:28:26 +0000
committerwei <>2006-06-17 10:28:26 +0000
commit3a30ede1c03fdd097398b14734822f7ce8e46b6b (patch)
tree516537f9872da97f25a2187cf1880df65804014c
parent649082a9eb89991189fafce8432c4fd266fac027 (diff)
Update TAutoComplete, OnSuggest event for getting suggestions.
-rw-r--r--framework/Web/Javascripts/extended/base.js141
-rw-r--r--framework/Web/Javascripts/js/ajax.js16
-rw-r--r--framework/Web/Javascripts/js/prado.js10
-rw-r--r--framework/Web/Javascripts/prado/activecontrols3.js65
-rw-r--r--framework/Web/Javascripts/prado/controls.js4
-rw-r--r--framework/Web/UI/ActiveControls/TAutoComplete.php159
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.page2
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.page1
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.page28
-rw-r--r--tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.php8
-rw-r--r--tests/FunctionalTests/active-controls/tests/AutoCompleteTestCase.php21
11 files changed, 379 insertions, 76 deletions
diff --git a/framework/Web/Javascripts/extended/base.js b/framework/Web/Javascripts/extended/base.js
index d7fabdd0..d88f82db 100644
--- a/framework/Web/Javascripts/extended/base.js
+++ b/framework/Web/Javascripts/extended/base.js
@@ -29,15 +29,22 @@ Class.extend = function(base, definition)
}
/*
- Base, version 1.0.1
+ Base, version 1.0.2
Copyright 2006, Dean Edwards
License: http://creativecommons.org/licenses/LGPL/2.1/
*/
-
-function Base() {
+/*
+var Base = function() {
+ if (arguments.length) {
+ if (this == window) { // cast an object to this class
+ Base.prototype.extend.call(arguments[0], arguments.callee.prototype);
+ } else {
+ this.extend(arguments[0]);
+ }
+ }
};
-Base.version = "1.0.1";
+Base.version = "1.0.2";
Base.prototype = {
extend: function(source, value) {
@@ -46,13 +53,16 @@ Base.prototype = {
var ancestor = this[source];
// overriding?
if ((ancestor instanceof Function) && (value instanceof Function) &&
- ancestor.valueOf() != value.valueOf() && /\binherit\b/.test(value)) {
+ ancestor.valueOf() != value.valueOf() && /\bbase\b/.test(value)) {
var method = value;
+ // var _prototype = this.constructor.prototype;
+ // var fromPrototype = !Base._prototyping && _prototype[source] == ancestor;
value = function() {
- var previous = this.inherit;
- this.inherit = ancestor;
+ var previous = this.base;
+ // this.base = fromPrototype ? _prototype[source] : ancestor;
+ this.base = ancestor;
var returnValue = method.apply(this, arguments);
- this.inherit = previous;
+ this.base = previous;
return returnValue;
};
// point to the underlying method
@@ -85,18 +95,14 @@ Base.prototype = {
return this;
},
- inherit: function() {
+ base: function() {
// call this method from any other method to invoke that method's ancestor
}
};
-Base.extend = function(_instance, _static) {
+Base.extend = function(_instance, _static) {
var extend = Base.prototype.extend;
if (!_instance) _instance = {};
- // create the constructor
- if (_instance.constructor == Object) {
- _instance.constructor = new Function;
- }
// build the prototype
Base._prototyping = true;
var _prototype = new this;
@@ -112,13 +118,116 @@ Base.extend = function(_instance, _static) {
klass.prototype = _prototype;
// build the class interface
klass.extend = this.extend;
+ klass.implement = this.implement;
klass.toString = function() {
return String(constructor);
};
extend.call(klass, _static);
- // support singletons
+ // single instance
var object = constructor ? klass : _prototype;
// class initialisation
if (object.init instanceof Function) object.init();
return object;
-}; \ No newline at end of file
+};
+
+Base.implement = function(_interface) {
+ if (_interface instanceof Function) _interface = _interface.prototype;
+ this.prototype.extend(_interface);
+};
+*/
+
+/*
+ * Signals and Slots for Prototype: Easy custom javascript events
+ * http://tetlaw.id.au/view/blog/signals-and-slots-for-prototype-easy-custom-javascript-events
+ * Andrew Tetlaw
+ * Version 1 (2006-05-03)
+ *
+ * http://creativecommons.org/licenses/by-sa/2.5/
+ */
+Signal = {
+ throwErrors : true,
+ MT : function(){ return true },
+ connect : function(obj1, func1, obj2, func2, options) {
+ var options = Object.extend({
+ connectOnce : false
+ }, options || {});
+ if(typeof func1 != 'string' || typeof func2 != 'string') return;
+
+ var sigObj = obj1 || window;
+ var slotObj = obj2 || window;
+ var signame = func1+'__signal_';
+ var slotsname = func1+'__slots_';
+ if(!sigObj[signame]) {
+ // having the slotFunc in a var and setting it by using an anonymous function in this way
+ // is apparently a good way to prevent memory leaks in IE if the objects are DOM nodes.
+ var slotFunc = function() {
+ var args = arguments;
+ var result = sigObj[signame].apply(sigObj,args);
+ sigObj[slotsname].each(function(slot){
+ try {
+ if(slot && slot[0]) { // testing for null, a disconnect may have nulled this slot
+ slot[0][slot[1]].apply(slot[0],args); //[0] = obj, [1] = func name
+ }
+ } catch(e) {
+ if(Signal.throwErrors) throw e;
+ }
+ });
+ return result;
+ };
+ (function() {
+ sigObj[slotsname] = $A([]);
+ sigObj[signame] = sigObj[func1] || Signal.MT;
+ sigObj[func1] = slotFunc;
+ })();
+ }
+ var con = (sigObj[slotsname].length > 0) ?
+ (options.connectOnce ? !sigObj[slotsname].any(function(slot) { return (slot[0] == slotObj && slot[1] == func2) }) : true) :
+ true;
+ if(con) {
+ sigObj[slotsname].push([slotObj,func2]);
+ }
+ },
+ connectOnce : function(obj1, func1, obj2, func2, options) {
+ Signal.connect(obj1, func1, obj2, func2, Object.extend(options || {}, {connectOnce : true}))
+ },
+ disconnect : function(obj1, func1, obj2, func2, options) {
+ var options = Object.extend({
+ disconnectAll : false
+ }, options || {});
+ if(typeof func1 != 'string' || typeof func2 != 'string') return;
+
+ var sigObj = obj1 || window;
+ var slotObj = obj2 || window;
+ var signame = func1+'__signal_';
+ var slotsname = func1+'__slots_';
+
+ // I null them in this way so that any currectly active signal will read a null slot,
+ // otherwise the slot will be applied even though it's been disconnected
+ if(sigObj[slotsname]) {
+ if(options.disconnectAll) {
+ sigObj[slotsname] = sigObj[slotsname].collect(function(slot) {
+ if(slot[0] == slotObj && slot[1] == func2) {
+ slot[0] = null;
+ return null;
+ } else {
+ return slot;
+ }
+ }).compact();
+ } else {
+ var idx = -1;
+ sigObj[slotsname] = sigObj[slotsname].collect(function(slot, index) {
+ if(slot[0] == slotObj && slot[1] == func2 && idx < 0) { //disconnect first match
+ idx = index;
+ slot[0] = null;
+ return null;
+ } else {
+ return slot;
+ }
+ }).compact();
+ }
+ }
+ },
+ disconnectAll : function(obj1, func1, obj2, func2, options) {
+ Signal.disconnect(obj1, func1, obj2, func2, Object.extend(options || {}, {disconnectAll : true}))
+ }
+}
diff --git a/framework/Web/Javascripts/js/ajax.js b/framework/Web/Javascripts/js/ajax.js
index bfc080e9..fea7e573 100644
--- a/framework/Web/Javascripts/js/ajax.js
+++ b/framework/Web/Javascripts/js/ajax.js
@@ -189,11 +189,15 @@ if(this.saving)return;this.effect=new Effect.Highlight(this.element,{startcolor:
this.editing=false;this.saving=false;this.oldInnerHTML=null;this.onLeaveEditMode();},onComplete:function(transport){this.leaveEditMode();this.options.onComplete.bind(this)(transport,this.element);},onEnterEditMode:function(){},onLeaveEditMode:function(){},dispose:function(){if(this.oldInnerHTML){this.element.innerHTML=this.oldInnerHTML;}
this.leaveEditMode();Event.stopObserving(this.element,'click',this.onclickListener);Event.stopObserving(this.element,'mouseover',this.mouseoverListener);Event.stopObserving(this.element,'mouseout',this.mouseoutListener);if(this.options.externalControl){Event.stopObserving(this.options.externalControl,'click',this.onclickListener);Event.stopObserving(this.options.externalControl,'mouseover',this.mouseoverListener);Event.stopObserving(this.options.externalControl,'mouseout',this.mouseoutListener);}}};Ajax.InPlaceCollectionEditor=Class.create();Object.extend(Ajax.InPlaceCollectionEditor.prototype,Ajax.InPlaceEditor.prototype);Object.extend(Ajax.InPlaceCollectionEditor.prototype,{createEditField:function(){if(!this.cached_selectTag){var selectTag=document.createElement("select");var collection=this.options.collection||[];var optionTag;collection.each(function(e,i){optionTag=document.createElement("option");optionTag.value=(e instanceof Array)?e[0]:e;if(this.options.value==optionTag.value)optionTag.selected=true;optionTag.appendChild(document.createTextNode((e instanceof Array)?e[1]:e));selectTag.appendChild(optionTag);}.bind(this));this.cached_selectTag=selectTag;}
this.editField=this.cached_selectTag;if(this.options.loadTextURL)this.loadExternalText();this.form.appendChild(this.editField);this.options.callback=function(form,value){return"value="+encodeURIComponent(value);}}});Form.Element.DelayedObserver=Class.create();Form.Element.DelayedObserver.prototype={initialize:function(element,delay,callback){this.delay=delay||0.5;this.element=$(element);this.callback=callback;this.timer=null;this.lastValue=$F(this.element);Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));},delayedListener:function(event){if(this.lastValue==$F(this.element))return;if(this.timer)clearTimeout(this.timer);this.timer=setTimeout(this.onTimerEvent.bind(this),this.delay*1000);this.lastValue=$F(this.element);},onTimerEvent:function(){this.timer=null;this.callback(this.element,$F(this.element));}};Prado.WebUI.CallbackControl=Class.extend(Prado.WebUI.PostBackControl,{onPostBack:function(event,options)
-{new Prado.CallbackRequest(options.EventTarget,options);Event.stop(event);}});Prado.WebUI.TActiveButton=Class.extend(Prado.WebUI.CallbackControl);Prado.WebUI.TAutoComplete=Class.extend(Autocompleter.Base,{initialize:function(options)
-{this.options=options;this.baseInitialize(options.ID,options.ResultPanel,options);Object.extend(this.options,{onSuccess:this.onComplete.bind(this)});},getUpdatedChoices:function()
-{Prado.Callback(this.options.EventTarget,this.getToken(),null,this.options);},onComplete:function(request,boundary)
-{result=Prado.Element.extractContent(request.responseText,boundary);if(typeof(result)=="string"&&result.length>0)
-this.updateChoices(result);}});Prado.WebUI.TActiveTextBox=Class.extend(Prado.WebUI.TTextBox,{onInit:function(options)
+{new Prado.CallbackRequest(options.EventTarget,options);Event.stop(event);}});Prado.WebUI.TActiveButton=Class.extend(Prado.WebUI.CallbackControl);Prado.WebUI.TActiveTextBox=Class.extend(Prado.WebUI.TTextBox,{onInit:function(options)
{if(options['TextMode']!='MultiLine')
Event.observe(this.element,"keydown",this.handleReturnKey.bind(this));Event.observe(this.element,"change",this.doCallback.bindEvent(this,options));},doCallback:function(event,options)
-{new Prado.CallbackRequest(options.EventTarget,options);Event.stop(event);}}); \ No newline at end of file
+{new Prado.CallbackRequest(options.EventTarget,options);Event.stop(event);}});Prado.WebUI.TAutoComplete=Class.extend(Autocompleter.Base,Prado.WebUI.TActiveTextBox.prototype);Prado.WebUI.TAutoComplete=Class.extend(Prado.WebUI.TAutoComplete,{initialize:function(options)
+{this.options=options;this.baseInitialize(options.ID,options.ResultPanel,options);Object.extend(this.options,{onSuccess:this.onComplete.bind(this)});if(options.AutoPostBack)
+this.onInit(options);},doCallback:function(event,options)
+{if(!this.active)
+{new Prado.CallbackRequest(options.EventTarget,options);Event.stop(event);}},onClick:function(event)
+{var element=Event.findElement(event,'LI');this.index=element.autocompleteIndex;this.selectEntry();this.hide();Event.fireEvent(this.element,"change");},getUpdatedChoices:function()
+{options=new Array(this.getToken(),"__TAutComplete_onSuggest__");Prado.Callback(this.options.EventTarget,options,null,this.options);},onComplete:function(request,boundary)
+{result=Prado.Element.extractContent(request.responseText,boundary);if(typeof(result)=="string"&&result.length>0)
+this.updateChoices(result);}}); \ No newline at end of file
diff --git a/framework/Web/Javascripts/js/prado.js b/framework/Web/Javascripts/js/prado.js
index d0744649..38160fee 100644
--- a/framework/Web/Javascripts/js/prado.js
+++ b/framework/Web/Javascripts/js/prado.js
@@ -15,11 +15,9 @@ Function.prototype.bindEvent=function()
Class.extend=function(base,definition)
{var component=Class.create();Object.extend(component.prototype,base.prototype);if(definition)
Object.extend(component.prototype,definition);return component;}
-function Base(){};Base.version="1.0.1";Base.prototype={extend:function(source,value){var extend=Base.prototype.extend;if(arguments.length==2){var ancestor=this[source];if((ancestor instanceof Function)&&(value instanceof Function)&&ancestor.valueOf()!=value.valueOf()&&/\binherit\b/.test(value)){var method=value;value=function(){var previous=this.inherit;this.inherit=ancestor;var returnValue=method.apply(this,arguments);this.inherit=previous;return returnValue;};value.valueOf=function(){return method;};value.toString=function(){return String(method);};}
-return this[source]=value;}else if(source){var _prototype={toSource:null};var _protected=["toString","valueOf"];if(Base._prototyping)_protected[2]="constructor";for(var i=0;(name=_protected[i]);i++){if(source[name]!=_prototype[name]){extend.call(this,name,source[name]);}}
-for(var name in source){if(!_prototype[name]){extend.call(this,name,source[name]);}}}
-return this;},inherit:function(){}};Base.extend=function(_instance,_static){var extend=Base.prototype.extend;if(!_instance)_instance={};if(_instance.constructor==Object){_instance.constructor=new Function;}
-Base._prototyping=true;var _prototype=new this;extend.call(_prototype,_instance);var constructor=_prototype.constructor;_prototype.constructor=this;delete Base._prototyping;var klass=function(){if(!Base._prototyping)constructor.apply(this,arguments);this.constructor=klass;};klass.prototype=_prototype;klass.extend=this.extend;klass.toString=function(){return String(constructor);};extend.call(klass,_static);var object=constructor?klass:_prototype;if(object.init instanceof Function)object.init();return object;};Object.extend(String.prototype,{gsub:function(pattern,replacement){var result='',source=this,match;replacement=arguments.callee.prepareReplacement(replacement);while(source.length>0){if(match=source.match(pattern)){result+=source.slice(0,match.index);result+=(replacement(match)||'').toString();source=source.slice(match.index+match[0].length);}else{result+=source,source='';}}
+Signal={throwErrors:true,MT:function(){return true},connect:function(obj1,func1,obj2,func2,options){var options=Object.extend({connectOnce:false},options||{});if(typeof func1!='string'||typeof func2!='string')return;var sigObj=obj1||window;var slotObj=obj2||window;var signame=func1+'__signal_';var slotsname=func1+'__slots_';if(!sigObj[signame]){var slotFunc=function(){var args=arguments;var result=sigObj[signame].apply(sigObj,args);sigObj[slotsname].each(function(slot){try{if(slot&&slot[0]){slot[0][slot[1]].apply(slot[0],args);}}catch(e){if(Signal.throwErrors)throw e;}});return result;};(function(){sigObj[slotsname]=$A([]);sigObj[signame]=sigObj[func1]||Signal.MT;sigObj[func1]=slotFunc;})();}
+var con=(sigObj[slotsname].length>0)?(options.connectOnce?!sigObj[slotsname].any(function(slot){return(slot[0]==slotObj&&slot[1]==func2)}):true):true;if(con){sigObj[slotsname].push([slotObj,func2]);}},connectOnce:function(obj1,func1,obj2,func2,options){Signal.connect(obj1,func1,obj2,func2,Object.extend(options||{},{connectOnce:true}))},disconnect:function(obj1,func1,obj2,func2,options){var options=Object.extend({disconnectAll:false},options||{});if(typeof func1!='string'||typeof func2!='string')return;var sigObj=obj1||window;var slotObj=obj2||window;var signame=func1+'__signal_';var slotsname=func1+'__slots_';if(sigObj[slotsname]){if(options.disconnectAll){sigObj[slotsname]=sigObj[slotsname].collect(function(slot){if(slot[0]==slotObj&&slot[1]==func2){slot[0]=null;return null;}else{return slot;}}).compact();}else{var idx=-1;sigObj[slotsname]=sigObj[slotsname].collect(function(slot,index){if(slot[0]==slotObj&&slot[1]==func2&&idx<0){idx=index;slot[0]=null;return null;}else{return slot;}}).compact();}}},disconnectAll:function(obj1,func1,obj2,func2,options){Signal.disconnect(obj1,func1,obj2,func2,Object.extend(options||{},{disconnectAll:true}))}}
+Object.extend(String.prototype,{gsub:function(pattern,replacement){var result='',source=this,match;replacement=arguments.callee.prepareReplacement(replacement);while(source.length>0){if(match=source.match(pattern)){result+=source.slice(0,match.index);result+=(replacement(match)||'').toString();source=source.slice(match.index+match[0].length);}else{result+=source,source='';}}
return result;},sub:function(pattern,replacement,count){replacement=this.gsub.prepareReplacement(replacement);count=count===undefined?1:count;return this.gsub(pattern,function(match){if(--count<0)return match[0];return replacement(match);});},scan:function(pattern,iterator){this.gsub(pattern,iterator);return this;},truncate:function(length,truncation){length=length||30;truncation=truncation===undefined?'...':truncation;return this.length>length?this.slice(0,length-truncation.length)+truncation:this;},strip:function(){return this.replace(/^\s+/,'').replace(/\s+$/,'');},stripTags:function(){return this.replace(/<\/?[^>]+>/gi,'');},stripScripts:function(){return this.replace(new RegExp(Prototype.ScriptFragment,'img'),'');},extractScripts:function(){var matchAll=new RegExp(Prototype.ScriptFragment,'img');var matchOne=new RegExp(Prototype.ScriptFragment,'im');return(this.match(matchAll)||[]).map(function(scriptTag){return(scriptTag.match(matchOne)||['',''])[1];});},evalScripts:function(){return this.extractScripts().map(function(script){return eval(script)});},escapeHTML:function(){var div=document.createElement('div');var text=document.createTextNode(this);div.appendChild(text);return div.innerHTML;},unescapeHTML:function(){var div=document.createElement('div');div.innerHTML=this.stripTags();return div.childNodes[0]?div.childNodes[0].nodeValue:'';},toQueryParams:function(){var pairs=this.match(/^\??(.*)$/)[1].split('&');return pairs.inject({},function(params,pairString){var pair=pairString.split('=');params[pair[0]]=pair[1];return params;});},toArray:function(){return this.split('');},camelize:function(){var oStringList=this.split('-');if(oStringList.length==1)return oStringList[0];var camelizedString=this.indexOf('-')==0?oStringList[0].charAt(0).toUpperCase()+oStringList[0].substring(1):oStringList[0];for(var i=1,len=oStringList.length;i<len;i++){var s=oStringList[i];camelizedString+=s.charAt(0).toUpperCase()+s.substring(1);}
return camelizedString;},inspect:function(){return"'"+this.replace(/\\/g,'\\\\').replace(/'/g,'\\\'')+"'";}});String.prototype.gsub.prepareReplacement=function(replacement){if(typeof replacement=='function')return replacement;var template=new Template(replacement);return function(match){return template.evaluate(match)};}
String.prototype.parseQuery=String.prototype.toQueryParams;var Template=Class.create();Template.Pattern=/(^|.|\r|\n)(#\{(.*?)\})/;Template.prototype={initialize:function(template,pattern){this.template=template.toString();this.pattern=pattern||Template.Pattern;},evaluate:function(object){return this.template.gsub(this.pattern,function(match){var before=match[1];if(before=='\\')return match[2];return before+(object[match[3]]||'').toString();});}}
@@ -306,7 +304,7 @@ elements[i].checked=true;}},checkClear:function(name)
this.onInit(options);},onInit:function(options)
{if(typeof(this.element.onclick)=="function")
{this._elementOnClick=this.element.onclick;this.element.onclick=null;}
-Event.observe(this.element,"click",this.onClick.bindEvent(this,options));},onClick:function(event,options)
+Event.observe(this.element,"click",this.elementClicked.bindEvent(this,options));},elementClicked:function(event,options)
{var src=Event.element(event);var doPostBack=true;var onclicked=null;if(this._elementOnClick)
{var onclicked=this._elementOnClick(event);if(typeof(onclicked)=="boolean")
doPostBack=onclicked;}
diff --git a/framework/Web/Javascripts/prado/activecontrols3.js b/framework/Web/Javascripts/prado/activecontrols3.js
index 6fbde405..c0964dcb 100644
--- a/framework/Web/Javascripts/prado/activecontrols3.js
+++ b/framework/Web/Javascripts/prado/activecontrols3.js
@@ -16,9 +16,29 @@ Prado.WebUI.CallbackControl = Class.extend(Prado.WebUI.PostBackControl,
Prado.WebUI.TActiveButton = Class.extend(Prado.WebUI.CallbackControl);
/**
+ * TActiveTextBox control, handles onchange event.
+ */
+Prado.WebUI.TActiveTextBox = Class.extend(Prado.WebUI.TTextBox,
+{
+ onInit : function(options)
+ {
+ if(options['TextMode'] != 'MultiLine')
+ Event.observe(this.element, "keydown", this.handleReturnKey.bind(this));
+ Event.observe(this.element, "change", this.doCallback.bindEvent(this,options));
+ },
+
+ doCallback : function(event, options)
+ {
+ new Prado.CallbackRequest(options.EventTarget, options);
+ Event.stop(event);
+ }
+});
+
+/**
* TAutoComplete control.
*/
-Prado.WebUI.TAutoComplete = Class.extend(Autocompleter.Base,
+Prado.WebUI.TAutoComplete = Class.extend(Autocompleter.Base, Prado.WebUI.TActiveTextBox.prototype);
+Prado.WebUI.TAutoComplete = Class.extend(Prado.WebUI.TAutoComplete,
{
initialize : function(options)
{
@@ -28,11 +48,34 @@ Prado.WebUI.TAutoComplete = Class.extend(Autocompleter.Base,
{
onSuccess : this.onComplete.bind(this)
});
+
+ if(options.AutoPostBack)
+ this.onInit(options);
+ },
+
+ doCallback : function(event, options)
+ {
+ if(!this.active)
+ {
+ new Prado.CallbackRequest(options.EventTarget, options);
+ Event.stop(event);
+ }
+ },
+
+ //Overrides parent implementation, fires onchange event.
+ onClick: function(event)
+ {
+ var element = Event.findElement(event, 'LI');
+ this.index = element.autocompleteIndex;
+ this.selectEntry();
+ this.hide();
+ Event.fireEvent(this.element, "change");
},
getUpdatedChoices : function()
{
- Prado.Callback(this.options.EventTarget, this.getToken(), null, this.options);
+ options = new Array(this.getToken(),"__TAutComplete_onSuggest__");
+ Prado.Callback(this.options.EventTarget, options, null, this.options);
},
onComplete : function(request, boundary)
@@ -41,20 +84,4 @@ Prado.WebUI.TAutoComplete = Class.extend(Autocompleter.Base,
if(typeof(result) == "string" && result.length > 0)
this.updateChoices(result);
}
-});
-
-Prado.WebUI.TActiveTextBox = Class.extend(Prado.WebUI.TTextBox,
-{
- onInit : function(options)
- {
- if(options['TextMode'] != 'MultiLine')
- Event.observe(this.element, "keydown", this.handleReturnKey.bind(this));
- Event.observe(this.element, "change", this.doCallback.bindEvent(this,options));
- },
-
- doCallback : function(event, options)
- {
- new Prado.CallbackRequest(options.EventTarget, options);
- Event.stop(event);
- }
-});
+}); \ No newline at end of file
diff --git a/framework/Web/Javascripts/prado/controls.js b/framework/Web/Javascripts/prado/controls.js
index 2497c586..285374a3 100644
--- a/framework/Web/Javascripts/prado/controls.js
+++ b/framework/Web/Javascripts/prado/controls.js
@@ -53,10 +53,10 @@ Prado.WebUI.PostBackControl.prototype =
this._elementOnClick = this.element.onclick;
this.element.onclick = null;
}
- Event.observe(this.element, "click", this.onClick.bindEvent(this,options));
+ Event.observe(this.element, "click", this.elementClicked.bindEvent(this,options));
},
- onClick : function(event, options)
+ elementClicked : function(event, options)
{
var src = Event.element(event);
var doPostBack = true;
diff --git a/framework/Web/UI/ActiveControls/TAutoComplete.php b/framework/Web/UI/ActiveControls/TAutoComplete.php
index 601894ff..55ffde04 100644
--- a/framework/Web/UI/ActiveControls/TAutoComplete.php
+++ b/framework/Web/UI/ActiveControls/TAutoComplete.php
@@ -1,77 +1,171 @@
<?php
-/*
- * Created on 7/05/2006
+/**
+ * TAutoComplete class file.
+ *
+ * @author Wei Zhuo <weizhuo[at]gamil[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2006 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ : $
+ * @package System.Web.UI.ActiveControls
*/
+/**
+ * TAutoComplete class.
+ *
+ * TAutoComplete is a textbox that provides a list of suggestion on
+ * the current partial word typed in the textbox. The suggestions are
+ * requested using callbacks, and raises the {@link onSuggestion OnSuggestion}
+ * event. The events of the TActiveText (from which TAutoComplete is extended from)
+ * and {@link onSuggestion OnSuggestion} are mutually exculsive. That is,
+ * if {@link onTextChange OnTextChange} and/or {@link onCallback OnCallback}
+ * events are raise, then {@link onSuggestion OnSuggestion} will not be raise, and
+ * vice versa.
+ *
+ * The list of suggestions should be set in the {@link onSuggestion OnSuggestion}
+ * event handler. The partial word to match the suggestion is in the
+ * {@link TCallbackEventParameter::getParameter TCallbackEventParameter::Parameter}
+ * property. The datasource of the TAutoComplete must be set using {@link setDataSource}
+ * method. This sets the datasource for the suggestions repeater, available through
+ * the {@link getSuggestions Suggestions} property. Header, footer templates and
+ * other properties of the repeater can be access via the {@link getSuggestions Suggestions}
+ * property (e.g. they can be set in the .page templates).
+ *
+ * To return the list of suggestions back to the browser, in your {@link onSuggestion OnSuggestion}
+ * event handler, do
+ * <code>
+ * function autocomplete_suggestion($sender, $param)
+ * {
+ * $token = $param->getParameter(); //the partial word to match
+ * $sender->setDataSource($this->getSuggestionsFor($token)); //set suggestions
+ * $sender->dataBind();
+ * $sender->flush($param->getOutput()); //sends suggestion back to browser.
+ * }
+ * </code>
+ *
+ * TAutoComplete allows multiple suggestions within one textbox with each
+ * word or phrase separated by any characters specified in the
+ * {@link setSeparator Separator} property. The {@link setFrequency Frequency}
+ * and {@link setMinChars MinChars} properties sets the delay and minimum number
+ * of characters typed, respectively, before requesting for sugggestions.
+ *
+ * Use {@link onTextChange OnTextChange} and/or {@link onCallback OnCallback} events
+ * to handle post backs due to {@link setAutoPostBack AutoPostBack}.
+ *
+ * In the {@link getSuggestions Suggestions} TRepater item template, all HTML text elements
+ * are considered as text for the suggestion. Text within HTML elements with CSS class name
+ * "informal" are ignored as text for suggestions.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ Mon Jun 19 03:50:05 EST 2006 $
+ * @package System
+ * @since 3.0
+ */
class TAutoComplete extends TActiveTextBox implements INamingContainer
{
/**
* @var ITemplate template for repeater items
*/
private $_repeater=null;
+ /**
+ * @var TPanel result panel holding the suggestion items.
+ */
private $_resultPanel=null;
+ /**
+ * @return string word or token separators (delimiters).
+ */
public function getSeparator()
{
return $this->getViewState('tokens', '');
}
+ /**
+ * @return string word or token separators (delimiters).
+ */
public function setSeparator($value)
{
$this->setViewState('tokens', TPropertyValue::ensureString($value), '');
}
+ /**
+ * @return float maximum delay (in seconds) before requesting a suggestion.
+ */
public function getFrequency()
{
return $this->getViewState('frequency', '');
}
+ /**
+ * @param float maximum delay (in seconds) before requesting a suggestion.
+ * Default is 0.4.
+ */
public function setFrequency($value)
{
$this->setViewState('frequency', TPropertyValue::ensureFloat($value),'');
}
+ /**
+ * @return integer minimum number of characters before requesting a suggestion.
+ */
public function getMinChars()
{
return $this->getViewState('minChars','');
}
+ /**
+ * @param integer minimum number of characters before requesting a suggestion.
+ */
public function setMinChars($value)
{
$this->setViewState('minChars', TPropertyValue::ensureInteger($value), '');
}
/**
- * Raises the callback event. This method is required by {@link
- * ICallbackEventHandler} interface. If {@link getCausesValidation
- * CausesValidation} is true, it will invoke the page's {@link TPage::
- * validate validate} method first. It will raise {@link onCallback
- * OnCallback} event and then the {@link onClick OnClick} event. This method
- * is mainly used by framework and control developers.
+ * Raises the callback event. This method is overrides the parent implementation.
+ * If {@link setAutoPostBack AutoPostBack} is enabled it will raise
+ * {@link onTextChanged OnTextChanged} event event and then the
+ * {@link onCallback OnCallback} event. The {@link onSuggest OnSuggest} event is
+ * raise if the request is to find sugggestions, the {@link onTextChanged OnTextChanged}
+ * and {@link onCallback OnCallback} events are <b>NOT</b> raised.
+ * This method is mainly used by framework and control developers.
* @param TCallbackEventParameter the event parameter
*/
public function raiseCallbackEvent($param)
{
- $this->onCallback($param);
+ $token = $param->getParameter();
+ if(is_array($token) && count($token) == 2 && $token[1] === '__TAutComplete_onSuggest__')
+ {
+ $parameter = new TCallbackEventParameter($this->getResponse(), $token[0]);
+ $this->onSuggest($parameter);
+ }
+ else if($this->getAutoPostBack())
+ parent::raiseCallbackEvent($param);
}
/**
- * This method is invoked when a callback is requested. The method raises
- * 'OnCallback' event to fire up the event handlers. If you override this
+ * This method is invoked when a autocomplete suggestion is requested.
+ * The method raises 'OnSuggest' event. If you override this
* method, be sure to call the parent implementation so that the event
* handler can be invoked.
* @param TCallbackEventParameter event parameter to be passed to the event handlers
*/
- public function onCallback($param)
+ public function onSuggest($param)
{
- $this->raiseEvent('OnCallback', $this, $param);
+ $this->raiseEvent('OnSuggest', $this, $param);
}
-
+
+ /**
+ * @param array data source for suggestions.
+ */
public function setDataSource($data)
{
$this->getSuggestions()->setDataSource($data);
}
+ /**
+ * @return TPanel suggestion results panel.
+ */
public function getResultPanel()
{
if(is_null($this->_resultPanel))
@@ -79,6 +173,9 @@ class TAutoComplete extends TActiveTextBox implements INamingContainer
return $this->_resultPanel;
}
+ /**
+ * @return TPanel new instance of result panel. Default uses TPanel.
+ */
protected function createResultPanel()
{
$panel = Prado::createComponent('System.Web.UI.WebControls.TPanel');
@@ -98,7 +195,7 @@ class TAutoComplete extends TActiveTextBox implements INamingContainer
}
/**
- *
+ * @return TRepeater new instance of TRepater to render the list of suggestions.
*/
protected function createRepeater()
{
@@ -110,6 +207,9 @@ class TAutoComplete extends TActiveTextBox implements INamingContainer
return $repeater;
}
+ /**
+ * Renders the end tag and registers javascript effects library.
+ */
public function renderEndTag($writer)
{
$this->getPage()->getClientScript()->registerPradoScript('effects');
@@ -117,22 +217,29 @@ class TAutoComplete extends TActiveTextBox implements INamingContainer
$this->renderResultPanel($writer);
}
- public function renderResultPanel($writer)
+ /**
+ * Renders the result panel.
+ * @param THtmlWriter the renderer.
+ */
+ protected function renderResultPanel($writer)
{
$this->getResultPanel()->render($writer);
}
- public function render($writer)
+ /**
+ * Flush and returns the suggestions content back to the browser client.
+ * @param THtmlWriter the renderer.
+ */
+ public function flush($writer)
{
- if($this->getPage()->getIsCallback())
- {
- if($this->getActiveControl()->canUpdateClientSide())
+ if($this->getActiveControl()->canUpdateClientSide())
$this->renderSuggestions($writer);
- }
- else
- parent::render($writer);
}
+ /**
+ * Renders the suggestions repeater.
+ * @param THtmlWriter the renderer.
+ */
protected function renderSuggestions($writer)
{
if($this->getSuggestions()->getItems()->getCount() > 0)
@@ -151,11 +258,15 @@ class TAutoComplete extends TActiveTextBox implements INamingContainer
$this->getActiveControl()->getClientSide()->setEnablePageStateUpdate(false);
if(strlen($string = $this->getSeparator()))
{
+ $string = strtr($string,array('\t'=>"\t",'\n'=>"\n",'\r'=>"\r"));
$token = preg_split('//', $string, -1, PREG_SPLIT_NO_EMPTY);
$options['tokens'] = TJavascript::encode($token,false);
}
if($this->getAutoPostBack())
- $options = array_merge($options,$this->getPostBackOptions());
+ {
+ $options = array_merge($options,$this->getPostBackOptions());
+ $options['AutoPostBack'] = true;
+ }
$options['ResultPanel'] = $this->getResultPanel()->getClientID();
$options['ID'] = $this->getClientID();
$options['EventTarget'] = $this->getUniqueID();
diff --git a/tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.page b/tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.page
index 5c2d1abb..8eff7105 100644
--- a/tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.page
+++ b/tests/FunctionalTests/active-controls/protected/pages/ActiveButtonTest.page
@@ -1,4 +1,5 @@
<com:TForm ID="form1">
+
<h1>TActiveButton Functional Test</h1>
<com:TActiveButton ID="button2" Text="Button 1"
OnClick="button2_onclick" OnCallback="button2_oncallback" />
@@ -6,4 +7,5 @@
<com:TActiveLabel ID="label1" Text="Label 1" />
<com:TJavascriptLogger />
+
</com:TForm> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.page b/tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.page
index d0a750ac..b05ce62e 100644
--- a/tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.page
+++ b/tests/FunctionalTests/active-controls/protected/pages/ActiveTextBoxCallback.page
@@ -2,4 +2,5 @@
<h1>ActiveTextBox Callback Test</h1>
<com:TActiveTextBox ID="textbox1" AutoPostBack="true" OnCallback="textbox1_callback" />
<com:TActiveLabel ID="label1" Text="Label 1" />
+ <com:TJavascriptLogger />
</com:TForm> \ No newline at end of file
diff --git a/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.page b/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.page
index 93658bd7..b7429c60 100644
--- a/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.page
+++ b/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.page
@@ -15,6 +15,11 @@
font-family: Tahoma, Arial, Helvetica, sans-serif;
color: #333;
}
+
+ ul.different
+ {
+ background-color: pink;
+ }
.autocomplete li
{
@@ -27,14 +32,33 @@
}
</style>
- <h1>TAutoComplete Test</h1>
+ <h1 id="heading">TAutoComplete Test</h1>
+ <div><strong>Manual Testing Required</strong></div>
<com:TAutoComplete Style="width: 20em"
- OnCallback="suggestCountries"
+ ID="textbox1"
+ OnSuggest="suggestCountries"
Separator=", "
ResultPanel.CssClass="autocomplete" />
+ <com:TAutoComplete Style="width: 30em; height: 20em" TextMode="MultiLine"
+ OnSuggest="suggestCountries"
+ Separator=", \n"
+ ResultPanel.CssClass="autocomplete">
+ <prop:Suggestions.HeaderTemplate>
+ <ul class="different">
+ </prop:Suggestions.HeaderTemplate>
+ </com:TAutoComplete>
+
+ <com:TAutoComplete Style="width: 20em"
+ id="textbox3"
+ OnSuggest="suggestCountries"
+ OnCallback="callback_requested"
+ Separator=", " AutoPostBack="true"
+ ResultPanel.CssClass="autocomplete" />
+
<p><br /></p>
+ <com:TActiveLabel ID="label1" Text="Label 1" />
<p><br /></p>
<p><br /></p>
<p><br /></p>
diff --git a/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.php b/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.php
index 938b8640..df314891 100644
--- a/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.php
+++ b/tests/FunctionalTests/active-controls/protected/pages/AutoCompleteTest.php
@@ -9,7 +9,13 @@ class AutoCompleteTest extends TPage
{
$sender->setDataSource($this->matchCountries($param->getParameter()));
$sender->dataBind();
- $sender->render($param->getOutput());
+ $sender->flush($param->getOutput());
+ $this->label1->Text = "suggestion for ".$param->getParameter();
+ }
+
+ public function callback_requested($sender, $param)
+ {
+ $this->label1->Text = "Label 1: ".$this->textbox3->Text;
}
protected function matchCountries($token)
diff --git a/tests/FunctionalTests/active-controls/tests/AutoCompleteTestCase.php b/tests/FunctionalTests/active-controls/tests/AutoCompleteTestCase.php
new file mode 100644
index 00000000..f8b4cf55
--- /dev/null
+++ b/tests/FunctionalTests/active-controls/tests/AutoCompleteTestCase.php
@@ -0,0 +1,21 @@
+<?php
+
+class AutoCompleteTestCase extends SeleniumTestCase
+{
+ function test()
+ {
+ $this->open("active-controls/index.php?page=AutoCompleteTest");
+ $this->verifyTextPresent("TAutoComplete Test");
+
+ $this->assertText("label1", "Label 1");
+
+ $this->type("textbox3", "Australia");
+ $this->pause(500);
+ $this->click("heading"); //click somewhere else.
+ $this->pause(500);
+ $this->assertText("label1", "Label 1: Australia");
+
+ }
+}
+
+?> \ No newline at end of file