From 6b685cc4e496c5eabbb3e6415fb7fdf0474bf0f1 Mon Sep 17 00:00:00 2001 From: wei <> Date: Sat, 23 Dec 2006 00:22:48 +0000 Subject: add currency-converter tutorial --- demos/currency-converter/assets/2bffd82d/ajax.js | 2836 ++++++++++++++++ .../assets/2bffd82d/clientscripts.php | 61 + .../currency-converter/assets/2bffd82d/effects.js | 968 ++++++ demos/currency-converter/assets/2bffd82d/logger.js | 755 +++++ demos/currency-converter/assets/2bffd82d/prado.js | 3542 ++++++++++++++++++++ .../assets/2bffd82d/validator.js | 1362 ++++++++ demos/currency-converter/index.php | 22 + demos/currency-converter/protected/.htaccess | 1 + demos/currency-converter/protected/pages/Home.page | 51 + demos/currency-converter/protected/pages/Home.php | 15 + .../protected/runtime/global.cache | 1 + demos/currency-converter/themes/Basic/common.css | 52 + 12 files changed, 9666 insertions(+) create mode 100644 demos/currency-converter/assets/2bffd82d/ajax.js create mode 100644 demos/currency-converter/assets/2bffd82d/clientscripts.php create mode 100644 demos/currency-converter/assets/2bffd82d/effects.js create mode 100644 demos/currency-converter/assets/2bffd82d/logger.js create mode 100644 demos/currency-converter/assets/2bffd82d/prado.js create mode 100644 demos/currency-converter/assets/2bffd82d/validator.js create mode 100644 demos/currency-converter/index.php create mode 100644 demos/currency-converter/protected/.htaccess create mode 100644 demos/currency-converter/protected/pages/Home.page create mode 100644 demos/currency-converter/protected/pages/Home.php create mode 100644 demos/currency-converter/protected/runtime/global.cache create mode 100644 demos/currency-converter/themes/Basic/common.css (limited to 'demos/currency-converter') 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[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. + * + * request = new Prado.CallbackRequest(UniqueID, callback); + * request.dispatch(); + * + */ +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, + ''); + 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 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("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + 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("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + 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 ""; + } + }, 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(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /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 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 halfMax ? base+1 : base; + for(var i = 0; i halfMax ? base+1 : base; + var hasHalf = remainder >= halfMin && remainder <= halfMax; + for(var i = 0; 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 \ 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.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 += '' + this.buttonsContainerElement.innerHTML += '' + if(!Prado.Inspector.disabled) + this.buttonsContainerElement.innerHTML += '' + + + //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 = '' + 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 += "

    " + message + "
    " + + 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,">"); + 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-1){ + this.d.getElementById(spanID).innerHTML="[-]"; + } else { + this.d.getElementById(spanID).innerHTML="[+]"; + } + }, + + buildInspectionLevel : function() + { + var display = this.displaying; + var list = display.split("."); + var links = ["[object Window]"]; + 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] = ""+list[i]+""; + } + return links.join("."); + }, + + buildTree : function() { + mHTML = "
    Inspecting "+this.buildInspectionLevel()+"
    "; + mHTML +="
      "; + this.types.sort(); + var so_objIndex=0; + for(i=0;i[+]" + this.types[i] + " (" + this.nameList[this.types[i]].length + ")
        "; + this.hidden["ul"+i]=0; + for(e=0;e= 0 && /^[a-zA-Z_]/.test(prop)) + { + if(this.displaying.indexOf("[object ") < 0) + more = " more"; + else if(this.displaying.indexOf("[object Window]") >= 0) + more = " more"; + } + mHTML+="
      • [+]" + prop + "
        • " + value + more + "
        "; + this.hidden["mul"+so_objIndex]=0; + so_objIndex++; + } + mHTML+="
      "; + } + mHTML+="
    "; + 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 = "[esc] to close
    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 + * + * 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: '(?:)((\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 base and optional definition. + * @param function a base function to copy from. + * @param array additional definition + * @param function return a new function with definition from both + * base and definition. + */ +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.lengthInternationalization + * is not supported + * @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. + * The currency input format is very strict, null will be returned if + * the pattern does not match. + * @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 '#'; + } +} + +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 '#'; + } +} + +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 = '' + this.content + '
    '; + 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. + * + * Event.OnLoad(function(){ alert("Page Loaded!"); }); + * + * + * @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 type on a DOM + * element. 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 + ">"; + } 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 + ">"; + } 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 + * + * var browser = Prado.Browser(); + * alert(browser.ie); //should ouput true if IE, false otherwise + * + * @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 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)([\\s\\S\\w\\W]*)()',"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