From ccf76e430b7703db028966a845a966f50956f490 Mon Sep 17 00:00:00 2001 From: xue <> Date: Mon, 5 Dec 2005 01:00:16 +0000 Subject: --- framework/Web/Javascripts/base/ajax.js | 355 +++ framework/Web/Javascripts/base/controls.js | 179 ++ framework/Web/Javascripts/base/datepicker.js | 928 ++++++++ framework/Web/Javascripts/base/focus.js | 99 + framework/Web/Javascripts/base/json.js | 340 +++ framework/Web/Javascripts/base/postback.js | 72 + framework/Web/Javascripts/base/prado.js | 3 + framework/Web/Javascripts/base/scroll.js | 0 framework/Web/Javascripts/base/validation.js | 675 ++++++ framework/Web/Javascripts/base/validators.js | 228 ++ framework/Web/Javascripts/build.php | 144 ++ framework/Web/Javascripts/custom_rhino.jar | Bin 0 -> 731885 bytes framework/Web/Javascripts/effects/controls.js | 711 ++++++ framework/Web/Javascripts/effects/dragdrop.js | 516 +++++ framework/Web/Javascripts/effects/effects.js | 811 +++++++ framework/Web/Javascripts/effects/rico.js | 2289 ++++++++++++++++++++ framework/Web/Javascripts/effects/slider.js | 258 +++ framework/Web/Javascripts/effects/util.js | 548 +++++ framework/Web/Javascripts/extended/array.js | 465 ++++ framework/Web/Javascripts/extended/base.js | 24 + framework/Web/Javascripts/extended/dom.js | 6 + framework/Web/Javascripts/extended/event.js | 24 + framework/Web/Javascripts/extended/functional.js | 171 ++ framework/Web/Javascripts/extended/string.js | 37 + framework/Web/Javascripts/extended/util.js | 90 + framework/Web/Javascripts/extra/behaviour.js | 83 + .../Web/Javascripts/extra/getElementsBySelector.js | 176 ++ framework/Web/Javascripts/extra/logger.js | 659 ++++++ framework/Web/Javascripts/extra/tp_template.js | 315 +++ framework/Web/Javascripts/prototype/ajax.js | 268 +++ framework/Web/Javascripts/prototype/array.js | 64 + framework/Web/Javascripts/prototype/base.js | 121 ++ framework/Web/Javascripts/prototype/compat.js | 27 + framework/Web/Javascripts/prototype/dom.js | 305 +++ framework/Web/Javascripts/prototype/enumerable.js | 183 ++ framework/Web/Javascripts/prototype/event.js | 107 + framework/Web/Javascripts/prototype/form.js | 299 +++ framework/Web/Javascripts/prototype/hash.js | 47 + framework/Web/Javascripts/prototype/position.js | 233 ++ framework/Web/Javascripts/prototype/prototype.js | 5 + framework/Web/Javascripts/prototype/range.js | 29 + framework/Web/Javascripts/prototype/string.js | 53 + .../Web/Javascripts/tests/CompareValidator.html | 95 + .../Web/Javascripts/tests/CustomValidator.html | 74 + framework/Web/Javascripts/tests/DatePicker.html | 99 + framework/Web/Javascripts/tests/Effects.html | 124 ++ framework/Web/Javascripts/tests/Form.disable.html | 37 + framework/Web/Javascripts/tests/Insertion.html | 47 + .../Web/Javascripts/tests/PradoTestSuite.html | 37 + .../Web/Javascripts/tests/RangeValidator.html | 65 + .../tests/RegularExpressionValidator.html | 72 + .../Javascripts/tests/RequiredFieldValidator.html | 95 + .../Javascripts/tests/RequiredListValidator.html | 110 + .../Web/Javascripts/tests/ValidationTests.html | 79 + .../Web/Javascripts/tests/calendar_system.css | 70 + framework/Web/Javascripts/tests/compression.html | 18 + framework/Web/Javascripts/tests/console.html | 30 + framework/Web/Javascripts/tests/fungii_logo.gif | Bin 0 -> 5473 bytes .../Javascripts/tests/getElementsByClassName.html | 28 + .../Javascripts/tests/getElementsBySelector.html | 55 + framework/Web/Javascripts/tests/index.html | 138 ++ framework/Web/Javascripts/tests/librarytest.html | 49 + .../test_scripts/TestRequiredFieldValidator.html | 85 + .../Javascripts/tests/test_scripts/TestSuite.html | 36 + 64 files changed, 13390 insertions(+) create mode 100644 framework/Web/Javascripts/base/ajax.js create mode 100644 framework/Web/Javascripts/base/controls.js create mode 100644 framework/Web/Javascripts/base/datepicker.js create mode 100644 framework/Web/Javascripts/base/focus.js create mode 100644 framework/Web/Javascripts/base/json.js create mode 100644 framework/Web/Javascripts/base/postback.js create mode 100644 framework/Web/Javascripts/base/prado.js create mode 100644 framework/Web/Javascripts/base/scroll.js create mode 100644 framework/Web/Javascripts/base/validation.js create mode 100644 framework/Web/Javascripts/base/validators.js create mode 100644 framework/Web/Javascripts/build.php create mode 100644 framework/Web/Javascripts/custom_rhino.jar create mode 100644 framework/Web/Javascripts/effects/controls.js create mode 100644 framework/Web/Javascripts/effects/dragdrop.js create mode 100644 framework/Web/Javascripts/effects/effects.js create mode 100644 framework/Web/Javascripts/effects/rico.js create mode 100644 framework/Web/Javascripts/effects/slider.js create mode 100644 framework/Web/Javascripts/effects/util.js create mode 100644 framework/Web/Javascripts/extended/array.js create mode 100644 framework/Web/Javascripts/extended/base.js create mode 100644 framework/Web/Javascripts/extended/dom.js create mode 100644 framework/Web/Javascripts/extended/event.js create mode 100644 framework/Web/Javascripts/extended/functional.js create mode 100644 framework/Web/Javascripts/extended/string.js create mode 100644 framework/Web/Javascripts/extended/util.js create mode 100644 framework/Web/Javascripts/extra/behaviour.js create mode 100644 framework/Web/Javascripts/extra/getElementsBySelector.js create mode 100644 framework/Web/Javascripts/extra/logger.js create mode 100644 framework/Web/Javascripts/extra/tp_template.js create mode 100644 framework/Web/Javascripts/prototype/ajax.js create mode 100644 framework/Web/Javascripts/prototype/array.js create mode 100644 framework/Web/Javascripts/prototype/base.js create mode 100644 framework/Web/Javascripts/prototype/compat.js create mode 100644 framework/Web/Javascripts/prototype/dom.js create mode 100644 framework/Web/Javascripts/prototype/enumerable.js create mode 100644 framework/Web/Javascripts/prototype/event.js create mode 100644 framework/Web/Javascripts/prototype/form.js create mode 100644 framework/Web/Javascripts/prototype/hash.js create mode 100644 framework/Web/Javascripts/prototype/position.js create mode 100644 framework/Web/Javascripts/prototype/prototype.js create mode 100644 framework/Web/Javascripts/prototype/range.js create mode 100644 framework/Web/Javascripts/prototype/string.js create mode 100644 framework/Web/Javascripts/tests/CompareValidator.html create mode 100644 framework/Web/Javascripts/tests/CustomValidator.html create mode 100644 framework/Web/Javascripts/tests/DatePicker.html create mode 100644 framework/Web/Javascripts/tests/Effects.html create mode 100644 framework/Web/Javascripts/tests/Form.disable.html create mode 100644 framework/Web/Javascripts/tests/Insertion.html create mode 100644 framework/Web/Javascripts/tests/PradoTestSuite.html create mode 100644 framework/Web/Javascripts/tests/RangeValidator.html create mode 100644 framework/Web/Javascripts/tests/RegularExpressionValidator.html create mode 100644 framework/Web/Javascripts/tests/RequiredFieldValidator.html create mode 100644 framework/Web/Javascripts/tests/RequiredListValidator.html create mode 100644 framework/Web/Javascripts/tests/ValidationTests.html create mode 100644 framework/Web/Javascripts/tests/calendar_system.css create mode 100644 framework/Web/Javascripts/tests/compression.html create mode 100644 framework/Web/Javascripts/tests/console.html create mode 100644 framework/Web/Javascripts/tests/fungii_logo.gif create mode 100644 framework/Web/Javascripts/tests/getElementsByClassName.html create mode 100644 framework/Web/Javascripts/tests/getElementsBySelector.html create mode 100644 framework/Web/Javascripts/tests/index.html create mode 100644 framework/Web/Javascripts/tests/librarytest.html create mode 100644 framework/Web/Javascripts/tests/test_scripts/TestRequiredFieldValidator.html create mode 100644 framework/Web/Javascripts/tests/test_scripts/TestSuite.html (limited to 'framework/Web/Javascripts') diff --git a/framework/Web/Javascripts/base/ajax.js b/framework/Web/Javascripts/base/ajax.js new file mode 100644 index 00000000..f1dd69c5 --- /dev/null +++ b/framework/Web/Javascripts/base/ajax.js @@ -0,0 +1,355 @@ +/** + * Prado AJAX service. The default service provider is JPSpan. + */ +Prado.AJAX = { Service : 'Prototype' }; + +/** + * Parse and execute javascript embedded in html. + */ +Prado.AJAX.EvalScript = function(output) +{ + + var match = new RegExp(Ajax.Updater.ScriptFragment, 'img'); + var scripts = output.match(match); + if (scripts) + { + match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); + setTimeout((function() + { + for (var i = 0; i < scripts.length; i++) + eval(scripts[i].match(match)[1]); + }).bind(this), 50); + } +} + + +/** + * AJAX service request using Prototype's AJAX request class. + */ +Prado.AJAX.Request = Class.create(); +Prado.AJAX.Request.prototype = Object.extend(Ajax.Request.prototype, +{ + /** + * Evaluate the respond JSON data, override parent implementing. + * If default eval fails, try parsing the JSON data (slower). + */ + evalJSON: function() + { + try + { + var json = this.transport.getResponseHeader('X-JSON'), object; + object = eval(json); + return object; + } + catch (e) + { + if(isString(json)) + { + return Prado.AJAX.JSON.parse(json); + } + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + if(event == 'Complete' && transport.status) + Ajax.Responders.dispatch('on' + transport.status, this, transport, json); + + if (event == 'Complete') + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + } + +}); + +Prado.AJAX.Error = function(e, code) +{ + e.name = 'Prado.AJAX.Error'; + e.code = code; + return e; +} + +/** + * Post data builder, serialize the data using JSON. + */ +Prado.AJAX.RequestBuilder = Class.create(); +Prado.AJAX.RequestBuilder.prototype = +{ + initialize : function() + { + this.body = ''; + this.data = []; + }, + encode : function(data) + { + return Prado.AJAX.JSON.stringify(data); + }, + build : function(data) + { + var sep = ''; + for ( var argName in data) + { + if(isFunction(data[argName])) continue; + try + { + this.body += sep + argName + '='; + this.body += encodeURIComponent(this.encode(data[argName])); + } catch (e) { + throw Prado.AJAX.Error(e, 1006); + } + sep = '&'; + } + }, + + getAll : function() + { + this.build(this.data); + return this.body; + } +} + + +Prado.AJAX.RemoteObject = function(){}; + +/** + * AJAX service request for Prado RemoteObjects + */ +Prado.AJAX.RemoteObject.Request = Class.create(); +Prado.AJAX.RemoteObject.Request.prototype = Object.extend(Prado.AJAX.Request.prototype, +{ + /** + * Initialize the RemoteObject Request, overrides parent + * implementation by delaying the request to invokeRemoteObject. + */ + initialize : function(options) + { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.post = new Prado.AJAX.RequestBuilder(); + }, + + /** + * Call the remote object, + * @param string the remote server url + * @param array additional arguments + */ + invokeRemoteObject : function(url, args) + { + this.initParameters(args); + this.options.postBody = this.post.getAll(); + this.request(url); + }, + + /** + * Set the additional arguments as post data with key '__parameters' + */ + initParameters : function(args) + { + this.post.data['__parameters'] = []; + for(var i = 0; i + * var TestObject1 = Class.create(); + * TestObject1.prototype = Object.extend(new Prado.AJAX.RemoteObject(), + * { + * initialize : function(handlers, options) + * { + * this.__serverurl = 'http://127.0.0.1/.....'; + * this.baseInitialize(handlers, options); + * } + * + * method1 : function() + * { + * return this.__call(this.__serverurl, 'method1', arguments); + * } + * }); + * + * And client usage, + * + * var test1 = new TestObject1(); //create new remote object + * test1.method1(); //call the method, no onComplete hook + * + * var onComplete = { method1 : function(result){ alert(result) } }; + * //create new remote object with onComplete callback + * var test2 = new TestObject1(onComplete); + * test2.method1(); //call it, on success, onComplete's method1 is called. + * + */ +Prado.AJAX.RemoteObject.prototype = +{ + baseInitialize : function(handlers, options) + { + this.__handlers = handlers || {}; + this.__service = new Prado.AJAX.RemoteObject.Request(options); + }, + + __call : function(url, method, args) + { + this.__service.options.onSuccess = this.__onSuccess.bind(this); + this.__callback = method; + return this.__service.invokeRemoteObject(url+"/"+method, args); + }, + + __onSuccess : function(transport, json) + { + if(this.__handlers[this.__callback]) + this.__handlers[this.__callback](json, transport.responseText); + } +}; + + + +/** + * Respond to Prado AJAX request exceptions. + */ +Prado.AJAX.Exception = +{ + /** + * Server returns 505 exception. Just log it. + */ + "on505" : function(request, transport, e) + { + var msg = 'HTTP '+transport.status+" with response"; + Logger.error(msg, transport.responseText); + Logger.exception(e); + }, + + onComplete : function(request, transport, e) + { + if(transport.status != 505) + { + var msg = 'HTTP '+transport.status+" with response : \n"; + msg += transport.responseText + "\n"; + msg += "Data : \n"+e; + Logger.warn(msg); + } + }, + + format : 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"; + } + return msg; + }, + + logException : function(e) + { + var msg = Prado.AJAX.Exception.format(e); + Logger.error("Server Error "+e.code, msg); + } +} + +//Add HTTP exception respones when logger is enabled. +Event.OnLoad(function() +{ + if(typeof Logger != "undefined") + { + Logger.exception = Prado.AJAX.Exception.logException; + Ajax.Responders.register(Prado.AJAX.Exception); + } +}); + +/** + * Prado Callback service that provides component intergration, + * viewstate (read only), and automatic form data serialization. + * Usage: new Prado.AJAX.Callback('MyPage.MyComponentID.raiseCallbackEvent', options) + * These classes should be called by the components developers. + * For inline callback service, use Prado.Callback(callbackID, params). + */ +Prado.AJAX.Callback = Class.create(); +Prado.AJAX.Callback.prototype = Object.extend(new Prado.AJAX.RemoteObject(), +{ + + /** + * Create and request a new Prado callback service. + * @param string the callback ID, must be of the form, ClassName.ComponentID.MethodName + * @param list options with list key onCallbackReturn, and more. + * + */ + initialize : function(ID, options) + { + if(!isString(ID)) + throw new Error('A Control ID must be specified'); + this.baseInitialize(this, options); + this.options = options || []; + this.__service.post.data['__ID'] = ID; + this.requestCallback(); + }, + + /** + * Get form data for components that implements IPostBackHandler. + */ + collectPostData : function() + { + var IDs = Prado.AJAX.Callback.IDs; + this.__service.post.data['__data'] = {}; + for(var i = 0; iClick me + * @param string callback ID + * @param array parameters to pass to the callback service + */ +Prado.Callback = function(ID, params, onSuccess) +{ + var options = + { + 'params' : [params] || [], + 'onSuccess' : onSuccess || Prototype.emptyFunction + }; + + new Prado.AJAX.Callback(ID, options); + return false; +} \ No newline at end of file diff --git a/framework/Web/Javascripts/base/controls.js b/framework/Web/Javascripts/base/controls.js new file mode 100644 index 00000000..6cb908ed --- /dev/null +++ b/framework/Web/Javascripts/base/controls.js @@ -0,0 +1,179 @@ +/** + * Auto complete textbox via AJAX. + */ +Prado.AutoCompleter = Class.create(); + + +/** + * Overrides parent implementation of updateElement by trimming the value. + */ +Prado.AutoCompleter.Base = function(){}; +Prado.AutoCompleter.Base.prototype = Object.extend(Autocompleter.Base.prototype, +{ + updateElement: function(selectedElement) + { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + + var 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).trim(); + } else { + this.element.value = value.trim(); + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + } +}); + +/** + * Based on the Prototype Autocompleter class. + * This client-side component should be instantiated from a Prado component. + * Usage: new Prado.AutoCompleter('textboxID', 'updateDivID', {callbackID : '...'}); + */ +Prado.AutoCompleter.prototype = Object.extend(new Autocompleter.Base(), +{ + /** + * This component is initialized by + * new Prado.AutoCompleter(...) + * @param string the ID of the textbox element to observe + * @param string the ID of the div to display the auto-complete options + * @param array a hash of options, e.g. auto-completion token separator. + */ + initialize : function(element, update, options) + { + this.baseInitialize(element, update, options); + }, + + /** + * The callback function, i.e., function called on successful AJAX return. + * Calls update choices in the Autocompleter. + * @param string new auto-complete options for display + */ + onUpdateReturn : function(result) + { + if(isString(result) && result.length > 0) + this.updateChoices(result); + }, + + /** + * Requesting new choices using Prado's client-side callback scheme. + */ + getUpdatedChoices : function() + { + Prado.Callback(this.element.id, this.getToken(), this.onUpdateReturn.bind(this)); + } +}); + +/** + * Prado TActivePanel client javascript. Usage + * + * Prado.ActivePanel.register("id", options); + * Prado.ActivePanel.update("id", "hello"); + * + */ +Prado.ActivePanel = +{ + callbacks : {}, + + register : function(id, options) + { + Prado.ActivePanel.callbacks[id] = options; + }, + + update : function(id, param) + { + var request = new Prado.ActivePanel.Request(id, + Prado.ActivePanel.callbacks[id]); + request.callback(param); + } +} + +/** + * Client-script for TActivePanel. Uses Callback to notify the server + * for updates, if update option is set, the innerHTML of the update ID + * is set to the returned output. + */ +Prado.ActivePanel.Request = Class.create(); +Prado.ActivePanel.Request.prototype = +{ + initialize : function(element, options) + { + this.element = element; + this.setOptions(options); + }, + + /** + * Set some options. + */ + setOptions : function(options) + { + this.options = + { + onSuccess : this.onSuccess.bind(this) + } + Object.extend(this.options, options || {}); + }, + + /** + * Make the callback request + */ + callback : function(param) + { + this.options.params = [param]; + new Prado.AJAX.Callback(this.element, this.options); + }, + + /** + * Callback onSuccess handler, update the element innerHTML if necessary + */ + onSuccess : function(result, output) + { + if(this.options.update) + { + var element = $(this.options.update) + if(element) element.innerHTML = output; + } + } +} + +/** + * Drop container to accept draggable component drops. + */ +Prado.DropContainer = Class.create(); +Prado.DropContainer.prototype = Object.extend(new Prado.ActivePanel.Request(), +{ + initialize : function(element, options) + { + this.element = element; + this.setOptions(options); + Object.extend(this.options, + { + onDrop : this.onDrop.bind(this), + evalScripts : true, + onSuccess : options.onSuccess || this.update.bind(this) + }); + Droppables.add(element, this.options); + }, + + onDrop : function(draggable, droppable) + { + this.callback(draggable.id) + }, + + update : function(result, output) + { + this.onSuccess(result, output); + if (this.options.evalScripts) + Prado.AJAX.EvalScript(output); + } +}); \ No newline at end of file diff --git a/framework/Web/Javascripts/base/datepicker.js b/framework/Web/Javascripts/base/datepicker.js new file mode 100644 index 00000000..046a0a4b --- /dev/null +++ b/framework/Web/Javascripts/base/datepicker.js @@ -0,0 +1,928 @@ +Prado.Calendar = Class.create(); + +Prado.Calendar.Util = Class.create(); + +Object.extend(Prado.Calendar.Util, +{ + // utility function to pad a number to a given width + pad : function(number, X) + { + X = (!X ? 2 : X); + number = ""+number; + while (number.length < X) + number = "0" + number; + return number; + }, + + //allow for deprecated formats + FormatDate : function(date, format) + { + if(!isObject(date)) return ""; + if(format.indexOf("%") > -1) + { + alert('Please use the new SimpleDateFormat pattern, e.g. yyyy-MM-dd'); + return this.FormatDateDepr(date,format); + } + else + { + return this.SimpleFormatDate(date, format); + } + }, + + //allow for deprecated format + ParseDate : function(value, format) + { + val=String(value); + format=String(format); + + if(val.length <= 0) return null; + + if(format.length <= 0) return new Date(value); + + if(format.indexOf("%") > -1) + return this.ParseDateDepr(value, format); + else + return this.SimpleParseDate(value, format); + }, + + //deprecated format + FormatDateDepr : function(date, str) + { + var m = date.getMonth(); + var d = date.getDate(); + var y = date.getFullYear(); + + var s = {}; + + s["%d"] = this.pad(d); // the day of the month (range 01 to 31) + s["%e"] = d; // the day of the month (range 1 to 31) + s["%m"] = this.pad(m+1); + s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) + s["%Y"] = y; // year with the century + + var re = /%./g; + + var a = str.match(re); + for (var i = 0; i < a.length; i++) + { + var tmp = s[a[i]]; + if (tmp) + { + re = new RegExp(a[i], 'g'); + str = str.replace(re, tmp); + } + } + + return str; + }, + + //deprecated format parser + ParseDateDepr : function(value, format) + { + var y = 0; + var m = -1; + var d = 0; + var a = value.split(/\W+/); + var b = format.match(/%./g); + var i = 0, j = 0; + var hr = 0; + var min = 0; + for (i = 0; i < a.length; ++i) { + if (!a[i]) + continue; + switch (b[i]) { + case "%d": + case "%e": + d = parseInt(a[i], 10); + break; + + case "%m": + m = parseInt(a[i], 10) - 1; + break; + + case "%Y": + case "%y": + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + break; + + case "%H": + case "%I": + case "%k": + case "%l": + hr = parseInt(a[i], 10); + break; + + case "%P": + case "%p": + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + break; + + case "%M": + min = parseInt(a[i], 10); + break; + } + } + if (y != 0 && m != -1 && d != 0) + { + var date = new Date(y, m, d, hr, min, 0); + return (isObject(date) + && y == date.getFullYear() + && m == date.getMonth() + && d == date.getDate()) ? date : null; + } + return null; + }, + + SimpleFormatDate : function(date, format) + { + if(!isObject(date)) return ""; + var bits = new Array(); + bits['d'] = date.getDate(); + bits['dd'] = this.pad(date.getDate(),2); + + bits['M'] = date.getMonth()+1; + bits['MM'] = this.pad(date.getMonth()+1,2); + + var yearStr = "" + date.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; + }, + + SimpleParseDate : 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; + }, + + IsLeapYear : function (year) + { + return ((year%4 == 0) && ((year%100 != 0) || (year%400 == 0))); + }, + + yearLength : function(year) + { + if (this.isLeapYear(year)) + return 366; + else + return 365; + }, + + dayOfYear : function(date) + { + var a = this.isLeapYear(date.getFullYear()) ? + Calendar.LEAP_NUM_DAYS : Calendar.NUM_DAYS; + return a[date.getMonth()] + date.getDate(); + }, + ISODate : function(date) + { + var y = date.getFullYear(); + var m = this.pad(date.getMonth() + 1); + var d = this.pad(date.getDate()); + return String(y) + String(m) + String(d); + }, + + 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 (this.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); + } + } +}); + +Object.extend(Prado.Calendar, +{ + // Accumulated days per month, for normal and for leap years. + // Used in week number calculations. + NUM_DAYS : [0,31,59,90,120,151,181,212,243,273,304,334], + LEAP_NUM_DAYS : [0,31,60,91,121,152,182,213,244,274,305,335] +}); + +Prado.Calendar.prototype = +{ + monthNames : [ "January", "February", "March", "April", + "May", "June", "July", "August", + "September", "October", "November", "December" + ], + + shortWeekDayNames : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], + + format : "yyyy-MM-dd", + + css : "calendar_system.css", + + initialize : function(control, attr) + { + this.attr = attr || []; + this.control = $(control); + this.dateSlot = new Array(42); + this.weekSlot = new Array(6); + this.firstDayOfWeek = 1; + this.minimalDaysInFirstWeek = 4; + this.currentDate = new Date(); + this.selectedDate = null; + this.className = "Prado_Calendar"; + + //which element to trigger to show the calendar + this.trigger = this.attr.trigger ? $(this.attr.trigger) : this.control; + Event.observe(this.trigger, "click", this.show.bind(this)); + + Prado.Calendar.Util.ImportCss(document, this.css); + + if(this.attr.format) this.format = this.attr.format; + + //create it + this.create(); + this.hookEvents(); + }, + + create : function() + { + var div; + var table; + var tbody; + var tr; + var td; + + // Create the top-level div element + this._calDiv = document.createElement("div"); + this._calDiv.className = this.className; + this._calDiv.style.display = "none"; + + // header div + div = document.createElement("div"); + div.className = "calendarHeader"; + this._calDiv.appendChild(div); + + table = document.createElement("table"); + table.style.cellSpacing = 0; + div.appendChild(table); + + tbody = document.createElement("tbody"); + table.appendChild(tbody); + + tr = document.createElement("tr"); + tbody.appendChild(tr); + + // Previous Month Button + td = document.createElement("td"); + td.className = "prevMonthButton"; + this._previousMonth = document.createElement("button"); + this._previousMonth.appendChild(document.createTextNode("<<")); + td.appendChild(this._previousMonth); + tr.appendChild(td); + + + + // + // Create the month drop down + // + td = document.createElement("td"); + td.className = "labelContainer"; + tr.appendChild(td); + this._monthSelect = document.createElement("select"); + for (var i = 0 ; i < this.monthNames.length ; i++) { + var opt = document.createElement("option"); + opt.innerHTML = this.monthNames[i]; + opt.value = i; + if (i == this.currentDate.getMonth()) { + opt.selected = true; + } + this._monthSelect.appendChild(opt); + } + td.appendChild(this._monthSelect); + + + // + // Create the year drop down + // + td = document.createElement("td"); + td.className = "labelContainer"; + tr.appendChild(td); + this._yearSelect = document.createElement("select"); + for(var i=1920; i < 2050; ++i) { + var opt = document.createElement("option"); + opt.innerHTML = i; + opt.value = i; + if (i == this.currentDate.getFullYear()) { + opt.selected = false; + } + this._yearSelect.appendChild(opt); + } + td.appendChild(this._yearSelect); + + + td = document.createElement("td"); + td.className = "nextMonthButton"; + this._nextMonth = document.createElement("button"); + this._nextMonth.appendChild(document.createTextNode(">>")); + td.appendChild(this._nextMonth); + tr.appendChild(td); + + // Calendar body + div = document.createElement("div"); + div.className = "calendarBody"; + this._calDiv.appendChild(div); + this._table = div; + + // Create the inside of calendar body + + var text; + table = document.createElement("table"); + //table.style.width="100%"; + table.className = "grid"; + + div.appendChild(table); + var thead = document.createElement("thead"); + table.appendChild(thead); + tr = document.createElement("tr"); + thead.appendChild(tr); + + for(i=0; i < 7; ++i) { + td = document.createElement("th"); + text = document.createTextNode(this.shortWeekDayNames[(i+this.firstDayOfWeek)%7]); + td.appendChild(text); + td.className = "weekDayHead"; + tr.appendChild(td); + } + + // Date grid + tbody = document.createElement("tbody"); + table.appendChild(tbody); + + for(week=0; week<6; ++week) { + tr = document.createElement("tr"); + tbody.appendChild(tr); + + for(day=0; day<7; ++day) { + td = document.createElement("td"); + td.className = "calendarDate"; + text = document.createTextNode(String.fromCharCode(160)); + td.appendChild(text); + + tr.appendChild(td); + var tmp = new Object(); + tmp.tag = "DATE"; + tmp.value = -1; + tmp.data = text; + this.dateSlot[(week*7)+day] = tmp; + + Event.observe(td, "mouseover", this.hover.bind(this)); + Event.observe(td, "mouseout", this.hover.bind(this)); + + } + } + + // Calendar Footer + div = document.createElement("div"); + div.className = "calendarFooter"; + this._calDiv.appendChild(div); + + table = document.createElement("table"); + //table.style.width="100%"; + table.className = "footerTable"; + //table.cellSpacing = 0; + div.appendChild(table); + + tbody = document.createElement("tbody"); + table.appendChild(tbody); + + tr = document.createElement("tr"); + tbody.appendChild(tr); + + // + // The TODAY button + // + td = document.createElement("td"); + td.className = "todayButton"; + this._todayButton = document.createElement("button"); + var today = new Date(); + var buttonText = today.getDate() + " " + this.monthNames[today.getMonth()] + ", " + today.getFullYear(); + this._todayButton.appendChild(document.createTextNode(buttonText)); + td.appendChild(this._todayButton); + tr.appendChild(td); + + // + // The CLEAR button + // + td = document.createElement("td"); + td.className = "clearButton"; + this._clearButton = document.createElement("button"); + var today = new Date(); + buttonText = "Clear"; + this._clearButton.appendChild(document.createTextNode(buttonText)); + td.appendChild(this._clearButton); + tr.appendChild(td); + + document.body.appendChild(this._calDiv); + + this.update(); + this.updateHeader(); + + return this._calDiv; + }, + + hookEvents : function() + { + // IE55+ extension + this._previousMonth.hideFocus = true; + this._nextMonth.hideFocus = true; + this._todayButton.hideFocus = true; + // end IE55+ extension + + // hook up events + Event.observe(this._previousMonth, "click", this.prevMonth.bind(this)); + Event.observe(this._nextMonth, "click", this.nextMonth.bind(this)); + Event.observe(this._todayButton, "click", this.selectToday.bind(this)); + Event.observe(this._clearButton, "click", this.clearSelection.bind(this)); + + Event.observe(this._monthSelect, "change", this.monthSelect.bind(this)); + Event.observe(this._yearSelect, "change", this.yearSelect.bind(this)); + + // ie6 extension + Event.observe(this._calDiv, "mousewheel", this.mouseWheelChange.bind(this)); + + Event.observe(this._table, "click", this.selectDate.bind(this)); + + Event.observe(this._calDiv,"keydown", this.keyPressed.bind(this)); + + /* + this._calDiv.onkeydown = function (e) { + if (e == null) e = document.parentWindow.event; + var kc = e.keyCode != null ? e.keyCode : e.charCode; + + if(kc == 13) { + var d = new Date(dp._currentDate).valueOf(); + dp.setSelectedDate(d); + + if (!dp._alwaysVisible && dp._hideOnSelect) { + dp.hide(); + } + return false; + } + + + if (kc < 37 || kc > 40) return true; + + var d = new Date(dp._currentDate).valueOf(); + if (kc == 37) // left + d -= 24 * 60 * 60 * 1000; + else if (kc == 39) // right + d += 24 * 60 * 60 * 1000; + else if (kc == 38) // up + d -= 7 * 24 * 60 * 60 * 1000; + else if (kc == 40) // down + d += 7 * 24 * 60 * 60 * 1000; + + dp.setCurrentDate(new Date(d)); + return false; + }*/ + + + }, + + keyPressed : function(ev) + { + if (!ev) ev = document.parentWindow.event; + var kc = ev.keyCode != null ? ev.keyCode : ev.charCode; + + if(kc = Event.KEY_RETURN) + { + //var d = new Date(this.currentDate); + this.setSelectedDate(this.currentDate); + this.hide(); + return false; + } + + if(kc < 37 || kc > 40) return true; + + var d = new Date(this.currentDate).valueOf(); + if(kc == Event.KEY_LEFT) + d -= 86400000; //-1 day + else if (kc == Event.KEY_RIGHT) + d += 86400000; //+1 day + else if (kc == Event.KEY_UP) + d -= 604800000; // -7 days + else if (kc == Event.KEY_DOWN) + d += 604800000; // +7 days + this.setCurrentDate(new Date(d)); + return false; + }, + + selectDate : function(ev) + { + var el = Event.element(ev); + while (el.nodeType != 1) + el = el.parentNode; + + while (el != null && el.tagName && el.tagName.toLowerCase() != "td") + el = el.parentNode; + + // if no td found, return + if (el == null || el.tagName == null || el.tagName.toLowerCase() != "td") + return; + + var d = new Date(this.currentDate); + var n = Number(el.firstChild.data); + if (isNaN(n) || n <= 0 || n == null) + return; + + d.setDate(n); + this.setSelectedDate(d); + this.hide(); + }, + + selectToday : function() + { + this.setSelectedDate(new Date()); + this.hide(); + }, + + clearSelection : function() + { + this.selectedDate = null; + if (isFunction(this.onchange)) + this.onchange(); + this.hide(); + }, + + monthSelect : function(ev) + { + this.setMonth(Form.Element.getValue(Event.element(ev))); + }, + + yearSelect : function(ev) + { + this.setYear(Form.Element.getValue(Event.element(ev))); + }, + + // ie6 extension + mouseWheelChange : function (e) + { + if (e == null) e = document.parentWindow.event; + var n = - e.wheelDelta / 120; + var d = new Date(this.currentDate); + var m = this.getMonth() + n; + this.setMonth(m); + this.setCurrentDate(d); + + return false; + }, + + onchange : function() + { + this.control.value = this.formatDate(); + }, + + formatDate : function() + { + return Prado.Calendar.Util.FormatDate(this.selectedDate, this.format); + }, + + setCurrentDate : function(date) + { + if (date == null) + return; + + // if string or number create a Date object + if (isString(date) || isNumber(date)) + date = new Date(date); + + // do not update if not really changed + if (this.currentDate.getDate() != date.getDate() || + this.currentDate.getMonth() != date.getMonth() || + this.currentDate.getFullYear() != date.getFullYear()) + { + + this.currentDate = new Date(date); + + this.updateHeader(); + this.update(); + } + + }, + + setSelectedDate : function(date) + { + this.selectedDate = new Date(date); + this.setCurrentDate(this.selectedDate); + if (isFunction(this.onchange)) + this.onchange(); + }, + + getElement : function() + { + return this._calDiv; + }, + + getSelectedDate : function () + { + return isNull(this.selectedDate) ? null : new Date(this.selectedDate); + }, + + setYear : function(year) + { + var d = new Date(this.currentDate); + d.setFullYear(year); + this.setCurrentDate(d); + }, + + setMonth : function (month) + { + var d = new Date(this.currentDate); + d.setMonth(month); + this.setCurrentDate(d); + }, + + nextMonth : function () + { + this.setMonth(this.currentDate.getMonth()+1); + }, + + prevMonth : function () + { + this.setMonth(this.currentDate.getMonth()-1); + }, + + show : function() + { + if(!this.showing) + { + var pos = Position.cumulativeOffset(this.control); + pos[1] += this.control.offsetHeight; + this._calDiv.style.display = "block"; + this._calDiv.style.top = pos[1] + "px"; + this._calDiv.style.left = pos[0] + "px"; + Event.observe(document.body, "click", this.hideOnClick.bind(this)); + var date = Prado.Calendar.Util.ParseDate(Form.Element.getValue(this.control), this.format); + if(!isNull(date)) + { + this.selectedDate = date; + this.setCurrentDate(date); + } + this.showing = true; + } + }, + + //hide the calendar when clicked outside any calendar + hideOnClick : function(ev) + { + if(!this.showing) return; + var el = Event.element(ev); + var within = false; + do + { + within = within || el.className == this.className; + within = within || el == this.trigger; + within = within || el == this.control; + if(within) break; + el = el.parentNode; + } + while(el); + if(!within) this.hide(); + }, + + hide : function() + { + if(this.showing) + { + this._calDiv.style.display = "none"; + this.showing = false; + Event.stopObserving(document.body, "click", this.hideOnClick.bind(this)); + } + }, + + update : function() + { + var Util = Prado.Calendar.Util; + + // Calculate the number of days in the month for the selected date + var date = this.currentDate; + var today = Util.ISODate(new Date()); + + var selected = isNull(this.selectedDate) ? "" : Util.ISODate(this.selectedDate); + var current = Util.ISODate(date); + var d1 = new Date(date.getFullYear(), date.getMonth(), 1); + var d2 = new Date(date.getFullYear(), date.getMonth()+1, 1); + var monthLength = Math.round((d2 - d1) / (24 * 60 * 60 * 1000)); + + // Find out the weekDay index for the first of this month + var firstIndex = (d1.getDay() - this.firstDayOfWeek) % 7 ; + if (firstIndex < 0) + firstIndex += 7; + + var index = 0; + while (index < firstIndex) { + this.dateSlot[index].value = -1; + this.dateSlot[index].data.data = String.fromCharCode(160); + this.dateSlot[index].data.parentNode.className = "empty"; + index++; + } + + for (i = 1; i <= monthLength; i++, index++) { + var slot = this.dateSlot[index]; + var slotNode = slot.data.parentNode; + slot.value = i; + slot.data.data = i; + slotNode.className = "date"; + if (Util.ISODate(d1) == today) { + slotNode.className += " today"; + } + if (Util.ISODate(d1) == current) { + slotNode.className += " current"; + } + if (Util.ISODate(d1) == selected) { + slotNode.className += " selected"; + } + d1 = new Date(d1.getFullYear(), d1.getMonth(), d1.getDate()+1); + } + + var lastDateIndex = index; + + while(index < 42) { + this.dateSlot[index].value = -1; + this.dateSlot[index].data.data = String.fromCharCode(160); + this.dateSlot[index].data.parentNode.className = "empty"; + ++index; + } + + }, + + hover : function(ev) + { + //conditionally add the hover class to the event target element. + Element.condClassName(Event.element(ev), "hover", ev.type=="mouseover"); + }, + + updateHeader : function () { + + var options = this._monthSelect.options; + var m = this.currentDate.getMonth(); + for(var i=0; i < options.length; ++i) { + options[i].selected = false; + if (options[i].value == m) { + options[i].selected = true; + } + } + + options = this._yearSelect.options; + var year = this.currentDate.getFullYear(); + for(var i=0; i < options.length; ++i) { + options[i].selected = false; + if (options[i].value == year) { + options[i].selected = true; + } + } + + } + + +}; \ No newline at end of file diff --git a/framework/Web/Javascripts/base/focus.js b/framework/Web/Javascripts/base/focus.js new file mode 100644 index 00000000..6c1359cd --- /dev/null +++ b/framework/Web/Javascripts/base/focus.js @@ -0,0 +1,99 @@ +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= ' ') { + 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(); + } +}; \ No newline at end of file diff --git a/framework/Web/Javascripts/base/postback.js b/framework/Web/Javascripts/base/postback.js new file mode 100644 index 00000000..8b2d522e --- /dev/null +++ b/framework/Web/Javascripts/base/postback.js @@ -0,0 +1,72 @@ + +Prado.PostBack = Class.create(); + +Prado.PostBack.Options = Class.create(); + +Prado.PostBack.Options.prototype = +{ + initialize : function(performValidation, validationGroup, actionUrl, trackFocus, clientSubmit) + { + this.performValidation = performValidation; + this.validationGroup = validationGroup; + this.actionUrl = actionUrl; + this.trackFocus = trackFocus; + this.clientSubmit = clientSubmit; + } +} + +Prado.PostBack.perform = function(formID, eventTarget, eventParameter, options) +{ + var theForm = document.getElementById ? document.getElementById(formID) : document.forms[formID]; + var canSubmit = true; + if ((typeof(options) != 'undefined') || options == null) + { + if (options.performValidation) + { + canSubmit = Prado.Validation.validate(options.validationGroup); + } + if (canSubmit) + { + if ((typeof(options.actionUrl) != 'undefined') && (options.actionUrl != null) && (options.actionUrl.length > 0)) + { + theForm.action = options.actionUrl; + } + if (options.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 (!options.clientSubmit) + { + canSubmit = false; + } + } + } + if (canSubmit && (!theForm.onsubmit || theForm.onsubmit())) + { + theForm.PRADO_POSTBACK_TARGET.value = eventTarget; + theForm.PRADO_POSTBACK_PARAMETER.value = eventParameter; + theForm.submit(); + } +} \ No newline at end of file diff --git a/framework/Web/Javascripts/base/prado.js b/framework/Web/Javascripts/base/prado.js new file mode 100644 index 00000000..81f51e41 --- /dev/null +++ b/framework/Web/Javascripts/base/prado.js @@ -0,0 +1,3 @@ +Prado = Class.create(); + +Prado.version = '3.0a'; diff --git a/framework/Web/Javascripts/base/scroll.js b/framework/Web/Javascripts/base/scroll.js new file mode 100644 index 00000000..e69de29b diff --git a/framework/Web/Javascripts/base/validation.js b/framework/Web/Javascripts/base/validation.js new file mode 100644 index 00000000..4129ad70 --- /dev/null +++ b/framework/Web/Javascripts/base/validation.js @@ -0,0 +1,675 @@ + +/** + * Prado client-side javascript validation class. + */ +Prado.Validation = Class.create(); + +/** + * Utilities for validation. Static class. + */ +Prado.Validation.Util = Class.create(); + +/** + * Convert a string into integer, returns null if not integer. + * @param {string} the string to convert to integer + * @type {integer|null} null if string does not represent an integer. + */ +Prado.Validation.Util.toInteger = function(value) +{ + var exp = /^\s*[-\+]?\d+\s*$/; + if (value.match(exp) == null) + return null; + var num = parseInt(value, 10); + return (isNaN(num) ? null : num); +} + +/** + * Convert a string into a double/float value. Internationalization + * is not supported + * @param {string} the string to convert to double/float + * @param {string} the decimal character + * @return {float|null} null if string does not represent a float value + */ +Prado.Validation.Util.toDouble = function(value, decimalchar) +{ + decimalchar = undef(decimalchar) ? "." : decimalchar; + var exp = new RegExp("^\\s*([-\\+])?(\\d+)?(\\" + decimalchar + "(\\d+))?\\s*$"); + var m = value.match(exp); + if (m == null) + return null; + 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 currency value + * @param {string} the grouping character, default is "," + * @param {int} number of decimal digits + * @param {string} the decimal character, default is "." + * @type {float|null} the currency value as float. + */ +Prado.Validation.Util.toCurrency = function(value, groupchar, digits, decimalchar) +{ + groupchar = undef(groupchar) ? "," : groupchar; + decimalchar = undef(decimalchar) ? "." : decimalchar; + digits = undef(digits) ? 2 : digits; + + var exp = new RegExp("^\\s*([-\\+])?(((\\d+)\\" + groupchar + ")*)(\\d+)" + + ((digits > 0) ? "(\\" + decimalchar + "(\\d{1," + digits + "}))?" : "") + + "\\s*$"); + var m = value.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); +} + +/** + * Get the date from string using the prodivided date format string. + * The format notations are + * # day -- %d or %e + * # month -- %m + * # year -- %y or %Y + * # hour -- %H, %I, %k, or %l + * # minutes -- %M + * # P.M. -- %p or %P + * @param {string} the formatted date string + * @param {string} the date format + * @type {Date} the date represented in the string + */ +Prado.Validation.Util.toDate = function(value, format) +{ + var y = 0; + var m = -1; + var d = 0; + var a = value.split(/\W+/); + var b = format.match(/%./g); + var i = 0, j = 0; + var hr = 0; + var min = 0; + for (i = 0; i < a.length; ++i) { + if (!a[i]) + continue; + switch (b[i]) { + case "%d": + case "%e": + d = parseInt(a[i], 10); + break; + + case "%m": + m = parseInt(a[i], 10) - 1; + break; + + case "%Y": + case "%y": + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + break; + + case "%H": + case "%I": + case "%k": + case "%l": + hr = parseInt(a[i], 10); + break; + + case "%P": + case "%p": + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + break; + + case "%M": + min = parseInt(a[i], 10); + break; + } + } + if (y != 0 && m != -1 && d != 0) + { + var date = new Date(y, m, d, hr, min, 0); + return (isObject(date) + && y == date.getFullYear() + && m == date.getMonth() + && d == date.getDate()) ? date.valueOf() : null; + } + return null; +} + +/** + * Trim the value, if the value is undefined, empty string is return. + * @param {string} string to be trimmed. + * @type {string} trimmed string. + */ +Prado.Validation.Util.trim = function(value) +{ + if(undef(value)) return ""; + return value.replace(/^\s+|\s+$/g, ""); +} + +/** + * A delayed focus on a particular element + * @param {element} element to apply focus() + */ +Prado.Validation.Util.focus = function(element) +{ + var obj = $(element); + if(isObject(obj) && isdef(obj.focus)) + setTimeout(function(){ obj.focus(); }, 100); + return false; +} + +/** + * List of validator instances. + */ +Prado.Validation.validators = []; + +/** + * List of forms. + * @type {int} + */ +Prado.Validation.forms = []; + +/** + * List of summary controls. + */ +Prado.Validation.summaries = []; + +/** + * Validation groups. + */ +Prado.Validation.groups = []; + + +/** + * Second type of grouping. + */ +Prado.Validation.TargetGroups = []; + + +/** + * Current Target group. + */ +Prado.Validation.CurrentTargetGroup = null; + +Prado.Validation.HasTargetGroup = false; + +/** + * Targets that can cause validation. + */ +Prado.Validation.ActiveTarget = null; + + +/** + * Determine if group validation is active. + */ +Prado.Validation.IsGroupValidation = false; + +/** + * Add a form for validation. + * @param {string} form ID + */ +Prado.Validation.AddForm = function(id) +{ + Prado.Validation.forms.push($(id)); +} + +/** + * Add a target that causes validation. Only elements that have been added + * can cause validation. + * @param {string} target id + */ +Prado.Validation.AddTarget = function(id, group) +{ + var target = $(id); + Event.observe(target, "click", function() + { + Prado.Validation.ActiveTarget = target; + Prado.Validation.CurrentTargetGroup = Prado.Validation.TargetGroups[id]; + }); + if(group) + { + Prado.Validation.TargetGroups[id] = group; + Prado.Validation.HasTargetGroup = true; + } +} + +/** + * Associate a list of validators to a particular control element. + * This essentially allows a set of validators to be grouped to a particular button. + * @param {list} group array show have, {group : "id", target : "target button"} + * @param {array} validator ids + */ +Prado.Validation.AddGroup = function(group, validators) +{ + group.active = false; //default active status is false. + group.target = $(group.target); + group.validators = validators; + Prado.Validation.groups.push(group); + + //update the active group when the button is clicked. + Event.observe(group.target, "click", Prado.Validation.UpdateActiveGroup); +} + +/** + * Update the active group, if call manually it will deactivate all groups. + * @param {string} + * @type {int} + */ +Prado.Validation.UpdateActiveGroup = function(ev) +{ + var groups = Prado.Validation.groups; + for (var i = 0; i < groups.length; i++) + { + groups[i].active = (isdef(ev) && groups[i].target == Event.element(ev)); + } + Prado.Validation.IsGroupValidation = isdef(ev); +} + +/** + * Determine if validation is sucessful. Iterate through the list + * of validator instances and call validate(). Only validators that + * for a particular form are evaluated. Other validators will be disabled. + * If performing group validation, only active validators are visible. + * @param {element} the form for the controls to validate. + * @type {boolean} true is all validators are valid, false otherwise. + */ +Prado.Validation.IsValid = function(form) +{ + var valid = true; + var validators = Prado.Validation.validators; + + for(var i = 0; i < validators.length; i++) + { + //prevent validating multiple forms + validators[i].enabled = !validators[i].control || undef(validators[i].control.form) || validators[i].control.form == form; + //when group validation, only validators in the active group are visible. + validators[i].visible = Prado.Validation.IsGroupValidation ? validators[i].inActiveGroup() : true; + + if(Prado.Validation.HasTargetGroup) + { + if(validators[i].group != Prado.Validation.CurrentTargetGroup) + validators[i].enabled = false; + } + + valid &= validators[i].validate(); + } + + //show the summary including the alert box + Prado.Validation.ShowSummary(form); + //reset all the group active status to false + Prado.Validation.UpdateActiveGroup(); + return valid; +} + +/** + * Base validator class. Supply a different validation function + * to obtain a different validator. E.g. to use the RequiredFieldValidator + * new Prado.Validation(Prado.Validation.RequiredFieldValidator, options); + * or to use the CustomValidator, + * new Prado.Validation(Prado.Validation.CustomValidator, options); + */ +Prado.Validation.prototype = +{ + /** + * Initialize the validator. + * @param {function} the function to call to evaluate if + * the validator is valid + * @param {string|element} the control ID or element + * @param {array} the list of attributes for the validator + */ + initialize : function(validator, attr) + { + this.evaluateIsValid = validator; + this.attr = undef(attr) ? [] : attr; + this.message = $(attr.id); + this.control = $(attr.controltovalidate); + this.enabled = isdef(attr.enabled) ? attr.enabled : true; + this.visible = isdef(attr.visible) ? attr.visible : true; + this.group = isdef(attr.validationgroup) ? attr.validationgroup : null; + this.isValid = true; + Prado.Validation.validators.push(this); + if(this.evaluateIsValid) + this.evaluateIsValid.bind(this); + }, + + /** + * Evaluate the validator only when visible and enabled. + * @type {boolean} true if valid, false otherwise. + */ + validate : function() + { + if(this.visible && this.enabled && this.evaluateIsValid) + { + this.isValid = this.evaluateIsValid(); + } + else + { + this.isValid = true; + } + + this.observe(); //watch for changes to the control values + this.update(); //update the validation messages + return this.isValid; + }, + + /** + * Hide or show the error messages for "Dynamic" displays. + */ + update : function() + { + if(this.attr.display == "Dynamic") + this.isValid ? Element.hide(this.message) : Element.show(this.message); + + if(this.message) + this.message.style.visibility = this.isValid ? "hidden" : "visible"; + + //update the control css class name + var className = this.attr.controlcssclass; + if(this.control && isString(className) && className.length>0) + Element.condClassName(this.control, className, !this.isValid); + Prado.Validation.ShowSummary(); + }, + + /** + * Change the validity of the validator, calls update(). + * @param {boolean} change the isValid state of the validator. + */ + setValid : function(valid) + { + this.isValid = valid; + this.update(); + }, + + /** + * Observe changes to the control values, add "onblur" event to the control once. + */ + observe : function() + { + if(undef(this.observing)) + { + if(this.control && this.control.form) + Event.observe(this.control, "blur", this.validate.bind(this)); + this.observing = true; + } + }, + + /** + * Convert the value of the control to a specific data type. + * @param {string} the data type, "Integer", "Double", "Currency" or "Date". + * @param {string} the value to convert, null to get the value from the control. + * @type {mixed|null} the converted data value. + */ + convert : function(dataType, value) + { + if(undef(value)) + value = Form.Element.getValue(this.control); + switch(dataType) + { + case "Integer": + return Prado.Validation.Util.toInteger(value); + case "Double" : + case "Float" : + return Prado.Validation.Util.toDouble(value, this.attr.decimalchar); + case "Currency" : + return Prado.Validation.Util.toCurrency( + value, this.attr.groupchar, this.attr.digits, this.attr.decimalchar); + case "Date": + return Prado.Validation.Util.toDate(value, this.attr.dateformat); + } + return value.toString(); + }, + + /** + * Determine if the current validator is part of a active validation group. + * @type {boolean} true if part of active validation group, false otherwise. + */ + inActiveGroup : function() + { + var groups = Prado.Validation.groups; + for (var i = 0; i < groups.length; i++) + { + if(groups[i].active && groups[i].validators.contains(this.attr.id)) + return true; + } + return false; + } +} + +/** + * Validation summary class. + */ +Prado.Validation.Summary = Class.create(); +Prado.Validation.Summary.prototype = +{ + /** + * Initialize a validation summary. + * @param {array} summary options. + */ + initialize : function(attr) + { + this.attr = attr; + this.div = $(attr.id); + this.visible = false; + this.enabled = false; + this.group = isdef(attr.validationgroup) ? attr.validationgroup : null; + Prado.Validation.summaries.push(this); + }, + + /** + * Show the validation summary. + * @param {boolean} true to allow alert message + */ + show : function(warn) + { + var refresh = warn || this.attr.refresh == "1"; + var messages = this.getMessages(); + if(messages.length <= 0 || !this.visible || !this.enabled) + { + if(refresh) + Element.hide(this.div); + return; + } + + if(Prado.Validation.HasTargetGroup) + { + if(Prado.Validation.CurrentTargetGroup != this.group) + { + if(refresh) + Element.hide(this.div); + return; + } + } + + if(this.attr.showsummary != "False" && refresh) + { + //Element.show(this.div); + this.div.style.display = "block"; + while(this.div.childNodes.length > 0) + this.div.removeChild(this.div.lastChild); + new Insertion.Bottom(this.div, this.formatSummary(messages)); + } + + if(warn) + window.scrollTo(this.div.offsetLeft-20, this.div.offsetTop-20); + + var summary = this; + if(warn && this.attr.showmessagebox == "True" && refresh) + setTimeout(function(){alert(summary.formatMessageBox(messages));},20); + }, + + /** + * Get a list of error messages from the validators. + * @type {array} list of messages + */ + getMessages : function() + { + var validators = Prado.Validation.validators; + var messages = []; + for(var i = 0; i < validators.length; i++) + { + if(validators[i].isValid == false + && isString(validators[i].attr.errormessage) + && validators[i].attr.errormessage.length > 0) + { + + messages.push(validators[i].attr.errormessage); + } + } + return messages; + }, + + /** + * Return the format parameters for the summary. + * @param {string} format type, "List", "SingleParagraph" or "BulletList" + * @type {array} formatting parameters + */ + formats : function(type) + { + switch(type) + { + case "List": + return { header : "
", first : "", pre : "", post : "
", last : ""}; + case "SingleParagraph": + return { header : " ", first : "", pre : "", post : " ", last : "
"}; + case "BulletList": + default: + return { header : "", first : "
    ", pre : "
  • ", post : "
  • ", last : "
"}; + } + }, + + /** + * Format the message summary. + * @param {array} list of error messages. + * @type {string} formatted message + */ + formatSummary : function(messages) + { + var format = this.formats(this.attr.displaymode); + var output = isdef(this.attr.headertext) ? this.attr.headertext + format.header : ""; + output += format.first; + for(var i = 0; i < messages.length; i++) + output += (messages[i].length>0) ? format.pre + messages[i] + format.post : ""; + output += format.last; + return output; + }, + /** + * Format the message alert box. + * @param {array} a list of error messages. + * @type {string} format message for alert. + */ + formatMessageBox : function(messages) + { + var output = isdef(this.attr.headertext) ? this.attr.headertext + "\n" : ""; + for(var i = 0; i < messages.length; i++) + { + switch(this.attr.displaymode) + { + case "List": + output += messages[i] + "\n"; + break; + case "BulletList": + default: + output += " - " + messages[i] + "\n"; + break; + case "SingleParagraph": + output += messages[i] + " "; + break; + } + } + return output; + }, + + /** + * Determine if this summary belongs to an active group. + * @type {boolean} true if belongs to an active group. + */ + inActiveGroup : function() + { + var groups = Prado.Validation.groups; + for (var i = 0; i < groups.length; i++) + { + if(groups[i].active && groups[i].id == this.attr.group) + return true; + } + return false; + } +} + +/** + * Show the validation error message summary. + * @param {element} the form that activated the summary call. + */ +Prado.Validation.ShowSummary = function(form) +{ + var summary = Prado.Validation.summaries; + for(var i = 0; i < summary.length; i++) + { + if(isdef(form)) + { + if(Prado.Validation.IsGroupValidation) + { + summary[i].visible = summary[i].inActiveGroup(); + } + else + { + summary[i].visible = undef(summary[i].attr.group); + } + + summary[i].enabled = $(summary[i].attr.form) == form; + } + summary[i].show(form); + } +} + + + +/** + * When a form is try to submit, check the validators, submit + * the form only when all validators are valid. + * @param {event} form submit event. + */ +Prado.Validation.OnSubmit = function(ev) +{ + //HTML text editor, tigger save first. + //alert(tinyMCE); + if(typeof tinyMCE != "undefined") + tinyMCE.triggerSave(); + + //no active target? + if(!Prado.Validation.ActiveTarget) return true; + var valid = Prado.Validation.IsValid(Event.element(ev) || ev); + + //not valid? do not submit the form + if(Event.element(ev) && !valid) + Event.stop(ev); + + //reset the target + Prado.Validation.ActiveTarget = null; + //Prado.Validation.CurrentTargetGroup = null; + + return valid; +} + +/** + * During window onload event, attach onsubmit event for each of the + * forms in Prado.Validation.forms. + */ +Prado.Validation.OnLoad = function() +{ + Event.observe(Prado.Validation.forms,"submit", Prado.Validation.OnSubmit); +} + +/** + * Register Prado.Validation.Onload() for window.onload event. + */ +Event.OnLoad(Prado.Validation.OnLoad); \ No newline at end of file diff --git a/framework/Web/Javascripts/base/validators.js b/framework/Web/Javascripts/base/validators.js new file mode 100644 index 00000000..f281c87e --- /dev/null +++ b/framework/Web/Javascripts/base/validators.js @@ -0,0 +1,228 @@ + +Prado.Validation.TRequiredFieldValidator=function(){ + var inputType = this.control.getAttribute("type"); + if(inputType == 'file'){ + return true; + } + else{ + var trim=Prado.Validation.Util.trim; + var a=trim(Form.Element.getValue(this.control)); + var b=trim(this.attr.initialvalue); + return(a!=b); + } +} + + +Prado.Validation.TRegularExpressionValidator = function() +{ + var trim = Prado.Validation.Util.trim; + var value = trim(Form.Element.getValue(this.control)); + if (value == "") return true; + var rx = new RegExp(this.attr.validationexpression); + var matches = rx.exec(value); + return (matches != null && value == matches[0]); +} + +Prado.Validation.TEmailAddressValidator = Prado.Validation.TRegularExpressionValidator; + +Prado.Validation.TCustomValidator = function() +{ + var trim = Prado.Validation.Util.trim; + var value = isNull(this.control) ? '' : trim(Form.Element.getValue(this.control)); + var valid = true; + var func = this.attr.clientvalidationfunction; + if (isString(func) && func != "") + eval("valid = (" + func + "(this, value) != false);"); + return valid; +} + +Prado.Validation.TRangeValidator = function() +{ + var trim = Prado.Validation.Util.trim; + var value = trim(Form.Element.getValue(this.control)); + if (value == "") return true; + + var minval = this.attr.minimumvalue; + var maxval = this.attr.maximumvalue; + + if (undef(minval) && undef(maxval)) + return true; + + if (minval == "") minval = 0; + if (maxval == "") maxval = 0; + + var dataType = this.attr.type; + + if(undef(dataType)) + return (parseFloat(value) >= parseFloat(minval)) && (parseFloat(value) <= parseFloat(maxval)); + + //now do datatype range check. + var min = this.convert(dataType, minval); + var max = this.convert(dataType, maxval); + value = this.convert(dataType, value); + return value >= min && value <= max; +} + +Prado.Validation.TCompareValidator = function() +{ + var trim = Prado.Validation.Util.trim; + var value = trim(Form.Element.getValue(this.control)); + if (value.length == 0) return true; + + var compareTo; + + var comparee = $(this.attr.controlhookup);; + + if(comparee) + compareTo = trim(Form.Element.getValue(comparee)); + else + { + compareTo = isString(this.attr.valuetocompare) ? this.attr.valuetocompare : ""; + } + + var compare = Prado.Validation.TCompareValidator.compare; + + var isValid = compare.bind(this)(value, compareTo); + + //update the comparee control css class name and add onchange event once. + if(comparee) + { + var className = this.attr.controlcssclass; + if(isString(className) && className.length>0) + Element.condClassName(comparee, className, !isValid); + if(undef(this.observingComparee)) + { + Event.observe(comparee, "change", this.validate.bind(this)); + this.observingComparee = true; + } + } + return isValid; +} + +/** + * Compare the two values, also performs data type check. + * @param {string} value to compare with + * @param {string} value to compare + * @type {boolean} true if comparison or type check is valid, false otherwise. + */ +Prado.Validation.TCompareValidator.compare = function(operand1, operand2) +{ + var op1, op2; + if ((op1 = this.convert(this.attr.type, operand1)) == null) + return false; + if (this.attr.operator == "DataTypeCheck") + return true; + if ((op2 = this.convert(this.attr.type, operand2)) == null) + return true; + switch (this.attr.operator) + { + case "NotEqual": + return (op1 != op2); + case "GreaterThan": + return (op1 > op2); + case "GreaterThanEqual": + return (op1 >= op2); + case "LessThan": + return (op1 < op2); + case "LessThanEqual": + return (op1 <= op2); + default: + return (op1 == op2); + } +} + +Prado.Validation.TRequiredListValidator = function() +{ + var min = undef(this.attr.min) ? Number.NEGATIVE_INFINITY : parseInt(this.attr.min); + var max = undef(this.attr.max) ? Number.POSITIVE_INFINITY : parseInt(this.attr.max); + + var elements = document.getElementsByName(this.attr.selector); + + if(elements.length <= 0) + elements = document.getElementsBySelector(this.attr.selector); + + if(elements.length <= 0) + return true; + + var required = new Array(); + if(isString(this.attr.required) && this.attr.required.length > 0) + required = this.attr.required.split(/,\s* /); + + var isValid = true; + + var validator = Prado.Validation.TRequiredListValidator; + + switch(elements[0].type) + { + case 'radio': + case 'checkbox': + isValid = validator.IsValidRadioList(elements, min, max, required); + break; + case 'select-multiple': + isValid = validator.IsValidSelectMultipleList(elements, min, max, required); + break; + } + + var className = this.attr.elementcssclass; + if(isString(className) && className.length>0) + map(elements, function(element){ condClass(element, className, !isValid); }); + if(undef(this.observingRequiredList)) + { + Event.observe(elements, "change", this.validate.bind(this)); + this.observingRequiredList = true; + } + return isValid; +} + +//radio group selection +Prado.Validation.TRequiredListValidator.IsValidRadioList = function(elements, min, max, required) +{ + var checked = 0; + var values = new Array(); + for(var i = 0; i < elements.length; i++) + { + if(elements[i].checked) + { + checked++; + values.push(elements[i].value); + } + } + return Prado.Validation.TRequiredListValidator.IsValidList(checked, values, min, max, required); +} + +//multiple selection check +Prado.Validation.TRequiredListValidator.IsValidSelectMultipleList = function(elements, min, max, required) +{ + var checked = 0; + var values = new Array(); + for(var i = 0; i < elements.length; i++) + { + var selection = elements[i]; + for(var j = 0; j < selection.options.length; j++) + { + if(selection.options[j].selected) + { + checked++; + values.push(selection.options[j].value); + } + } + } + return Prado.Validation.TRequiredListValidator.IsValidList(checked, values, min, max, required); +} + +//check if the list was valid +Prado.Validation.TRequiredListValidator.IsValidList = function(checkes, values, min, max, required) +{ + var exists = true; + + if(required.length > 0) + { + //required and the values must at least be have same lengths + if(values.length < required.length) + return false; + for(var k = 0; k < required.length; k++) + exists = exists && values.contains(required[k]); + } + + return exists && checkes >= min && checkes <= max; +} diff --git a/framework/Web/Javascripts/build.php b/framework/Web/Javascripts/build.php new file mode 100644 index 00000000..6b21a76d --- /dev/null +++ b/framework/Web/Javascripts/build.php @@ -0,0 +1,144 @@ +#!/usr/bin/php +, Qiang Xue + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Revision: $ $Date: $ + * @package System.Web.UI + */ + +/** + * The compression command line + */ +define('COMPRESS_COMMAND','java -jar custom_rhino.jar -c %s > %s'); +/** + * The root directory for storing all source js files + */ +define('SOURCE_DIR',dirname(__FILE__)); +/** + * The directory for storing compressed js files + */ +define('TARGET_DIR',dirname(__FILE__).'/js'); + +/** + * list of js library files to be compressed and built + */ +$libraries = array( + //base javascript functions + 'base.js' => array( + 'prototype/prototype.js', + 'prototype/compat.js', + 'prototype/base.js', + 'extended/base.js', + 'extended/util.js', + 'prototype/string.js', + 'extended/string.js', + 'prototype/enumerable.js', + 'prototype/array.js', + 'extended/array.js', + 'prototype/hash.js', + 'prototype/range.js', + 'extended/functional.js', + 'base/prado.js', + 'base/postback.js', + 'base/focus.js', + 'base/scroll.js' + ), + //dom functions + 'dom.js' => array( + 'prototype/dom.js', + 'extended/dom.js', + 'prototype/form.js', + 'prototype/event.js', + 'extended/event.js', + 'prototype/position.js', + 'extra/getElementsBySelector.js', + 'extra/behaviour.js', + 'effects/util.js' + ), + //effects + 'effects.js' => array( + 'effects/effects.js' + ), + //controls + 'controls.js' => array( + 'effects/controls.js', + 'effects/dragdrop.js', + 'base/controls.js' + ), + //logging + 'logger.js' => array( + 'extra/logger.js', + ), + //ajax + 'ajax.js' => array( + 'prototype/ajax.js', + 'base/ajax.js', + 'base/json.js' + ), + //rico + 'rico.js' => array( + 'effects/rico.js' + ), + //javascript templating + 'template.js' => array( + 'extra/tp_template.js' + ), + //validator + 'validator.js' => array( + 'base/validation.js', + 'base/validators.js' + ), + //date picker + 'datepicker.js' => array( + 'base/datepicker.js' + ) +); + +/** + * Collect specific libraries to be built from command line + */ +$requestedLibs=array(); +for($i=1;$i<$argc;++$i) + $requestedLibs[]=$argv[$i].'.js'; + +/** + * loop through all target files and build them one by one + */ +foreach($libraries as $libFile => $sourceFiles) +{ + if(!empty($requestedLibs) && !in_array($libFile,$requestedLibs)) + continue; + $libFile=TARGET_DIR.'/'.$libFile; + $contents=''; + foreach($sourceFiles as $sourceFile) + { + $sourceFile=SOURCE_DIR.'/'.$sourceFile; + if(!is_file($sourceFile)) + echo "Source file not found: $sourceFile\n"; + $tempFile=$sourceFile.'.tmp'; + $command=sprintf(COMPRESS_COMMAND,$sourceFile,$tempFile); + echo "Compressing $sourceFile\n". + system($command); + $contents.=file_get_contents($tempFile); + @unlink($tempFile); + } + file_put_contents($libFile,$contents); +} + +?> \ No newline at end of file diff --git a/framework/Web/Javascripts/custom_rhino.jar b/framework/Web/Javascripts/custom_rhino.jar new file mode 100644 index 00000000..4a97cdeb Binary files /dev/null and b/framework/Web/Javascripts/custom_rhino.jar differ diff --git a/framework/Web/Javascripts/effects/controls.js b/framework/Web/Javascripts/effects/controls.js new file mode 100644 index 00000000..5212df51 --- /dev/null +++ b/framework/Web/Javascripts/effects/controls.js @@ -0,0 +1,711 @@ +// 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. + +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) && (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); + 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) + return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + 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.hide(); + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + }, + + 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 = 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 "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor +/* +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({ + okText: "ok", + 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, + ajaxOptions: {} + }, 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() { + 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); + Field.focus(this.editField); + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + }, + 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); + } + + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + this.form.appendChild(okButton); + + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + 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(); + } + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.type = "text"; + textField.name = "value"; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + 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(); + }, + 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(); + + 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); + } + } +}; +*/ \ No newline at end of file diff --git a/framework/Web/Javascripts/effects/dragdrop.js b/framework/Web/Javascripts/effects/dragdrop.js new file mode 100644 index 00000000..bfb6e8e9 --- /dev/null +++ b/framework/Web/Javascripts/effects/dragdrop.js @@ -0,0 +1,516 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Element.Class part Copyright (c) 2005 by Rick Olson +// +// See scriptaculous.js for full license. + +/*--------------------------------------------------------------------------*/ + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==element }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + isContained: function(element, drop) { + var parentNode = element.parentNode; + return drop._containers.detect(function(c) { return parentNode == c }); + }, + + isAffected: function(pX, pY, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.Class.has_any(element, drop.accept))) && + Position.within(drop.element, pX, pY) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.Class.remove(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(this.last_active) this.deactivate(this.last_active); + if(drop.hoverclass) + Element.Class.add(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(event, element) { + if(!this.drops.length) return; + var pX = Event.pointerX(event); + var pY = Event.pointerY(event); + Position.prepare(); + + var i = this.drops.length-1; do { + var drop = this.drops[i]; + if(this.isAffected(pX, pY, element, drop)) { + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + if(drop.greedy) { + this.activate(drop); + return; + } + } + } while (i--); + + if(this.last_active) this.deactivate(this.last_active); + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active)) + if (this.last_active.onDrop) + this.last_active.onDrop(element, this.last_active.element, event); + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + observers: [], + addObserver: function(observer) { + this.observers.push(observer); + }, + removeObserver: function(element) { // element instead of obsever fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + }, + notify: function(eventName, draggable) { // 'onStart', 'onEnd' + this.observers.invoke(eventName, draggable); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable.prototype = { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ + handle: false, + starteffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); + }, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur}); + }, + endeffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); + }, + zindex: 1000, + revert: false + }, arguments[1] || {}); + + if(options.handle && (typeof options.handle == 'string')) + this.handle = Element.Class.childrenWith(this.element, options.handle)[0]; + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + Element.makePositioned(this.element); // fix IE + + this.offsetX = 0; + this.offsetY = 0; + this.originalLeft = this.currentLeft(); + this.originalTop = this.currentTop(); + this.originalX = this.element.offsetLeft; + this.originalY = this.element.offsetTop; + + this.options = options; + + this.active = false; + this.dragging = false; + + this.eventMouseDown = this.startDrag.bindAsEventListener(this); + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.update.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + this.registerEvents(); + }, + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + this.unregisterEvents(); + }, + registerEvents: function() { + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + }, + unregisterEvents: function() { + //if(!this.active) return; + //Event.stopObserving(document, "mouseup", this.eventMouseUp); + //Event.stopObserving(document, "mousemove", this.eventMouseMove); + //Event.stopObserving(document, "keypress", this.eventKeypress); + }, + currentLeft: function() { + return parseInt(this.element.style.left || '0'); + }, + currentTop: function() { + return parseInt(this.element.style.top || '0') + }, + startDrag: function(event) { + if(Event.isLeftClick(event)) { + + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + // this.registerEvents(); + this.active = true; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.element); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + Event.stop(event); + } + }, + finishDrag: function(event, success) { + // this.unregisterEvents(); + + this.active = false; + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + this.currentTop()-this.originalTop, + this.currentLeft()-this.originalLeft); + } else { + this.originalLeft = this.currentLeft(); + this.originalTop = this.currentTop(); + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + + Droppables.reset(); + }, + keyPress: function(event) { + if(this.active) { + if(event.keyCode==Event.KEY_ESC) { + this.finishDrag(event, false); + Event.stop(event); + } + } + }, + endDrag: function(event) { + if(this.active && this.dragging) { + this.finishDrag(event, true); + Event.stop(event); + } + this.active = false; + this.dragging = false; + }, + draw: function(event) { + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.element); + offsets[0] -= this.currentLeft(); + offsets[1] -= this.currentTop(); + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = (pointer[0] - offsets[0] - this.offsetX) + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = (pointer[1] - offsets[1] - this.offsetY) + "px"; + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + update: function(event) { + if(this.active) { + if(!this.dragging) { + var style = this.element.style; + this.dragging = true; + + if(Element.getStyle(this.element,'position')=='') + style.position = "relative"; + + if(this.options.zindex) { + this.options.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + Draggables.notify('onStart', this); + if(this.options.starteffect) this.options.starteffect(this.element); + } + + Droppables.show(event, this.element); + this.draw(event); + if(this.options.change) this.options.change(this); + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + } + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + sortables: new Array(), + options: function(element){ + element = $(element); + return this.sortables.detect(function(s) { return s.element == element }); + }, + destroy: function(element){ + element = $(element); + this.sortables.findAll(function(s) { return s.element == element }).each(function(s){ + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + }); + this.sortables = this.sortables.reject(function(s) { return s.element == element }); + }, + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, // fixme: unimplemented + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + hoverclass: null, + ghosting: false, + format: null, + onChange: function() {}, + onUpdate: function() {} + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass, + onHover: Sortable.onHover, + greedy: !options.dropOnEmpty + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // make it so + + // drop on empty handling + if(options.dropOnEmpty) { + Droppables.add(element, + {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false}); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.Class.childrenWith(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + options.droppables.push(e); + }); + + // keep reference + this.sortables.push(options); + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + if(!element.hasChildNodes()) return null; + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName==options.tag.toUpperCase() && + (!options.only || (Element.Class.has(e, options.only)))) + elements.push(e); + if(options.tree) { + var grandchildren = this.findElements(e, options); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : null); + }, + + onHover: function(element, dropon, overlap) { + if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon) { + if(element.parentNode!=dropon) { + dropon.appendChild(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.Class.add(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.top = offsets[1] + 'px'; + if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + Sortable._marker.style.left = offsets[0] + 'px'; + Element.show(Sortable._marker); + }, + + serialize: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format || /^[^_]*_(.*)$/ + }, arguments[1] || {}); + return $(this.findElements(element, options) || []).collect( function(item) { + return (encodeURIComponent(options.name) + "[]=" + + encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : '')); + }).join("&"); + } +} \ No newline at end of file diff --git a/framework/Web/Javascripts/effects/effects.js b/framework/Web/Javascripts/effects/effects.js new file mode 100644 index 00000000..62aa9a3e --- /dev/null +++ b/framework/Web/Javascripts/effects/effects.js @@ -0,0 +1,811 @@ +// 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. + +var Effect = { + tagifyText: function(element) { + 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 speed = options.speed; + var delay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: delay + index * speed })); + }); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = function(pos) { + return pos; +} +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.Queue = { + effects: [], + interval: null, + add: function(effect) { + var timestamp = new Date().getTime(); + + switch(effect.options.queue) { + 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; + 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.Base = function() {}; +Effect.Base.prototype = { + position: null, + setOptions: function(options) { + this.options = Object.extend({ + 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' + }, options || {}); + }, + start: function(options) { + this.setOptions(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.Queue.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.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.Queue.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); + } +} + +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.hasLayout)) + this.element.style.zoom = 1; + var options = Object.extend({ + from: Element.getOpacity(this.element) || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + Element.setOpacity(this.element, position); + } +}); + +Effect.MoveBy = Class.create(); +Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { + initialize: function(element, toTop, toLeft) { + this.element = $(element); + this.toTop = toTop; + this.toLeft = toLeft; + this.start(arguments[3]); + }, + 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) + + Element.makePositioned(this.element); + this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0'); + this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0'); + }, + update: function(position) { + var topd = this.toTop * position + this.originalTop; + var leftd = this.toLeft * position + this.originalLeft; + this.setPosition(topd, leftd); + }, + setPosition: function(topd, leftd) { + this.element.style.top = topd + "px"; + this.element.style.left = leftd + "px"; + } +}); + +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() { + var effect = this; + + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = Element.getStyle(this.element,'position'); + + effect.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + effect.originalStyle[k] = effect.element.style[k]; + }); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = Element.getStyle(this.element,'font-size') || "100%"; + ['em','px','%'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + effect.fontSize = parseFloat(fontSize); + effect.fontSizeType = fontSizeType; + } + }); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.clientHeight, this.element.clientWidth]; + if(this.options.scaleMode=='content') + 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.style.fontSize = this.fontSize*currentScale + this.fontSizeType; + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) { + var effect = this; + ['top','left','width','height','fontSize'].each( function(k) { + effect.element.style[k] = effect.originalStyle[k]; + }); + } + }, + setDimensions: function(height, width) { + var els = this.element.style; + if(this.options.scaleX) els.width = width + 'px'; + if(this.options.scaleY) els.height = 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) els.top = this.originalTop-topd + "px"; + if(this.options.scaleX) els.left = this.originalLeft-leftd + "px"; + } else { + if(this.options.scaleY) els.top = -topd + "px"; + if(this.options.scaleX) els.left = -leftd + "px"; + } + } + } +}); + +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() { + // Disable background image during the effect + this.oldBgImage = this.element.style.backgroundImage; + this.element.style.backgroundImage = "none"; + if(!this.options.endcolor) + this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff'); + if (typeof this.options.restorecolor == "undefined") + this.options.restorecolor = this.element.style.backgroundColor; + // init color calculations + this.colors_base = [ + parseInt(this.options.startcolor.slice(1,3),16), + parseInt(this.options.startcolor.slice(3,5),16), + parseInt(this.options.startcolor.slice(5),16) ]; + this.colors_delta = [ + parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0], + parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1], + parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]]; + }, + update: function(position) { + var effect = this; var colors = $R(0,2).map( function(i){ + return Math.round(effect.colors_base[i]+(effect.colors_delta[i]*position)) + }); + this.element.style.backgroundColor = "#" + + colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); + }, + finish: function() { + this.element.style.backgroundColor = this.options.restorecolor; + this.element.style.backgroundImage = this.oldBgImage; + } +}); + +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); + 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) { + var oldOpacity = Element.getInlineOpacity(element); + var options = Object.extend({ + from: Element.getOpacity(element) || 1.0, + to: 0.0, + afterFinishInternal: function(effect) + { if (effect.options.to == 0) { + Element.hide(effect.element); + Element.setInlineOpacity(effect.element, oldOpacity); + } + } + }, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + var options = Object.extend({ + from: (Element.getStyle(element, "display") == "none" ? 0.0 : Element.getOpacity(element) || 0.0), + to: 1.0, + beforeSetup: function(effect) + { Element.setOpacity(effect.element, effect.options.from); + Element.show(effect.element); } + }, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + var oldPosition = element.style.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.style.position = 'absolute'; }, + afterFinishInternal: function(effect) + { Element.hide(effect.effects[0].element); + effect.effects[0].element.style.position = oldPosition; + Element.setInlineOpacity(effect.effects[0].element, oldOpacity); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + Element.makeClipping(element); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) + { + Element.hide(effect.element); + Element.undoClipping(effect.element); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var oldHeight = element.style.height; + var elementDimensions = Element.getDimensions(element); + 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) { + Element.makeClipping(effect.element); + effect.element.style.height = "0px"; + Element.show(effect.element); + }, + afterFinishInternal: function(effect) { + Element.undoClipping(effect.element); + effect.element.style.height = oldHeight; + } + }, arguments[1] || {}) + ); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Appear(element, { + 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) { + Element.makePositioned(effect.element); + Element.makeClipping(effect.element); + }, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element); + Element.setInlineOpacity(effect.element, oldOpacity); + } + }) + } + }); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Parallel( + [ new Effect.MoveBy(element, 100, 0, { sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + Element.makePositioned(effect.effects[0].element); }, + afterFinishInternal: function(effect) { + Element.hide(effect.effects[0].element); + Element.undoPositioned(effect.effects[0].element); + effect.effects[0].element.style.left = oldLeft; + effect.effects[0].element.style.top = oldTop; + Element.setInlineOpacity(effect.effects[0].element, oldOpacity); } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldTop = element.style.top; + var oldLeft = element.style.left; + return new Effect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinishInternal: function(effect) { + new Effect.MoveBy(effect.element, 0, -20, + { duration: 0.05, afterFinishInternal: function(effect) { + Element.undoPositioned(effect.element); + effect.element.style.left = oldLeft; + effect.element.style.top = oldTop; + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + Element.cleanWhitespace(element); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.firstChild.style.bottom; + var elementDimensions = Element.getDimensions(element); + 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) { + Element.makePositioned(effect.element.firstChild); + if (window.opera) effect.element.firstChild.style.top = ""; + Element.makeClipping(effect.element); + element.style.height = '0'; + Element.show(element); + }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinishInternal: function(effect) { + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element.firstChild); + effect.element.firstChild.style.bottom = oldInnerBottom; } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + Element.cleanWhitespace(element); + var oldInnerBottom = element.firstChild.style.bottom; + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + Element.makePositioned(effect.element.firstChild); + if (window.opera) effect.element.firstChild.style.top = ""; + Element.makeClipping(effect.element); + Element.show(element); + }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element.firstChild); + effect.element.firstChild.style.bottom = oldInnerBottom; } + }, arguments[1] || {}) + ); +} + +Effect.Squish = function(element) { + // Bug in opera makes the TD containing this element expand for a instance after finish + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { + Element.makeClipping(effect.element); }, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var elementDimensions = Element.getDimensions(element); + var originalWidth = elementDimensions.width; + var originalHeight = elementDimensions.height; + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldHeight = element.style.height; + var oldWidth = element.style.width; + var oldOpacity = Element.getInlineOpacity(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.full; + + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = originalWidth; + initialMoveY = moveY = 0; + moveX = -originalWidth; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = originalHeight; + moveY = -originalHeight; + break; + case 'bottom-right': + initialMoveX = originalWidth; + initialMoveY = originalHeight; + moveX = -originalWidth; + moveY = -originalHeight; + break; + case 'center': + initialMoveX = originalWidth / 2; + initialMoveY = originalHeight / 2; + moveX = -originalWidth / 2; + moveY = -originalHeight / 2; + break; + } + + return new Effect.MoveBy(element, initialMoveY, initialMoveX, { + duration: 0.01, + beforeSetup: function(effect) { + Element.hide(effect.element); + Element.makeClipping(effect.element); + Element.makePositioned(effect.element); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), + new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.style.height = 0; + Element.show(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + var el = effect.effects[0].element; + var els = el.style; + Element.undoClipping(el); + Element.undoPositioned(el); + els.top = oldTop; + els.left = oldLeft; + els.height = oldHeight; + els.width = originalWidth; + Element.setInlineOpacity(el, oldOpacity); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldHeight = element.style.height; + var oldWidth = element.style.width; + var oldOpacity = Element.getInlineOpacity(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.none; + + var moveX, moveY; + + switch (direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = originalWidth; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = originalHeight; + break; + case 'bottom-right': + moveX = originalWidth; + moveY = originalHeight; + break; + case 'center': + moveX = originalWidth / 2; + moveY = originalHeight / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: scaleTransition, restoreAfterFinish: true}), + new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + Element.makePositioned(effect.effects[0].element); + Element.makeClipping(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + var el = effect.effects[0].element; + var els = el.style; + Element.hide(el); + Element.undoClipping(el); + Element.undoPositioned(el); + els.top = oldTop; + els.left = oldLeft; + els.height = oldHeight; + els.width = oldWidth; + Element.setInlineOpacity(el, oldOpacity); + } + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = Element.getInlineOpacity(element); + 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) { Element.setInlineOpacity(effect.element, oldOpacity); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var originalTop = element.style.top; + var originalLeft = element.style.left; + var originalWidth = element.style.width; + var originalHeight = 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) { + Element.hide(effect.element); + Element.undoClipping(effect.element); + effect.element.style.top = originalTop; + effect.element.style.left = originalLeft; + effect.element.style.width = originalWidth; + effect.element.style.height = originalHeight; + } }); + }}, arguments[1] || {})); +} diff --git a/framework/Web/Javascripts/effects/rico.js b/framework/Web/Javascripts/effects/rico.js new file mode 100644 index 00000000..4253d4b0 --- /dev/null +++ b/framework/Web/Javascripts/effects/rico.js @@ -0,0 +1,2289 @@ +/* openrico.org rico.js */ + +/** + * + * Copyright 2005 Sabre Airline Solutions + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + **/ + +// rico.js -------------------- + +var Rico = { + Version: '1.1-beta' +} + +Rico.ArrayExtensions = new Array(); + +if (Object.prototype.extend) { + // in prototype.js... + Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Object.prototype.extend; +} + +if (Array.prototype.push) { + // in prototype.js... + Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.push; +} + +if (!Array.prototype.remove) { + Array.prototype.remove = function(dx) { + if( isNaN(dx) || dx > this.length ) + return false; + for( var i=0,n=0; i 1 ) + queryString = this._createQueryString(arguments, 1); + + new Ajax.Request(requestURL, this._requestOptions(queryString)); + }, + + sendRequestWithData: function(requestName, xmlDocument) { + var requestURL = this.requestURLS[requestName]; + if ( requestURL == null ) + return; + + var queryString = ""; + if ( arguments.length > 2 ) + queryString = this._createQueryString(arguments, 2); + + new Ajax.Request(requestURL + "?" + queryString, this._requestOptions(null,xmlDocument)); + }, + + sendRequestAndUpdate: function(requestName,container,options) { + var requestURL = this.requestURLS[requestName]; + if ( requestURL == null ) + return; + + var queryString = ""; + if ( arguments.length > 3 ) + queryString = this._createQueryString(arguments, 3); + + var updaterOptions = this._requestOptions(queryString); + updaterOptions.onComplete = null; + updaterOptions.extend(options); + + new Ajax.Updater(container, requestURL, updaterOptions); + }, + + sendRequestWithDataAndUpdate: function(requestName,xmlDocument,container,options) { + var requestURL = this.requestURLS[requestName]; + if ( requestURL == null ) + return; + + var queryString = ""; + if ( arguments.length > 4 ) + queryString = this._createQueryString(arguments, 4); + + + var updaterOptions = this._requestOptions(queryString,xmlDocument); + updaterOptions.onComplete = null; + updaterOptions.extend(options); + + new Ajax.Updater(container, requestURL + "?" + queryString, updaterOptions); + }, + + // Private -- not part of intended engine API -------------------------------------------------------------------- + + _requestOptions: function(queryString,xmlDoc) { + var self = this; + + var requestHeaders = ['X-Rico-Version', Rico.Version ]; + var sendMethod = "post"; + if ( arguments[1] ) + requestHeaders.push( 'Content-type', 'text/xml' ); + else + sendMethod = "get"; + + return { requestHeaders: requestHeaders, + parameters: queryString, + postBody: arguments[1] ? xmlDoc : null, + method: sendMethod, + onComplete: self._onRequestComplete.bind(self) }; + }, + + _createQueryString: function( theArgs, offset ) { + var queryString = ""; + for ( var i = offset ; i < theArgs.length ; i++ ) { + if ( i != offset ) + queryString += "&"; + + var anArg = theArgs[i]; + + if ( anArg.name != undefined && anArg.value != undefined ) { + queryString += anArg.name + "=" + escape(anArg.value); + } + else { + var ePos = anArg.indexOf('='); + var argName = anArg.substring( 0, ePos ); + var argValue = anArg.substring( ePos + 1 ); + queryString += argName + "=" + escape(argValue); + } + } + + return queryString; + }, + + _onRequestComplete : function(request) { + + //!!TODO: error handling infrastructure?? + if (request.status != 200) + return; + + var response = request.responseXML.getElementsByTagName("ajax-response"); + if (response == null || response.length != 1) + return; + this._processAjaxResponse( response[0].childNodes ); + }, + + _processAjaxResponse: function( xmlResponseElements ) { + for ( var i = 0 ; i < xmlResponseElements.length ; i++ ) { + var responseElement = xmlResponseElements[i]; + + // only process nodes of type element..... + if ( responseElement.nodeType != 1 ) + continue; + + var responseType = responseElement.getAttribute("type"); + var responseId = responseElement.getAttribute("id"); + + if ( responseType == "object" ) + this._processAjaxObjectUpdate( this.ajaxObjects[ responseId ], responseElement ); + else if ( responseType == "element" ) + this._processAjaxElementUpdate( this.ajaxElements[ responseId ], responseElement ); + else + alert('unrecognized AjaxResponse type : ' + responseType ); + } + }, + + _processAjaxObjectUpdate: function( ajaxObject, responseElement ) { + ajaxObject.ajaxUpdate( responseElement ); + }, + + _processAjaxElementUpdate: function( ajaxElement, responseElement ) { + if ( responseElement.xml != undefined ) + this._processAjaxElementUpdateIE( ajaxElement, responseElement ); + else + this._processAjaxElementUpdateMozilla( ajaxElement, responseElement ); + }, + + _processAjaxElementUpdateIE: function( ajaxElement, responseElement ) { + var newHTML = ""; + for ( var i = 0 ; i < responseElement.childNodes.length ; i++ ) + newHTML += responseElement.childNodes[i].xml; + + ajaxElement.innerHTML = newHTML; + }, + + _processAjaxElementUpdateMozilla: function( ajaxElement, responseElement ) { + var xmlSerializer = new XMLSerializer(); + var newHTML = ""; + for ( var i = 0 ; i < responseElement.childNodes.length ; i++ ) + newHTML += xmlSerializer.serializeToString(responseElement.childNodes[i]); + + ajaxElement.innerHTML = newHTML; + } +} + +var ajaxEngine = new Rico.AjaxEngine(); + +// ricoColor.js -------------------- + +Rico.Color = Class.create(); + +Rico.Color.prototype = { + + initialize: function(red, green, blue) { + this.rgb = { r: red, g : green, b : blue }; + }, + + setRed: function(r) { + this.rgb.r = r; + }, + + setGreen: function(g) { + this.rgb.g = g; + }, + + setBlue: function(b) { + this.rgb.b = b; + }, + + setHue: function(h) { + + // get an HSB model, and set the new hue... + var hsb = this.asHSB(); + hsb.h = h; + + // convert back to RGB... + this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b); + }, + + setSaturation: function(s) { + // get an HSB model, and set the new hue... + var hsb = this.asHSB(); + hsb.s = s; + + // convert back to RGB and set values... + this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b); + }, + + setBrightness: function(b) { + // get an HSB model, and set the new hue... + var hsb = this.asHSB(); + hsb.b = b; + + // convert back to RGB and set values... + this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b ); + }, + + darken: function(percent) { + var hsb = this.asHSB(); + this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0)); + }, + + brighten: function(percent) { + var hsb = this.asHSB(); + this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1)); + }, + + blend: function(other) { + this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2); + this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2); + this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2); + }, + + isBright: function() { + var hsb = this.asHSB(); + return this.asHSB().b > 0.5; + }, + + isDark: function() { + return ! this.isBright(); + }, + + asRGB: function() { + return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")"; + }, + + asHex: function() { + return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart(); + }, + + asHSB: function() { + return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b); + }, + + toString: function() { + return this.asHex(); + } + +}; + +Rico.Color.createFromHex = function(hexCode) { + + if ( hexCode.indexOf('#') == 0 ) + hexCode = hexCode.substring(1); + var red = hexCode.substring(0,2); + var green = hexCode.substring(2,4); + var blue = hexCode.substring(4,6); + return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) ); +} + +/** + * Factory method for creating a color from the background of + * an HTML element. + */ +Rico.Color.createColorFromBackground = function(elem) { + + var actualColor = RicoUtil.getElementsComputedStyle($(elem), "backgroundColor", "background-color"); + + if ( actualColor == "transparent" && elem.parent ) + return Rico.Color.createColorFromBackground(elem.parent); + + if ( actualColor == null ) + return new Rico.Color(255,255,255); + + if ( actualColor.indexOf("rgb(") == 0 ) { + var colors = actualColor.substring(4, actualColor.length - 1 ); + var colorArray = colors.split(","); + return new Rico.Color( parseInt( colorArray[0] ), + parseInt( colorArray[1] ), + parseInt( colorArray[2] ) ); + + } + else if ( actualColor.indexOf("#") == 0 ) { + var redPart = parseInt(actualColor.substring(1,3), 16); + var greenPart = parseInt(actualColor.substring(3,5), 16); + var bluePart = parseInt(actualColor.substring(5), 16); + return new Rico.Color( redPart, greenPart, bluePart ); + } + else + return new Rico.Color(255,255,255); +} + +Rico.Color.HSBtoRGB = function(hue, saturation, brightness) { + + var red = 0; + var green = 0; + var blue = 0; + + if (saturation == 0) { + red = parseInt(brightness * 255.0 + 0.5); + green = red; + blue = red; + } + else { + var h = (hue - Math.floor(hue)) * 6.0; + var f = h - Math.floor(h); + var p = brightness * (1.0 - saturation); + var q = brightness * (1.0 - saturation * f); + var t = brightness * (1.0 - (saturation * (1.0 - f))); + + switch (parseInt(h)) { + case 0: + red = (brightness * 255.0 + 0.5); + green = (t * 255.0 + 0.5); + blue = (p * 255.0 + 0.5); + break; + case 1: + red = (q * 255.0 + 0.5); + green = (brightness * 255.0 + 0.5); + blue = (p * 255.0 + 0.5); + break; + case 2: + red = (p * 255.0 + 0.5); + green = (brightness * 255.0 + 0.5); + blue = (t * 255.0 + 0.5); + break; + case 3: + red = (p * 255.0 + 0.5); + green = (q * 255.0 + 0.5); + blue = (brightness * 255.0 + 0.5); + break; + case 4: + red = (t * 255.0 + 0.5); + green = (p * 255.0 + 0.5); + blue = (brightness * 255.0 + 0.5); + break; + case 5: + red = (brightness * 255.0 + 0.5); + green = (p * 255.0 + 0.5); + blue = (q * 255.0 + 0.5); + break; + } + } + + return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) }; +} + +Rico.Color.RGBtoHSB = function(r, g, b) { + + var hue; + var saturaton; + var brightness; + + var cmax = (r > g) ? r : g; + if (b > cmax) + cmax = b; + + var cmin = (r < g) ? r : g; + if (b < cmin) + cmin = b; + + brightness = cmax / 255.0; + if (cmax != 0) + saturation = (cmax - cmin)/cmax; + else + saturation = 0; + + if (saturation == 0) + hue = 0; + else { + var redc = (cmax - r)/(cmax - cmin); + var greenc = (cmax - g)/(cmax - cmin); + var bluec = (cmax - b)/(cmax - cmin); + + if (r == cmax) + hue = bluec - greenc; + else if (g == cmax) + hue = 2.0 + redc - bluec; + else + hue = 4.0 + greenc - redc; + + hue = hue / 6.0; + if (hue < 0) + hue = hue + 1.0; + } + + return { h : hue, s : saturation, b : brightness }; +} + +// ricoCorner.js -------------------- + + +Rico.Corner = { + + round: function(e, options) { + var e = $(e); + this._setOptions(options); + + var color = this.options.color; + if ( this.options.color == "fromElement" ) + color = this._background(e); + + var bgColor = this.options.bgColor; + if ( this.options.bgColor == "fromParent" ) + bgColor = this._background(e.offsetParent); + + this._roundCornersImpl(e, color, bgColor); + }, + + _roundCornersImpl: function(e, color, bgColor) { + if(this.options.border) + this._renderBorder(e,bgColor); + if(this._isTopRounded()) + this._roundTopCorners(e,color,bgColor); + if(this._isBottomRounded()) + this._roundBottomCorners(e,color,bgColor); + }, + + _renderBorder: function(el,bgColor) { + var borderValue = "1px solid " + this._borderColor(bgColor); + var borderL = "border-left: " + borderValue; + var borderR = "border-right: " + borderValue; + var style = "style='" + borderL + ";" + borderR + "'"; + el.innerHTML = "

    " + el.innerHTML + "
    " + }, + + _roundTopCorners: function(el, color, bgColor) { + var corner = this._createCorner(bgColor); + for(var i=0 ; i < this.options.numSlices ; i++ ) + corner.appendChild(this._createCornerSlice(color,bgColor,i,"top")); + el.style.paddingTop = 0; + el.insertBefore(corner,el.firstChild); + }, + + _roundBottomCorners: function(el, color, bgColor) { + var corner = this._createCorner(bgColor); + for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- ) + corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom")); + el.style.paddingBottom = 0; + el.appendChild(corner); + }, + + _createCorner: function(bgColor) { + var corner = document.createElement("div"); + corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor); + return corner; + }, + + _createCornerSlice: function(color,bgColor, n, position) { + var slice = document.createElement("span"); + + var inStyle = slice.style; + inStyle.backgroundColor = color; + inStyle.display = "block"; + inStyle.height = "1px"; + inStyle.overflow = "hidden"; + inStyle.fontSize = "1px"; + + var borderColor = this._borderColor(color,bgColor); + if ( this.options.border && n == 0 ) { + inStyle.borderTopStyle = "solid"; + inStyle.borderTopWidth = "1px"; + inStyle.borderLeftWidth = "0px"; + inStyle.borderRightWidth = "0px"; + inStyle.borderBottomWidth = "0px"; + inStyle.height = "0px"; // assumes css compliant box model + inStyle.borderColor = borderColor; + } + else if(borderColor) { + inStyle.borderColor = borderColor; + inStyle.borderStyle = "solid"; + inStyle.borderWidth = "0px 1px"; + } + + if ( !this.options.compact && (n == (this.options.numSlices-1)) ) + inStyle.height = "2px"; + + this._setMargin(slice, n, position); + this._setBorder(slice, n, position); + + return slice; + }, + + _setOptions: function(options) { + this.options = { + corners : "all", + color : "fromElement", + bgColor : "fromParent", + blend : true, + border : false, + compact : false + }.extend(options || {}); + + this.options.numSlices = this.options.compact ? 2 : 4; + if ( this._isTransparent() ) + this.options.blend = false; + }, + + _whichSideTop: function() { + if ( this._hasString(this.options.corners, "all", "top") ) + return ""; + + if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 ) + return ""; + + if (this.options.corners.indexOf("tl") >= 0) + return "left"; + else if (this.options.corners.indexOf("tr") >= 0) + return "right"; + return ""; + }, + + _whichSideBottom: function() { + if ( this._hasString(this.options.corners, "all", "bottom") ) + return ""; + + if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 ) + return ""; + + if(this.options.corners.indexOf("bl") >=0) + return "left"; + else if(this.options.corners.indexOf("br")>=0) + return "right"; + return ""; + }, + + _borderColor : function(color,bgColor) { + if ( color == "transparent" ) + return bgColor; + else if ( this.options.border ) + return this.options.border; + else if ( this.options.blend ) + return this._blend( bgColor, color ); + else + return ""; + }, + + + _setMargin: function(el, n, corners) { + var marginSize = this._marginSize(n); + var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom(); + + if ( whichSide == "left" ) { + el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px"; + } + else if ( whichSide == "right" ) { + el.style.marginRight = marginSize + "px"; el.style.marginLeft = "0px"; + } + else { + el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px"; + } + }, + + _setBorder: function(el,n,corners) { + var borderSize = this._borderSize(n); + var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom(); + + if ( whichSide == "left" ) { + el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px"; + } + else if ( whichSide == "right" ) { + el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth = "0px"; + } + else { + el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px"; + } + }, + + _marginSize: function(n) { + if ( this._isTransparent() ) + return 0; + + var marginSizes = [ 5, 3, 2, 1 ]; + var blendedMarginSizes = [ 3, 2, 1, 0 ]; + var compactMarginSizes = [ 2, 1 ]; + var smBlendedMarginSizes = [ 1, 0 ]; + + if ( this.options.compact && this.options.blend ) + return smBlendedMarginSizes[n]; + else if ( this.options.compact ) + return compactMarginSizes[n]; + else if ( this.options.blend ) + return blendedMarginSizes[n]; + else + return marginSizes[n]; + }, + + _borderSize: function(n) { + var transparentBorderSizes = [ 5, 3, 2, 1 ]; + var blendedBorderSizes = [ 2, 1, 1, 1 ]; + var compactBorderSizes = [ 1, 0 ]; + var actualBorderSizes = [ 0, 2, 0, 0 ]; + + if ( this.options.compact && (this.options.blend || this._isTransparent()) ) + return 1; + else if ( this.options.compact ) + return compactBorderSizes[n]; + else if ( this.options.blend ) + return blendedBorderSizes[n]; + else if ( this.options.border ) + return actualBorderSizes[n]; + else if ( this._isTransparent() ) + return transparentBorderSizes[n]; + return 0; + }, + + _hasString: function(str) { for(var i=1 ; i= 0) return true; return false; }, + _blend: function(c1, c2) { var cc1 = Rico.Color.createFromHex(c1); cc1.blend(Rico.Color.createFromHex(c2)); return cc1; }, + _background: function(el) { try { return Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } }, + _isTransparent: function() { return this.options.color == "transparent"; }, + _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); }, + _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); }, + _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; } +} + +// ricoDragAndDrop.js -------------------- + +Rico.DragAndDrop = Class.create(); + +Rico.DragAndDrop.prototype = { + + initialize: function() { + this.dropZones = new Array(); + this.draggables = new Array(); + this.currentDragObjects = new Array(); + this.dragElement = null; + this.lastSelectedDraggable = null; + this.currentDragObjectVisible = false; + this.interestedInMotionEvents = false; + }, + + registerDropZone: function(aDropZone) { + this.dropZones[ this.dropZones.length ] = aDropZone; + }, + + deregisterDropZone: function(aDropZone) { + var newDropZones = new Array(); + var j = 0; + for ( var i = 0 ; i < this.dropZones.length ; i++ ) { + if ( this.dropZones[i] != aDropZone ) + newDropZones[j++] = this.dropZones[i]; + } + + this.dropZones = newDropZones; + }, + + clearDropZones: function() { + this.dropZones = new Array(); + }, + + registerDraggable: function( aDraggable ) { + this.draggables[ this.draggables.length ] = aDraggable; + this._addMouseDownHandler( aDraggable ); + }, + + clearSelection: function() { + for ( var i = 0 ; i < this.currentDragObjects.length ; i++ ) + this.currentDragObjects[i].deselect(); + this.currentDragObjects = new Array(); + this.lastSelectedDraggable = null; + }, + + hasSelection: function() { + return this.currentDragObjects.length > 0; + }, + + setStartDragFromElement: function( e, mouseDownElement ) { + this.origPos = RicoUtil.toDocumentPosition(mouseDownElement); + this.startx = e.screenX - this.origPos.x; + this.starty = e.screenY - this.origPos.y; + //this.startComponentX = e.layerX ? e.layerX : e.offsetX; + //this.startComponentY = e.layerY ? e.layerY : e.offsetY; + //this.adjustedForDraggableSize = false; + + this.interestedInMotionEvents = this.hasSelection(); + this._terminateEvent(e); + }, + + updateSelection: function( draggable, extendSelection ) { + if ( ! extendSelection ) + this.clearSelection(); + + if ( draggable.isSelected() ) { + this.currentDragObjects.removeItem(draggable); + draggable.deselect(); + if ( draggable == this.lastSelectedDraggable ) + this.lastSelectedDraggable = null; + } + else { + this.currentDragObjects[ this.currentDragObjects.length ] = draggable; + draggable.select(); + this.lastSelectedDraggable = draggable; + } + }, + + _mouseDownHandler: function(e) { + if ( arguments.length == 0 ) + e = event; + + // if not button 1 ignore it... + var nsEvent = e.which != undefined; + if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1)) + return; + + var eventTarget = e.target ? e.target : e.srcElement; + var draggableObject = eventTarget.draggable; + + this.updateSelection( draggableObject, e.ctrlKey ); + + // clear the drop zones postion cache... + if ( this.hasSelection() ) + for ( var i = 0 ; i < this.dropZones.length ; i++ ) + this.dropZones[i].clearPositionCache(); + + this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() ); + }, + + + _mouseMoveHandler: function(e) { + var nsEvent = e.which != undefined; + if ( !this.interestedInMotionEvents ) { + this._terminateEvent(e); + return; + } + + if ( ! this.hasSelection() ) + return; + + if ( ! this.currentDragObjectVisible ) + this._startDrag(e); + + if ( !this.activatedDropZones ) + this._activateRegisteredDropZones(); + + //if ( !this.adjustedForDraggableSize ) + // this._adjustForDraggableSize(e); + + this._updateDraggableLocation(e); + this._updateDropZonesHover(e); + + this._terminateEvent(e); + }, + + _makeDraggableObjectVisible: function(e) + { + if ( !this.hasSelection() ) + return; + + var dragElement; + if ( this.currentDragObjects.length > 1 ) + dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects); + else + dragElement = this.currentDragObjects[0].getSingleObjectDragGUI(); + + // go ahead and absolute position it... + if ( RicoUtil.getElementsComputedStyle(dragElement, "position") != "absolute" ) + dragElement.style.position = "absolute"; + + // need to parent him into the document... + if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 ) + document.body.appendChild(dragElement); + + this.dragElement = dragElement; + this._updateDraggableLocation(e); + + this.currentDragObjectVisible = true; + }, + + /** + _adjustForDraggableSize: function(e) { + var dragElementWidth = this.dragElement.offsetWidth; + var dragElementHeight = this.dragElement.offsetHeight; + if ( this.startComponentX > dragElementWidth ) + this.startx -= this.startComponentX - dragElementWidth + 2; + if ( e.offsetY ) { + if ( this.startComponentY > dragElementHeight ) + this.starty -= this.startComponentY - dragElementHeight + 2; + } + this.adjustedForDraggableSize = true; + }, + **/ + + _updateDraggableLocation: function(e) { + var dragObjectStyle = this.dragElement.style; + dragObjectStyle.left = (e.screenX - this.startx) + "px"; + dragObjectStyle.top = (e.screenY - this.starty) + "px"; + }, + + _updateDropZonesHover: function(e) { + var n = this.dropZones.length; + for ( var i = 0 ; i < n ; i++ ) { + if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) ) + this.dropZones[i].hideHover(); + } + + for ( var i = 0 ; i < n ; i++ ) { + if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) { + if ( this.dropZones[i].canAccept(this.currentDragObjects) ) + this.dropZones[i].showHover(); + } + } + }, + + _startDrag: function(e) { + for ( var i = 0 ; i < this.currentDragObjects.length ; i++ ) + this.currentDragObjects[i].startDrag(); + + this._makeDraggableObjectVisible(e); + }, + + _mouseUpHandler: function(e) { + if ( ! this.hasSelection() ) + return; + + var nsEvent = e.which != undefined; + if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1)) + return; + + this.interestedInMotionEvents = false; + + if ( this.dragElement == null ) { + this._terminateEvent(e); + return; + } + + if ( this._placeDraggableInDropZone(e) ) + this._completeDropOperation(e); + else { + this._terminateEvent(e); + new Effect.Position( this.dragElement, + this.origPos.x, + this.origPos.y, + 200, + 20, + { complete : this._doCancelDragProcessing.bind(this) } ); + } + }, + + _completeDropOperation: function(e) { + if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) { + if ( this.dragElement.parentNode != null ) + this.dragElement.parentNode.removeChild(this.dragElement); + } + + this._deactivateRegisteredDropZones(); + this._endDrag(); + this.clearSelection(); + this.dragElement = null; + this.currentDragObjectVisible = false; + this._terminateEvent(e); + }, + + _doCancelDragProcessing: function() { + this._cancelDrag(); + + if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) { + if ( this.dragElement.parentNode != null ) { + this.dragElement.parentNode.removeChild(this.dragElement); + } + } + + this._deactivateRegisteredDropZones(); + this.dragElement = null; + this.currentDragObjectVisible = false; + }, + + _placeDraggableInDropZone: function(e) { + var foundDropZone = false; + var n = this.dropZones.length; + for ( var i = 0 ; i < n ; i++ ) { + if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) { + if ( this.dropZones[i].canAccept(this.currentDragObjects) ) { + this.dropZones[i].hideHover(); + this.dropZones[i].accept(this.currentDragObjects); + foundDropZone = true; + break; + } + } + } + + return foundDropZone; + }, + + _cancelDrag: function() { + for ( var i = 0 ; i < this.currentDragObjects.length ; i++ ) + this.currentDragObjects[i].cancelDrag(); + }, + + _endDrag: function() { + for ( var i = 0 ; i < this.currentDragObjects.length ; i++ ) + this.currentDragObjects[i].endDrag(); + }, + + _mousePointInDropZone: function( e, dropZone ) { + + var absoluteRect = dropZone.getAbsoluteRect(); + + return e.clientX > absoluteRect.left && + e.clientX < absoluteRect.right && + e.clientY > absoluteRect.top && + e.clientY < absoluteRect.bottom; + }, + + _addMouseDownHandler: function( aDraggable ) + { + var htmlElement = aDraggable.getMouseDownHTMLElement(); + if ( htmlElement != null ) { + htmlElement.draggable = aDraggable; + this._addMouseDownEvent( htmlElement ); + } + }, + + _activateRegisteredDropZones: function() { + var n = this.dropZones.length; + for ( var i = 0 ; i < n ; i++ ) { + var dropZone = this.dropZones[i]; + if ( dropZone.canAccept(this.currentDragObjects) ) + dropZone.activate(); + } + + this.activatedDropZones = true; + }, + + _deactivateRegisteredDropZones: function() { + var n = this.dropZones.length; + for ( var i = 0 ; i < n ; i++ ) + this.dropZones[i].deactivate(); + this.activatedDropZones = false; + }, + + _addMouseDownEvent: function( htmlElement ) { + if ( typeof document.implementation != "undefined" && + document.implementation.hasFeature("HTML", "1.0") && + document.implementation.hasFeature("Events", "2.0") && + document.implementation.hasFeature("CSS", "2.0") ) { + htmlElement.addEventListener("mousedown", this._mouseDownHandler.bindAsEventListener(this), false); + } + else { + htmlElement.attachEvent( "onmousedown", this._mouseDownHandler.bindAsEventListener(this) ); + } + }, + + _terminateEvent: function(e) { + if ( e.stopPropagation != undefined ) + e.stopPropagation(); + else if ( e.cancelBubble != undefined ) + e.cancelBubble = true; + + if ( e.preventDefault != undefined ) + e.preventDefault(); + else + { + e.returnValue = false; + } + }, + + initializeEventHandlers: function() { + if ( typeof document.implementation != "undefined" && + document.implementation.hasFeature("HTML", "1.0") && + document.implementation.hasFeature("Events", "2.0") && + document.implementation.hasFeature("CSS", "2.0") ) { + document.addEventListener("mouseup", this._mouseUpHandler.bindAsEventListener(this), false); + document.addEventListener("mousemove", this._mouseMoveHandler.bindAsEventListener(this), false); + } + else { + document.attachEvent( "onmouseup", this._mouseUpHandler.bindAsEventListener(this) ); + document.attachEvent( "onmousemove", this._mouseMoveHandler.bindAsEventListener(this) ); + } + } +}; + +var dndMgr = new Rico.DragAndDrop(); +dndMgr.initializeEventHandlers(); + +// ricoDraggable.js -------------------- + +Rico.Draggable = Class.create(); + +Rico.Draggable.prototype = { + + initialize: function( type, htmlElement ) { + this.type = type; + this.htmlElement = $(htmlElement); + this.selected = false; + }, + + /** + * Returns the HTML element that should have a mouse down event + * added to it in order to initiate a drag operation + * + **/ + getMouseDownHTMLElement: function() { + return this.htmlElement; + }, + + select: function() { + this.selected = true; + + if ( this.showingSelected ) + return; + + var htmlElement = this.getMouseDownHTMLElement(); + + var color = Rico.Color.createColorFromBackground(htmlElement); + color.isBright() ? color.darken(0.033) : color.brighten(0.033); + + this.saveBackground = RicoUtil.getElementsComputedStyle(htmlElement, "backgroundColor", "background-color"); + htmlElement.style.backgroundColor = color.asHex(); + this.showingSelected = true; + }, + + deselect: function() { + this.selected = false; + if ( !this.showingSelected ) + return; + + var htmlElement = this.getMouseDownHTMLElement(); + + htmlElement.style.backgroundColor = this.saveBackground; + this.showingSelected = false; + }, + + isSelected: function() { + return this.selected; + }, + + startDrag: function() { + }, + + cancelDrag: function() { + }, + + endDrag: function() { + }, + + getSingleObjectDragGUI: function() { + return this.htmlElement; + }, + + getMultiObjectDragGUI: function( draggables ) { + return this.htmlElement; + }, + + getDroppedGUI: function() { + return this.htmlElement; + }, + + toString: function() { + return this.type + ":" + this.htmlElement + ":"; + } + +} + +// ricoDropzone.js -------------------- + +Rico.Dropzone = Class.create(); + +Rico.Dropzone.prototype = { + + initialize: function( htmlElement ) { + this.htmlElement = $(htmlElement); + this.absoluteRect = null; + }, + + getHTMLElement: function() { + return this.htmlElement; + }, + + clearPositionCache: function() { + this.absoluteRect = null; + }, + + getAbsoluteRect: function() { + if ( this.absoluteRect == null ) { + var htmlElement = this.getHTMLElement(); + var pos = RicoUtil.toViewportPosition(htmlElement); + + this.absoluteRect = { + top: pos.y, + left: pos.x, + bottom: pos.y + htmlElement.offsetHeight, + right: pos.x + htmlElement.offsetWidth + }; + } + return this.absoluteRect; + }, + + activate: function() { + var htmlElement = this.getHTMLElement(); + if (htmlElement == null || this.showingActive) + return; + + this.showingActive = true; + this.saveBackgroundColor = htmlElement.style.backgroundColor; + + var fallbackColor = "#ffea84"; + var currentColor = Rico.Color.createColorFromBackground(htmlElement); + if ( currentColor == null ) + htmlElement.style.backgroundColor = fallbackColor; + else { + currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2); + htmlElement.style.backgroundColor = currentColor.asHex(); + } + }, + + deactivate: function() { + var htmlElement = this.getHTMLElement(); + if (htmlElement == null || !this.showingActive) + return; + + htmlElement.style.backgroundColor = this.saveBackgroundColor; + this.showingActive = false; + this.saveBackgroundColor = null; + }, + + showHover: function() { + var htmlElement = this.getHTMLElement(); + if ( htmlElement == null || this.showingHover ) + return; + + this.saveBorderWidth = htmlElement.style.borderWidth; + this.saveBorderStyle = htmlElement.style.borderStyle; + this.saveBorderColor = htmlElement.style.borderColor; + + this.showingHover = true; + htmlElement.style.borderWidth = "1px"; + htmlElement.style.borderStyle = "solid"; + //htmlElement.style.borderColor = "#ff9900"; + htmlElement.style.borderColor = "#ffff00"; + }, + + hideHover: function() { + var htmlElement = this.getHTMLElement(); + if ( htmlElement == null || !this.showingHover ) + return; + + htmlElement.style.borderWidth = this.saveBorderWidth; + htmlElement.style.borderStyle = this.saveBorderStyle; + htmlElement.style.borderColor = this.saveBorderColor; + this.showingHover = false; + }, + + canAccept: function(draggableObjects) { + return true; + }, + + accept: function(draggableObjects) { + var htmlElement = this.getHTMLElement(); + if ( htmlElement == null ) + return; + + n = draggableObjects.length; + for ( var i = 0 ; i < n ; i++ ) + { + var theGUI = draggableObjects[i].getDroppedGUI(); + if ( RicoUtil.getElementsComputedStyle( theGUI, "position" ) == "absolute" ) + { + theGUI.style.position = "static"; + theGUI.style.top = ""; + theGUI.style.top = ""; + } + htmlElement.appendChild(theGUI); + } + } +} + +// ricoEffects.js -------------------- + + +Effect.SizeAndPosition = Class.create(); +Effect.SizeAndPosition.prototype = { + + initialize: function(element, x, y, w, h, duration, steps, options) { + this.element = $(element); + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.duration = duration; + this.steps = steps; + this.options = arguments[7] || {}; + + this.sizeAndPosition(); + }, + + sizeAndPosition: function() { + if (this.isFinished()) { + if(this.options.complete) this.options.complete(this); + return; + } + + if (this.timer) + clearTimeout(this.timer); + + var stepDuration = Math.round(this.duration/this.steps) ; + + // Get original values: x,y = top left corner; w,h = width height + var currentX = this.element.offsetLeft; + var currentY = this.element.offsetTop; + var currentW = this.element.offsetWidth; + var currentH = this.element.offsetHeight; + + // If values not set, or zero, we do not modify them, and take original as final as well + this.x = (this.x) ? this.x : currentX; + this.y = (this.y) ? this.y : currentY; + this.w = (this.w) ? this.w : currentW; + this.h = (this.h) ? this.h : currentH; + + // how much do we need to modify our values for each step? + var difX = this.steps > 0 ? (this.x - currentX)/this.steps : 0; + var difY = this.steps > 0 ? (this.y - currentY)/this.steps : 0; + var difW = this.steps > 0 ? (this.w - currentW)/this.steps : 0; + var difH = this.steps > 0 ? (this.h - currentH)/this.steps : 0; + + this.moveBy(difX, difY); + this.resizeBy(difW, difH); + + this.duration -= stepDuration; + this.steps--; + + this.timer = setTimeout(this.sizeAndPosition.bind(this), stepDuration); + }, + + isFinished: function() { + return this.steps <= 0; + }, + + moveBy: function( difX, difY ) { + var currentLeft = this.element.offsetLeft; + var currentTop = this.element.offsetTop; + var intDifX = parseInt(difX); + var intDifY = parseInt(difY); + + var style = this.element.style; + if ( intDifX != 0 ) + style.left = (currentLeft + intDifX) + "px"; + if ( intDifY != 0 ) + style.top = (currentTop + intDifY) + "px"; + }, + + resizeBy: function( difW, difH ) { + var currentWidth = this.element.offsetWidth; + var currentHeight = this.element.offsetHeight; + var intDifW = parseInt(difW); + var intDifH = parseInt(difH); + + var style = this.element.style; + if ( intDifW != 0 ) + style.width = (currentWidth + intDifW) + "px"; + if ( intDifH != 0 ) + style.height = (currentHeight + intDifH) + "px"; + } +} + +Effect.Size = Class.create(); +Effect.Size.prototype = { + + initialize: function(element, w, h, duration, steps, options) { + new Effect.SizeAndPosition(element, null, null, w, h, duration, steps, options); + } +} + +Effect.Position = Class.create(); +Effect.Position.prototype = { + + initialize: function(element, x, y, duration, steps, options) { + new Effect.SizeAndPosition(element, x, y, null, null, duration, steps, options); + } +} + +Effect.Round = Class.create(); +Effect.Round.prototype = { + + initialize: function(tagName, className, options) { + var elements = document.getElementsByTagAndClassName(tagName,className); + for ( var i = 0 ; i < elements.length ; i++ ) + Rico.Corner.round( elements[i], options ); + } +}; + +Effect.FadeTo = Class.create(); +Effect.FadeTo.prototype = { + + initialize: function( element, opacity, duration, steps, options) { + this.element = $(element); + this.opacity = opacity; + this.duration = duration; + this.steps = steps; + this.options = arguments[4] || {}; + this.fadeTo(); + }, + + fadeTo: function() { + if (this.isFinished()) { + if(this.options.complete) this.options.complete(this); + return; + } + + if (this.timer) + clearTimeout(this.timer); + + var stepDuration = Math.round(this.duration/this.steps) ; + var currentOpacity = this.getElementOpacity(); + var delta = this.steps > 0 ? (this.opacity - currentOpacity)/this.steps : 0; + + this.changeOpacityBy(delta); + this.duration -= stepDuration; + this.steps--; + + this.timer = setTimeout(this.fadeTo.bind(this), stepDuration); + }, + + changeOpacityBy: function(v) { + var currentOpacity = this.getElementOpacity(); + var newOpacity = Math.max(0, Math.min(currentOpacity+v, 1)); + this.element.ricoOpacity = newOpacity; + + this.element.style.filter = "alpha(opacity:"+Math.round(newOpacity*100)+")"; + this.element.style.opacity = newOpacity; /*//*/; + }, + + isFinished: function() { + return this.steps <= 0; + }, + + getElementOpacity: function() { + if ( this.element.ricoOpacity == undefined ) { + var opacity; + if ( this.element.currentStyle ) { + opacity = this.element.currentStyle.opacity; + } + else if ( document.defaultView.getComputedStyle != undefined ) { + var computedStyle = document.defaultView.getComputedStyle; + opacity = computedStyle(this.element, null).getPropertyValue('opacity'); + } + + this.element.ricoOpacity = opacity != undefined ? opacity : 1.0; + } + + return parseFloat(this.element.ricoOpacity); + } +} + +Effect.AccordionSize = Class.create(); + +Effect.AccordionSize.prototype = { + + initialize: function(e1, e2, start, end, duration, steps, options) { + this.e1 = $(e1); + this.e2 = $(e2); + this.start = start; + this.end = end; + this.duration = duration; + this.steps = steps; + this.options = arguments[6] || {}; + + this.accordionSize(); + }, + + accordionSize: function() { + + if (this.isFinished()) { + // just in case there are round errors or such... + this.e1.style.height = this.start + "px"; + this.e2.style.height = this.end + "px"; + + if(this.options.complete) + this.options.complete(this); + return; + } + + if (this.timer) + clearTimeout(this.timer); + + var stepDuration = Math.round(this.duration/this.steps) ; + + var diff = this.steps > 0 ? (parseInt(this.e1.offsetHeight) - this.start)/this.steps : 0; + this.resizeBy(diff); + + this.duration -= stepDuration; + this.steps--; + + this.timer = setTimeout(this.accordionSize.bind(this), stepDuration); + }, + + isFinished: function() { + return this.steps <= 0; + }, + + resizeBy: function(diff) { + var h1Height = this.e1.offsetHeight; + var h2Height = this.e2.offsetHeight; + var intDiff = parseInt(diff); + if ( diff != 0 ) { + this.e1.style.height = (h1Height - intDiff) + "px"; + this.e2.style.height = (h2Height + intDiff) + "px"; + } + } + +}; + + +// ricoLiveGrid.js -------------------- + + +// Rico.LiveGridMetaData ----------------------------------------------------- + +Rico.LiveGridMetaData = Class.create(); + +Rico.LiveGridMetaData.prototype = { + + initialize: function( pageSize, totalRows, options ) { + this.pageSize = pageSize; + this.totalRows = totalRows; + this.setOptions(options); + this.scrollArrowHeight = 16; + }, + + setOptions: function(options) { + this.options = { + largeBufferSize : 7.0, // 7 pages + smallBufferSize : 1.0, // 1 page + nearLimitFactor : 0.2 // 20% of buffer + }.extend(options || {}); + }, + + getPageSize: function() { + return this.pageSize; + }, + + getTotalRows: function() { + return this.totalRows; + }, + + setTotalRows: function(n) { + this.totalRows = n; + }, + + getLargeBufferSize: function() { + return parseInt(this.options.largeBufferSize * this.pageSize); + }, + + getSmallBufferSize: function() { + return parseInt(this.options.smallBufferSize * this.pageSize); + }, + + getLimitTolerance: function() { + return parseInt(this.getLargeBufferSize() * this.options.nearLimitFactor); + }, + + getBufferSize: function(isFull) { + return isFull ? this.getLargeBufferSize() : this.getSmallBufferSize(); + } +}; + +// Rico.LiveGridScroller ----------------------------------------------------- + +Rico.LiveGridScroller = Class.create(); + +Rico.LiveGridScroller.prototype = { + + initialize: function(liveGrid) { + this.isIE = navigator.userAgent.toLowerCase().indexOf("msie") >= 0; + this.liveGrid = liveGrid; + this.metaData = liveGrid.metaData; + this.createScrollBar(); + this.scrollTimeout = null; + //this.sizeIEHeaderHack(); + this.lastScrollPos = 0; + }, + + isUnPlugged: function() { + return this.scrollerDiv.onscroll == null; + }, + + plugin: function() { + this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this); + }, + + unplug: function() { + this.scrollerDiv.onscroll = null; + }, + + sizeIEHeaderHack: function() { + if ( !this.isIE ) return; + var headerTable = $(this.liveGrid.tableId + "_header"); + if ( headerTable ) + headerTable.rows[0].cells[0].style.width = + (headerTable.rows[0].cells[0].offsetWidth + 1) + "px"; + }, + + createScrollBar: function() { + var table = this.liveGrid.table; + var visibleHeight = table.offsetHeight; + + // create the outer div... + this.scrollerDiv = document.createElement("div"); + var scrollerStyle = this.scrollerDiv.style; + scrollerStyle.borderRight = "1px solid #ababab"; // hard coded color!!! + scrollerStyle.position = "relative"; + scrollerStyle.left = this.isIE ? "-6px" : "-3px"; + scrollerStyle.width = "19px"; + scrollerStyle.height = visibleHeight + "px"; + scrollerStyle.overflow = "auto"; + + // create the inner div... + this.heightDiv = document.createElement("div"); + this.heightDiv.style.width = "1px"; + this.heightDiv.style.height = parseInt(visibleHeight * + this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ; + this.lineHeight = visibleHeight/this.metaData.getPageSize(); + + this.scrollerDiv.appendChild(this.heightDiv); + this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this); + table.parentNode.insertBefore( this.scrollerDiv, table.nextSibling ); + }, + + updateSize: function() { + var table = this.liveGrid.table; + var visibleHeight = table.offsetHeight; + this.heightDiv.style.height = parseInt(visibleHeight * + this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px"; + }, + + adjustScrollTop: function() { + this.unplug(); + var rem = this.scrollerDiv.scrollTop % this.lineHeight; + if (rem != 0) { + if (this.lastScrollPos < this.scrollerDiv.scrollTop) + this.scrollerDiv.scrollTop = this.scrollerDiv.scrollTop + this.lineHeight -rem; + else + this.scrollerDiv.scrollTop = this.scrollerDiv.scrollTop - rem; + } + this.lastScrollPos = this.scrollerDiv.scrollTop; + this.plugin(); + }, + + handleScroll: function() { + if ( this.scrollTimeout ) + clearTimeout( this.scrollTimeout ); + + //this.adjustScrollTop(); + var contentOffset = parseInt(this.scrollerDiv.scrollTop * + this.metaData.getTotalRows() / this.heightDiv.offsetHeight); + this.liveGrid.requestContentRefresh(contentOffset); + if ( this.metaData.options.onscroll ) + this.metaData.options.onscroll( contentOffset, this.metaData ); + + this.scrollTimeout = setTimeout( this.scrollIdle.bind(this), 1200 ); + }, + + scrollIdle: function() { + if ( this.metaData.options.onscrollidle ) + this.metaData.options.onscrollidle(); + } +}; + +// Rico.LiveGridBuffer ----------------------------------------------------- + +Rico.LiveGridBuffer = Class.create(); + +Rico.LiveGridBuffer.prototype = { + + initialize: function(metaData) { + this.startPos = 0; + this.size = 0; + this.metaData = metaData; + this.rows = new Array(); + this.updateInProgress = false; + }, + + update: function(ajaxResponse,start) { + + this.startPos = start; + this.rows = new Array(); + + var rowsElement = ajaxResponse.getElementsByTagName('rows')[0]; + this.updateUI = rowsElement.getAttribute("update_ui") == "true"; + var trs = rowsElement.getElementsByTagName("tr"); + for ( var i=0 ; i < trs.length; i++ ) { + var row = this.rows[i] = new Array(); + var cells = trs[i].getElementsByTagName("td"); + for ( var j=0; j < cells.length ; j++ ) { + var cell = cells[j]; + var convertSpaces = cell.getAttribute("convert_spaces") == "true"; + var cellContent = cell.text != undefined ? cell.text : cell.textContent; + row[j] = convertSpaces ? this.convertSpaces(cellContent) : cellContent; + } + } + this.size = trs.length; + }, + + isFullP: function() { + return this.metaData.pageSize != this.size; + }, + + isClose: function(start) { + return (start < this.startPos + this.size + (this.size/2)) && + (start + this.size + (this.size/2) > this.startPos) + }, + + isInRange: function(start, count) { + return (start < this.startPos + this.size) && (start + count > this.startPos) + }, + + isFullyInRange: function(position) { + return (position >= this.startPos) && (position+this.metaData.getPageSize()) <= (this.startPos + this.size) + }, + + isNearingTopLimit: function(position) { + return position - this.startPos < this.metaData.getLimitTolerance(); + }, + + isNearingBottomLimit: function(position) { + var myEnd = position + this.metaData.getPageSize(); + var bufferEnd = this.startPos + this.size; + return bufferEnd - myEnd < this.metaData.getLimitTolerance(); + }, + + isAtTop: function() { + return this.startPos == 0; + }, + + isAtBottom: function() { + return this.startPos + this.size == this.metaData.getTotalRows(); + }, + + isNearingLimit: function(position) { + return ( !this.isAtTop() && this.isNearingTopLimit(position)) || + ( !this.isAtBottom() && this.isNearingBottomLimit(position) ) + }, + + getRows: function(start, count) { + var begPos = start - this.startPos; + var endPos = begPos + count; + + // er? need more data... + if ( endPos > this.size ) + endPos = this.size; + + var results = new Array(); + var index = 0; + for ( var i=begPos ; i < endPos; i++ ) { + results[index++] = this.rows[i] + } + return results + }, + + convertSpaces: function(s) { + return s.split(" ").join(" "); + } + +}; + +Rico.LiveGridRequest = Class.create(); +Rico.LiveGridRequest.prototype = { + initialize: function( requestOffset, options ) { + this.requestOffset = requestOffset; + } +}; + +// Rico.LiveGrid ----------------------------------------------------- + +Rico.LiveGrid = Class.create(); + +Rico.LiveGrid.prototype = { + + initialize: function( tableId, visibleRows, totalRows, url, options ) { + + if ( options == null ) + options = {}; + + this.tableId = tableId; + this.table = $(tableId); + this.metaData = new Rico.LiveGridMetaData(visibleRows, totalRows, options); + this.buffer = new Rico.LiveGridBuffer(this.metaData); + this.scroller = new Rico.LiveGridScroller(this); + + this.lastDisplayedStartPos = 0; + this.timeoutHander = null; + this.additionalParms = options.requestParameters || []; + + this.processingRequest = null; + this.unprocessedRequest = null; + + this.initAjax(url); + if ( options.prefetchBuffer ) + this.fetchBuffer(0, true); + }, + + setRequestParams: function() { + this.additionalParms = []; + for ( var i=0 ; i < arguments.length ; i++ ) + this.additionalParms[i] = arguments[i]; + }, + + setTotalRows: function( newTotalRows ) { + this.metaData.setTotalRows(newTotalRows); + this.scroller.updateSize(); + }, + + initAjax: function(url) { + ajaxEngine.registerRequest( this.tableId + '_request', url ); + ajaxEngine.registerAjaxObject( this.tableId + '_updater', this ); + }, + + invokeAjax: function() { + }, + + largeBufferWindowStart: function(offset) { + val = offset - ( (.5 * this.metaData.getLargeBufferSize()) - (.5 * this.metaData.getPageSize()) ); + return Math.max(parseInt(val), 0); + }, + + handleTimedOut: function() { + //server did not respond in 4 seconds... assume that there could have been + //an error or something, and allow requests to be processed again... + this.processingRequest = null; + }, + + fetchBuffer: function(offset, fullBufferp) { + if (this.processingRequest) { + this.unprocessedRequest = new Rico.LiveGridRequest(offset); + return; + } + + var fetchSize = this.metaData.getBufferSize(fullBufferp); + bufferStartPos = Math.max(0,fullBufferp ? this.largeBufferWindowStart(offset) : offset); + + this.processingRequest = new Rico.LiveGridRequest(offset); + this.processingRequest.bufferOffset = bufferStartPos; + + var callParms = []; + callParms.push(this.tableId + '_request'); + callParms.push('id=' + this.tableId); + callParms.push('page_size=' + fetchSize); + callParms.push('offset=' + bufferStartPos); + + for( var i=0 ; i < this.additionalParms.length ; i++ ) + callParms.push(this.additionalParms[i]); + + ajaxEngine.sendRequest.apply( ajaxEngine, callParms ); + this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), 4000 ); + }, + + requestContentRefresh: function(contentOffset) { + if ( this.buffer && this.buffer.isFullyInRange(contentOffset) ) { + this.updateContent(contentOffset); + if (this.buffer.isNearingLimit(contentOffset)) + this.fetchBuffer(contentOffset, true); + } + else if (this.buffer && this.buffer.isClose(contentOffset)) + this.fetchBuffer(contentOffset, true); + else + this.fetchBuffer(contentOffset, false); + }, + + ajaxUpdate: function(ajaxResponse) { + try { + clearTimeout( this.timeoutHandler ); + this.buffer = new Rico.LiveGridBuffer(this.metaData); + this.buffer.update(ajaxResponse,this.processingRequest.bufferOffset); + if (this.unprocessedRequest == null) { + offset = this.processingRequest.requestOffset; + this.updateContent (offset); + } + this.processingRequest = null; + if (this.unprocessedRequest != null) { + this.requestContentRefresh(this.unprocessedRequest.requestOffset); + this.unprocessedRequest = null + } + } + catch(err) { + } + }, + + updateContent: function( offset ) { + this.replaceCellContents(this.buffer, offset); + }, + + replaceCellContents: function(buffer, startPos) { + if (startPos == this.lastDisplayedStartPos){ + return; + } + + this.lastDisplayedStartPos = startPos; + var rows = buffer.getRows(startPos, this.metaData.getPageSize()); + for (var i=0; i < rows.length; i++) { + var row = rows[i]; + for (var j=0; j < row.length; j++) { + this.table.rows[i].cells[j].innerHTML = rows[i][j] + } + } + } +}; + +// ricoUtil.js -------------------- + + +var RicoUtil = { + + getElementsComputedStyle: function ( htmlElement, cssProperty, mozillaEquivalentCSS) { + if ( arguments.length == 2 ) + mozillaEquivalentCSS = cssProperty; + + var el = $(htmlElement); + if ( el.currentStyle ) + return el.currentStyle[cssProperty]; + else + return document.defaultView.getComputedStyle(el, null).getPropertyValue(mozillaEquivalentCSS); + }, + + createXmlDocument : function() { + if (document.implementation && document.implementation.createDocument) { + var doc = document.implementation.createDocument("", "", null); + + if (doc.readyState == null) { + doc.readyState = 1; + doc.addEventListener("load", function () { + doc.readyState = 4; + if (typeof doc.onreadystatechange == "function") + doc.onreadystatechange(); + }, false); + } + + return doc; + } + + if (window.ActiveXObject) + return Try.these( + function() { return new ActiveXObject('MSXML2.DomDocument') }, + function() { return new ActiveXObject('Microsoft.DomDocument')}, + function() { return new ActiveXObject('MSXML.DomDocument') }, + function() { return new ActiveXObject('MSXML3.DomDocument') } + ) || false; + + return null; + }, + + toViewportPosition: function(element) { + return this._toAbsolute(element,true); + }, + + toDocumentPosition: function(element) { + return this._toAbsolute(element,false); + }, + + /** + * Compute the elements position in terms of the window viewport + * so that it can be compared to the position of the mouse (dnd) + * This is additions of all the offsetTop,offsetLeft values up the + * offsetParent hierarchy, ...taking into account any scrollTop, + * scrollLeft values along the way... + * + * IE has a bug reporting a correct offsetLeft of elements within a + * a relatively positioned parent!!! + **/ + _toAbsolute: function(element,accountForDocScroll) { + + if ( navigator.userAgent.toLowerCase().indexOf("msie") == -1 ) + return this._toAbsoluteMozilla(element,accountForDocScroll); + + var x = 0; + var y = 0; + var parent = element; + while ( parent ) { + + var borderXOffset = 0; + var borderYOffset = 0; + if ( parent != element ) { + var borderXOffset = parseInt(this.getElementsComputedStyle(parent, "borderLeftWidth" )); + var borderYOffset = parseInt(this.getElementsComputedStyle(parent, "borderTopWidth" )); + borderXOffset = isNaN(borderXOffset) ? 0 : borderXOffset; + borderYOffset = isNaN(borderYOffset) ? 0 : borderYOffset; + } + + x += parent.offsetLeft - parent.scrollLeft + borderXOffset; + y += parent.offsetTop - parent.scrollTop + borderYOffset; + parent = parent.offsetParent; + } + + if ( accountForDocScroll ) { + x -= this.docScrollLeft(); + y -= this.docScrollTop(); + } + + return { x:x, y:y }; + }, + + /** + * Mozilla did not report all of the parents up the hierarchy via the + * offsetParent property that IE did. So for the calculation of the + * offsets we use the offsetParent property, but for the calculation of + * the scrollTop/scrollLeft adjustments we navigate up via the parentNode + * property instead so as to get the scroll offsets... + * + **/ + _toAbsoluteMozilla: function(element,accountForDocScroll) { + var x = 0; + var y = 0; + var parent = element; + while ( parent ) { + x += parent.offsetLeft; + y += parent.offsetTop; + parent = parent.offsetParent; + } + + parent = element; + while ( parent && + parent != document.body && + parent != document.documentElement ) { + if ( parent.scrollLeft ) + x -= parent.scrollLeft; + if ( parent.scrollTop ) + y -= parent.scrollTop; + parent = parent.parentNode; + } + + if ( accountForDocScroll ) { + x -= this.docScrollLeft(); + y -= this.docScrollTop(); + } + + return { x:x, y:y }; + }, + + docScrollLeft: function() { + if ( window.pageXOffset ) + return window.pageXOffset; + else if ( document.documentElement && document.documentElement.scrollLeft ) + return document.documentElement.scrollLeft; + else if ( document.body ) + return document.body.scrollLeft; + else + return 0; + }, + + docScrollTop: function() { + if ( window.pageYOffset ) + return window.pageYOffset; + else if ( document.documentElement && document.documentElement.scrollTop ) + return document.documentElement.scrollTop; + else if ( document.body ) + return document.body.scrollTop; + else + return 0; + } + +}; \ No newline at end of file diff --git a/framework/Web/Javascripts/effects/slider.js b/framework/Web/Javascripts/effects/slider.js new file mode 100644 index 00000000..dc3ccab4 --- /dev/null +++ b/framework/Web/Javascripts/effects/slider.js @@ -0,0 +1,258 @@ +// Copyright (c) 2005 Marty Haught +// +// See scriptaculous.js for full license. + +if(!Control) var Control = {}; +Control.Slider = Class.create(); + +// options: +// axis: 'vertical', or 'horizontal' (default) +// increment: (default: 1) +// step: (default: 1) +// +// callbacks: +// onChange(value) +// onSlide(value) +Control.Slider.prototype = { + initialize: function(handle, track, options) { + this.handle = $(handle); + this.track = $(track); + + this.options = options || {}; + + this.axis = this.options.axis || 'horizontal'; + this.increment = this.options.increment || 1; + this.step = parseInt(this.options.step) || 1; + this.value = 0; + + var defaultMaximum = Math.round(this.track.offsetWidth / this.increment); + if(this.isVertical()) defaultMaximum = Math.round(this.track.offsetHeight / this.increment); + + this.maximum = this.options.maximum || defaultMaximum; + this.minimum = this.options.minimum || 0; + + // Will be used to align the handle onto the track, if necessary + this.alignX = parseInt (this.options.alignX) || 0; + this.alignY = parseInt (this.options.alignY) || 0; + + // Zero out the slider position + this.setCurrentLeft(Position.cumulativeOffset(this.track)[0] - Position.cumulativeOffset(this.handle)[0] + this.alignX); + this.setCurrentTop(this.trackTop() - Position.cumulativeOffset(this.handle)[1] + this.alignY); + + this.offsetX = 0; + this.offsetY = 0; + + this.originalLeft = this.currentLeft(); + this.originalTop = this.currentTop(); + this.originalZ = parseInt(this.handle.style.zIndex || "0"); + + // Prepopulate Slider value + this.setSliderValue(parseInt(this.options.sliderValue) || 0); + + this.active = false; + this.dragging = false; + this.disabled = false; + + // FIXME: use css + this.handleImage = $(this.options.handleImage) || false; + this.handleDisabled = this.options.handleDisabled || false; + this.handleEnabled = false; + if(this.handleImage) + this.handleEnabled = this.handleImage.src || false; + + if(this.options.disabled) + this.setDisabled(); + + // Value Array + this.values = this.options.values || false; // Add method to validate and sort?? + + Element.makePositioned(this.handle); // fix IE + + this.eventMouseDown = this.startDrag.bindAsEventListener(this); + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.update.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(this.handle, "mousedown", this.eventMouseDown); + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + }, + dispose: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + }, + setDisabled: function(){ + this.disabled = true; + if(this.handleDisabled) + this.handleImage.src = this.handleDisabled; + }, + setEnabled: function(){ + this.disabled = false; + if(this.handleEnabled) + this.handleImage.src = this.handleEnabled; + }, + currentLeft: function() { + return parseInt(this.handle.style.left || '0'); + }, + currentTop: function() { + return parseInt(this.handle.style.top || '0'); + }, + setCurrentLeft: function(left) { + this.handle.style.left = left +"px"; + }, + setCurrentTop: function(top) { + this.handle.style.top = top +"px"; + }, + trackLeft: function(){ + return Position.cumulativeOffset(this.track)[0]; + }, + trackTop: function(){ + return Position.cumulativeOffset(this.track)[1]; + }, + getNearestValue: function(value){ + if(this.values){ + var i = 0; + var offset = Math.abs(this.values[0] - value); + var newValue = this.values[0]; + + for(i=0; i < this.values.length; i++){ + var currentOffset = Math.abs(this.values[i] - value); + if(currentOffset < offset){ + newValue = this.values[i]; + offset = currentOffset; + } + } + return newValue; + } + return value; + }, + setSliderValue: function(sliderValue){ + // First check our max and minimum and nearest values + sliderValue = this.getNearestValue(sliderValue); + if(sliderValue > this.maximum) sliderValue = this.maximum; + if(sliderValue < this.minimum) sliderValue = this.minimum; + var offsetDiff = (sliderValue - (this.value||this.minimum)) * this.increment; + + if(this.isVertical()){ + this.setCurrentTop(offsetDiff + this.currentTop()); + } else { + this.setCurrentLeft(offsetDiff + this.currentLeft()); + } + this.value = sliderValue; + this.updateFinished(); + }, + minimumOffset: function(){ + return(this.isVertical() ? + this.trackTop() + this.alignY : + this.trackLeft() + this.alignX); + }, + maximumOffset: function(){ + return(this.isVertical() ? + this.trackTop() + this.alignY + (this.maximum - this.minimum) * this.increment : + this.trackLeft() + this.alignX + (this.maximum - this.minimum) * this.increment); + }, + isVertical: function(){ + return (this.axis == 'vertical'); + }, + startDrag: function(event) { + if(Event.isLeftClick(event)) { + if(!this.disabled){ + this.active = true; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.handle); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); + this.originalLeft = this.currentLeft(); + this.originalTop = this.currentTop(); + } + Event.stop(event); + } + }, + update: function(event) { + if(this.active) { + if(!this.dragging) { + var style = this.handle.style; + this.dragging = true; + if(style.position=="") style.position = "relative"; + style.zIndex = this.options.zindex; + } + this.draw(event); + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + Event.stop(event); + } + }, + draw: function(event) { + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.handle); + + offsets[0] -= this.currentLeft(); + offsets[1] -= this.currentTop(); + + // Adjust for the pointer's position on the handle + pointer[0] -= this.offsetX; + pointer[1] -= this.offsetY; + var style = this.handle.style; + + if(this.isVertical()){ + if(pointer[1] > this.maximumOffset()) + pointer[1] = this.maximumOffset(); + if(pointer[1] < this.minimumOffset()) + pointer[1] = this.minimumOffset(); + + // Increment by values + if(this.values){ + this.value = this.getNearestValue(Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum); + pointer[1] = this.trackTop() + this.alignY + (this.value - this.minimum) * this.increment; + } else { + this.value = Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum; + } + style.top = pointer[1] - offsets[1] + "px"; + } else { + if(pointer[0] > this.maximumOffset()) pointer[0] = this.maximumOffset(); + if(pointer[0] < this.minimumOffset()) pointer[0] = this.minimumOffset(); + // Increment by values + if(this.values){ + this.value = this.getNearestValue(Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum); + pointer[0] = this.trackLeft() + this.alignX + (this.value - this.minimum) * this.increment; + } else { + this.value = Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum; + } + style.left = (pointer[0] - offsets[0]) + "px"; + } + if(this.options.onSlide) this.options.onSlide(this.value); + }, + endDrag: function(event) { + if(this.active && this.dragging) { + this.finishDrag(event, true); + Event.stop(event); + } + this.active = false; + this.dragging = false; + }, + finishDrag: function(event, success) { + this.active = false; + this.dragging = false; + this.handle.style.zIndex = this.originalZ; + this.originalLeft = this.currentLeft(); + this.originalTop = this.currentTop(); + this.updateFinished(); + }, + updateFinished: function() { + if(this.options.onChange) this.options.onChange(this.value); + }, + keyPress: function(event) { + if(this.active && !this.disabled) { + switch(event.keyCode) { + case Event.KEY_ESC: + this.finishDrag(event, false); + Event.stop(event); + break; + } + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + } + } +} diff --git a/framework/Web/Javascripts/effects/util.js b/framework/Web/Javascripts/effects/util.js new file mode 100644 index 00000000..b4a31a97 --- /dev/null +++ b/framework/Web/Javascripts/effects/util.js @@ -0,0 +1,548 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// See scriptaculous.js for full license. + + +Object.debug = function(obj) { + var info = []; + + if(typeof obj in ["string","number"]) { + return obj; + } else { + for(property in obj) + if(typeof obj[property]!="function") + info.push(property + ' => ' + + (typeof obj[property] == "string" ? + '"' + obj[property] + '"' : + obj[property])); + } + + return ("'" + obj + "' #" + typeof obj + + ": {" + info.join(", ") + "}"); +} + + +String.prototype.toArray = function() { + var results = []; + for (var i = 0; i < this.length; i++) + results.push(this.charAt(i)); + return results; +} + +/*--------------------------------------------------------------------------*/ + +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); + parentElement.innerHTML = "<" + elementName + ">"; + 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) { + parentElement.innerHTML = "<" +elementName + " " + + attrs + ">"; + 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'); + } +} + +/* ------------- element ext -------------- */ + +// adapted from http://dhtmlkitchen.com/learn/js/setstyle/index4.jsp +// note: Safari return null on elements with display:none; see http://bugzilla.opendarwin.org/show_bug.cgi?id=4125 +// instead of "auto" values returns null so it's easier to use with || constructs + +String.prototype.camelize = function() { + var oStringList = this.split('-'); + if(oStringList.length == 1) + return oStringList[0]; + var ret = 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]; + ret += s.charAt(0).toUpperCase() + s.substring(1) + } + return ret; +} + +Element.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!=null) ? css.getPropertyValue(style) : null; + } else if(element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + + // If top, left, bottom, or right values have been queried, return "auto" for consistency resaons + // if position is "static", as Opera (and others?) returns the pixel values relative to root element + // (or positioning context?) + if (window.opera && (style == "left" || style == "top" || style == "right" || style == "bottom")) + if (Element.getStyle(element, "position") == "static") value = "auto"; + + if(value=='auto') value = null; + return value; +} + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + 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.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; + } + } +} + +Element.undoPositioned = function(element) { + element = $(element); + if(typeof element._madePositioned != "undefined"){ + element._madePositioned = undefined; + element.style.position = ""; + element.style.top = ""; + element.style.left = ""; + element.style.bottom = ""; + element.style.right = ""; + } +} + +Element.makeClipping = function(element) { + element = $(element); + if (typeof element._overflow != 'undefined') return; + element._overflow = element.style.overflow; + if((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') element.style.overflow = 'hidden'; +} + +Element.undoClipping = function(element) { + element = $(element); + if (typeof element._overflow == 'undefined') return; + element.style.overflow = element._overflow; + element._overflow = undefined; +} + +Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { + var children = $(element).childNodes; + var text = ""; + var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); + + for (var i = 0; i < children.length; i++) { + if(children[i].nodeType==3) { + text+=children[i].nodeValue; + } else { + if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) + text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); + } + } + + return text; +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.style.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); + var els = element.style; + if (value == 1){ + els.opacity = '0.999999'; + if(/MSIE/.test(navigator.userAgent)) + els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,''); + } else { + if(value < 0.00001) value = 0; + els.opacity = value; + if(/MSIE/.test(navigator.userAgent)) + els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + "alpha(opacity="+value*100+")"; + } +} + +Element.getInlineOpacity = function(element){ + element= $(element); + var op; + op = element.style.opacity; + if (typeof op != "undefined" && op != "") return op; + return ""; +} + +Element.setInlineOpacity = function(element, value){ + element= $(element); + var els = element.style; + els.opacity = value; +} + +Element.getDimensions = function(element){ + element = $(element); + // All *Width and *Height properties give 0 on elements with display "none", + // so enable the element temporarily + if (Element.getStyle(element,'display') == "none"){ + 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}; + } + + return {width: element.offsetWidth, height: element.offsetHeight}; +} + +/*--------------------------------------------------------------------------*/ + +Position.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]; +} + +// Safari returns margins on body which is incorrect if the child is absolutely positioned. +// for performance reasons, we create a specialized version of 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]; + } +} + +Position.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]; +} + +// elements with display:none don't return an offsetParent, +// fall back to manual calculation +Position.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; +} + +Position.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"; +} + +Position.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';; +} + +Position.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; +} + +/*--------------------------------------------------------------------------*/ + +Element.Class = { + // Element.toggleClass(element, className) toggles the class being on/off + // Element.toggleClass(element, className1, className2) toggles between both classes, + // defaulting to className1 if neither exist + toggle: function(element, className) { + if(Element.Class.has(element, className)) { + Element.Class.remove(element, className); + if(arguments.length == 3) Element.Class.add(element, arguments[2]); + } else { + Element.Class.add(element, className); + if(arguments.length == 3) Element.Class.remove(element, arguments[2]); + } + }, + + // gets space-delimited classnames of an element as an array + get: function(element) { + return $(element).className.split(' '); + }, + + // functions adapted from original functions by Gavin Kistner + remove: function(element) { + element = $(element); + var removeClasses = arguments; + $R(1,arguments.length-1).each( function(index) { + element.className = + element.className.split(' ').reject( + function(klass) { return (klass == removeClasses[index]) } ).join(' '); + }); + }, + + add: function(element) { + element = $(element); + for(var i = 1; i < arguments.length; i++) { + Element.Class.remove(element, arguments[i]); + element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; + } + }, + + // returns true if all given classes exist in said element + has: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); + if(!regEx.test(element.className)) return false; + } + } else { + regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); + if(!regEx.test(element.className)) return false; + } + } + return true; + }, + + // expects arrays of strings and/or strings as optional paramters + // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') + has_any: function(element) { + element = $(element); + if(!element || !element.className) return false; + var regEx; + for(var i = 1; i < arguments.length; i++) { + if((typeof arguments[i] == 'object') && + (arguments[i].constructor == Array)) { + for(var j = 0; j < arguments[i].length; j++) { + regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); + if(regEx.test(element.className)) return true; + } + } else { + regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); + if(regEx.test(element.className)) return true; + } + } + return false; + }, + + childrenWith: function(element, className) { + var children = $(element).getElementsByTagName('*'); + var elements = new Array(); + + for (var i = 0; i < children.length; i++) + if (Element.Class.has(children[i], className)) + elements.push(children[i]); + + return elements; + } +} \ No newline at end of file diff --git a/framework/Web/Javascripts/extended/array.js b/framework/Web/Javascripts/extended/array.js new file mode 100644 index 00000000..2aeb9084 --- /dev/null +++ b/framework/Web/Javascripts/extended/array.js @@ -0,0 +1,465 @@ +/** +ARRAY EXTENSIONS +by Caio Chassot (http://v2studio.com/k/code/) +*/ + +//function v2studio_com_code() +//{ + + +/** + * Searches Array for value. + * returns the index of the first item + * which matches value, or -1 if not found. + * searching starts at index 0, or at start, if specified. + * + * Here are the rules for an item to match value + * if strict is false or not specified (default): + * if value is a: + * # function -> value(item) must be true + * # RegExp -> value.test(item) must be true + * # anything else -> item == value must be true + * @param value the value (function, regexp) to search + * @param start where to start the search + * @param strict use strict comparison (===) for everything + */ +Array.prototype.indexOf = function(value, start, strict) { + start = start || 0; + for (var i=start; ivalue returns the first matched item, or null if not found + * Parameters work the same as indexOf + * @see #indexOf + */ +Array.prototype.find = function(value, start, strict) { + var i = this.indexOf(value, start, strict); + if (i != -1) return this[i]; + return null +} + + + +/* A.contains(value [, strict]) +/** + * aliases: has, include + * returns true if value is found in Array, otherwise false; + * relies on indexOf, see its doc for details on value and strict + * @see #indexOf + */ +Array.prototype.contains = function(value,strict) { + return this.indexOf(value,0,strict) !== -1; +} + + +Array.prototype.has = Array.prototype.contains; + +Array.prototype.include = Array.prototype.contains; + + +/** + * counts occurences of value in Array + * relies on indexOf, see its doc for details on value and strict + * @see #indexOf + */ +Array.prototype.count = function(value, strict) { + var pos, start = 0, count = 0; + while ((pos = this.indexOf(value, start, strict)) !== -1) { + start = pos + 1; + count++; + } + return count; +} + + +/** + * if all is false or not provied: + * removes first occurence of value from Array + * if all is provided and true: + * removes all occurences of value from Array + * returns the array + * relies on indexOf, see its doc for details on value and strict + * @see #indexOf + */ +Array.prototype.remove = function(value,all,strict) { + while (this.contains(value,strict)) { + this.splice(this.indexOf(value,0,strict),1); + if (!all) break + } + return this; +} + + + +/* A.merge(a [, a]*) + Append the contents of provided arrays into the current + takes: one or more arrays + returns: current array (modified) +*/ +Array.prototype.merge = function() { + var a = []; + for (var i=0; i +*/ +Array.prototype.min = function() { + if (!this.length) return; + var n = this[0]; + for (var i=1; ithis[i]) n=this[i]; + return n; +} + + + +/* A.min() + returns the graetest item in array by comparing them with < +*/ +Array.prototype.max = function() { + if (!this.length) return; + var n = this[0]; + for (var i=1; i 0; +} + + + +/* A.each(fn) + method form of each function +*/ +Array.prototype.each = function(fn) { return each(this, fn) } + + + +/* A.map([fn]) + method form of map function +*/ +Array.prototype.map = function(fn) { return map(this, fn) } + + + +/* A.filter([fn]) + method form of filter function +*/ +Array.prototype.filter = function(fn) { return filter(this, fn) } + + +Array.prototype.select = Array.prototype.filter + + +/* A.reduce([initial,] fn) + method form of filter function +*/ +Array.prototype.reduce = function() { + var args = map(arguments); + fn = args.pop(); + d = args.pop(); + return reduce(this, d, fn); +} + + +Array.prototype.inject = Array.prototype.reduce + + + +/* A.reject(fn) + deletes items in A *in place* for which fn(item) is true + returns a +*/ +Array.prototype.reject = function(fn) { + if (typeof(fn)=='string') fn = __strfn('item,idx,list', fn); + var self = this; + var itemsToRemove = []; + fn = fn || function(v) {return v}; + map(self, function(item,idx,list) { if (fn(item,idx,list)) itemsToRemove.push(idx) } ); + itemsToRemove.reverse().each(function(idx) { self.splice(idx,1) }); + return self; +} + + + +/* __strfn(args, fn) + this is used internally by each, map, combine, filter and reduce to accept + strings as functions. + + takes: + `args` -> a string of comma separated names of the function arguments + `fn` -> the function body + + if `fn` does not contain a return statement, a return keyword will be added + before the last statement. the last statement is determined by removing the + trailing semicolon (';') (if it exists) and then searching for the last + semicolon, hence, caveats may apply (i.e. if the last statement has a + string or regex containing the ';' character things will go wrong) +*/ +function __strfn(args, fn) { + function quote(s) { return '"' + s.replace(/"/g,'\\"') + '"' } + if (!/\breturn\b/.test(fn)) { + fn = fn.replace(/;\s*$/, ''); + fn = fn.insert(fn.lastIndexOf(';')+1, ' return '); + } + return eval('new Function(' + + map(args.split(/\s*,\s*/), quote).join() + + ',' + + quote(fn) + + ')' + ); +} + + + +/* each(list, fn) + traverses `list`, applying `fn` to each item of `list` + takes: + `list` -> anything that can be indexed and has a `length` property. + usually an array. + `fn` -> either a function, or a string containing a function body, + in which case the name of the paremeters passed to it will be + 'item', 'idx' and 'list'. + se doc for `__strfn` for peculiarities about passing strings + for `fn` + + `each` provides a safe way for traversing only an array's indexed items, + ignoring its other properties. (as opposed to how for-in works) +*/ +function each(list, fn) { + if (typeof(fn)=='string') return each(list, __strfn('item,idx,list', fn)); + for (var i=0; i < list.length; i++) fn(list[i], i, list); +} + + +/* map(list [, fn]) + traverses `list`, applying `fn` to each item of `list`, returning an array + of values returned by `fn` + + parameters work the same as for `each`, same `__strfn` caveats apply + + if `fn` is not provided, the list item is returned itself. this is an easy + way to transform fake arrays (e.g. the arguments object of a function or + nodeList objects) into real javascript arrays. + e.g.: args = map(arguments) + + If you don't care about map's return value, you should use `each` + + this is a simplified version of python's map. parameter order is different, + only a single list (array) is accepted, and the parameters passed to [fn] + are different: + [fn] takes the current item, then, optionally, the current index and a + reference to the list (so that [fn] can modify list) + see `combine` if you want to pass multiple lists +*/ +function map(list, fn) { + if (typeof(fn)=='string') return map(list, __strfn('item,idx,list', fn)); + + var result = []; + fn = fn || function(v) {return v}; + for (var i=0; i < list.length; i++) result.push(fn(list[i], i, list)); + return result; +} + + +/* combine(list [, list]* [, fn]) + + takes: + `list`s -> one or more lists (see `each` for definition of a list) + `fn` -> Similar s `each` or `map`, a function or a string containing + a function body. + if a string is used, the name of parameters passed to the + created function will be the lowercase alphabet letters, in + order: a,b,c... + same `__strfn` caveats apply + + combine will traverse all lists concurrently, passing each row if items as + parameters to `fn` + if `fn` is not provided, a function that returns a list containing each + item in the row is used. + if a list is smaller than the other, `null` is used in place of its missing + items + + returns: + an array of the values returned by calling `fn` for each row of items +*/ +function combine() { + var args = map(arguments); + var lists = map(args.slice(0,-1),'map(item)'); + var fn = args.last(); + var toplen = map(lists, "item.length").max(); + var vals = []; + + if (!fn) fn = function(){return map(arguments)}; + if (typeof fn == 'string') { + if (lists.length > 26) throw 'string functions can take at most 26 lists'; + var a = 'a'.charCodeAt(0); + fn = __strfn(map(range(a, a+lists.length),'String.fromCharCode(item)').join(','), fn); + } + + map(lists, function(li) { + while (li.length < toplen) li.push(null); + map(li, function(item,ix){ + if (ix < vals.length) vals[ix].push(item); + else vals.push([item]); + }); + }); + + return map(vals, function(val) { return fn.apply(fn, val) }); +} + + + +/* filter(list [, fn]) + returns an array of items in `list` for which `fn(item)` is true + + parameters work the same as for `each`, same `__strfn` caveats apply + + if `fn` is not specified the items are evaluated themselves, that is, + filter will return an array of the items in `list` which evaluate to true + + this is a similar to python's filter, but parameter order is inverted +*/ +function filter(list, fn) { + if (typeof(fn)=='string') return filter(list, __strfn('item,idx,list', fn)); + + var result = []; + fn = fn || function(v) {return v}; + map(list, function(item,idx,list) { if (fn(item,idx,list)) result.push(item) } ); + return result; +} + + + +/* reduce(list [, initial], fn) + similar to python's reduce. paremeter onder inverted... + + TODO: document this properly + + takes: + `list` -> see doc for `each` to learn more about it + `inirial -> TODO: doc` + `fn` -> similar to `each` too, but in the case where it's a string, + the name of the paremeters passed to it will be 'a' and 'b' + same `__strfn` caveats apply + +*/ +function reduce(list, initial, fn) { + if (undef(fn)) { + fn = initial; + initial = window.undefined; // explicit `window` object so browsers that do not have an `undefined` keyword will evaluate to the (hopefully) undefined parameter `undefined` of `window` + } + if (typeof(fn)=='string') return reduce(list, initial, __strfn('a,b', fn)); + if (isdef(initial)) list.splice(0,0,initial); + if (list.length===0) return false; + if (list.length===1) return list[0]; + var result = list[0]; + var i = 1; + while(i0&&parent.frames.length) { + d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);} + if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i 0) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + } +}); \ No newline at end of file diff --git a/framework/Web/Javascripts/extended/functional.js b/framework/Web/Javascripts/extended/functional.js new file mode 100644 index 00000000..2ff0f4a3 --- /dev/null +++ b/framework/Web/Javascripts/extended/functional.js @@ -0,0 +1,171 @@ +/** +FUNCTIONAL +by Caio Chassot (http://v2studio.com/k/code/) +*/ + +/** + * this is used internally by each, map, combine, filter and reduce to accept + * strings as functions. + * + * if fn does not contain a return statement, a return keyword will be added + * before the last statement. the last statement is determined by removing the + * trailing semicolon (';') (if it exists) and then searching for the last + * semicolon, hence, caveats may apply (i.e. if the last statement has a + * string or regex containing the ';' character things will go wrong) + * @param args a string of comma separated names of the function arguments + * @param fn the function body + */ +function __strfn(args, fn) { + /** + * Internal function. Do not call it directly. + */ + function quote(s) { return '"' + s.replace(/"/g,'\\"') + '"' } + if (!/\breturn\b/.test(fn)) { + fn = fn.replace(/;\s*$/, ''); + fn = fn.insert(fn.lastIndexOf(';')+1, ' return '); + } + return eval('new Function(' + + map(args.split(/\s*,\s*/), quote).join() + + ',' + + quote(fn) + + ')' + ); +} + + +/** + * traverses list, applying fn to each item of list. + * see doc for __strfn for peculiarities about passing strings for fn + * + * each provides a safe way for traversing only an array's indexed items, + * ignoring its other properties. (as opposed to how for-in works) + * @param list anything that can be indexed and has a length property. usually an array. + * @param fn either a function, or a string containing a function body, + * in which case the name of the paremeters passed to it will be + * 'item', 'idx' and 'list'. + * @see #__strfn + */ +function each(list, fn) { + if (typeof(fn)=='string') return each(list, __strfn('item,idx,list', fn)); + for (var i=0; i < list.length; i++) fn(list[i], i, list); +} + + +/** + * traverses list, applying fn to each item of list, returning an array + of values returned by fn + + parameters work the same as for each, same __strfn caveats apply + + if fn is not provided, the list item is returned itself. this is an easy + way to transform fake arrays (e.g. the arguments object of a function or + nodeList objects) into real javascript arrays. + e.g.: args = map(arguments) + + If you don't care about map's return value, you should use each + + this is a simplified version of python's map. parameter order is different, + only a single list (array) is accepted, and the parameters passed to [fn] + are different: + [fn] takes the current item, then, optionally, the current index and a + reference to the list (so that [fn] can modify list) + see combine if you want to pass multiple lists + */ +function map(list, fn) { + if (typeof(fn)=='string') return map(list, __strfn('item,idx,list', fn)); + + var result = []; + fn = fn || function(v) {return v}; + for (var i=0; i < list.length; i++) result.push(fn(list[i], i, list)); + return result; +} + + +/** + * combine will traverse all lists concurrently, passing each row if items as + parameters to fn + if fn is not provided, a function that returns a list containing each + item in the row is used. + if a list is smaller than the other, null is used in place of its missing + items + * @param list one or more lists (see each for definition of a list) + * @param fn Similar s each or map, a function or a string containing + a function body. + if a string is used, the name of parameters passed to the + created function will be the lowercase alphabet letters, in + order: a,b,c... + same __strfn caveats apply + * @see #each + * @see #__strfn + * @return an array of the values returned by calling fn for each row of items + *//* +function combine() { + var args = map(arguments); + var lists = map(args.slice(0,-1),'map(item)'); + var fn = args.last(); + var toplen = map(lists, "item.length").max(); + var vals = []; + + if (!fn) fn = function(){return map(arguments)}; + if (typeof fn == 'string') { + if (lists.length > 26) throw 'string functions can take at most 26 lists'; + var a = 'a'.charCodeAt(0); + fn = __strfn(map(range(a, a+lists.length),'String.fromCharCode(item)').join(','), fn); + } + + map(lists, function(li) { + while (li.length < toplen) li.push(null); + map(li, function(item,ix){ + if (ix < vals.length) vals[ix].push(item); + else vals.push([item]); + }); + }); + + return map(vals, function(val) { return fn.apply(fn, val) }); +} + +/** + * returns an array of items in list for which fn(item) is true + + parameters work the same as for each, same __strfn caveats apply + + if fn is not specified the items are evaluated themselves, that is, + filter will return an array of the items in list which evaluate to true + + this is a similar to python's filter, but parameter order is inverted + *//* +function filter(list, fn) { + if (typeof(fn)=='string') return filter(list, __strfn('item,idx,list', fn)); + + var result = []; + fn = fn || function(v) {return v}; + map(list, function(item,idx,list) { if (fn(item,idx,list)) result.push(item) } ); + return result; +} + +/** + * similar to python's reduce. paremeter order inverted... + * @param list see doc for each to learn more about it + * @param initial TODO + * @param fn similar to each too, but in the case where it's a string, + the name of the paremeters passed to it will be 'a' and 'b' + same __strfn caveats apply + *//* +function reduce(list, initial, fn) { + if (undef(fn)) { + fn = initial; + // explicit window object so browsers that do not have an undefined + //keyword will evaluate to the (hopefully) undefined parameter + //undefined of window + initial = window.undefined; + } + if (typeof(fn)=='string') return reduce(list, initial, __strfn('a,b', fn)); + if (isdef(initial)) list.splice(0,0,initial); + if (list.length===0) return false; + if (list.length===1) return list[0]; + var result = list[0]; + var i = 1; + while(ielements = document.getElementsBySelect('div#main p a.external') + + Will return an array of all 'a' elements with 'external' in their + class attribute that are contained inside 'p' elements that are + contained inside the 'div' element which has id="main" + */ +document.getElementsBySelector = function(selector) { + // Attempt to fail gracefully in lesser browsers + if (!document.getElementsByTagName) { + return new Array(); + } + // Split selector in to tokens + var tokens = selector.split(' '); + var currentContext = new Array(document); + for (var i = 0; i < tokens.length; i++) { + token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');; + if (token.indexOf('#') > -1) { + // Token is an ID selector + var bits = token.split('#'); + var tagName = bits[0]; + var id = bits[1]; + var element = document.getElementById(id); + if (tagName && element.nodeName.toLowerCase() != tagName) { + // tag with that ID not found, return false + return new Array(); + } + // Set currentContext to contain just this element + currentContext = new Array(element); + continue; // Skip to next token + } + if (token.indexOf('.') > -1) { + // Token contains a class selector + var bits = token.split('.'); + var tagName = bits[0]; + var className = bits[1]; + if (!tagName) { + tagName = '*'; + } + // Get elements matching tag, filter them for class selector + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + elements = currentContext[h].getElementsByTagName(tagName); + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) { + currentContext[currentContextIndex++] = found[k]; + } + } + continue; // Skip to next token + } + // Code to deal with attribute selectors + if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) { + var tagName = RegExp.$1; + var attrName = RegExp.$2; + var attrOperator = RegExp.$3; + var attrValue = RegExp.$4; + if (!tagName) { + tagName = '*'; + } + // Grab all of the tagName elements within current context + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + elements = currentContext[h].getElementsByTagName(tagName); + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + var checkFunction; // This function will be used to filter the elements + switch (attrOperator) { + case '=': // Equality + checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); }; + break; + case '~': // Match one of space seperated words + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); }; + break; + case '|': // Match start with value followed by optional hyphen + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); }; + break; + case '^': // Match starts with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); }; + break; + case '$': // Match ends with value - fails with "Warning" in Opera 7 + checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); }; + break; + case '*': // Match ends with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); }; + break; + default : + // Just test for existence of attribute + checkFunction = function(e) { return e.getAttribute(attrName); }; + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (checkFunction(found[k])) { + currentContext[currentContextIndex++] = found[k]; + } + } + // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue); + continue; // Skip to next token + } + // If we get here, token is JUST an element (not a class or ID selector) + tagName = token; + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements = currentContext[h].getElementsByTagName(tagName); + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = found; + } + return currentContext; +} + +/* That revolting regular expression explained +/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/ + \---/ \---/\-------------/ \-------/ + | | | | + | | | The value + | | ~,|,^,$,* or = + | Attribute + Tag +*/ diff --git a/framework/Web/Javascripts/extra/logger.js b/framework/Web/Javascripts/extra/logger.js new file mode 100644 index 00000000..438772e9 --- /dev/null +++ b/framework/Web/Javascripts/extra/logger.js @@ -0,0 +1,659 @@ +/* + +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') + }, + + debug : function(message) { + this.log(message, 'debug') + }, + + warn : function(message) { + this.log(message, 'warning') + }, + + error : function(message, error) { + this.log(message + ": \n" + error, 'error') + }, + + 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, + + // Methods + // ------- + + initialize : function() { + 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)) + + window.setInterval(this.repositionWindow.bind(this), 500) + this.repositionWindow() + + // 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 + Cookie.set('ConsoleVisible', 'true') + this.inputElement.select() + }, + + hide : function() { + 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 + } + } +} + +// Load the Console when the window loads +Event.observe(window, "load", function() {logConsole = new LogConsole()}) + +// ------------------------- +// Helper Functions And Junk +// ------------------------- +function inspect(element, hideProperties, showMethods) { + var properties = [] + var methods = [] + + + for(var internal in element) { + if(internal == '______array') continue; + try { + if (element[internal] instanceof Function) { + if (showMethods) + methods.push(internal + ":\t" + element[internal] ) + } + else if(element[internal] instanceof Object) + { + methods.push(internal + ":\t" + inspect(element[internal], hideProperties, showMethods)); + } + else { + if (!hideProperties) + properties.push(internal + ":\t" + element[internal] ) + } + } + catch (e) { + Logger.error("Excetion thrown while inspecting object.", e) + } + } + + properties.sort() + methods.sort() + + var internals = properties.concat(methods) + var output = "" + for (var i = 0; i < internals.length; i++) { + output += (internals[i] + "\n") + } + + return output +} + +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 : '', + + format : function(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].toString().indexOf("[native code]")==-1) { + t=typeof(win[js]); + if(!this.objs[t.toString()]) { + this.types[this.types.length]=t;; + this.objs[t]=new Array(); + } + index=this.objs[t].length + this.objs[t][index]=new Array(); + this.objs[t][index][0]=js; + this.objs[t][index][1]=this.format(win[js].toString()); + } + } catch(err) { } + } + + }, + + show : function(objID) { + this.d.getElementById(objID).style.display=this.hidden[objID]?"none":"block"; + this.hidden[objID]=this.hidden[objID]?0:1; + }, + + changeSpan : function(spanID) { + if(this.d.getElementById(spanID).innerHTML.indexOf("+")>-1){ + this.d.getElementById(spanID).innerHTML="[-]"; + } else { + this.d.getElementById(spanID).innerHTML="[+]"; + } + }, + + buildInspectionLevel : function() + { + var display = this.displaying; + var list = display.split("."); + var links = ["[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.objs[this.types[i]].length + ")
        "; + this.hidden["ul"+i]=0; + for(e=0;e= 0 && /^[a-zA-Z_]/.test(this.objs[this.types[i]][e][0][0])) + { + if(this.displaying.indexOf("[object ") < 0) + more = " more"; + else if(this.displaying.indexOf("[object Window]") >= 0) + more = " more"; + } + mHTML+="
      • [+]" + this.objs[this.types[i]][e][0] + "
        • " + this.objs[this.types[i]][e][1] + 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.handleKeyEvent.bind(this)); + 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; + if(typeof Event != "undefined") + Event.observe(this.d, "keydown", this.handleKeyEvent.bind(this)); + + 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, more info."; + + 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; }" +} + +function var_dump(obj) +{ + Prado.Inspector.inspect(obj); +} \ No newline at end of file diff --git a/framework/Web/Javascripts/extra/tp_template.js b/framework/Web/Javascripts/extra/tp_template.js new file mode 100644 index 00000000..6015034c --- /dev/null +++ b/framework/Web/Javascripts/extra/tp_template.js @@ -0,0 +1,315 @@ +/** + * TrimPath Template. Release 1.0.16. + * Copyright (C) 2004, 2005 Metaha. + * + * TrimPath Template is licensed under the GNU General Public License + * and the Apache License, Version 2.0, as follows: + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +var TrimPath; + +// TODO: Debugging mode vs stop-on-error mode - runtime flag. +// TODO: Handle || (or) characters and backslashes. +// TODO: Add more modifiers. + +(function() { // Using a closure to keep global namespace clean. + var theEval = eval; // Security, to ensure eval cleanliness. + if (TrimPath == null) + TrimPath = new Object(); + if (TrimPath.evalEx == null) + TrimPath.evalEx = function(src) { return theEval(src); }; + + TrimPath.parseTemplate = function(tmplContent, optTmplName, optEtc) { + if (optEtc == null) + optEtc = TrimPath.parseTemplate_etc; + var funcSrc = parse(tmplContent, optTmplName, optEtc); + var func = TrimPath.evalEx(funcSrc, optTmplName, 1); + if (func != null) + return new optEtc.Template(optTmplName, tmplContent, funcSrc, func, optEtc); + return null; + } + + try { + String.prototype.process = function(context, optFlags) { + var template = TrimPath.parseTemplate(this, null); + if (template != null) + return template.process(context, optFlags); + return this; + } + } catch (e) { // Swallow exception, such as when String.prototype is sealed. + } + + TrimPath.parseTemplate_etc = {}; // Exposed for extensibility. + TrimPath.parseTemplate_etc.statementTag = "forelse|for|if|elseif|else|var|macro"; + TrimPath.parseTemplate_etc.statementDef = { // Lookup table for statement tags. + "if" : { delta: 1, prefix: "if (", suffix: ") {", paramMin: 1 }, + "else" : { delta: 0, prefix: "} else {" }, + "elseif" : { delta: 0, prefix: "} else { if (", suffix: ") {", paramDefault: "true" }, + "/if" : { delta: -1, prefix: "}" }, + "for" : { delta: 1, paramMin: 3, + prefixFunc : function(stmtParts, state, tmplName, etc) { + if (stmtParts[2] != "in") + throw new etc.ParseError(tmplName, state.line, "bad for loop statement: " + stmtParts.join(' ')); + var iterVar = stmtParts[1]; + var listVar = "__LIST__" + iterVar; + return [ "var ", listVar, " = ", stmtParts[3], ";", + "if ((", listVar, ") != null && (", listVar, ").length > 0) { for (var ", + iterVar, "_index in ", listVar, ") { var ", + iterVar, " = ", listVar, "[", iterVar, "_index];" ].join(""); + } }, + "forelse" : { delta: 0, prefix: "} } else { if (", suffix: ") {", paramDefault: "true" }, + "/for" : { delta: -1, prefix: "} }" }, + "var" : { delta: 0, prefix: "var ", suffix: ";" }, + "macro" : { delta: 1, prefix: "function ", suffix: "{ var _OUT_arr = []; var _OUT = { write: function(m) { if (m) _OUT_arr.push(m); }, }; " }, + "/macro" : { delta: -1, prefix: " return _OUT_arr.join(''); }" } + } + TrimPath.parseTemplate_etc.modifierDef = { + "eat" : function(v) { return ""; }, + "escape" : function(s) { return String(s).replace(/&/g, "&").replace(//g, ">"); }, + "capitalize" : function(s) { return String(s).toUpperCase(); }, + "default" : function(s, d) { return s != null ? s : d; } + } + TrimPath.parseTemplate_etc.modifierDef.h = TrimPath.parseTemplate_etc.modifierDef.escape; + + TrimPath.parseTemplate_etc.Template = function(tmplName, tmplContent, funcSrc, func, etc) { + this.process = function(context, flags) { + if (context == null) + context = {}; + if (context._MODIFIERS == null) + context._MODIFIERS = {}; + for (var k in etc.modifierDef) { + if (context._MODIFIERS[k] == null) + context._MODIFIERS[k] = etc.modifierDef[k]; + } + if (flags == null) + flags = {}; + var resultArr = []; + var resultOut = { write: function(m) { if (m) resultArr.push(m); } }; + try { + func(resultOut, context, flags); + } catch (e) { + if (flags.throwExceptions == true) + throw e; + var result = new String(resultArr.join("") + "[ERROR: " + e.toString() + "]"); + result["exception"] = e; + return result; + } + return resultArr.join(""); + } + this.name = tmplName; + this.source = tmplContent; + this.sourceFunc = funcSrc; + this.toString = function() { return "TrimPath.Template [" + tmplName + "]"; } + } + TrimPath.parseTemplate_etc.ParseError = function(name, line, message) { + this.name = name; + this.line = line; + this.message = message; + } + TrimPath.parseTemplate_etc.ParseError.prototype.toString = function() { + return ("TrimPath template ParseError in " + this.name + ": line " + this.line + ", " + this.msg); + } + + var parse = function(body, tmplName, etc) { + body = cleanWhiteSpace(body); + var funcText = [ "var TrimPath_Template_TEMP = function(_OUT, _CONTEXT, _FLAGS) { with (_CONTEXT) {" ]; + var state = { stack: [], line: 1 }; // TODO: Fix line number counting. + var endStmtPrev = -1; + while (endStmtPrev + 1 < body.length) { + var begStmt = endStmtPrev; + // Scan until we find some statement markup. + begStmt = body.indexOf("{", begStmt + 1); + while (begStmt >= 0) { + if (body.charAt(begStmt - 1) != '$' && // Not an expression or backslashed, + body.charAt(begStmt - 1) != '\\') { // so we assume it must be a statement tag. + var offset = (body.charAt(begStmt + 1) == '/' ? 2 : 1); // Close tags offset of 2 skips '/'. + // 10 is larger than maximum statement tag length. + if (body.substring(begStmt + offset, begStmt + 10 + offset).search(TrimPath.parseTemplate_etc.statementTag) == 0) + break; // Found a match. + } + begStmt = body.indexOf("{", begStmt + 1); + } + if (begStmt < 0) // In "a{for}c", begStmt will be 1. + break; + var endStmt = body.indexOf("}", begStmt + 1); // In "a{for}c", endStmt will be 5. + if (endStmt < 0) + break; + emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText); + emitStatement(body.substring(begStmt, endStmt +1), state, funcText, tmplName, etc); + endStmtPrev = endStmt; + } + emitSectionText(body.substring(endStmtPrev + 1), funcText); + if (state.stack.length != 0) + throw new etc.ParseError(tmplName, state.line, "unclosed, unmatched statement(s): " + state.stack.join(",")); + funcText.push("}}; TrimPath_Template_TEMP"); + return funcText.join(""); + } + + var emitStatement = function(stmtStr, state, funcText, tmplName, etc) { + var parts = stmtStr.slice(1, -1).split(' '); + var stmt = etc.statementDef[parts[0]]; // Here, parts[0] == for/if/else/... + if (stmt == null) { // Not a real statement. + emitSectionText(stmtStr, funcText); + return; + } + if (stmt.delta < 0) { + if (state.stack.length <= 0) + throw new etc.ParseError(tmplName, state.line, "close tag does not match any previous statement: " + stmtStr); + state.stack.pop(); + } + if (stmt.delta > 0) + state.stack.push(stmtStr); + + if (stmt.paramMin != null && + stmt.paramMin >= parts.length) + throw new etc.ParseError(tmplName, state.line, "statement needs more parameters: " + stmtStr); + if (stmt.prefixFunc != null) + funcText.push(stmt.prefixFunc(parts, state, tmplName, etc)); + else + funcText.push(stmt.prefix); + if (stmt.suffix != null) { + if (parts.length <= 1) { + if (stmt.paramDefault != null) + funcText.push(stmt.paramDefault); + } else { + for (var i = 1; i < parts.length; i++) { + if (i > 1) + funcText.push(' '); + funcText.push(parts[i]); + } + } + funcText.push(stmt.suffix); + } + } + + var emitSectionText = function(text, funcText) { + if (text.length <= 0) + return; + var nlPrefix = 0; // Index to first non-newline in prefix. + var nlSuffix = text.length - 1; // Index to first non-space/tab in suffix. + while (nlPrefix < text.length && (text.charAt(nlPrefix) == '\n')) + nlPrefix++; + while (nlSuffix >= 0 && (text.charAt(nlSuffix) == ' ' || text.charAt(nlSuffix) == '\t')) + nlSuffix--; + if (nlSuffix < nlPrefix) + nlSuffix = nlPrefix; + if (nlPrefix > 0) { + funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("'); + funcText.push(text.substring(0, nlPrefix).replace('\n', '\\n')); + funcText.push('");'); + } + var lines = text.substring(nlPrefix, nlSuffix + 1).split('\n'); + for (var i = 0; i < lines.length; i++) { + emitSectionTextLine(lines[i], funcText); + if (i < lines.length - 1) + funcText.push('_OUT.write("\\n");\n'); + } + if (nlSuffix + 1 < text.length) { + funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("'); + funcText.push(text.substring(nlSuffix + 1).replace('\n', '\\n')); + funcText.push('");'); + } + } + + var emitSectionTextLine = function(line, funcText) { + var endExprPrev = -1; + while (endExprPrev + 1 < line.length) { + var begExpr = line.indexOf("${", endExprPrev + 1); // In "a${b}c", begExpr == 1 + if (begExpr < 0) + break; + var endExpr = line.indexOf("}", begExpr + 2); // In "a${b}c", endExpr == 4; 2 == "${".length + if (endExpr < 0) + break; + emitText(line.substring(endExprPrev + 1, begExpr), funcText); + // Example: exprs == 'firstName|default:"John Doe"|capitalize'.split('|') + var exprArr = line.substring(begExpr + 2, endExpr).replace(/\|\|/g, "#@@#").split('|'); + for (var k in exprArr) + exprArr[k] = exprArr[k].replace(/#@@#/g, '||'); + funcText.push('_OUT.write('); + emitExpression(exprArr, exprArr.length - 1, funcText); + funcText.push(');'); + endExprPrev = endExpr; + } + emitText(line.substring(endExprPrev + 1), funcText); + } + + var emitText = function(text, funcText) { + if (text == null || + text.length <= 0) + return; + text = text.replace(/\\/g, '\\\\'); + text = text.replace(/"/g, '\\"'); + funcText.push('_OUT.write("'); + funcText.push(text); + funcText.push('");'); + } + + var emitExpression = function(exprArr, index, funcText) { + // Ex: foo|a:x|b:y1,y2|c:z1,z2 is emitted as c(b(a(foo,x),y1,y2),z1,z2) + var expr = exprArr[index]; // Ex: exprArr == [firstName,capitalize,default:"John Doe"] + if (index <= 0) { // Ex: expr == 'default:"John Doe"' + funcText.push(expr); + return; + } + var parts = expr.split(':'); + funcText.push('_MODIFIERS["'); + funcText.push(parts[0]); // The parts[0] is a modifier function name, like capitalize. + funcText.push('"]('); + emitExpression(exprArr, index - 1, funcText); + if (parts.length > 1) { + funcText.push(','); + funcText.push(parts[1]); + } + funcText.push(')'); + } + + var cleanWhiteSpace = function(result) { + result = result.replace(/\t/g, " "); + result = result.replace(/\r\n/g, "\n"); + result = result.replace(/\r/g, "\n"); + result = result.replace(/^(.*\S)[ \t]+$/gm, "$1"); // Right trim. + return result; + } + + // The DOM helper functions depend on DOM/DHTML, so they only work in a browser. + // However, these are not considered core to the engine. + // + TrimPath.parseDOMTemplate = function(elementId, optDocument, optEtc) { + if (optDocument == null) + optDocument = document; + var element = optDocument.getElementById(elementId); + var content = element.value; // Like textarea.value. + if (content == null) + content = element.innerHTML; // Like textarea.innerHTML. + content = content.replace(/</g, "<").replace(/>/g, ">"); + return TrimPath.parseTemplate(content, elementId, optEtc); + } + + TrimPath.processDOMTemplate = function(elementId, context, optFlags, optDocument, optEtc) { + return TrimPath.parseDOMTemplate(elementId, optDocument, optEtc).process(context, optFlags); + } +}) (); diff --git a/framework/Web/Javascripts/prototype/ajax.js b/framework/Web/Javascripts/prototype/ajax.js new file mode 100644 index 00000000..1fe0dab9 --- /dev/null +++ b/framework/Web/Javascripts/prototype/ajax.js @@ -0,0 +1,268 @@ +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || 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, + 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.options.onException || Prototype.emptyFunction)(this, e); + Ajax.Responders.dispatch('onException', this, e); + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', + 'application/x-www-form-urlencoded'); + + /* 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); + }, + + evalJSON: function() { + try { + var json = this.transport.getResponseHeader('X-JSON'), object; + object = eval(json); + return object; + } catch (e) { + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + } +}); + +Ajax.Updater = Class.create(); +Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)'; + +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 match = new RegExp(Ajax.Updater.ScriptFragment, 'img'); + var response = this.transport.responseText.replace(match, ''); + var scripts = this.transport.responseText.match(match); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + receiver.innerHTML = response; + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + + if (this.options.evalScripts && scripts) { + match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); + setTimeout((function() { + for (var i = 0; i < scripts.length; i++) + eval(scripts[i].match(match)[1]); + }).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); + } +}); diff --git a/framework/Web/Javascripts/prototype/array.js b/framework/Web/Javascripts/prototype/array.js new file mode 100644 index 00000000..397afbbf --- /dev/null +++ b/framework/Web/Javascripts/prototype/array.js @@ -0,0 +1,64 @@ +var $A = Array.from = function(iterable) { + 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); + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + 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.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 false; + }, + + reverse: function() { + var result = []; + for (var i = this.length; i > 0; i--) + result.push(this[i-1]); + return result; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); diff --git a/framework/Web/Javascripts/prototype/base.js b/framework/Web/Javascripts/prototype/base.js new file mode 100644 index 00000000..70f92add --- /dev/null +++ b/framework/Web/Javascripts/prototype/base.js @@ -0,0 +1,121 @@ +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (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(object) { + var __method = this; + return function() { + return __method.apply(object, 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; + } + } + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + return elements; +} diff --git a/framework/Web/Javascripts/prototype/compat.js b/framework/Web/Javascripts/prototype/compat.js new file mode 100644 index 00000000..09c32582 --- /dev/null +++ b/framework/Web/Javascripts/prototype/compat.js @@ -0,0 +1,27 @@ +if (!Array.prototype.push) { + Array.prototype.push = function() { + var startLength = this.length; + for (var i = 0; i < arguments.length; i++) + this[startLength + i] = arguments[i]; + return this.length; + } +} + +if (!Function.prototype.apply) { + // Based on code from http://www.youngpup.net/ + Function.prototype.apply = function(object, parameters) { + var parameterStrings = new Array(); + if (!object) object = window; + if (!parameters) parameters = new Array(); + + for (var i = 0; i < parameters.length; i++) + parameterStrings[i] = 'parameters[' + i + ']'; + + object.__apply__ = this; + var result = eval('object.__apply__(' + + parameterStrings.join(', ') + ')'); + object.__apply__ = null; + + return result; + } +} diff --git a/framework/Web/Javascripts/prototype/dom.js b/framework/Web/Javascripts/prototype/dom.js new file mode 100644 index 00000000..282fbfe0 --- /dev/null +++ b/framework/Web/Javascripts/prototype/dom.js @@ -0,0 +1,305 @@ +document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (Element.hasClassName(child, className)) + elements.push(child); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + 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); + }, + + 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) { + if(undef(element) || isNull(element)) return; + element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + node.parentNode.removeChild(node); + } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + 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; + }, + + 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; + } +}); + +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; + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + if (this.element.tagName.toLowerCase() == 'tbody') { + 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)]); + } + }, + + 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().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; + })); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); diff --git a/framework/Web/Javascripts/prototype/enumerable.js b/framework/Web/Javascripts/prototype/enumerable.js new file mode 100644 index 00000000..edeb297b --- /dev/null +++ b/framework/Web/Javascripts/prototype/enumerable.js @@ -0,0 +1,183 @@ +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) { + if (!(result &= (iterator || Prototype.K)(value, index))) + 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 (value >= (result || value)) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value <= (result || value)) + 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) { + iterator(value = collections.pluck(index)); + return value; + }); + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); diff --git a/framework/Web/Javascripts/prototype/event.js b/framework/Web/Javascripts/prototype/event.js new file mode 100644 index 00000000..fd5d1577 --- /dev/null +++ b/framework/Web/Javascripts/prototype/event.js @@ -0,0 +1,107 @@ +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, + + 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 */ +Event.observe(window, 'unload', Event.unloadCache, false); diff --git a/framework/Web/Javascripts/prototype/form.js b/framework/Web/Javascripts/prototype/form.js new file mode 100644 index 00000000..92a5d91a --- /dev/null +++ b/framework/Web/Javascripts/prototype/form.js @@ -0,0 +1,299 @@ +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).focus(); + $(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) { + var form = $(form); + var elements = new Array(); + + for (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) { + var 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 = ''; + } + }, + + focusFirstElement: function(form) { + var form = $(form); + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (element.type != 'hidden' && !element.disabled) { + Field.activate(element); + break; + } + } + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + var element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return encodeURIComponent(parameter[0]) + '=' + + encodeURIComponent(parameter[1]); + }, + + getValue: function(element) { + var 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) { + 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; + if (!value && !('value' in opt)) + value = opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) { + var optValue = opt.value; + if (!optValue && !('value' in opt)) + optValue = opt.text; + value.push(optValue); + } + } + 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': + element.target = this; + element.prev_onclick = element.onclick || Prototype.emptyFunction; + element.onclick = function() { + this.prev_onclick(); + this.target.onElementEvent(); + } + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + element.target = this; + element.prev_onchange = element.onchange || Prototype.emptyFunction; + element.onchange = function() { + this.prev_onchange(); + this.target.onElementEvent(); + } + 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); + } +}); + diff --git a/framework/Web/Javascripts/prototype/hash.js b/framework/Web/Javascripts/prototype/hash.js new file mode 100644 index 00000000..81a77f1c --- /dev/null +++ b/framework/Web/Javascripts/prototype/hash.js @@ -0,0 +1,47 @@ +var Hash = { + _each: function(iterator) { + for (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) { + 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; +} diff --git a/framework/Web/Javascripts/prototype/position.js b/framework/Web/Javascripts/prototype/position.js new file mode 100644 index 00000000..92e94da0 --- /dev/null +++ b/framework/Web/Javascripts/prototype/position.js @@ -0,0 +1,233 @@ +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]; + } +} + + diff --git a/framework/Web/Javascripts/prototype/prototype.js b/framework/Web/Javascripts/prototype/prototype.js new file mode 100644 index 00000000..8beb4d43 --- /dev/null +++ b/framework/Web/Javascripts/prototype/prototype.js @@ -0,0 +1,5 @@ +var Prototype = { + Version: '1.4.0_rc1', + emptyFunction: function() {}, + K: function(x) {return x} +} \ No newline at end of file diff --git a/framework/Web/Javascripts/prototype/range.js b/framework/Web/Javascripts/prototype/range.js new file mode 100644 index 00000000..f7c87166 --- /dev/null +++ b/framework/Web/Javascripts/prototype/range.js @@ -0,0 +1,29 @@ +var Range = Class.create(); +Object.extend(Range.prototype, Enumerable); +Object.extend(Range.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 Range(start, end, exclusive); +} \ No newline at end of file diff --git a/framework/Web/Javascripts/prototype/string.js b/framework/Web/Javascripts/prototype/string.js new file mode 100644 index 00000000..c869e7db --- /dev/null +++ b/framework/Web/Javascripts/prototype/string.js @@ -0,0 +1,53 @@ +Object.extend(String.prototype, { + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + 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('\\', '\\\\').replace("'", '\\\'') + "'"; + } +}); + +String.prototype.parseQuery = String.prototype.toQueryParams; diff --git a/framework/Web/Javascripts/tests/CompareValidator.html b/framework/Web/Javascripts/tests/CompareValidator.html new file mode 100644 index 00000000..1a41fd11 --- /dev/null +++ b/framework/Web/Javascripts/tests/CompareValidator.html @@ -0,0 +1,95 @@ + + + + + + + Prado Client-Side CompareValidator Tests + + + + + + + + + +

    Prado Client-Side CompareValidator Tests

    + +
    + + +
    +
    + + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    + + + + diff --git a/framework/Web/Javascripts/tests/CustomValidator.html b/framework/Web/Javascripts/tests/CustomValidator.html new file mode 100644 index 00000000..34303781 --- /dev/null +++ b/framework/Web/Javascripts/tests/CustomValidator.html @@ -0,0 +1,74 @@ + + + + + + + Prado Client-Side CustomValidator Tests + + + + + + + + + +

    Prado Client-Side CustomValidator Tests

    + +
    + + +
    + + + +
    + +
    + + + + diff --git a/framework/Web/Javascripts/tests/DatePicker.html b/framework/Web/Javascripts/tests/DatePicker.html new file mode 100644 index 00000000..d173006d --- /dev/null +++ b/framework/Web/Javascripts/tests/DatePicker.html @@ -0,0 +1,99 @@ + + + + + Prado Date Picker + + + + + + + + +
    + + +
    + + +as
    +dv
    +as
    +d
    +as
    +d
    +asd
    +a
    +sd
    +dv
    +as
    +d
    +as
    +d
    +asd
    +a
    +sd
    +dv
    +as
    +d
    +as
    +d
    +asd
    +a
    +sd
    +
    + + +
    +d
    +as
    +d
    +asd
    +a
    +sd
    +dv
    +as
    +d
    +as
    +d
    +asd
    +a
    +sd
    + + diff --git a/framework/Web/Javascripts/tests/Effects.html b/framework/Web/Javascripts/tests/Effects.html new file mode 100644 index 00000000..6ee5324b --- /dev/null +++ b/framework/Web/Javascripts/tests/Effects.html @@ -0,0 +1,124 @@ + + + + + Effects demo page + + + + + + + +

    Effect.Fade()

    + +

    Call with new Effect.Fade(element)

    +

    Example: onclick="new Effect.Fade(this)"

    + +
    + Click here or the image to start effect +
    + + +

    Effect.Highlight()

    + +

    Call with new Effect.Highlight(element)

    +

    Example: onclick="new Effect.Highlight(this)"

    + +
    + Click here or the image to start effect +
    + +

    Effect.Appear()

    + +

    Call with new Effect.Appear(element)

    +

    Example: onclick="new Effect.Appear('appear')"

    + +Start effect + + + +

    Effect.Scale()

    + +

    Call with new Effect.Scale(element, percent)

    +

    Note: if you scale a div, all contained elements must have width or height set with em. If you +scale an image, width and height are not required to be set. Also, Effect.Scale is aware of other scaling effects done on +the target element, and will act accordingly. The percent parameter is always relative of the original size of the element.

    +

    Example: onmouseover="new Effect.Scale('scale',150)" onmouseout="new Effect.Scale('scale',100)"

    + +
    + + + + +
    + +

    Effect.Squish()

    +

    Call with new Effect.Squish(element)

    +
    + Click me to squish.
    + + +
    + +

    Effect.Puff()

    +

    Call with new Effect.Puff(element)

    +

    Works with absolute and relative positioned elements. In this example, divs with float:left are used.

    +
    + (floating) Click me to puff.
    + + +
    +
    + (floating) Click me to puff.
    + +
    +
    + (floating) Click me to puff.
    + +
    +
    + (floating) Click me to puff.
    + + +
    +
    + (floating) Click me to puff.
    + +
    +
    + (floating) Click me to puff.
    + +
    + +
    + (floating) Click me to puff.
    + +
    +
    + (floating) Click me to puff.
    + +
    +
    + (floating) Click me to puff.
    + + +
    +
    + (floating) Click me to puff.
    + +
    +
    + (floating) Click me to puff.
    + +
    + +
    + (floating) Click me to puff.
    + +
    + + + \ No newline at end of file diff --git a/framework/Web/Javascripts/tests/Form.disable.html b/framework/Web/Javascripts/tests/Form.disable.html new file mode 100644 index 00000000..5ff5f7d6 --- /dev/null +++ b/framework/Web/Javascripts/tests/Form.disable.html @@ -0,0 +1,37 @@ + + + + + Form disable test + + + + + + +
    +Number + + + +
    + + + yes + no + + +
    +

    +disable +enable diff --git a/framework/Web/Javascripts/tests/Insertion.html b/framework/Web/Javascripts/tests/Insertion.html new file mode 100644 index 00000000..1aefd3aa --- /dev/null +++ b/framework/Web/Javascripts/tests/Insertion.html @@ -0,0 +1,47 @@ + + + + + + + + + + +

    + content +
    +
    + + + +
    + + \ No newline at end of file diff --git a/framework/Web/Javascripts/tests/PradoTestSuite.html b/framework/Web/Javascripts/tests/PradoTestSuite.html new file mode 100644 index 00000000..8b3c8ccf --- /dev/null +++ b/framework/Web/Javascripts/tests/PradoTestSuite.html @@ -0,0 +1,37 @@ + + + + + + Prado Javascript Test Suite + + + + + + +

    Prado Javascript Test Suite

    + +

    This page contains a suite of tests for testing + Prado javascripts.

    + + + diff --git a/framework/Web/Javascripts/tests/RangeValidator.html b/framework/Web/Javascripts/tests/RangeValidator.html new file mode 100644 index 00000000..0dd3c283 --- /dev/null +++ b/framework/Web/Javascripts/tests/RangeValidator.html @@ -0,0 +1,65 @@ + + + + + + + Prado Client-Side RangeValidator Tests + + + + + + + + + +

    Prado Client-Side RangeValidator Tests

    + +
    + + +
    + + + +
    + +
    + + + + diff --git a/framework/Web/Javascripts/tests/RegularExpressionValidator.html b/framework/Web/Javascripts/tests/RegularExpressionValidator.html new file mode 100644 index 00000000..619ec6f5 --- /dev/null +++ b/framework/Web/Javascripts/tests/RegularExpressionValidator.html @@ -0,0 +1,72 @@ + + + + + + + Prado Client-Side RegularExpressionValidator Tests + + + + + + + + + +

    Prado Client-Side RegularExpressionValidator Tests

    + +
    + + +
    + + + + + + +
    + +
    + + + + diff --git a/framework/Web/Javascripts/tests/RequiredFieldValidator.html b/framework/Web/Javascripts/tests/RequiredFieldValidator.html new file mode 100644 index 00000000..ae99d355 --- /dev/null +++ b/framework/Web/Javascripts/tests/RequiredFieldValidator.html @@ -0,0 +1,95 @@ + + + + + + + Prado Client-Side RequiredFieldValidator Tests + + + + + +

    Prado Client-Side RequiredFieldValidator Tests

    + +
    + + + + + + + +
    +
    + + + + + + + + +
    + +
    +
    + + + + + + + + +
    + +
    + + + + + + + + +
    \ No newline at end of file diff --git a/framework/Web/Javascripts/tests/RequiredListValidator.html b/framework/Web/Javascripts/tests/RequiredListValidator.html new file mode 100644 index 00000000..7e79e4af --- /dev/null +++ b/framework/Web/Javascripts/tests/RequiredListValidator.html @@ -0,0 +1,110 @@ + + + + + + + Prado Client-Side RequiredListValidator Tests + + + + + + + + + + + + + + + + + +

    Prado Client-Side RequiredListValidator Tests

    + +
    + + +
    +
    + 1 + 2 + 3 + 4 + +
    + +
    + + +
    + + +
    + +
    + + + + diff --git a/framework/Web/Javascripts/tests/ValidationTests.html b/framework/Web/Javascripts/tests/ValidationTests.html new file mode 100644 index 00000000..67c9aecb --- /dev/null +++ b/framework/Web/Javascripts/tests/ValidationTests.html @@ -0,0 +1,79 @@ + + + + + + Prado Client-Side Validation Tests + + + + + + + + + + + + +

    Prado Client-Side Validation Tests

    + +

    This page contains tests for the utility functions + that JsUnit uses. To see them, take a look at the source.

    + + + diff --git a/framework/Web/Javascripts/tests/calendar_system.css b/framework/Web/Javascripts/tests/calendar_system.css new file mode 100644 index 00000000..a797372e --- /dev/null +++ b/framework/Web/Javascripts/tests/calendar_system.css @@ -0,0 +1,70 @@ +div.Prado_Calendar +{ + border: 1px solid WindowText; + position: absolute; + text-align: center; + background-color: Window; + z-index: 1000; + font: small-caption; + font-weight: normal; + width: 20em; +} + +div.Prado_Calendar .calendarHeader +{ + background-color: ActiveCaption; + padding: 1px; + border-bottom: 1px solid WindowText; +} + +div.Prado_Calendar table +{ + width: 100%; +} + +div.Prado_Calendar .date +{ + font-weight: normal; + cursor: pointer; +} + +div.Prado_Calendar .selected +{ + border: 1px solid WindowText; +} + +div.Prado_Calendar .today +{ + font-weight: bold; +} + +div.Prado_Calendar .current +{ + border: 1px dotted WindowText; +} + +div.Prado_Calendar .calendarBody td +{ + padding: 2px 0; +} + +div.Prado_Calendar .hover +{ + background-color: Highlight; + color: HighlightText; +} + +div.Prado_Calendar td.empty +{ + background-color: Window; +} + +div.Prado_Calendar .grid td +{ + width: 14%; +} + +div.Prado_Calendar .grid +{ + border-spacing: 0; +} \ No newline at end of file diff --git a/framework/Web/Javascripts/tests/compression.html b/framework/Web/Javascripts/tests/compression.html new file mode 100644 index 00000000..66e10a97 --- /dev/null +++ b/framework/Web/Javascripts/tests/compression.html @@ -0,0 +1,18 @@ + + + + +Test all compressed JS + + + + + + + + + +OK + + diff --git a/framework/Web/Javascripts/tests/console.html b/framework/Web/Javascripts/tests/console.html new file mode 100644 index 00000000..e6aed30f --- /dev/null +++ b/framework/Web/Javascripts/tests/console.html @@ -0,0 +1,30 @@ + + + + Prototype Console + + + + +
    + + +
    +
    +
    + + diff --git a/framework/Web/Javascripts/tests/fungii_logo.gif b/framework/Web/Javascripts/tests/fungii_logo.gif new file mode 100644 index 00000000..b667c73e Binary files /dev/null and b/framework/Web/Javascripts/tests/fungii_logo.gif differ diff --git a/framework/Web/Javascripts/tests/getElementsByClassName.html b/framework/Web/Javascripts/tests/getElementsByClassName.html new file mode 100644 index 00000000..b764aac4 --- /dev/null +++ b/framework/Web/Javascripts/tests/getElementsByClassName.html @@ -0,0 +1,28 @@ + + + + + + + +
    a: foo
    +
    b: foo, bar
    +
    c: bar
    +
    d: bar, baz
    +
    e: baz
    +
    f: foo, baz
    + +
    + + + +
    + + diff --git a/framework/Web/Javascripts/tests/getElementsBySelector.html b/framework/Web/Javascripts/tests/getElementsBySelector.html new file mode 100644 index 00000000..e8f716fc --- /dev/null +++ b/framework/Web/Javascripts/tests/getElementsBySelector.html @@ -0,0 +1,55 @@ + + + + +document.getElementsBySelector Demo + + + + + + +

    document.getElementsBySelector Demo

    +

    See this blog entry for more information.

    +

    Here are some links in a normal paragraph: Google, Google Groups. This link has class="blog": diveintomark

    +
    +

    Everything inside the red border is inside a div with id="foo".

    +

    This is a normal link: Yahoo

    + +

    This link has class="blog": Simon Willison's Weblog

    +
    + +
    +

    Try them out:

    +
      +
    • +
    • +
    • +
    • +
    • +
    • +
    • (fails in Opera 7)
    • +
    • +
    • +
    • +
    +
    + + diff --git a/framework/Web/Javascripts/tests/index.html b/framework/Web/Javascripts/tests/index.html new file mode 100644 index 00000000..2ca89cc6 --- /dev/null +++ b/framework/Web/Javascripts/tests/index.html @@ -0,0 +1,138 @@ + + + + + + + + + + +Selenium Functional Test Runner + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Selenium TestRunner

    +
    + + + + + + + + + + + +
    + Run: + + +
    + Mode: + + + +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Elapsed:00.00
    TestsCommands
    0run0passed
    0failed0failed
    0incomplete
    +
    + +
    +

    Javascript Log Console (Close Clear)

    +
      +
    + + + diff --git a/framework/Web/Javascripts/tests/librarytest.html b/framework/Web/Javascripts/tests/librarytest.html new file mode 100644 index 00000000..14a024a2 --- /dev/null +++ b/framework/Web/Javascripts/tests/librarytest.html @@ -0,0 +1,49 @@ + + + + +Test all compressed JS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +OK + + diff --git a/framework/Web/Javascripts/tests/test_scripts/TestRequiredFieldValidator.html b/framework/Web/Javascripts/tests/test_scripts/TestRequiredFieldValidator.html new file mode 100644 index 00000000..f9895a23 --- /dev/null +++ b/framework/Web/Javascripts/tests/test_scripts/TestRequiredFieldValidator.html @@ -0,0 +1,85 @@ + + + +Test Open + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Test Open
    +
    open../tests/RequiredFieldValidator.html 
    assertLocationRequiredFieldValidator.html 
    assertTextPresent RequiredFieldValidator Tests +  
    assertNotVisiblevalidator1
    assertNotVisiblevalidator2
    clicksubmit1
    assertVisiblevalidator1
    assertVisiblevalidator2
    typetext1testing
    clicksubmit1
    assertNotVisiblevalidator1
    clicksubmit2
    assertNotVisiblevalidator1
    assertNotVisiblevalidator2
    assertVisiblevalidator3
    assertVisiblevalidator4
    + + \ No newline at end of file diff --git a/framework/Web/Javascripts/tests/test_scripts/TestSuite.html b/framework/Web/Javascripts/tests/test_scripts/TestSuite.html new file mode 100644 index 00000000..9b8b8918 --- /dev/null +++ b/framework/Web/Javascripts/tests/test_scripts/TestSuite.html @@ -0,0 +1,36 @@ + + + + +Test Suite + + + + + + + + + + +
    Test Suite
    TestTRequiredFieldValidator
    + + + -- cgit v1.2.3