summaryrefslogtreecommitdiff
path: root/demos/currency-converter
diff options
context:
space:
mode:
authorwei <>2006-12-23 00:22:48 +0000
committerwei <>2006-12-23 00:22:48 +0000
commit6b685cc4e496c5eabbb3e6415fb7fdf0474bf0f1 (patch)
tree7a5859c07da28454b894a3ca003fede8880da4e4 /demos/currency-converter
parent2c221ea67d0512961beea8fbcb30b23865c16bb0 (diff)
add currency-converter tutorial
Diffstat (limited to 'demos/currency-converter')
-rw-r--r--demos/currency-converter/assets/2bffd82d/ajax.js2836
-rw-r--r--demos/currency-converter/assets/2bffd82d/clientscripts.php61
-rw-r--r--demos/currency-converter/assets/2bffd82d/effects.js968
-rw-r--r--demos/currency-converter/assets/2bffd82d/logger.js755
-rw-r--r--demos/currency-converter/assets/2bffd82d/prado.js3542
-rw-r--r--demos/currency-converter/assets/2bffd82d/validator.js1362
-rw-r--r--demos/currency-converter/index.php22
-rw-r--r--demos/currency-converter/protected/.htaccess1
-rw-r--r--demos/currency-converter/protected/pages/Home.page51
-rw-r--r--demos/currency-converter/protected/pages/Home.php15
-rw-r--r--demos/currency-converter/protected/runtime/global.cache1
-rw-r--r--demos/currency-converter/themes/Basic/common.css52
12 files changed, 9666 insertions, 0 deletions
diff --git a/demos/currency-converter/assets/2bffd82d/ajax.js b/demos/currency-converter/assets/2bffd82d/ajax.js
new file mode 100644
index 00000000..ed5694cf
--- /dev/null
+++ b/demos/currency-converter/assets/2bffd82d/ajax.js
@@ -0,0 +1,2836 @@
+var Ajax = {
+ getTransport: function() {
+ return Try.these(
+ function() {return new XMLHttpRequest()},
+ function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+ function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+ ) || false;
+ },
+
+ activeRequestCount: 0
+}
+
+Ajax.Responders = {
+ responders: [],
+
+ _each: function(iterator) {
+ this.responders._each(iterator);
+ },
+
+ register: function(responderToAdd) {
+ if (!this.include(responderToAdd))
+ this.responders.push(responderToAdd);
+ },
+
+ unregister: function(responderToRemove) {
+ this.responders = this.responders.without(responderToRemove);
+ },
+
+ dispatch: function(callback, request, transport, json) {
+ this.each(function(responder) {
+ if (responder[callback] && typeof responder[callback] == 'function') {
+ try {
+ responder[callback].apply(responder, [request, transport, json]);
+ } catch (e) {}
+ }
+ });
+ }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+ onCreate: function() {
+ Ajax.activeRequestCount++;
+ },
+
+ onComplete: function() {
+ Ajax.activeRequestCount--;
+ }
+});
+
+Ajax.Base = function() {};
+Ajax.Base.prototype = {
+ setOptions: function(options) {
+ this.options = {
+ method: 'post',
+ asynchronous: true,
+ contentType: 'application/x-www-form-urlencoded',
+ parameters: ''
+ }
+ Object.extend(this.options, options || {});
+ },
+
+ responseIsSuccess: function() {
+ return this.transport.status == undefined
+ || this.transport.status == 0
+ || (this.transport.status >= 200 && this.transport.status < 300);
+ },
+
+ responseIsFailure: function() {
+ return !this.responseIsSuccess();
+ }
+}
+
+Ajax.Request = Class.create();
+Ajax.Request.Events =
+ ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
+ initialize: function(url, options) {
+ this.transport = Ajax.getTransport();
+ this.setOptions(options);
+ this.request(url);
+ },
+
+ request: function(url) {
+ var parameters = this.options.parameters || '';
+ if (parameters.length > 0) parameters += '&_=';
+
+ try {
+ this.url = url;
+ if (this.options.method == 'get' && parameters.length > 0)
+ this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
+
+ Ajax.Responders.dispatch('onCreate', this, this.transport);
+
+ this.transport.open(this.options.method, this.url,
+ this.options.asynchronous);
+
+ if (this.options.asynchronous) {
+ this.transport.onreadystatechange = this.onStateChange.bind(this);
+ setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
+ }
+
+ this.setRequestHeaders();
+
+ var body = this.options.postBody ? this.options.postBody : parameters;
+ this.transport.send(this.options.method == 'post' ? body : null);
+
+ } catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ setRequestHeaders: function() {
+ var requestHeaders =
+ ['X-Requested-With', 'XMLHttpRequest',
+ 'X-Prototype-Version', Prototype.Version,
+ 'Accept', 'text/javascript, text/html, application/xml, text/xml'];
+
+ if (this.options.method == 'post') {
+ requestHeaders.push('Content-type', this.options.contentType);
+
+ /* Force "Connection: close" for Mozilla browsers to work around
+ * a bug where XMLHttpReqeuest sends an incorrect Content-length
+ * header. See Mozilla Bugzilla #246651.
+ */
+ if (this.transport.overrideMimeType)
+ requestHeaders.push('Connection', 'close');
+ }
+
+ if (this.options.requestHeaders)
+ requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
+
+ for (var i = 0; i < requestHeaders.length; i += 2)
+ this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
+ },
+
+ onStateChange: function() {
+ var readyState = this.transport.readyState;
+ if (readyState != 1)
+ this.respondToReadyState(this.transport.readyState);
+ },
+
+ header: function(name) {
+ try {
+ return this.transport.getResponseHeader(name);
+ } catch (e) {}
+ },
+
+ evalJSON: function() {
+ try {
+ return eval('(' + this.header('X-JSON') + ')');
+ } catch (e) {}
+ },
+
+ evalResponse: function() {
+ try {
+ return eval(this.transport.responseText);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ respondToReadyState: function(readyState) {
+ var event = Ajax.Request.Events[readyState];
+ var transport = this.transport, json = this.evalJSON();
+
+ if (event == 'Complete') {
+ try {
+ (this.options['on' + this.transport.status]
+ || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
+ || Prototype.emptyFunction)(transport, json);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ if ((this.header('Content-type') || '').match(/^text\/javascript/i))
+ this.evalResponse();
+ }
+
+ try {
+ (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
+ Ajax.Responders.dispatch('on' + event, this, transport, json);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
+ if (event == 'Complete')
+ this.transport.onreadystatechange = Prototype.emptyFunction;
+ },
+
+ dispatchException: function(exception) {
+ (this.options.onException || Prototype.emptyFunction)(this, exception);
+ Ajax.Responders.dispatch('onException', this, exception);
+ }
+});
+
+Ajax.Updater = Class.create();
+
+Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
+ initialize: function(container, url, options) {
+ this.containers = {
+ success: container.success ? $(container.success) : $(container),
+ failure: container.failure ? $(container.failure) :
+ (container.success ? null : $(container))
+ }
+
+ this.transport = Ajax.getTransport();
+ this.setOptions(options);
+
+ var onComplete = this.options.onComplete || Prototype.emptyFunction;
+ this.options.onComplete = (function(transport, object) {
+ this.updateContent();
+ onComplete(transport, object);
+ }).bind(this);
+
+ this.request(url);
+ },
+
+ updateContent: function() {
+ var receiver = this.responseIsSuccess() ?
+ this.containers.success : this.containers.failure;
+ var response = this.transport.responseText;
+
+ if (!this.options.evalScripts)
+ response = response.stripScripts();
+
+ if (receiver) {
+ if (this.options.insertion) {
+ new this.options.insertion(receiver, response);
+ } else {
+ Element.update(receiver, response);
+ }
+ }
+
+ if (this.responseIsSuccess()) {
+ if (this.onComplete)
+ setTimeout(this.onComplete.bind(this), 10);
+ }
+ }
+});
+
+Ajax.PeriodicalUpdater = Class.create();
+Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
+ initialize: function(container, url, options) {
+ this.setOptions(options);
+ this.onComplete = this.options.onComplete;
+
+ this.frequency = (this.options.frequency || 2);
+ this.decay = (this.options.decay || 1);
+
+ this.updater = {};
+ this.container = container;
+ this.url = url;
+
+ this.start();
+ },
+
+ start: function() {
+ this.options.onComplete = this.updateComplete.bind(this);
+ this.onTimerEvent();
+ },
+
+ stop: function() {
+ this.updater.onComplete = undefined;
+ clearTimeout(this.timer);
+ (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+ },
+
+ updateComplete: function(request) {
+ if (this.options.decay) {
+ this.decay = (request.responseText == this.lastText ?
+ this.decay * this.options.decay : 1);
+
+ this.lastText = request.responseText;
+ }
+ this.timer = setTimeout(this.onTimerEvent.bind(this),
+ this.decay * this.frequency * 1000);
+ },
+
+ onTimerEvent: function() {
+ this.updater = new Ajax.Updater(this.container, this.url, this.options);
+ }
+});
+
+
+/**
+ * Override Prototype's response implementation.
+ */
+Object.extend(Ajax.Request.prototype,
+{
+ /**
+ * Customize the response, dispatch onXXX response code events, and
+ * tries to execute response actions (javascript statements).
+ */
+ respondToReadyState : function(readyState)
+ {
+ var event = Ajax.Request.Events[readyState];
+ var transport = this.transport, json = this.getHeaderData(Prado.CallbackRequest.DATA_HEADER);
+
+ if (event == 'Complete')
+ {
+ if(this.header('X-PRADO-REDIRECT'))
+ document.location.href = this.header('X-PRADO-REDIRECT');
+
+ if ((this.header('Content-type') || '').match(/^text\/javascript/i))
+ {
+ try
+ {
+ json = eval('(' + transport.responseText + ')');
+ }catch (e)
+ {
+ if(typeof(json) == "string")
+ json = Prado.CallbackRequest.decode(result);
+ }
+ }
+
+ try
+ {
+ Prado.CallbackRequest.updatePageState(this,transport);
+ Ajax.Responders.dispatch('on' + transport.status, this, transport, json);
+ Prado.CallbackRequest.dispatchActions(transport,this.getHeaderData(Prado.CallbackRequest.ACTION_HEADER));
+
+ (this.options['on' + this.transport.status]
+ || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
+ || Prototype.emptyFunction)(this, json);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+ }
+
+ try {
+ (this.options['on' + event] || Prototype.emptyFunction)(this, json);
+ Ajax.Responders.dispatch('on' + event, this, transport, json);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
+ if (event == 'Complete')
+ this.transport.onreadystatechange = Prototype.emptyFunction;
+ },
+
+ /**
+ * Gets header data assuming JSON encoding.
+ * @param string header name
+ * @return object header data as javascript structures.
+ */
+ getHeaderData : function(name)
+ {
+ try
+ {
+ var json = this.header(name);
+ return eval('(' + json + ')');
+ }
+ catch (e)
+ {
+ if(typeof(json) == "string")
+ return Prado.CallbackRequest.decode(json);
+ }
+ }
+});
+
+/**
+ * Prado Callback client-side request handler.
+ */
+Prado.CallbackRequest = Class.create();
+
+/**
+ * Static definitions.
+ */
+Object.extend(Prado.CallbackRequest,
+{
+ /**
+ * Callback request target POST field name.
+ */
+ FIELD_CALLBACK_TARGET : 'PRADO_CALLBACK_TARGET',
+ /**
+ * Callback request parameter POST field name.
+ */
+ FIELD_CALLBACK_PARAMETER : 'PRADO_CALLBACK_PARAMETER',
+ /**
+ * Callback request page state field name,
+ */
+ FIELD_CALLBACK_PAGESTATE : 'PRADO_PAGESTATE',
+
+ FIELD_POSTBACK_TARGET : 'PRADO_POSTBACK_TARGET',
+
+ FIELD_POSTBACK_PARAMETER : 'PRADO_POSTBACK_PARAMETER',
+
+ /**
+ * List of form fields that will be collected during callback.
+ */
+ PostDataLoaders : [],
+ /**
+ * Response data header name.
+ */
+ DATA_HEADER : 'X-PRADO-DATA',
+ /**
+ * Response javascript execution statement header name.
+ */
+ ACTION_HEADER : 'X-PRADO-ACTIONS',
+ /**
+ * Response errors/exceptions header name.
+ */
+ ERROR_HEADER : 'X-PRADO-ERROR',
+ /**
+ * Page state header name.
+ */
+ PAGESTATE_HEADER : 'X-PRADO-PAGESTATE',
+
+ requestQueue : [],
+
+ /**
+ * Add ids of inputs element to post in the request.
+ */
+ addPostLoaders : function(ids)
+ {
+ var self = Prado.CallbackRequest;
+ self.PostDataLoaders = self.PostDataLoaders.concat(ids);
+ var list = [];
+ self.PostDataLoaders.each(function(id)
+ {
+ if(list.indexOf(id) < 0)
+ list.push(id);
+ });
+ self.PostDataLoaders = list;
+ },
+
+ /**
+ * Dispatch callback response actions.
+ */
+ dispatchActions : function(transport,actions)
+ {
+ var self = Prado.CallbackRequest;
+ if(actions && actions.length > 0)
+ actions.each(self.__run.bind(self,transport));
+ },
+
+ /**
+ * Prase and evaluate a Callback clien-side action
+ */
+ __run : function(transport, command)
+ {
+ var self = Prado.CallbackRequest;
+ self.transport = transport;
+ for(var method in command)
+ {
+ try
+ {
+ method.toFunction().apply(self,command[method]);
+ }
+ catch(e)
+ {
+ if(typeof(Logger) != "undefined")
+ self.Exception.onException(null,e);
+ }
+ }
+ },
+
+ /**
+ * Respond to Prado Callback request exceptions.
+ */
+ Exception :
+ {
+ /**
+ * Server returns 500 exception. Just log it.
+ */
+ "on500" : function(request, transport, data)
+ {
+ var e = request.getHeaderData(Prado.CallbackRequest.ERROR_HEADER);
+ Logger.error("Callback Server Error "+e.code, this.formatException(e));
+ },
+
+ /**
+ * Callback OnComplete event,logs reponse and data to console.
+ */
+ 'on200' : function(request, transport, data)
+ {
+ if(transport.status < 500)
+ {
+ var msg = 'HTTP '+transport.status+" with response : \n";
+ if(transport.responseText.trim().length >0)
+ msg += transport.responseText + "\n";
+ if(typeof(data)!="undefined" && data != null)
+ msg += "Data : \n"+inspect(data)+"\n";
+ data = request.getHeaderData(Prado.CallbackRequest.ACTION_HEADER);
+ if(data && data.length > 0)
+ {
+ msg += "Actions : \n";
+ data.each(function(action)
+ {
+ msg += inspect(action)+"\n";
+ });
+ }
+ Logger.info(msg);
+ }
+ },
+
+ /**
+ * Uncaught exceptions during callback response.
+ */
+ onException : function(request,e)
+ {
+ msg = "";
+ $H(e).each(function(item)
+ {
+ msg += item.key+": "+item.value+"\n";
+ })
+ Logger.error('Uncaught Callback Client Exception:', msg);
+ },
+
+ /**
+ * Formats the exception message for display in console.
+ */
+ formatException : function(e)
+ {
+ var msg = e.type + " with message \""+e.message+"\"";
+ msg += " in "+e.file+"("+e.line+")\n";
+ msg += "Stack trace:\n";
+ var trace = e.trace;
+ for(var i = 0; i<trace.length; i++)
+ {
+ msg += " #"+i+" "+trace[i].file;
+ msg += "("+trace[i].line+"): ";
+ msg += trace[i]["class"]+"->"+trace[i]["function"]+"()"+"\n";
+ }
+ msg += e.version+" "+e.time+"\n";
+ return msg;
+ }
+ },
+
+ /**
+ * @return string JSON encoded data.
+ */
+ encode : function(data)
+ {
+ return Prado.JSON.stringify(data);
+ },
+
+ /**
+ * @return mixed javascript data decoded from string using JSON decoding.
+ */
+ decode : function(data)
+ {
+ if(typeof(data) == "string" && data.trim().length > 0)
+ return Prado.JSON.parse(data);
+ else
+ return null;
+ },
+
+ /**
+ * Dispatch a normal request, no timeouts or aborting of requests.
+ */
+ dispatchNormalRequest : function(callback)
+ {
+ //Logger.info("dispatching normal request");
+ new Ajax.Request(callback.url, callback.options);
+ return true;
+ },
+
+ /**
+ * Abort the current priority request in progress.
+ */
+ tryNextRequest : function()
+ {
+ var self = Prado.CallbackRequest;
+ //Logger.debug('trying next request');
+ if(typeof(self.currentRequest) == 'undefined' || self.currentRequest==null)
+ {
+ if(self.requestQueue.length > 0)
+ return self.dispatchQueue();
+ //else
+ //Logger.warn('empty queque');
+ }
+// else
+ // Logger.warn('current request ' + self.currentRequest.id);
+ },
+
+ /**
+ * Updates the page state. It will update only if EnablePageStateUpdate and
+ * HasPriority options are both true.
+ */
+ updatePageState : function(request, transport)
+ {
+ var self = Prado.CallbackRequest;
+ var pagestate = $(self.FIELD_CALLBACK_PAGESTATE);
+ var enabled = request.options.EnablePageStateUpdate && request.options.HasPriority;
+ var aborted = self.currentRequest == null;
+ if(enabled && !aborted && pagestate)
+ {
+ var data = request.header(self.PAGESTATE_HEADER);
+ if(typeof(data) == "string" && data.length > 0)
+ pagestate.value = data;
+ else
+ {
+ if(typeof(Logger) != "undefined")
+ Logger.warn("Missing page state:"+data);
+// Logger.warn('## bad state: setting current request to null');
+ self.endCurrentRequest();
+ //self.tryNextRequest();
+ return false;
+ }
+ }
+ self.endCurrentRequest();
+ // Logger.warn('## state updated: setting current request to null');
+ // self.tryNextRequest();
+ return true;
+ },
+
+ enqueue : function(callback)
+ {
+ var self = Prado.CallbackRequest;
+ self.requestQueue.push(callback);
+ //Logger.warn("equeued "+callback.id+", current queque length="+self.requestQueue.length);
+ self.tryNextRequest();
+ },
+
+ dispatchQueue : function()
+ {
+ var self = Prado.CallbackRequest;
+ //Logger.warn("dispatching queque, length="+self.requestQueue.length+" request="+self.currentRequest);
+ var callback = self.requestQueue.shift();
+ self.currentRequest = callback;
+
+ //get data
+ callback.options.postBody = callback._getPostData(),
+
+ callback.request = new Ajax.Request(callback.url, callback.options);
+ callback.timeout = setTimeout(function()
+ {
+ //Logger.warn("priority timeout");
+ self.abortRequest(callback.id);
+ },callback.options.RequestTimeOut);
+ //Logger.debug("dispatched "+self.currentRequest.id + " ...")
+ },
+
+ endCurrentRequest : function()
+ {
+ var self = Prado.CallbackRequest;
+ clearTimeout(self.currentRequest.timeout);
+ self.currentRequest=null;
+ },
+
+ abortRequest : function(id)
+ {
+ //Logger.warn("abort id="+id);
+ var self = Prado.CallbackRequest;
+ if(typeof(self.currentRequest) != 'undefined'
+ && self.currentRequest != null && self.currentRequest.id == id)
+ {
+ var request = self.currentRequest.request;
+ if(request.transport.readyState < 4)
+ request.transport.abort();
+ //Logger.warn('## aborted: setting current request to null');
+ self.endCurrentRequest();
+ }
+ self.tryNextRequest();
+ }
+})
+
+/**
+ * Automatically aborts the current request when a priority request has returned.
+ */
+Ajax.Responders.register({onComplete : function(request)
+{
+ if(request.options.HasPriority)
+ Prado.CallbackRequest.tryNextRequest();
+}});
+
+//Add HTTP exception respones when logger is enabled.
+Event.OnLoad(function()
+{
+ if(typeof Logger != "undefined")
+ Ajax.Responders.register(Prado.CallbackRequest.Exception);
+});
+
+/**
+ * Create and prepare a new callback request.
+ * Call the dispatch() method to start the callback request.
+ * <code>
+ * request = new Prado.CallbackRequest(UniqueID, callback);
+ * request.dispatch();
+ * </code>
+ */
+Prado.CallbackRequest.prototype =
+{
+
+ /**
+ * Prepare and inititate a callback request.
+ */
+ initialize : function(id, options)
+ {
+ /**
+ * Callback URL, same url as the current page.
+ */
+ this.url = window.location.href;
+
+ /**
+ * Current callback request.
+ */
+ this.request = null;
+
+ this.Enabled = true;
+
+ this.id = id;
+ this.options = Object.extend(
+ {
+ RequestTimeOut : 30000, // 30 second timeout.
+ EnablePageStateUpdate : true,
+ HasPriority : true,
+ CausesValidation : true,
+ ValidationGroup : null,
+ PostInputs : true
+ }, options || {});
+ },
+
+ /**
+ * Sets the request parameter
+ * @param {Object} parameter value
+ */
+ setCallbackParameter : function(value)
+ {
+ this.options['params'] = value;
+ },
+
+ /**
+ * @return {Object} request paramater value.
+ */
+ getCallbackParameter : function()
+ {
+ return this.options['params'];
+ },
+
+ /**
+ * Sets the callback request timeout.
+ * @param {integer} timeout in milliseconds
+ */
+ setRequestTimeOut : function(timeout)
+ {
+ this.options['RequestTimeOut'] = timeout;
+ },
+
+ /**
+ * @return {integer} request timeout in milliseconds
+ */
+ getRequestTimeOut : function()
+ {
+ return this.options['RequestTimeOut'];
+ },
+
+ /**
+ * Set true to enable validation on callback dispatch.
+ * @param {boolean} true to validate
+ */
+ setCausesValidation : function(validate)
+ {
+ this.options['CausesValidation'] = validate;
+ },
+
+ /**
+ * @return {boolean} validate on request dispatch
+ */
+ getCausesValidation : function()
+ {
+ return this.options['CausesValidation'];
+ },
+
+ /**
+ * Sets the validation group to validate during request dispatch.
+ * @param {string} validation group name
+ */
+ setValidationGroup : function(group)
+ {
+ this.options['ValidationGroup'] = group;
+ },
+
+ /**
+ * @return {string} validation group name.
+ */
+ getValidationGroup : function()
+ {
+ return this.options['ValidationGroup'];
+ },
+
+ /**
+ * Dispatch the callback request.
+ */
+ dispatch : function()
+ {
+ //Logger.info("dispatching request");
+ //trigger tinyMCE to save data.
+ if(typeof tinyMCE != "undefined")
+ tinyMCE.triggerSave();
+
+ //override parameter and postBody options.
+ Object.extend(this.options,
+ {
+// postBody : this._getPostData(),
+ parameters : ''
+ });
+
+ if(this.options.CausesValidation && typeof(Prado.Validation) != "undefined")
+ {
+ var form = this.options.Form || Prado.Validation.getForm();
+ if(Prado.Validation.validate(form,this.options.ValidationGroup,this) == false)
+ return false;
+ }
+
+ if(this.options.onPreDispatch)
+ this.options.onPreDispatch(this,null);
+
+ if(!this.Enabled)
+ return;
+
+ if(this.options.HasPriority)
+ {
+ return Prado.CallbackRequest.enqueue(this);
+ //return Prado.CallbackRequest.dispatchPriorityRequest(this);
+ }
+ else
+ return Prado.CallbackRequest.dispatchNormalRequest(this);
+ },
+
+ abort : function()
+ {
+ return Prado.CallbackRequest.abortRequest(this.id);
+ },
+
+ /**
+ * Collects the form inputs, encode the parameters, and sets the callback
+ * target id. The resulting string is the request content body.
+ * @return string request body content containing post data.
+ */
+ _getPostData : function()
+ {
+ var data = {};
+ var callback = Prado.CallbackRequest;
+ if(this.options.PostInputs != false)
+ {
+ callback.PostDataLoaders.each(function(name)
+ {
+ $A(document.getElementsByName(name)).each(function(element)
+ {
+ //IE will try to get elements with ID == name as well.
+ if(element.type && element.name == name)
+ {
+ value = $F(element);
+ if(typeof(value) != "undefined")
+ data[name] = value;
+ }
+ })
+ })
+ }
+ if(typeof(this.options.params) != "undefined")
+ data[callback.FIELD_CALLBACK_PARAMETER] = callback.encode(this.options.params);
+ var pageState = $F(callback.FIELD_CALLBACK_PAGESTATE);
+ if(typeof(pageState) != "undefined")
+ data[callback.FIELD_CALLBACK_PAGESTATE] = pageState;
+ data[callback.FIELD_CALLBACK_TARGET] = this.id;
+ if(this.options.EventTarget)
+ data[callback.FIELD_POSTBACK_TARGET] = this.options.EventTarget;
+ if(this.options.EventParameter)
+ data[callback.FIELD_POSTBACK_PARAMETER] = this.options.EventParameter;
+ return $H(data).toQueryString();
+ }
+}
+
+/**
+ * Create a new callback request using default settings.
+ * @param string callback handler unique ID.
+ * @param mixed parameter to pass to callback handler on the server side.
+ * @param function client side onSuccess event handler.
+ * @param object additional request options.
+ * @return boolean always false.
+ */
+Prado.Callback = function(UniqueID, parameter, onSuccess, options)
+{
+ var callback =
+ {
+ 'params' : parameter || '',
+ 'onSuccess' : onSuccess || Prototype.emptyFunction
+ };
+
+ Object.extend(callback, options || {});
+
+ request = new Prado.CallbackRequest(UniqueID, callback);
+ request.dispatch();
+ return false;
+}
+
+
+/*
+Copyright (c) 2005 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+Array.prototype.______array = '______array';
+
+Prado.JSON = {
+ org: 'http://www.JSON.org',
+ copyright: '(c)2005 JSON.org',
+ license: 'http://www.crockford.com/JSON/license.html',
+
+ stringify: function (arg) {
+ var c, i, l, s = '', v;
+
+ switch (typeof arg) {
+ case 'object':
+ if (arg) {
+ if (arg.______array == '______array') {
+ for (i = 0; i < arg.length; ++i) {
+ v = this.stringify(arg[i]);
+ if (s) {
+ s += ',';
+ }
+ s += v;
+ }
+ return '[' + s + ']';
+ } else if (typeof arg.toString != 'undefined') {
+ for (i in arg) {
+ v = arg[i];
+ if (typeof v != 'undefined' && typeof v != 'function') {
+ v = this.stringify(v);
+ if (s) {
+ s += ',';
+ }
+ s += this.stringify(i) + ':' + v;
+ }
+ }
+ return '{' + s + '}';
+ }
+ }
+ return 'null';
+ case 'number':
+ return isFinite(arg) ? String(arg) : 'null';
+ case 'string':
+ l = arg.length;
+ s = '"';
+ for (i = 0; i < l; i += 1) {
+ c = arg.charAt(i);
+ if (c >= ' ') {
+ if (c == '\\' || c == '"') {
+ s += '\\';
+ }
+ s += c;
+ } else {
+ switch (c) {
+ case '\b':
+ s += '\\b';
+ break;
+ case '\f':
+ s += '\\f';
+ break;
+ case '\n':
+ s += '\\n';
+ break;
+ case '\r':
+ s += '\\r';
+ break;
+ case '\t':
+ s += '\\t';
+ break;
+ default:
+ c = c.charCodeAt();
+ s += '\\u00' + Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }
+ }
+ }
+ return s + '"';
+ case 'boolean':
+ return String(arg);
+ default:
+ return 'null';
+ }
+ },
+ parse: function (text) {
+ var at = 0;
+ var ch = ' ';
+
+ function error(m) {
+ throw {
+ name: 'JSONError',
+ message: m,
+ at: at - 1,
+ text: text
+ };
+ }
+
+ function next() {
+ ch = text.charAt(at);
+ at += 1;
+ return ch;
+ }
+
+ function white() {
+ while (ch) {
+ if (ch <= ' ') {
+ next();
+ } else if (ch == '/') {
+ switch (next()) {
+ case '/':
+ while (next() && ch != '\n' && ch != '\r') {}
+ break;
+ case '*':
+ next();
+ for (;;) {
+ if (ch) {
+ if (ch == '*') {
+ if (next() == '/') {
+ next();
+ break;
+ }
+ } else {
+ next();
+ }
+ } else {
+ error("Unterminated comment");
+ }
+ }
+ break;
+ default:
+ error("Syntax error");
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ function string() {
+ var i, s = '', t, u;
+
+ if (ch == '"') {
+outer: while (next()) {
+ if (ch == '"') {
+ next();
+ return s;
+ } else if (ch == '\\') {
+ switch (next()) {
+ case 'b':
+ s += '\b';
+ break;
+ case 'f':
+ s += '\f';
+ break;
+ case 'n':
+ s += '\n';
+ break;
+ case 'r':
+ s += '\r';
+ break;
+ case 't':
+ s += '\t';
+ break;
+ case 'u':
+ u = 0;
+ for (i = 0; i < 4; i += 1) {
+ t = parseInt(next(), 16);
+ if (!isFinite(t)) {
+ break outer;
+ }
+ u = u * 16 + t;
+ }
+ s += String.fromCharCode(u);
+ break;
+ default:
+ s += ch;
+ }
+ } else {
+ s += ch;
+ }
+ }
+ }
+ error("Bad string");
+ }
+
+ function array() {
+ var a = [];
+
+ if (ch == '[') {
+ next();
+ white();
+ if (ch == ']') {
+ next();
+ return a;
+ }
+ while (ch) {
+ a.push(value());
+ white();
+ if (ch == ']') {
+ next();
+ return a;
+ } else if (ch != ',') {
+ break;
+ }
+ next();
+ white();
+ }
+ }
+ error("Bad array");
+ }
+
+ function object() {
+ var k, o = {};
+
+ if (ch == '{') {
+ next();
+ white();
+ if (ch == '}') {
+ next();
+ return o;
+ }
+ while (ch) {
+ k = string();
+ white();
+ if (ch != ':') {
+ break;
+ }
+ next();
+ o[k] = value();
+ white();
+ if (ch == '}') {
+ next();
+ return o;
+ } else if (ch != ',') {
+ break;
+ }
+ next();
+ white();
+ }
+ }
+ error("Bad object");
+ }
+
+ function number() {
+ var n = '', v;
+ if (ch == '-') {
+ n = '-';
+ next();
+ }
+ while (ch >= '0' && ch <= '9') {
+ n += ch;
+ next();
+ }
+ if (ch == '.') {
+ n += '.';
+ while (next() && ch >= '0' && ch <= '9') {
+ n += ch;
+ }
+ }
+ if (ch == 'e' || ch == 'E') {
+ n += 'e';
+ next();
+ if (ch == '-' || ch == '+') {
+ n += ch;
+ next();
+ }
+ while (ch >= '0' && ch <= '9') {
+ n += ch;
+ next();
+ }
+ }
+ v = +n;
+ if (!isFinite(v)) {
+ ////error("Bad number");
+ } else {
+ return v;
+ }
+ }
+
+ function word() {
+ switch (ch) {
+ case 't':
+ if (next() == 'r' && next() == 'u' && next() == 'e') {
+ next();
+ return true;
+ }
+ break;
+ case 'f':
+ if (next() == 'a' && next() == 'l' && next() == 's' &&
+ next() == 'e') {
+ next();
+ return false;
+ }
+ break;
+ case 'n':
+ if (next() == 'u' && next() == 'l' && next() == 'l') {
+ next();
+ return null;
+ }
+ break;
+ }
+ error("Syntax error");
+ }
+
+ function value() {
+ white();
+ switch (ch) {
+ case '{':
+ return object();
+ case '[':
+ return array();
+ case '"':
+ return string();
+ case '-':
+ return number();
+ default:
+ return ch >= '0' && ch <= '9' ? number() : word();
+ }
+ }
+
+ return value();
+ }
+};
+
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+// (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+// Richard Livsey
+// Rahul Bhargava
+// Rob Wills
+//
+// See scriptaculous.js for full license.
+
+// Autocompleter.Base handles all the autocompletion functionality
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least,
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most
+// useful when one of the tokens is \n (a newline), as it
+// allows smart autocompletion after linebreaks.
+
+if(typeof Effect == 'undefined')
+ throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = {}
+Autocompleter.Base = function() {};
+Autocompleter.Base.prototype = {
+ baseInitialize: function(element, update, options) {
+ this.element = $(element);
+ this.update = $(update);
+ this.hasFocus = false;
+ this.changed = false;
+ this.active = false;
+ this.index = 0;
+ this.entryCount = 0;
+
+ if (this.setOptions)
+ this.setOptions(options);
+ else
+ this.options = options || {};
+
+ this.options.paramName = this.options.paramName || this.element.name;
+ this.options.tokens = this.options.tokens || [];
+ this.options.frequency = this.options.frequency || 0.4;
+ this.options.minChars = this.options.minChars || 1;
+ this.options.onShow = this.options.onShow ||
+ function(element, update){
+ if(!update.style.position || update.style.position=='absolute') {
+ update.style.position = 'absolute';
+ Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
+ }
+ Effect.Appear(update,{duration:0.15});
+ };
+ this.options.onHide = this.options.onHide ||
+ function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+ if (typeof(this.options.tokens) == 'string')
+ this.options.tokens = new Array(this.options.tokens);
+
+ this.observer = null;
+
+ this.element.setAttribute('autocomplete','off');
+
+ Element.hide(this.update);
+
+ Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
+ Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
+ },
+
+ show: function() {
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+ if(!this.iefix &&
+ (navigator.appVersion.indexOf('MSIE')>0) &&
+ (navigator.userAgent.indexOf('Opera')<0) &&
+ (Element.getStyle(this.update, 'position')=='absolute')) {
+ new Insertion.After(this.update,
+ '<iframe id="' + this.update.id + '_iefix" '+
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+ this.iefix = $(this.update.id+'_iefix');
+ }
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+ },
+
+ fixIEOverlapping: function() {
+ Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
+ this.iefix.style.zIndex = 1;
+ this.update.style.zIndex = 2;
+ Element.show(this.iefix);
+ },
+
+ hide: function() {
+ this.stopIndicator();
+ if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+ if(this.iefix) Element.hide(this.iefix);
+ },
+
+ startIndicator: function() {
+ if(this.options.indicator) Element.show(this.options.indicator);
+ },
+
+ stopIndicator: function() {
+ if(this.options.indicator) Element.hide(this.options.indicator);
+ },
+
+ onKeyPress: function(event) {
+ if(this.active)
+ switch(event.keyCode) {
+ case Event.KEY_TAB:
+ case Event.KEY_RETURN:
+ this.selectEntry();
+ Event.stop(event);
+ case Event.KEY_ESC:
+ this.hide();
+ this.active = false;
+ Event.stop(event);
+ return;
+ case Event.KEY_LEFT:
+ case Event.KEY_RIGHT:
+ return;
+ case Event.KEY_UP:
+ this.markPrevious();
+ this.render();
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+ return;
+ case Event.KEY_DOWN:
+ this.markNext();
+ this.render();
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+ return;
+ }
+ else
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
+ (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
+
+ this.changed = true;
+ this.hasFocus = true;
+
+ if(this.observer) clearTimeout(this.observer);
+ this.observer =
+ setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+ },
+
+ activate: function() {
+ this.changed = false;
+ this.hasFocus = true;
+ this.getUpdatedChoices();
+ },
+
+ onHover: function(event) {
+ var element = Event.findElement(event, 'LI');
+ if(this.index != element.autocompleteIndex)
+ {
+ this.index = element.autocompleteIndex;
+ this.render();
+ }
+ Event.stop(event);
+ },
+
+ onClick: function(event) {
+ var element = Event.findElement(event, 'LI');
+ this.index = element.autocompleteIndex;
+ this.selectEntry();
+ this.hide();
+ },
+
+ onBlur: function(event) {
+ // needed to make click events working
+ setTimeout(this.hide.bind(this), 250);
+ this.hasFocus = false;
+ this.active = false;
+ },
+
+ render: function() {
+ if(this.entryCount > 0) {
+ for (var i = 0; i < this.entryCount; i++)
+ this.index==i ?
+ Element.addClassName(this.getEntry(i),"selected") :
+ Element.removeClassName(this.getEntry(i),"selected");
+
+ if(this.hasFocus) {
+ this.show();
+ this.active = true;
+ }
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ },
+
+ markPrevious: function() {
+ if(this.index > 0) this.index--
+ else this.index = this.entryCount-1;
+ this.getEntry(this.index).scrollIntoView(true);
+ },
+
+ markNext: function() {
+ if(this.index < this.entryCount-1) this.index++
+ else this.index = 0;
+ this.getEntry(this.index).scrollIntoView(false);
+ },
+
+ getEntry: function(index) {
+ return this.update.firstChild.childNodes[index];
+ },
+
+ getCurrentEntry: function() {
+ return this.getEntry(this.index);
+ },
+
+ selectEntry: function() {
+ this.active = false;
+ this.updateElement(this.getCurrentEntry());
+ },
+
+ updateElement: function(selectedElement) {
+ if (this.options.updateElement) {
+ this.options.updateElement(selectedElement);
+ return;
+ }
+ var value = '';
+ if (this.options.select) {
+ var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
+ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+ } else
+ value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+
+ var lastTokenPos = this.findLastToken();
+ if (lastTokenPos != -1) {
+ var newValue = this.element.value.substr(0, lastTokenPos + 1);
+ var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
+ if (whitespace)
+ newValue += whitespace[0];
+ this.element.value = newValue + value;
+ } else {
+ this.element.value = value;
+ }
+ this.element.focus();
+
+ if (this.options.afterUpdateElement)
+ this.options.afterUpdateElement(this.element, selectedElement);
+ },
+
+ updateChoices: function(choices) {
+ if(!this.changed && this.hasFocus) {
+ this.update.innerHTML = choices;
+ Element.cleanWhitespace(this.update);
+ Element.cleanWhitespace(this.update.firstChild);
+
+ if(this.update.firstChild && this.update.firstChild.childNodes) {
+ this.entryCount =
+ this.update.firstChild.childNodes.length;
+ for (var i = 0; i < this.entryCount; i++) {
+ var entry = this.getEntry(i);
+ entry.autocompleteIndex = i;
+ this.addObservers(entry);
+ }
+ } else {
+ this.entryCount = 0;
+ }
+
+ this.stopIndicator();
+
+ this.index = 0;
+ this.render();
+ }
+ },
+
+ addObservers: function(element) {
+ Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
+ Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+ },
+
+ onObserverEvent: function() {
+ this.changed = false;
+ if(this.getToken().length>=this.options.minChars) {
+ this.startIndicator();
+ this.getUpdatedChoices();
+ } else {
+ this.active = false;
+ this.hide();
+ }
+ },
+
+ getToken: function() {
+ var tokenPos = this.findLastToken();
+ if (tokenPos != -1)
+ var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
+ else
+ var ret = this.element.value;
+
+ return /\n/.test(ret) ? '' : ret;
+ },
+
+ findLastToken: function() {
+ var lastTokenPos = -1;
+
+ for (var i=0; i<this.options.tokens.length; i++) {
+ var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
+ if (thisTokenPos > lastTokenPos)
+ lastTokenPos = thisTokenPos;
+ }
+ return lastTokenPos;
+ }
+}
+
+Ajax.Autocompleter = Class.create();
+Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
+ initialize: function(element, update, url, options) {
+ this.baseInitialize(element, update, options);
+ this.options.asynchronous = true;
+ this.options.onComplete = this.onComplete.bind(this);
+ this.options.defaultParams = this.options.parameters || null;
+ this.url = url;
+ },
+
+ getUpdatedChoices: function() {
+ entry = encodeURIComponent(this.options.paramName) + '=' +
+ encodeURIComponent(this.getToken());
+
+ this.options.parameters = this.options.callback ?
+ this.options.callback(this.element, entry) : entry;
+
+ if(this.options.defaultParams)
+ this.options.parameters += '&' + this.options.defaultParams;
+
+ new Ajax.Request(this.url, this.options);
+ },
+
+ onComplete: function(request) {
+ this.updateChoices(request.responseText);
+ }
+
+});
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+// text only at the beginning of strings in the
+// autocomplete array. Defaults to true, which will
+// match text at the beginning of any *word* in the
+// strings in the autocomplete array. If you want to
+// search anywhere in the string, additionally set
+// the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+// a partial match (unlike minChars, which defines
+// how many characters are required to do any match
+// at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+// Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector'
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create();
+Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
+ initialize: function(element, update, array, options) {
+ this.baseInitialize(element, update, options);
+ this.options.array = array;
+ },
+
+ getUpdatedChoices: function() {
+ this.updateChoices(this.options.selector(this));
+ },
+
+ setOptions: function(options) {
+ this.options = Object.extend({
+ choices: 10,
+ partialSearch: true,
+ partialChars: 2,
+ ignoreCase: true,
+ fullSearch: false,
+ selector: function(instance) {
+ var ret = []; // Beginning matches
+ var partial = []; // Inside matches
+ var entry = instance.getToken();
+ var count = 0;
+
+ for (var i = 0; i < instance.options.array.length &&
+ ret.length < instance.options.choices ; i++) {
+
+ var elem = instance.options.array[i];
+ var foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase()) :
+ elem.indexOf(entry);
+
+ while (foundPos != -1) {
+ if (foundPos == 0 && elem.length != entry.length) {
+ ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
+ elem.substr(entry.length) + "</li>");
+ break;
+ } else if (entry.length >= instance.options.partialChars &&
+ instance.options.partialSearch && foundPos != -1) {
+ if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+ partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+ elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+ foundPos + entry.length) + "</li>");
+ break;
+ }
+ }
+
+ foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
+ elem.indexOf(entry, foundPos + 1);
+
+ }
+ }
+ if (partial.length)
+ ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
+ return "<ul>" + ret.join('') + "</ul>";
+ }
+ }, options || {});
+ }
+});
+
+// AJAX in-place editor
+//
+// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+ setTimeout(function() {
+ Field.activate(field);
+ }, 1);
+}
+
+Ajax.InPlaceEditor = Class.create();
+Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
+Ajax.InPlaceEditor.prototype = {
+ initialize: function(element, url, options) {
+ this.url = url;
+ this.element = $(element);
+
+ this.options = Object.extend({
+ okButton: true,
+ okText: "ok",
+ cancelLink: true,
+ cancelText: "cancel",
+ savingText: "Saving...",
+ clickToEditText: "Click to edit",
+ okText: "ok",
+ rows: 1,
+ onComplete: function(transport, element) {
+ new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
+ },
+ onFailure: function(transport) {
+ alert("Error communicating with the server: " + transport.responseText.stripTags());
+ },
+ callback: function(form) {
+ return Form.serialize(form);
+ },
+ handleLineBreaks: true,
+ loadingText: 'Loading...',
+ savingClassName: 'inplaceeditor-saving',
+ loadingClassName: 'inplaceeditor-loading',
+ formClassName: 'inplaceeditor-form',
+ highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
+ highlightendcolor: "#FFFFFF",
+ externalControl: null,
+ submitOnBlur: false,
+ ajaxOptions: {},
+ evalScripts: false
+ }, options || {});
+
+ if(!this.options.formId && this.element.id) {
+ this.options.formId = this.element.id + "-inplaceeditor";
+ if ($(this.options.formId)) {
+ // there's already a form with that name, don't specify an id
+ this.options.formId = null;
+ }
+ }
+
+ if (this.options.externalControl) {
+ this.options.externalControl = $(this.options.externalControl);
+ }
+
+ this.originalBackground = Element.getStyle(this.element, 'background-color');
+ if (!this.originalBackground) {
+ this.originalBackground = "transparent";
+ }
+
+ this.element.title = this.options.clickToEditText;
+
+ this.onclickListener = this.enterEditMode.bindAsEventListener(this);
+ this.mouseoverListener = this.enterHover.bindAsEventListener(this);
+ this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
+ Event.observe(this.element, 'click', this.onclickListener);
+ Event.observe(this.element, 'mouseover', this.mouseoverListener);
+ Event.observe(this.element, 'mouseout', this.mouseoutListener);
+ if (this.options.externalControl) {
+ Event.observe(this.options.externalControl, 'click', this.onclickListener);
+ Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
+ Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
+ }
+ },
+ enterEditMode: function(evt) {
+ if (this.saving) return;
+ if (this.editing) return;
+ this.editing = true;
+ this.onEnterEditMode();
+ if (this.options.externalControl) {
+ Element.hide(this.options.externalControl);
+ }
+ Element.hide(this.element);
+ this.createForm();
+ this.element.parentNode.insertBefore(this.form, this.element);
+ if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
+ // stop the event to avoid a page refresh in Safari
+ if (evt) {
+ Event.stop(evt);
+ }
+ return false;
+ },
+ createForm: function() {
+ this.form = document.createElement("form");
+ this.form.id = this.options.formId;
+ Element.addClassName(this.form, this.options.formClassName)
+ this.form.onsubmit = this.onSubmit.bind(this);
+
+ this.createEditField();
+
+ if (this.options.textarea) {
+ var br = document.createElement("br");
+ this.form.appendChild(br);
+ }
+
+ if (this.options.okButton) {
+ okButton = document.createElement("input");
+ okButton.type = "submit";
+ okButton.value = this.options.okText;
+ okButton.className = 'editor_ok_button';
+ this.form.appendChild(okButton);
+ }
+
+ if (this.options.cancelLink) {
+ cancelLink = document.createElement("a");
+ cancelLink.href = "#";
+ cancelLink.appendChild(document.createTextNode(this.options.cancelText));
+ cancelLink.onclick = this.onclickCancel.bind(this);
+ cancelLink.className = 'editor_cancel';
+ this.form.appendChild(cancelLink);
+ }
+ },
+ hasHTMLLineBreaks: function(string) {
+ if (!this.options.handleLineBreaks) return false;
+ return string.match(/<br/i) || string.match(/<p>/i);
+ },
+ convertHTMLLineBreaks: function(string) {
+ return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
+ },
+ createEditField: function() {
+ var text;
+ if(this.options.loadTextURL) {
+ text = this.options.loadingText;
+ } else {
+ text = this.getText();
+ }
+
+ var obj = this;
+
+ if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
+ this.options.textarea = false;
+ var textField = document.createElement("input");
+ textField.obj = this;
+ textField.type = "text";
+ textField.name = "value";
+ textField.value = text;
+ textField.style.backgroundColor = this.options.highlightcolor;
+ textField.className = 'editor_field';
+ var size = this.options.size || this.options.cols || 0;
+ if (size != 0) textField.size = size;
+ if (this.options.submitOnBlur)
+ textField.onblur = this.onSubmit.bind(this);
+ this.editField = textField;
+ } else {
+ this.options.textarea = true;
+ var textArea = document.createElement("textarea");
+ textArea.obj = this;
+ textArea.name = "value";
+ textArea.value = this.convertHTMLLineBreaks(text);
+ textArea.rows = this.options.rows;
+ textArea.cols = this.options.cols || 40;
+ textArea.className = 'editor_field';
+ if (this.options.submitOnBlur)
+ textArea.onblur = this.onSubmit.bind(this);
+ this.editField = textArea;
+ }
+
+ if(this.options.loadTextURL) {
+ this.loadExternalText();
+ }
+ this.form.appendChild(this.editField);
+ },
+ getText: function() {
+ return this.element.innerHTML;
+ },
+ loadExternalText: function() {
+ Element.addClassName(this.form, this.options.loadingClassName);
+ this.editField.disabled = true;
+ new Ajax.Request(
+ this.options.loadTextURL,
+ Object.extend({
+ asynchronous: true,
+ onComplete: this.onLoadedExternalText.bind(this)
+ }, this.options.ajaxOptions)
+ );
+ },
+ onLoadedExternalText: function(transport) {
+ Element.removeClassName(this.form, this.options.loadingClassName);
+ this.editField.disabled = false;
+ this.editField.value = transport.responseText.stripTags();
+ Field.scrollFreeActivate(this.editField);
+ },
+ onclickCancel: function() {
+ this.onComplete();
+ this.leaveEditMode();
+ return false;
+ },
+ onFailure: function(transport) {
+ this.options.onFailure(transport);
+ if (this.oldInnerHTML) {
+ this.element.innerHTML = this.oldInnerHTML;
+ this.oldInnerHTML = null;
+ }
+ return false;
+ },
+ onSubmit: function() {
+ // onLoading resets these so we need to save them away for the Ajax call
+ var form = this.form;
+ var value = this.editField.value;
+
+ // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
+ // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
+ // to be displayed indefinitely
+ this.onLoading();
+
+ if (this.options.evalScripts) {
+ new Ajax.Request(
+ this.url, Object.extend({
+ parameters: this.options.callback(form, value),
+ onComplete: this.onComplete.bind(this),
+ onFailure: this.onFailure.bind(this),
+ asynchronous:true,
+ evalScripts:true
+ }, this.options.ajaxOptions));
+ } else {
+ new Ajax.Updater(
+ { success: this.element,
+ // don't update on failure (this could be an option)
+ failure: null },
+ this.url, Object.extend({
+ parameters: this.options.callback(form, value),
+ onComplete: this.onComplete.bind(this),
+ onFailure: this.onFailure.bind(this)
+ }, this.options.ajaxOptions));
+ }
+ // stop the event to avoid a page refresh in Safari
+ if (arguments.length > 1) {
+ Event.stop(arguments[0]);
+ }
+ return false;
+ },
+ onLoading: function() {
+ this.saving = true;
+ this.removeForm();
+ this.leaveHover();
+ this.showSaving();
+ },
+ showSaving: function() {
+ this.oldInnerHTML = this.element.innerHTML;
+ this.element.innerHTML = this.options.savingText;
+ Element.addClassName(this.element, this.options.savingClassName);
+ this.element.style.backgroundColor = this.originalBackground;
+ Element.show(this.element);
+ },
+ removeForm: function() {
+ if(this.form) {
+ if (this.form.parentNode) Element.remove(this.form);
+ this.form = null;
+ }
+ },
+ enterHover: function() {
+ if (this.saving) return;
+ this.element.style.backgroundColor = this.options.highlightcolor;
+ if (this.effect) {
+ this.effect.cancel();
+ }
+ Element.addClassName(this.element, this.options.hoverClassName)
+ },
+ leaveHover: function() {
+ if (this.options.backgroundColor) {
+ this.element.style.backgroundColor = this.oldBackground;
+ }
+ Element.removeClassName(this.element, this.options.hoverClassName)
+ if (this.saving) return;
+ this.effect = new Effect.Highlight(this.element, {
+ startcolor: this.options.highlightcolor,
+ endcolor: this.options.highlightendcolor,
+ restorecolor: this.originalBackground
+ });
+ },
+ leaveEditMode: function() {
+ Element.removeClassName(this.element, this.options.savingClassName);
+ this.removeForm();
+ this.leaveHover();
+ this.element.style.backgroundColor = this.originalBackground;
+ Element.show(this.element);
+ if (this.options.externalControl) {
+ Element.show(this.options.externalControl);
+ }
+ 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);
+ }
+ }
+});
+
+// Delayed observer, like Form.Element.Observer,
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+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));
+ }
+};
+
+
+/**
+ * Generic postback control.
+ */
+Prado.WebUI.CallbackControl = Class.extend(Prado.WebUI.PostBackControl,
+{
+ onPostBack : function(event, options)
+ {
+ var request = new Prado.CallbackRequest(options.EventTarget, options);
+ request.dispatch();
+ Event.stop(event);
+ }
+});
+
+/**
+ * TActiveButton control.
+ */
+Prado.WebUI.TActiveButton = Class.extend(Prado.WebUI.CallbackControl);
+/**
+ * TActiveLinkButton control.
+ */
+Prado.WebUI.TActiveLinkButton = Class.extend(Prado.WebUI.CallbackControl);
+
+Prado.WebUI.TActiveImageButton = Class.extend(Prado.WebUI.TImageButton,
+{
+ onPostBack : function(event, options)
+ {
+ this.addXYInput(event,options);
+ var request = new Prado.CallbackRequest(options.EventTarget, options);
+ request.dispatch();
+ Event.stop(event);
+ }
+});
+/**
+ * Active check box.
+ */
+Prado.WebUI.TActiveCheckBox = Class.extend(Prado.WebUI.CallbackControl,
+{
+ onPostBack : function(event, options)
+ {
+ var request = new Prado.CallbackRequest(options.EventTarget, options);
+ if(request.dispatch()==false)
+ Event.stop(event);
+ }
+});
+
+/**
+ * TActiveRadioButton control.
+ */
+Prado.WebUI.TActiveRadioButton = Class.extend(Prado.WebUI.TActiveCheckBox);
+
+
+Prado.WebUI.TActiveCheckBoxList = Base.extend(
+{
+ constructor : function(options)
+ {
+ for(var i = 0; i<options.ItemCount; i++)
+ {
+ var checkBoxOptions = Object.extend(
+ {
+ ID : options.ListID+"_c"+i,
+ EventTarget : options.ListName+"$c"+i
+ }, options);
+ new Prado.WebUI.TActiveCheckBox(checkBoxOptions);
+ }
+ }
+});
+
+Prado.WebUI.TActiveRadioButtonList = Prado.WebUI.TActiveCheckBoxList;
+
+/**
+ * TActiveTextBox control, handles onchange event.
+ */
+Prado.WebUI.TActiveTextBox = Class.extend(Prado.WebUI.TTextBox,
+{
+ onInit : function(options)
+ {
+ this.options=options;
+ if(options['TextMode'] != 'MultiLine')
+ Event.observe(this.element, "keydown", this.handleReturnKey.bind(this));
+ if(this.options['AutoPostBack']==true)
+ Event.observe(this.element, "change", this.doCallback.bindEvent(this,options));
+ },
+
+ doCallback : function(event, options)
+ {
+ var request = new Prado.CallbackRequest(options.EventTarget, options);
+ request.dispatch();
+ Event.stop(event);
+ }
+});
+
+/**
+ * TAutoComplete control.
+ */
+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)
+ {
+ var request = new Prado.CallbackRequest(this.options.EventTarget, options);
+ request.dispatch();
+ 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()
+ {
+ var options = new Array(this.getToken(),"__TAutoComplete_onSuggest__");
+ Prado.Callback(this.options.EventTarget, options, null, this.options);
+ },
+
+ onComplete : function(request, boundary)
+ {
+ var result = Prado.Element.extractContent(request.transport.responseText, boundary);
+ if(typeof(result) == "string" && result.length > 0)
+ this.updateChoices(result);
+ }
+});
+
+/**
+ * Time Triggered Callback class.
+ */
+Prado.WebUI.TTimeTriggeredCallback = Base.extend(
+{
+ constructor : function(options)
+ {
+ this.options = Object.extend({ Interval : 1 }, options || {});
+ Prado.WebUI.TTimeTriggeredCallback.register(this);
+ },
+
+ startTimer : function()
+ {
+ setTimeout(this.onTimerEvent.bind(this), 100);
+ if(typeof(this.timer) == 'undefined' || this.timer == null)
+ this.timer = setInterval(this.onTimerEvent.bind(this),this.options.Interval*1000);
+ },
+
+ stopTimer : function()
+ {
+ if(typeof(this.timer) != 'undefined')
+ {
+ clearInterval(this.timer);
+ this.timer = null;
+ }
+ },
+
+ onTimerEvent : function()
+ {
+ var request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
+ request.dispatch();
+ }
+},
+//class methods
+{
+ timers : {},
+
+ register : function(timer)
+ {
+ Prado.WebUI.TTimeTriggeredCallback.timers[timer.options.ID] = timer;
+ },
+
+ start : function(id)
+ {
+ Prado.WebUI.TTimeTriggeredCallback.timers[id].startTimer();
+ },
+
+ stop : function(id)
+ {
+ Prado.WebUI.TTimeTriggeredCallback.timers[id].stopTimer();
+ }
+});
+
+Prado.WebUI.ActiveListControl = Base.extend(
+{
+ constructor : function(options)
+ {
+ this.element = $(options.ID);
+ this.options = options;
+ Event.observe(this.element, "change", this.doCallback.bind(this));
+ },
+
+ doCallback : function(event)
+ {
+ var request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
+ request.dispatch();
+ Event.stop(event);
+ }
+});
+
+Prado.WebUI.TActiveDropDownList = Prado.WebUI.ActiveListControl;
+Prado.WebUI.TActiveListBox = Prado.WebUI.ActiveListControl;
+
+/**
+ * Observe event of a particular control to trigger a callback request.
+ */
+Prado.WebUI.TEventTriggeredCallback = Base.extend(
+{
+ constructor : function(options)
+ {
+ this.options = options;
+ var element = $(options['ControlID']);
+ if(element)
+ Event.observe(element, this.getEventName(element), this.doCallback.bind(this));
+ },
+
+ getEventName : function(element)
+ {
+ var name = this.options.EventName;
+ if(typeof(name) == "undefined" && element.type)
+ {
+ switch (element.type.toLowerCase())
+ {
+ case 'password':
+ case 'text':
+ case 'textarea':
+ case 'select-one':
+ case 'select-multiple':
+ return 'change';
+ }
+ }
+ return typeof(name) == "undefined" || name == "undefined" ? 'click' : name;
+ },
+
+ doCallback : function(event)
+ {
+ var request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
+ request.dispatch();
+ if(this.options.StopEvent == true)
+ Event.stop(event);
+ }
+});
+
+/**
+ * Observe changes to a property of a particular control to trigger a callback.
+ */
+Prado.WebUI.TValueTriggeredCallback = Base.extend(
+{
+ count : 1,
+
+ observing : true,
+
+ constructor : function(options)
+ {
+ this.options = options;
+ this.options.PropertyName = this.options.PropertyName || 'value';
+ var element = $(options['ControlID']);
+ this.value = element ? element[this.options.PropertyName] : undefined;
+ Prado.WebUI.TValueTriggeredCallback.register(this);
+ this.startObserving();
+ },
+
+ stopObserving : function()
+ {
+ clearTimeout(this.timer);
+ this.observing = false;
+ },
+
+ startObserving : function()
+ {
+ this.timer = setTimeout(this.checkChanges.bind(this), this.options.Interval*1000);
+ },
+
+ checkChanges : function()
+ {
+ var element = $(this.options.ControlID);
+ if(element)
+ {
+ var value = element[this.options.PropertyName];
+ if(this.value != value)
+ {
+ this.doCallback(this.value, value);
+ this.value = value;
+ this.count=1;
+ }
+ else
+ this.count = this.count + this.options.Decay;
+ if(this.observing)
+ this.time = setTimeout(this.checkChanges.bind(this),
+ parseInt(this.options.Interval*1000*this.count));
+ }
+ },
+
+ doCallback : function(oldValue, newValue)
+ {
+ var request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
+ var param = {'OldValue' : oldValue, 'NewValue' : newValue};
+ request.setCallbackParameter(param);
+ request.dispatch();
+ }
+},
+//class methods
+{
+ timers : {},
+
+ register : function(timer)
+ {
+ Prado.WebUI.TValueTriggeredCallback.timers[timer.options.ID] = timer;
+ },
+
+ stop : function(id)
+ {
+ Prado.WebUI.TValueTriggeredCallback.timers[id].stopObserving();
+ }
+});
+
+
+Prado.WebUI.TInPlaceTextBox = Base.extend(
+{
+ isSaving : false,
+ isEditing : false,
+ editField : null,
+
+ constructor : function(options)
+ {
+ this.options = Object.extend(
+ {
+ LoadTextFromSource : false,
+ TextMode : 'SingleLine'
+
+ }, options || {});
+ this.element = $(this.options.ID);
+ Prado.WebUI.TInPlaceTextBox.register(this);
+ this.createEditorInput();
+ this.initializeListeners();
+ },
+
+ /**
+ * Initialize the listeners.
+ */
+ initializeListeners : function()
+ {
+ this.onclickListener = this.enterEditMode.bindAsEventListener(this);
+ Event.observe(this.element, 'click', this.onclickListener);
+ if (this.options.ExternalControl)
+ Event.observe($(this.options.ExternalControl), 'click', this.onclickListener);
+ },
+
+ /**
+ * Changes the panel to an editable input.
+ * @param {Event} evt event source
+ */
+ enterEditMode : function(evt)
+ {
+ if (this.isSaving || this.isEditing) return;
+ this.isEditing = true;
+ this.onEnterEditMode();
+ this.createEditorInput();
+ this.showTextBox();
+ this.editField.disabled = false;
+ if(this.options.LoadTextOnEdit)
+ this.loadExternalText();
+ Prado.Element.focus(this.editField);
+ if (evt)
+ Event.stop(evt);
+ return false;
+ },
+
+ exitEditMode : function(evt)
+ {
+ this.isEditing = false;
+ this.isSaving = false;
+ this.editField.disabled = false;
+ this.element.innerHTML = this.editField.value;
+ this.showLabel();
+ },
+
+ showTextBox : function()
+ {
+ Element.hide(this.element);
+ Element.show(this.editField);
+ },
+
+ showLabel : function()
+ {
+ Element.show(this.element);
+ Element.hide(this.editField);
+ },
+
+ /**
+ * Create the edit input field.
+ */
+ createEditorInput : function()
+ {
+ if(this.editField == null)
+ this.createTextBox();
+
+ this.editField.value = this.getText();
+ },
+
+ loadExternalText : function()
+ {
+ this.editField.disabled = true;
+ this.onLoadingText();
+ options = new Array('__InlineEditor_loadExternalText__', this.getText());
+ request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
+ request.setCausesValidation(false);
+ request.setCallbackParameter(options);
+ request.options.onSuccess = this.onloadExternalTextSuccess.bind(this);
+ request.options.onFailure = this.onloadExternalTextFailure.bind(this);
+ request.dispatch();
+ },
+
+ /**
+ * Create a new input textbox or textarea
+ */
+ createTextBox : function()
+ {
+ cssClass= this.element.className || '';
+ inputName = this.options.EventTarget;
+ options = {'className' : cssClass, name : inputName, id : this.options.TextBoxID};
+ if(this.options.TextMode == 'SingleLine')
+ {
+ if(this.options.MaxLength > 0)
+ options['maxlength'] = this.options.MaxLength;
+ this.editField = INPUT(options);
+ }
+ else
+ {
+ if(this.options.Rows > 0)
+ options['rows'] = this.options.Rows;
+ if(this.options.Columns > 0)
+ options['cols'] = this.options.Columns;
+ if(this.options.Wrap)
+ options['wrap'] = 'off';
+ this.editField = TEXTAREA(options);
+ }
+
+ this.editField.style.display="none";
+ this.element.parentNode.insertBefore(this.editField,this.element)
+
+ //handle return key within single line textbox
+ if(this.options.TextMode == 'SingleLine')
+ {
+ Event.observe(this.editField, "keydown", function(e)
+ {
+ if(Event.keyCode(e) == Event.KEY_RETURN)
+ {
+ var target = Event.element(e);
+ if(target)
+ {
+ Event.fireEvent(target, "blur");
+ Event.stop(e);
+ }
+ }
+ });
+ }
+
+ Event.observe(this.editField, "blur", this.onTextBoxBlur.bind(this));
+ },
+
+ /**
+ * @return {String} panel inner html text.
+ */
+ getText: function()
+ {
+ return this.element.innerHTML;
+ },
+
+ /**
+ * Edit mode entered, calls optional event handlers.
+ */
+ onEnterEditMode : function()
+ {
+ if(typeof(this.options.onEnterEditMode) == "function")
+ this.options.onEnterEditMode(this,null);
+ },
+
+ onTextBoxBlur : function(e)
+ {
+ text = this.element.innerHTML;
+ if(this.options.AutoPostBack && text != this.editField.value)
+ this.onTextChanged(text);
+ else
+ {
+ this.element.innerHTML = this.editField.value;
+ this.isEditing = false;
+ if(this.options.AutoHide)
+ this.showLabel();
+ }
+ },
+
+ /**
+ * When the text input value has changed.
+ * @param {String} original text
+ */
+ onTextChanged : function(text)
+ {
+ request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
+ request.setCallbackParameter(text);
+ request.options.onSuccess = this.onTextChangedSuccess.bind(this);
+ request.options.onFailure = this.onTextChangedFailure.bind(this);
+ if(request.dispatch())
+ {
+ this.isSaving = true;
+ this.editField.disabled = true;
+ }
+ },
+
+ /**
+ * When loading external text.
+ */
+ onLoadingText : function()
+ {
+ //Logger.info("on loading text");
+ },
+
+ onloadExternalTextSuccess : function(request, parameter)
+ {
+ this.isEditing = true;
+ this.editField.disabled = false;
+ this.editField.value = this.getText();
+ Prado.Element.focus(this.editField);
+ if(typeof(this.options.onSuccess)=="function")
+ this.options.onSuccess(sender,parameter);
+ },
+
+ onloadExternalTextFailure : function(request, parameter)
+ {
+ this.isSaving = false;
+ this.isEditing = false;
+ this.showLabel();
+ if(typeof(this.options.onFailure)=="function")
+ this.options.onFailure(sender,parameter);
+ },
+
+ /**
+ * Text change successfully.
+ * @param {Object} sender
+ * @param {Object} parameter
+ */
+ onTextChangedSuccess : function(sender, parameter)
+ {
+ this.isSaving = false;
+ this.isEditing = false;
+ if(this.options.AutoHide)
+ this.showLabel();
+ this.element.innerHTML = parameter == null ? this.editField.value : parameter;
+ this.editField.disabled = false;
+ if(typeof(this.options.onSuccess)=="function")
+ this.options.onSuccess(sender,parameter);
+ },
+
+ onTextChangedFailure : function(sender, parameter)
+ {
+ this.editField.disabled = false;
+ this.isSaving = false;
+ this.isEditing = false;
+ if(typeof(this.options.onFailure)=="function")
+ this.options.onFailure(sender,parameter);
+ }
+},
+{
+ textboxes : {},
+
+ register : function(obj)
+ {
+ Prado.WebUI.TInPlaceTextBox.textboxes[obj.options.TextBoxID] = obj;
+ },
+
+ setDisplayTextBox : function(id,value)
+ {
+ var textbox = Prado.WebUI.TInPlaceTextBox.textboxes[id];
+ if(textbox)
+ {
+ if(value)
+ textbox.enterEditMode(null);
+ else
+ {
+ textbox.exitEditMode(null);
+ }
+ }
+ }
+});
+
+Prado.WebUI.TRatingList = Base.extend(
+{
+ selectedIndex : -1,
+ rating: -1,
+ enabled : true,
+ readOnly : false,
+
+ constructor : function(options)
+ {
+ var cap = $(options.CaptionID);
+ this.options = Object.extend(
+ {
+ caption : cap ? cap.innerHTML : ''
+ }, options || {});
+
+ Prado.WebUI.TRatingList.register(this);
+ this._init();
+ this.selectedIndex = options.SelectedIndex;
+ this.rating = options.Rating;
+ if(options.Rating <= 0 && options.SelectedIndex >= 0)
+ this.rating = options.SelectedIndex+1;
+ this.showRating(this.rating);
+ },
+
+ _init: function(options)
+ {
+ Element.addClassName($(this.options.ListID),this.options.Style);
+ this.radios = new Array();
+ var index=0;
+ for(var i = 0; i<this.options.ItemCount; i++)
+ {
+ var radio = $(this.options.ListID+'_c'+i);
+ var td = radio.parentNode;
+ if(radio && td.tagName.toLowerCase()=='td')
+ {
+ this.radios.push(radio);
+ Event.observe(td, "mouseover", this.hover.bindEvent(this,index));
+ Event.observe(td, "mouseout", this.recover.bindEvent(this,index));
+ Event.observe(td, "click", this.click.bindEvent(this, index));
+ index++;
+ Element.addClassName(td,"rating");
+ }
+ }
+ },
+
+ hover : function(ev,index)
+ {
+ if(this.enabled==false) return;
+ for(var i = 0; i<this.radios.length; i++)
+ {
+ var node = this.radios[i].parentNode;
+ var action = i <= index ? 'addClassName' : 'removeClassName'
+ Element[action](node,"rating_hover");
+ Element.removeClassName(node,"rating_selected");
+ Element.removeClassName(node,"rating_half");
+ }
+ this.showCaption(this.getIndexCaption(index));
+ },
+
+ recover : function(ev,index)
+ {
+ if(this.enabled==false) return;
+ this.showRating(this.rating);
+ this.showCaption(this.options.caption);
+ },
+
+ click : function(ev, index)
+ {
+ if(this.enabled==false) return;
+ for(var i = 0; i<this.radios.length; i++)
+ this.radios[i].checked = (i == index);
+
+ this.selectedIndex = index;
+ this.setRating(index+1);
+
+ this.dispatchRequest(ev);
+ },
+
+ dispatchRequest : function(ev)
+ {
+ var requestOptions = Object.extend(
+ {
+ ID : this.options.ListID+"_c"+this.selectedIndex,
+ EventTarget : this.options.ListName+"$c"+this.selectedIndex
+ },this.options);
+ var request = new Prado.CallbackRequest(requestOptions.EventTarget, requestOptions);
+ if(request.dispatch()==false)
+ Event.stop(ev);
+ },
+
+ setRating : function(value)
+ {
+ this.rating = value;
+ var base = Math.floor(value-1);
+ var remainder = value - base-1;
+ var halfMax = this.options.HalfRating["1"];
+ var index = remainder > halfMax ? base+1 : base;
+ for(var i = 0; i<this.radios.length; i++)
+ this.radios[i].checked = (i == index);
+
+ var caption = this.getIndexCaption(index);
+ this.setCaption(caption);
+ this.showCaption(caption);
+
+ this.showRating(value);
+ },
+
+ showRating: function(value)
+ {
+ var base = Math.floor(value-1);
+ var remainder = value - base-1;
+ var halfMin = this.options.HalfRating["0"];
+ var halfMax = this.options.HalfRating["1"];
+ var index = remainder > halfMax ? base+1 : base;
+ var hasHalf = remainder >= halfMin && remainder <= halfMax;
+ for(var i = 0; i<this.radios.length; i++)
+ {
+ var node = this.radios[i].parentNode;
+ var action = i > index ? 'removeClassName' : 'addClassName';
+ Element[action](node, "rating_selected");
+ if(i==index+1 && hasHalf)
+ Element.addClassName(node, "rating_half");
+ else
+ Element.removeClassName(node, "rating_half");
+ Element.removeClassName(node,"rating_hover");
+ }
+ },
+
+ getIndexCaption : function(index)
+ {
+ return index > -1 ? this.radios[index].value : this.options.caption;
+ },
+
+ showCaption : function(value)
+ {
+ var caption = $(this.options.CaptionID);
+ if(caption) caption.innerHTML = value;
+ $(this.options.ListID).title = value;
+ },
+
+ setCaption : function(value)
+ {
+ this.options.caption = value;
+ this.showCaption(value);
+ },
+
+ setEnabled : function(value)
+ {
+ this.enabled = value;
+ for(var i = 0; i<this.radios.length; i++)
+ {
+ var action = value ? 'removeClassName' : 'addClassName'
+ Element[action](this.radios[i].parentNode, "rating_disabled");
+ }
+ }
+},
+{
+ratings : {},
+register : function(rating)
+{
+ Prado.WebUI.TRatingList.ratings[rating.options.ListID] = rating;
+},
+
+setEnabled : function(id,value)
+{
+ Prado.WebUI.TRatingList.ratings[id].setEnabled(value);
+},
+
+setRating : function(id,value)
+{
+ Prado.WebUI.TRatingList.ratings[id].setRating(value);
+},
+
+setCaption : function(id,value)
+{
+ Prado.WebUI.TRatingList.ratings[id].setCaption(value);
+}
+});
+
diff --git a/demos/currency-converter/assets/2bffd82d/clientscripts.php b/demos/currency-converter/assets/2bffd82d/clientscripts.php
new file mode 100644
index 00000000..3ac3b062
--- /dev/null
+++ b/demos/currency-converter/assets/2bffd82d/clientscripts.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * This file compresses the javascript files using GZip
+ *
+ * Todo:
+ * - Add local file cache for the GZip:ed version.
+ */
+
+$debugMode=(isset($_GET['mode']) && $_GET['mode']==='debug');
+
+// if debug mode, js is not cached; otherwise cached for 10 days.
+$expiresOffset = $debugMode ? -10000 : 3600 * 24 * 10; //no cache
+
+//allowed libraries
+$library = array('prado', 'effects', 'ajax', 'validator', 'logger', 'datepicker', 'rico', 'colorpicker');
+
+$param = isset($_GET['js']) ? $_GET['js'] : '';
+
+//check for proper matching parameters, otherwise exit;
+if(preg_match('/(\w)+(,\w+)*/', $param)) $js = explode(',', $param); else exit();
+foreach($js as $lib) if(!in_array($lib, $library)) exit();
+
+// Only gzip the contents if clients and server support it
+if (isset($_SERVER['HTTP_ACCEPT_ENCODING']))
+ $encodings = explode(',', strtolower($_SERVER['HTTP_ACCEPT_ENCODING']));
+else
+ $encodings = array();
+
+// Check for gzip header or northon internet securities
+if ((in_array('gzip', $encodings) || isset($_SERVER['---------------']))
+ && function_exists('ob_gzhandler') && !ini_get('zlib.output_compression')
+ && ini_get('output_handler') != 'ob_gzhandler')
+ ob_start("ob_gzhandler");
+
+// Output rest of headers
+header('Content-type: text/javascript; charset: UTF-8');
+// header("Cache-Control: must-revalidate");
+header('Vary: Accept-Encoding'); // Handle proxies
+header('Expires: ' . @gmdate('D, d M Y H:i:s', @time() + $expiresOffset) . ' GMT');
+
+if ($debugMode)
+{
+ foreach($js as $lib)
+ {
+ $file = realpath($lib.'.js');
+ if(is_file($file))
+ echo file_get_contents($file);
+ else //log missings files to console logger
+ {
+ echo 'setTimeout(function(){ if(Logger) Logger.error("Missing file", "'.$lib.'.js"); }, 1000);';
+ error_log("Unable to find asset file {$lib}.js");
+ }
+ }
+}
+else
+{
+ foreach($js as $lib)
+ echo file_get_contents($lib.'.js');
+}
+
+?> \ No newline at end of file
diff --git a/demos/currency-converter/assets/2bffd82d/effects.js b/demos/currency-converter/assets/2bffd82d/effects.js
new file mode 100644
index 00000000..c0f3c4af
--- /dev/null
+++ b/demos/currency-converter/assets/2bffd82d/effects.js
@@ -0,0 +1,968 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+// Justin Palmer (http://encytemedia.com/)
+// Mark Pilgrim (http://diveintomark.org/)
+// Martin Bialasinki
+//
+// See scriptaculous.js for full license.
+
+// converts rgb() and #xxx to #xxxxxx format,
+// returns self (or first argument) if not convertable
+String.prototype.parseColor = function() {
+ var color = '#';
+ if(this.slice(0,4) == 'rgb(') {
+ var cols = this.slice(4,this.length-1).split(',');
+ var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
+ } else {
+ if(this.slice(0,1) == '#') {
+ if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
+ if(this.length==7) color = this.toLowerCase();
+ }
+ }
+ return(color.length==7 ? color : (arguments[0] || this));
+}
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {
+ return $A($(element).childNodes).collect( function(node) {
+ return (node.nodeType==3 ? node.nodeValue :
+ (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+ }).flatten().join('');
+}
+
+Element.collectTextNodesIgnoreClass = function(element, className) {
+ return $A($(element).childNodes).collect( function(node) {
+ return (node.nodeType==3 ? node.nodeValue :
+ ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
+ Element.collectTextNodesIgnoreClass(node, className) : ''));
+ }).flatten().join('');
+}
+
+Element.setContentZoom = function(element, percent) {
+ element = $(element);
+ Element.setStyle(element, {fontSize: (percent/100) + 'em'});
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+}
+
+Element.getOpacity = function(element){
+ var opacity;
+ if (opacity = Element.getStyle(element, 'opacity'))
+ return parseFloat(opacity);
+ if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))
+ if(opacity[1]) return parseFloat(opacity[1]) / 100;
+ return 1.0;
+}
+
+Element.setOpacity = function(element, value){
+ element= $(element);
+ if (value == 1){
+ Element.setStyle(element, { opacity:
+ (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
+ 0.999999 : null });
+ if(/MSIE/.test(navigator.userAgent))
+ Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
+ } else {
+ if(value < 0.00001) value = 0;
+ Element.setStyle(element, {opacity: value});
+ if(/MSIE/.test(navigator.userAgent))
+ Element.setStyle(element,
+ { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
+ 'alpha(opacity='+value*100+')' });
+ }
+}
+
+Element.getInlineOpacity = function(element){
+ return $(element).style.opacity || '';
+}
+
+Element.childrenWithClassName = function(element, className, findFirst) {
+ var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
+ var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) {
+ return (c.className && c.className.match(classNameRegExp));
+ });
+ if(!results) results = [];
+ return results;
+}
+
+Element.forceRerendering = function(element) {
+ try {
+ element = $(element);
+ var n = document.createTextNode(' ');
+ element.appendChild(n);
+ element.removeChild(n);
+ } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Array.prototype.call = function() {
+ var args = arguments;
+ this.each(function(f){ f.apply(this, args) });
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+ tagifyText: function(element) {
+ if(typeof Builder == 'undefined')
+ throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
+
+ var tagifyStyle = 'position:relative';
+ if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
+ element = $(element);
+ $A(element.childNodes).each( function(child) {
+ if(child.nodeType==3) {
+ child.nodeValue.toArray().each( function(character) {
+ element.insertBefore(
+ Builder.node('span',{style: tagifyStyle},
+ character == ' ' ? String.fromCharCode(160) : character),
+ child);
+ });
+ Element.remove(child);
+ }
+ });
+ },
+ multiple: function(element, effect) {
+ var elements;
+ if(((typeof element == 'object') ||
+ (typeof element == 'function')) &&
+ (element.length))
+ elements = element;
+ else
+ elements = $(element).childNodes;
+
+ var options = Object.extend({
+ speed: 0.1,
+ delay: 0.0
+ }, arguments[2] || {});
+ var masterDelay = options.delay;
+
+ $A(elements).each( function(element, index) {
+ new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+ });
+ },
+ PAIRS: {
+ 'slide': ['SlideDown','SlideUp'],
+ 'blind': ['BlindDown','BlindUp'],
+ 'appear': ['Appear','Fade']
+ },
+ toggle: function(element, effect) {
+ element = $(element);
+ effect = (effect || 'appear').toLowerCase();
+ var options = Object.extend({
+ queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+ }, arguments[2] || {});
+ Effect[element.visible() ?
+ Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
+ }
+};
+
+var Effect2 = Effect; // deprecated
+
+/* ------------- transitions ------------- */
+
+Effect.Transitions = {}
+
+Effect.Transitions.linear = Prototype.K;
+
+Effect.Transitions.sinoidal = function(pos) {
+ return (-Math.cos(pos*Math.PI)/2) + 0.5;
+}
+Effect.Transitions.reverse = function(pos) {
+ return 1-pos;
+}
+Effect.Transitions.flicker = function(pos) {
+ return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+}
+Effect.Transitions.wobble = function(pos) {
+ return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+}
+Effect.Transitions.pulse = function(pos) {
+ return (Math.floor(pos*10) % 2 == 0 ?
+ (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
+}
+Effect.Transitions.none = function(pos) {
+ return 0;
+}
+Effect.Transitions.full = function(pos) {
+ return 1;
+}
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create();
+Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
+ initialize: function() {
+ this.effects = [];
+ this.interval = null;
+ },
+ _each: function(iterator) {
+ this.effects._each(iterator);
+ },
+ add: function(effect) {
+ var timestamp = new Date().getTime();
+
+ var position = (typeof effect.options.queue == 'string') ?
+ effect.options.queue : effect.options.queue.position;
+
+ switch(position) {
+ case 'front':
+ // move unstarted effects after this effect
+ this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+ e.startOn += effect.finishOn;
+ e.finishOn += effect.finishOn;
+ });
+ break;
+ case 'end':
+ // start effect after last queued effect has finished
+ timestamp = this.effects.pluck('finishOn').max() || timestamp;
+ break;
+ }
+
+ effect.startOn += timestamp;
+ effect.finishOn += timestamp;
+
+ if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
+ this.effects.push(effect);
+
+ if(!this.interval)
+ this.interval = setInterval(this.loop.bind(this), 40);
+ },
+ remove: function(effect) {
+ this.effects = this.effects.reject(function(e) { return e==effect });
+ if(this.effects.length == 0) {
+ clearInterval(this.interval);
+ this.interval = null;
+ }
+ },
+ loop: function() {
+ var timePos = new Date().getTime();
+ this.effects.invoke('loop', timePos);
+ }
+});
+
+Effect.Queues = {
+ instances: $H(),
+ get: function(queueName) {
+ if(typeof queueName != 'string') return queueName;
+
+ if(!this.instances[queueName])
+ this.instances[queueName] = new Effect.ScopedQueue();
+
+ return this.instances[queueName];
+ }
+}
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.DefaultOptions = {
+ transition: Effect.Transitions.sinoidal,
+ duration: 1.0, // seconds
+ fps: 25.0, // max. 25fps due to Effect.Queue implementation
+ sync: false, // true for combining
+ from: 0.0,
+ to: 1.0,
+ delay: 0.0,
+ queue: 'parallel'
+}
+
+Effect.Base = function() {};
+Effect.Base.prototype = {
+ position: null,
+ start: function(options) {
+ this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
+ this.currentFrame = 0;
+ this.state = 'idle';
+ this.startOn = this.options.delay*1000;
+ this.finishOn = this.startOn + (this.options.duration*1000);
+ this.event('beforeStart');
+ if(!this.options.sync)
+ Effect.Queues.get(typeof this.options.queue == 'string' ?
+ 'global' : this.options.queue.scope).add(this);
+ },
+ loop: function(timePos) {
+ if(timePos >= this.startOn) {
+ if(timePos >= this.finishOn) {
+ this.render(1.0);
+ this.cancel();
+ this.event('beforeFinish');
+ if(this.finish) this.finish();
+ this.event('afterFinish');
+ return;
+ }
+ var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
+ var frame = Math.round(pos * this.options.fps * this.options.duration);
+ if(frame > this.currentFrame) {
+ this.render(pos);
+ this.currentFrame = frame;
+ }
+ }
+ },
+ render: function(pos) {
+ if(this.state == 'idle') {
+ this.state = 'running';
+ this.event('beforeSetup');
+ if(this.setup) this.setup();
+ this.event('afterSetup');
+ }
+ if(this.state == 'running') {
+ if(this.options.transition) pos = this.options.transition(pos);
+ pos *= (this.options.to-this.options.from);
+ pos += this.options.from;
+ this.position = pos;
+ this.event('beforeUpdate');
+ if(this.update) this.update(pos);
+ this.event('afterUpdate');
+ }
+ },
+ cancel: function() {
+ if(!this.options.sync)
+ Effect.Queues.get(typeof this.options.queue == 'string' ?
+ 'global' : this.options.queue.scope).remove(this);
+ this.state = 'finished';
+ },
+ event: function(eventName) {
+ if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+ if(this.options[eventName]) this.options[eventName](this);
+ },
+ inspect: function() {
+ return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
+ }
+}
+
+Effect.Parallel = Class.create();
+Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
+ initialize: function(effects) {
+ this.effects = effects || [];
+ this.start(arguments[1]);
+ },
+ update: function(position) {
+ this.effects.invoke('render', position);
+ },
+ finish: function(position) {
+ this.effects.each( function(effect) {
+ effect.render(1.0);
+ effect.cancel();
+ effect.event('beforeFinish');
+ if(effect.finish) effect.finish(position);
+ effect.event('afterFinish');
+ });
+ }
+});
+
+Effect.Opacity = Class.create();
+Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
+ initialize: function(element) {
+ this.element = $(element);
+ // make this work on IE on elements without 'layout'
+ if(/MSIE/.test(navigator.userAgent) && (!this.element.currentStyle.hasLayout))
+ this.element.setStyle({zoom: 1});
+ var options = Object.extend({
+ from: this.element.getOpacity() || 0.0,
+ to: 1.0
+ }, arguments[1] || {});
+ this.start(options);
+ },
+ update: function(position) {
+ this.element.setOpacity(position);
+ }
+});
+
+Effect.Move = Class.create();
+Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
+ initialize: function(element) {
+ this.element = $(element);
+ var options = Object.extend({
+ x: 0,
+ y: 0,
+ mode: 'relative'
+ }, arguments[1] || {});
+ this.start(options);
+ },
+ setup: function() {
+ // Bug in Opera: Opera returns the "real" position of a static element or
+ // relative element that does not have top/left explicitly set.
+ // ==> Always set top and left for position relative elements in your stylesheets
+ // (to 0 if you do not need them)
+ this.element.makePositioned();
+ this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+ this.originalTop = parseFloat(this.element.getStyle('top') || '0');
+ if(this.options.mode == 'absolute') {
+ // absolute movement, so we need to calc deltaX and deltaY
+ this.options.x = this.options.x - this.originalLeft;
+ this.options.y = this.options.y - this.originalTop;
+ }
+ },
+ update: function(position) {
+ this.element.setStyle({
+ left: Math.round(this.options.x * position + this.originalLeft) + 'px',
+ top: Math.round(this.options.y * position + this.originalTop) + 'px'
+ });
+ }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+ return new Effect.Move(element,
+ Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
+};
+
+Effect.Scale = Class.create();
+Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
+ initialize: function(element, percent) {
+ this.element = $(element)
+ var options = Object.extend({
+ scaleX: true,
+ scaleY: true,
+ scaleContent: true,
+ scaleFromCenter: false,
+ scaleMode: 'box', // 'box' or 'contents' or {} with provided values
+ scaleFrom: 100.0,
+ scaleTo: percent
+ }, arguments[2] || {});
+ this.start(options);
+ },
+ setup: function() {
+ this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+ this.elementPositioning = this.element.getStyle('position');
+
+ this.originalStyle = {};
+ ['top','left','width','height','fontSize'].each( function(k) {
+ this.originalStyle[k] = this.element.style[k];
+ }.bind(this));
+
+ this.originalTop = this.element.offsetTop;
+ this.originalLeft = this.element.offsetLeft;
+
+ var fontSize = this.element.getStyle('font-size') || '100%';
+ ['em','px','%','pt'].each( function(fontSizeType) {
+ if(fontSize.indexOf(fontSizeType)>0) {
+ this.fontSize = parseFloat(fontSize);
+ this.fontSizeType = fontSizeType;
+ }
+ }.bind(this));
+
+ this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+
+ this.dims = null;
+ if(this.options.scaleMode=='box')
+ this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+ if(/^content/.test(this.options.scaleMode))
+ this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+ if(!this.dims)
+ this.dims = [this.options.scaleMode.originalHeight,
+ this.options.scaleMode.originalWidth];
+ },
+ update: function(position) {
+ var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+ if(this.options.scaleContent && this.fontSize)
+ this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
+ this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+ },
+ finish: function(position) {
+ if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+ },
+ setDimensions: function(height, width) {
+ var d = {};
+ if(this.options.scaleX) d.width = Math.round(width) + 'px';
+ if(this.options.scaleY) d.height = Math.round(height) + 'px';
+ if(this.options.scaleFromCenter) {
+ var topd = (height - this.dims[0])/2;
+ var leftd = (width - this.dims[1])/2;
+ if(this.elementPositioning == 'absolute') {
+ if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
+ if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+ } else {
+ if(this.options.scaleY) d.top = -topd + 'px';
+ if(this.options.scaleX) d.left = -leftd + 'px';
+ }
+ }
+ this.element.setStyle(d);
+ }
+});
+
+Effect.Highlight = Class.create();
+Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
+ initialize: function(element) {
+ this.element = $(element);
+ var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
+ this.start(options);
+ },
+ setup: function() {
+ // Prevent executing on elements not in the layout flow
+ if(this.element.getStyle('display')=='none') { this.cancel(); return; }
+ // Disable background image during the effect
+ this.oldStyle = {
+ backgroundImage: this.element.getStyle('background-image') };
+ this.element.setStyle({backgroundImage: 'none'});
+ if(!this.options.endcolor)
+ this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+ if(!this.options.restorecolor)
+ this.options.restorecolor = this.element.getStyle('background-color');
+ // init color calculations
+ this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+ this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+ },
+ update: function(position) {
+ this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+ return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
+ },
+ finish: function() {
+ this.element.setStyle(Object.extend(this.oldStyle, {
+ backgroundColor: this.options.restorecolor
+ }));
+ }
+});
+
+Effect.ScrollTo = Class.create();
+Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
+ initialize: function(element) {
+ this.element = $(element);
+ this.start(arguments[1] || {});
+ },
+ setup: function() {
+ Position.prepare();
+ var offsets = Position.cumulativeOffset(this.element);
+ if(this.options.offset) offsets[1] += this.options.offset;
+ var max = window.innerHeight ?
+ window.height - window.innerHeight :
+ document.body.scrollHeight -
+ (document.documentElement.clientHeight ?
+ document.documentElement.clientHeight : document.body.clientHeight);
+ this.scrollStart = Position.deltaY;
+ this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
+ },
+ update: function(position) {
+ Position.prepare();
+ window.scrollTo(Position.deltaX,
+ this.scrollStart + (position*this.delta));
+ }
+});
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+ element = $(element);
+ var oldOpacity = element.getInlineOpacity();
+ var options = Object.extend({
+ from: element.getOpacity() || 1.0,
+ to: 0.0,
+ afterFinishInternal: function(effect) {
+ if(effect.options.to!=0) return;
+ effect.element.hide();
+ effect.element.setStyle({opacity: oldOpacity});
+ }}, arguments[1] || {});
+ return new Effect.Opacity(element,options);
+}
+
+Effect.Appear = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
+ to: 1.0,
+ // force Safari to render floated elements properly
+ afterFinishInternal: function(effect) {
+ effect.element.forceRerendering();
+ },
+ beforeSetup: function(effect) {
+ effect.element.setOpacity(effect.options.from);
+ effect.element.show();
+ }}, arguments[1] || {});
+ return new Effect.Opacity(element,options);
+}
+
+Effect.Puff = function(element) {
+ element = $(element);
+ var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') };
+ return new Effect.Parallel(
+ [ new Effect.Scale(element, 200,
+ { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
+ Object.extend({ duration: 1.0,
+ beforeSetupInternal: function(effect) {
+ effect.effects[0].element.setStyle({position: 'absolute'}); },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide();
+ effect.effects[0].element.setStyle(oldStyle); }
+ }, arguments[1] || {})
+ );
+}
+
+Effect.BlindUp = function(element) {
+ element = $(element);
+ element.makeClipping();
+ return new Effect.Scale(element, 0,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ restoreAfterFinish: true,
+ afterFinishInternal: function(effect) {
+ effect.element.hide();
+ effect.element.undoClipping();
+ }
+ }, arguments[1] || {})
+ );
+}
+
+Effect.BlindDown = function(element) {
+ element = $(element);
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, 100, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ scaleFrom: 0,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makeClipping();
+ effect.element.setStyle({height: '0px'});
+ effect.element.show();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.undoClipping();
+ }
+ }, arguments[1] || {}));
+}
+
+Effect.SwitchOff = function(element) {
+ element = $(element);
+ var oldOpacity = element.getInlineOpacity();
+ return new Effect.Appear(element, Object.extend({
+ duration: 0.4,
+ from: 0,
+ transition: Effect.Transitions.flicker,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(effect.element, 1, {
+ duration: 0.3, scaleFromCenter: true,
+ scaleX: false, scaleContent: false, restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ effect.element.makePositioned();
+ effect.element.makeClipping();
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.hide();
+ effect.element.undoClipping();
+ effect.element.undoPositioned();
+ effect.element.setStyle({opacity: oldOpacity});
+ }
+ })
+ }
+ }, arguments[1] || {}));
+}
+
+Effect.DropOut = function(element) {
+ element = $(element);
+ var oldStyle = {
+ top: element.getStyle('top'),
+ left: element.getStyle('left'),
+ opacity: element.getInlineOpacity() };
+ return new Effect.Parallel(
+ [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+ Object.extend(
+ { duration: 0.5,
+ beforeSetup: function(effect) {
+ effect.effects[0].element.makePositioned();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide();
+ effect.effects[0].element.undoPositioned();
+ effect.effects[0].element.setStyle(oldStyle);
+ }
+ }, arguments[1] || {}));
+}
+
+Effect.Shake = function(element) {
+ element = $(element);
+ var oldStyle = {
+ top: element.getStyle('top'),
+ left: element.getStyle('left') };
+ return new Effect.Move(element,
+ { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
+ new Effect.Move(effect.element,
+ { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
+ effect.element.undoPositioned();
+ effect.element.setStyle(oldStyle);
+ }}) }}) }}) }}) }}) }});
+}
+
+Effect.SlideDown = function(element) {
+ element = $(element);
+ element.cleanWhitespace();
+ // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+ var oldInnerBottom = $(element.firstChild).getStyle('bottom');
+ var elementDimensions = element.getDimensions();
+ return new Effect.Scale(element, 100, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ scaleFrom: window.opera ? 0 : 1,
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ effect.element.makePositioned();
+ effect.element.firstChild.makePositioned();
+ if(window.opera) effect.element.setStyle({top: ''});
+ effect.element.makeClipping();
+ effect.element.setStyle({height: '0px'});
+ effect.element.show(); },
+ afterUpdateInternal: function(effect) {
+ effect.element.firstChild.setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' });
+ },
+ afterFinishInternal: function(effect) {
+ effect.element.undoClipping();
+ // IE will crash if child is undoPositioned first
+ if(/MSIE/.test(navigator.userAgent)){
+ effect.element.undoPositioned();
+ effect.element.firstChild.undoPositioned();
+ }else{
+ effect.element.firstChild.undoPositioned();
+ effect.element.undoPositioned();
+ }
+ effect.element.firstChild.setStyle({bottom: oldInnerBottom}); }
+ }, arguments[1] || {})
+ );
+}
+
+Effect.SlideUp = function(element) {
+ element = $(element);
+ element.cleanWhitespace();
+ var oldInnerBottom = $(element.firstChild).getStyle('bottom');
+ return new Effect.Scale(element, window.opera ? 0 : 1,
+ Object.extend({ scaleContent: false,
+ scaleX: false,
+ scaleMode: 'box',
+ scaleFrom: 100,
+ restoreAfterFinish: true,
+ beforeStartInternal: function(effect) {
+ effect.element.makePositioned();
+ effect.element.firstChild.makePositioned();
+ if(window.opera) effect.element.setStyle({top: ''});
+ effect.element.makeClipping();
+ effect.element.show(); },
+ afterUpdateInternal: function(effect) {
+ effect.element.firstChild.setStyle({bottom:
+ (effect.dims[0] - effect.element.clientHeight) + 'px' }); },
+ afterFinishInternal: function(effect) {
+ effect.element.hide();
+ effect.element.undoClipping();
+ effect.element.firstChild.undoPositioned();
+ effect.element.undoPositioned();
+ effect.element.setStyle({bottom: oldInnerBottom}); }
+ }, arguments[1] || {})
+ );
+}
+
+// Bug in opera makes the TD containing this element expand for a instance after finish
+Effect.Squish = function(element) {
+ return new Effect.Scale(element, window.opera ? 1 : 0,
+ { restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ effect.element.makeClipping(effect.element); },
+ afterFinishInternal: function(effect) {
+ effect.element.hide(effect.element);
+ effect.element.undoClipping(effect.element); }
+ });
+}
+
+Effect.Grow = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ direction: 'center',
+ moveTransition: Effect.Transitions.sinoidal,
+ scaleTransition: Effect.Transitions.sinoidal,
+ opacityTransition: Effect.Transitions.full
+ }, arguments[1] || {});
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ height: element.style.height,
+ width: element.style.width,
+ opacity: element.getInlineOpacity() };
+
+ var dims = element.getDimensions();
+ var initialMoveX, initialMoveY;
+ var moveX, moveY;
+
+ switch (options.direction) {
+ case 'top-left':
+ initialMoveX = initialMoveY = moveX = moveY = 0;
+ break;
+ case 'top-right':
+ initialMoveX = dims.width;
+ initialMoveY = moveY = 0;
+ moveX = -dims.width;
+ break;
+ case 'bottom-left':
+ initialMoveX = moveX = 0;
+ initialMoveY = dims.height;
+ moveY = -dims.height;
+ break;
+ case 'bottom-right':
+ initialMoveX = dims.width;
+ initialMoveY = dims.height;
+ moveX = -dims.width;
+ moveY = -dims.height;
+ break;
+ case 'center':
+ initialMoveX = dims.width / 2;
+ initialMoveY = dims.height / 2;
+ moveX = -dims.width / 2;
+ moveY = -dims.height / 2;
+ break;
+ }
+
+ return new Effect.Move(element, {
+ x: initialMoveX,
+ y: initialMoveY,
+ duration: 0.01,
+ beforeSetup: function(effect) {
+ effect.element.hide();
+ effect.element.makeClipping();
+ effect.element.makePositioned();
+ },
+ afterFinishInternal: function(effect) {
+ new Effect.Parallel(
+ [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+ new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+ new Effect.Scale(effect.element, 100, {
+ scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
+ sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+ ], Object.extend({
+ beforeSetup: function(effect) {
+ effect.effects[0].element.setStyle({height: '0px'});
+ effect.effects[0].element.show();
+ },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.undoClipping();
+ effect.effects[0].element.undoPositioned();
+ effect.effects[0].element.setStyle(oldStyle);
+ }
+ }, options)
+ )
+ }
+ });
+}
+
+Effect.Shrink = function(element) {
+ element = $(element);
+ var options = Object.extend({
+ direction: 'center',
+ moveTransition: Effect.Transitions.sinoidal,
+ scaleTransition: Effect.Transitions.sinoidal,
+ opacityTransition: Effect.Transitions.none
+ }, arguments[1] || {});
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ height: element.style.height,
+ width: element.style.width,
+ opacity: element.getInlineOpacity() };
+
+ var dims = element.getDimensions();
+ var moveX, moveY;
+
+ switch (options.direction) {
+ case 'top-left':
+ moveX = moveY = 0;
+ break;
+ case 'top-right':
+ moveX = dims.width;
+ moveY = 0;
+ break;
+ case 'bottom-left':
+ moveX = 0;
+ moveY = dims.height;
+ break;
+ case 'bottom-right':
+ moveX = dims.width;
+ moveY = dims.height;
+ break;
+ case 'center':
+ moveX = dims.width / 2;
+ moveY = dims.height / 2;
+ break;
+ }
+
+ return new Effect.Parallel(
+ [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+ new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+ new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+ ], Object.extend({
+ beforeStartInternal: function(effect) {
+ effect.effects[0].element.makePositioned();
+ effect.effects[0].element.makeClipping(); },
+ afterFinishInternal: function(effect) {
+ effect.effects[0].element.hide();
+ effect.effects[0].element.undoClipping();
+ effect.effects[0].element.undoPositioned();
+ effect.effects[0].element.setStyle(oldStyle); }
+ }, options)
+ );
+}
+
+Effect.Pulsate = function(element) {
+ element = $(element);
+ var options = arguments[1] || {};
+ var oldOpacity = element.getInlineOpacity();
+ var transition = options.transition || Effect.Transitions.sinoidal;
+ var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
+ reverser.bind(transition);
+ return new Effect.Opacity(element,
+ Object.extend(Object.extend({ duration: 3.0, from: 0,
+ afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
+ }, options), {transition: reverser}));
+}
+
+Effect.Fold = function(element) {
+ element = $(element);
+ var oldStyle = {
+ top: element.style.top,
+ left: element.style.left,
+ width: element.style.width,
+ height: element.style.height };
+ Element.makeClipping(element);
+ return new Effect.Scale(element, 5, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(element, 1, {
+ scaleContent: false,
+ scaleY: false,
+ afterFinishInternal: function(effect) {
+ effect.element.hide();
+ effect.element.undoClipping();
+ effect.element.setStyle(oldStyle);
+ } });
+ }}, arguments[1] || {}));
+};
+
+['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
+ 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(
+ function(f) { Element.Methods[f] = Element[f]; }
+);
+
+Element.Methods.visualEffect = function(element, effect, options) {
+ s = effect.gsub(/_/, '-').camelize();
+ effect_class = s.charAt(0).toUpperCase() + s.substring(1);
+ new Effect[effect_class](element, options);
+ return $(element);
+};
+
+Element.addMethods();
+
+Prado.Effect =
+{
+ Highlight : function(element, options)
+ {
+ new Effect.Highlight(element,options||{});
+ }
+}
+
diff --git a/demos/currency-converter/assets/2bffd82d/logger.js b/demos/currency-converter/assets/2bffd82d/logger.js
new file mode 100644
index 00000000..37e5028e
--- /dev/null
+++ b/demos/currency-converter/assets/2bffd82d/logger.js
@@ -0,0 +1,755 @@
+/*
+
+Created By: Corey Johnson
+E-mail: probablyCorey@gmail.com
+
+Requires: Prototype Javascript library (http://prototype.conio.net/)
+
+Use it all you want. Just remember to give me some credit :)
+
+*/
+
+// ------------
+// Custom Event
+// ------------
+
+CustomEvent = Class.create()
+CustomEvent.prototype = {
+ initialize : function() {
+ this.listeners = []
+ },
+
+ addListener : function(method) {
+ this.listeners.push(method)
+ },
+
+ removeListener : function(method) {
+ var foundIndexes = this._findListenerIndexes(method)
+
+ for(var i = 0; i < foundIndexes.length; i++) {
+ this.listeners.splice(foundIndexes[i], 1)
+ }
+ },
+
+ dispatch : function(handler) {
+ for(var i = 0; i < this.listeners.length; i++) {
+ try {
+ this.listeners[i](handler)
+ }
+ catch (e) {
+ alert("Could not run the listener " + this.listeners[i] + ". " + e)
+ }
+ }
+ },
+
+ // Private Methods
+ // ---------------
+ _findListenerIndexes : function(method) {
+ var indexes = []
+ for(var i = 0; i < this.listeners.length; i++) {
+ if (this.listeners[i] == method) {
+ indexes.push(i)
+ }
+ }
+
+ return indexes
+ }
+}
+
+// ------
+// Cookie
+// ------
+
+var Cookie = {
+ set : function(name, value, expirationInDays, path) {
+ var cookie = escape(name) + "=" + escape(value)
+
+ if (expirationInDays) {
+ var date = new Date()
+ date.setDate(date.getDate() + expirationInDays)
+ cookie += "; expires=" + date.toGMTString()
+ }
+
+ if (path) {
+ cookie += ";path=" + path
+ }
+
+ document.cookie = cookie
+
+ if (value && (expirationInDays == undefined || expirationInDays > 0) && !this.get(name)) {
+ Logger.error("Cookie (" + name + ") was not set correctly... The value was " + value.toString().length + " charachters long (This may be over the cookie limit)");
+ }
+ },
+
+ get : function(name) {
+ var pattern = "(^|;)\\s*" + escape(name) + "=([^;]+)"
+
+ var m = document.cookie.match(pattern)
+ if (m && m[2]) {
+ return unescape(m[2])
+ }
+ else return null
+ },
+
+ getAll : function() {
+ var cookies = document.cookie.split(';')
+ var cookieArray = []
+
+ for (var i = 0; i < cookies.length; i++) {
+ try {
+ var name = unescape(cookies[i].match(/^\s*([^=]+)/m)[1])
+ var value = unescape(cookies[i].match(/=(.*$)/m)[1])
+ }
+ catch (e) {
+ continue
+ }
+
+ cookieArray.push({name : name, value : value})
+
+ if (cookieArray[name] != undefined) {
+ Logger.waring("Trying to retrieve cookie named(" + name + "). There appears to be another property with this name though.");
+ }
+
+ cookieArray[name] = value
+ }
+
+ return cookieArray
+ },
+
+ clear : function(name) {
+ this.set(name, "", -1)
+ },
+
+ clearAll : function() {
+ var cookies = this.getAll()
+
+ for(var i = 0; i < cookies.length; i++) {
+ this.clear(cookies[i].name)
+ }
+
+ }
+}
+
+// ------
+// Logger
+// -----
+
+Logger = {
+ logEntries : [],
+
+ onupdate : new CustomEvent(),
+ onclear : new CustomEvent(),
+
+
+ // Logger output
+ log : function(message, tag) {
+ var logEntry = new LogEntry(message, tag || "info")
+ this.logEntries.push(logEntry)
+ this.onupdate.dispatch(logEntry)
+ },
+
+ info : function(message) {
+ this.log(message, 'info')
+ if(typeof(console) != "undefined")
+ console.info(message);
+ },
+
+ debug : function(message) {
+ this.log(message, 'debug')
+ if(typeof(console) != "undefined")
+ console.debug(message);
+ },
+
+ warn : function(message) {
+ this.log(message, 'warning')
+ if(typeof(console) != "undefined")
+ console.warn(message);
+ },
+
+ error : function(message, error) {
+ this.log(message + ": \n" + error, 'error')
+ if(typeof(console) != "undefined")
+ console.error(message);
+
+ },
+
+ clear : function () {
+ this.logEntries = []
+ this.onclear.dispatch()
+ }
+}
+
+LogEntry = Class.create()
+LogEntry.prototype = {
+ initialize : function(message, tag) {
+ this.message = message
+ this.tag = tag
+ }
+}
+
+LogConsole = Class.create()
+LogConsole.prototype = {
+
+ // Properties
+ // ----------
+ commandHistory : [],
+ commandIndex : 0,
+
+ hidden : true,
+
+ // Methods
+ // -------
+
+ initialize : function(toggleKey) {
+ this.outputCount = 0
+ this.tagPattern = Cookie.get('tagPattern') || ".*"
+
+ // I hate writing javascript in HTML... but what's a better alternative
+ this.logElement = document.createElement('div')
+ document.body.appendChild(this.logElement)
+ Element.hide(this.logElement)
+
+ this.logElement.style.position = "absolute"
+ this.logElement.style.left = '0px'
+ this.logElement.style.width = '100%'
+
+ this.logElement.style.textAlign = "left"
+ this.logElement.style.fontFamily = "lucida console"
+ this.logElement.style.fontSize = "100%"
+ this.logElement.style.backgroundColor = 'darkgray'
+ this.logElement.style.opacity = 0.9
+ this.logElement.style.zIndex = 2000
+
+ // Add toolbarElement
+ this.toolbarElement = document.createElement('div')
+ this.logElement.appendChild(this.toolbarElement)
+ this.toolbarElement.style.padding = "0 0 0 2px"
+
+ // Add buttons
+ this.buttonsContainerElement = document.createElement('span')
+ this.toolbarElement.appendChild(this.buttonsContainerElement)
+
+ this.buttonsContainerElement.innerHTML += '<button onclick="logConsole.toggle()" style="float:right;color:black">close</button>'
+ this.buttonsContainerElement.innerHTML += '<button onclick="Logger.clear()" style="float:right;color:black">clear</button>'
+ if(!Prado.Inspector.disabled)
+ this.buttonsContainerElement.innerHTML += '<button onclick="Prado.Inspector.inspect()" style="float:right;color:black; margin-right:15px;">Object Tree</button>'
+
+
+ //Add Tag Filter
+ this.tagFilterContainerElement = document.createElement('span')
+ this.toolbarElement.appendChild(this.tagFilterContainerElement)
+ this.tagFilterContainerElement.style.cssFloat = 'left'
+ this.tagFilterContainerElement.appendChild(document.createTextNode("Log Filter"))
+
+ this.tagFilterElement = document.createElement('input')
+ this.tagFilterContainerElement.appendChild(this.tagFilterElement)
+ this.tagFilterElement.style.width = '200px'
+ this.tagFilterElement.value = this.tagPattern
+ this.tagFilterElement.setAttribute('autocomplete', 'off') // So Firefox doesn't flip out
+
+ Event.observe(this.tagFilterElement, 'keyup', this.updateTags.bind(this))
+ Event.observe(this.tagFilterElement, 'click', function() {this.tagFilterElement.select()}.bind(this))
+
+ // Add outputElement
+ this.outputElement = document.createElement('div')
+ this.logElement.appendChild(this.outputElement)
+ this.outputElement.style.overflow = "auto"
+ this.outputElement.style.clear = "both"
+ this.outputElement.style.height = "200px"
+ this.outputElement.style.backgroundColor = 'black'
+
+ this.inputContainerElement = document.createElement('div')
+ this.inputContainerElement.style.width = "100%"
+ this.logElement.appendChild(this.inputContainerElement)
+
+ this.inputElement = document.createElement('input')
+ this.inputContainerElement.appendChild(this.inputElement)
+ this.inputElement.style.width = '100%'
+ this.inputElement.style.borderWidth = '0px' // Inputs with 100% width always seem to be too large (I HATE THEM) they only work if the border, margin and padding are 0
+ this.inputElement.style.margin = '0px'
+ this.inputElement.style.padding = '0px'
+ this.inputElement.value = 'Type command here'
+ this.inputElement.setAttribute('autocomplete', 'off') // So Firefox doesn't flip out
+
+ Event.observe(this.inputElement, 'keyup', this.handleInput.bind(this))
+ Event.observe(this.inputElement, 'click', function() {this.inputElement.select()}.bind(this))
+
+ if(document.all && !window.opera)
+ {
+ window.setInterval(this.repositionWindow.bind(this), 500)
+ }
+ else
+ {
+ this.logElement.style.position="fixed";
+ this.logElement.style.bottom="0px";
+ }
+ var self=this;
+ Event.observe(document, 'keydown', function(e)
+ {
+ if((e.altKey==true) && Event.keyCode(e) == toggleKey ) //Alt+J | Ctrl+J
+ self.toggle();
+ });
+
+ // Listen to the logger....
+ Logger.onupdate.addListener(this.logUpdate.bind(this))
+ Logger.onclear.addListener(this.clear.bind(this))
+
+ // Preload log element with the log entries that have been entered
+ for (var i = 0; i < Logger.logEntries.length; i++) {
+ this.logUpdate(Logger.logEntries[i])
+ }
+
+ // Feed all errors into the logger (For some unknown reason I can only get this to work
+ // with an inline event declaration)
+ Event.observe(window, 'error', function(msg, url, lineNumber) {Logger.error("Error in (" + (url || location) + ") on line "+lineNumber+"", msg)})
+
+ // Allow acess key link
+ var accessElement = document.createElement('span')
+ accessElement.innerHTML = '<button style="position:absolute;top:-100px" onclick="javascript:logConsole.toggle()" accesskey="d"></button>'
+ document.body.appendChild(accessElement)
+
+ if (Cookie.get('ConsoleVisible') == 'true') {
+ this.toggle()
+ }
+ },
+
+ toggle : function() {
+ if (this.logElement.style.display == 'none') {
+ this.show()
+ }
+ else {
+ this.hide()
+ }
+ },
+
+ show : function() {
+ Element.show(this.logElement)
+ this.outputElement.scrollTop = this.outputElement.scrollHeight // Scroll to bottom when toggled
+ if(document.all && !window.opera)
+ this.repositionWindow();
+ Cookie.set('ConsoleVisible', 'true')
+ this.inputElement.select()
+ this.hidden = false;
+ },
+
+ hide : function() {
+ this.hidden = true;
+ Element.hide(this.logElement)
+ Cookie.set('ConsoleVisible', 'false')
+ },
+
+ output : function(message, style) {
+ // If we are at the bottom of the window, then keep scrolling with the output
+ var shouldScroll = (this.outputElement.scrollTop + (2 * this.outputElement.clientHeight)) >= this.outputElement.scrollHeight
+
+ this.outputCount++
+ style = (style ? style += ';' : '')
+ style += 'padding:1px;margin:0 0 5px 0'
+
+ if (this.outputCount % 2 == 0) style += ";background-color:#101010"
+
+ message = message || "undefined"
+ message = message.toString().escapeHTML()
+
+ this.outputElement.innerHTML += "<pre style='" + style + "'>" + message + "</pre>"
+
+ if (shouldScroll) {
+ this.outputElement.scrollTop = this.outputElement.scrollHeight
+ }
+ },
+
+ updateTags : function() {
+ var pattern = this.tagFilterElement.value
+
+ if (this.tagPattern == pattern) return
+
+ try {
+ new RegExp(pattern)
+ }
+ catch (e) {
+ return
+ }
+
+ this.tagPattern = pattern
+ Cookie.set('tagPattern', this.tagPattern)
+
+ this.outputElement.innerHTML = ""
+
+ // Go through each log entry again
+ this.outputCount = 0;
+ for (var i = 0; i < Logger.logEntries.length; i++) {
+ this.logUpdate(Logger.logEntries[i])
+ }
+ },
+
+ repositionWindow : function() {
+ var offset = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
+ var pageHeight = self.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
+ this.logElement.style.top = (offset + pageHeight - Element.getHeight(this.logElement)) + "px"
+ },
+
+ // Event Handlers
+ // --------------
+
+ logUpdate : function(logEntry) {
+ if (logEntry.tag.search(new RegExp(this.tagPattern, 'igm')) == -1) return
+ var style = ''
+ if (logEntry.tag.search(/error/) != -1) style += 'color:red'
+ else if (logEntry.tag.search(/warning/) != -1) style += 'color:orange'
+ else if (logEntry.tag.search(/debug/) != -1) style += 'color:green'
+ else if (logEntry.tag.search(/info/) != -1) style += 'color:white'
+ else style += 'color:yellow'
+
+ this.output(logEntry.message, style)
+ },
+
+ clear : function(e) {
+ this.outputElement.innerHTML = ""
+ },
+
+ handleInput : function(e) {
+ if (e.keyCode == Event.KEY_RETURN ) {
+ var command = this.inputElement.value
+
+ switch(command) {
+ case "clear":
+ Logger.clear()
+ break
+
+ default:
+ var consoleOutput = ""
+
+ try {
+ consoleOutput = eval(this.inputElement.value)
+ }
+ catch (e) {
+ Logger.error("Problem parsing input <" + command + ">", e)
+ break
+ }
+
+ Logger.log(consoleOutput)
+ break
+ }
+
+ if (this.inputElement.value != "" && this.inputElement.value != this.commandHistory[0]) {
+ this.commandHistory.unshift(this.inputElement.value)
+ }
+
+ this.commandIndex = 0
+ this.inputElement.value = ""
+ }
+ else if (e.keyCode == Event.KEY_UP && this.commandHistory.length > 0) {
+ this.inputElement.value = this.commandHistory[this.commandIndex]
+
+ if (this.commandIndex < this.commandHistory.length - 1) {
+ this.commandIndex += 1
+ }
+ }
+ else if (e.keyCode == Event.KEY_DOWN && this.commandHistory.length > 0) {
+ if (this.commandIndex > 0) {
+ this.commandIndex -= 1
+ }
+
+ this.inputElement.value = this.commandHistory[this.commandIndex]
+ }
+ else {
+ this.commandIndex = 0
+ }
+ }
+}
+
+
+// -------------------------
+// Helper Functions And Junk
+// -------------------------
+function inspect(o)
+{
+ var objtype = typeof(o);
+ if (objtype == "undefined") {
+ return "undefined";
+ } else if (objtype == "number" || objtype == "boolean") {
+ return o + "";
+ } else if (o === null) {
+ return "null";
+ }
+
+ try {
+ var ostring = (o + "");
+ } catch (e) {
+ return "[" + typeof(o) + "]";
+ }
+
+ if (typeof(o) == "function")
+ {
+ o = ostring.replace(/^\s+/, "");
+ var idx = o.indexOf("{");
+ if (idx != -1) {
+ o = o.substr(0, idx) + "{...}";
+ }
+ return o;
+ }
+
+ var reprString = function (o)
+ {
+ return ('"' + o.replace(/(["\\])/g, '\\$1') + '"'
+ ).replace(/[\f]/g, "\\f"
+ ).replace(/[\b]/g, "\\b"
+ ).replace(/[\n]/g, "\\n"
+ ).replace(/[\t]/g, "\\t"
+ ).replace(/[\r]/g, "\\r");
+ };
+
+ if (objtype == "string") {
+ return reprString(o);
+ }
+ // recurse
+ var me = arguments.callee;
+ // short-circuit for objects that support "json" serialization
+ // if they return "self" then just pass-through...
+ var newObj;
+ if (typeof(o.__json__) == "function") {
+ newObj = o.__json__();
+ if (o !== newObj) {
+ return me(newObj);
+ }
+ }
+ if (typeof(o.json) == "function") {
+ newObj = o.json();
+ if (o !== newObj) {
+ return me(newObj);
+ }
+ }
+ // array
+ if (objtype != "function" && typeof(o.length) == "number") {
+ var res = [];
+ for (var i = 0; i < o.length; i++) {
+ var val = me(o[i]);
+ if (typeof(val) != "string") {
+ val = "undefined";
+ }
+ res.push(val);
+ }
+ return "[" + res.join(", ") + "]";
+ }
+
+ // generic object code path
+ res = [];
+ for (var k in o) {
+ var useKey;
+ if (typeof(k) == "number") {
+ useKey = '"' + k + '"';
+ } else if (typeof(k) == "string") {
+ useKey = reprString(k);
+ } else {
+ // skip non-string or number keys
+ continue;
+ }
+ val = me(o[k]);
+ if (typeof(val) != "string") {
+ // skip non-serializable values
+ continue;
+ }
+ res.push(useKey + ":" + val);
+ }
+ return "{" + res.join(", ") + "}";
+}
+
+Array.prototype.contains = function(object) {
+ for(var i = 0; i < this.length; i++) {
+ if (object == this[i]) return true
+ }
+
+ return false
+}
+
+// Helper Alias for simple logging
+var puts = function() {return Logger.log(arguments[0], arguments[1])}
+
+/*************************************
+
+ Javascript Object Tree
+ version 1.0
+ last revision:04.11.2004
+ steve@slayeroffice.com
+ http://slayeroffice.com
+
+ (c)2004 S.G. Chipman
+
+ Please notify me of any modifications
+ you make to this code so that I can
+ update the version hosted on slayeroffice.com
+
+
+************************************/
+if(typeof Prado == "undefined")
+ var Prado = {};
+Prado.Inspector =
+{
+ d : document,
+ types : new Array(),
+ objs : new Array(),
+ hidden : new Array(),
+ opera : window.opera,
+ displaying : '',
+ nameList : new Array(),
+
+ format : function(str) {
+ if(typeof(str) != "string") return str;
+ str=str.replace(/</g,"&lt;");
+ str=str.replace(/>/g,"&gt;");
+ return str;
+ },
+
+ parseJS : function(obj) {
+ var name;
+ if(typeof obj == "string") { name = obj; obj = eval(obj); }
+ win= typeof obj == 'undefined' ? window : obj;
+ this.displaying = name ? name : win.toString();
+ for(js in win) {
+ try {
+ if(win[js] && js.toString().indexOf("Inspector")==-1 && (win[js]+"").indexOf("[native code]")==-1) {
+
+ t=typeof(win[js]);
+ if(!this.objs[t.toString()]) {
+ this.types[this.types.length]=t;
+ this.objs[t]={};
+ this.nameList[t] = new Array();
+ }
+ this.nameList[t].push(js);
+ this.objs[t][js] = this.format(win[js]+"");
+ }
+ } catch(err) { }
+ }
+
+ for(i=0;i<this.types.length;i++)
+ this.nameList[this.types[i]].sort();
+ },
+
+ show : function(objID) {
+ this.d.getElementById(objID).style.display=this.hidden[objID]?"none":"block";
+ this.hidden[objID]=this.hidden[objID]?0:1;
+ },
+
+ changeSpan : function(spanID) {
+ if(this.d.getElementById(spanID).innerHTML.indexOf("+")>-1){
+ this.d.getElementById(spanID).innerHTML="[-]";
+ } else {
+ this.d.getElementById(spanID).innerHTML="[+]";
+ }
+ },
+
+ buildInspectionLevel : function()
+ {
+ var display = this.displaying;
+ var list = display.split(".");
+ var links = ["<a href=\"javascript:var_dump()\">[object Window]</a>"];
+ var name = '';
+ if(display.indexOf("[object ") >= 0) return links.join(".");
+ for(var i = 0; i < list.length; i++)
+ {
+ name += (name.length ? "." : "") + list[i];
+ links[i+1] = "<a href=\"javascript:var_dump('"+name+"')\">"+list[i]+"</a>";
+ }
+ return links.join(".");
+ },
+
+ buildTree : function() {
+ mHTML = "<div>Inspecting "+this.buildInspectionLevel()+"</div>";
+ mHTML +="<ul class=\"topLevel\">";
+ this.types.sort();
+ var so_objIndex=0;
+ for(i=0;i<this.types.length;i++)
+ {
+ mHTML+="<li style=\"cursor:pointer;\" onclick=\"Prado.Inspector.show('ul"+i+"');Prado.Inspector.changeSpan('sp" + i + "')\"><span id=\"sp" + i + "\">[+]</span><b>" + this.types[i] + "</b> (" + this.nameList[this.types[i]].length + ")</li><ul style=\"display:none;\" id=\"ul"+i+"\">";
+ this.hidden["ul"+i]=0;
+ for(e=0;e<this.nameList[this.types[i]].length;e++)
+ {
+ var prop = this.nameList[this.types[i]][e];
+ var value = this.objs[this.types[i]][prop]
+ var more = "";
+ if(value.indexOf("[object ") >= 0 && /^[a-zA-Z_]/.test(prop))
+ {
+ if(this.displaying.indexOf("[object ") < 0)
+ more = " <a href=\"javascript:var_dump('"+this.displaying+"."+prop+"')\"><b>more</b></a>";
+ else if(this.displaying.indexOf("[object Window]") >= 0)
+ more = " <a href=\"javascript:var_dump('"+prop+"')\"><b>more</b></a>";
+ }
+ mHTML+="<li style=\"cursor:pointer;\" onclick=\"Prado.Inspector.show('mul" + so_objIndex + "');Prado.Inspector.changeSpan('sk" + so_objIndex + "')\"><span id=\"sk" + so_objIndex + "\">[+]</span>" + prop + "</li><ul id=\"mul" + so_objIndex + "\" style=\"display:none;\"><li style=\"list-style-type:none;\"><pre>" + value + more + "</pre></li></ul>";
+ this.hidden["mul"+so_objIndex]=0;
+ so_objIndex++;
+ }
+ mHTML+="</ul>";
+ }
+ mHTML+="</ul>";
+ this.d.getElementById("so_mContainer").innerHTML =mHTML;
+ },
+
+ handleKeyEvent : function(e) {
+ keyCode=document.all?window.event.keyCode:e.keyCode;
+ if(keyCode==27) {
+ this.cleanUp();
+ }
+ },
+
+ cleanUp : function()
+ {
+ if(this.d.getElementById("so_mContainer"))
+ {
+ this.d.body.removeChild(this.d.getElementById("so_mContainer"));
+ this.d.body.removeChild(this.d.getElementById("so_mStyle"));
+ if(typeof Event != "undefined")
+ Event.stopObserving(this.d, "keydown", this.dKeyDownEvent);
+ this.types = new Array();
+ this.objs = new Array();
+ this.hidden = new Array();
+ }
+ },
+
+ disabled : document.all && !this.opera,
+
+ inspect : function(obj)
+ {
+ if(this.disabled)return alert("Sorry, this only works in Mozilla and Firefox currently.");
+ this.cleanUp();
+ mObj=this.d.body.appendChild(this.d.createElement("div"));
+ mObj.id="so_mContainer";
+ sObj=this.d.body.appendChild(this.d.createElement("style"));
+ sObj.id="so_mStyle";
+ sObj.type="text/css";
+ sObj.innerHTML = this.style;
+ this.dKeyDownEvent = this.handleKeyEvent.bind(this);
+ if(typeof Event != "undefined")
+ Event.observe(this.d, "keydown", this.dKeyDownEvent);
+
+ this.parseJS(obj);
+ this.buildTree();
+
+ cObj=mObj.appendChild(this.d.createElement("div"));
+ cObj.className="credits";
+ cObj.innerHTML = "<b>[esc] to <a href=\"javascript:Prado.Inspector.cleanUp();\">close</a></b><br />Javascript Object Tree V2.0.";
+
+ window.scrollTo(0,0);
+ },
+
+ style : "#so_mContainer { position:absolute; top:5px; left:5px; background-color:#E3EBED; text-align:left; font:9pt verdana; width:85%; border:2px solid #000; padding:5px; z-index:1000; color:#000; } " +
+ "#so_mContainer ul { padding-left:20px; } " +
+ "#so_mContainer ul li { display:block; list-style-type:none; list-style-image:url(); line-height:2em; -moz-border-radius:.75em; font:10px verdana; padding:0; margin:2px; color:#000; } " +
+ "#so_mContainer li:hover { background-color:#E3EBED; } " +
+ "#so_mContainer ul li span { position:relative; width:15px; height:15px; margin-right:4px; } " +
+ "#so_mContainer pre { background-color:#F9FAFB; border:1px solid #638DA1; height:auto; padding:5px; font:9px verdana; color:#000; } " +
+ "#so_mContainer .topLevel { margin:0; padding:0; } " +
+ "#so_mContainer .credits { float:left; width:200px; font:6.5pt verdana; color:#000; padding:2px; margin-left:5px; text-align:left; border-top:1px solid #000; margin-top:15px; width:75%; } " +
+ "#so_mContainer .credits a { font:9px verdana; font-weight:bold; color:#004465; text-decoration:none; background-color:transparent; }"
+}
+
+//similar function to var_dump in PHP, brings up the javascript object tree UI.
+function var_dump(obj)
+{
+ Prado.Inspector.inspect(obj);
+}
+
+//similar function to print_r for PHP
+var print_r = inspect;
+
+
+
diff --git a/demos/currency-converter/assets/2bffd82d/prado.js b/demos/currency-converter/assets/2bffd82d/prado.js
new file mode 100644
index 00000000..ca95bfbb
--- /dev/null
+++ b/demos/currency-converter/assets/2bffd82d/prado.js
@@ -0,0 +1,3542 @@
+/* Prototype JavaScript framework, version <%= PROTOTYPE_VERSION %>
+ * (c) 2005 Sam Stephenson <sam@conio.net>
+ *
+ * Prototype is freely distributable under the terms of an MIT-style license.
+ * For details, see the Prototype web site: http://prototype.conio.net/
+ *
+/*--------------------------------------------------------------------------*/
+
+var Prototype = {
+ Version: '1.50',
+ ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)',
+
+ emptyFunction: function() {},
+ K: function(x) {return x}
+}
+
+/*
+<%= include 'base.js', 'string.js' %>
+
+<%= include 'enumerable.js', 'array.js', 'hash.js', 'range.js' %>
+
+<%= include 'ajax.js', 'dom.js', 'selector.js', 'form.js', 'event.js', 'position.js' %>
+
+*/
+
+var Class = {
+ create: function() {
+ return function() {
+ this.initialize.apply(this, arguments);
+ }
+ }
+}
+
+var Abstract = new Object();
+
+Object.extend = function(destination, source) {
+ for (var property in source) {
+ destination[property] = source[property];
+ }
+ return destination;
+}
+
+Object.inspect = function(object) {
+ try {
+ if (object == undefined) return 'undefined';
+ if (object == null) return 'null';
+ return object.inspect ? object.inspect() : object.toString();
+ } catch (e) {
+ if (e instanceof RangeError) return '...';
+ throw e;
+ }
+}
+
+Function.prototype.bind = function() {
+ var __method = this, args = $A(arguments), object = args.shift();
+ return function() {
+ return __method.apply(object, args.concat($A(arguments)));
+ }
+}
+
+Function.prototype.bindAsEventListener = function(object) {
+ var __method = this;
+ return function(event) {
+ return __method.call(object, event || window.event);
+ }
+}
+
+Object.extend(Number.prototype, {
+ toColorPart: function() {
+ var digits = this.toString(16);
+ if (this < 16) return '0' + digits;
+ return digits;
+ },
+
+ succ: function() {
+ return this + 1;
+ },
+
+ times: function(iterator) {
+ $R(0, this, true).each(iterator);
+ return this;
+ }
+});
+
+var Try = {
+ these: function() {
+ var returnValue;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var lambda = arguments[i];
+ try {
+ returnValue = lambda();
+ break;
+ } catch (e) {}
+ }
+
+ return returnValue;
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create();
+PeriodicalExecuter.prototype = {
+ initialize: function(callback, frequency) {
+ this.callback = callback;
+ this.frequency = frequency;
+ this.currentlyExecuting = false;
+
+ this.registerCallback();
+ },
+
+ registerCallback: function() {
+ setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+ },
+
+ onTimerEvent: function() {
+ if (!this.currentlyExecuting) {
+ try {
+ this.currentlyExecuting = true;
+ this.callback();
+ } finally {
+ this.currentlyExecuting = false;
+ }
+ }
+ }
+}
+
+
+
+/**
+ * Similar to bindAsEventLister, but takes additional arguments.
+ */
+Function.prototype.bindEvent = function()
+{
+ var __method = this, args = $A(arguments), object = args.shift();
+ return function(event)
+ {
+ return __method.apply(object, [event || window.event].concat(args));
+ }
+}
+
+/**
+ * Creates a new function by copying function definition from
+ * the <tt>base</tt> and optional <tt>definition</tt>.
+ * @param function a base function to copy from.
+ * @param array additional definition
+ * @param function return a new function with definition from both
+ * <tt>base</tt> and <tt>definition</tt>.
+ */
+Class.extend = function(base, definition)
+{
+ var component = Class.create();
+ Object.extend(component.prototype, base.prototype);
+ if(definition)
+ Object.extend(component.prototype, definition);
+ return component;
+}
+
+/*
+ Base, version 1.0.2
+ Copyright 2006, Dean Edwards
+ License: http://creativecommons.org/licenses/LGPL/2.1/
+*/
+
+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.2";
+
+Base.prototype = {
+ extend: function(source, value) {
+ var extend = Base.prototype.extend;
+ if (arguments.length == 2) {
+ var ancestor = this[source];
+ // overriding?
+ if ((ancestor instanceof Function) && (value instanceof Function) &&
+ 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.base;
+ // this.base = fromPrototype ? _prototype[source] : ancestor;
+ this.base = ancestor;
+ var returnValue = method.apply(this, arguments);
+ this.base = previous;
+ return returnValue;
+ };
+ // point to the underlying method
+ value.valueOf = function() {
+ return method;
+ };
+ value.toString = function() {
+ return String(method);
+ };
+ }
+ return this[source] = value;
+ } else if (source) {
+ var _prototype = {toSource: null};
+ // do the "toString" and other methods manually
+ var _protected = ["toString", "valueOf"];
+ // if we are prototyping then include the constructor
+ 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]);
+ }
+ }
+ // copy each of the source object's properties to this object
+ for (var name in source) {
+ if (!_prototype[name]) {
+ extend.call(this, name, source[name]);
+ }
+ }
+ }
+ return this;
+ },
+
+ base: function() {
+ // call this method from any other method to invoke that method's ancestor
+ }
+};
+
+Base.extend = function(_instance, _static) {
+ var extend = Base.prototype.extend;
+ if (!_instance) _instance = {};
+ // build the prototype
+ Base._prototyping = true;
+ var _prototype = new this;
+ extend.call(_prototype, _instance);
+ var constructor = _prototype.constructor;
+ _prototype.constructor = this;
+ delete Base._prototyping;
+ // create the wrapper for the constructor function
+ var klass = function() {
+ if (!Base._prototyping) constructor.apply(this, arguments);
+ this.constructor = klass;
+ };
+ 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);
+ // single instance
+ var object = constructor ? klass : _prototype;
+ // class initialisation
+ if (object.init instanceof Function) object.init();
+ return object;
+};
+
+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.2 (2006-06-19)
+ *
+ * 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,
+ before : false,
+ mutate : function() {return arguments;}
+ }, 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 = [];
+ for(var x = 0; x < arguments.length; x++){
+ args.push(arguments[x]);
+ }
+ args = options.mutate.apply(null,args)
+ var result;
+ if(!options.before) result = sigObj[signame].apply(sigObj,arguments); //default: call sign before slot
+ 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;
+ }
+ });
+ if(options.before) result = sigObj[signame].apply(sigObj,arguments); //call slot before sig
+ return result; //return sig 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}))
+ }
+}
+*/
+
+/*
+ Tests
+
+// 1. Simple Test 1 "hello Fred" should trigger "Fred is a stupid head"
+
+
+ sayHello = function(n) {
+ alert("Hello! " + n);
+ }
+ moron = function(n) {
+ alert(n + " is a stupid head");
+ }
+ Signal.connect(null,'sayHello',null,'moron');
+
+ onclick="sayHello('Fred')"
+
+
+// 2. Simple Test 2 repeated insults about Fred
+
+
+ Signal.connect(null,'sayHello2',null,'moron2');
+ Signal.connect(null,'sayHello2',null,'moron2');
+ Signal.connect(null,'sayHello2',null,'moron2');
+
+
+// 3. Simple Test 3 multiple insults about Fred
+
+
+ Signal.connect(null,'sayHello3',null,'moron3');
+ Signal.connect(null,'sayHello3',null,'bonehead3');
+ Signal.connect(null,'sayHello3',null,'idiot3');
+
+
+// 4. Simple Test 4 3 insults about Fred first - 3 then none
+
+
+ Signal.connect(null,'sayHello4',null,'moron4');
+ Signal.connect(null,'sayHello4',null,'moron4');
+ Signal.connect(null,'sayHello4',null,'moron4');
+ Signal.disconnect(null,'sayHello4',null,'moron4');
+ Signal.disconnect(null,'sayHello4',null,'moron4');
+ Signal.disconnect(null,'sayHello4',null,'moron4');
+
+
+// 5. Simple Test 5 connect 3 insults about Fred first - only one, then none
+
+
+ Signal.connect(null,'sayHello5',null,'moron5');
+ Signal.connect(null,'sayHello5',null,'moron5');
+ Signal.connect(null,'sayHello5',null,'moron5');
+ Signal.disconnectAll(null,'sayHello5',null,'moron5');
+
+
+// 6. Simple Test 6 connect 3 insults but only one comes out
+
+
+ Signal.connectOnce(null,'sayHello6',null,'moron6');
+ Signal.connectOnce(null,'sayHello6',null,'moron6');
+ Signal.connectOnce(null,'sayHello6',null,'moron6');
+
+
+// 7. Simple Test 7 connect via objects
+
+
+ var o = {};
+ o.sayHello = function(n) {
+ alert("Hello! " + n + " (from object o)");
+ }
+ var m = {};
+ m.moron = function(n) {
+ alert(n + " is a stupid head (from object m)");
+ }
+
+ Signal.connect(o,'sayHello',m,'moron');
+
+ onclick="o.sayHello('Fred')"
+
+
+// 8. Simple Test 8 connect but the insult comes first using {before:true}
+
+
+ Signal.connect(null,'sayHello8',null,'moron8', {before:true});
+
+
+// 9. Simple Test 9 connect but the insult is mutated
+
+
+ Signal.connect(null,'sayHello9',null,'moron9', {mutate:function() { return ['smelly ' + arguments[0]] }});
+
+ */
+
+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();
+ });
+ }
+}
+
+
+/**
+ * @class String extensions
+ */
+Object.extend(String.prototype,
+{
+ /**
+ * @param {String} "left" to pad the string on the left, "right" to pad right.
+ * @param {Number} minimum string length.
+ * @param {String} character(s) to pad
+ * @return {String} padded character(s) on the left or right to satisfy minimum string length
+ */
+
+ pad : function(side, len, chr) {
+ if (!chr) chr = ' ';
+ var s = this;
+ var left = side.toLowerCase()=='left';
+ while (s.length<len) s = left? chr + s : s + chr;
+ return s;
+ },
+
+ /**
+ * @param {Number} minimum string length.
+ * @param {String} character(s) to pad
+ * @return {String} padded character(s) on the left to satisfy minimum string length
+ */
+ padLeft : function(len, chr) {
+ return this.pad('left',len,chr);
+ },
+
+ /**
+ * @param {Number} minimum string length.
+ * @param {String} character(s) to pad
+ * @return {String} padded character(s) on the right to satisfy minimum string length
+ */
+ padRight : function(len, chr) {
+ return this.pad('right',len,chr);
+ },
+
+ /**
+ * @param {Number} minimum string length.
+ * @return {String} append zeros to the left to satisfy minimum string length.
+ */
+ zerofill : function(len) {
+ return this.padLeft(len,'0');
+ },
+
+ /**
+ * @return {String} removed white spaces from both ends.
+ */
+ trim : function() {
+ return this.replace(/^\s+|\s+$/g,'');
+ },
+
+ /**
+ * @return {String} removed white spaces from the left end.
+ */
+ trimLeft : function() {
+ return this.replace(/^\s+/,'');
+ },
+
+ /**
+ * @return {String} removed white spaces from the right end.
+ */
+ trimRight : function() {
+ return this.replace(/\s+$/,'');
+ },
+
+ /**
+ * Convert period separated function names into a function reference.
+ * e.g. "Prado.AJAX.Callback.Action.setValue".toFunction() will return
+ * the actual function Prado.AJAX.Callback.Action.setValue()
+ * @return {Function} the corresponding function represented by the string.
+ */
+ toFunction : function()
+ {
+ var commands = this.split(/\./);
+ var command = window;
+ commands.each(function(action)
+ {
+ if(command[new String(action)])
+ command=command[new String(action)];
+ });
+ if(typeof(command) == "function")
+ return command;
+ else
+ {
+ if(typeof Logger != "undefined")
+ Logger.error("Missing function", this);
+
+ throw new Error ("Missing function '"+this+"'");
+ }
+ },
+
+ /**
+ * Convert a string into integer, returns null if not integer.
+ * @return {Number} null if string does not represent an integer.
+ */
+ toInteger : function()
+ {
+ var exp = /^\s*[-\+]?\d+\s*$/;
+ if (this.match(exp) == null)
+ return null;
+ var num = parseInt(this, 10);
+ return (isNaN(num) ? null : num);
+ },
+
+ /**
+ * Convert a string into a double/float value. <b>Internationalization
+ * is not supported</b>
+ * @param {String} the decimal character
+ * @return {Double} null if string does not represent a float value
+ */
+ toDouble : function(decimalchar)
+ {
+ if(this.length <= 0) return null;
+ decimalchar = decimalchar || ".";
+ var exp = new RegExp("^\\s*([-\\+])?(\\d+)?(\\" + decimalchar + "(\\d+))?\\s*$");
+ var m = this.match(exp);
+
+ if (m == null)
+ return null;
+ m[1] = m[1] || "";
+ m[2] = m[2] || "0";
+ m[4] = m[4] || "0";
+
+ var cleanInput = m[1] + (m[2].length>0 ? m[2] : "0") + "." + m[4];
+ var num = parseFloat(cleanInput);
+ return (isNaN(num) ? null : num);
+ },
+
+ /**
+ * Convert strings that represent a currency value (e.g. a float with grouping
+ * characters) to float. E.g. "10,000.50" will become "10000.50". The number
+ * of dicimal digits, grouping and decimal characters can be specified.
+ * <i>The currency input format is <b>very</b> strict, null will be returned if
+ * the pattern does not match</i>.
+ * @param {String} the grouping character, default is ","
+ * @param {Number} number of decimal digits
+ * @param {String} the decimal character, default is "."
+ * @type {Double} the currency value as float.
+ */
+ toCurrency : function(groupchar, digits, decimalchar)
+ {
+ groupchar = groupchar || ",";
+ decimalchar = decimalchar || ".";
+ digits = typeof(digits) == "undefined" ? 2 : digits;
+
+ var exp = new RegExp("^\\s*([-\\+])?(((\\d+)\\" + groupchar + ")*)(\\d+)"
+ + ((digits > 0) ? "(\\" + decimalchar + "(\\d{1," + digits + "}))?" : "")
+ + "\\s*$");
+ var m = this.match(exp);
+ if (m == null)
+ return null;
+ var intermed = m[2] + m[5] ;
+ var cleanInput = m[1] + intermed.replace(
+ new RegExp("(\\" + groupchar + ")", "g"), "")
+ + ((digits > 0) ? "." + m[7] : "");
+ var num = parseFloat(cleanInput);
+ return (isNaN(num) ? null : num);
+ },
+
+ /**
+ * Converts the string to a date by finding values that matches the
+ * date format pattern.
+ * @param string date format pattern, e.g. MM-dd-yyyy
+ * @return {Date} the date extracted from the string
+ */
+ toDate : function(format)
+ {
+ return Date.SimpleParse(this, format);
+ }
+});
+
+var $break = new Object();
+var $continue = new Object();
+
+var Enumerable = {
+ each: function(iterator) {
+ var index = 0;
+ try {
+ this._each(function(value) {
+ try {
+ iterator(value, index++);
+ } catch (e) {
+ if (e != $continue) throw e;
+ }
+ });
+ } catch (e) {
+ if (e != $break) throw e;
+ }
+ },
+
+ all: function(iterator) {
+ var result = true;
+ this.each(function(value, index) {
+ result = result && !!(iterator || Prototype.K)(value, index);
+ if (!result) throw $break;
+ });
+ return result;
+ },
+
+ any: function(iterator) {
+ var result = true;
+ this.each(function(value, index) {
+ if (result = !!(iterator || Prototype.K)(value, index))
+ throw $break;
+ });
+ return result;
+ },
+
+ collect: function(iterator) {
+ var results = [];
+ this.each(function(value, index) {
+ results.push(iterator(value, index));
+ });
+ return results;
+ },
+
+ detect: function (iterator) {
+ var result;
+ this.each(function(value, index) {
+ if (iterator(value, index)) {
+ result = value;
+ throw $break;
+ }
+ });
+ return result;
+ },
+
+ findAll: function(iterator) {
+ var results = [];
+ this.each(function(value, index) {
+ if (iterator(value, index))
+ results.push(value);
+ });
+ return results;
+ },
+
+ grep: function(pattern, iterator) {
+ var results = [];
+ this.each(function(value, index) {
+ var stringValue = value.toString();
+ if (stringValue.match(pattern))
+ results.push((iterator || Prototype.K)(value, index));
+ })
+ return results;
+ },
+
+ include: function(object) {
+ var found = false;
+ this.each(function(value) {
+ if (value == object) {
+ found = true;
+ throw $break;
+ }
+ });
+ return found;
+ },
+
+ inject: function(memo, iterator) {
+ this.each(function(value, index) {
+ memo = iterator(memo, value, index);
+ });
+ return memo;
+ },
+
+ invoke: function(method) {
+ var args = $A(arguments).slice(1);
+ return this.collect(function(value) {
+ return value[method].apply(value, args);
+ });
+ },
+
+ max: function(iterator) {
+ var result;
+ this.each(function(value, index) {
+ value = (iterator || Prototype.K)(value, index);
+ if (result == undefined || value >= result)
+ result = value;
+ });
+ return result;
+ },
+
+ min: function(iterator) {
+ var result;
+ this.each(function(value, index) {
+ value = (iterator || Prototype.K)(value, index);
+ if (result == undefined || value < result)
+ result = value;
+ });
+ return result;
+ },
+
+ partition: function(iterator) {
+ var trues = [], falses = [];
+ this.each(function(value, index) {
+ ((iterator || Prototype.K)(value, index) ?
+ trues : falses).push(value);
+ });
+ return [trues, falses];
+ },
+
+ pluck: function(property) {
+ var results = [];
+ this.each(function(value, index) {
+ results.push(value[property]);
+ });
+ return results;
+ },
+
+ reject: function(iterator) {
+ var results = [];
+ this.each(function(value, index) {
+ if (!iterator(value, index))
+ results.push(value);
+ });
+ return results;
+ },
+
+ sortBy: function(iterator) {
+ return this.collect(function(value, index) {
+ return {value: value, criteria: iterator(value, index)};
+ }).sort(function(left, right) {
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ }).pluck('value');
+ },
+
+ toArray: function() {
+ return this.collect(Prototype.K);
+ },
+
+ zip: function() {
+ var iterator = Prototype.K, args = $A(arguments);
+ if (typeof args.last() == 'function')
+ iterator = args.pop();
+
+ var collections = [this].concat(args).map($A);
+ return this.map(function(value, index) {
+ return iterator(collections.pluck(index));
+ });
+ },
+
+ inspect: function() {
+ return '#<Enumerable:' + this.toArray().inspect() + '>';
+ }
+}
+
+Object.extend(Enumerable, {
+ map: Enumerable.collect,
+ find: Enumerable.detect,
+ select: Enumerable.findAll,
+ member: Enumerable.include,
+ entries: Enumerable.toArray
+});
+
+
+var $A = Array.from = function(iterable) {
+ if (!iterable) return [];
+ if (iterable.toArray) {
+ return iterable.toArray();
+ } else {
+ var results = [];
+ for (var i = 0; i < iterable.length; i++)
+ results.push(iterable[i]);
+ return results;
+ }
+}
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse)
+ Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+ _each: function(iterator) {
+ for (var i = 0; i < this.length; i++)
+ iterator(this[i]);
+ },
+
+ clear: function() {
+ this.length = 0;
+ return this;
+ },
+
+ first: function() {
+ return this[0];
+ },
+
+ last: function() {
+ return this[this.length - 1];
+ },
+
+ compact: function() {
+ return this.select(function(value) {
+ return value != undefined || value != null;
+ });
+ },
+
+ flatten: function() {
+ return this.inject([], function(array, value) {
+ return array.concat(value && value.constructor == Array ?
+ value.flatten() : [value]);
+ });
+ },
+
+ without: function() {
+ var values = $A(arguments);
+ return this.select(function(value) {
+ return !values.include(value);
+ });
+ },
+
+ indexOf: function(object) {
+ for (var i = 0; i < this.length; i++)
+ if (this[i] == object) return i;
+ return -1;
+ },
+
+ reverse: function(inline) {
+ return (inline !== false ? this : this.toArray())._reverse();
+ },
+
+ inspect: function() {
+ return '[' + this.map(Object.inspect).join(', ') + ']';
+ }
+});
+
+
+var Hash = {
+ _each: function(iterator) {
+ for (var key in this) {
+ var value = this[key];
+ if (typeof value == 'function') continue;
+
+ var pair = [key, value];
+ pair.key = key;
+ pair.value = value;
+ iterator(pair);
+ }
+ },
+
+ keys: function() {
+ return this.pluck('key');
+ },
+
+ values: function() {
+ return this.pluck('value');
+ },
+
+ merge: function(hash) {
+ return $H(hash).inject($H(this), function(mergedHash, pair) {
+ mergedHash[pair.key] = pair.value;
+ return mergedHash;
+ });
+ },
+
+ toQueryString: function() {
+ return this.map(function(pair)
+ {
+ //special case for PHP, array post data.
+ if(typeof(pair[1]) == 'object' || typeof(pair[1]) == 'array')
+ {
+ return $A(pair[1]).collect(function(value)
+ {
+ return encodeURIComponent(pair[0])+'='+encodeURIComponent(value);
+ }).join('&');
+ }
+ else
+ return pair.map(encodeURIComponent).join('=');
+ }).join('&');
+ },
+
+ inspect: function() {
+ return '#<Hash:{' + this.map(function(pair) {
+ return pair.map(Object.inspect).join(': ');
+ }).join(', ') + '}>';
+ }
+}
+
+function $H(object) {
+ var hash = Object.extend({}, object || {});
+ Object.extend(hash, Enumerable);
+ Object.extend(hash, Hash);
+ return hash;
+}
+
+
+ObjectRange = Class.create();
+Object.extend(ObjectRange.prototype, Enumerable);
+Object.extend(ObjectRange.prototype, {
+ initialize: function(start, end, exclusive) {
+ this.start = start;
+ this.end = end;
+ this.exclusive = exclusive;
+ },
+
+ _each: function(iterator) {
+ var value = this.start;
+ do {
+ iterator(value);
+ value = value.succ();
+ } while (this.include(value));
+ },
+
+ include: function(value) {
+ if (value < this.start)
+ return false;
+ if (this.exclusive)
+ return value < this.end;
+ return value <= this.end;
+ }
+});
+
+var $R = function(start, end, exclusive) {
+ return new ObjectRange(start, end, exclusive);
+}
+
+function $() {
+ var results = [], element;
+ for (var i = 0; i < arguments.length; i++) {
+ element = arguments[i];
+ if (typeof element == 'string')
+ element = document.getElementById(element);
+ results.push(Element.extend(element));
+ }
+ return results.length < 2 ? results[0] : results;
+}
+
+document.getElementsByClassName = function(className, parentElement) {
+ var children = ($(parentElement) || document.body).getElementsByTagName('*');
+ return $A(children).inject([], function(elements, child) {
+ if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
+ elements.push(Element.extend(child));
+ return elements;
+ });
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Element)
+ var Element = new Object();
+
+Element.extend = function(element) {
+ if (!element) return;
+ if (_nativeExtensions) return element;
+
+ if (!element._extended && element.tagName && element != window) {
+ var methods = Element.Methods, cache = Element.extend.cache;
+ for (property in methods) {
+ var value = methods[property];
+ if (typeof value == 'function')
+ element[property] = cache.findOrStore(value);
+ }
+ }
+
+ element._extended = true;
+ return element;
+}
+
+Element.extend.cache = {
+ findOrStore: function(value) {
+ return this[value] = this[value] || function() {
+ return value.apply(null, [this].concat($A(arguments)));
+ }
+ }
+}
+
+Element.Methods = {
+ visible: function(element) {
+ return $(element).style.display != 'none';
+ },
+
+ toggle: function() {
+ for (var i = 0; i < arguments.length; i++) {
+ var element = $(arguments[i]);
+ Element[Element.visible(element) ? 'hide' : 'show'](element);
+ }
+ },
+
+ hide: function() {
+ for (var i = 0; i < arguments.length; i++) {
+ var element = $(arguments[i]);
+ element.style.display = 'none';
+ }
+ },
+
+ show: function() {
+ for (var i = 0; i < arguments.length; i++) {
+ var element = $(arguments[i]);
+ element.style.display = '';
+ }
+ },
+
+ remove: function(element) {
+ element = $(element);
+ element.parentNode.removeChild(element);
+ },
+
+ update: function(element, html) {
+ $(element).innerHTML = html.stripScripts();
+ setTimeout(function() {html.evalScripts()}, 10);
+ },
+
+ replace: function(element, html) {
+ element = $(element);
+ if (element.outerHTML) {
+ element.outerHTML = html.stripScripts();
+ } else {
+ var range = element.ownerDocument.createRange();
+ range.selectNodeContents(element);
+ element.parentNode.replaceChild(
+ range.createContextualFragment(html.stripScripts()), element);
+ }
+ setTimeout(function() {html.evalScripts()}, 10);
+ },
+
+ getHeight: function(element) {
+ element = $(element);
+ return element.offsetHeight;
+ },
+
+ classNames: function(element) {
+ return new Element.ClassNames(element);
+ },
+
+ hasClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ return Element.classNames(element).include(className);
+ },
+
+ addClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ return Element.classNames(element).add(className);
+ },
+
+ removeClassName: function(element, className) {
+ if (!(element = $(element))) return;
+ return Element.classNames(element).remove(className);
+ },
+
+ // removes whitespace-only text node children
+ cleanWhitespace: function(element) {
+ element = $(element);
+ for (var i = 0; i < element.childNodes.length; i++) {
+ var node = element.childNodes[i];
+ if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+ Element.remove(node);
+ }
+ },
+
+ empty: function(element) {
+ return $(element).innerHTML.match(/^\s*$/);
+ },
+
+ childOf: function(element, ancestor) {
+ element = $(element), ancestor = $(ancestor);
+ while (element = element.parentNode)
+ if (element == ancestor) return true;
+ return false;
+ },
+
+ scrollTo: function(element) {
+ element = $(element);
+ var x = element.x ? element.x : element.offsetLeft,
+ y = element.y ? element.y : element.offsetTop;
+ window.scrollTo(x, y);
+ },
+
+ getStyle: function(element, style) {
+ element = $(element);
+ var value = element.style[style.camelize()];
+ if (!value) {
+ if (document.defaultView && document.defaultView.getComputedStyle) {
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = css ? css.getPropertyValue(style) : null;
+ } else if (element.currentStyle) {
+ value = element.currentStyle[style.camelize()];
+ }
+ }
+
+ if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
+ if (Element.getStyle(element, 'position') == 'static') value = 'auto';
+
+ return value == 'auto' ? null : value;
+ },
+
+ setStyle: function(element, style) {
+ element = $(element);
+ for (var name in style)
+ element.style[name.camelize()] = style[name];
+ },
+
+ getDimensions: function(element) {
+ element = $(element);
+ if (Element.getStyle(element, 'display') != 'none')
+ return {width: element.offsetWidth, height: element.offsetHeight};
+
+ // All *Width and *Height properties give 0 on elements with display none,
+ // so enable the element temporarily
+ var els = element.style;
+ var originalVisibility = els.visibility;
+ var originalPosition = els.position;
+ els.visibility = 'hidden';
+ els.position = 'absolute';
+ els.display = '';
+ var originalWidth = element.clientWidth;
+ var originalHeight = element.clientHeight;
+ els.display = 'none';
+ els.position = originalPosition;
+ els.visibility = originalVisibility;
+ return {width: originalWidth, height: originalHeight};
+ },
+
+ makePositioned: function(element) {
+ element = $(element);
+ var pos = Element.getStyle(element, 'position');
+ if (pos == 'static' || !pos) {
+ element._madePositioned = true;
+ element.style.position = 'relative';
+ // Opera returns the offset relative to the positioning context, when an
+ // element is position relative but top and left have not been defined
+ if (window.opera) {
+ element.style.top = 0;
+ element.style.left = 0;
+ }
+ }
+ },
+
+ undoPositioned: function(element) {
+ element = $(element);
+ if (element._madePositioned) {
+ element._madePositioned = undefined;
+ element.style.position =
+ element.style.top =
+ element.style.left =
+ element.style.bottom =
+ element.style.right = '';
+ }
+ },
+
+ makeClipping: function(element) {
+ element = $(element);
+ if (element._overflow) return;
+ element._overflow = element.style.overflow;
+ if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
+ element.style.overflow = 'hidden';
+ },
+
+ undoClipping: function(element) {
+ element = $(element);
+ if (element._overflow) return;
+ element.style.overflow = element._overflow;
+ element._overflow = undefined;
+ }
+}
+
+Object.extend(Element, Element.Methods);
+
+var _nativeExtensions = false;
+
+if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+ var HTMLElement = {}
+ HTMLElement.prototype = document.createElement('div').__proto__;
+}
+
+Element.addMethods = function(methods) {
+ Object.extend(Element.Methods, methods || {});
+
+ if(typeof HTMLElement != 'undefined') {
+ var methods = Element.Methods, cache = Element.extend.cache;
+ for (property in methods) {
+ var value = methods[property];
+ if (typeof value == 'function')
+ HTMLElement.prototype[property] = cache.findOrStore(value);
+ }
+ _nativeExtensions = true;
+ }
+}
+
+Element.addMethods();
+
+var Toggle = new Object();
+Toggle.display = Element.toggle;
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.Insertion = function(adjacency) {
+ this.adjacency = adjacency;
+}
+
+Abstract.Insertion.prototype = {
+ initialize: function(element, content) {
+ this.element = $(element);
+ this.content = content.stripScripts();
+
+ if (this.adjacency && this.element.insertAdjacentHTML) {
+ try {
+ this.element.insertAdjacentHTML(this.adjacency, this.content);
+ } catch (e) {
+ var tagName = this.element.tagName.toLowerCase();
+ if (tagName == 'tbody' || tagName == 'tr') {
+ this.insertContent(this.contentFromAnonymousTable());
+ } else {
+ throw e;
+ }
+ }
+ } else {
+ this.range = this.element.ownerDocument.createRange();
+ if (this.initializeRange) this.initializeRange();
+ this.insertContent([this.range.createContextualFragment(this.content)]);
+ }
+
+ setTimeout(function() {content.evalScripts()}, 10);
+ },
+
+ contentFromAnonymousTable: function() {
+ var div = document.createElement('div');
+ div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
+ return $A(div.childNodes[0].childNodes[0].childNodes);
+ }
+}
+
+var Insertion = new Object();
+
+Insertion.Before = Class.create();
+Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
+ initializeRange: function() {
+ this.range.setStartBefore(this.element);
+ },
+
+ insertContent: function(fragments) {
+ fragments.each((function(fragment) {
+ this.element.parentNode.insertBefore(fragment, this.element);
+ }).bind(this));
+ }
+});
+
+Insertion.Top = Class.create();
+Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
+ initializeRange: function() {
+ this.range.selectNodeContents(this.element);
+ this.range.collapse(true);
+ },
+
+ insertContent: function(fragments) {
+ fragments.reverse(false).each((function(fragment) {
+ this.element.insertBefore(fragment, this.element.firstChild);
+ }).bind(this));
+ }
+});
+
+Insertion.Bottom = Class.create();
+Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
+ initializeRange: function() {
+ this.range.selectNodeContents(this.element);
+ this.range.collapse(this.element);
+ },
+
+ insertContent: function(fragments) {
+ fragments.each((function(fragment) {
+ this.element.appendChild(fragment);
+ }).bind(this));
+ }
+});
+
+Insertion.After = Class.create();
+Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
+ initializeRange: function() {
+ this.range.setStartAfter(this.element);
+ },
+
+ insertContent: function(fragments) {
+ fragments.each((function(fragment) {
+ this.element.parentNode.insertBefore(fragment,
+ this.element.nextSibling);
+ }).bind(this));
+ }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+ initialize: function(element) {
+ this.element = $(element);
+ },
+
+ _each: function(iterator) {
+ this.element.className.split(/\s+/).select(function(name) {
+ return name.length > 0;
+ })._each(iterator);
+ },
+
+ set: function(className) {
+ this.element.className = className;
+ },
+
+ add: function(classNameToAdd) {
+ if (this.include(classNameToAdd)) return;
+ this.set(this.toArray().concat(classNameToAdd).join(' '));
+ },
+
+ remove: function(classNameToRemove) {
+ if (!this.include(classNameToRemove)) return;
+ this.set(this.select(function(className) {
+ return className != classNameToRemove;
+ }).join(' '));
+ },
+
+ toString: function() {
+ return this.toArray().join(' ');
+ }
+}
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+
+var Field = {
+ clear: function() {
+ for (var i = 0; i < arguments.length; i++)
+ $(arguments[i]).value = '';
+ },
+
+ focus: function(element) {
+ $(element).focus();
+ },
+
+ present: function() {
+ for (var i = 0; i < arguments.length; i++)
+ if ($(arguments[i]).value == '') return false;
+ return true;
+ },
+
+ select: function(element) {
+ $(element).select();
+ },
+
+ activate: function(element) {
+ element = $(element);
+ element.focus();
+ if (element.select)
+ element.select();
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Form = {
+ serialize: function(form) {
+ var elements = Form.getElements($(form));
+ var queryComponents = new Array();
+
+ for (var i = 0; i < elements.length; i++) {
+ var queryComponent = Form.Element.serialize(elements[i]);
+ if (queryComponent)
+ queryComponents.push(queryComponent);
+ }
+
+ return queryComponents.join('&');
+ },
+
+ getElements: function(form) {
+ form = $(form);
+ var elements = new Array();
+
+ for (var tagName in Form.Element.Serializers) {
+ var tagElements = form.getElementsByTagName(tagName);
+ for (var j = 0; j < tagElements.length; j++)
+ elements.push(tagElements[j]);
+ }
+ return elements;
+ },
+
+ getInputs: function(form, typeName, name) {
+ form = $(form);
+ var inputs = form.getElementsByTagName('input');
+
+ if (!typeName && !name)
+ return inputs;
+
+ var matchingInputs = new Array();
+ for (var i = 0; i < inputs.length; i++) {
+ var input = inputs[i];
+ if ((typeName && input.type != typeName) ||
+ (name && input.name != name))
+ continue;
+ matchingInputs.push(input);
+ }
+
+ return matchingInputs;
+ },
+
+ disable: function(form) {
+ var elements = Form.getElements(form);
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ element.blur();
+ element.disabled = 'true';
+ }
+ },
+
+ enable: function(form) {
+ var elements = Form.getElements(form);
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i];
+ element.disabled = '';
+ }
+ },
+
+ findFirstElement: function(form) {
+ return Form.getElements(form).find(function(element) {
+ return element.type != 'hidden' && !element.disabled &&
+ ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+ });
+ },
+
+ focusFirstElement: function(form) {
+ Field.activate(Form.findFirstElement(form));
+ },
+
+ reset: function(form) {
+ $(form).reset();
+ }
+}
+
+Form.Element = {
+ serialize: function(element) {
+ element = $(element);
+ var method = element.tagName.toLowerCase();
+ var parameter = Form.Element.Serializers[method](element);
+
+ if (parameter) {
+ var key = encodeURIComponent(parameter[0]);
+ if (key.length == 0) return;
+
+ if (parameter[1].constructor != Array)
+ parameter[1] = [parameter[1]];
+
+ return parameter[1].map(function(value) {
+ return key + '=' + encodeURIComponent(value);
+ }).join('&');
+ }
+ },
+
+ getValue: function(element) {
+ element = $(element);
+ var method = element.tagName.toLowerCase();
+ var parameter = Form.Element.Serializers[method](element);
+
+ if (parameter)
+ return parameter[1];
+ }
+}
+
+Form.Element.Serializers = {
+ input: function(element) {
+ if(typeof(element.type) == "undefined")
+ return false;
+ switch (element.type.toLowerCase()) {
+ case 'submit':
+ case 'hidden':
+ case 'password':
+ case 'text':
+ return Form.Element.Serializers.textarea(element);
+ case 'checkbox':
+ case 'radio':
+ return Form.Element.Serializers.inputSelector(element);
+ }
+ return false;
+ },
+
+ inputSelector: function(element) {
+ if (element.checked)
+ return [element.name, element.value];
+ },
+
+ textarea: function(element) {
+ return [element.name, element.value];
+ },
+
+ select: function(element) {
+ return Form.Element.Serializers[element.type == 'select-one' ?
+ 'selectOne' : 'selectMany'](element);
+ },
+
+ selectOne: function(element) {
+ var value = '', opt, index = element.selectedIndex;
+ if (index >= 0) {
+ opt = element.options[index];
+ value = opt.value || opt.text;
+ }
+ return [element.name, value];
+ },
+
+ selectMany: function(element) {
+ var value = [];
+ for (var i = 0; i < element.length; i++) {
+ var opt = element.options[i];
+ if (opt.selected)
+ value.push(opt.value || opt.text);
+ }
+ return [element.name, value];
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var $F = Form.Element.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = function() {}
+Abstract.TimedObserver.prototype = {
+ initialize: function(element, frequency, callback) {
+ this.frequency = frequency;
+ this.element = $(element);
+ this.callback = callback;
+
+ this.lastValue = this.getValue();
+ this.registerCallback();
+ },
+
+ registerCallback: function() {
+ setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+ },
+
+ onTimerEvent: function() {
+ var value = this.getValue();
+ if (this.lastValue != value) {
+ this.callback(this.element, value);
+ this.lastValue = value;
+ }
+ }
+}
+
+Form.Element.Observer = Class.create();
+Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+ getValue: function() {
+ return Form.Element.getValue(this.element);
+ }
+});
+
+Form.Observer = Class.create();
+Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+ getValue: function() {
+ return Form.serialize(this.element);
+ }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = function() {}
+Abstract.EventObserver.prototype = {
+ initialize: function(element, callback) {
+ this.element = $(element);
+ this.callback = callback;
+
+ this.lastValue = this.getValue();
+ if (this.element.tagName.toLowerCase() == 'form')
+ this.registerFormCallbacks();
+ else
+ this.registerCallback(this.element);
+ },
+
+ onElementEvent: function() {
+ var value = this.getValue();
+ if (this.lastValue != value) {
+ this.callback(this.element, value);
+ this.lastValue = value;
+ }
+ },
+
+ registerFormCallbacks: function() {
+ var elements = Form.getElements(this.element);
+ for (var i = 0; i < elements.length; i++)
+ this.registerCallback(elements[i]);
+ },
+
+ registerCallback: function(element) {
+ if (element.type) {
+ switch (element.type.toLowerCase()) {
+ case 'checkbox':
+ case 'radio':
+ Event.observe(element, 'click', this.onElementEvent.bind(this));
+ break;
+ case 'password':
+ case 'text':
+ case 'textarea':
+ case 'select-one':
+ case 'select-multiple':
+ Event.observe(element, 'change', this.onElementEvent.bind(this));
+ break;
+ }
+ }
+ }
+}
+
+Form.Element.EventObserver = Class.create();
+Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+ getValue: function() {
+ return Form.Element.getValue(this.element);
+ }
+});
+
+Form.EventObserver = Class.create();
+Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+ getValue: function() {
+ return Form.serialize(this.element);
+ }
+});
+
+
+
+if (!window.Event) {
+ var Event = new Object();
+}
+
+Object.extend(Event, {
+ KEY_BACKSPACE: 8,
+ KEY_TAB: 9,
+ KEY_RETURN: 13,
+ KEY_ESC: 27,
+ KEY_LEFT: 37,
+ KEY_UP: 38,
+ KEY_RIGHT: 39,
+ KEY_DOWN: 40,
+ KEY_DELETE: 46,
+ KEY_SPACEBAR: 32,
+
+ element: function(event) {
+ return event.target || event.srcElement;
+ },
+
+ isLeftClick: function(event) {
+ return (((event.which) && (event.which == 1)) ||
+ ((event.button) && (event.button == 1)));
+ },
+
+ pointerX: function(event) {
+ return event.pageX || (event.clientX +
+ (document.documentElement.scrollLeft || document.body.scrollLeft));
+ },
+
+ pointerY: function(event) {
+ return event.pageY || (event.clientY +
+ (document.documentElement.scrollTop || document.body.scrollTop));
+ },
+
+ stop: function(event) {
+ if (event.preventDefault) {
+ event.preventDefault();
+ event.stopPropagation();
+ } else {
+ event.returnValue = false;
+ event.cancelBubble = true;
+ }
+ },
+
+ // find the first node with the given tagName, starting from the
+ // node the event was triggered on; traverses the DOM upwards
+ findElement: function(event, tagName) {
+ var element = Event.element(event);
+ while (element.parentNode && (!element.tagName ||
+ (element.tagName.toUpperCase() != tagName.toUpperCase())))
+ element = element.parentNode;
+ return element;
+ },
+
+ observers: false,
+
+ _observeAndCache: function(element, name, observer, useCapture) {
+ if (!this.observers) this.observers = [];
+ if (element.addEventListener) {
+ this.observers.push([element, name, observer, useCapture]);
+ element.addEventListener(name, observer, useCapture);
+ } else if (element.attachEvent) {
+ this.observers.push([element, name, observer, useCapture]);
+ element.attachEvent('on' + name, observer);
+ }
+ },
+
+ unloadCache: function() {
+ if (!Event.observers) return;
+ for (var i = 0; i < Event.observers.length; i++) {
+ Event.stopObserving.apply(this, Event.observers[i]);
+ Event.observers[i][0] = null;
+ }
+ Event.observers = false;
+ },
+
+ observe: function(element, name, observer, useCapture) {
+ var element = $(element);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+ || element.attachEvent))
+ name = 'keydown';
+
+ this._observeAndCache(element, name, observer, useCapture);
+ },
+
+ stopObserving: function(element, name, observer, useCapture) {
+ var element = $(element);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+ || element.detachEvent))
+ name = 'keydown';
+
+ if (element.removeEventListener) {
+ element.removeEventListener(name, observer, useCapture);
+ } else if (element.detachEvent) {
+ element.detachEvent('on' + name, observer);
+ }
+ }
+});
+
+/* prevent memory leaks in IE */
+if (navigator.appVersion.match(/\bMSIE\b/))
+ Event.observe(window, 'unload', Event.unloadCache, false);
+
+
+/**
+ * @class Event extensions.
+ */
+Object.extend(Event,
+{
+ /**
+ * Register a function to be executed when the page is loaded.
+ * Note that the page is only loaded if all resources (e.g. images)
+ * are loaded.
+ *
+ * Example: Show an alert box with message "Page Loaded!" when the
+ * page finished loading.
+ * <code>
+ * Event.OnLoad(function(){ alert("Page Loaded!"); });
+ * </code>
+ *
+ * @param {Function} function to execute when page is loaded.
+ */
+ OnLoad : function (fn)
+ {
+ // opera onload is in document, not window
+ var w = document.addEventListener &&
+ !window.addEventListener ? document : window;
+ Event.observe(w,'load',fn);
+ },
+
+ /**
+ * @param {Event} a keyboard event
+ * @return {Number} the Unicode character code generated by the key
+ * that was struck.
+ */
+ keyCode : function(e)
+ {
+ return e.keyCode != null ? e.keyCode : e.charCode
+ },
+
+ /**
+ * @param {String} event type or event name.
+ * @return {Boolean} true if event type is of HTMLEvent, false
+ * otherwise
+ */
+ isHTMLEvent : function(type)
+ {
+ var events = ['abort', 'blur', 'change', 'error', 'focus',
+ 'load', 'reset', 'resize', 'scroll', 'select',
+ 'submit', 'unload'];
+ return events.include(type);
+ },
+
+ /**
+ * @param {String} event type or event name
+ * @return {Boolean} true if event type is of MouseEvent,
+ * false otherwise
+ */
+ isMouseEvent : function(type)
+ {
+ var events = ['click', 'mousedown', 'mousemove', 'mouseout',
+ 'mouseover', 'mouseup'];
+ return events.include(type);
+ },
+
+ /**
+ * Dispatch the DOM event of a given <tt>type</tt> on a DOM
+ * <tt>element</tt>. Only HTMLEvent and MouseEvent can be
+ * dispatched, keyboard events or UIEvent can not be dispatch
+ * via javascript consistently.
+ * For the "submit" event the submit() method is called.
+ * @param {Object} element id string or a DOM element.
+ * @param {String} event type to dispatch.
+ */
+ fireEvent : function(element,type,canBubble)
+ {
+ canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
+ element = $(element);
+ if(type == "submit")
+ return element.submit();
+ if(document.createEvent)
+ {
+ if(Event.isHTMLEvent(type))
+ {
+ var event = document.createEvent('HTMLEvents');
+ event.initEvent(type, canBubble, true);
+ }
+ else if(Event.isMouseEvent(type))
+ {
+ var event = document.createEvent('MouseEvents');
+ if (event.initMouseEvent)
+ {
+ event.initMouseEvent(type,canBubble,true,
+ document.defaultView, 1, 0, 0, 0, 0, false,
+ false, false, false, 0, null);
+ }
+ else
+ {
+ // Safari
+ // TODO we should be initialising other mouse-event related attributes here
+ event.initEvent(type, canBubble, true);
+ }
+ }
+ element.dispatchEvent(event);
+ }
+ else if(document.createEventObject)
+ {
+ var evObj = document.createEventObject();
+ element.fireEvent('on'+type, evObj);
+ }
+ else if(typeof(element['on'+type]) == "function")
+ element['on'+type]();
+ }
+});
+
+var Position = {
+ // set to true if needed, warning: firefox performance problems
+ // NOT neeeded for page scrolling, only if draggable contained in
+ // scrollable elements
+ includeScrollOffsets: false,
+
+ // must be called before calling withinIncludingScrolloffset, every time the
+ // page is scrolled
+ prepare: function() {
+ this.deltaX = window.pageXOffset
+ || document.documentElement.scrollLeft
+ || document.body.scrollLeft
+ || 0;
+ this.deltaY = window.pageYOffset
+ || document.documentElement.scrollTop
+ || document.body.scrollTop
+ || 0;
+ },
+
+ realOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.scrollTop || 0;
+ valueL += element.scrollLeft || 0;
+ element = element.parentNode;
+ } while (element);
+ return [valueL, valueT];
+ },
+
+ cumulativeOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while (element);
+ return [valueL, valueT];
+ },
+
+ positionedOffset: function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ if (element) {
+ p = Element.getStyle(element, 'position');
+ if (p == 'relative' || p == 'absolute') break;
+ }
+ } while (element);
+ return [valueL, valueT];
+ },
+
+ offsetParent: function(element) {
+ if (element.offsetParent) return element.offsetParent;
+ if (element == document.body) return element;
+
+ while ((element = element.parentNode) && element != document.body)
+ if (Element.getStyle(element, 'position') != 'static')
+ return element;
+
+ return document.body;
+ },
+
+ // caches x/y coordinate pair to use with overlap
+ within: function(element, x, y) {
+ if (this.includeScrollOffsets)
+ return this.withinIncludingScrolloffsets(element, x, y);
+ this.xcomp = x;
+ this.ycomp = y;
+ this.offset = this.cumulativeOffset(element);
+
+ return (y >= this.offset[1] &&
+ y < this.offset[1] + element.offsetHeight &&
+ x >= this.offset[0] &&
+ x < this.offset[0] + element.offsetWidth);
+ },
+
+ withinIncludingScrolloffsets: function(element, x, y) {
+ var offsetcache = this.realOffset(element);
+
+ this.xcomp = x + offsetcache[0] - this.deltaX;
+ this.ycomp = y + offsetcache[1] - this.deltaY;
+ this.offset = this.cumulativeOffset(element);
+
+ return (this.ycomp >= this.offset[1] &&
+ this.ycomp < this.offset[1] + element.offsetHeight &&
+ this.xcomp >= this.offset[0] &&
+ this.xcomp < this.offset[0] + element.offsetWidth);
+ },
+
+ // within must be called directly before
+ overlap: function(mode, element) {
+ if (!mode) return 0;
+ if (mode == 'vertical')
+ return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+ element.offsetHeight;
+ if (mode == 'horizontal')
+ return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+ element.offsetWidth;
+ },
+
+ clone: function(source, target) {
+ source = $(source);
+ target = $(target);
+ target.style.position = 'absolute';
+ var offsets = this.cumulativeOffset(source);
+ target.style.top = offsets[1] + 'px';
+ target.style.left = offsets[0] + 'px';
+ target.style.width = source.offsetWidth + 'px';
+ target.style.height = source.offsetHeight + 'px';
+ },
+
+ page: function(forElement) {
+ var valueT = 0, valueL = 0;
+
+ var element = forElement;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+
+ // Safari fix
+ if (element.offsetParent==document.body)
+ if (Element.getStyle(element,'position')=='absolute') break;
+
+ } while (element = element.offsetParent);
+
+ element = forElement;
+ do {
+ valueT -= element.scrollTop || 0;
+ valueL -= element.scrollLeft || 0;
+ } while (element = element.parentNode);
+
+ return [valueL, valueT];
+ },
+
+ clone: function(source, target) {
+ var options = Object.extend({
+ setLeft: true,
+ setTop: true,
+ setWidth: true,
+ setHeight: true,
+ offsetTop: 0,
+ offsetLeft: 0
+ }, arguments[2] || {})
+
+ // find page position of source
+ source = $(source);
+ var p = Position.page(source);
+
+ // find coordinate system to use
+ target = $(target);
+ var delta = [0, 0];
+ var parent = null;
+ // delta [0,0] will do fine with position: fixed elements,
+ // position:absolute needs offsetParent deltas
+ if (Element.getStyle(target,'position') == 'absolute') {
+ parent = Position.offsetParent(target);
+ delta = Position.page(parent);
+ }
+
+ // correct by body offsets (fixes Safari)
+ if (parent == document.body) {
+ delta[0] -= document.body.offsetLeft;
+ delta[1] -= document.body.offsetTop;
+ }
+
+ // set position
+ if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px';
+ if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px';
+ if(options.setWidth) target.style.width = source.offsetWidth + 'px';
+ if(options.setHeight) target.style.height = source.offsetHeight + 'px';
+ },
+
+ absolutize: function(element) {
+ element = $(element);
+ if (element.style.position == 'absolute') return;
+ Position.prepare();
+
+ var offsets = Position.positionedOffset(element);
+ var top = offsets[1];
+ var left = offsets[0];
+ var width = element.clientWidth;
+ var height = element.clientHeight;
+
+ element._originalLeft = left - parseFloat(element.style.left || 0);
+ element._originalTop = top - parseFloat(element.style.top || 0);
+ element._originalWidth = element.style.width;
+ element._originalHeight = element.style.height;
+
+ element.style.position = 'absolute';
+ element.style.top = top + 'px';;
+ element.style.left = left + 'px';;
+ element.style.width = width + 'px';;
+ element.style.height = height + 'px';;
+ },
+
+ relativize: function(element) {
+ element = $(element);
+ if (element.style.position == 'relative') return;
+ Position.prepare();
+
+ element.style.position = 'relative';
+ var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
+ var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+ element.style.top = top + 'px';
+ element.style.left = left + 'px';
+ element.style.height = element._originalHeight;
+ element.style.width = element._originalWidth;
+ }
+}
+
+// Safari returns margins on body which is incorrect if the child is absolutely
+// positioned. For performance reasons, redefine Position.cumulativeOffset for
+// KHTML/WebKit only.
+if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+ Position.cumulativeOffset = function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ if (element.offsetParent == document.body)
+ if (Element.getStyle(element, 'position') == 'absolute') break;
+
+ element = element.offsetParent;
+ } while (element);
+
+ return [valueL, valueT];
+ }
+}
+
+
+
+
+var Selector = Class.create();
+Selector.prototype = {
+ initialize: function(expression) {
+ this.params = {classNames: []};
+ this.expression = expression.toString().strip();
+ this.parseExpression();
+ this.compileMatcher();
+ },
+
+ parseExpression: function() {
+ function abort(message) { throw 'Parse error in selector: ' + message; }
+
+ if (this.expression == '') abort('empty expression');
+
+ var params = this.params, expr = this.expression, match, modifier, clause, rest;
+ while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {
+ params.attributes = params.attributes || [];
+ params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});
+ expr = match[1];
+ }
+
+ if (expr == '*') return this.params.wildcard = true;
+
+ while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
+ modifier = match[1], clause = match[2], rest = match[3];
+ switch (modifier) {
+ case '#': params.id = clause; break;
+ case '.': params.classNames.push(clause); break;
+ case '':
+ case undefined: params.tagName = clause.toUpperCase(); break;
+ default: abort(expr.inspect());
+ }
+ expr = rest;
+ }
+
+ if (expr.length > 0) abort(expr.inspect());
+ },
+
+ buildMatchExpression: function() {
+ var params = this.params, conditions = [], clause;
+
+ if (params.wildcard)
+ conditions.push('true');
+ if (clause = params.id)
+ conditions.push('element.id == ' + clause.inspect());
+ if (clause = params.tagName)
+ conditions.push('element.tagName.toUpperCase() == ' + clause.inspect());
+ if ((clause = params.classNames).length > 0)
+ for (var i = 0; i < clause.length; i++)
+ conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')');
+ if (clause = params.attributes) {
+ clause.each(function(attribute) {
+ var value = 'element.getAttribute(' + attribute.name.inspect() + ')';
+ var splitValueBy = function(delimiter) {
+ return value + ' && ' + value + '.split(' + delimiter.inspect() + ')';
+ }
+
+ switch (attribute.operator) {
+ case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break;
+ case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break;
+ case '|=': conditions.push(
+ splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect()
+ ); break;
+ case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break;
+ case '':
+ case undefined: conditions.push(value + ' != null'); break;
+ default: throw 'Unknown operator ' + attribute.operator + ' in selector';
+ }
+ });
+ }
+
+ return conditions.join(' && ');
+ },
+
+ compileMatcher: function() {
+ this.match = new Function('element', 'if (!element.tagName) return false; \
+ return ' + this.buildMatchExpression());
+ },
+
+ findElements: function(scope) {
+ var element;
+
+ if (element = $(this.params.id))
+ if (this.match(element))
+ if (!scope || Element.childOf(element, scope))
+ return [element];
+
+ scope = (scope || document).getElementsByTagName(this.params.tagName || '*');
+
+ var results = [];
+ for (var i = 0; i < scope.length; i++)
+ if (this.match(element = scope[i]))
+ results.push(Element.extend(element));
+
+ return results;
+ },
+
+ toString: function() {
+ return this.expression;
+ }
+}
+
+function $$() {
+ return $A(arguments).map(function(expression) {
+ return expression.strip().split(/\s+/).inject([null], function(results, expr) {
+ var selector = new Selector(expr);
+ return results.map(selector.findElements.bind(selector)).flatten();
+ });
+ }).flatten();
+}
+
+
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// See scriptaculous.js for full license.
+
+var Builder = {
+ NODEMAP: {
+ AREA: 'map',
+ CAPTION: 'table',
+ COL: 'table',
+ COLGROUP: 'table',
+ LEGEND: 'fieldset',
+ OPTGROUP: 'select',
+ OPTION: 'select',
+ PARAM: 'object',
+ TBODY: 'table',
+ TD: 'table',
+ TFOOT: 'table',
+ TH: 'table',
+ THEAD: 'table',
+ TR: 'table'
+ },
+ // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+ // due to a Firefox bug
+ node: function(elementName) {
+ elementName = elementName.toUpperCase();
+
+ // try innerHTML approach
+ var parentTag = this.NODEMAP[elementName] || 'div';
+ var parentElement = document.createElement(parentTag);
+ try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+ parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+ } catch(e) {}
+ var element = parentElement.firstChild || null;
+
+ // see if browser added wrapping tags
+ if(element && (element.tagName != elementName))
+ element = element.getElementsByTagName(elementName)[0];
+
+ // fallback to createElement approach
+ if(!element) element = document.createElement(elementName);
+
+ // abort if nothing could be created
+ if(!element) return;
+
+ // attributes (or text)
+ if(arguments[1])
+ if(this._isStringOrNumber(arguments[1]) ||
+ (arguments[1] instanceof Array)) {
+ this._children(element, arguments[1]);
+ } else {
+ var attrs = this._attributes(arguments[1]);
+ if(attrs.length) {
+ try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+ parentElement.innerHTML = "<" +elementName + " " +
+ attrs + "></" + elementName + ">";
+ } catch(e) {}
+ element = parentElement.firstChild || null;
+ // workaround firefox 1.0.X bug
+ if(!element) {
+ element = document.createElement(elementName);
+ for(attr in arguments[1])
+ element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+ }
+ if(element.tagName != elementName)
+ element = parentElement.getElementsByTagName(elementName)[0];
+ }
+ }
+
+ // text, or array of children
+ if(arguments[2])
+ this._children(element, arguments[2]);
+
+ return element;
+ },
+ _text: function(text) {
+ return document.createTextNode(text);
+ },
+ _attributes: function(attributes) {
+ var attrs = [];
+ for(attribute in attributes)
+ attrs.push((attribute=='className' ? 'class' : attribute) +
+ '="' + attributes[attribute].toString().escapeHTML() + '"');
+ return attrs.join(" ");
+ },
+ _children: function(element, children) {
+ if(typeof children=='object') { // array can hold nodes and text
+ children.flatten().each( function(e) {
+ if(typeof e=='object')
+ element.appendChild(e)
+ else
+ if(Builder._isStringOrNumber(e))
+ element.appendChild(Builder._text(e));
+ });
+ } else
+ if(Builder._isStringOrNumber(children))
+ element.appendChild(Builder._text(children));
+ },
+ _isStringOrNumber: function(param) {
+ return(typeof param=='string' || typeof param=='number');
+ }
+}
+
+
+Object.extend(Builder,
+{
+ exportTags:function()
+ {
+ var tags=["BUTTON","TT","PRE","H1","H2","H3","BR","CANVAS","HR","LABEL","TEXTAREA","FORM","STRONG","SELECT","OPTION","OPTGROUP","LEGEND","FIELDSET","P","UL","OL","LI","TD","TR","THEAD","TBODY","TFOOT","TABLE","TH","INPUT","SPAN","A","DIV","IMG", "CAPTION"];
+ tags.each(function(tag)
+ {
+ window[tag]=function()
+ {
+ var args=$A(arguments);
+ if(args.length==0)
+ return Builder.node(tag,null);
+ if(args.length==1)
+ return Builder.node(tag,args[0]);
+ if(args.length>1)
+ return Builder.node(tag,args.shift(),args);
+
+ };
+ });
+ }
+});
+
+Builder.exportTags();
+
+
+
+Object.extend(Date.prototype,
+{
+ SimpleFormat: function(format, data)
+ {
+ data = data || {};
+ var bits = new Array();
+ bits['d'] = this.getDate();
+ bits['dd'] = String(this.getDate()).zerofill(2);
+
+ bits['M'] = this.getMonth()+1;
+ bits['MM'] = String(this.getMonth()+1).zerofill(2);
+ if(data.AbbreviatedMonthNames)
+ bits['MMM'] = data.AbbreviatedMonthNames[this.getMonth()];
+ if(data.MonthNames)
+ bits['MMMM'] = data.MonthNames[this.getMonth()];
+ var yearStr = "" + this.getFullYear();
+ yearStr = (yearStr.length == 2) ? '19' + yearStr: yearStr;
+ bits['yyyy'] = yearStr;
+ bits['yy'] = bits['yyyy'].toString().substr(2,2);
+
+ // do some funky regexs to replace the format string
+ // with the real values
+ var frm = new String(format);
+ for (var sect in bits)
+ {
+ var reg = new RegExp("\\b"+sect+"\\b" ,"g");
+ frm = frm.replace(reg, bits[sect]);
+ }
+ return frm;
+ },
+
+ toISODate : function()
+ {
+ var y = this.getFullYear();
+ var m = String(this.getMonth() + 1).zerofill(2);
+ var d = String(this.getDate()).zerofill(2);
+ return String(y) + String(m) + String(d);
+ }
+});
+
+Object.extend(Date,
+{
+ SimpleParse: function(value, format)
+ {
+ val=String(value);
+ format=String(format);
+
+ if(val.length <= 0) return null;
+
+ if(format.length <= 0) return new Date(value);
+
+ var isInteger = function (val)
+ {
+ var digits="1234567890";
+ for (var i=0; i < val.length; i++)
+ {
+ if (digits.indexOf(val.charAt(i))==-1) { return false; }
+ }
+ return true;
+ };
+
+ var getInt = function(str,i,minlength,maxlength)
+ {
+ for (var x=maxlength; x>=minlength; x--)
+ {
+ var token=str.substring(i,i+x);
+ if (token.length < minlength) { return null; }
+ if (isInteger(token)) { return token; }
+ }
+ return null;
+ };
+
+ var i_val=0;
+ var i_format=0;
+ var c="";
+ var token="";
+ var token2="";
+ var x,y;
+ var now=new Date();
+ var year=now.getFullYear();
+ var month=now.getMonth()+1;
+ var date=1;
+
+ while (i_format < format.length)
+ {
+ // Get next token from format string
+ c=format.charAt(i_format);
+ token="";
+ while ((format.charAt(i_format)==c) && (i_format < format.length))
+ {
+ token += format.charAt(i_format++);
+ }
+
+ // Extract contents of value based on format token
+ if (token=="yyyy" || token=="yy" || token=="y")
+ {
+ if (token=="yyyy") { x=4;y=4; }
+ if (token=="yy") { x=2;y=2; }
+ if (token=="y") { x=2;y=4; }
+ year=getInt(val,i_val,x,y);
+ if (year==null) { return null; }
+ i_val += year.length;
+ if (year.length==2)
+ {
+ if (year > 70) { year=1900+(year-0); }
+ else { year=2000+(year-0); }
+ }
+ }
+
+ else if (token=="MM"||token=="M")
+ {
+ month=getInt(val,i_val,token.length,2);
+ if(month==null||(month<1)||(month>12)){return null;}
+ i_val+=month.length;
+ }
+ else if (token=="dd"||token=="d")
+ {
+ date=getInt(val,i_val,token.length,2);
+ if(date==null||(date<1)||(date>31)){return null;}
+ i_val+=date.length;
+ }
+ else
+ {
+ if (val.substring(i_val,i_val+token.length)!=token) {return null;}
+ else {i_val+=token.length;}
+ }
+ }
+
+ // If there are any trailing characters left in the value, it doesn't match
+ if (i_val != val.length) { return null; }
+
+ // Is date valid for month?
+ if (month==2)
+ {
+ // Check for leap year
+ if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year
+ if (date > 29){ return null; }
+ }
+ else { if (date > 28) { return null; } }
+ }
+
+ if ((month==4)||(month==6)||(month==9)||(month==11))
+ {
+ if (date > 30) { return null; }
+ }
+
+ var newdate=new Date(year,month-1,date, 0, 0, 0);
+ return newdate;
+ }
+});
+
+
+var Prado =
+{
+ Version: '3.1',
+
+ /**
+ * Returns browser information. Example
+ * <code>
+ * var browser = Prado.Browser();
+ * alert(browser.ie); //should ouput true if IE, false otherwise
+ * </code>
+ * @param ${parameter}
+ * @return ${return}
+ */
+ Browser : function()
+ {
+ var info = { Version : "1.0" };
+ var is_major = parseInt( navigator.appVersion );
+ info.nver = is_major;
+ info.ver = navigator.appVersion;
+ info.agent = navigator.userAgent;
+ info.dom = document.getElementById ? 1 : 0;
+ info.opera = window.opera ? 1 : 0;
+ info.ie5 = ( info.ver.indexOf( "MSIE 5" ) > -1 && info.dom && !info.opera ) ? 1 : 0;
+ info.ie6 = ( info.ver.indexOf( "MSIE 6" ) > -1 && info.dom && !info.opera ) ? 1 : 0;
+ info.ie4 = ( document.all && !info.dom && !info.opera ) ? 1 : 0;
+ info.ie = info.ie4 || info.ie5 || info.ie6;
+ info.mac = info.agent.indexOf( "Mac" ) > -1;
+ info.ns6 = ( info.dom && parseInt( info.ver ) >= 5 ) ? 1 : 0;
+ info.ie3 = ( info.ver.indexOf( "MSIE" ) && ( is_major < 4 ) );
+ info.hotjava = ( info.agent.toLowerCase().indexOf( 'hotjava' ) != -1 ) ? 1 : 0;
+ info.ns4 = ( document.layers && !info.dom && !info.hotjava ) ? 1 : 0;
+ info.bw = ( info.ie6 || info.ie5 || info.ie4 || info.ns4 || info.ns6 || info.opera );
+ info.ver3 = ( info.hotjava || info.ie3 );
+ info.opera7 = ( ( info.agent.toLowerCase().indexOf( 'opera 7' ) > -1 ) || ( info.agent.toLowerCase().indexOf( 'opera/7' ) > -1 ) );
+ info.operaOld = info.opera && !info.opera7;
+ return info;
+ },
+
+ ImportCss : function(doc, css_file)
+ {
+ if (Prado.Browser().ie)
+ var styleSheet = doc.createStyleSheet(css_file);
+ else
+ {
+ var elm = doc.createElement("link");
+
+ elm.rel = "stylesheet";
+ elm.href = css_file;
+
+ if (headArr = doc.getElementsByTagName("head"))
+ headArr[0].appendChild(elm);
+ }
+ }
+};
+
+
+/*Prado.Focus = Class.create();
+
+Prado.Focus.setFocus = function(id)
+{
+ var target = document.getElementById ? document.getElementById(id) : document.all[id];
+ if(target && !Prado.Focus.canFocusOn(target))
+ {
+ target = Prado.Focus.findTarget(target);
+ }
+ if(target)
+ {
+ try
+ {
+ target.focus();
+ target.scrollIntoView(false);
+ if (window.__smartNav)
+ {
+ window.__smartNav.ae = target.id;
+ }
+ }
+ catch (e)
+ {
+ }
+ }
+}
+
+Prado.Focus.canFocusOn = function(element)
+{
+ if(!element || !(element.tagName))
+ return false;
+ var tagName = element.tagName.toLowerCase();
+ return !element.disabled && (!element.type || element.type.toLowerCase() != "hidden") && Prado.Focus.isFocusableTag(tagName) && Prado.Focus.isVisible(element);
+}
+
+Prado.Focus.isFocusableTag = function(tagName)
+{
+ return (tagName == "input" || tagName == "textarea" || tagName == "select" || tagName == "button" || tagName == "a");
+}
+
+
+Prado.Focus.findTarget = function(element)
+{
+ if(!element || !(element.tagName))
+ {
+ return null;
+ }
+ var tagName = element.tagName.toLowerCase();
+ if (tagName == "undefined")
+ {
+ return null;
+ }
+ var children = element.childNodes;
+ if (children)
+ {
+ for(var i=0;i<children.length;i++)
+ {
+ try
+ {
+ if(Prado.Focus.canFocusOn(children[i]))
+ {
+ return children[i];
+ }
+ else
+ {
+ var target = Prado.Focus.findTarget(children[i]);
+ if(target)
+ {
+ return target;
+ }
+ }
+ }
+ catch (e)
+ {
+ }
+ }
+ }
+ return null;
+}
+
+Prado.Focus.isVisible = function(element)
+{
+ var current = element;
+ while((typeof(current) != "undefined") && (current != null))
+ {
+ if(current.disabled || (typeof(current.style) != "undefined" && ((typeof(current.style.display) != "undefined" && current.style.display == "none") || (typeof(current.style.visibility) != "undefined" && current.style.visibility == "hidden") )))
+ {
+ return false;
+ }
+ if(typeof(current.parentNode) != "undefined" && current.parentNode != null && current.parentNode != current && current.parentNode.tagName.toLowerCase() != "body")
+ {
+ current = current.parentNode;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ return true;
+}
+*/
+
+
+Prado.PostBack = function(event,options)
+{
+ var form = $(options['FormID']);
+ var canSubmit = true;
+
+ if(options['CausesValidation'] && typeof(Prado.Validation) != "undefined")
+ {
+ if(!Prado.Validation.validate(options['FormID'], options['ValidationGroup'], $(options['ID'])))
+ return Event.stop(event);
+ }
+
+ if(options['PostBackUrl'] && options['PostBackUrl'].length > 0)
+ form.action = options['PostBackUrl'];
+
+ if(options['TrackFocus'])
+ {
+ var lastFocus = $('PRADO_LASTFOCUS');
+ if(lastFocus)
+ {
+ var active = document.activeElement; //where did this come from
+ if(active)
+ lastFocus.value = active.id;
+ else
+ lastFocus.value = options['EventTarget'];
+ }
+ }
+
+ $('PRADO_POSTBACK_TARGET').value = options['EventTarget'];
+ $('PRADO_POSTBACK_PARAMETER').value = options['EventParameter'];
+ /**
+ * Since google toolbar prevents browser default action,
+ * we will always disable default client-side browser action
+ */
+ /*if(options['StopEvent']) */
+ Event.stop(event);
+ Event.fireEvent(form,"submit");
+}
+
+/*
+
+Prado.doPostBack = function(formID, eventTarget, eventParameter, performValidation, validationGroup, actionUrl, trackFocus, clientSubmit)
+{
+ if (typeof(performValidation) == 'undefined')
+ {
+ var performValidation = false;
+ var validationGroup = '';
+ var actionUrl = null;
+ var trackFocus = false;
+ var clientSubmit = true;
+ }
+ var theForm = document.getElementById ? document.getElementById(formID) : document.forms[formID];
+ var canSubmit = true;
+ if (performValidation)
+ {
+ //canSubmit = Prado.Validation.validate(validationGroup);
+ * Prado.Validation.ActiveTarget = theForm;
+ Prado.Validation.CurrentTargetGroup = null;
+ Prado.Validation.IsGroupValidation = false;
+ canSubmit = Prado.Validation.IsValid(theForm);
+ Logger.debug(canSubmit);*
+ canSubmit = Prado.Validation.IsValid(theForm);
+ }
+ if (canSubmit)
+ {
+ if (actionUrl != null && (actionUrl.length > 0))
+ {
+ theForm.action = actionUrl;
+ }
+ if (trackFocus)
+ {
+ var lastFocus = theForm.elements['PRADO_LASTFOCUS'];
+ if ((typeof(lastFocus) != 'undefined') && (lastFocus != null))
+ {
+ var active = document.activeElement;
+ if (typeof(active) == 'undefined')
+ {
+ lastFocus.value = eventTarget;
+ }
+ else
+ {
+ if ((active != null) && (typeof(active.id) != 'undefined'))
+ {
+ if (active.id.length > 0)
+ {
+ lastFocus.value = active.id;
+ }
+ else if (typeof(active.name) != 'undefined')
+ {
+ lastFocus.value = active.name;
+ }
+ }
+ }
+ }
+ }
+ if (!clientSubmit)
+ {
+ canSubmit = false;
+ }
+ }
+ if (canSubmit && (!theForm.onsubmit || theForm.onsubmit()))
+ {
+ theForm.PRADO_POSTBACK_TARGET.value = eventTarget;
+ theForm.PRADO_POSTBACK_PARAMETER.value = eventParameter;
+ theForm.submit();
+ }
+}
+*/
+
+Prado.Element =
+{
+ /**
+ * Set the value of a particular element.
+ * @param string element id
+ * @param string new element value.
+ */
+ setValue : function(element, value)
+ {
+ var el = $(element);
+ if(el && typeof(el.value) != "undefined")
+ el.value = value;
+ },
+
+ select : function(element, method, value, total)
+ {
+ var el = $(element);
+ var selection = Prado.Element.Selection;
+ if(typeof(selection[method]) == "function")
+ {
+ control = selection.isSelectable(el) ? [el] : selection.getListElements(element,total);
+ selection[method](control, value);
+ }
+ },
+
+ click : function(element)
+ {
+ var el = $(element);
+ if(el)
+ Event.fireEvent(el,'click');
+ },
+
+ setAttribute : function(element, attribute, value)
+ {
+ var el = $(element);
+ if((attribute == "disabled" || attribute == "multiple") && value==false)
+ el.removeAttribute(attribute);
+ else if(attribute.match(/^on/i)) //event methods
+ {
+ try
+ {
+ eval("(func = function(event){"+value+"})");
+ el[attribute] = func;
+ }
+ catch(e)
+ {
+ throw "Error in evaluating '"+value+"' for attribute "+attribute+" for element "+element.id;
+ }
+ }
+ else
+ el.setAttribute(attribute, value);
+ },
+
+ setOptions : function(element, options)
+ {
+ var el = $(element);
+ if(el && el.tagName.toLowerCase() == "select")
+ {
+ el.options.length = options.length;
+ for(var i = 0; i<options.length; i++)
+ el.options[i] = new Option(options[i][0],options[i][1]);
+ }
+ },
+
+ /**
+ * A delayed focus on a particular element
+ * @param {element} element to apply focus()
+ */
+ focus : function(element)
+ {
+ var obj = $(element);
+ if(typeof(obj) != "undefined" && typeof(obj.focus) != "undefined")
+ setTimeout(function(){ obj.focus(); }, 100);
+ return false;
+ },
+
+ replace : function(element, method, content, boundary)
+ {
+ if(boundary)
+ {
+ result = Prado.Element.extractContent(this.transport.responseText, boundary);
+ if(result != null)
+ content = result;
+ }
+ if(typeof(element) == "string")
+ {
+ if($(element))
+ method.toFunction().apply(this,[element,""+content]);
+ }
+ else
+ {
+ method.toFunction().apply(this,[""+content]);
+ }
+ },
+
+ extractContent : function(text, boundary)
+ {
+ f = RegExp('(<!--'+boundary+'-->)([\\s\\S\\w\\W]*)(<!--//'+boundary+'-->)',"m");
+ result = text.match(f);
+ if(result && result.length >= 2)
+ return result[2];
+ else
+ return null;
+ },
+
+ evaluateScript : function(content)
+ {
+ content.evalScripts();
+ }
+}
+
+Prado.Element.Selection =
+{
+ isSelectable : function(el)
+ {
+ if(el && el.type)
+ {
+ switch(el.type.toLowerCase())
+ {
+ case 'checkbox':
+ case 'radio':
+ case 'select':
+ case 'select-multiple':
+ case 'select-one':
+ return true;
+ }
+ }
+ return false;
+ },
+
+ inputValue : function(el, value)
+ {
+ switch(el.type.toLowerCase())
+ {
+ case 'checkbox':
+ case 'radio':
+ return el.checked = value;
+ }
+ },
+
+ selectValue : function(elements, value)
+ {
+ elements.each(function(el)
+ {
+ $A(el.options).each(function(option)
+ {
+ if(typeof(value) == "boolean")
+ options.selected = value;
+ else if(option.value == value)
+ option.selected = true;
+ });
+ })
+ },
+
+ selectValues : function(elements, values)
+ {
+ selection = this;
+ values.each(function(value)
+ {
+ selection.selectValue(elements,value);
+ })
+ },
+
+ selectIndex : function(elements, index)
+ {
+ elements.each(function(el)
+ {
+ if(el.type.toLowerCase() == 'select-one')
+ el.selectedIndex = index;
+ else
+ {
+ for(var i = 0; i<el.length; i++)
+ {
+ if(i == index)
+ el.options[i].selected = true;
+ }
+ }
+ })
+ },
+
+ selectAll : function(elements)
+ {
+ elements.each(function(el)
+ {
+ if(el.type.toLowerCase() != 'select-one')
+ {
+ $A(el.options).each(function(option)
+ {
+ option.selected = true;
+ })
+ }
+ })
+ },
+
+ selectInvert : function(elements)
+ {
+ elements.each(function(el)
+ {
+ if(el.type.toLowerCase() != 'select-one')
+ {
+ $A(el.options).each(function(option)
+ {
+ option.selected = !options.selected;
+ })
+ }
+ })
+ },
+
+ selectIndices : function(elements, indices)
+ {
+ selection = this;
+ indices.each(function(index)
+ {
+ selection.selectIndex(elements,index);
+ })
+ },
+
+ selectClear : function(elements)
+ {
+ elements.each(function(el)
+ {
+ el.selectedIndex = -1;
+ })
+ },
+
+ getListElements : function(element, total)
+ {
+ elements = new Array();
+ for(i = 0; i < total; i++)
+ {
+ el = $(element+"_c"+i);
+ if(el)
+ elements.push(el);
+ }
+ return elements;
+ },
+
+ checkValue : function(elements, value)
+ {
+ elements.each(function(el)
+ {
+ if(typeof(value) == "boolean")
+ el.checked = value;
+ else if(el.value == value)
+ el.checked = true;
+ });
+ },
+
+ checkValues : function(elements, values)
+ {
+ selection = this;
+ values.each(function(value)
+ {
+ selection.checkValue(elements, value);
+ })
+ },
+
+ checkIndex : function(elements, index)
+ {
+ for(var i = 0; i<elements.length; i++)
+ {
+ if(i == index)
+ elements[i].checked = true;
+ }
+ },
+
+ checkIndices : function(elements, indices)
+ {
+ selection = this;
+ indices.each(function(index)
+ {
+ selection.checkIndex(elements, index);
+ })
+ },
+
+ checkClear : function(elements)
+ {
+ elements.each(function(el)
+ {
+ el.checked = false;
+ });
+ },
+
+ checkAll : function(elements)
+ {
+ elements.each(function(el)
+ {
+ el.checked = true;
+ })
+ },
+
+ checkInvert : function(elements)
+ {
+ elements.each(function(el)
+ {
+ el.checked != el.checked;
+ })
+ }
+};
+
+
+Prado.Element.Insert =
+{
+ append: function(element, content)
+ {
+ new Insertion.Bottom(element, content);
+ },
+
+ prepend: function(element, content)
+ {
+ new Insertion.Top(element, content);
+ },
+
+ after: function(element, content)
+ {
+ new Insertion.After(element, content);
+ },
+
+ before: function(element, content)
+ {
+ new Insertion.Before(element, content);
+ }
+}
+
+Prado.WebUI = Class.create();
+
+Prado.WebUI.PostBackControl = Class.create();
+
+Prado.WebUI.PostBackControl.prototype =
+{
+ initialize : function(options)
+ {
+ this._elementOnClick = null, //capture the element's onclick function
+
+ this.element = $(options.ID);
+ if(this.onInit)
+ 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.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;
+ }
+ if(doPostBack)
+ this.onPostBack(event,options);
+ if(typeof(onclicked) == "boolean" && !onclicked)
+ Event.stop(event);
+ },
+
+ onPostBack : function(event, options)
+ {
+ Prado.PostBack(event,options);
+ }
+};
+
+Prado.WebUI.TButton = Class.extend(Prado.WebUI.PostBackControl);
+Prado.WebUI.TLinkButton = Class.extend(Prado.WebUI.PostBackControl);
+Prado.WebUI.TCheckBox = Class.extend(Prado.WebUI.PostBackControl);
+Prado.WebUI.TBulletedList = Class.extend(Prado.WebUI.PostBackControl);
+Prado.WebUI.TImageMap = Class.extend(Prado.WebUI.PostBackControl);
+
+/**
+ * TImageButton client-side behaviour. With validation, Firefox needs
+ * to capture the x,y point of the clicked image in hidden form fields.
+ */
+Prado.WebUI.TImageButton = Class.extend(Prado.WebUI.PostBackControl);
+Object.extend(Prado.WebUI.TImageButton.prototype,
+{
+ /**
+ * Only add the hidden inputs once.
+ */
+ hasXYInput : false,
+
+ /**
+ * Override parent onPostBack function, tried to add hidden forms
+ * inputs to capture x,y clicked point.
+ */
+ onPostBack : function(event, options)
+ {
+ if(!this.hasXYInput)
+ {
+ this.addXYInput(event,options);
+ this.hasXYInput = true;
+ }
+ Prado.PostBack(event, options);
+ },
+
+ /**
+ * Add hidden inputs to capture the x,y point clicked on the image.
+ * @param event DOM click event.
+ * @param array image button options.
+ */
+ addXYInput : function(event,options)
+ {
+ imagePos = Position.cumulativeOffset(this.element);
+ clickedPos = [event.clientX, event.clientY];
+ x = clickedPos[0]-imagePos[0]+1;
+ y = clickedPos[1]-imagePos[1]+1;
+ x = x < 0 ? 0 : x;
+ y = y < 0 ? 0 : y;
+ id = options['EventTarget'];
+ x_input = $(id+"_x");
+ y_input = $(id+"_y");
+ if(x_input)
+ {
+ x_input.value = x;
+ }
+ else
+ {
+ x_input = INPUT({type:'hidden',name:id+'_x','id':id+'_x',value:x});
+ this.element.parentNode.appendChild(x_input);
+ }
+ if(y_input)
+ {
+ y_input.value = y;
+ }
+ else
+ {
+ y_input = INPUT({type:'hidden',name:id+'_y','id':id+'_y',value:y});
+ this.element.parentNode.appendChild(y_input);
+ }
+ }
+});
+
+
+/**
+ * Radio button, only initialize if not already checked.
+ */
+Prado.WebUI.TRadioButton = Class.extend(Prado.WebUI.PostBackControl);
+Prado.WebUI.TRadioButton.prototype.onRadioButtonInitialize = Prado.WebUI.TRadioButton.prototype.initialize;
+Object.extend(Prado.WebUI.TRadioButton.prototype,
+{
+ initialize : function(options)
+ {
+ this.element = $(options['ID']);
+ if(!this.element.checked)
+ this.onRadioButtonInitialize(options);
+ }
+});
+
+
+Prado.WebUI.TTextBox = Class.extend(Prado.WebUI.PostBackControl,
+{
+ onInit : function(options)
+ {
+ this.options=options;
+ if(options['TextMode'] != 'MultiLine')
+ Event.observe(this.element, "keydown", this.handleReturnKey.bind(this));
+ if(this.options['AutoPostBack']==true)
+ Event.observe(this.element, "change", Prado.PostBack.bindEvent(this,options));
+ },
+
+ handleReturnKey : function(e)
+ {
+ if(Event.keyCode(e) == Event.KEY_RETURN)
+ {
+ var target = Event.element(e);
+ if(target)
+ {
+ if(this.options['AutoPostBack']==true)
+ {
+ Event.fireEvent(target, "change");
+ Event.stop(e);
+ }
+ else
+ {
+ if(this.options['CausesValidation'] && typeof(Prado.Validation) != "undefined")
+ {
+ if(!Prado.Validation.validate(this.options['FormID'], this.options['ValidationGroup'], $(this.options['ID'])))
+ return Event.stop(e);
+ }
+ }
+ }
+ }
+ }
+});
+
+Prado.WebUI.TListControl = Class.extend(Prado.WebUI.PostBackControl,
+{
+ onInit : function(options)
+ {
+ Event.observe(this.element, "change", Prado.PostBack.bindEvent(this,options));
+ }
+});
+
+Prado.WebUI.TListBox = Class.extend(Prado.WebUI.TListControl);
+Prado.WebUI.TDropDownList = Class.extend(Prado.WebUI.TListControl);
+
+Prado.WebUI.DefaultButton = Class.create();
+Prado.WebUI.DefaultButton.prototype =
+{
+ initialize : function(options)
+ {
+ this.options = options;
+ this._event = this.triggerEvent.bindEvent(this);
+ Event.observe(options['Panel'], 'keydown', this._event);
+ },
+
+ triggerEvent : function(ev, target)
+ {
+ var enterPressed = Event.keyCode(ev) == Event.KEY_RETURN;
+ var isTextArea = Event.element(ev).tagName.toLowerCase() == "textarea";
+ if(enterPressed && !isTextArea)
+ {
+ var defaultButton = $(this.options['Target']);
+ if(defaultButton)
+ {
+ this.triggered = true;
+ $('PRADO_POSTBACK_TARGET').value = this.options.EventTarget;
+ Event.fireEvent(defaultButton, this.options['Event']);
+ Event.stop(ev);
+ }
+ }
+ }
+};
+
+Prado.WebUI.TTextHighlighter=Class.create();
+Prado.WebUI.TTextHighlighter.prototype=
+{
+ initialize:function(id)
+ {
+ if(!window.clipboardData) return;
+ var options =
+ {
+ href : 'javascript:;/'+'/copy code to clipboard',
+ onclick : 'Prado.WebUI.TTextHighlighter.copy(this)',
+ onmouseover : 'Prado.WebUI.TTextHighlighter.hover(this)',
+ onmouseout : 'Prado.WebUI.TTextHighlighter.out(this)'
+ }
+ var div = DIV({className:'copycode'}, A(options, 'Copy Code'));
+ document.write(DIV(null,div).innerHTML);
+ }
+};
+
+Object.extend(Prado.WebUI.TTextHighlighter,
+{
+ copy : function(obj)
+ {
+ var parent = obj.parentNode.parentNode.parentNode;
+ var text = '';
+ for(var i = 0; i < parent.childNodes.length; i++)
+ {
+ var node = parent.childNodes[i];
+ if(node.innerText)
+ text += node.innerText == 'Copy Code' ? '' : node.innerText;
+ else
+ text += node.nodeValue;
+ }
+ if(text.length > 0)
+ window.clipboardData.setData("Text", text);
+ },
+
+ hover : function(obj)
+ {
+ obj.parentNode.className = "copycode copycode_hover";
+ },
+
+ out : function(obj)
+ {
+ obj.parentNode.className = "copycode";
+ }
+});
+
+
+Prado.WebUI.TCheckBoxList = Base.extend(
+{
+ constructor : function(options)
+ {
+ for(var i = 0; i<options.ItemCount; i++)
+ {
+ var checkBoxOptions = Object.extend(
+ {
+ ID : options.ListID+"_c"+i,
+ EventTarget : options.ListName+"$c"+i
+ }, options);
+ new Prado.WebUI.TCheckBox(checkBoxOptions);
+ }
+ }
+});
+
+Prado.WebUI.TRadioButtonList = Base.extend(
+{
+ constructor : function(options)
+ {
+ for(var i = 0; i<options.ItemCount; i++)
+ {
+ var radioButtonOptions = Object.extend(
+ {
+ ID : options.ListID+"_c"+i,
+ EventTarget : options.ListName+"$c"+i
+ }, options);
+ new Prado.WebUI.TRadioButton(radioButtonOptions);
+ }
+ }
+});
+
diff --git a/demos/currency-converter/assets/2bffd82d/validator.js b/demos/currency-converter/assets/2bffd82d/validator.js
new file mode 100644
index 00000000..f3a37d0f
--- /dev/null
+++ b/demos/currency-converter/assets/2bffd82d/validator.js
@@ -0,0 +1,1362 @@
+
+/**
+ * Prado client-side javascript validation fascade.
+ *
+ * There are 4 basic classes, Validation, ValidationManager, ValidationSummary
+ * and TBaseValidator, that interact together to perform validation.
+ * The <tt>Prado.Validation</tt> class co-ordinates together the
+ * validation scheme and is responsible for maintaining references
+ * to ValidationManagers.
+ *
+ * The ValidationManager class is responsible for maintaining refereneces
+ * to individual validators, validation summaries and their associated
+ * groupings.
+ *
+ * The ValidationSummary take cares of display the validator error messages
+ * as html output or an alert output.
+ *
+ * The TBaseValidator is the base class for all validators and contains
+ * methods to interact with the actual inputs, data type conversion.
+ *
+ * An instance of ValidationManager must be instantiated first for a
+ * particular form before instantiating validators and summaries.
+ *
+ * Usage example: adding a required field to a text box input with
+ * ID "input1" in a form with ID "form1".
+ * <code>
+ * <script type="text/javascript" src="../prado.js"></script>
+ * <script type="text/javascript" src="../validator.js"></script>
+ * <form id="form1" action="...">
+ * <div>
+ * <input type="text" id="input1" />
+ * <span id="validator1" style="display:none; color:red">*</span>
+ * <input type="submit text="submit" />
+ * <script type="text/javascript">
+ * new Prado.ValidationManager({FormID : 'form1'});
+ * var options =
+ * {
+ * ID : 'validator1',
+ * FormID : 'form1',
+ * ErrorMessage : '*',
+ * ControlToValidate : 'input1'
+ * }
+ * new Prado.WebUI.TRequiredFieldValidator(options);
+ * new Prado.WebUI.TValidationSummary({ID:'summary1',FormID:'form1'});
+ *
+ * //watch the form onsubmit event, check validators, stop if not valid.
+ * Event.observe("form1", "submit" function(ev)
+ * {
+ * if(Prado.WebUI.Validation.isValid("form1") == false)
+ * Event.stop(ev);
+ * });
+ * </script>
+ * </div>
+ * </form>
+ * </code>
+ */
+Prado.Validation = Class.create();
+
+/**
+ * A global validation manager.
+ * To validate the inputs of a particular form, call
+ * <code>Prado.Validation.validate(formID, groupID)</code>
+ * where <tt>formID</tt> is the HTML form ID, and the optional
+ * <tt>groupID</tt> if present will only validate the validators
+ * in a particular group.
+ */
+Object.extend(Prado.Validation,
+{
+ managers : {},
+
+ /**
+ * Validate the validators (those that <strong>DO NOT</strong>
+ * belong to a particular group) the form specified by the
+ * <tt>formID</tt> parameter. If <tt>groupID</tt> is specified
+ * then only validators belonging to that group will be validated.
+ * @param string ID of the form to validate
+ * @param string ID of the group to validate.
+ * @param HTMLElement element that calls for validation
+ */
+ validate : function(formID, groupID, invoker)
+ {
+ if(this.managers[formID])
+ {
+ return this.managers[formID].validate(groupID, invoker);
+ }
+ else
+ {
+ throw new Error("Form '"+form+"' is not registered with Prado.Validation");
+ }
+ },
+
+ /**
+ * @return string first form ID.
+ */
+ getForm : function()
+ {
+ var keys = $H(this.managers).keys();
+ return keys[0];
+ },
+
+ /**
+ * Check if the validators are valid for a particular form (and group).
+ * The validators states will not be changed.
+ * The <tt>validate</tt> function should be called first.
+ * @param string ID of the form to validate
+ * @param string ID of the group to validate.
+ */
+ isValid : function(formID, groupID)
+ {
+ if(this.managers[formID])
+ return this.managers[formID].isValid(groupID);
+ return true;
+ },
+
+ /**
+ * Add a new validator to a particular form.
+ * @param string the form that the validator belongs.
+ * @param object a validator
+ * @return object the manager
+ */
+ addValidator : function(formID, validator)
+ {
+ if(this.managers[formID])
+ this.managers[formID].addValidator(validator);
+ else
+ throw new Error("A validation manager for form '"+formID+"' needs to be created first.");
+ return this.managers[formID];
+ },
+
+ /**
+ * Add a new validation summary.
+ * @param string the form that the validation summary belongs.
+ * @param object a validation summary
+ * @return object manager
+ */
+ addSummary : function(formID, validator)
+ {
+ if(this.managers[formID])
+ this.managers[formID].addSummary(validator);
+ else
+ throw new Error("A validation manager for form '"+formID+"' needs to be created first.");
+ return this.managers[formID];
+ },
+
+ setErrorMessage : function(validatorID, message)
+ {
+ $H(Prado.Validation.managers).each(function(manager)
+ {
+ manager[1].validators.each(function(validator)
+ {
+ if(validator.options.ID == validatorID)
+ {
+ validator.options.ErrorMessage = message;
+ $(validatorID).innerHTML = message;
+ }
+ });
+ });
+ }
+});
+
+Prado.ValidationManager = Class.create();
+/**
+ * Validation manager instances. Manages validators for a particular
+ * HTML form. The manager contains references to all the validators
+ * summaries, and their groupings for a particular form.
+ * Generally, <tt>Prado.Validation</tt> methods should be called rather
+ * than calling directly the ValidationManager.
+ */
+Prado.ValidationManager.prototype =
+{
+ /**
+ * <code>
+ * options['FormID']* The ID of HTML form to manage.
+ * </code>
+ */
+ initialize : function(options)
+ {
+ this.validators = []; // list of validators
+ this.summaries = []; // validation summaries
+ this.groups = []; // validation groups
+ this.options = {};
+
+ this.options = options;
+ Prado.Validation.managers[options.FormID] = this;
+ },
+
+ /**
+ * Validate the validators managed by this validation manager.
+ * @param string only validate validators belonging to a group (optional)
+ * @param HTMLElement element that calls for validation
+ * @return boolean true if all validators are valid, false otherwise.
+ */
+ validate : function(group, invoker)
+ {
+ if(group)
+ return this._validateGroup(group, invoker);
+ else
+ return this._validateNonGroup(invoker);
+ },
+
+ /**
+ * Validate a particular group of validators.
+ * @param string ID of the form
+ * @param HTMLElement element that calls for validation
+ * @return boolean false if group is not valid, true otherwise.
+ */
+ _validateGroup: function(groupID, invoker)
+ {
+ var valid = true;
+ if(this.groups.include(groupID))
+ {
+ this.validators.each(function(validator)
+ {
+ if(validator.group == groupID)
+ valid = valid & validator.validate(invoker);
+ else
+ validator.hide();
+ });
+ }
+ this.updateSummary(groupID, true);
+ return valid;
+ },
+
+ /**
+ * Validate validators that doesn't belong to any group.
+ * @return boolean false if not valid, true otherwise.
+ * @param HTMLElement element that calls for validation
+ */
+ _validateNonGroup : function(invoker)
+ {
+ var valid = true;
+ this.validators.each(function(validator)
+ {
+ if(!validator.group)
+ valid = valid & validator.validate(invoker);
+ else
+ validator.hide();
+ });
+ this.updateSummary(null, true);
+ return valid;
+ },
+
+ /**
+ * Gets the state of all the validators, true if they are all valid.
+ * @return boolean true if the validators are valid.
+ */
+ isValid : function(group)
+ {
+ if(group)
+ return this._isValidGroup(group);
+ else
+ return this._isValidNonGroup();
+ },
+
+ /**
+ * @return boolean true if all validators not belonging to a group are valid.
+ */
+ _isValidNonGroup : function()
+ {
+ var valid = true;
+ this.validators.each(function(validator)
+ {
+ if(!validator.group)
+ valid = valid & validator.isValid;
+ });
+ return valid;
+ },
+
+ /**
+ * @return boolean true if all validators belonging to the group are valid.
+ */
+ _isValidGroup : function(groupID)
+ {
+ var valid = true;
+ if(this.groups.include(groupID))
+ {
+ this.validators.each(function(validator)
+ {
+ if(validator.group == groupID)
+ valid = valid & validator.isValid;
+ });
+ }
+ return valid;
+ },
+
+ /**
+ * Add a validator to this manager.
+ * @param Prado.WebUI.TBaseValidator a new validator
+ */
+ addValidator : function(validator)
+ {
+ this.validators.push(validator);
+ if(validator.group && !this.groups.include(validator.group))
+ this.groups.push(validator.group);
+ },
+
+ /**
+ * Add a validation summary.
+ * @param Prado.WebUI.TValidationSummary validation summary.
+ */
+ addSummary : function(summary)
+ {
+ this.summaries.push(summary);
+ },
+
+ /**
+ * Gets all validators that belong to a group or that the validator
+ * group is null and the validator validation was false.
+ * @return array list of validators with error.
+ */
+ getValidatorsWithError : function(group)
+ {
+ var validators = this.validators.findAll(function(validator)
+ {
+ var notValid = !validator.isValid;
+ var inGroup = group && validator.group == group;
+ var noGroup = validator.group == null;
+ return notValid && (inGroup || noGroup);
+ });
+ return validators;
+ },
+
+ /**
+ * Update the summary of a particular group.
+ * @param string validation group to update.
+ */
+ updateSummary : function(group, refresh)
+ {
+ var validators = this.getValidatorsWithError(group);
+ this.summaries.each(function(summary)
+ {
+ var inGroup = group && summary.group == group;
+ var noGroup = !group && !summary.group;
+ if(inGroup || noGroup)
+ summary.updateSummary(validators, refresh);
+ else
+ summary.hideSummary(true);
+ });
+ }
+};
+
+/**
+ * TValidationSummary displays a summary of validation errors inline on a Web page,
+ * in a message box, or both. By default, a validation summary will collect
+ * <tt>ErrorMessage</tt> of all failed validators on the page. If
+ * <tt>ValidationGroup</tt> is not empty, only those validators who belong
+ * to the group will show their error messages in the summary.
+ *
+ * The summary can be displayed as a list, as a bulleted list, or as a single
+ * paragraph based on the <tt>DisplayMode</tt> option.
+ * The messages shown can be prefixed with <tt>HeaderText</tt>.
+ *
+ * The summary can be displayed on the Web page and in a message box by setting
+ * the <tt>ShowSummary</tt> and <tt>ShowMessageBox</tt>
+ * options, respectively.
+ */
+Prado.WebUI.TValidationSummary = Class.create();
+Prado.WebUI.TValidationSummary.prototype =
+{
+ /**
+ * <code>
+ * options['ID']* Validation summary ID, i.e., an HTML element ID
+ * options['FormID']* HTML form that this summary belongs.
+ * options['ShowMessageBox'] True to show the summary in an alert box.
+ * options['ShowSummary'] True to show the inline summary.
+ * options['HeaderText'] Summary header text
+ * options['DisplayMode'] Summary display style, 'BulletList', 'List', 'SingleParagraph'
+ * options['Refresh'] True to update the summary upon validator state change.
+ * options['ValidationGroup'] Validation summary group
+ * options['Display'] Display mode, 'None', 'Fixed', 'Dynamic'.
+ * options['ScrollToSummary'] True to scroll to the validation summary upon refresh.
+ * </code>
+ */
+ initialize : function(options)
+ {
+ this.options = options;
+ this.group = options.ValidationGroup;
+ this.messages = $(options.ID);
+ this.visible = this.messages.style.visibility != "hidden"
+ this.visible = this.visible && this.messages.style.display != "none";
+ Prado.Validation.addSummary(options.FormID, this);
+ },
+
+ /**
+ * Update the validation summary to show the error message from
+ * validators that failed validation.
+ * @param array list of validators that failed validation.
+ * @param boolean update the summary;
+ */
+ updateSummary : function(validators, update)
+ {
+ if(validators.length <= 0)
+ {
+ if(update || this.options.Refresh != false)
+ {
+ return this.hideSummary(validators);
+ }
+ return;
+ }
+
+ var refresh = update || this.visible == false || this.options.Refresh != false;
+
+ if(this.options.ShowSummary != false && refresh)
+ {
+ this.updateHTMLMessages(this.getMessages(validators));
+ this.showSummary(validators);
+ }
+
+ if(this.options.ScrollToSummary != false && refresh)
+ window.scrollTo(this.messages.offsetLeft-20, this.messages.offsetTop-20);
+
+ if(this.options.ShowMessageBox == true && refresh)
+ {
+ this.alertMessages(this.getMessages(validators));
+ this.visible = true;
+ }
+ },
+
+ /**
+ * Display the validator error messages as inline HTML.
+ */
+ updateHTMLMessages : function(messages)
+ {
+ while(this.messages.childNodes.length > 0)
+ this.messages.removeChild(this.messages.lastChild);
+ new Insertion.Bottom(this.messages, this.formatSummary(messages));
+ },
+
+ /**
+ * Display the validator error messages as an alert box.
+ */
+ alertMessages : function(messages)
+ {
+ var text = this.formatMessageBox(messages);
+ setTimeout(function(){ alert(text); },20);
+ },
+
+ /**
+ * @return array list of validator error messages.
+ */
+ getMessages : function(validators)
+ {
+ var messages = [];
+ validators.each(function(validator)
+ {
+ var message = validator.getErrorMessage();
+ if(typeof(message) == 'string' && message.length > 0)
+ messages.push(message);
+ })
+ return messages;
+ },
+
+ /**
+ * Hides the validation summary.
+ */
+ hideSummary : function(validators)
+ { if(typeof(this.options.OnHideSummary) == "function")
+ {
+ this.messages.style.visibility="visible";
+ this.options.OnHideSummary(this,validators)
+ }
+ else
+ {
+ this.messages.style.visibility="hidden";
+ if(this.options.Display == "None" || this.options.Display == "Dynamic")
+ this.messages.hide();
+ }
+ this.visible = false;
+ },
+
+ /**
+ * Shows the validation summary.
+ */
+ showSummary : function(validators)
+ {
+ this.messages.style.visibility="visible";
+ if(typeof(this.options.OnShowSummary) == "function")
+ this.options.OnShowSummary(this,validators);
+ else
+ this.messages.show();
+ this.visible = true;
+ },
+
+ /**
+ * Return the format parameters for the summary.
+ * @param string format type, "List", "SingleParagraph" or "BulletList"
+ * @type array formatting parameters
+ */
+ formats : function(type)
+ {
+ switch(type)
+ {
+ case "List":
+ return { header : "<br />", first : "", pre : "", post : "<br />", last : ""};
+ case "SingleParagraph":
+ return { header : " ", first : "", pre : "", post : " ", last : "<br />"};
+ case "BulletList":
+ default:
+ return { header : "", first : "<ul>", pre : "<li>", post : "</li>", last : "</ul>"};
+ }
+ },
+
+ /**
+ * Format the message summary.
+ * @param array list of error messages.
+ * @type string formatted message
+ */
+ formatSummary : function(messages)
+ {
+ var format = this.formats(this.options.DisplayMode);
+ var output = this.options.HeaderText ? this.options.HeaderText + format.header : "";
+ output += format.first;
+ messages.each(function(message)
+ {
+ output += message.length > 0 ? format.pre + message + format.post : "";
+ });
+// for(var i = 0; i < messages.length; i++)
+ // output += (messages[i].length>0) ? format.pre + messages[i] + format.post : "";
+ output += format.last;
+ return output;
+ },
+ /**
+ * Format the message alert box.
+ * @param array a list of error messages.
+ * @type string format message for alert.
+ */
+ formatMessageBox : function(messages)
+ {
+ var output = this.options.HeaderText ? this.options.HeaderText + "\n" : "";
+ for(var i = 0; i < messages.length; i++)
+ {
+ switch(this.options.DisplayMode)
+ {
+ case "List":
+ output += messages[i] + "\n";
+ break;
+ case "BulletList":
+ default:
+ output += " - " + messages[i] + "\n";
+ break;
+ case "SingleParagraph":
+ output += messages[i] + " ";
+ break;
+ }
+ }
+ return output;
+ }
+};
+
+/**
+ * TBaseValidator serves as the base class for validator controls.
+ *
+ * Validation is performed when a postback control, such as a TButton,
+ * a TLinkButton or a TTextBox (under AutoPostBack mode) is submitting
+ * the page and its <tt>CausesValidation</tt> option is true.
+ * The input control to be validated is specified by <tt>ControlToValidate</tt>
+ * option.
+ */
+Prado.WebUI.TBaseValidator = Class.create();
+Prado.WebUI.TBaseValidator.prototype =
+{
+ /**
+ * <code>
+ * options['ID']* Validator ID, e.g. span with message
+ * options['FormID']* HTML form that the validator belongs
+ * options['ControlToValidate']*HTML form input to validate
+ * options['Display'] Display mode, 'None', 'Fixed', 'Dynamic'
+ * options['ErrorMessage'] Validation error message
+ * options['FocusOnError'] True to focus on validation error
+ * options['FocusElementID'] Element to focus on error
+ * options['ValidationGroup'] Validation group
+ * options['ControlCssClass'] Css class to use on the input upon error
+ * options['OnValidate'] Function to call immediately after validation
+ * options['OnValidationSuccess'] Function to call upon after successful validation
+ * options['OnValidationError'] Function to call upon after error in validation.
+ * options['ObserveChanges'] True to observe changes in input
+ * </code>
+ */
+ initialize : function(options)
+ {
+ /* options.OnValidate = options.OnValidate || Prototype.emptyFunction;
+ options.OnSuccess = options.OnSuccess || Prototype.emptyFunction;
+ options.OnError = options.OnError || Prototype.emptyFunction;
+ */
+
+ this.enabled = true;
+ this.visible = false;
+ this.isValid = true;
+ this._isObserving = {};
+ this.group = null;
+ this.requestDispatched = false;
+
+ this.options = options;
+ this.control = $(options.ControlToValidate);
+ this.message = $(options.ID);
+ this.group = options.ValidationGroup;
+
+ this.manager = Prado.Validation.addValidator(options.FormID, this);
+ },
+
+ /**
+ * @return string validation error message.
+ */
+ getErrorMessage : function()
+ {
+ return this.options.ErrorMessage;
+ },
+
+ /**
+ * Update the validator span, input CSS class, and focus particular
+ * element. Updating the validator control will set the validator
+ * <tt>visible</tt> property to true.
+ */
+ updateControl: function(focus)
+ {
+ this.refreshControlAndMessage();
+
+ if(this.options.FocusOnError && !this.isValid )
+ Prado.Element.focus(this.options.FocusElementID);
+
+ this.visible = true;
+ },
+
+ refreshControlAndMessage : function()
+ {
+ this.visible = true;
+ if(this.message)
+ {
+ if(this.options.Display == "Dynamic")
+ this.isValid ? this.message.hide() : this.message.show();
+ this.message.style.visibility = this.isValid ? "hidden" : "visible";
+ }
+ if(this.control)
+ this.updateControlCssClass(this.control, this.isValid);
+ },
+
+ /**
+ * Add a css class to the input control if validator is invalid,
+ * removes the css class if valid.
+ * @param object html control element
+ * @param boolean true to remove the css class, false to add.
+ */
+ updateControlCssClass : function(control, valid)
+ {
+ var CssClass = this.options.ControlCssClass;
+ if(typeof(CssClass) == "string" && CssClass.length > 0)
+ {
+ if(valid)
+ control.removeClassName(CssClass);
+ else
+ control.addClassName(CssClass);
+ }
+ },
+
+ /**
+ * Hides the validator messages and remove any validation changes.
+ */
+ hide : function()
+ {
+ this.isValid = true;
+ this.updateControl();
+ this.visible = false;
+ },
+
+ /**
+ * Calls evaluateIsValid() function to set the value of isValid property.
+ * Triggers onValidate event and onSuccess or onError event.
+ * @param HTMLElement element that calls for validation
+ * @return boolean true if valid.
+ */
+ validate : function(invoker)
+ {
+ //try to find the control.
+ if(!this.control)
+ this.control = $(this.options.ControlToValidate);
+
+ if(!this.control)
+ {
+ this.isValid = true;
+ return this.isValid;
+ }
+
+ if(typeof(this.options.OnValidate) == "function")
+ {
+ if(this.requestDispatched == false)
+ this.options.OnValidate(this, invoker);
+ }
+
+ if(this.enabled)
+ this.isValid = this.evaluateIsValid();
+ else
+ this.isValid = true;
+
+ if(this.isValid)
+ {
+ if(typeof(this.options.OnValidationSuccess) == "function")
+ {
+ if(this.requestDispatched == false)
+ {
+ this.refreshControlAndMessage();
+ this.options.OnValidationSuccess(this, invoker);
+ }
+ }
+ else
+ this.updateControl();
+ }
+ else
+ {
+ if(typeof(this.options.OnValidationError) == "function")
+ {
+ if(this.requestDispatched == false)
+ {
+ this.refreshControlAndMessage();
+ this.options.OnValidationError(this, invoker)
+ }
+ }
+ else
+ this.updateControl();
+ }
+
+ this.observeChanges(this.control);
+
+ return this.isValid;
+ },
+
+ /**
+ * Observe changes to the control input, re-validate upon change. If
+ * the validator is not visible, no updates are propagated.
+ * @param HTMLElement element that calls for validation
+ */
+ observeChanges : function(control)
+ {
+ if(!control) return;
+
+ var canObserveChanges = this.options.ObserveChanges != false;
+ var currentlyObserving = this._isObserving[control.id+this.options.ID];
+
+ if(canObserveChanges && !currentlyObserving)
+ {
+ var validator = this;
+
+ Event.observe(control, 'change', function()
+ {
+ if(validator.visible)
+ {
+ validator.validate();
+ validator.manager.updateSummary(validator.group);
+ }
+ });
+ this._isObserving[control.id+this.options.ID] = true;
+ }
+ },
+
+ /**
+ * @return string trims the string value, empty string if value is not string.
+ */
+ trim : function(value)
+ {
+ return typeof(value) == "string" ? value.trim() : "";
+ },
+
+ /**
+ * Convert the value to a specific data type.
+ * @param {string} the data type, "Integer", "Double", "Date" or "String"
+ * @param {string} the value to convert.
+ * @type {mixed|null} the converted data value.
+ */
+ convert : function(dataType, value)
+ {
+ if(typeof(value) == "undefined")
+ value = this.getValidationValue();
+ var string = new String(value);
+ switch(dataType)
+ {
+ case "Integer":
+ return string.toInteger();
+ case "Double" :
+ case "Float" :
+ return string.toDouble(this.options.DecimalChar);
+ case "Date":
+ if(typeof(value) != "string")
+ return value;
+ else
+ {
+ var value = string.toDate(this.options.DateFormat);
+ if(value && typeof(value.getTime) == "function")
+ return value.getTime();
+ else
+ return null;
+ }
+ case "String":
+ return string.toString();
+ }
+ return value;
+ },
+
+ /**
+ * The ControlType property comes from TBaseValidator::getClientControlClass()
+ * Be sure to update the TBaseValidator::$_clientClass if new cases are added.
+ * @return mixed control value to validate
+ */
+ getValidationValue : function(control)
+ {
+ if(!control)
+ control = this.control
+ switch(this.options.ControlType)
+ {
+ case 'TDatePicker':
+ if(control.type == "text")
+ {
+ value = this.trim($F(control));
+
+ if(this.options.DateFormat)
+ {
+ date = value.toDate(this.options.DateFormat);
+ return date == null ? value : date;
+ }
+ else
+ return value;
+ }
+ else
+ {
+ this.observeDatePickerChanges();
+
+ return Prado.WebUI.TDatePicker.getDropDownDate(control).getTime();
+ }
+ case 'THtmlArea':
+ if(typeof tinyMCE != "undefined")
+ tinyMCE.triggerSave();
+ return this.trim($F(control));
+ case 'TRadioButton':
+ if(this.options.GroupName)
+ return this.getRadioButtonGroupValue();
+ default:
+ if(this.isListControlType())
+ return this.getFirstSelectedListValue();
+ else
+ return this.trim($F(control));
+ }
+ },
+
+ getRadioButtonGroupValue : function()
+ {
+ name = this.control.name;
+ value = "";
+ $A(document.getElementsByName(name)).each(function(el)
+ {
+ if(el.checked)
+ value = el.value;
+ });
+ return value;
+ },
+
+ /**
+ * Observe changes in the drop down list date picker, IE only.
+ */
+ observeDatePickerChanges : function()
+ {
+ if(Prado.Browser().ie)
+ {
+ var DatePicker = Prado.WebUI.TDatePicker;
+ this.observeChanges(DatePicker.getDayListControl(this.control));
+ this.observeChanges(DatePicker.getMonthListControl(this.control));
+ this.observeChanges(DatePicker.getYearListControl(this.control));
+ }
+ },
+
+ /**
+ * Gets numeber selections and their values.
+ * @return object returns selected values in <tt>values</tt> property
+ * and number of selections in <tt>checks</tt> property.
+ */
+ getSelectedValuesAndChecks : function(elements, initialValue)
+ {
+ var checked = 0;
+ var values = [];
+ var isSelected = this.isCheckBoxType(elements[0]) ? 'checked' : 'selected';
+ elements.each(function(element)
+ {
+ if(element[isSelected] && element.value != initialValue)
+ {
+ checked++;
+ values.push(element.value);
+ }
+ });
+ return {'checks' : checked, 'values' : values};
+ },
+
+ /**
+ * Gets an array of the list control item input elements, for TCheckBoxList
+ * checkbox inputs are returned, for TListBox HTML option elements are returned.
+ * @return array list control option elements.
+ */
+ getListElements : function()
+ {
+ switch(this.options.ControlType)
+ {
+ case 'TCheckBoxList': case 'TRadioButtonList':
+ var elements = [];
+ for(var i = 0; i < this.options.TotalItems; i++)
+ {
+ var element = $(this.options.ControlToValidate+"_c"+i);
+ if(this.isCheckBoxType(element))
+ elements.push(element);
+ }
+ return elements;
+ case 'TListBox':
+ var elements = [];
+ var element = $(this.options.ControlToValidate);
+ if(element && (type = element.type.toLowerCase()))
+ {
+ if(type == "select-one" || type == "select-multiple")
+ elements = $A(element.options);
+ }
+ return elements;
+ default:
+ return [];
+ }
+ },
+
+ /**
+ * @return boolean true if element is of checkbox or radio type.
+ */
+ isCheckBoxType : function(element)
+ {
+ if(element && element.type)
+ {
+ var type = element.type.toLowerCase();
+ return type == "checkbox" || type == "radio";
+ }
+ return false;
+ },
+
+ /**
+ * @return boolean true if control to validate is of some of the TListControl type.
+ */
+ isListControlType : function()
+ {
+ var list = ['TCheckBoxList', 'TRadioButtonList', 'TListBox'];
+ return list.include(this.options.ControlType);
+ },
+
+ /**
+ * @return string gets the first selected list value, initial value if none found.
+ */
+ getFirstSelectedListValue : function()
+ {
+ var initial = "";
+ if(typeof(this.options.InitialValue) != "undefined")
+ initial = this.options.InitialValue;
+ var elements = this.getListElements();
+ var selection = this.getSelectedValuesAndChecks(elements, initial);
+ return selection.values.length > 0 ? selection.values[0] : initial;
+ }
+}
+
+
+/**
+ * TRequiredFieldValidator makes the associated input control a required field.
+ * The input control fails validation if its value does not change from
+ * the <tt>InitialValue<tt> option upon losing focus.
+ * <code>
+ * options['InitialValue'] Validation fails if control input equals initial value.
+ * </code>
+ */
+Prado.WebUI.TRequiredFieldValidator = Class.extend(Prado.WebUI.TBaseValidator,
+{
+ /**
+ * @return boolean true if the input value is not empty nor equal to the initial value.
+ */
+ evaluateIsValid : function()
+ {
+ var inputType = this.control.getAttribute("type");
+ if(inputType == 'file')
+ {
+ return true;
+ }
+ else
+ {
+ var a = this.getValidationValue();
+ var b = this.trim(this.options.InitialValue);
+ return(a != b);
+ }
+ }
+});
+
+
+/**
+ * TCompareValidator compares the value entered by the user into an input
+ * control with the value entered into another input control or a constant value.
+ * To compare the associated input control with another input control,
+ * set the <tt>ControlToCompare</tt> option to the ID path
+ * of the control to compare with. To compare the associated input control with
+ * a constant value, specify the constant value to compare with by setting the
+ * <tt>ValueToCompare</tt> option.
+ *
+ * The <tt>DataType</tt> property is used to specify the data type
+ * of both comparison values. Both values are automatically converted to this data
+ * type before the comparison operation is performed. The following value types are supported:
+ * - <b>Integer</b> A 32-bit signed integer data type.
+ * - <b>Float</b> A double-precision floating point number data type.
+ * - <b>Date</b> A date data type. The format can be by the <tt>DateFormat</tt> option.
+ * - <b>String</b> A string data type.
+ *
+ * Use the <tt>Operator</tt> property to specify the type of comparison
+ * to perform. Valid operators include Equal, NotEqual, GreaterThan, GreaterThanEqual,
+ * LessThan and LessThanEqual.
+ * <code>
+ * options['ControlToCompare']
+ * options['ValueToCompare']
+ * options['Operator']
+ * options['Type']
+ * options['DateFormat']
+ * </code>
+ */
+Prado.WebUI.TCompareValidator = Class.extend(Prado.WebUI.TBaseValidator,
+{
+ //_observingComparee : false,
+
+ /**
+ * Compares the input to another input or a given value.
+ */
+ evaluateIsValid : function()
+ {
+ var value = this.getValidationValue();
+ if (value.length <= 0)
+ return true;
+
+ var comparee = $(this.options.ControlToCompare);
+
+ if(comparee)
+ var compareTo = this.getValidationValue(comparee);
+ else
+ var compareTo = this.options.ValueToCompare || "";
+
+ var isValid = this.compare(value, compareTo);
+
+ if(comparee)
+ {
+ this.updateControlCssClass(comparee, isValid);
+ this.observeChanges(comparee);
+ }
+ return isValid;
+ },
+
+ /**
+ * Compares two values, their values are casted to type defined
+ * by <tt>DataType</tt> option. False is returned if the first
+ * operand converts to null. Returns true if the second operand
+ * converts to null. The comparision is done based on the
+ * <tt>Operator</tt> option.
+ */
+ compare : function(operand1, operand2)
+ {
+ var op1, op2;
+ if((op1 = this.convert(this.options.DataType, operand1)) == null)
+ return false;
+ if ((op2 = this.convert(this.options.DataType, operand2)) == null)
+ return true;
+ switch (this.options.Operator)
+ {
+ case "NotEqual":
+ return (op1 != op2);
+ case "GreaterThan":
+ return (op1 > op2);
+ case "GreaterThanEqual":
+ return (op1 >= op2);
+ case "LessThan":
+ return (op1 < op2);
+ case "LessThanEqual":
+ return (op1 <= op2);
+ default:
+ return (op1 == op2);
+ }
+ }
+});
+
+/**
+ * TCustomValidator performs user-defined client-side validation on an
+ * input component.
+ *
+ * To create a client-side validation function, add the client-side
+ * validation javascript function to the page template.
+ * The function should have the following signature:
+ * <code>
+ * <script type="text/javascript"><!--
+ * function ValidationFunctionName(sender, parameter)
+ * {
+ * // if(parameter == ...)
+ * // return true;
+ * // else
+ * // return false;
+ * }
+ * -->
+ * </script>
+ * </code>
+ * Use the <tt>ClientValidationFunction</tt> option
+ * to specify the name of the client-side validation script function associated
+ * with the TCustomValidator.
+ * <code>
+ * options['ClientValidationFunction'] custom validation function.
+ * </code>
+ */
+Prado.WebUI.TCustomValidator = Class.extend(Prado.WebUI.TBaseValidator,
+{
+ /**
+ * Calls custom validation function.
+ */
+ evaluateIsValid : function()
+ {
+ var value = this.getValidationValue();
+ var clientFunction = this.options.ClientValidationFunction;
+ if(typeof(clientFunction) == "string" && clientFunction.length > 0)
+ {
+ validate = clientFunction.toFunction();
+ return validate(this, value);
+ }
+ return true;
+ }
+});
+
+/**
+ * Uses callback request to perform validation.
+ */
+Prado.WebUI.TActiveCustomValidator = Class.extend(Prado.WebUI.TBaseValidator,
+{
+ validatingValue : null,
+
+ /**
+ * Calls custom validation function.
+ */
+ evaluateIsValid : function()
+ {
+ value = this.getValidationValue();
+ if(!this.requestDispatched && value != this.validatingValue)
+ {
+ this.validatingValue = value;
+ request = new Prado.CallbackRequest(this.options.EventTarget, this.options);
+ request.setCallbackParameter(value);
+ request.setCausesValidation(false);
+ request.options.onSuccess = this.callbackOnSuccess.bind(this);
+ request.options.onFailure = this.callbackOnFailure.bind(this);
+ request.dispatch();
+ this.requestDispatched = true;
+ return false;
+ }
+ return this.isValid;
+ },
+
+ callbackOnSuccess : function(request, data)
+ {
+ this.isValid = data;
+ this.requestDispatched = false;
+ if(typeof(this.options.onSuccess) == "function")
+ this.options.onSuccess(request,data);
+ Prado.Validation.validate(this.options.FormID, this.group,null);
+ },
+
+ callbackOnFailure : function(request, data)
+ {
+ this.requestDispatched = false;
+ if(typeof(this.options.onFailure) == "function")
+ this.options.onFailure(request,data);
+ }
+});
+
+/**
+ * TRangeValidator tests whether an input value is within a specified range.
+ *
+ * TRangeValidator uses three key properties to perform its validation.
+ * The <tt>MinValue</tt> and <tt>MaxValue</tt> options specify the minimum
+ * and maximum values of the valid range. The <tt>DataType</tt> option is
+ * used to specify the data type of the value and the minimum and maximum range values.
+ * These values are converted to this data type before the validation
+ * operation is performed. The following value types are supported:
+ * - <b>Integer</b> A 32-bit signed integer data type.
+ * - <b>Float</b> A double-precision floating point number data type.
+ * - <b>Date</b> A date data type. The date format can be specified by
+ * setting <tt>DateFormat</tt> option, which must be recognizable
+ * by <tt>Date.SimpleParse</tt> javascript function.
+ * - <b>String</b> A string data type.
+ * <code>
+ * options['MinValue'] Minimum range value
+ * options['MaxValue'] Maximum range value
+ * options['DataType'] Value data type
+ * options['DateFormat'] Date format for date data type.
+ * </code>
+ */
+Prado.WebUI.TRangeValidator = Class.extend(Prado.WebUI.TBaseValidator,
+{
+ /**
+ * Compares the input value with a minimum and/or maximum value.
+ * @return boolean true if the value is empty, returns false if conversion fails.
+ */
+ evaluateIsValid : function()
+ {
+ var value = this.getValidationValue();
+ if(value.length <= 0)
+ return true;
+ if(typeof(this.options.DataType) == "undefined")
+ this.options.DataType = "String";
+
+ if(this.options.DataType != "StringLength")
+ {
+ var min = this.convert(this.options.DataType, this.options.MinValue || null);
+ var max = this.convert(this.options.DataType, this.options.MaxValue || null);
+ value = this.convert(this.options.DataType, value);
+ }
+ else
+ {
+ var min = this.options.MinValue || 0;
+ var max = this.options.MaxValue || Number.POSITIVE_INFINITY;
+ value = value.length;
+ }
+
+ if(value == null)
+ return false;
+
+ var valid = true;
+
+ if(min != null)
+ valid = valid && value >= min;
+ if(max != null)
+ valid = valid && value <= max;
+ return valid;
+ }
+});
+
+/**
+ * TRegularExpressionValidator validates whether the value of an associated
+ * input component matches the pattern specified by a regular expression.
+ * <code>
+ * options['ValidationExpression'] regular expression to match against.
+ * </code>
+ */
+Prado.WebUI.TRegularExpressionValidator = Class.extend(Prado.WebUI.TBaseValidator,
+{
+ /**
+ * Compare the control input against a regular expression.
+ */
+ evaluateIsValid : function()
+ {
+ var value = this.getValidationValue();
+ if (value.length <= 0)
+ return true;
+
+ var rx = new RegExp(this.options.ValidationExpression);
+ var matches = rx.exec(value);
+ return (matches != null && value == matches[0]);
+ }
+});
+
+/**
+ * TEmailAddressValidator validates whether the value of an associated
+ * input component is a valid email address.
+ */
+Prado.WebUI.TEmailAddressValidator = Prado.WebUI.TRegularExpressionValidator;
+
+
+/**
+ * TListControlValidator checks the number of selection and their values
+ * for a TListControl that allows multiple selections.
+ */
+Prado.WebUI.TListControlValidator = Class.extend(Prado.WebUI.TBaseValidator,
+{
+ /**
+ * @return true if the number of selections and/or their values
+ * match the requirements.
+ */
+ evaluateIsValid : function()
+ {
+ var elements = this.getListElements();
+ if(elements && elements.length <= 0)
+ return true;
+
+ this.observeListElements(elements);
+
+ var selection = this.getSelectedValuesAndChecks(elements);
+ return this.isValidList(selection.checks, selection.values);
+ },
+
+ /**
+ * Observe list elements for IE browsers of changes
+ */
+ observeListElements : function(elements)
+ {
+ if(Prado.Browser().ie && this.isCheckBoxType(elements[0]))
+ {
+ var validator = this;
+ elements.each(function(element)
+ {
+ validator.observeChanges(element);
+ });
+ }
+ },
+
+ /**
+ * Determine if the number of checked and the checked values
+ * satisfy the required number of checks and/or the checked values
+ * equal to the required values.
+ * @return boolean true if checked values and number of checks are satisfied.
+ */
+ isValidList : function(checked, values)
+ {
+ var exists = true;
+
+ //check the required values
+ var required = this.getRequiredValues();
+ if(required.length > 0)
+ {
+ if(values.length < required.length)
+ return false;
+ required.each(function(requiredValue)
+ {
+ exists = exists && values.include(requiredValue);
+ });
+ }
+
+ var min = typeof(this.options.Min) == "undefined" ?
+ Number.NEGATIVE_INFINITY : this.options.Min;
+ var max = typeof(this.options.Max) == "undefined" ?
+ Number.POSITIVE_INFINITY : this.options.Max;
+ return exists && checked >= min && checked <= max;
+ },
+
+ /**
+ * @return array list of required options that must be selected.
+ */
+ getRequiredValues : function()
+ {
+ var required = [];
+ if(this.options.Required && this.options.Required.length > 0)
+ required = this.options.Required.split(/,\s*/);
+ return required;
+ }
+});
+
+
+/**
+ * TDataTypeValidator verifies if the input data is of the type specified
+ * by <tt>DataType</tt> option.
+ * The following data types are supported:
+ * - <b>Integer</b> A 32-bit signed integer data type.
+ * - <b>Float</b> A double-precision floating point number data type.
+ * - <b>Date</b> A date data type.
+ * - <b>String</b> A string data type.
+ * For <b>Date</b> type, the option <tt>DateFormat</tt>
+ * will be used to determine how to parse the date string.
+ */
+Prado.WebUI.TDataTypeValidator = Class.extend(Prado.WebUI.TBaseValidator,
+{
+ evaluateIsValid : function()
+ {
+ value = this.getValidationValue();
+ if(value.length <= 0)
+ return true;
+ return this.convert(this.options.DataType, value) != null;
+ }
+});
+
+
+
diff --git a/demos/currency-converter/index.php b/demos/currency-converter/index.php
new file mode 100644
index 00000000..9471651b
--- /dev/null
+++ b/demos/currency-converter/index.php
@@ -0,0 +1,22 @@
+<?php
+$frameworkPath='/Users/weizhuo/Sites/prado-trunk/framework/prado.php';
+
+/** The directory checks may be removed if performance is required **/
+$basePath=dirname(__FILE__);
+$assetsPath=$basePath."/assets";
+$runtimePath=$basePath."/protected/runtime";
+
+if(!is_file($frameworkPath))
+ die("Unable to find prado framework path $frameworkPath.");
+if(!is_writable($assetsPath))
+ die("Please make sure that the directory $assetsPath is writable by Web server process.");
+if(!is_writable($runtimePath))
+ die("Please make sure that the directory $runtimePath is writable by Web server process.");
+
+
+require_once($frameworkPath);
+
+$application=new TApplication;
+$application->run();
+
+?> \ No newline at end of file
diff --git a/demos/currency-converter/protected/.htaccess b/demos/currency-converter/protected/.htaccess
new file mode 100644
index 00000000..3418e55a
--- /dev/null
+++ b/demos/currency-converter/protected/.htaccess
@@ -0,0 +1 @@
+deny from all \ No newline at end of file
diff --git a/demos/currency-converter/protected/pages/Home.page b/demos/currency-converter/protected/pages/Home.page
new file mode 100644
index 00000000..47b91344
--- /dev/null
+++ b/demos/currency-converter/protected/pages/Home.page
@@ -0,0 +1,51 @@
+<%@ Theme="Basic" %>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
+<com:THead Title="Currency Converter" />
+<body>
+<com:TForm>
+ <fieldset>
+ <legend>Currency Converter</legend>
+ <div class="rate-field">
+ <com:TLabel ForControl="currencyRate" Text="Exchange Rate per $1:" />
+ <com:TTextBox ID="currencyRate" />
+ <com:TRequiredFieldValidator
+ ControlToValidate="currencyRate"
+ Display="Dynamic"
+ ErrorMessage="Please enter a currency rate." />
+ <com:TCompareValidator
+ ControlToValidate="currencyRate"
+ DataType="Float"
+ ValueToCompare="0"
+ Operator="GreaterThan"
+ Display="Dynamic"
+ ErrorMessage="Please enter a positive currency rate." />
+ </div>
+ <div class="dollar-field">
+ <com:TLabel ForControl="dollars" Text="Dollars to Convert:" />
+ <com:TTextBox ID="dollars" />
+ <com:TRequiredFieldValidator
+ ControlToValidate="dollars"
+ Display="Dynamic"
+ ErrorMessage="Please enter the amount you wish to calculate." />
+ <com:TDataTypeValidator
+ ControlToValidate="dollars"
+ DataType="Float"
+ Display="Dynamic"
+ ErrorMessage="Please enter a number." />
+ </div>
+ <div class="total-field">
+ <span class="total-label">Amount in Other Currency:</span>
+ <com:TActiveLabel ID="total" CssClass="result" />
+ </div>
+ <div class="convert-button">
+ <com:TActiveButton Text="Convert" OnClick="convert_clicked" >
+ <prop:ClientSide.OnLoading>
+ $('<%= $this->total->ClientID %>').innerHTML = "calculating..."
+ </prop:ClientSide.OnLoading>
+ </com:TActiveButton>
+ </div>
+ </fieldset>
+</com:TForm>
+</body>
+</html> \ No newline at end of file
diff --git a/demos/currency-converter/protected/pages/Home.php b/demos/currency-converter/protected/pages/Home.php
new file mode 100644
index 00000000..dcf9339e
--- /dev/null
+++ b/demos/currency-converter/protected/pages/Home.php
@@ -0,0 +1,15 @@
+<?php
+Prado::using('System.Web.UI.ActiveControls.*');
+class Home extends TPage
+{
+ public function convert_clicked($sender, $param)
+ {
+ if($this->Page->IsValid)
+ {
+ $rate = floatval($this->currencyRate->Text);
+ $dollars = floatval($this->dollars->Text);
+ $this->total->Text = $rate * $dollars;
+ }
+ }
+}
+?> \ No newline at end of file
diff --git a/demos/currency-converter/protected/runtime/global.cache b/demos/currency-converter/protected/runtime/global.cache
new file mode 100644
index 00000000..81ed6269
--- /dev/null
+++ b/demos/currency-converter/protected/runtime/global.cache
@@ -0,0 +1 @@
+a:1:{s:35:"prado:securitymanager:validationkey";s:38:"44099869715766197794850216601377650299";} \ No newline at end of file
diff --git a/demos/currency-converter/themes/Basic/common.css b/demos/currency-converter/themes/Basic/common.css
new file mode 100644
index 00000000..8d80a2ba
--- /dev/null
+++ b/demos/currency-converter/themes/Basic/common.css
@@ -0,0 +1,52 @@
+body
+{
+ font-family: arial;
+ font-size: 0.85em;
+}
+
+fieldset
+{
+ background-color: #ffc;
+ border: 1px solid #ccc;
+}
+
+legend
+{
+ font-weight: bold;
+ font-size: 1.2em;
+ color: #333;
+}
+
+fieldset div
+{
+ margin: 0.5em;
+}
+
+label, span.total-label
+{
+ width: 15em;
+ float: left;
+ text-align: right;
+ margin-right: 0.35em;
+}
+
+.total-field
+{
+ margin-bottom: 2.5em;
+}
+
+.total-field .result
+{
+ float: left;
+ height: 1em;
+ width: 10em;
+}
+
+.convert-button
+{
+ display: block;
+ border-top: 1px solid #ccc;
+ margin-top: 1.7em;
+ padding-left: 15.5em;
+ padding-top: 0.5em;
+} \ No newline at end of file