summaryrefslogtreecommitdiff
path: root/framework/Web/Javascripts/base
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/Javascripts/base
parent418baf36d477bcbdd6fb4eaf4037ea6a2d93f21c (diff)
Diffstat (limited to 'framework/Web/Javascripts/base')
-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
10 files changed, 2879 insertions, 0 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;
+}