From ccf76e430b7703db028966a845a966f50956f490 Mon Sep 17 00:00:00 2001 From: xue <> Date: Mon, 5 Dec 2005 01:00:16 +0000 Subject: --- framework/Web/Javascripts/base/ajax.js | 355 ++++++++++ framework/Web/Javascripts/base/controls.js | 179 ++++++ framework/Web/Javascripts/base/datepicker.js | 928 +++++++++++++++++++++++++++ framework/Web/Javascripts/base/focus.js | 99 +++ framework/Web/Javascripts/base/json.js | 340 ++++++++++ framework/Web/Javascripts/base/postback.js | 72 +++ framework/Web/Javascripts/base/prado.js | 3 + framework/Web/Javascripts/base/scroll.js | 0 framework/Web/Javascripts/base/validation.js | 675 +++++++++++++++++++ framework/Web/Javascripts/base/validators.js | 228 +++++++ 10 files changed, 2879 insertions(+) create mode 100644 framework/Web/Javascripts/base/ajax.js create mode 100644 framework/Web/Javascripts/base/controls.js create mode 100644 framework/Web/Javascripts/base/datepicker.js create mode 100644 framework/Web/Javascripts/base/focus.js create mode 100644 framework/Web/Javascripts/base/json.js create mode 100644 framework/Web/Javascripts/base/postback.js create mode 100644 framework/Web/Javascripts/base/prado.js create mode 100644 framework/Web/Javascripts/base/scroll.js create mode 100644 framework/Web/Javascripts/base/validation.js create mode 100644 framework/Web/Javascripts/base/validators.js (limited to 'framework/Web/Javascripts/base') diff --git a/framework/Web/Javascripts/base/ajax.js b/framework/Web/Javascripts/base/ajax.js new file mode 100644 index 00000000..f1dd69c5 --- /dev/null +++ b/framework/Web/Javascripts/base/ajax.js @@ -0,0 +1,355 @@ +/** + * Prado AJAX service. The default service provider is JPSpan. + */ +Prado.AJAX = { Service : 'Prototype' }; + +/** + * Parse and execute javascript embedded in html. + */ +Prado.AJAX.EvalScript = function(output) +{ + + var match = new RegExp(Ajax.Updater.ScriptFragment, 'img'); + var scripts = output.match(match); + if (scripts) + { + match = new RegExp(Ajax.Updater.ScriptFragment, 'im'); + setTimeout((function() + { + for (var i = 0; i < scripts.length; i++) + eval(scripts[i].match(match)[1]); + }).bind(this), 50); + } +} + + +/** + * AJAX service request using Prototype's AJAX request class. + */ +Prado.AJAX.Request = Class.create(); +Prado.AJAX.Request.prototype = Object.extend(Ajax.Request.prototype, +{ + /** + * Evaluate the respond JSON data, override parent implementing. + * If default eval fails, try parsing the JSON data (slower). + */ + evalJSON: function() + { + try + { + var json = this.transport.getResponseHeader('X-JSON'), object; + object = eval(json); + return object; + } + catch (e) + { + if(isString(json)) + { + return Prado.AJAX.JSON.parse(json); + } + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + if(event == 'Complete' && transport.status) + Ajax.Responders.dispatch('on' + transport.status, this, transport, json); + + if (event == 'Complete') + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + } + +}); + +Prado.AJAX.Error = function(e, code) +{ + e.name = 'Prado.AJAX.Error'; + e.code = code; + return e; +} + +/** + * Post data builder, serialize the data using JSON. + */ +Prado.AJAX.RequestBuilder = Class.create(); +Prado.AJAX.RequestBuilder.prototype = +{ + initialize : function() + { + this.body = ''; + this.data = []; + }, + encode : function(data) + { + return Prado.AJAX.JSON.stringify(data); + }, + build : function(data) + { + var sep = ''; + for ( var argName in data) + { + if(isFunction(data[argName])) continue; + try + { + this.body += sep + argName + '='; + this.body += encodeURIComponent(this.encode(data[argName])); + } catch (e) { + throw Prado.AJAX.Error(e, 1006); + } + sep = '&'; + } + }, + + getAll : function() + { + this.build(this.data); + return this.body; + } +} + + +Prado.AJAX.RemoteObject = function(){}; + +/** + * AJAX service request for Prado RemoteObjects + */ +Prado.AJAX.RemoteObject.Request = Class.create(); +Prado.AJAX.RemoteObject.Request.prototype = Object.extend(Prado.AJAX.Request.prototype, +{ + /** + * Initialize the RemoteObject Request, overrides parent + * implementation by delaying the request to invokeRemoteObject. + */ + initialize : function(options) + { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.post = new Prado.AJAX.RequestBuilder(); + }, + + /** + * Call the remote object, + * @param string the remote server url + * @param array additional arguments + */ + invokeRemoteObject : function(url, args) + { + this.initParameters(args); + this.options.postBody = this.post.getAll(); + this.request(url); + }, + + /** + * Set the additional arguments as post data with key '__parameters' + */ + initParameters : function(args) + { + this.post.data['__parameters'] = []; + for(var i = 0; i + * var TestObject1 = Class.create(); + * TestObject1.prototype = Object.extend(new Prado.AJAX.RemoteObject(), + * { + * initialize : function(handlers, options) + * { + * this.__serverurl = 'http://127.0.0.1/.....'; + * this.baseInitialize(handlers, options); + * } + * + * method1 : function() + * { + * return this.__call(this.__serverurl, 'method1', arguments); + * } + * }); + * + * And client usage, + * + * var test1 = new TestObject1(); //create new remote object + * test1.method1(); //call the method, no onComplete hook + * + * var onComplete = { method1 : function(result){ alert(result) } }; + * //create new remote object with onComplete callback + * var test2 = new TestObject1(onComplete); + * test2.method1(); //call it, on success, onComplete's method1 is called. + * + */ +Prado.AJAX.RemoteObject.prototype = +{ + baseInitialize : function(handlers, options) + { + this.__handlers = handlers || {}; + this.__service = new Prado.AJAX.RemoteObject.Request(options); + }, + + __call : function(url, method, args) + { + this.__service.options.onSuccess = this.__onSuccess.bind(this); + this.__callback = method; + return this.__service.invokeRemoteObject(url+"/"+method, args); + }, + + __onSuccess : function(transport, json) + { + if(this.__handlers[this.__callback]) + this.__handlers[this.__callback](json, transport.responseText); + } +}; + + + +/** + * Respond to Prado AJAX request exceptions. + */ +Prado.AJAX.Exception = +{ + /** + * Server returns 505 exception. Just log it. + */ + "on505" : function(request, transport, e) + { + var msg = 'HTTP '+transport.status+" with response"; + Logger.error(msg, transport.responseText); + Logger.exception(e); + }, + + onComplete : function(request, transport, e) + { + if(transport.status != 505) + { + var msg = 'HTTP '+transport.status+" with response : \n"; + msg += transport.responseText + "\n"; + msg += "Data : \n"+e; + Logger.warn(msg); + } + }, + + format : function(e) + { + var msg = e.type + " with message \""+e.message+"\""; + msg += " in "+e.file+"("+e.line+")\n"; + msg += "Stack trace:\n"; + var trace = e.trace; + for(var i = 0; i"+trace[i]["function"]+"()"+"\n"; + } + return msg; + }, + + logException : function(e) + { + var msg = Prado.AJAX.Exception.format(e); + Logger.error("Server Error "+e.code, msg); + } +} + +//Add HTTP exception respones when logger is enabled. +Event.OnLoad(function() +{ + if(typeof Logger != "undefined") + { + Logger.exception = Prado.AJAX.Exception.logException; + Ajax.Responders.register(Prado.AJAX.Exception); + } +}); + +/** + * Prado Callback service that provides component intergration, + * viewstate (read only), and automatic form data serialization. + * Usage: new Prado.AJAX.Callback('MyPage.MyComponentID.raiseCallbackEvent', options) + * These classes should be called by the components developers. + * For inline callback service, use Prado.Callback(callbackID, params). + */ +Prado.AJAX.Callback = Class.create(); +Prado.AJAX.Callback.prototype = Object.extend(new Prado.AJAX.RemoteObject(), +{ + + /** + * Create and request a new Prado callback service. + * @param string the callback ID, must be of the form, ClassName.ComponentID.MethodName + * @param list options with list key onCallbackReturn, and more. + * + */ + initialize : function(ID, options) + { + if(!isString(ID)) + throw new Error('A Control ID must be specified'); + this.baseInitialize(this, options); + this.options = options || []; + this.__service.post.data['__ID'] = ID; + this.requestCallback(); + }, + + /** + * Get form data for components that implements IPostBackHandler. + */ + collectPostData : function() + { + var IDs = Prado.AJAX.Callback.IDs; + this.__service.post.data['__data'] = {}; + for(var i = 0; iClick me + * @param string callback ID + * @param array parameters to pass to the callback service + */ +Prado.Callback = function(ID, params, onSuccess) +{ + var options = + { + 'params' : [params] || [], + 'onSuccess' : onSuccess || Prototype.emptyFunction + }; + + new Prado.AJAX.Callback(ID, options); + return false; +} \ No newline at end of file diff --git a/framework/Web/Javascripts/base/controls.js b/framework/Web/Javascripts/base/controls.js new file mode 100644 index 00000000..6cb908ed --- /dev/null +++ b/framework/Web/Javascripts/base/controls.js @@ -0,0 +1,179 @@ +/** + * Auto complete textbox via AJAX. + */ +Prado.AutoCompleter = Class.create(); + + +/** + * Overrides parent implementation of updateElement by trimming the value. + */ +Prado.AutoCompleter.Base = function(){}; +Prado.AutoCompleter.Base.prototype = Object.extend(Autocompleter.Base.prototype, +{ + updateElement: function(selectedElement) + { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + + var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = (newValue + value).trim(); + } else { + this.element.value = value.trim(); + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + } +}); + +/** + * Based on the Prototype Autocompleter class. + * This client-side component should be instantiated from a Prado component. + * Usage: new Prado.AutoCompleter('textboxID', 'updateDivID', {callbackID : '...'}); + */ +Prado.AutoCompleter.prototype = Object.extend(new Autocompleter.Base(), +{ + /** + * This component is initialized by + * new Prado.AutoCompleter(...) + * @param string the ID of the textbox element to observe + * @param string the ID of the div to display the auto-complete options + * @param array a hash of options, e.g. auto-completion token separator. + */ + initialize : function(element, update, options) + { + this.baseInitialize(element, update, options); + }, + + /** + * The callback function, i.e., function called on successful AJAX return. + * Calls update choices in the Autocompleter. + * @param string new auto-complete options for display + */ + onUpdateReturn : function(result) + { + if(isString(result) && result.length > 0) + this.updateChoices(result); + }, + + /** + * Requesting new choices using Prado's client-side callback scheme. + */ + getUpdatedChoices : function() + { + Prado.Callback(this.element.id, this.getToken(), this.onUpdateReturn.bind(this)); + } +}); + +/** + * Prado TActivePanel client javascript. Usage + * + * Prado.ActivePanel.register("id", options); + * Prado.ActivePanel.update("id", "hello"); + * + */ +Prado.ActivePanel = +{ + callbacks : {}, + + register : function(id, options) + { + Prado.ActivePanel.callbacks[id] = options; + }, + + update : function(id, param) + { + var request = new Prado.ActivePanel.Request(id, + Prado.ActivePanel.callbacks[id]); + request.callback(param); + } +} + +/** + * Client-script for TActivePanel. Uses Callback to notify the server + * for updates, if update option is set, the innerHTML of the update ID + * is set to the returned output. + */ +Prado.ActivePanel.Request = Class.create(); +Prado.ActivePanel.Request.prototype = +{ + initialize : function(element, options) + { + this.element = element; + this.setOptions(options); + }, + + /** + * Set some options. + */ + setOptions : function(options) + { + this.options = + { + onSuccess : this.onSuccess.bind(this) + } + Object.extend(this.options, options || {}); + }, + + /** + * Make the callback request + */ + callback : function(param) + { + this.options.params = [param]; + new Prado.AJAX.Callback(this.element, this.options); + }, + + /** + * Callback onSuccess handler, update the element innerHTML if necessary + */ + onSuccess : function(result, output) + { + if(this.options.update) + { + var element = $(this.options.update) + if(element) element.innerHTML = output; + } + } +} + +/** + * Drop container to accept draggable component drops. + */ +Prado.DropContainer = Class.create(); +Prado.DropContainer.prototype = Object.extend(new Prado.ActivePanel.Request(), +{ + initialize : function(element, options) + { + this.element = element; + this.setOptions(options); + Object.extend(this.options, + { + onDrop : this.onDrop.bind(this), + evalScripts : true, + onSuccess : options.onSuccess || this.update.bind(this) + }); + Droppables.add(element, this.options); + }, + + onDrop : function(draggable, droppable) + { + this.callback(draggable.id) + }, + + update : function(result, output) + { + this.onSuccess(result, output); + if (this.options.evalScripts) + Prado.AJAX.EvalScript(output); + } +}); \ No newline at end of file diff --git a/framework/Web/Javascripts/base/datepicker.js b/framework/Web/Javascripts/base/datepicker.js new file mode 100644 index 00000000..046a0a4b --- /dev/null +++ b/framework/Web/Javascripts/base/datepicker.js @@ -0,0 +1,928 @@ +Prado.Calendar = Class.create(); + +Prado.Calendar.Util = Class.create(); + +Object.extend(Prado.Calendar.Util, +{ + // utility function to pad a number to a given width + pad : function(number, X) + { + X = (!X ? 2 : X); + number = ""+number; + while (number.length < X) + number = "0" + number; + return number; + }, + + //allow for deprecated formats + FormatDate : function(date, format) + { + if(!isObject(date)) return ""; + if(format.indexOf("%") > -1) + { + alert('Please use the new SimpleDateFormat pattern, e.g. yyyy-MM-dd'); + return this.FormatDateDepr(date,format); + } + else + { + return this.SimpleFormatDate(date, format); + } + }, + + //allow for deprecated format + ParseDate : function(value, format) + { + val=String(value); + format=String(format); + + if(val.length <= 0) return null; + + if(format.length <= 0) return new Date(value); + + if(format.indexOf("%") > -1) + return this.ParseDateDepr(value, format); + else + return this.SimpleParseDate(value, format); + }, + + //deprecated format + FormatDateDepr : function(date, str) + { + var m = date.getMonth(); + var d = date.getDate(); + var y = date.getFullYear(); + + var s = {}; + + s["%d"] = this.pad(d); // the day of the month (range 01 to 31) + s["%e"] = d; // the day of the month (range 1 to 31) + s["%m"] = this.pad(m+1); + s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) + s["%Y"] = y; // year with the century + + var re = /%./g; + + var a = str.match(re); + for (var i = 0; i < a.length; i++) + { + var tmp = s[a[i]]; + if (tmp) + { + re = new RegExp(a[i], 'g'); + str = str.replace(re, tmp); + } + } + + return str; + }, + + //deprecated format parser + ParseDateDepr : function(value, format) + { + var y = 0; + var m = -1; + var d = 0; + var a = value.split(/\W+/); + var b = format.match(/%./g); + var i = 0, j = 0; + var hr = 0; + var min = 0; + for (i = 0; i < a.length; ++i) { + if (!a[i]) + continue; + switch (b[i]) { + case "%d": + case "%e": + d = parseInt(a[i], 10); + break; + + case "%m": + m = parseInt(a[i], 10) - 1; + break; + + case "%Y": + case "%y": + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + break; + + case "%H": + case "%I": + case "%k": + case "%l": + hr = parseInt(a[i], 10); + break; + + case "%P": + case "%p": + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + break; + + case "%M": + min = parseInt(a[i], 10); + break; + } + } + if (y != 0 && m != -1 && d != 0) + { + var date = new Date(y, m, d, hr, min, 0); + return (isObject(date) + && y == date.getFullYear() + && m == date.getMonth() + && d == date.getDate()) ? date : null; + } + return null; + }, + + SimpleFormatDate : function(date, format) + { + if(!isObject(date)) return ""; + var bits = new Array(); + bits['d'] = date.getDate(); + bits['dd'] = this.pad(date.getDate(),2); + + bits['M'] = date.getMonth()+1; + bits['MM'] = this.pad(date.getMonth()+1,2); + + var yearStr = "" + date.getFullYear(); + yearStr = (yearStr.length == 2) ? '19' + yearStr: yearStr; + bits['yyyy'] = yearStr; + bits['yy'] = bits['yyyy'].toString().substr(2,2); + + // do some funky regexs to replace the format string + // with the real values + var frm = new String(format); + for (var sect in bits) + { + var reg = new RegExp("\\b"+sect+"\\b" ,"g"); + frm = frm.replace(reg, bits[sect]); + } + return frm; + }, + + SimpleParseDate : function(value, format) + { + val=String(value); + format=String(format); + + if(val.length <= 0) return null; + + if(format.length <= 0) return new Date(value); + + var isInteger = function (val) + { + var digits="1234567890"; + for (var i=0; i < val.length; i++) + { + if (digits.indexOf(val.charAt(i))==-1) { return false; } + } + return true; + }; + + var getInt = function(str,i,minlength,maxlength) + { + for (var x=maxlength; x>=minlength; x--) + { + var token=str.substring(i,i+x); + if (token.length < minlength) { return null; } + if (isInteger(token)) { return token; } + } + return null; + }; + + var i_val=0; + var i_format=0; + var c=""; + var token=""; + var token2=""; + var x,y; + var now=new Date(); + var year=now.getFullYear(); + var month=now.getMonth()+1; + var date=1; + + while (i_format < format.length) + { + // Get next token from format string + c=format.charAt(i_format); + token=""; + while ((format.charAt(i_format)==c) && (i_format < format.length)) + { + token += format.charAt(i_format++); + } + + // Extract contents of value based on format token + if (token=="yyyy" || token=="yy" || token=="y") + { + if (token=="yyyy") { x=4;y=4; } + if (token=="yy") { x=2;y=2; } + if (token=="y") { x=2;y=4; } + year=getInt(val,i_val,x,y); + if (year==null) { return null; } + i_val += year.length; + if (year.length==2) + { + if (year > 70) { year=1900+(year-0); } + else { year=2000+(year-0); } + } + } + + else if (token=="MM"||token=="M") + { + month=getInt(val,i_val,token.length,2); + if(month==null||(month<1)||(month>12)){return null;} + i_val+=month.length; + } + else if (token=="dd"||token=="d") + { + date=getInt(val,i_val,token.length,2); + if(date==null||(date<1)||(date>31)){return null;} + i_val+=date.length; + } + else + { + if (val.substring(i_val,i_val+token.length)!=token) {return null;} + else {i_val+=token.length;} + } + } + + // If there are any trailing characters left in the value, it doesn't match + if (i_val != val.length) { return null; } + + // Is date valid for month? + if (month==2) + { + // Check for leap year + if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year + if (date > 29){ return null; } + } + else { if (date > 28) { return null; } } + } + + if ((month==4)||(month==6)||(month==9)||(month==11)) + { + if (date > 30) { return null; } + } + + var newdate=new Date(year,month-1,date, 0, 0, 0); + return newdate; + }, + + IsLeapYear : function (year) + { + return ((year%4 == 0) && ((year%100 != 0) || (year%400 == 0))); + }, + + yearLength : function(year) + { + if (this.isLeapYear(year)) + return 366; + else + return 365; + }, + + dayOfYear : function(date) + { + var a = this.isLeapYear(date.getFullYear()) ? + Calendar.LEAP_NUM_DAYS : Calendar.NUM_DAYS; + return a[date.getMonth()] + date.getDate(); + }, + ISODate : function(date) + { + var y = date.getFullYear(); + var m = this.pad(date.getMonth() + 1); + var d = this.pad(date.getDate()); + return String(y) + String(m) + String(d); + }, + + browser : function() + { + var info = { Version : "1.0" }; + var is_major = parseInt( navigator.appVersion ); + info.nver = is_major; + info.ver = navigator.appVersion; + info.agent = navigator.userAgent; + info.dom = document.getElementById ? 1 : 0; + info.opera = window.opera ? 1 : 0; + info.ie5 = ( info.ver.indexOf( "MSIE 5" ) > -1 && info.dom && !info.opera ) ? 1 : 0; + info.ie6 = ( info.ver.indexOf( "MSIE 6" ) > -1 && info.dom && !info.opera ) ? 1 : 0; + info.ie4 = ( document.all && !info.dom && !info.opera ) ? 1 : 0; + info.ie = info.ie4 || info.ie5 || info.ie6; + info.mac = info.agent.indexOf( "Mac" ) > -1; + info.ns6 = ( info.dom && parseInt( info.ver ) >= 5 ) ? 1 : 0; + info.ie3 = ( info.ver.indexOf( "MSIE" ) && ( is_major < 4 ) ); + info.hotjava = ( info.agent.toLowerCase().indexOf( 'hotjava' ) != -1 ) ? 1 : 0; + info.ns4 = ( document.layers && !info.dom && !info.hotjava ) ? 1 : 0; + info.bw = ( info.ie6 || info.ie5 || info.ie4 || info.ns4 || info.ns6 || info.opera ); + info.ver3 = ( info.hotjava || info.ie3 ); + info.opera7 = ( ( info.agent.toLowerCase().indexOf( 'opera 7' ) > -1 ) || ( info.agent.toLowerCase().indexOf( 'opera/7' ) > -1 ) ); + info.operaOld = info.opera && !info.opera7; + return info; + }, + + ImportCss : function(doc, css_file) + { + if (this.browser().ie) + var styleSheet = doc.createStyleSheet(css_file); + else + { + var elm = doc.createElement("link"); + + elm.rel = "stylesheet"; + elm.href = css_file; + + if (headArr = doc.getElementsByTagName("head")) + headArr[0].appendChild(elm); + } + } +}); + +Object.extend(Prado.Calendar, +{ + // Accumulated days per month, for normal and for leap years. + // Used in week number calculations. + NUM_DAYS : [0,31,59,90,120,151,181,212,243,273,304,334], + LEAP_NUM_DAYS : [0,31,60,91,121,152,182,213,244,274,305,335] +}); + +Prado.Calendar.prototype = +{ + monthNames : [ "January", "February", "March", "April", + "May", "June", "July", "August", + "September", "October", "November", "December" + ], + + shortWeekDayNames : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], + + format : "yyyy-MM-dd", + + css : "calendar_system.css", + + initialize : function(control, attr) + { + this.attr = attr || []; + this.control = $(control); + this.dateSlot = new Array(42); + this.weekSlot = new Array(6); + this.firstDayOfWeek = 1; + this.minimalDaysInFirstWeek = 4; + this.currentDate = new Date(); + this.selectedDate = null; + this.className = "Prado_Calendar"; + + //which element to trigger to show the calendar + this.trigger = this.attr.trigger ? $(this.attr.trigger) : this.control; + Event.observe(this.trigger, "click", this.show.bind(this)); + + Prado.Calendar.Util.ImportCss(document, this.css); + + if(this.attr.format) this.format = this.attr.format; + + //create it + this.create(); + this.hookEvents(); + }, + + create : function() + { + var div; + var table; + var tbody; + var tr; + var td; + + // Create the top-level div element + this._calDiv = document.createElement("div"); + this._calDiv.className = this.className; + this._calDiv.style.display = "none"; + + // header div + div = document.createElement("div"); + div.className = "calendarHeader"; + this._calDiv.appendChild(div); + + table = document.createElement("table"); + table.style.cellSpacing = 0; + div.appendChild(table); + + tbody = document.createElement("tbody"); + table.appendChild(tbody); + + tr = document.createElement("tr"); + tbody.appendChild(tr); + + // Previous Month Button + td = document.createElement("td"); + td.className = "prevMonthButton"; + this._previousMonth = document.createElement("button"); + this._previousMonth.appendChild(document.createTextNode("<<")); + td.appendChild(this._previousMonth); + tr.appendChild(td); + + + + // + // Create the month drop down + // + td = document.createElement("td"); + td.className = "labelContainer"; + tr.appendChild(td); + this._monthSelect = document.createElement("select"); + for (var i = 0 ; i < this.monthNames.length ; i++) { + var opt = document.createElement("option"); + opt.innerHTML = this.monthNames[i]; + opt.value = i; + if (i == this.currentDate.getMonth()) { + opt.selected = true; + } + this._monthSelect.appendChild(opt); + } + td.appendChild(this._monthSelect); + + + // + // Create the year drop down + // + td = document.createElement("td"); + td.className = "labelContainer"; + tr.appendChild(td); + this._yearSelect = document.createElement("select"); + for(var i=1920; i < 2050; ++i) { + var opt = document.createElement("option"); + opt.innerHTML = i; + opt.value = i; + if (i == this.currentDate.getFullYear()) { + opt.selected = false; + } + this._yearSelect.appendChild(opt); + } + td.appendChild(this._yearSelect); + + + td = document.createElement("td"); + td.className = "nextMonthButton"; + this._nextMonth = document.createElement("button"); + this._nextMonth.appendChild(document.createTextNode(">>")); + td.appendChild(this._nextMonth); + tr.appendChild(td); + + // Calendar body + div = document.createElement("div"); + div.className = "calendarBody"; + this._calDiv.appendChild(div); + this._table = div; + + // Create the inside of calendar body + + var text; + table = document.createElement("table"); + //table.style.width="100%"; + table.className = "grid"; + + div.appendChild(table); + var thead = document.createElement("thead"); + table.appendChild(thead); + tr = document.createElement("tr"); + thead.appendChild(tr); + + for(i=0; i < 7; ++i) { + td = document.createElement("th"); + text = document.createTextNode(this.shortWeekDayNames[(i+this.firstDayOfWeek)%7]); + td.appendChild(text); + td.className = "weekDayHead"; + tr.appendChild(td); + } + + // Date grid + tbody = document.createElement("tbody"); + table.appendChild(tbody); + + for(week=0; week<6; ++week) { + tr = document.createElement("tr"); + tbody.appendChild(tr); + + for(day=0; day<7; ++day) { + td = document.createElement("td"); + td.className = "calendarDate"; + text = document.createTextNode(String.fromCharCode(160)); + td.appendChild(text); + + tr.appendChild(td); + var tmp = new Object(); + tmp.tag = "DATE"; + tmp.value = -1; + tmp.data = text; + this.dateSlot[(week*7)+day] = tmp; + + Event.observe(td, "mouseover", this.hover.bind(this)); + Event.observe(td, "mouseout", this.hover.bind(this)); + + } + } + + // Calendar Footer + div = document.createElement("div"); + div.className = "calendarFooter"; + this._calDiv.appendChild(div); + + table = document.createElement("table"); + //table.style.width="100%"; + table.className = "footerTable"; + //table.cellSpacing = 0; + div.appendChild(table); + + tbody = document.createElement("tbody"); + table.appendChild(tbody); + + tr = document.createElement("tr"); + tbody.appendChild(tr); + + // + // The TODAY button + // + td = document.createElement("td"); + td.className = "todayButton"; + this._todayButton = document.createElement("button"); + var today = new Date(); + var buttonText = today.getDate() + " " + this.monthNames[today.getMonth()] + ", " + today.getFullYear(); + this._todayButton.appendChild(document.createTextNode(buttonText)); + td.appendChild(this._todayButton); + tr.appendChild(td); + + // + // The CLEAR button + // + td = document.createElement("td"); + td.className = "clearButton"; + this._clearButton = document.createElement("button"); + var today = new Date(); + buttonText = "Clear"; + this._clearButton.appendChild(document.createTextNode(buttonText)); + td.appendChild(this._clearButton); + tr.appendChild(td); + + document.body.appendChild(this._calDiv); + + this.update(); + this.updateHeader(); + + return this._calDiv; + }, + + hookEvents : function() + { + // IE55+ extension + this._previousMonth.hideFocus = true; + this._nextMonth.hideFocus = true; + this._todayButton.hideFocus = true; + // end IE55+ extension + + // hook up events + Event.observe(this._previousMonth, "click", this.prevMonth.bind(this)); + Event.observe(this._nextMonth, "click", this.nextMonth.bind(this)); + Event.observe(this._todayButton, "click", this.selectToday.bind(this)); + Event.observe(this._clearButton, "click", this.clearSelection.bind(this)); + + Event.observe(this._monthSelect, "change", this.monthSelect.bind(this)); + Event.observe(this._yearSelect, "change", this.yearSelect.bind(this)); + + // ie6 extension + Event.observe(this._calDiv, "mousewheel", this.mouseWheelChange.bind(this)); + + Event.observe(this._table, "click", this.selectDate.bind(this)); + + Event.observe(this._calDiv,"keydown", this.keyPressed.bind(this)); + + /* + this._calDiv.onkeydown = function (e) { + if (e == null) e = document.parentWindow.event; + var kc = e.keyCode != null ? e.keyCode : e.charCode; + + if(kc == 13) { + var d = new Date(dp._currentDate).valueOf(); + dp.setSelectedDate(d); + + if (!dp._alwaysVisible && dp._hideOnSelect) { + dp.hide(); + } + return false; + } + + + if (kc < 37 || kc > 40) return true; + + var d = new Date(dp._currentDate).valueOf(); + if (kc == 37) // left + d -= 24 * 60 * 60 * 1000; + else if (kc == 39) // right + d += 24 * 60 * 60 * 1000; + else if (kc == 38) // up + d -= 7 * 24 * 60 * 60 * 1000; + else if (kc == 40) // down + d += 7 * 24 * 60 * 60 * 1000; + + dp.setCurrentDate(new Date(d)); + return false; + }*/ + + + }, + + keyPressed : function(ev) + { + if (!ev) ev = document.parentWindow.event; + var kc = ev.keyCode != null ? ev.keyCode : ev.charCode; + + if(kc = Event.KEY_RETURN) + { + //var d = new Date(this.currentDate); + this.setSelectedDate(this.currentDate); + this.hide(); + return false; + } + + if(kc < 37 || kc > 40) return true; + + var d = new Date(this.currentDate).valueOf(); + if(kc == Event.KEY_LEFT) + d -= 86400000; //-1 day + else if (kc == Event.KEY_RIGHT) + d += 86400000; //+1 day + else if (kc == Event.KEY_UP) + d -= 604800000; // -7 days + else if (kc == Event.KEY_DOWN) + d += 604800000; // +7 days + this.setCurrentDate(new Date(d)); + return false; + }, + + selectDate : function(ev) + { + var el = Event.element(ev); + while (el.nodeType != 1) + el = el.parentNode; + + while (el != null && el.tagName && el.tagName.toLowerCase() != "td") + el = el.parentNode; + + // if no td found, return + if (el == null || el.tagName == null || el.tagName.toLowerCase() != "td") + return; + + var d = new Date(this.currentDate); + var n = Number(el.firstChild.data); + if (isNaN(n) || n <= 0 || n == null) + return; + + d.setDate(n); + this.setSelectedDate(d); + this.hide(); + }, + + selectToday : function() + { + this.setSelectedDate(new Date()); + this.hide(); + }, + + clearSelection : function() + { + this.selectedDate = null; + if (isFunction(this.onchange)) + this.onchange(); + this.hide(); + }, + + monthSelect : function(ev) + { + this.setMonth(Form.Element.getValue(Event.element(ev))); + }, + + yearSelect : function(ev) + { + this.setYear(Form.Element.getValue(Event.element(ev))); + }, + + // ie6 extension + mouseWheelChange : function (e) + { + if (e == null) e = document.parentWindow.event; + var n = - e.wheelDelta / 120; + var d = new Date(this.currentDate); + var m = this.getMonth() + n; + this.setMonth(m); + this.setCurrentDate(d); + + return false; + }, + + onchange : function() + { + this.control.value = this.formatDate(); + }, + + formatDate : function() + { + return Prado.Calendar.Util.FormatDate(this.selectedDate, this.format); + }, + + setCurrentDate : function(date) + { + if (date == null) + return; + + // if string or number create a Date object + if (isString(date) || isNumber(date)) + date = new Date(date); + + // do not update if not really changed + if (this.currentDate.getDate() != date.getDate() || + this.currentDate.getMonth() != date.getMonth() || + this.currentDate.getFullYear() != date.getFullYear()) + { + + this.currentDate = new Date(date); + + this.updateHeader(); + this.update(); + } + + }, + + setSelectedDate : function(date) + { + this.selectedDate = new Date(date); + this.setCurrentDate(this.selectedDate); + if (isFunction(this.onchange)) + this.onchange(); + }, + + getElement : function() + { + return this._calDiv; + }, + + getSelectedDate : function () + { + return isNull(this.selectedDate) ? null : new Date(this.selectedDate); + }, + + setYear : function(year) + { + var d = new Date(this.currentDate); + d.setFullYear(year); + this.setCurrentDate(d); + }, + + setMonth : function (month) + { + var d = new Date(this.currentDate); + d.setMonth(month); + this.setCurrentDate(d); + }, + + nextMonth : function () + { + this.setMonth(this.currentDate.getMonth()+1); + }, + + prevMonth : function () + { + this.setMonth(this.currentDate.getMonth()-1); + }, + + show : function() + { + if(!this.showing) + { + var pos = Position.cumulativeOffset(this.control); + pos[1] += this.control.offsetHeight; + this._calDiv.style.display = "block"; + this._calDiv.style.top = pos[1] + "px"; + this._calDiv.style.left = pos[0] + "px"; + Event.observe(document.body, "click", this.hideOnClick.bind(this)); + var date = Prado.Calendar.Util.ParseDate(Form.Element.getValue(this.control), this.format); + if(!isNull(date)) + { + this.selectedDate = date; + this.setCurrentDate(date); + } + this.showing = true; + } + }, + + //hide the calendar when clicked outside any calendar + hideOnClick : function(ev) + { + if(!this.showing) return; + var el = Event.element(ev); + var within = false; + do + { + within = within || el.className == this.className; + within = within || el == this.trigger; + within = within || el == this.control; + if(within) break; + el = el.parentNode; + } + while(el); + if(!within) this.hide(); + }, + + hide : function() + { + if(this.showing) + { + this._calDiv.style.display = "none"; + this.showing = false; + Event.stopObserving(document.body, "click", this.hideOnClick.bind(this)); + } + }, + + update : function() + { + var Util = Prado.Calendar.Util; + + // Calculate the number of days in the month for the selected date + var date = this.currentDate; + var today = Util.ISODate(new Date()); + + var selected = isNull(this.selectedDate) ? "" : Util.ISODate(this.selectedDate); + var current = Util.ISODate(date); + var d1 = new Date(date.getFullYear(), date.getMonth(), 1); + var d2 = new Date(date.getFullYear(), date.getMonth()+1, 1); + var monthLength = Math.round((d2 - d1) / (24 * 60 * 60 * 1000)); + + // Find out the weekDay index for the first of this month + var firstIndex = (d1.getDay() - this.firstDayOfWeek) % 7 ; + if (firstIndex < 0) + firstIndex += 7; + + var index = 0; + while (index < firstIndex) { + this.dateSlot[index].value = -1; + this.dateSlot[index].data.data = String.fromCharCode(160); + this.dateSlot[index].data.parentNode.className = "empty"; + index++; + } + + for (i = 1; i <= monthLength; i++, index++) { + var slot = this.dateSlot[index]; + var slotNode = slot.data.parentNode; + slot.value = i; + slot.data.data = i; + slotNode.className = "date"; + if (Util.ISODate(d1) == today) { + slotNode.className += " today"; + } + if (Util.ISODate(d1) == current) { + slotNode.className += " current"; + } + if (Util.ISODate(d1) == selected) { + slotNode.className += " selected"; + } + d1 = new Date(d1.getFullYear(), d1.getMonth(), d1.getDate()+1); + } + + var lastDateIndex = index; + + while(index < 42) { + this.dateSlot[index].value = -1; + this.dateSlot[index].data.data = String.fromCharCode(160); + this.dateSlot[index].data.parentNode.className = "empty"; + ++index; + } + + }, + + hover : function(ev) + { + //conditionally add the hover class to the event target element. + Element.condClassName(Event.element(ev), "hover", ev.type=="mouseover"); + }, + + updateHeader : function () { + + var options = this._monthSelect.options; + var m = this.currentDate.getMonth(); + for(var i=0; i < options.length; ++i) { + options[i].selected = false; + if (options[i].value == m) { + options[i].selected = true; + } + } + + options = this._yearSelect.options; + var year = this.currentDate.getFullYear(); + for(var i=0; i < options.length; ++i) { + options[i].selected = false; + if (options[i].value == year) { + options[i].selected = true; + } + } + + } + + +}; \ No newline at end of file diff --git a/framework/Web/Javascripts/base/focus.js b/framework/Web/Javascripts/base/focus.js new file mode 100644 index 00000000..6c1359cd --- /dev/null +++ b/framework/Web/Javascripts/base/focus.js @@ -0,0 +1,99 @@ +Prado.Focus = Class.create(); + +Prado.Focus.setFocus = function(id) +{ + var target = document.getElementById ? document.getElementById(id) : document.all[id]; + if(target && !Prado.Focus.canFocusOn(target)) + { + target = Prado.Focus.findTarget(target); + } + if(target) + { + try + { + target.focus(); + target.scrollIntoView(false); + if (window.__smartNav) + { + window.__smartNav.ae = target.id; + } + } + catch (e) + { + } + } +} + +Prado.Focus.canFocusOn = function(element) +{ + if(!element || !(element.tagName)) + return false; + var tagName = element.tagName.toLowerCase(); + return !element.disabled && (!element.type || element.type.toLowerCase() != "hidden") && Prado.Focus.isFocusableTag(tagName) && Prado.Focus.isVisible(element); +} + +Prado.Focus.isFocusableTag = function(tagName) +{ + return (tagName == "input" || tagName == "textarea" || tagName == "select" || tagName == "button" || tagName == "a"); +} + + +Prado.Focus.findTarget = function(element) +{ + if(!element || !(element.tagName)) + { + return null; + } + var tagName = element.tagName.toLowerCase(); + if (tagName == "undefined") + { + return null; + } + var children = element.childNodes; + if (children) + { + for(var i=0;i= ' ') { + if (c == '\\' || c == '"') { + s += '\\'; + } + s += c; + } else { + switch (c) { + case '\b': + s += '\\b'; + break; + case '\f': + s += '\\f'; + break; + case '\n': + s += '\\n'; + break; + case '\r': + s += '\\r'; + break; + case '\t': + s += '\\t'; + break; + default: + c = c.charCodeAt(); + s += '\\u00' + Math.floor(c / 16).toString(16) + + (c % 16).toString(16); + } + } + } + return s + '"'; + case 'boolean': + return String(arg); + default: + return 'null'; + } + }, + parse: function (text) { + var at = 0; + var ch = ' '; + + function error(m) { + throw { + name: 'JSONError', + message: m, + at: at - 1, + text: text + }; + } + + function next() { + ch = text.charAt(at); + at += 1; + return ch; + } + + function white() { + while (ch) { + if (ch <= ' ') { + next(); + } else if (ch == '/') { + switch (next()) { + case '/': + while (next() && ch != '\n' && ch != '\r') {} + break; + case '*': + next(); + for (;;) { + if (ch) { + if (ch == '*') { + if (next() == '/') { + next(); + break; + } + } else { + next(); + } + } else { + error("Unterminated comment"); + } + } + break; + default: + error("Syntax error"); + } + } else { + break; + } + } + } + + function string() { + var i, s = '', t, u; + + if (ch == '"') { +outer: while (next()) { + if (ch == '"') { + next(); + return s; + } else if (ch == '\\') { + switch (next()) { + case 'b': + s += '\b'; + break; + case 'f': + s += '\f'; + break; + case 'n': + s += '\n'; + break; + case 'r': + s += '\r'; + break; + case 't': + s += '\t'; + break; + case 'u': + u = 0; + for (i = 0; i < 4; i += 1) { + t = parseInt(next(), 16); + if (!isFinite(t)) { + break outer; + } + u = u * 16 + t; + } + s += String.fromCharCode(u); + break; + default: + s += ch; + } + } else { + s += ch; + } + } + } + error("Bad string"); + } + + function array() { + var a = []; + + if (ch == '[') { + next(); + white(); + if (ch == ']') { + next(); + return a; + } + while (ch) { + a.push(value()); + white(); + if (ch == ']') { + next(); + return a; + } else if (ch != ',') { + break; + } + next(); + white(); + } + } + error("Bad array"); + } + + function object() { + var k, o = {}; + + if (ch == '{') { + next(); + white(); + if (ch == '}') { + next(); + return o; + } + while (ch) { + k = string(); + white(); + if (ch != ':') { + break; + } + next(); + o[k] = value(); + white(); + if (ch == '}') { + next(); + return o; + } else if (ch != ',') { + break; + } + next(); + white(); + } + } + error("Bad object"); + } + + function number() { + var n = '', v; + if (ch == '-') { + n = '-'; + next(); + } + while (ch >= '0' && ch <= '9') { + n += ch; + next(); + } + if (ch == '.') { + n += '.'; + while (next() && ch >= '0' && ch <= '9') { + n += ch; + } + } + if (ch == 'e' || ch == 'E') { + n += 'e'; + next(); + if (ch == '-' || ch == '+') { + n += ch; + next(); + } + while (ch >= '0' && ch <= '9') { + n += ch; + next(); + } + } + v = +n; + if (!isFinite(v)) { + ////error("Bad number"); + } else { + return v; + } + } + + function word() { + switch (ch) { + case 't': + if (next() == 'r' && next() == 'u' && next() == 'e') { + next(); + return true; + } + break; + case 'f': + if (next() == 'a' && next() == 'l' && next() == 's' && + next() == 'e') { + next(); + return false; + } + break; + case 'n': + if (next() == 'u' && next() == 'l' && next() == 'l') { + next(); + return null; + } + break; + } + error("Syntax error"); + } + + function value() { + white(); + switch (ch) { + case '{': + return object(); + case '[': + return array(); + case '"': + return string(); + case '-': + return number(); + default: + return ch >= '0' && ch <= '9' ? number() : word(); + } + } + + return value(); + } +}; \ No newline at end of file diff --git a/framework/Web/Javascripts/base/postback.js b/framework/Web/Javascripts/base/postback.js new file mode 100644 index 00000000..8b2d522e --- /dev/null +++ b/framework/Web/Javascripts/base/postback.js @@ -0,0 +1,72 @@ + +Prado.PostBack = Class.create(); + +Prado.PostBack.Options = Class.create(); + +Prado.PostBack.Options.prototype = +{ + initialize : function(performValidation, validationGroup, actionUrl, trackFocus, clientSubmit) + { + this.performValidation = performValidation; + this.validationGroup = validationGroup; + this.actionUrl = actionUrl; + this.trackFocus = trackFocus; + this.clientSubmit = clientSubmit; + } +} + +Prado.PostBack.perform = function(formID, eventTarget, eventParameter, options) +{ + var theForm = document.getElementById ? document.getElementById(formID) : document.forms[formID]; + var canSubmit = true; + if ((typeof(options) != 'undefined') || options == null) + { + if (options.performValidation) + { + canSubmit = Prado.Validation.validate(options.validationGroup); + } + if (canSubmit) + { + if ((typeof(options.actionUrl) != 'undefined') && (options.actionUrl != null) && (options.actionUrl.length > 0)) + { + theForm.action = options.actionUrl; + } + if (options.trackFocus) + { + var lastFocus = theForm.elements['PRADO_LASTFOCUS']; + if ((typeof(lastFocus) != 'undefined') && (lastFocus != null)) + { + var active = document.activeElement; + if (typeof(active) == 'undefined') + { + lastFocus.value = eventTarget; + } + else + { + if ((active != null) && (typeof(active.id) != 'undefined')) + { + if (active.id.length > 0) + { + lastFocus.value = active.id; + } + else if (typeof(active.name) != 'undefined') + { + lastFocus.value = active.name; + } + } + } + } + } + if (!options.clientSubmit) + { + canSubmit = false; + } + } + } + if (canSubmit && (!theForm.onsubmit || theForm.onsubmit())) + { + theForm.PRADO_POSTBACK_TARGET.value = eventTarget; + theForm.PRADO_POSTBACK_PARAMETER.value = eventParameter; + theForm.submit(); + } +} \ No newline at end of file diff --git a/framework/Web/Javascripts/base/prado.js b/framework/Web/Javascripts/base/prado.js new file mode 100644 index 00000000..81f51e41 --- /dev/null +++ b/framework/Web/Javascripts/base/prado.js @@ -0,0 +1,3 @@ +Prado = Class.create(); + +Prado.version = '3.0a'; diff --git a/framework/Web/Javascripts/base/scroll.js b/framework/Web/Javascripts/base/scroll.js new file mode 100644 index 00000000..e69de29b diff --git a/framework/Web/Javascripts/base/validation.js b/framework/Web/Javascripts/base/validation.js new file mode 100644 index 00000000..4129ad70 --- /dev/null +++ b/framework/Web/Javascripts/base/validation.js @@ -0,0 +1,675 @@ + +/** + * Prado client-side javascript validation class. + */ +Prado.Validation = Class.create(); + +/** + * Utilities for validation. Static class. + */ +Prado.Validation.Util = Class.create(); + +/** + * Convert a string into integer, returns null if not integer. + * @param {string} the string to convert to integer + * @type {integer|null} null if string does not represent an integer. + */ +Prado.Validation.Util.toInteger = function(value) +{ + var exp = /^\s*[-\+]?\d+\s*$/; + if (value.match(exp) == null) + return null; + var num = parseInt(value, 10); + return (isNaN(num) ? null : num); +} + +/** + * Convert a string into a double/float value. Internationalization + * is not supported + * @param {string} the string to convert to double/float + * @param {string} the decimal character + * @return {float|null} null if string does not represent a float value + */ +Prado.Validation.Util.toDouble = function(value, decimalchar) +{ + decimalchar = undef(decimalchar) ? "." : decimalchar; + var exp = new RegExp("^\\s*([-\\+])?(\\d+)?(\\" + decimalchar + "(\\d+))?\\s*$"); + var m = value.match(exp); + if (m == null) + return null; + var cleanInput = m[1] + (m[2].length>0 ? m[2] : "0") + "." + m[4]; + var num = parseFloat(cleanInput); + return (isNaN(num) ? null : num); +} + +/** + * Convert strings that represent a currency value (e.g. a float with grouping + * characters) to float. E.g. "10,000.50" will become "10000.50". The number + * of dicimal digits, grouping and decimal characters can be specified. + * The currency input format is very strict, null will be returned if + * the pattern does not match. + * @param {string} the currency value + * @param {string} the grouping character, default is "," + * @param {int} number of decimal digits + * @param {string} the decimal character, default is "." + * @type {float|null} the currency value as float. + */ +Prado.Validation.Util.toCurrency = function(value, groupchar, digits, decimalchar) +{ + groupchar = undef(groupchar) ? "," : groupchar; + decimalchar = undef(decimalchar) ? "." : decimalchar; + digits = undef(digits) ? 2 : digits; + + var exp = new RegExp("^\\s*([-\\+])?(((\\d+)\\" + groupchar + ")*)(\\d+)" + + ((digits > 0) ? "(\\" + decimalchar + "(\\d{1," + digits + "}))?" : "") + + "\\s*$"); + var m = value.match(exp); + if (m == null) + return null; + var intermed = m[2] + m[5] ; + var cleanInput = m[1] + intermed.replace( + new RegExp("(\\" + groupchar + ")", "g"), "") + + ((digits > 0) ? "." + m[7] : ""); + var num = parseFloat(cleanInput); + return (isNaN(num) ? null : num); +} + +/** + * Get the date from string using the prodivided date format string. + * The format notations are + * # day -- %d or %e + * # month -- %m + * # year -- %y or %Y + * # hour -- %H, %I, %k, or %l + * # minutes -- %M + * # P.M. -- %p or %P + * @param {string} the formatted date string + * @param {string} the date format + * @type {Date} the date represented in the string + */ +Prado.Validation.Util.toDate = function(value, format) +{ + var y = 0; + var m = -1; + var d = 0; + var a = value.split(/\W+/); + var b = format.match(/%./g); + var i = 0, j = 0; + var hr = 0; + var min = 0; + for (i = 0; i < a.length; ++i) { + if (!a[i]) + continue; + switch (b[i]) { + case "%d": + case "%e": + d = parseInt(a[i], 10); + break; + + case "%m": + m = parseInt(a[i], 10) - 1; + break; + + case "%Y": + case "%y": + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + break; + + case "%H": + case "%I": + case "%k": + case "%l": + hr = parseInt(a[i], 10); + break; + + case "%P": + case "%p": + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + break; + + case "%M": + min = parseInt(a[i], 10); + break; + } + } + if (y != 0 && m != -1 && d != 0) + { + var date = new Date(y, m, d, hr, min, 0); + return (isObject(date) + && y == date.getFullYear() + && m == date.getMonth() + && d == date.getDate()) ? date.valueOf() : null; + } + return null; +} + +/** + * Trim the value, if the value is undefined, empty string is return. + * @param {string} string to be trimmed. + * @type {string} trimmed string. + */ +Prado.Validation.Util.trim = function(value) +{ + if(undef(value)) return ""; + return value.replace(/^\s+|\s+$/g, ""); +} + +/** + * A delayed focus on a particular element + * @param {element} element to apply focus() + */ +Prado.Validation.Util.focus = function(element) +{ + var obj = $(element); + if(isObject(obj) && isdef(obj.focus)) + setTimeout(function(){ obj.focus(); }, 100); + return false; +} + +/** + * List of validator instances. + */ +Prado.Validation.validators = []; + +/** + * List of forms. + * @type {int} + */ +Prado.Validation.forms = []; + +/** + * List of summary controls. + */ +Prado.Validation.summaries = []; + +/** + * Validation groups. + */ +Prado.Validation.groups = []; + + +/** + * Second type of grouping. + */ +Prado.Validation.TargetGroups = []; + + +/** + * Current Target group. + */ +Prado.Validation.CurrentTargetGroup = null; + +Prado.Validation.HasTargetGroup = false; + +/** + * Targets that can cause validation. + */ +Prado.Validation.ActiveTarget = null; + + +/** + * Determine if group validation is active. + */ +Prado.Validation.IsGroupValidation = false; + +/** + * Add a form for validation. + * @param {string} form ID + */ +Prado.Validation.AddForm = function(id) +{ + Prado.Validation.forms.push($(id)); +} + +/** + * Add a target that causes validation. Only elements that have been added + * can cause validation. + * @param {string} target id + */ +Prado.Validation.AddTarget = function(id, group) +{ + var target = $(id); + Event.observe(target, "click", function() + { + Prado.Validation.ActiveTarget = target; + Prado.Validation.CurrentTargetGroup = Prado.Validation.TargetGroups[id]; + }); + if(group) + { + Prado.Validation.TargetGroups[id] = group; + Prado.Validation.HasTargetGroup = true; + } +} + +/** + * Associate a list of validators to a particular control element. + * This essentially allows a set of validators to be grouped to a particular button. + * @param {list} group array show have, {group : "id", target : "target button"} + * @param {array} validator ids + */ +Prado.Validation.AddGroup = function(group, validators) +{ + group.active = false; //default active status is false. + group.target = $(group.target); + group.validators = validators; + Prado.Validation.groups.push(group); + + //update the active group when the button is clicked. + Event.observe(group.target, "click", Prado.Validation.UpdateActiveGroup); +} + +/** + * Update the active group, if call manually it will deactivate all groups. + * @param {string} + * @type {int} + */ +Prado.Validation.UpdateActiveGroup = function(ev) +{ + var groups = Prado.Validation.groups; + for (var i = 0; i < groups.length; i++) + { + groups[i].active = (isdef(ev) && groups[i].target == Event.element(ev)); + } + Prado.Validation.IsGroupValidation = isdef(ev); +} + +/** + * Determine if validation is sucessful. Iterate through the list + * of validator instances and call validate(). Only validators that + * for a particular form are evaluated. Other validators will be disabled. + * If performing group validation, only active validators are visible. + * @param {element} the form for the controls to validate. + * @type {boolean} true is all validators are valid, false otherwise. + */ +Prado.Validation.IsValid = function(form) +{ + var valid = true; + var validators = Prado.Validation.validators; + + for(var i = 0; i < validators.length; i++) + { + //prevent validating multiple forms + validators[i].enabled = !validators[i].control || undef(validators[i].control.form) || validators[i].control.form == form; + //when group validation, only validators in the active group are visible. + validators[i].visible = Prado.Validation.IsGroupValidation ? validators[i].inActiveGroup() : true; + + if(Prado.Validation.HasTargetGroup) + { + if(validators[i].group != Prado.Validation.CurrentTargetGroup) + validators[i].enabled = false; + } + + valid &= validators[i].validate(); + } + + //show the summary including the alert box + Prado.Validation.ShowSummary(form); + //reset all the group active status to false + Prado.Validation.UpdateActiveGroup(); + return valid; +} + +/** + * Base validator class. Supply a different validation function + * to obtain a different validator. E.g. to use the RequiredFieldValidator + * new Prado.Validation(Prado.Validation.RequiredFieldValidator, options); + * or to use the CustomValidator, + * new Prado.Validation(Prado.Validation.CustomValidator, options); + */ +Prado.Validation.prototype = +{ + /** + * Initialize the validator. + * @param {function} the function to call to evaluate if + * the validator is valid + * @param {string|element} the control ID or element + * @param {array} the list of attributes for the validator + */ + initialize : function(validator, attr) + { + this.evaluateIsValid = validator; + this.attr = undef(attr) ? [] : attr; + this.message = $(attr.id); + this.control = $(attr.controltovalidate); + this.enabled = isdef(attr.enabled) ? attr.enabled : true; + this.visible = isdef(attr.visible) ? attr.visible : true; + this.group = isdef(attr.validationgroup) ? attr.validationgroup : null; + this.isValid = true; + Prado.Validation.validators.push(this); + if(this.evaluateIsValid) + this.evaluateIsValid.bind(this); + }, + + /** + * Evaluate the validator only when visible and enabled. + * @type {boolean} true if valid, false otherwise. + */ + validate : function() + { + if(this.visible && this.enabled && this.evaluateIsValid) + { + this.isValid = this.evaluateIsValid(); + } + else + { + this.isValid = true; + } + + this.observe(); //watch for changes to the control values + this.update(); //update the validation messages + return this.isValid; + }, + + /** + * Hide or show the error messages for "Dynamic" displays. + */ + update : function() + { + if(this.attr.display == "Dynamic") + this.isValid ? Element.hide(this.message) : Element.show(this.message); + + if(this.message) + this.message.style.visibility = this.isValid ? "hidden" : "visible"; + + //update the control css class name + var className = this.attr.controlcssclass; + if(this.control && isString(className) && className.length>0) + Element.condClassName(this.control, className, !this.isValid); + Prado.Validation.ShowSummary(); + }, + + /** + * Change the validity of the validator, calls update(). + * @param {boolean} change the isValid state of the validator. + */ + setValid : function(valid) + { + this.isValid = valid; + this.update(); + }, + + /** + * Observe changes to the control values, add "onblur" event to the control once. + */ + observe : function() + { + if(undef(this.observing)) + { + if(this.control && this.control.form) + Event.observe(this.control, "blur", this.validate.bind(this)); + this.observing = true; + } + }, + + /** + * Convert the value of the control to a specific data type. + * @param {string} the data type, "Integer", "Double", "Currency" or "Date". + * @param {string} the value to convert, null to get the value from the control. + * @type {mixed|null} the converted data value. + */ + convert : function(dataType, value) + { + if(undef(value)) + value = Form.Element.getValue(this.control); + switch(dataType) + { + case "Integer": + return Prado.Validation.Util.toInteger(value); + case "Double" : + case "Float" : + return Prado.Validation.Util.toDouble(value, this.attr.decimalchar); + case "Currency" : + return Prado.Validation.Util.toCurrency( + value, this.attr.groupchar, this.attr.digits, this.attr.decimalchar); + case "Date": + return Prado.Validation.Util.toDate(value, this.attr.dateformat); + } + return value.toString(); + }, + + /** + * Determine if the current validator is part of a active validation group. + * @type {boolean} true if part of active validation group, false otherwise. + */ + inActiveGroup : function() + { + var groups = Prado.Validation.groups; + for (var i = 0; i < groups.length; i++) + { + if(groups[i].active && groups[i].validators.contains(this.attr.id)) + return true; + } + return false; + } +} + +/** + * Validation summary class. + */ +Prado.Validation.Summary = Class.create(); +Prado.Validation.Summary.prototype = +{ + /** + * Initialize a validation summary. + * @param {array} summary options. + */ + initialize : function(attr) + { + this.attr = attr; + this.div = $(attr.id); + this.visible = false; + this.enabled = false; + this.group = isdef(attr.validationgroup) ? attr.validationgroup : null; + Prado.Validation.summaries.push(this); + }, + + /** + * Show the validation summary. + * @param {boolean} true to allow alert message + */ + show : function(warn) + { + var refresh = warn || this.attr.refresh == "1"; + var messages = this.getMessages(); + if(messages.length <= 0 || !this.visible || !this.enabled) + { + if(refresh) + Element.hide(this.div); + return; + } + + if(Prado.Validation.HasTargetGroup) + { + if(Prado.Validation.CurrentTargetGroup != this.group) + { + if(refresh) + Element.hide(this.div); + return; + } + } + + if(this.attr.showsummary != "False" && refresh) + { + //Element.show(this.div); + this.div.style.display = "block"; + while(this.div.childNodes.length > 0) + this.div.removeChild(this.div.lastChild); + new Insertion.Bottom(this.div, this.formatSummary(messages)); + } + + if(warn) + window.scrollTo(this.div.offsetLeft-20, this.div.offsetTop-20); + + var summary = this; + if(warn && this.attr.showmessagebox == "True" && refresh) + setTimeout(function(){alert(summary.formatMessageBox(messages));},20); + }, + + /** + * Get a list of error messages from the validators. + * @type {array} list of messages + */ + getMessages : function() + { + var validators = Prado.Validation.validators; + var messages = []; + for(var i = 0; i < validators.length; i++) + { + if(validators[i].isValid == false + && isString(validators[i].attr.errormessage) + && validators[i].attr.errormessage.length > 0) + { + + messages.push(validators[i].attr.errormessage); + } + } + return messages; + }, + + /** + * Return the format parameters for the summary. + * @param {string} format type, "List", "SingleParagraph" or "BulletList" + * @type {array} formatting parameters + */ + formats : function(type) + { + switch(type) + { + case "List": + return { header : "
", first : "", pre : "", post : "
", last : ""}; + case "SingleParagraph": + return { header : " ", first : "", pre : "", post : " ", last : "
"}; + case "BulletList": + default: + return { header : "", first : "
    ", pre : "
  • ", post : "
  • ", last : "
"}; + } + }, + + /** + * Format the message summary. + * @param {array} list of error messages. + * @type {string} formatted message + */ + formatSummary : function(messages) + { + var format = this.formats(this.attr.displaymode); + var output = isdef(this.attr.headertext) ? this.attr.headertext + format.header : ""; + output += format.first; + for(var i = 0; i < messages.length; i++) + output += (messages[i].length>0) ? format.pre + messages[i] + format.post : ""; + output += format.last; + return output; + }, + /** + * Format the message alert box. + * @param {array} a list of error messages. + * @type {string} format message for alert. + */ + formatMessageBox : function(messages) + { + var output = isdef(this.attr.headertext) ? this.attr.headertext + "\n" : ""; + for(var i = 0; i < messages.length; i++) + { + switch(this.attr.displaymode) + { + case "List": + output += messages[i] + "\n"; + break; + case "BulletList": + default: + output += " - " + messages[i] + "\n"; + break; + case "SingleParagraph": + output += messages[i] + " "; + break; + } + } + return output; + }, + + /** + * Determine if this summary belongs to an active group. + * @type {boolean} true if belongs to an active group. + */ + inActiveGroup : function() + { + var groups = Prado.Validation.groups; + for (var i = 0; i < groups.length; i++) + { + if(groups[i].active && groups[i].id == this.attr.group) + return true; + } + return false; + } +} + +/** + * Show the validation error message summary. + * @param {element} the form that activated the summary call. + */ +Prado.Validation.ShowSummary = function(form) +{ + var summary = Prado.Validation.summaries; + for(var i = 0; i < summary.length; i++) + { + if(isdef(form)) + { + if(Prado.Validation.IsGroupValidation) + { + summary[i].visible = summary[i].inActiveGroup(); + } + else + { + summary[i].visible = undef(summary[i].attr.group); + } + + summary[i].enabled = $(summary[i].attr.form) == form; + } + summary[i].show(form); + } +} + + + +/** + * When a form is try to submit, check the validators, submit + * the form only when all validators are valid. + * @param {event} form submit event. + */ +Prado.Validation.OnSubmit = function(ev) +{ + //HTML text editor, tigger save first. + //alert(tinyMCE); + if(typeof tinyMCE != "undefined") + tinyMCE.triggerSave(); + + //no active target? + if(!Prado.Validation.ActiveTarget) return true; + var valid = Prado.Validation.IsValid(Event.element(ev) || ev); + + //not valid? do not submit the form + if(Event.element(ev) && !valid) + Event.stop(ev); + + //reset the target + Prado.Validation.ActiveTarget = null; + //Prado.Validation.CurrentTargetGroup = null; + + return valid; +} + +/** + * During window onload event, attach onsubmit event for each of the + * forms in Prado.Validation.forms. + */ +Prado.Validation.OnLoad = function() +{ + Event.observe(Prado.Validation.forms,"submit", Prado.Validation.OnSubmit); +} + +/** + * Register Prado.Validation.Onload() for window.onload event. + */ +Event.OnLoad(Prado.Validation.OnLoad); \ No newline at end of file diff --git a/framework/Web/Javascripts/base/validators.js b/framework/Web/Javascripts/base/validators.js new file mode 100644 index 00000000..f281c87e --- /dev/null +++ b/framework/Web/Javascripts/base/validators.js @@ -0,0 +1,228 @@ + +Prado.Validation.TRequiredFieldValidator=function(){ + var inputType = this.control.getAttribute("type"); + if(inputType == 'file'){ + return true; + } + else{ + var trim=Prado.Validation.Util.trim; + var a=trim(Form.Element.getValue(this.control)); + var b=trim(this.attr.initialvalue); + return(a!=b); + } +} + + +Prado.Validation.TRegularExpressionValidator = function() +{ + var trim = Prado.Validation.Util.trim; + var value = trim(Form.Element.getValue(this.control)); + if (value == "") return true; + var rx = new RegExp(this.attr.validationexpression); + var matches = rx.exec(value); + return (matches != null && value == matches[0]); +} + +Prado.Validation.TEmailAddressValidator = Prado.Validation.TRegularExpressionValidator; + +Prado.Validation.TCustomValidator = function() +{ + var trim = Prado.Validation.Util.trim; + var value = isNull(this.control) ? '' : trim(Form.Element.getValue(this.control)); + var valid = true; + var func = this.attr.clientvalidationfunction; + if (isString(func) && func != "") + eval("valid = (" + func + "(this, value) != false);"); + return valid; +} + +Prado.Validation.TRangeValidator = function() +{ + var trim = Prado.Validation.Util.trim; + var value = trim(Form.Element.getValue(this.control)); + if (value == "") return true; + + var minval = this.attr.minimumvalue; + var maxval = this.attr.maximumvalue; + + if (undef(minval) && undef(maxval)) + return true; + + if (minval == "") minval = 0; + if (maxval == "") maxval = 0; + + var dataType = this.attr.type; + + if(undef(dataType)) + return (parseFloat(value) >= parseFloat(minval)) && (parseFloat(value) <= parseFloat(maxval)); + + //now do datatype range check. + var min = this.convert(dataType, minval); + var max = this.convert(dataType, maxval); + value = this.convert(dataType, value); + return value >= min && value <= max; +} + +Prado.Validation.TCompareValidator = function() +{ + var trim = Prado.Validation.Util.trim; + var value = trim(Form.Element.getValue(this.control)); + if (value.length == 0) return true; + + var compareTo; + + var comparee = $(this.attr.controlhookup);; + + if(comparee) + compareTo = trim(Form.Element.getValue(comparee)); + else + { + compareTo = isString(this.attr.valuetocompare) ? this.attr.valuetocompare : ""; + } + + var compare = Prado.Validation.TCompareValidator.compare; + + var isValid = compare.bind(this)(value, compareTo); + + //update the comparee control css class name and add onchange event once. + if(comparee) + { + var className = this.attr.controlcssclass; + if(isString(className) && className.length>0) + Element.condClassName(comparee, className, !isValid); + if(undef(this.observingComparee)) + { + Event.observe(comparee, "change", this.validate.bind(this)); + this.observingComparee = true; + } + } + return isValid; +} + +/** + * Compare the two values, also performs data type check. + * @param {string} value to compare with + * @param {string} value to compare + * @type {boolean} true if comparison or type check is valid, false otherwise. + */ +Prado.Validation.TCompareValidator.compare = function(operand1, operand2) +{ + var op1, op2; + if ((op1 = this.convert(this.attr.type, operand1)) == null) + return false; + if (this.attr.operator == "DataTypeCheck") + return true; + if ((op2 = this.convert(this.attr.type, operand2)) == null) + return true; + switch (this.attr.operator) + { + case "NotEqual": + return (op1 != op2); + case "GreaterThan": + return (op1 > op2); + case "GreaterThanEqual": + return (op1 >= op2); + case "LessThan": + return (op1 < op2); + case "LessThanEqual": + return (op1 <= op2); + default: + return (op1 == op2); + } +} + +Prado.Validation.TRequiredListValidator = function() +{ + var min = undef(this.attr.min) ? Number.NEGATIVE_INFINITY : parseInt(this.attr.min); + var max = undef(this.attr.max) ? Number.POSITIVE_INFINITY : parseInt(this.attr.max); + + var elements = document.getElementsByName(this.attr.selector); + + if(elements.length <= 0) + elements = document.getElementsBySelector(this.attr.selector); + + if(elements.length <= 0) + return true; + + var required = new Array(); + if(isString(this.attr.required) && this.attr.required.length > 0) + required = this.attr.required.split(/,\s* /); + + var isValid = true; + + var validator = Prado.Validation.TRequiredListValidator; + + switch(elements[0].type) + { + case 'radio': + case 'checkbox': + isValid = validator.IsValidRadioList(elements, min, max, required); + break; + case 'select-multiple': + isValid = validator.IsValidSelectMultipleList(elements, min, max, required); + break; + } + + var className = this.attr.elementcssclass; + if(isString(className) && className.length>0) + map(elements, function(element){ condClass(element, className, !isValid); }); + if(undef(this.observingRequiredList)) + { + Event.observe(elements, "change", this.validate.bind(this)); + this.observingRequiredList = true; + } + return isValid; +} + +//radio group selection +Prado.Validation.TRequiredListValidator.IsValidRadioList = function(elements, min, max, required) +{ + var checked = 0; + var values = new Array(); + for(var i = 0; i < elements.length; i++) + { + if(elements[i].checked) + { + checked++; + values.push(elements[i].value); + } + } + return Prado.Validation.TRequiredListValidator.IsValidList(checked, values, min, max, required); +} + +//multiple selection check +Prado.Validation.TRequiredListValidator.IsValidSelectMultipleList = function(elements, min, max, required) +{ + var checked = 0; + var values = new Array(); + for(var i = 0; i < elements.length; i++) + { + var selection = elements[i]; + for(var j = 0; j < selection.options.length; j++) + { + if(selection.options[j].selected) + { + checked++; + values.push(selection.options[j].value); + } + } + } + return Prado.Validation.TRequiredListValidator.IsValidList(checked, values, min, max, required); +} + +//check if the list was valid +Prado.Validation.TRequiredListValidator.IsValidList = function(checkes, values, min, max, required) +{ + var exists = true; + + if(required.length > 0) + { + //required and the values must at least be have same lengths + if(values.length < required.length) + return false; + for(var k = 0; k < required.length; k++) + exists = exists && values.contains(required[k]); + } + + return exists && checkes >= min && checkes <= max; +} -- cgit v1.2.3