summaryrefslogtreecommitdiff
path: root/framework/Web
diff options
context:
space:
mode:
authorxue <>2005-12-05 01:00:16 +0000
committerxue <>2005-12-05 01:00:16 +0000
commitccf76e430b7703db028966a845a966f50956f490 (patch)
tree9762b746f8b7d432dbe5e5cb8f38f90007e0e1b5 /framework/Web
parent418baf36d477bcbdd6fb4eaf4037ea6a2d93f21c (diff)
Diffstat (limited to 'framework/Web')
-rw-r--r--framework/Web/Javascripts/base/ajax.js355
-rw-r--r--framework/Web/Javascripts/base/controls.js179
-rw-r--r--framework/Web/Javascripts/base/datepicker.js928
-rw-r--r--framework/Web/Javascripts/base/focus.js99
-rw-r--r--framework/Web/Javascripts/base/json.js340
-rw-r--r--framework/Web/Javascripts/base/postback.js72
-rw-r--r--framework/Web/Javascripts/base/prado.js3
-rw-r--r--framework/Web/Javascripts/base/scroll.js0
-rw-r--r--framework/Web/Javascripts/base/validation.js675
-rw-r--r--framework/Web/Javascripts/base/validators.js228
-rw-r--r--framework/Web/Javascripts/build.php144
-rw-r--r--framework/Web/Javascripts/custom_rhino.jarbin0 -> 731885 bytes
-rw-r--r--framework/Web/Javascripts/effects/controls.js711
-rw-r--r--framework/Web/Javascripts/effects/dragdrop.js516
-rw-r--r--framework/Web/Javascripts/effects/effects.js811
-rw-r--r--framework/Web/Javascripts/effects/rico.js2289
-rw-r--r--framework/Web/Javascripts/effects/slider.js258
-rw-r--r--framework/Web/Javascripts/effects/util.js548
-rw-r--r--framework/Web/Javascripts/extended/array.js465
-rw-r--r--framework/Web/Javascripts/extended/base.js24
-rw-r--r--framework/Web/Javascripts/extended/dom.js6
-rw-r--r--framework/Web/Javascripts/extended/event.js24
-rw-r--r--framework/Web/Javascripts/extended/functional.js171
-rw-r--r--framework/Web/Javascripts/extended/string.js37
-rw-r--r--framework/Web/Javascripts/extended/util.js90
-rw-r--r--framework/Web/Javascripts/extra/behaviour.js83
-rw-r--r--framework/Web/Javascripts/extra/getElementsBySelector.js176
-rw-r--r--framework/Web/Javascripts/extra/logger.js659
-rw-r--r--framework/Web/Javascripts/extra/tp_template.js315
-rw-r--r--framework/Web/Javascripts/prototype/ajax.js268
-rw-r--r--framework/Web/Javascripts/prototype/array.js64
-rw-r--r--framework/Web/Javascripts/prototype/base.js121
-rw-r--r--framework/Web/Javascripts/prototype/compat.js27
-rw-r--r--framework/Web/Javascripts/prototype/dom.js305
-rw-r--r--framework/Web/Javascripts/prototype/enumerable.js183
-rw-r--r--framework/Web/Javascripts/prototype/event.js107
-rw-r--r--framework/Web/Javascripts/prototype/form.js299
-rw-r--r--framework/Web/Javascripts/prototype/hash.js47
-rw-r--r--framework/Web/Javascripts/prototype/position.js233
-rw-r--r--framework/Web/Javascripts/prototype/prototype.js5
-rw-r--r--framework/Web/Javascripts/prototype/range.js29
-rw-r--r--framework/Web/Javascripts/prototype/string.js53
-rw-r--r--framework/Web/Javascripts/tests/CompareValidator.html95
-rw-r--r--framework/Web/Javascripts/tests/CustomValidator.html74
-rw-r--r--framework/Web/Javascripts/tests/DatePicker.html99
-rw-r--r--framework/Web/Javascripts/tests/Effects.html124
-rw-r--r--framework/Web/Javascripts/tests/Form.disable.html37
-rw-r--r--framework/Web/Javascripts/tests/Insertion.html47
-rw-r--r--framework/Web/Javascripts/tests/PradoTestSuite.html37
-rw-r--r--framework/Web/Javascripts/tests/RangeValidator.html65
-rw-r--r--framework/Web/Javascripts/tests/RegularExpressionValidator.html72
-rw-r--r--framework/Web/Javascripts/tests/RequiredFieldValidator.html95
-rw-r--r--framework/Web/Javascripts/tests/RequiredListValidator.html110
-rw-r--r--framework/Web/Javascripts/tests/ValidationTests.html79
-rw-r--r--framework/Web/Javascripts/tests/calendar_system.css70
-rw-r--r--framework/Web/Javascripts/tests/compression.html18
-rw-r--r--framework/Web/Javascripts/tests/console.html30
-rw-r--r--framework/Web/Javascripts/tests/fungii_logo.gifbin0 -> 5473 bytes
-rw-r--r--framework/Web/Javascripts/tests/getElementsByClassName.html28
-rw-r--r--framework/Web/Javascripts/tests/getElementsBySelector.html55
-rw-r--r--framework/Web/Javascripts/tests/index.html138
-rw-r--r--framework/Web/Javascripts/tests/librarytest.html49
-rw-r--r--framework/Web/Javascripts/tests/test_scripts/TestRequiredFieldValidator.html85
-rw-r--r--framework/Web/Javascripts/tests/test_scripts/TestSuite.html36
-rw-r--r--framework/Web/UI/WebControls/TPanel.php16
65 files changed, 13405 insertions, 1 deletions
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<args.length; i++)
+ this.post.data['__parameters'][i] = args[i];
+ }
+});
+
+/**
+ * Base proxy class for Prado RemoteObjects via AJAX.
+ * e.g.
+ * <code>
+ * 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);
+ * }
+ * });
+ *</code>
+ * And client usage,
+ * <code>
+ * 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.
+ * </code>
+ */
+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.length; i++)
+ {
+ msg += " #"+i+" "+trace[i].file;
+ msg += "("+trace[i].line+"): ";
+ msg += trace[i]["class"]+"->"+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: <code>new Prado.AJAX.Callback('MyPage.MyComponentID.raiseCallbackEvent', options)</code>
+ * These classes should be called by the components developers.
+ * For inline callback service, use <t>Prado.Callback(callbackID, params)</t>.
+ */
+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, <t>ClassName.ComponentID.MethodName</t>
+ * @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; i<IDs.length; i++)
+ this.__service.post.data['__data'][IDs[i]] = $F(IDs[i]);
+ },
+
+ /**
+ * Prepares and calls the AJAX request.
+ * Collects the data from components that implements IPostBackHandler
+ * and the viewstate as part of the request payload.
+ */
+ requestCallback : function()
+ {
+ this.collectPostData();
+ return this.__call(Prado.AJAX.Callback.Server, 'handleCallback', this.options.params);
+ },
+
+ /**
+ * On callback request return, call the onSuccess function.
+ */
+ handleCallback : function(result, output)
+ {
+ this.options.onSuccess(result, output);
+ }
+});
+
+//Available callback service
+Prado.AJAX.Callback.Server = '';
+
+//List of IDs that implements IPostBackHandler
+Prado.AJAX.Callback.IDs = [];
+
+/**
+ * Simple AJAX callback interface, suitable for inline javascript.
+ * e.g., <code><a href="..." onclick="Prado.Callback('..', 'Hello');">Click me</a></code>
+ * @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: <t>new Prado.AutoCompleter('textboxID', 'updateDivID', {callbackID : '...'});
+ */
+Prado.AutoCompleter.prototype = Object.extend(new Autocompleter.Base(),
+{
+ /**
+ * This component is initialized by
+ * <code>new Prado.AutoCompleter(...)</code>
+ * @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
+ * <code>
+ * Prado.ActivePanel.register("id", options);
+ * Prado.ActivePanel.update("id", "hello");
+ * </code>
+ */
+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<children.length;i++)
+ {
+ try
+ {
+ if(Prado.Focus.canFocusOn(children[i]))
+ {
+ return children[i];
+ }
+ else
+ {
+ var target = Prado.Focus.findTarget(children[i]);
+ if(target)
+ {
+ return target;
+ }
+ }
+ }
+ catch (e)
+ {
+ }
+ }
+ }
+ return null;
+}
+
+Prado.Focus.isVisible = function(element)
+{
+ var current = element;
+ while((typeof(current) != "undefined") && (current != null))
+ {
+ if(current.disabled || (typeof(current.style) != "undefined" && ((typeof(current.style.display) != "undefined" && current.style.display == "none") || (typeof(current.style.visibility) != "undefined" && current.style.visibility == "hidden") )))
+ {
+ return false;
+ }
+ if(typeof(current.parentNode) != "undefined" && current.parentNode != null && current.parentNode != current && current.parentNode.tagName.toLowerCase() != "body")
+ {
+ current = current.parentNode;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ return true;
+}
diff --git a/framework/Web/Javascripts/base/json.js b/framework/Web/Javascripts/base/json.js
new file mode 100644
index 00000000..0981169d
--- /dev/null
+++ b/framework/Web/Javascripts/base/json.js
@@ -0,0 +1,340 @@
+/*
+Copyright (c) 2005 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+Array.prototype.______array = '______array';
+
+Prado.AJAX.JSON = {
+ org: 'http://www.JSON.org',
+ copyright: '(c)2005 JSON.org',
+ license: 'http://www.crockford.com/JSON/license.html',
+
+ stringify: function (arg) {
+ var c, i, l, s = '', v;
+
+ switch (typeof arg) {
+ case 'object':
+ if (arg) {
+ if (arg.______array == '______array') {
+ for (i = 0; i < arg.length; ++i) {
+ v = this.stringify(arg[i]);
+ if (s) {
+ s += ',';
+ }
+ s += v;
+ }
+ return '[' + s + ']';
+ } else if (typeof arg.toString != 'undefined') {
+ for (i in arg) {
+ v = arg[i];
+ if (typeof v != 'undefined' && typeof v != 'function') {
+ v = this.stringify(v);
+ if (s) {
+ s += ',';
+ }
+ s += this.stringify(i) + ':' + v;
+ }
+ }
+ return '{' + s + '}';
+ }
+ }
+ return 'null';
+ case 'number':
+ return isFinite(arg) ? String(arg) : 'null';
+ case 'string':
+ l = arg.length;
+ s = '"';
+ for (i = 0; i < l; i += 1) {
+ c = arg.charAt(i);
+ if (c >= ' ') {
+ if (c == '\\' || c == '"') {
+ s += '\\';
+ }
+ s += c;
+ } else {
+ switch (c) {
+ case '\b':
+ s += '\\b';
+ break;
+ case '\f':
+ s += '\\f';
+ break;
+ case '\n':
+ s += '\\n';
+ break;
+ case '\r':
+ s += '\\r';
+ break;
+ case '\t':
+ s += '\\t';
+ break;
+ default:
+ c = c.charCodeAt();
+ s += '\\u00' + Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }
+ }
+ }
+ return s + '"';
+ case 'boolean':
+ return String(arg);
+ default:
+ return 'null';
+ }
+ },
+ parse: function (text) {
+ var at = 0;
+ var ch = ' ';
+
+ function error(m) {
+ throw {
+ name: 'JSONError',
+ message: m,
+ at: at - 1,
+ text: text
+ };
+ }
+
+ function next() {
+ ch = text.charAt(at);
+ at += 1;
+ return ch;
+ }
+
+ function white() {
+ while (ch) {
+ if (ch <= ' ') {
+ next();
+ } else if (ch == '/') {
+ switch (next()) {
+ case '/':
+ while (next() && ch != '\n' && ch != '\r') {}
+ break;
+ case '*':
+ next();
+ for (;;) {
+ if (ch) {
+ if (ch == '*') {
+ if (next() == '/') {
+ next();
+ break;
+ }
+ } else {
+ next();
+ }
+ } else {
+ error("Unterminated comment");
+ }
+ }
+ break;
+ default:
+ error("Syntax error");
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ function string() {
+ var i, s = '', t, u;
+
+ if (ch == '"') {
+outer: while (next()) {
+ if (ch == '"') {
+ next();
+ return s;
+ } else if (ch == '\\') {
+ switch (next()) {
+ case 'b':
+ s += '\b';
+ break;
+ case 'f':
+ s += '\f';
+ break;
+ case 'n':
+ s += '\n';
+ break;
+ case 'r':
+ s += '\r';
+ break;
+ case 't':
+ s += '\t';
+ break;
+ case 'u':
+ u = 0;
+ for (i = 0; i < 4; i += 1) {
+ t = parseInt(next(), 16);
+ if (!isFinite(t)) {
+ break outer;
+ }
+ u = u * 16 + t;
+ }
+ s += String.fromCharCode(u);
+ break;
+ default:
+ s += ch;
+ }
+ } else {
+ s += ch;
+ }
+ }
+ }
+ error("Bad string");
+ }
+
+ function array() {
+ var a = [];
+
+ if (ch == '[') {
+ next();
+ white();
+ if (ch == ']') {
+ next();
+ return a;
+ }
+ while (ch) {
+ a.push(value());
+ white();
+ if (ch == ']') {
+ next();
+ return a;
+ } else if (ch != ',') {
+ break;
+ }
+ next();
+ white();
+ }
+ }
+ error("Bad array");
+ }
+
+ function object() {
+ var k, o = {};
+
+ if (ch == '{') {
+ next();
+ white();
+ if (ch == '}') {
+ next();
+ return o;
+ }
+ while (ch) {
+ k = string();
+ white();
+ if (ch != ':') {
+ break;
+ }
+ next();
+ o[k] = value();
+ white();
+ if (ch == '}') {
+ next();
+ return o;
+ } else if (ch != ',') {
+ break;
+ }
+ next();
+ white();
+ }
+ }
+ error("Bad object");
+ }
+
+ function number() {
+ var n = '', v;
+ if (ch == '-') {
+ n = '-';
+ next();
+ }
+ while (ch >= '0' && ch <= '9') {
+ n += ch;
+ next();
+ }
+ if (ch == '.') {
+ n += '.';
+ while (next() && ch >= '0' && ch <= '9') {
+ n += ch;
+ }
+ }
+ if (ch == 'e' || ch == 'E') {
+ n += 'e';
+ next();
+ if (ch == '-' || ch == '+') {
+ n += ch;
+ next();
+ }
+ while (ch >= '0' && ch <= '9') {
+ n += ch;
+ next();
+ }
+ }
+ v = +n;
+ if (!isFinite(v)) {
+ ////error("Bad number");
+ } else {
+ return v;
+ }
+ }
+
+ function word() {
+ switch (ch) {
+ case 't':
+ if (next() == 'r' && next() == 'u' && next() == 'e') {
+ next();
+ return true;
+ }
+ break;
+ case 'f':
+ if (next() == 'a' && next() == 'l' && next() == 's' &&
+ next() == 'e') {
+ next();
+ return false;
+ }
+ break;
+ case 'n':
+ if (next() == 'u' && next() == 'l' && next() == 'l') {
+ next();
+ return null;
+ }
+ break;
+ }
+ error("Syntax error");
+ }
+
+ function value() {
+ white();
+ switch (ch) {
+ case '{':
+ return object();
+ case '[':
+ return array();
+ case '"':
+ return string();
+ case '-':
+ return number();
+ default:
+ return ch >= '0' && ch <= '9' ? number() : word();
+ }
+ }
+
+ return value();
+ }
+}; \ 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
--- /dev/null
+++ b/framework/Web/Javascripts/base/scroll.js
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. <b>Internationalization
+ * is not supported</b>
+ * @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.
+ * <i>The currency input format is <b>very</b> strict, null will be returned if
+ * the pattern does not match</i>.
+ * @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
+ * <code>new Prado.Validation(Prado.Validation.RequiredFieldValidator, options);</code>
+ * or to use the CustomValidator,
+ * <code>new Prado.Validation(Prado.Validation.CustomValidator, options);</code>
+ */
+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 : "<br />", first : "", pre : "", post : "<br />", last : ""};
+ case "SingleParagraph":
+ return { header : " ", first : "", pre : "", post : " ", last : "<br />"};
+ case "BulletList":
+ default:
+ return { header : "", first : "<ul>", pre : "<li>", post : "</li>", last : "</ul>"};
+ }
+ },
+
+ /**
+ * Format the message summary.
+ * @param {array} list of error messages.
+ * @type {string} formatted message
+ */
+ formatSummary : function(messages)
+ {
+ var format = this.formats(this.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
+<?php
+/**
+ * Javascript build file.
+ *
+ * This script compresses a list of javascript source files
+ * and merges them into a few for redistribution.
+ *
+ * This script should be run from command line with PHP.
+ * JRE 1.4 or above is required in order to run the js compression program.
+ *
+ * By default, all libraries will be built.
+ * You may, however, specify one or several to be built (to save time during development).
+ * To do so, pass the library names (without .js) as command line arguments.
+ * For example: php build.php base dom
+ *
+ * @author Xiang Wei Zhuo <weizhuo@gmail.com>, Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 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
--- /dev/null
+++ b/framework/Web/Javascripts/custom_rhino.jar
Binary files 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,
+ '<iframe id="' + this.update.id + '_iefix" '+
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+ 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+ this.iefix = $(this.update.id+'_iefix');
+ }
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+ },
+
+ fixIEOverlapping: function() {
+ Position.clone(this.update, this.iefix);
+ 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<this.options.tokens.length; i++) {
+ var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
+ if (thisTokenPos > lastTokenPos)
+ lastTokenPos = thisTokenPos;
+ }
+ return lastTokenPos;
+ }
+}
+
+/*
+Ajax.Autocompleter = Class.create();
+Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
+ initialize: function(element, update, url, options) {
+ this.baseInitialize(element, update, options);
+ this.options.asynchronous = true;
+ this.options.onComplete = this.onComplete.bind(this);
+ this.options.defaultParams = this.options.parameters || null;
+ this.url = url;
+ },
+
+ getUpdatedChoices: function() {
+ entry = encodeURIComponent(this.options.paramName) + '=' +
+ encodeURIComponent(this.getToken());
+
+ this.options.parameters = this.options.callback ?
+ this.options.callback(this.element, entry) : entry;
+
+ if(this.options.defaultParams)
+ this.options.parameters += '&' + this.options.defaultParams;
+
+ new Ajax.Request(this.url, this.options);
+ },
+
+ onComplete: function(request) {
+ this.updateChoices(request.responseText);
+ }
+
+});
+*/
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+// text only at the beginning of strings in the
+// autocomplete array. Defaults to true, which will
+// match text at the beginning of any *word* in the
+// strings in the autocomplete array. If you want to
+// search anywhere in the string, additionally set
+// the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+// a partial match (unlike minChars, which defines
+// how many characters are required to do any match
+// at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+// Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector'
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create();
+Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
+ initialize: function(element, update, array, options) {
+ this.baseInitialize(element, update, options);
+ this.options.array = array;
+ },
+
+ getUpdatedChoices: function() {
+ this.updateChoices(this.options.selector(this));
+ },
+
+ setOptions: function(options) {
+ this.options = Object.extend({
+ choices: 10,
+ partialSearch: true,
+ partialChars: 2,
+ ignoreCase: true,
+ fullSearch: false,
+ selector: function(instance) {
+ var ret = []; // Beginning matches
+ var partial = []; // Inside matches
+ var entry = instance.getToken();
+ var count = 0;
+
+ for (var i = 0; i < instance.options.array.length &&
+ ret.length < instance.options.choices ; i++) {
+
+ var elem = instance.options.array[i];
+ var foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase()) :
+ elem.indexOf(entry);
+
+ while (foundPos != -1) {
+ if (foundPos == 0 && elem.length != entry.length) {
+ ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
+ elem.substr(entry.length) + "</li>");
+ break;
+ } else if (entry.length >= instance.options.partialChars &&
+ instance.options.partialSearch && foundPos != -1) {
+ if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+ partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+ elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+ foundPos + entry.length) + "</li>");
+ break;
+ }
+ }
+
+ foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
+ elem.indexOf(entry, foundPos + 1);
+
+ }
+ }
+ if (partial.length)
+ ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
+ return "<ul>" + ret.join('') + "</ul>";
+ }
+ }, options || {});
+ }
+});
+
+// AJAX in-place editor
+//
+// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
+/*
+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(/<br/i) || string.match(/<p>/i);
+ },
+ convertHTMLLineBreaks: function(string) {
+ return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
+ },
+ createEditField: function() {
+ var text;
+ if(this.options.loadTextURL) {
+ text = this.options.loadingText;
+ } else {
+ text = this.getText();
+ }
+
+ 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<this.length; i++ )
+ if( i != dx )
+ this[n++]=this[i];
+ this.length-=1;
+ };
+ Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.remove;
+}
+
+if (!Array.prototype.removeItem) {
+ Array.prototype.removeItem = function(item) {
+ for ( var i = 0 ; i < this.length ; i++ )
+ if ( this[i] == item ) {
+ this.remove(i);
+ break;
+ }
+ };
+ Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.removeItem;
+}
+
+if (!Array.prototype.indices) {
+ Array.prototype.indices = function() {
+ var indexArray = new Array();
+ for ( index in this ) {
+ var ignoreThis = false;
+ for ( var i = 0 ; i < Rico.ArrayExtensions.length ; i++ ) {
+ if ( this[index] == Rico.ArrayExtensions[i] ) {
+ ignoreThis = true;
+ break;
+ }
+ }
+ if ( !ignoreThis )
+ indexArray[ indexArray.length ] = index;
+ }
+ return indexArray;
+ }
+ Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.indices;
+}
+
+// Create the loadXML method and xml getter for Mozilla
+if ( window.DOMParser &&
+ window.XMLSerializer &&
+ window.Node && Node.prototype && Node.prototype.__defineGetter__ ) {
+
+ if (!Document.prototype.loadXML) {
+ Document.prototype.loadXML = function (s) {
+ var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
+ while (this.hasChildNodes())
+ this.removeChild(this.lastChild);
+
+ for (var i = 0; i < doc2.childNodes.length; i++) {
+ this.appendChild(this.importNode(doc2.childNodes[i], true));
+ }
+ };
+ }
+
+ Document.prototype.__defineGetter__( "xml",
+ function () {
+ return (new XMLSerializer()).serializeToString(this);
+ }
+ );
+}
+
+document.getElementsByTagAndClassName = function(tagName, className) {
+ if ( tagName == null )
+ tagName = '*';
+
+ var children = document.getElementsByTagName(tagName) || document.all;
+ var elements = new Array();
+
+ if ( className == null )
+ return children;
+
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i];
+ var classNames = child.className.split(' ');
+ for (var j = 0; j < classNames.length; j++) {
+ if (classNames[j] == className) {
+ elements.push(child);
+ break;
+ }
+ }
+ }
+
+ return elements;
+}
+
+
+// ricoAccordion.js --------------------
+
+
+Rico.Accordion = Class.create();
+
+Rico.Accordion.prototype = {
+
+ initialize: function(container, options) {
+ this.container = $(container);
+ this.lastExpandedTab = null;
+ this.accordionTabs = new Array();
+ this.setOptions(options);
+ this._attachBehaviors();
+
+ this.container.style.borderBottom = '1px solid ' + this.options.borderColor;
+
+ // set the initial visual state...
+ for ( var i=1 ; i < this.accordionTabs.length ; i++ )
+ {
+ this.accordionTabs[i].collapse();
+ this.accordionTabs[i].content.style.display = 'none';
+ }
+ this.lastExpandedTab = this.accordionTabs[0];
+ this.lastExpandedTab.content.style.height = this.options.panelHeight + "px";
+ this.lastExpandedTab.showExpanded();
+ this.lastExpandedTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
+ },
+
+ setOptions: function(options) {
+ this.options = {
+ expandedBg : '#63699c',
+ hoverBg : '#63699c',
+ collapsedBg : '#6b79a5',
+ expandedTextColor : '#ffffff',
+ expandedFontWeight : 'bold',
+ hoverTextColor : '#ffffff',
+ collapsedTextColor : '#ced7ef',
+ collapsedFontWeight : 'normal',
+ hoverTextColor : '#ffffff',
+ borderColor : '#1f669b',
+ panelHeight : 200,
+ onHideTab : null,
+ onShowTab : null
+ }.extend(options || {});
+ },
+
+ showTabByIndex: function( anIndex, animate ) {
+ var doAnimate = arguments.length == 1 ? true : animate;
+ this.showTab( this.accordionTabs[anIndex], doAnimate );
+ },
+
+ showTab: function( accordionTab, animate ) {
+
+ var doAnimate = arguments.length == 1 ? true : animate;
+
+ if ( this.options.onHideTab )
+ this.options.onHideTab(this.lastExpandedTab);
+
+ this.lastExpandedTab.showCollapsed();
+ var accordion = this;
+ var lastExpandedTab = this.lastExpandedTab;
+
+ this.lastExpandedTab.content.style.height = (this.options.panelHeight - 1) + 'px';
+ accordionTab.content.style.display = '';
+
+ accordionTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
+
+ if ( doAnimate ) {
+ new Effect.AccordionSize( this.lastExpandedTab.content,
+ accordionTab.content,
+ 1,
+ this.options.panelHeight,
+ 100, 10,
+ { complete: function() {accordion.showTabDone(lastExpandedTab)} } );
+ this.lastExpandedTab = accordionTab;
+ }
+ else {
+ this.lastExpandedTab.content.style.height = "1px";
+ accordionTab.content.style.height = this.options.panelHeight + "px";
+ this.lastExpandedTab = accordionTab;
+ this.showTabDone(lastExpandedTab);
+ }
+ },
+
+ showTabDone: function(collapsedTab) {
+ collapsedTab.content.style.display = 'none';
+ this.lastExpandedTab.showExpanded();
+ if ( this.options.onShowTab )
+ this.options.onShowTab(this.lastExpandedTab);
+ },
+
+ _attachBehaviors: function() {
+ var panels = this._getDirectChildrenByTag(this.container, 'DIV');
+ for ( var i = 0 ; i < panels.length ; i++ ) {
+
+ var tabChildren = this._getDirectChildrenByTag(panels[i],'DIV');
+ if ( tabChildren.length != 2 )
+ continue; // unexpected
+
+ var tabTitleBar = tabChildren[0];
+ var tabContentBox = tabChildren[1];
+ this.accordionTabs.push( new Rico.Accordion.Tab(this,tabTitleBar,tabContentBox) );
+ }
+ },
+
+ _getDirectChildrenByTag: function(e, tagName) {
+ var kids = new Array();
+ var allKids = e.childNodes;
+ for( var i = 0 ; i < allKids.length ; i++ )
+ if ( allKids[i] && allKids[i].tagName && allKids[i].tagName == tagName )
+ kids.push(allKids[i]);
+ return kids;
+ }
+
+};
+
+Rico.Accordion.Tab = Class.create();
+
+Rico.Accordion.Tab.prototype = {
+
+ initialize: function(accordion, titleBar, content) {
+ this.accordion = accordion;
+ this.titleBar = titleBar;
+ this.content = content;
+ this._attachBehaviors();
+ },
+
+ collapse: function() {
+ this.showCollapsed();
+ this.content.style.height = "1px";
+ },
+
+ showCollapsed: function() {
+ this.expanded = false;
+ this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
+ this.titleBar.style.color = this.accordion.options.collapsedTextColor;
+ this.titleBar.style.fontWeight = this.accordion.options.collapsedFontWeight;
+ this.content.style.overflow = "hidden";
+ },
+
+ showExpanded: function() {
+ this.expanded = true;
+ this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
+ this.titleBar.style.color = this.accordion.options.expandedTextColor;
+ this.content.style.overflow = "visible";
+ },
+
+ titleBarClicked: function(e) {
+ if ( this.accordion.lastExpandedTab == this )
+ return;
+ this.accordion.showTab(this);
+ },
+
+ hover: function(e) {
+ this.titleBar.style.backgroundColor = this.accordion.options.hoverBg;
+ this.titleBar.style.color = this.accordion.options.hoverTextColor;
+ },
+
+ unhover: function(e) {
+ if ( this.expanded ) {
+ this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
+ this.titleBar.style.color = this.accordion.options.expandedTextColor;
+ }
+ else {
+ this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
+ this.titleBar.style.color = this.accordion.options.collapsedTextColor;
+ }
+ },
+
+ _attachBehaviors: function() {
+ this.content.style.border = "1px solid " + this.accordion.options.borderColor;
+ this.content.style.borderTopWidth = "0px";
+ this.content.style.borderBottomWidth = "0px";
+ this.content.style.margin = "0px";
+
+ this.titleBar.onclick = this.titleBarClicked.bindAsEventListener(this);
+ this.titleBar.onmouseover = this.hover.bindAsEventListener(this);
+ this.titleBar.onmouseout = this.unhover.bindAsEventListener(this);
+ }
+
+};
+
+
+// ricoAjaxEngine.js --------------------
+
+
+Rico.AjaxEngine = Class.create();
+
+Rico.AjaxEngine.prototype = {
+
+ initialize: function() {
+ this.ajaxElements = new Array();
+ this.ajaxObjects = new Array();
+ this.requestURLS = new Array();
+ },
+
+ registerAjaxElement: function( anId, anElement ) {
+ if ( arguments.length == 1 )
+ anElement = $(anId);
+ this.ajaxElements[anId] = anElement;
+ },
+
+ registerAjaxObject: function( anId, anObject ) {
+ this.ajaxObjects[anId] = anObject;
+ },
+
+ registerRequest: function (requestLogicalName, requestURL) {
+ this.requestURLS[requestLogicalName] = requestURL;
+ },
+
+ sendRequest: function(requestName) {
+ var requestURL = this.requestURLS[requestName];
+ if ( requestURL == null )
+ return;
+
+ var queryString = "";
+ if ( arguments.length > 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 = "<div " + style + ">" + el.innerHTML + "</div>"
+ },
+
+ _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<arguments.length ; i++) if (str.indexOf(arguments[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("&nbsp;");
+ }
+
+};
+
+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 + "></" + 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 + "></" + elementName + ">";
+ 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 <b>value</b>.
+ * returns the index of the first item
+ * which matches <b>value</b>, or -1 if not found.
+ * searching starts at index 0, or at <b>start</b>, if specified.
+ *
+ * Here are the rules for an item to match <b>value</b>
+ * if strict is false or not specified (default):
+ * if <b>value</b> is a:
+ * # <b>function</b> -> <b>value(item)</b> must be true
+ * # <b>RegExp</b> -> <b>value.test(item)</b> must be true
+ * # anything else -> <b>item == value</b> 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; i<this.length; i++) {
+ var item = this[i];
+ if (strict ? item === value :
+ isRegexp(value) ? value.test(item) :
+ isFunction(value) ? value(item) :
+ item == value)
+ return i;
+ }
+ return -1;
+}
+
+/**
+ * searches Array for <b>value</b> 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 <b>value</b> is found in Array, otherwise false;
+ * relies on indexOf, see its doc for details on <b>value</b> and <b>strict</b>
+ * @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 <b>value</b> in Array
+ * relies on indexOf, see its doc for details on <b>value</b> and <b>strict</b>
+ * @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 <b>all</b> is false or not provied:
+ * removes first occurence of <b>value</b> from Array
+ * if <b>all</b> is provided and true:
+ * removes all occurences of <b>value</b> from Array
+ * returns the array
+ * relies on indexOf, see its doc for details on <b>value</b> and <b>strict</b>
+ * @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<arguments.length; i++)
+ for (var j=0; j<arguments[i].length; j++)
+ a.push(arguments[i][j]);
+ for (var i=0; i<a.length; i++) this.push(a[i]);
+ return this
+}
+
+
+
+/* A.min()
+ returns the smallest item in array by comparing them with >
+*/
+Array.prototype.min = function() {
+ if (!this.length) return;
+ var n = this[0];
+ for (var i=1; i<this.length; i++) if (n>this[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<this.length; i++) if (n<this[i]) n=this[i];
+ return n;
+}
+
+
+
+/* A.first()
+ returns first element of Array
+*/
+Array.prototype.first = function() { return this[0] }
+
+
+
+/* A.last()
+ returns last element of Array
+*/
+Array.prototype.last = function() { return this[this.length-1] }
+
+
+
+/* A.sjoin()
+ Shorthand for A.join(' ')
+*/
+Array.prototype.sjoin = function() { return this.join(' ') }
+
+
+
+/* A.njoin()
+ Shorthand for A.join('\n')
+*/
+Array.prototype.njoin = function() { return this.join('\n') }
+
+
+
+/* A.cjoin()
+ Shorthand for A.join(', ')
+*/
+Array.prototype.cjoin = function() { return this.join(', ') }
+
+
+
+/* A.equals(a [, strict])
+ true if all elements of array are equal to all elements of `a` in the same
+ order. if strict is specified and true, all elements must be equal and of
+ the same type.
+*/
+Array.prototype.equals = function(a, strict){
+ if (this==a) return true;
+ if (a.length != this.length) return false;
+ return this.map(function(item,idx){
+ return strict? item === a[idx] : item == a[idx]
+ }).all();
+}
+
+
+
+/* A.all([fn])
+ Returns true if fn returns true for all elements in array
+ if fn is not specified, returns true if all elements in array evaluate to
+ true
+*/
+Array.prototype.all = function(fn) {
+ return filter(this, fn).length == this.length;
+}
+
+
+
+/* A.any([fn])
+ Returns true if fn returns true for any elements in array
+ if fn is not specified, returns true if at least one element in array
+ evaluates to true
+*/
+Array.prototype.any = function(fn) {
+ return filter(this, fn).length > 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(i<list.length) result = fn(result,list[i++]);
+ return result;
+}
+
+/* range(start, stop, step)
+ identical to python's range.
+ range(stop)
+ range(start,stop)
+ range(start,stop,step)
+
+ Return a list containing an arithmetic progression of integers.
+ range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0.
+ When step is given, it specifies the increment (or decrement).
+ For example, range(4) returns [0, 1, 2, 3]. The end point is omitted!
+ [from python's range's docstring]
+*/
+function range(start,stop,step) {
+ if (isUndefined(stop)) return range(0,start,step);
+ if (isUndefined(step)) step = 1;
+ var ss = (step/Math.abs(step)); // step sign
+ var r = [];
+ for (i=start; i*ss<stop*ss; i=i+step) r.push(i);
+ return r;
+} \ No newline at end of file
diff --git a/framework/Web/Javascripts/extended/base.js b/framework/Web/Javascripts/extended/base.js
new file mode 100644
index 00000000..145b003f
--- /dev/null
+++ b/framework/Web/Javascripts/extended/base.js
@@ -0,0 +1,24 @@
+
+/**
+ * true if o is an Element Node or document or window. The last two because it's used for onload events
+ if you specify strict as true, return false for document or window
+ */
+function isElement(o, strict) {
+ return o && isObject(o) && ((!strict && (o==window || o==document)) || o.nodeType == 1)
+}
+
+/**
+ * get element
+ @ @param element or element id string
+ @ returns element
+ */
+function $(n,d) {
+ if(isElement(n)) return n;
+ if(isString(n)==false) return null;
+ var p,i,x;
+ if(!d) d=document; if((p=n.indexOf("?"))>0&&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<d.forms.length;i++) x=d.forms[i][n];
+ for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=DOM.find(n,d.layers[i].document);
+ if(!x && d.getElementById) x=d.getElementById(n); return x;
+} \ No newline at end of file
diff --git a/framework/Web/Javascripts/extended/dom.js b/framework/Web/Javascripts/extended/dom.js
new file mode 100644
index 00000000..4c664de7
--- /dev/null
+++ b/framework/Web/Javascripts/extended/dom.js
@@ -0,0 +1,6 @@
+Object.extend(Element, {
+ condClassName : function (element, className, cond)
+ {
+ (cond?Element.addClassName:Element.removeClassName)(element,className);
+ }
+}); \ No newline at end of file
diff --git a/framework/Web/Javascripts/extended/event.js b/framework/Web/Javascripts/extended/event.js
new file mode 100644
index 00000000..17a5432c
--- /dev/null
+++ b/framework/Web/Javascripts/extended/event.js
@@ -0,0 +1,24 @@
+Object.extend(Event, {
+ OnLoad : function (fn) {
+ // opera onload is in document, not window
+ var w = document.addEventListener && !window.addEventListener ? document : window;
+ Event.__observe(w,'load',fn);
+ },
+ observe: function(elements, name, observer, useCapture) {
+ if(isElement(elements))
+ return this.__observe(elements, name, observer, useCapture);
+ for(var i=0; i<elements.length; i++)
+ this.__observe(elements[i], name, observer, useCapture);
+ },
+ __observe: function(element, name, observer, useCapture) {
+ var element = $(element);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ ((navigator.appVersion.indexOf('AppleWebKit') > 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 <b>fn</b> 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 <b>list</b>, applying <b>fn</b> to each item of <b>list</b>.
+ * see doc for <b>__strfn</b> for peculiarities about passing strings for <b>fn</b>
+ *
+ * <b>each</b> 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 <b>length</b> 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 <b>list</b>, applying <b>fn</b> to each item of <b>list</b>, returning an array
+ of values returned by <b>fn</b>
+
+ parameters work the same as for <b>each</b>, same <b>__strfn</b> caveats apply
+
+ if <b>fn</b> 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 <b>each</b>
+
+ 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 <b>combine</b> 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 <b>fn</b>
+ if <b>fn</b> 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, <b>null</b> is used in place of its missing
+ items
+ * @param list one or more lists (see <b>each</b> for definition of a list)
+ * @param fn Similar s <b>each</b> or <b>map</b>, 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 <b>__strfn</b> caveats apply
+ * @see #each
+ * @see #__strfn
+ * @return an array of the values returned by calling <b>fn</b> 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 <b>list</b> for which <b>fn(item)</b> is true
+
+ parameters work the same as for <b>each</b>, same <b>__strfn</b> caveats apply
+
+ if <b>fn</b> is not specified the items are evaluated themselves, that is,
+ filter will return an array of the items in <b>list</b> 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 <b>each</b> to learn more about it
+ * @param initial TODO
+ * @param fn similar to <b>each</b> too, but in the case where it's a string,
+ the name of the paremeters passed to it will be 'a' and 'b'
+ same <b>__strfn</b> caveats apply
+ *//*
+function reduce(list, initial, fn) {
+ if (undef(fn)) {
+ fn = initial;
+ // explicit <b>window</b> object so browsers that do not have an <b>undefined</b>
+ //keyword will evaluate to the (hopefully) undefined parameter
+ //<b>undefined</b> of <b>window</b>
+ 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(i<list.length) result = fn(result,list[i++]);
+ return result;
+}*/
+
diff --git a/framework/Web/Javascripts/extended/string.js b/framework/Web/Javascripts/extended/string.js
new file mode 100644
index 00000000..f79072fb
--- /dev/null
+++ b/framework/Web/Javascripts/extended/string.js
@@ -0,0 +1,37 @@
+Object.extend(String.prototype, {
+ pad : function(side, len, chr) {
+ if (!chr) chr = ' ';
+ var s = this;
+ var left = side.toLowerCase()=='left';
+ while (s.length<len) s = left? chr + s : s + chr;
+ return s;
+ },
+
+ padLeft : function(len, chr) {
+ return this.pad('left',len,chr);
+ },
+
+ padRight : function(len, chr) {
+ return this.pad('right',len,chr);
+ },
+
+ zerofill : function(len) {
+ var s = this;
+ var ix = /^[+-]/.test(s) ? 1 : 0;
+ while (s.length<len) s = s.insert(ix, '0');
+ return s;
+ },
+
+ trim : function() {
+ return this.replace(/^\s+|\s+$/g,'');
+ },
+
+ trimLeft : function() {
+ return this.replace(/^\s+/,'');
+ },
+
+ trimRight : function() {
+ return this.replace(/\s+$/,'');
+ }
+
+});
diff --git a/framework/Web/Javascripts/extended/util.js b/framework/Web/Javascripts/extended/util.js
new file mode 100644
index 00000000..1e1fad69
--- /dev/null
+++ b/framework/Web/Javascripts/extended/util.js
@@ -0,0 +1,90 @@
+/**
+ * Test if it is an object and has no constructors.
+ */
+function isAlien(a) { return isObject(a) && typeof a.constructor != 'function' }
+
+/**
+ * isArray?
+ */
+function isArray(a) { return isObject(a) && a.constructor == Array }
+
+/**
+ * isBoolean?
+ */
+function isBoolean(a) { return typeof a == 'boolean' }
+
+/**
+ * isFunction?
+ */
+function isFunction(a) { return typeof a == 'function' }
+
+/**
+ * isNull?
+ */
+function isNull(a) { return typeof a == 'object' && !a }
+
+/**
+ * isNumber?
+ */
+function isNumber(a) { return typeof a == 'number' && isFinite(a) }
+
+/**
+ * isObject?
+ */
+function isObject(a) { return (a && typeof a == 'object') || isFunction(a) }
+
+/**
+ * isRegexp?
+ * we would prefer to use instanceof, but IE/mac is crippled and will choke at it
+ */
+function isRegexp(a) { return a && a.constructor == RegExp }
+
+/**
+ * isString?
+ */
+function isString(a) { return typeof a == 'string' }
+
+/**
+ * isUndefined?
+ */
+function isUndefined(a) { return typeof a == 'undefined' }
+
+/**
+ * isEmpty?
+ */
+function isEmpty(o) {
+ var i, v;
+ if (isObject(o)) {
+ for (i in o) {
+ v = o[i];
+ if (isUndefined(v) && isFunction(v)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/**
+ * alias for isUndefined
+ */
+function undef(v) { return isUndefined(v) }
+
+/**
+ * alias for !isUndefined
+ */
+function isdef(v) { return !isUndefined(v) }
+
+/**
+ * true if o is an Element Node or document or window. The last two because it's used for onload events
+ if you specify strict as true, return false for document or window
+ */
+function isElement(o, strict) {
+ return o && isObject(o) && ((!strict && (o==window || o==document)) || o.nodeType == 1)
+}
+
+/**
+ * true if o is an Array or a NodeList, (NodeList in Opera returns a type of function)
+ */
+function isList(o) { return o && isObject(o) && (isArray(o) || o.item) }
+
diff --git a/framework/Web/Javascripts/extra/behaviour.js b/framework/Web/Javascripts/extra/behaviour.js
new file mode 100644
index 00000000..0004c5db
--- /dev/null
+++ b/framework/Web/Javascripts/extra/behaviour.js
@@ -0,0 +1,83 @@
+/*
+ Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work
+ of Simon Willison (see comments by Simon below).
+
+ Description:
+
+ Uses css selectors to apply javascript behaviours to enable
+ unobtrusive javascript in html documents.
+
+ Usage:
+
+ var myrules = {
+ 'b.someclass' : function(element){
+ element.onclick = function(){
+ alert(this.innerHTML);
+ }
+ },
+ '#someid u' : function(element){
+ element.onmouseover = function(){
+ this.innerHTML = "BLAH!";
+ }
+ }
+ };
+
+ Behaviour.register(myrules);
+
+ // Call Behaviour.apply() to re-apply the rules (if you
+ // update the dom, etc).
+
+ License:
+
+ This file is entirely BSD licensed.
+
+ More information:
+
+ http://ripcord.co.nz/behaviour/
+
+*/
+
+var Behaviour = {
+ list : new Array,
+
+ register : function(sheet){
+ Behaviour.list.push(sheet);
+ },
+
+ start : function(){
+ Behaviour.addLoadEvent(function(){
+ Behaviour.apply();
+ });
+ },
+
+ apply : function(){
+ for (h=0;sheet=Behaviour.list[h];h++){
+ for (selector in sheet){
+ list = document.getElementsBySelector(selector);
+
+ if (!list){
+ continue;
+ }
+
+ for (i=0;element=list[i];i++){
+ sheet[selector](element);
+ }
+ }
+ }
+ },
+
+ addLoadEvent : function(func){
+ var oldonload = window.onload;
+
+ if (typeof window.onload != 'function') {
+ window.onload = func;
+ } else {
+ window.onload = function() {
+ oldonload();
+ func();
+ }
+ }
+ }
+}
+
+Behaviour.start(); \ No newline at end of file
diff --git a/framework/Web/Javascripts/extra/getElementsBySelector.js b/framework/Web/Javascripts/extra/getElementsBySelector.js
new file mode 100644
index 00000000..d6675cd5
--- /dev/null
+++ b/framework/Web/Javascripts/extra/getElementsBySelector.js
@@ -0,0 +1,176 @@
+/** document.getElementsBySelector(selector)
+ - returns an array of element objects from the current document
+ matching the CSS selector. Selectors can contain element names,
+ class names and ids and can be nested. For example:
+
+ elements = 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"
+
+ New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
+ See http://www.w3.org/TR/css3-selectors/#attribute-selectors
+
+ Version 0.4 - Simon Willison, March 25th 2003
+ -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
+ -- Opera 7 fails
+*/
+
+/**
+ * Returns all children of element. Workaround required for IE5/Windo
+ */
+function getAllChildren(e) {
+ // Returns all children of element. Workaround required for IE5/Windows. Ugh.
+ return e.all ? e.all : e.getElementsByTagName('*');
+}
+
+/**
+ * returns an array of element objects from the current document
+ matching the CSS selector. Selectors can contain element names,
+ class names and ids and can be nested. For example:
+
+ <pre><tt>elements = document.getElementsBySelect('div#main p a.external')</tt></pre>
+
+ 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 += '<button onclick="logConsole.toggle()" style="float:right;color:black">close</button>'
+ this.buttonsContainerElement.innerHTML += '<button onclick="Logger.clear()" style="float:right;color:black">clear</button>'
+ if(!Prado.Inspector.disabled)
+ this.buttonsContainerElement.innerHTML += '<button onclick="Prado.Inspector.inspect()" style="float:right;color:black; margin-right:15px;">Object Tree</button>'
+
+
+ //Add Tag Filter
+ this.tagFilterContainerElement = document.createElement('span')
+ this.toolbarElement.appendChild(this.tagFilterContainerElement)
+ this.tagFilterContainerElement.style.cssFloat = 'left'
+ this.tagFilterContainerElement.appendChild(document.createTextNode("Log Filter"))
+
+ this.tagFilterElement = document.createElement('input')
+ this.tagFilterContainerElement.appendChild(this.tagFilterElement)
+ this.tagFilterElement.style.width = '200px'
+ this.tagFilterElement.value = this.tagPattern
+ this.tagFilterElement.setAttribute('autocomplete', 'off') // So Firefox doesn't flip out
+
+ Event.observe(this.tagFilterElement, 'keyup', this.updateTags.bind(this))
+ Event.observe(this.tagFilterElement, 'click', function() {this.tagFilterElement.select()}.bind(this))
+
+ // Add outputElement
+ this.outputElement = document.createElement('div')
+ this.logElement.appendChild(this.outputElement)
+ this.outputElement.style.overflow = "auto"
+ this.outputElement.style.clear = "both"
+ this.outputElement.style.height = "200px"
+ this.outputElement.style.backgroundColor = 'black'
+
+ this.inputContainerElement = document.createElement('div')
+ this.inputContainerElement.style.width = "100%"
+ this.logElement.appendChild(this.inputContainerElement)
+
+ this.inputElement = document.createElement('input')
+ this.inputContainerElement.appendChild(this.inputElement)
+ this.inputElement.style.width = '100%'
+ this.inputElement.style.borderWidth = '0px' // Inputs with 100% width always seem to be too large (I HATE THEM) they only work if the border, margin and padding are 0
+ this.inputElement.style.margin = '0px'
+ this.inputElement.style.padding = '0px'
+ this.inputElement.value = 'Type command here'
+ this.inputElement.setAttribute('autocomplete', 'off') // So Firefox doesn't flip out
+
+ Event.observe(this.inputElement, 'keyup', this.handleInput.bind(this))
+ Event.observe(this.inputElement, 'click', function() {this.inputElement.select()}.bind(this))
+
+ 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 = '<button style="position:absolute;top:-100px" onclick="javascript:logConsole.toggle()" accesskey="d"></button>'
+ document.body.appendChild(accessElement)
+
+ if (Cookie.get('ConsoleVisible') == 'true') {
+ this.toggle()
+ }
+ },
+
+ toggle : function() {
+ if (this.logElement.style.display == 'none') {
+ this.show()
+ }
+ else {
+ this.hide()
+ }
+ },
+
+ show : function() {
+ Element.show(this.logElement)
+ this.outputElement.scrollTop = this.outputElement.scrollHeight // Scroll to bottom when toggled
+ 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 += "<pre style='" + style + "'>" + message + "</pre>"
+
+ if (shouldScroll) {
+ this.outputElement.scrollTop = this.outputElement.scrollHeight
+ }
+ },
+
+ updateTags : function() {
+ var pattern = this.tagFilterElement.value
+
+ if (this.tagPattern == pattern) return
+
+ try {
+ new RegExp(pattern)
+ }
+ catch (e) {
+ return
+ }
+
+ this.tagPattern = pattern
+ Cookie.set('tagPattern', this.tagPattern)
+
+ this.outputElement.innerHTML = ""
+
+ // Go through each log entry again
+ this.outputCount = 0;
+ for (var i = 0; i < Logger.logEntries.length; i++) {
+ this.logUpdate(Logger.logEntries[i])
+ }
+ },
+
+ repositionWindow : function() {
+ var offset = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
+ var pageHeight = self.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
+ this.logElement.style.top = (offset + pageHeight - Element.getHeight(this.logElement)) + "px"
+ },
+
+ // Event Handlers
+ // --------------
+
+ logUpdate : function(logEntry) {
+ if (logEntry.tag.search(new RegExp(this.tagPattern, 'igm')) == -1) return
+ var style = ''
+ if (logEntry.tag.search(/error/) != -1) style += 'color:red'
+ else if (logEntry.tag.search(/warning/) != -1) style += 'color:orange'
+ else if (logEntry.tag.search(/debug/) != -1) style += 'color:green'
+ else if (logEntry.tag.search(/info/) != -1) style += 'color:white'
+ else style += 'color:yellow'
+
+ this.output(logEntry.message, style)
+ },
+
+ clear : function(e) {
+ this.outputElement.innerHTML = ""
+ },
+
+ handleInput : function(e) {
+ if (e.keyCode == Event.KEY_RETURN ) {
+ var command = this.inputElement.value
+
+ switch(command) {
+ case "clear":
+ Logger.clear()
+ break
+
+ default:
+ var consoleOutput = ""
+
+ try {
+ consoleOutput = eval(this.inputElement.value)
+ }
+ catch (e) {
+ Logger.error("Problem parsing input <" + command + ">", e)
+ break
+ }
+
+ Logger.log(consoleOutput)
+ break
+ }
+
+ if (this.inputElement.value != "" && this.inputElement.value != this.commandHistory[0]) {
+ this.commandHistory.unshift(this.inputElement.value)
+ }
+
+ this.commandIndex = 0
+ this.inputElement.value = ""
+ }
+ else if (e.keyCode == Event.KEY_UP && this.commandHistory.length > 0) {
+ this.inputElement.value = this.commandHistory[this.commandIndex]
+
+ if (this.commandIndex < this.commandHistory.length - 1) {
+ this.commandIndex += 1
+ }
+ }
+ else if (e.keyCode == Event.KEY_DOWN && this.commandHistory.length > 0) {
+ if (this.commandIndex > 0) {
+ this.commandIndex -= 1
+ }
+
+ this.inputElement.value = this.commandHistory[this.commandIndex]
+ }
+ else {
+ this.commandIndex = 0
+ }
+ }
+}
+
+// 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,"&lt;");
+ str=str.replace(/>/g,"&gt;");
+ return str;
+ },
+
+ parseJS : function(obj) {
+ var name;
+ if(typeof obj == "string") { name = obj; obj = eval(obj); }
+ win= typeof obj == 'undefined' ? window : obj;
+ this.displaying = name ? name : win.toString();
+ for(js in win) {
+ try {
+ if(win[js] && js.toString().indexOf("Inspector")==-1 && win[js].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 = ["<a href=\"javascript:var_dump()\">[object Window]</a>"];
+ var name = '';
+ if(display.indexOf("[object ") >= 0) return links.join(".");
+ for(var i = 0; i < list.length; i++)
+ {
+ name += (name.length ? "." : "") + list[i];
+ links[i+1] = "<a href=\"javascript:var_dump('"+name+"')\">"+list[i]+"</a>";
+ }
+ return links.join(".");
+ },
+
+ buildTree : function() {
+ mHTML = "<div>Inspecting "+this.buildInspectionLevel()+"</div>";
+ mHTML +="<ul class=\"topLevel\">";
+ this.types.sort();
+ var so_objIndex=0;
+ for(i=0;i<this.types.length;i++) {
+ mHTML+="<li style=\"cursor:pointer;\" onclick=\"Prado.Inspector.show('ul"+i+"');Prado.Inspector.changeSpan('sp" + i + "')\"><span id=\"sp" + i + "\">[+]</span><b>" + this.types[i] + "</b> (" + this.objs[this.types[i]].length + ")</li><ul style=\"display:none;\" id=\"ul"+i+"\">";
+ this.hidden["ul"+i]=0;
+ for(e=0;e<this.objs[this.types[i]].length;e++) {
+ var more = "";
+ if(this.objs[this.types[i]][e][1].indexOf("[object ") >= 0 && /^[a-zA-Z_]/.test(this.objs[this.types[i]][e][0][0]))
+ {
+ if(this.displaying.indexOf("[object ") < 0)
+ more = " <a href=\"javascript:var_dump('"+this.displaying+"."+this.objs[this.types[i]][e][0]+"')\"><b>more</b></a>";
+ else if(this.displaying.indexOf("[object Window]") >= 0)
+ more = " <a href=\"javascript:var_dump('"+this.objs[this.types[i]][e][0]+"')\"><b>more</b></a>";
+ }
+ mHTML+="<li style=\"cursor:pointer;\" onclick=\"Prado.Inspector.show('mul" + so_objIndex + "');Prado.Inspector.changeSpan('sk" + so_objIndex + "')\"><span id=\"sk" + so_objIndex + "\">[+]</span>" + this.objs[this.types[i]][e][0] + "</li><ul id=\"mul" + so_objIndex + "\" style=\"display:none;\"><li style=\"list-style-type:none;\"><pre>" + this.objs[this.types[i]][e][1] + more + "</pre></li></ul>";
+ this.hidden["mul"+so_objIndex]=0;
+ so_objIndex++;
+ }
+ mHTML+="</ul>";
+ }
+ mHTML+="</ul>";
+ this.d.getElementById("so_mContainer").innerHTML =mHTML;
+ },
+
+ handleKeyEvent : function(e) {
+ keyCode=document.all?window.event.keyCode:e.keyCode;
+ if(keyCode==27) {
+ this.cleanUp();
+ }
+ },
+
+ cleanUp : function()
+ {
+ if(this.d.getElementById("so_mContainer"))
+ {
+ this.d.body.removeChild(this.d.getElementById("so_mContainer"));
+ this.d.body.removeChild(this.d.getElementById("so_mStyle"));
+ if(typeof Event != "undefined")
+ Event.stopObserving(this.d, "keydown", this.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 = "<b>[esc] to <a href=\"javascript:Prado.Inspector.cleanUp();\">close</a></b><br />Javascript Object Tree V2.0, <a target=\"_blank\" href=\"http://slayeroffice.com/?c=/content/tools/js_tree.html\">more info</a>.";
+
+ 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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); },
+ "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(/&lt;/g, "<").replace(/&gt;/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 = '(?:<script.*?>)((\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 = '<table><tbody>' + this.content + '</tbody></table>';
+ return $A(div.childNodes[0].childNodes[0].childNodes);
+ }
+}
+
+var Insertion = new Object();
+
+Insertion.Before = Class.create();
+Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
+ initializeRange: function() {
+ this.range.setStartBefore(this.element);
+ },
+
+ insertContent: function(fragments) {
+ fragments.each((function(fragment) {
+ this.element.parentNode.insertBefore(fragment, this.element);
+ }).bind(this));
+ }
+});
+
+Insertion.Top = Class.create();
+Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
+ initializeRange: function() {
+ this.range.selectNodeContents(this.element);
+ this.range.collapse(true);
+ },
+
+ insertContent: function(fragments) {
+ fragments.reverse().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 '#<Enumerable:' + this.toArray().inspect() + '>';
+ }
+}
+
+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 '#<Hash:{' + this.map(function(pair) {
+ return pair.map(Object.inspect).join(': ');
+ }).join(', ') + '}>';
+ }
+}
+
+function $H(object) {
+ var hash = Object.extend({}, object || {});
+ Object.extend(hash, Enumerable);
+ Object.extend(hash, Hash);
+ return hash;
+}
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+ <title>Prado Client-Side CompareValidator Tests</title>
+<!-- base files -->
+<script type="text/javascript" src="../../base.js"></script>
+<script type="text/javascript" src="../../dom.js"></script>
+<script type="text/javascript" src="../prado/validation.js"></script>
+<script type="text/javascript" src="../prado/validators.js"></script>
+<style type="text/css">
+/*<![CDATA[*/
+
+ .message
+ {
+ color: red;
+ }
+ .required, .required1, .required2
+ {
+ border: 1px solid red;
+ }
+/*]]>*/
+</style>
+ </head>
+
+ <body>
+ <h1>Prado Client-Side CompareValidator Tests</h1>
+
+<form id="form1" action="#">
+
+<!-- group 1 -->
+<div>
+ <div>
+ <input type="text" id="text1" name="text1" />
+ <input type="text" id="text2" name="text2" />
+ <span id="validator1" class="message" style="display:none">Must match</span>
+ </div>
+
+ <div>
+ <input type="text" id="text3" name="text3" />
+ <span id="validator2" class="message" style="display:none">Must equal "me!"</span>
+ </div>
+
+ <div>
+ <input type="text" id="text4" name="text4" />
+ <span id="validator3" class="message" style="display:none">Must be a date (d/m/Y)</span>
+ </div>
+ <input type="submit" value="Test" id="submit1" />
+</div>
+ <script type="text/javascript">
+ <!--
+ /*]]>*/
+ if(typeof(Prado) != "undefined" && typeof(Prado.Validation) != "undefined")
+ {
+ Prado.Validation.AddForm("form1");
+
+ var val1 = {id : "validator1",
+ controltovalidate : "text1",
+ errormessage : "*",
+ display : "Dynamic",
+ controlcssclass : "required",
+ controlhookup : "text2"};
+ new Prado.Validation(Prado.Validation.TCompareValidator, val1);
+
+ var val1 = {id : "validator2",
+ controltovalidate : "text3",
+ errormessage : "*",
+ display : "Dynamic",
+ controlcssclass : "required",
+ valuetocompare : "me!"};
+ new Prado.Validation(Prado.Validation.TCompareValidator, val1);
+
+ var val1 = {id : "validator3",
+ controltovalidate : "text4",
+ errormessage : "*",
+ display : "Dynamic",
+ controlcssclass : "required",
+ operator : "DataTypeCheck",
+ type : "Date",
+ dateformat : "%d/%m/%Y"};
+ new Prado.Validation(Prado.Validation.TCompareValidator, val1);
+
+ Prado.Validation.AddTarget("submit1");
+ }
+
+ /*]]>*/
+ //-->
+ </script>
+</form>
+
+ </body>
+</html>
+
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+ <title>Prado Client-Side CustomValidator Tests</title>
+<!-- base files -->
+<script type="text/javascript" src="../../base.js"></script>
+<script type="text/javascript" src="../../dom.js"></script>
+<script type="text/javascript" src="../prado/validation.js"></script>
+<script type="text/javascript" src="../prado/validators.js"></script>
+<style type="text/css">
+/*<![CDATA[*/
+
+ .message
+ {
+ color: red;
+ }
+ .required, .required1, .required2
+ {
+ border: 1px solid red;
+ }
+/*]]>*/
+</style>
+ </head>
+
+ <body>
+ <h1>Prado Client-Side CustomValidator Tests</h1>
+
+<form id="form1" action="#">
+
+<!-- group 1 -->
+<div>
+ <input type="text" id="text1" name="text1" />
+ <span id="validator1" class="message" style="display:none">*</span>
+ <input type="submit" value="Test" id="submit1" />
+</div>
+ <script type="text/javascript">
+ <!--
+ /*]]>*/
+ if(typeof(Prado) != "undefined" && typeof(Prado.Validation) != "undefined")
+ {
+ Prado.Validation.AddForm("form1");
+
+ var val1 = {id : "validator1",
+ controltovalidate : "text1",
+ errormessage : "*",
+ display : "Dynamic",
+ controlcssclass : "required2",
+ clientvalidationfunction : "CustomValidation"};
+ new Prado.Validation(Prado.Validation.TCustomValidator, val1);
+ Prado.Validation.AddTarget("submit1");
+ }
+
+ function CustomValidation(validator, value)
+ {
+ //call a custom callback function
+ setTimeout(callback.bind(validator), 500);
+ return false;
+ }
+
+ function callback()
+ {
+ this.setValid(true);
+ }
+ /*]]>*/
+ //-->
+ </script>
+</form>
+
+ </body>
+</html>
+
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>Prado Date Picker</title>
+ <script type="text/javascript" src="../../base.js"></script>
+ <script type="text/javascript" src="../../dom.js"></script>
+ <script type="text/javascript" src="../../datepicker.js"></script>
+<script type="text/javascript">
+
+ function init() {
+ var options = { "trigger" : "button1", "format" : "dd-MM-yyyy" };
+ var datepicker1 = new Prado.Calendar("textfield1", options);
+ //datepicker1.format = "yyyy-MM-d";
+ //datepicker1.create();
+ //datepicker1.show("button1");
+ //var button = $("button1");
+ //Event.observe(button, "click", function(){datepicker1.show(button);});
+ //var d = new Date();
+ //alert(Prado.Calendar.Util.ISODate(d));
+ var datepicker2 = new Prado.Calendar("textfield2");
+ }
+
+ /*var cal;
+
+ function init() {
+ cal = new Calendar();
+ cal.setIncludeWeek(true);
+ cal.setFormat("yyyy-MM-dd");
+ //cal.setMonthNames(.....);
+ //cal.setShortMonthNames(....);
+ cal.create();
+
+ var button = $("button1");
+ button.onclick = function() {
+ cal.toggle(button);
+ }
+ cal.onchange = function() {
+ $("textfield1").value = cal.formatDate();
+ }
+ }*/
+
+ window.onload = init;
+ </script>
+</script>
+</head>
+
+<body>
+<form ID=Form1>
+ <input type="text" name="textfield1" id="textfield1" />
+ <input type="button" name="button1" id="button1" value="..." />
+</form>
+
+
+as<br/>
+dv<br/>
+as<br/>
+d<br/>
+as<br/>
+d<br/>
+asd<br/>
+a<br/>
+sd<br/>
+dv<br/>
+as<br/>
+d<br/>
+as<br/>
+d<br/>
+asd<br/>
+a<br/>
+sd<br/>
+dv<br/>
+as<br/>
+d<br/>
+as<br/>
+d<br/>
+asd<br/>
+a<br/>
+sd<br/>
+<form>
+ <input type="text" name="textfield2" id="text1" />
+ <input type="button" name="button2" id="button2" value="..." />
+</form>
+d<br/>
+as<br/>
+d<br/>
+asd<br/>
+a<br/>
+sd<br/>
+dv<br/>
+as<br/>
+d<br/>
+as<br/>
+d<br/>
+asd<br/>
+a<br/>
+sd<br/>
+</body>
+</html>
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+ <title>Effects demo page</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <script type="text/javascript" src="../../base.js"></script>
+ <script type="text/javascript" src="../../dom.js"></script>
+ <script type="text/javascript" src="../../effects.js"></script>
+</head>
+
+<body style="font: 0.9em Arial;background: #f5f5f5;">
+<h1>Effect.Fade()</h1>
+
+<p>Call with <code>new Effect.Fade(element)</code></p>
+<p>Example: <code>onclick="new Effect.Fade(this)"</code></p>
+
+<div id="fade" onclick="new Effect.Fade(this)" style="border:1px solid red; padding:1em;">
+ Click here or the image to start effect
+</div>
+
+
+<h1>Effect.Highlight()</h1>
+
+<p>Call with <code>new Effect.Highlight(element)</code></p>
+<p>Example: <code>onclick="new Effect.Highlight(this)"</code></p>
+
+<div id="fade" onclick="new Effect.Highlight(this)" style="border:1px solid red; padding:1em;">
+ Click here or the image to start effect
+</div>
+
+<h1>Effect.Appear()</h1>
+
+<p>Call with <code>new Effect.Appear(element)</code></p>
+<p>Example: <code>onclick="new Effect.Appear('appear')"</code></p>
+
+<a href="#" onclick="new Effect.Appear('appear')">Start effect</a>
+
+<div id="appear" style="border:1px solid red; padding:1em;display:none;background:#fff;">
+ This is just some text
+</div>
+
+<h1>Effect.Scale()</h1>
+
+<p>Call with <code>new Effect.Scale(element, percent)</code></p>
+<p>Note: if you scale a div, all contained elements must have width or height set with <tt>em</tt>. 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 <tt>percent</tt> parameter is always relative of the original size of the element.</p>
+<p>Example: <code>onmouseover="new Effect.Scale('scale',150)" onmouseout="new Effect.Scale('scale',100)"</code></p>
+
+<div style="height:100px">
+<img src="fungii_logo.gif" style="width:130px;" onmouseover="new Effect.Scale(this,150)" onmouseout="new Effect.Scale(this,100)"/>
+<img src="fungii_logo.gif" style="width:130px;" onmouseover="new Effect.Scale(this,150)" onmouseout="new Effect.Scale(this,100)"/>
+<img src="fungii_logo.gif" style="width:130px;" onmouseover="new Effect.Scale(this,150)" onmouseout="new Effect.Scale(this,100)"/>
+<img src="fungii_logo.gif" style="width:130px;" onmouseover="new Effect.Scale(this,150)" onmouseout="new Effect.Scale(this,100)"/>
+</div>
+
+<h1>Effect.Squish()</h1>
+<p>Call with <code>new Effect.Squish(element)</code></p>
+<div id="squish" style="border:1px solid red; width:250px; height:150px;background:#fff;" onclick="new Effect.Squish(this)">
+ Click me to squish.<br/>
+ <img src="fungii_logo.gif" style="width:100%;" />
+
+</div>
+
+<h1>Effect.Puff()</h1>
+<p>Call with <code>new Effect.Puff(element)</code></p>
+<p>Works with absolute and relative positioned elements. In this example, divs with <tt>float:left</tt> are used.</p>
+<div id="puff1" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#fff;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+ <img src="fungii_logo.gif" style="width:8em" />
+
+</div>
+<div id="puff2" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#f8f;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+<div id="puff3" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#ff8;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+<div id="puff4" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#fff;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+<div id="puff5" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#f8f;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+<div id="puff6" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#ff8;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+
+<div id="puff11" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#fff;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+<div id="puff12" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#f8f;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+<div id="puff13" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#ff8;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+<div id="puff14" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#fff;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+<div id="puff15" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#f8f;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+
+<div id="puff16" style="float:left;margin-right:1em;border:1px solid red; padding:1em;width:10em;height:5em;background:#ff8;" onclick="new Effect.Puff(this)">
+ (floating) Click me to puff.<br/>
+ <img src="fungii_logo.gif" style="width:8em" />
+</div>
+
+</body>
+</html> \ 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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+ <title>Form disable test</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <script src="../dist/prototype.js"></script>
+</head>
+
+<body style="font: 0.9em Arial;background: #f5f5f5;">
+
+<form id="test" method="post">
+Number
+ <select name="guest[size]">
+ <option value="0"
+ > 0
+ </option>
+ <option value="1"
+ selected > 1
+ </option>
+ <option value="2"
+ > 2
+ </option>
+ </select>
+
+<label for="text">Text</label></td>
+<input id="form_text" name="form[text]" size="30" type="text" value="" /><br/>
+<label for="form_radio">Radio</label></td>
+<td>
+ <input id="form_radio_yes" name="form[radio]" size="30" type="radio" value="1" />yes
+ <input id="form_radio_no" name="form[radio]" size="30" type="radio" value="0" />no
+
+</td>
+</form>
+<p>
+<a href="#" onClick="Form.disable(document.forms['test']);">disable</a>
+<a href="#" onClick="Form.enable(document.forms['test']);">enable</a>
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 @@
+<html>
+<head>
+ <script type="text/javascript" src="../../base.js"></script>
+ <script type="text/javascript" src="../../dom.js"></script>
+ <script type="text/javascript" src="../../form.js"></script>
+ <script type="text/javascript">
+ function test() {
+ var type = Form.Element.getValue('type');
+ var html = '<div>' + Form.Element.getValue('content') + '</div>';
+ new Insertion[type]($('a'), html);
+ return false;
+ }
+ </script>
+
+ <style type="text/css">
+ div#a {
+ border: 2px solid blue;
+ padding: 10px;
+ margin-bottom: 10px;
+ }
+
+ div#a div {
+ border: 1px solid red;
+ padding: 5px;
+ }
+
+ textarea {
+ display: block;
+ }
+ </style>
+</head>
+<body>
+ <div id="a">
+ content
+ </div>
+ <fieldset>
+ <textarea id="content" rows="10" cols="60"></textarea>
+ <select id="type">
+ <option value="Before">Before</option>
+ <option value="Top">Top</option>
+ <option value="Bottom">Bottom</option>
+ <option value="After">After</option>
+ </select>
+ <button onclick="return test()">Create Insertion</button>
+ </fieldset>
+</body>
+</html> \ 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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+"http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Prado Javascript Test Suite</title>
+ <link rel="stylesheet" type="text/css" href="jsunit/css/jsUnitStyle.css">
+<script language="JavaScript" type="text/javascript" src="jsunit/app/jsUnitCore.js"></script>
+<script language="JavaScript" type="text/javascript">
+function coreTestSuite() {
+ var newsuite = new top.jsUnitTestSuite();
+ return newsuite;
+}
+
+function validationTestSuite() {
+ var newsuite = new top.jsUnitTestSuite();
+ return newsuite;
+}
+
+function suite() {
+ var newsuite = new top.jsUnitTestSuite();
+ newsuite.addTestSuite(coreTestSuite());
+ newsuite.addTestSuite(validationTestSuite());
+ return newsuite;
+}
+</script>
+ </head>
+
+ <body>
+ <h1>Prado Javascript Test Suite</h1>
+
+ <p>This page contains a suite of tests for testing
+ Prado javascripts.</p>
+ </body>
+</html>
+
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+ <title>Prado Client-Side RangeValidator Tests</title>
+<!-- base files -->
+<script type="text/javascript" src="../../base.js"></script>
+<script type="text/javascript" src="../../dom.js"></script>
+<script type="text/javascript" src="../prado/validation.js"></script>
+<script type="text/javascript" src="../prado/validators.js"></script>
+<style type="text/css">
+/*<![CDATA[*/
+
+ .message
+ {
+ color: red;
+ }
+ .required, .required1, .required2
+ {
+ border: 1px solid red;
+ }
+/*]]>*/
+</style>
+ </head>
+
+ <body>
+ <h1>Prado Client-Side RangeValidator Tests</h1>
+
+<form id="form1" action="#">
+
+<!-- group 1 -->
+<div>
+ <input type="text" id="text1" name="text1" />
+ <span id="validator1" class="message" style="display:none">Numbers between 1 and 4</span>
+ <input type="submit" value="Test" id="submit1" />
+</div>
+ <script type="text/javascript">
+ <!--
+ /*]]>*/
+ if(typeof(Prado) != "undefined" && typeof(Prado.Validation) != "undefined")
+ {
+ Prado.Validation.AddForm("form1");
+
+ var val1 = {id : "validator1",
+ controltovalidate : "text1",
+ errormessage : "*",
+ display : "Dynamic",
+ controlcssclass : "required2",
+ minimumvalue : "1",
+ maximumvalue : "4",
+ type : "Integer"};
+ new Prado.Validation(Prado.Validation.TRangeValidator, val1);
+ Prado.Validation.AddTarget("submit1");
+ }
+
+ /*]]>*/
+ //-->
+ </script>
+</form>
+
+ </body>
+</html>
+
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+ <title>Prado Client-Side RegularExpressionValidator Tests</title>
+<!-- base files -->
+<script type="text/javascript" src="../../base.js"></script>
+<script type="text/javascript" src="../../dom.js"></script>
+<script type="text/javascript" src="../prado/validation.js"></script>
+<script type="text/javascript" src="../prado/validators.js"></script>
+<style type="text/css">
+/*<![CDATA[*/
+
+ .message
+ {
+ color: red;
+ }
+ .required, .required1, .required2
+ {
+ border: 1px solid red;
+ }
+/*]]>*/
+</style>
+ </head>
+
+ <body>
+ <h1>Prado Client-Side RegularExpressionValidator Tests</h1>
+
+<form id="form1" action="#">
+
+<!-- group 1 -->
+<div>
+ <input type="text" id="text1" name="text1" />
+ <span id="validator1" class="message" style="display:none">5 digits</span>
+ <span id="validator2" class="message" style="display:none">*</span>
+ <input type="submit" value="Test 1" id="submit1" />
+ <input type="submit" value="Test 2" id="submit2" />
+ <input type="submit" value="Test 3" id="submit3" />
+</div>
+ <script type="text/javascript">
+ <!--
+ /*]]>*/
+ if(typeof(Prado) != "undefined" && typeof(Prado.Validation) != "undefined")
+ {
+ Prado.Validation.AddForm("form1");
+ var val1 = {id : "validator1",
+ controltovalidate : "text1",
+ errormessage : "5 digits",
+ display : "Dynamic",
+ controlcssclass : "required1",
+ validationexpression : "^\\d{5}$"};
+ var val2 = {id : "validator2",
+ controltovalidate : "text1",
+ errormessage : "*",
+ display : "Dynamic",
+ controlcssclass : "required2",
+ initialvalue : ""};
+ new Prado.Validation(Prado.Validation.TRegularExpressionValidator, val1);
+ //new Prado.Validation(Prado.Validation.TRequiredFieldValidator, val2);
+ Prado.Validation.AddTarget("submit1");
+ Prado.Validation.AddTarget("submit2");
+ }
+ /*]]>*/
+ //-->
+ </script>
+</form>
+
+ </body>
+</html>
+
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+ <title>Prado Client-Side RequiredFieldValidator Tests</title>
+<style type="text/css">
+/*<![CDATA[*/
+
+ .message
+ {
+ color: red;
+ }
+ .required
+ {
+ border: 1px solid red;
+ }
+/*]]>*/
+</style>
+
+ </head>
+
+ <body>
+ <h1>Prado Client-Side RequiredFieldValidator Tests</h1>
+
+<form action="" method="post" enctype="multipart/form-data" id="form1">
+<script type="text/javascript" src="../../js/base.js"></script>
+<script type="text/javascript" src="../../js/dom.js"></script>
+<script type="text/javascript" src="../prado/validation.js"></script>
+<script type="text/javascript" src="../prado/validators.js"></script>
+
+
+<!-- group 1 -->
+<div id="summary1"></div>
+<div>
+ <input id="text1" name="text1" type="text" value=""/>
+ <span id="validator1" class="message" style="display:none;"><a href="#text1" onclick="javascript:Prado.Validation.Util.focus('text1'); return false;" >text1!</a></span>
+
+ <span><input id="check1" type="checkbox" name="check1"/></span>
+ <span id="validator2" class="message" style="display:none;">check 1!</span>
+
+ <input id="submit1" type="submit" name="submit1" value="Group1"></input>
+
+</div>
+<!-- group 2 -->
+<div id="summary2"></div>
+<div>
+ <input id="text2" name="text2" type="text" value=""/>
+ <span id="validator3" class="message" style="visibility:hidden;">text2!</span>
+
+ <span><input id="check2" type="checkbox" name="check2"/></span>
+ <span id="validator4" class="message" style="visibility:hidden;">check 2!</span>
+
+ <input id="submit2" type="submit" name="submit2" value="Group2"></input>
+
+</div>
+
+<div id="summary3"></div>
+
+
+<input id="submit3" type="submit" name="submit3" value="Submit All"></input>
+<input id="submit4" type="submit" name="submit4" value="Submit By Pass"></input>
+
+
+
+<script type="text/javascript">
+//<![CDATA[
+
+ if(typeof(Prado) == 'undefined')
+ alert("Unable to find Prado javascript library '../../base.js'.");
+ else if(Prado.Version != 2.0)
+ alert("Prado javascript library version 2.0 required.");
+ else if(typeof(Prado.Validation) == 'undefined')
+ alert("Unable to find validation javascript library '../../validator.js'.");
+ else
+ Prado.Validation.AddForm('form1');
+
+new Prado.Validation.Summary({id:"summary1", form:"form1", displaymode:"BulletList", group:"group1"});
+new Prado.Validation(Prado.Validation.TRequiredFieldValidator, {id:"validator1", controltovalidate:"text1", errormessage:"<a href=\"#text1\" onclick=\"javascript:Prado.Validation.Util.focus(\'text1\'); return false;\" >text1!</a>", display:"Dynamic", controlcssclass:"required", initialvalue:""});
+new Prado.Validation(Prado.Validation.TRequiredFieldValidator, {id:"validator2", controltovalidate:"check1", errormessage:"check 1!", display:"Dynamic", controlcssclass:"required", initialvalue:""});
+Prado.Validation.AddGroup({id:"group1", target:"submit1"}, ["validator1", "validator2"]);
+Prado.Validation.AddTarget('submit1');
+
+new Prado.Validation.Summary({id:"summary2", form:"form1", displaymode:"BulletList", group:"group2"});
+new Prado.Validation(Prado.Validation.TRequiredFieldValidator, {id:"validator3", controltovalidate:"text2", errormessage:"text2!", controlcssclass:"required", initialvalue:""});
+new Prado.Validation(Prado.Validation.TRequiredFieldValidator, {id:"validator4", controltovalidate:"check2", errormessage:"check 2!", controlcssclass:"required", initialvalue:""});
+Prado.Validation.AddGroup({id:"group2", target:"submit2"}, ["validator3", "validator4"]);
+Prado.Validation.AddTarget('submit2');
+
+new Prado.Validation.Summary({id:"summary3", form:"form1", displaymode:"BulletList"});
+Prado.Validation.AddTarget('submit3');
+//]]>
+</script>
+</form> \ 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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Style-Type" content="text/css" />
+ <title>Prado Client-Side RequiredListValidator Tests</title>
+<!-- base files -->
+<script type="text/javascript" src="../../base.js"></script>
+<script type="text/javascript" src="../prototype/dom.js"></script>
+<script type="text/javascript" src="../extended/dom.js"></script>
+<script type="text/javascript" src="../prototype/event.js"></script>
+<script type="text/javascript" src="../extended/event.js"></script>
+<script type="text/javascript" src="../prototype/form.js"></script>
+<script type="text/javascript" src="../prototype/position.js"></script>
+<script type="text/javascript" src="../prototype/string.js"></script>
+<script type="text/javascript" src="../extra/getElementsBySelector.js"></script>
+<script type="text/javascript" src="../extra/behaviour.js"></script>
+<script type="text/javascript" src="../prado/validation.js"></script>
+<script type="text/javascript" src="../prado/validators.js"></script>
+<style type="text/css">
+/*<![CDATA[*/
+
+ .message
+ {
+ color: red;
+ }
+ .required, .required1, .required2
+ {
+ border: 1px solid red;
+ }
+ .requirede
+ {
+ background-color: red;
+ }
+/*]]>*/
+</style>
+ </head>
+
+ <body>
+ <h1>Prado Client-Side RequiredListValidator Tests</h1>
+
+<form id="form1" action="#">
+
+<!-- group 1 -->
+<div>
+ <div id="checks">
+ <input type="checkbox" id="check1" name="check1[]" value="1" />1
+ <input type="checkbox" id="check2" name="check1[]" value="2" />2
+ <input type="checkbox" id="check3" name="check1[]" value="3" />3
+ <input type="checkbox" id="check4" name="check1[]" value="4" />4
+ <span id="validator1" class="message" style="display:none">Must select at least 1 and no more than 3</span>
+ </div>
+
+ <div>
+ <select id="select1" name="select1[]" multiple="multiple" size="5" style="width:10em">
+ <option value="one">one</option>
+ <option value="two">two</option>
+ <option value="three">three</option>
+ <option value="four">four</option>
+ <option value="five">five</option>
+ </select>
+ <span id="validator2" class="message" style="display:none">Must select at least 2 and no more than 3 and value "two"</span>
+ </div>
+
+ <input type="submit" value="Test" id="submit1" />
+</div>
+ <script type="text/javascript">
+ <!--
+ /*]]>*/
+ if(typeof(Prado) != "undefined" && typeof(Prado.Validation) != "undefined")
+ {
+ Prado.Validation.AddForm("form1");
+
+ var val1 = {id : "validator1",
+ controltovalidate : "checks",
+ selector : "check1[]",
+ errormessage : "*",
+ display : "Dynamic",
+ controlcssclass : "required",
+ //elementcssclass : "requirede",
+ min : "1",
+ max : "3"
+ };
+ new Prado.Validation(Prado.Validation.TRequiredListValidator, val1);
+
+ var val1 = {id : "validator2",
+ controltovalidate : "select1",
+ selector : "select1[]",
+ errormessage : "*",
+ display : "Dynamic",
+ controlcssclass : "required",
+ //elementcssclass : "requirede",
+ min : "2",
+ max : "3",
+ required : "two"
+ };
+ new Prado.Validation(Prado.Validation.TRequiredListValidator, val1);
+ Prado.Validation.AddTarget("submit1");
+
+ }
+
+ /*]]>*/
+ //-->
+ </script>
+</form>
+
+ </body>
+</html>
+
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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+"http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Prado Client-Side Validation Tests</title>
+ <link rel="stylesheet" type="text/css" href="jsunit/css/jsUnitStyle.css">
+ <script language="JavaScript" type="text/javascript" src="jsunit/app/jsUnitCore.js"></script>
+<!-- unit tests -->
+<!-- base files -->
+<script language="JavaScript" Type="text/javascript" src="../../base.js"></script>
+<script language="JavaScript" Type="text/javascript" src="../../dom.js"></script>
+<!-- library file -->
+<script language="JavaScript" Type="text/javascript" src="../library/validation/validator.js"></script>
+<script language="JavaScript" type="text/javascript">
+
+ function testConvertInteger()
+ {
+ var convert = Prado.Validation.Util;
+
+ //test integers
+ assertEquals("Integer 10", 10, convert.toInteger("10"));
+ assertNull("Not an integer", convert.toInteger("10.0"));
+ assertEquals("+ integer", 10, convert.toInteger(" +10 "));
+ }
+
+ function testConvertDouble()
+ {
+ var convert = Prado.Validation.Util;
+
+ //test doubles
+ assertEquals("Double 10.0", 10.0, convert.toDouble("10.0","."));
+ assertNull("Not a double", convert.toDouble("10.0s"));
+ assertEquals("+ double", 10.2, convert.toDouble(" +10.2 "));
+ }
+
+ function testConvertAsCurrency()
+ {
+ var convert = Prado.Validation.Util;
+
+ //test currency convert
+ assertEquals("10 Dollars", 10, convert.toCurrency("10.00"));
+ assertEquals("10,000 Dollars", 10000.10, convert.toCurrency("10,000.10"));
+
+ //using space as grouping
+ assertEquals("10 000 Dollars", 10000.10, convert.toCurrency("10 000.10", " "));
+
+ assertEquals("1 0000 Yen", 10000, convert.toCurrency("1 0000", " ",0));
+
+ assertNull("Not 10 Dollars", convert.toCurrency("$10.0"));
+ }
+
+ function testConvertToDate()
+ {
+ var convert = Prado.Validation.Util;
+
+ //test dates !! Months are zero based for javascript Date objects !!
+ var testDate = (new Date(2005, 05, 20)).valueOf();
+ assertEquals("Matching date 2005 May 20", testDate, convert.toDate("2005-06-20", "%Y-%m-%d"));
+ }
+
+ function testValidatorConversion()
+ {
+ var validator = new Prado.Validation(null, {id : "", controltovalidate : "", groupchar: " ", digits : 0});
+ assertEquals("Integer 10", 10, validator.convert("Integer", "10"));
+ assertEquals("10 000 Yen", 10000, validator.convert("Currency", "10 000"));
+ }
+</script>
+ </head>
+
+ <body>
+ <h1>Prado Client-Side Validation Tests</h1>
+
+ <p>This page contains tests for the utility functions
+ that JsUnit uses. To see them, take a look at the source.</p>
+ </body>
+</html>
+
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>Test all compressed JS</title>
+<script type="text/javascript" src="../../base.js"></script>
+<script type="text/javascript" src="../../dom.js"></script>
+<script type="text/javascript" src="../../effects.js"></script>
+<script type="text/javascript" src="../../rico.js"></script>
+<script type="text/javascript" src="../../template.js"></script>
+<script type="text/javascript" src="../../validator.js"></script>
+
+</head>
+<body>
+OK
+</body>
+</html>
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+ <head>
+ <title>Prototype Console</title>
+ <script src="../dist/prototype.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ function test() {
+ var out = $('out');
+ try {
+ input = $F('input');
+ out.innerHTML += input.escapeHTML() + '<br />';
+ value = eval(input);
+ out.innerHTML += '=> ' + value.toString().escapeHTML() + '<br />';
+ } catch (e) {
+ out.innerHTML += 'Error: ' + e.toString().escapeHTML() + '<br />';
+ }
+ $('input').value = '';
+ }
+ </script>
+ </head>
+ <body>
+ <form onsubmit="test(); return false">
+ <input type="text" size="60" id="input" />
+ <input type="submit" value="Execute" />
+ </form>
+ <div id="test"></div>
+ <div id="out"></div>
+ </body>
+</html>
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
--- /dev/null
+++ b/framework/Web/Javascripts/tests/fungii_logo.gif
Binary files 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 @@
+<html>
+<head>
+ <script type="text/javascript" src="../../base.js"></script>
+ <script type="text/javascript" src="../../dom.js"></script>
+ <script type="text/javascript">
+ function test() {
+ var elements = document.getElementsByClassName($('class-name').value);
+ for (var i = 0; i < elements.length; i++) elements[i] = elements[i].id;
+ alert(elements.join(', '));
+ return false;
+ }
+ </script>
+</head>
+<body>
+ <div id="a" class="foo">a: foo</div>
+ <div id="b" class="foo bar">b: foo, bar</div>
+ <div id="c" class="bar">c: bar</div>
+ <div id="d" class="bar baz">d: bar, baz</div>
+ <div id="e" class="baz">e: baz</div>
+ <div id="f" class="foo baz">f: foo, baz</div>
+
+ <fieldset>
+ <label for="class-name">Class name:</label>
+ <input id="class-name" type="text" value="foo" />
+ <button onclick="return test()">Find</button>
+ </fieldset>
+</body>
+</html>
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>document.getElementsBySelector Demo</title>
+<style type="text/css">
+body {
+ margin: 1em 3em;
+ font-family: georgia;
+}
+div#foo {
+ border: 2px solid red;
+}
+</style>
+<script type="text/javascript" src="../../base.js"></script>
+<script type="text/javascript" src="../../dom.js"></script>
+<script type="text/javascript">
+function show(a) {
+ str = '';
+ for (i = 0; i != a.length; i++) {
+ str = str + a[i].toString() + '\n';
+ }
+ alert(str);
+}
+</script>
+</head>
+<body>
+<h1>document.getElementsBySelector Demo</h1>
+<p>See <a href="http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector" rel="bookmark">this blog entry</a> for more information.</p>
+<p>Here are some links in a normal paragraph: <a href="http://www.google.com/" title="Google!">Google</a>, <a href="http://groups.google.com/">Google Groups</a>. This link has <code>class="blog"</code>: <a href="http://diveintomark.org/" class="blog">diveintomark</a></p>
+<div id="foo">
+<p>Everything inside the red border is inside a div with <code>id="foo"</code>.</p>
+<p>This is a normal link: <a href="http://www.yahoo.com/">Yahoo</a></p>
+
+<p>This link has <code>class="blog"</code>: <a href="http://simon.incutio.com/" class="blog">Simon Willison's Weblog</a></p>
+</div>
+
+<form action="" method="get" onsubmit="return false;">
+<p>Try them out: </p>
+<ul>
+<li><input type="submit" value="document.getElementsBySelector('a')" onclick="show(document.getElementsBySelector('a'))" /></li>
+<li><input type="submit" value="document.getElementsBySelector('p a.blog')" onclick="show(document.getElementsBySelector('p a.blog'))" /></li>
+<li><input type="submit" value="document.getElementsBySelector('div#foo a')" onclick="show(document.getElementsBySelector('div#foo a'))" /></li>
+<li><input type="submit" value="document.getElementsBySelector('#foo a.blog')" onclick="show(document.getElementsBySelector('#foo a.blog'))" /></li>
+<li><input type="submit" value="document.getElementsBySelector('.blog')" onclick="show(document.getElementsBySelector('.blog'))" /></li>
+<li><input type="submit" value="document.getElementsBySelector('a[href^=&quot;http://www&quot;]')" onclick="show(document.getElementsBySelector('a[href*=&quot;http://www&quot;]'))" /></li>
+<li><input type="submit" value="document.getElementsBySelector('a[href$=&quot;org/&quot;]')" onclick="show(document.getElementsBySelector('a[href$=&quot;org/&quot;]'))" /> (fails in Opera 7)</li>
+<li><input type="submit" value="document.getElementsBySelector('a[href*=&quot;google&quot;]')" onclick="show(document.getElementsBySelector('a[href*=&quot;google&quot;]'))" /></li>
+<li><input type="submit" value="document.getElementsBySelector('a[rel=&quot;bookmark&quot;]')" onclick="show(document.getElementsBySelector('a[rel=&quot;bookmark&quot;]'))" /></li>
+<li><input type="submit" value="document.getElementsBySelector('a[title]')" onclick="show(document.getElementsBySelector('a[title]'))" /></li>
+</ul>
+</form>
+</body>
+</html>
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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<HTA:APPLICATION ID="SeleniumTestRunner" APPLICATIONNAME="Selenium" >
+<!-- the previous line is only relevant if you rename this
+ file to "TestRunner.hta" -->
+
+<!-- The copyright notice and other comments have been moved to after the HTA declaration,
+ to work-around a bug in IE on Win2K whereby the HTA application doesn't function correctly -->
+<!--
+Copyright 2004 ThoughtWorks, Inc
+
+ 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.
+-->
+<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type" />
+
+<title>Selenium Functional Test Runner</title>
+<script language="JavaScript" type="text/javascript" src="../../../tests/selenium/html-xpath/html-xpath-patched.js"></script>
+<script language="JavaScript" type="text/javascript" src="../../../tests/selenium/selenium-browserbot.js"></script>
+<script language="JavaScript" type="text/javascript" src="../../../tests/selenium/selenium-api.js"></script>
+<script language="JavaScript" type="text/javascript" src="../../../tests/selenium/selenium-commandhandlers.js"></script>
+<script language="JavaScript" type="text/javascript" src="../../../tests/selenium/selenium-executionloop.js"></script>
+<script language="JavaScript" type="text/javascript" src="../../../tests/selenium/selenium-fitrunner.js"></script>
+<script language="JavaScript" type="text/javascript" src="../../../tests/selenium/selenium-logging.js"></script>
+<script language="JavaScript" type="text/javascript" src="../../../tests/selenium/htmlutils.js"></script>
+<script language="JavaScript" type="text/javascript" src="../../../tests/selenium/xpath.js"></script>
+<script language="JavaScript" type="text/javascript">
+ function openDomViewer() {
+ var autFrame = document.getElementById('myiframe');
+ var autFrameDocument = getIframeDocument(autFrame);
+ var domViewer = window.open('../../../tests/selenium/domviewer.html');
+ domViewer.rootDocument = autFrameDocument;
+ return false;
+ }
+</script>
+<link rel="stylesheet" type="text/css" href="../../../tests/selenium/selenium.css" />
+</head>
+
+<body onload="start();">
+
+ <table class="layout">
+ <form action="" name="controlPanel">
+
+ <!-- Suite, Test, Control Panel -->
+
+ <tr class="selenium">
+ <td width="25%" height="30%" rowspan="2"><iframe name="testSuiteFrame" id="testSuiteFrame" src="test_scripts/TestSuite.html"></iframe></td>
+ <td width="50%" height="30%" rowspan="2"><iframe name="testFrame" id="testFrame"></iframe></td>
+ <th width="25%" height="1" class="header">
+ <h1><a href="http://selenium.thoughtworks.com" title="The Selenium Project">Selenium</a> TestRunner</h1>
+ </th>
+ </tr>
+
+ <tr class="selenium">
+ <td width="25%" height="30%" id="controlPanel">
+
+ <table id="controls">
+ <tr>
+ <td>
+ <b>Run:</b>
+ <button type="button" id="runTest" onclick="runSingleTest();"><em>Selected</em> Test</button>
+ <button type="button" id="runSuite" onclick="startTestSuite();"><strong>All</strong> Tests</button>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <b>Mode:</b>
+ <label><input id="modeRun" type="radio" name="runMode" value="0" checked="checked"/>Run</label>
+ <label><input id="modeWalk" type="radio" name="runMode" value="500" />Walk</label>
+ <label><input id="modeStep" type="radio" name="runMode" value="-1" />Step</label>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <button type="button" id="domViewer1" onclick="openDomViewer();">View DOM</button>
+ <button type="button" id="continueTest" disabled="disabled">Next/Continue</button>
+ </td>
+ </tr>
+ </table>
+
+ <table id="stats" align="center">
+ <tr>
+ <td colspan="2" align="right">Elapsed:</td>
+ <td id="elapsedTime" colspan="2">00.00</td>
+ </tr>
+ <tr>
+ <th colspan="2">Tests</th>
+ <th colspan="2">Commands</th>
+ </tr>
+ <tr>
+ <td class="count" id="testRuns">0</td>
+ <td>run</td>
+ <td class="count" id="commandPasses">0</td>
+ <td>passed</td>
+ </tr>
+ <tr>
+ <td class="count" id="testFailures">0</td>
+ <td>failed</td>
+ <td class="count" id="commandFailures">0</td>
+ <td>failed</td>
+ </tr>
+ <tr>
+ <td colspan="2"></td>
+ <td class="count" id="commandErrors">0</td>
+ <td>incomplete</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+
+ <!-- AUT -->
+
+ <tr>
+ <td colspan="3" height="70%"><iframe name="myiframe" id="myiframe" src="../../../tests/selenium/TestRunner-splash.html"></iframe></td>
+ </tr>
+ </form>
+ </table>
+
+ <div id="logging-console">
+ <h1>Javascript Log Console (<a id="closeLog" href="javascript:LOG.hide()">Close</a> <a href="javascript:LOG.clear();LOG.hide();">Clear</a>)</h1>
+ <ul id="log-list"/>
+ </div>
+ <script language="javascript">
+ LOG = new Logger(LEVEL_WARN);
+ LOG.hide();
+ </script>
+</body>
+</html>
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>Test all compressed JS</title>
+<script type="text/javascript" src="../prototype/prototype.js"></script>
+<script type="text/javascript" src="../prototype/compat.js"></script>
+<script type="text/javascript" src="../prototype/base.js"></script>
+<script type="text/javascript" src="../extended/base.js"></script>
+<script type="text/javascript" src="../extended/string.js"></script>
+<script type="text/javascript" src="../extended/util.js"></script>
+<script type="text/javascript" src="../extended/array.js"></script>
+<script type="text/javascript" src="../extended/functional.js"></script>
+<script type="text/javascript" src="../prado/prado.js"></script>
+
+<script type="text/javascript" src="../prototype/dom.js"></script>
+<script type="text/javascript" src="../extended/dom.js"></script>
+<script type="text/javascript" src="../prototype/event.js"></script>
+<script type="text/javascript" src="../extended/event.js"></script>
+<script type="text/javascript" src="../prototype/form.js"></script>
+<script type="text/javascript" src="../prototype/position.js"></script>
+<script type="text/javascript" src="../prototype/string.js"></script>
+<script type="text/javascript" src="../extra/getElementsBySelector.js"></script>
+<script type="text/javascript" src="../extra/behaviour.js"></script>
+
+
+<script type="text/javascript" src="../../effects.js"></script>
+<!--<script type="text/javascript" src="../../rico.js"></script>-->
+<script type="text/javascript" src="../../template.js"></script>
+
+<script type="text/javascript" src="../prado/validation.js"></script>
+<script type="text/javascript" src="../prado/validators.js"></script>
+
+
+<script type="text/javascript">
+
+ var a = [ "one", "two"];
+ for(var i in a)
+ alert(i+":"+a[i]);
+
+ alert(a.contains("two"));
+</script>
+
+</head>
+<body>
+OK
+</body>
+</html>
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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><!--Copyright 2004 ThoughtWorks, Inc 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.-->
+<html>
+<head>
+<title>Test Open</title>
+ <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1" id=TABLE1>
+ <tbody>
+ <tr>
+ <td rowspan="1" colspan="3">Test Open<br>
+ </td>
+ </tr>
+ <tr>
+ <td>open</td>
+ <td>../tests/RequiredFieldValidator.html</td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>assertLocation</td>
+ <td>RequiredFieldValidator.html</td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>assertTextPresent</td>
+ <td> RequiredFieldValidator Tests
+ </td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>assertNotVisible</td>
+ <td>validator1</td>
+ <td></td></tr>
+ <tr>
+ <td>assertNotVisible</td>
+ <td>validator2</td>
+ <td></td></tr>
+ <tr>
+ <td>click</td>
+ <td>submit1</td>
+ <td></td></tr>
+ <tr>
+ <td>assertVisible</td>
+ <td>validator1</td>
+ <td></td></tr>
+ <tr>
+ <td>assertVisible</td>
+ <td>validator2</td>
+ <td></td></tr>
+ <tr>
+ <td>type</td>
+ <td>text1</td>
+ <td>testing</td></tr>
+ <tr>
+ <td>click</td>
+ <td>submit1</td>
+ <td></td></tr>
+ <tr>
+ <td>assertNotVisible</td>
+ <td>validator1</td>
+ <td></td></tr>
+ <tr>
+ <td>click</td>
+ <td>submit2</td>
+ <td></td></tr>
+ <tr>
+ <td>assertNotVisible</td>
+ <td>validator1</td>
+ <td></td></tr>
+ <tr>
+ <td>assertNotVisible</td>
+ <td>validator2</td>
+ <td></td></tr>
+ <tr>
+ <td>assertVisible</td>
+ <td>validator3</td>
+ <td></td></tr>
+ <tr>
+ <td>assertVisible</td>
+ <td>validator4</td>
+ <td></td></tr>
+ </tbody>
+</table>
+</body>
+</html> \ 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 @@
+<!--
+Copyright 2004 ThoughtWorks, Inc
+
+ 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.
+-->
+<html>
+<head>
+<meta content="text/html; charset=ISO-8859-1"
+http-equiv="content-type">
+<title>Test Suite</title>
+
+</head>
+
+<body>
+
+<table cellpadding="1"
+ cellspacing="1"
+ border="1">
+ <tbody>
+ <tr><td><b>Test Suite</b></td></tr>
+ <tr><td><a href="./TestRequiredFieldValidator.html">TestTRequiredFieldValidator</a></td></tr>
+ </tbody>
+ </table>
+
+</body>
+</html>
diff --git a/framework/Web/UI/WebControls/TPanel.php b/framework/Web/UI/WebControls/TPanel.php
index c6f49859..5cdfa307 100644
--- a/framework/Web/UI/WebControls/TPanel.php
+++ b/framework/Web/UI/WebControls/TPanel.php
@@ -109,12 +109,20 @@ class TPanel extends TWebControl
$this->setViewState('BackImageUrl',$value,'');
}
+ /**
+ * @return string alignment of the content in the panel.
+ * Valid values include 'ltr' (left to right) and 'rtl' (right to left).
+ * Defaults to empty.
+ */
public function getDirection()
{
return $this->getViewState('Direction','');
}
- // valid values include 'ltr' and 'rtl'.
+ /**
+ * @param string alignment of the content in the panel.
+ * Valid values include 'ltr' (left to right) and 'rtl' (right to left).
+ */
public function setDirection($value)
{
$this->setViewState('Direction',$value,'');
@@ -130,11 +138,17 @@ class TPanel extends TWebControl
$this->_defaultButton=$value;
}
+ /**
+ * @return string the legend text when the panel is used as a fieldset. Defaults to empty.
+ */
public function getGroupingText()
{
return $this->getViewState('GroupingText','');
}
+ /**
+ * @param string the legend text. If this value is not empty, the panel will be rendered as a fieldset.
+ */
public function setGroupingText($value)
{
$this->setViewState('GroupingText',$value,'');