2 * (c) 2005-2009 Richard Cowin (http://openrico.org)
3 * (c) 2005-2009 Matt Brown (http://dowdybrown.com)
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6 * file except in compliance with the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software distributed under the
11 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12 * either express or implied. See the License for the specific language governing permissions
13 * and limitations under the License.
17 * @namespace Main Rico object
26 try { // fix IE background image flicker (credit: www.mister-pixel.com)
27 document.execCommand("BackgroundImageCache", false, true);
29 if (typeof Rico_CONFIG == 'object') {
30 this.setPaths(Rico_CONFIG.jsDir,Rico_CONFIG.cssDir,Rico_CONFIG.imgDir);
31 if (Rico_CONFIG.enableLogging) this.enableLogging();
34 this.baseHref= location.protocol + "//" + location.host;
36 this.windowIsLoaded=false;
37 this.onLoadCallbacks=[];
38 var onloadAction=Rico.bind(this,'windowLoaded');
39 if (window.addEventListener)
40 window.addEventListener('load', onloadAction, false);
41 else if (window.attachEvent)
42 window.attachEvent('onload', onloadAction);
43 this.onLoad(function() { Rico.log('Pre-load messages:\n'+Rico.preloadMsgs); });
46 setPaths : function(jsDir,cssDir,imgDir,htmDir) {
50 this.htmDir = htmDir || jsDir;
53 // Array entries can reference a javascript file or css stylesheet
54 // A dependency on another module can be indicated with a plus-sign prefix: '+DependsOnModule'
55 moduleDependencies : {
56 DragAndDrop: ['ricoDragDrop.js'],
57 Calendar : ['ricoCalendar.js'],
58 SearchBox : ['ricoSearch.js'],
59 Tree : ['ricoTree.js'],
60 ColorPicker: ['ricoColorPicker.js'],
61 SimpleGrid : ['ricoGridCommon.js', 'ricoSimpleGrid.js'],
62 LiveGridBasic : ['ricoGridCommon.js', 'ricoLiveGrid.js'],
63 LiveGrid : ['+LiveGridBasic', 'ricoLiveGridControls.js'],
64 LiveGridMenu : ['ricoLiveGridMenu.js'],
65 LiveGridAjax : ['+LiveGrid', 'ricoLiveGridAjax.js'],
66 LiveGridJSON : ['+LiveGridAjax', 'ricoLiveGridJSON.js'],
67 LiveGridForms : ['+LiveGridAjax', '+LiveGridMenu', 'ricoLiveGridForms.js']
71 de: "translations/ricoLocale_de.js",
72 en: "translations/ricoLocale_en.js",
73 es: "translations/ricoLocale_es.js",
74 fr: "translations/ricoLocale_fr.js",
75 it: "translations/ricoLocale_it.js",
76 ja: "translations/ricoLocale_ja.js",
77 ko: "translations/ricoLocale_ko.js",
78 pt: "translations/ricoLocale_pt.js",
79 zh: "translations/ricoLocale_zh.js"
82 languageInclude : function(lang2) {
83 var filename=this.languages[lang2];
84 if (filename) this.include(filename);
88 acceptLanguage : function(acceptLang) {
89 var arLang=acceptLang.toLowerCase().split(',');
\r
90 for (var i=0; i<arLang.length; i++) {
\r
91 var lang2=arLang[i].match(/\w\w/);
92 if (!lang2) continue;
\r
93 if (this.languageInclude(lang2)) return true;
\r
99 * Expects one or more module or file names and loads
100 * the appropriate files.
101 * MUST call Rico.setPaths() before calling this method
103 loadModule : function() {
104 for (var a=0, length=arguments.length; a<length; a++) {
105 var name=arguments[a];
106 var dep=this.moduleDependencies[name];
108 for (var i=0; i<dep.length; i++) {
109 if (dep[i].substring(0,1)=='+') {
110 this.loadModule(dep[i].slice(1));
112 this.include(dep[i]);
121 include : function(filename) {
122 if (this.loadedFiles[filename]) return;
123 this.addPreloadMsg('include: '+filename);
124 var ext = filename.substr(filename.lastIndexOf('.')+1);
125 switch (ext.toLowerCase()) {
127 this.loadedFiles[filename]=filename.substring(0,4)=='rico' ? this.loadRequested : this.loadComplete;
128 var el = document.createElement('script');
129 el.type = 'text/javascript';
130 el.src = this.jsDir+filename;
131 document.getElementsByTagName('head')[0].appendChild(el);
134 var el = document.createElement('link');
135 el.type = 'text/css';
136 el.rel = 'stylesheet';
137 el.href = this.cssDir+filename;
138 this.loadedFiles[filename]=this.loadComplete;
139 document.getElementsByTagName('head')[0].appendChild(el);
144 // called after a script file has finished loading
145 includeLoaded: function(filename) {
146 this.loadedFiles[filename]=this.loadComplete;
147 this.checkIfComplete();
150 // called by the document onload event
151 windowLoaded: function() {
152 this.windowIsLoaded=true;
153 this.checkIfComplete();
156 checkIfComplete: function() {
157 var waitingFor=this.windowIsLoaded ? '' : 'window';
158 for(var filename in this.loadedFiles) {
159 if (this.loadedFiles[filename]==this.loadRequested)
160 waitingFor+=' '+filename;
162 //window.status='waitingFor: '+waitingFor;
163 this.addPreloadMsg('waitingFor: '+waitingFor);
164 if (waitingFor.length==0) {
165 this.addPreloadMsg('Processing callbacks');
166 while (this.onLoadCallbacks.length > 0) {
167 var callback=this.onLoadCallbacks.shift();
168 if (callback) callback();
173 onLoad: function(callback,frontOfQ) {
175 this.onLoadCallbacks.unshift(callback);
177 this.onLoadCallbacks.push(callback);
178 this.checkIfComplete();
181 isKonqueror : navigator.userAgent.toLowerCase().indexOf("konqueror") > -1,
182 isIE: !!(window.attachEvent && navigator.userAgent.indexOf('Opera') === -1),
183 isOpera: navigator.userAgent.indexOf('Opera') > -1,
184 isWebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
185 isGecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') === -1,
186 ieVersion: /MSIE (\d+\.\d+);/.test(navigator.userAgent) ? new Number(RegExp.$1) : null,
190 startTime : new Date(),
192 timeStamp: function() {
193 var stamp = new Date();
194 return (stamp.getTime()-this.startTime.getTime())+": ";
197 setDebugArea: function(id, forceit) {
198 if (!this.debugArea || forceit) {
199 var newarea=document.getElementById(id);
200 if (!newarea) return;
201 this.debugArea=newarea;
206 addPreloadMsg: function(msg) {
207 this.preloadMsgs+=this.timeStamp()+msg+"\n";
212 enableLogging: function() {
213 if (this.debugArea) {
214 this.log = function(msg, resetFlag) {
215 if (resetFlag) this.debugArea.value='';
216 this.debugArea.value+=this.timeStamp()+msg+"\n";
218 } else if (window.console) {
219 if (window.console.firebug)
220 this.log = function(msg) { window.console.log(this.timeStamp(),msg); };
222 this.log = function(msg) { window.console.log(this.timeStamp()+msg); };
\r
223 } else if (window.opera) {
224 this.log = function(msg) { window.opera.postError(this.timeStamp()+msg); };
229 return typeof e == 'string' ? document.getElementById(e) : e;
233 var binderArgs = Array.prototype.slice.call(arguments);
234 var object = binderArgs.shift();
235 var method = binderArgs.shift();
237 var callerArgs = Array.prototype.slice.call(arguments);
238 return object[method].apply(object,binderArgs.concat(callerArgs));
242 runLater: function() {
243 var args = Array.prototype.slice.call(arguments);
244 var msec = args.shift();
245 var object = args.shift();
246 var method = args.shift();
247 return setTimeout(function() { object[method].apply(object,args); },msec);
250 visible: function(element) {
251 return Rico.getStyle(element,"display") != 'none';
254 show: function(element) {
255 element.style.display = '';
258 hide: function(element) {
259 element.style.display = 'none';
262 toggle: function(element) {
263 element.style.display = element.style.display == 'none' ? '' : 'none';
266 viewportOffset: function(element) {
267 var offset=Rico.cumulativeOffset(element);
268 offset.left -= this.docScrollLeft();
269 offset.top -= this.docScrollTop();
274 * Return text within an html element
276 * @param xImg true to exclude img tag info
277 * @param xForm true to exclude input, select, and textarea tags
278 * @param xClass exclude elements with a class name of xClass
280 getInnerText: function(el,xImg,xForm,xClass) {
282 case 'string': return el;
283 case 'undefined': return el;
284 case 'number': return el.toString();
286 var cs = el.childNodes;
289 for (var i = 0; i < l; i++) {
290 switch (cs[i].nodeType) {
291 case 1: //ELEMENT_NODE
292 if (this.getStyle(cs[i],'display')=='none') continue;
293 if (xClass && this.hasClass(cs[i],xClass)) continue;
294 switch (cs[i].tagName.toLowerCase()) {
295 case 'img': if (!xImg) str += cs[i].alt || cs[i].title || cs[i].src; break;
296 case 'input': if (!xForm && !cs[i].disabled && cs[i].type.toLowerCase()=='text') str += cs[i].value; break;
297 case 'select': if (!xForm && cs[i].selectedIndex>=0) str += cs[i].options[cs[i].selectedIndex].text; break;
298 case 'textarea': if (!xForm && !cs[i].disabled) str += cs[i].value; break;
299 default: str += this.getInnerText(cs[i],xImg,xForm,xClass); break;
303 str += cs[i].nodeValue;
311 * Return value of a node in an XML response.
312 * For Konqueror 3.5, isEncoded must be true.
314 getContentAsString: function( parentNode, isEncoded ) {
315 if (isEncoded) return this._getEncodedContent(parentNode);
316 if (typeof parentNode.xml != 'undefined') return this._getContentAsStringIE(parentNode);
317 return this._getContentAsStringMozilla(parentNode);
320 _getEncodedContent: function(parentNode) {
321 if (parentNode.innerHTML) return parentNode.innerHTML;
322 switch (parentNode.childNodes.length) {
324 case 1: return parentNode.firstChild.nodeValue;
325 default: return parentNode.childNodes[1].nodeValue;
329 _getContentAsStringIE: function(parentNode) {
331 for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
332 var n = parentNode.childNodes[i];
333 contentStr += (n.nodeType == 4) ? n.nodeValue : n.xml;
338 _getContentAsStringMozilla: function(parentNode) {
339 var xmlSerializer = new XMLSerializer();
341 for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
342 var n = parentNode.childNodes[i];
343 if (n.nodeType == 4) { // CDATA node
344 contentStr += n.nodeValue;
347 contentStr += xmlSerializer.serializeToString(n);
354 * @param n a number (or a string to be converted using parseInt)
355 * @returns the integer value of n, or 0 if n is not a number
357 nan2zero: function(n) {
358 if (typeof(n)=='string') n=parseInt(n,10);
359 return isNaN(n) || typeof(n)=='undefined' ? 0 : n;
362 stripTags: function(s) {
363 return s.replace(/<\/?[^>]+>/gi, '');
366 truncate: function(s,length) {
367 return s.length > length ? s.substr(0, length - 3) + '...' : s;
370 zFill: function(n,slen, radix) {
371 var s=n.toString(radix || 10);
372 while (s.length<slen) s='0'+s;
376 keys: function(obj) {
384 * @param e event object
385 * @returns the key code stored in the event
387 eventKey: function(e) {
388 if( typeof( e.keyCode ) == 'number' ) {
389 return e.keyCode; //DOM
390 } else if( typeof( e.which ) == 'number' ) {
391 return e.which; //NS 4 compatible
392 } else if( typeof( e.charCode ) == 'number' ) {
393 return e.charCode; //also NS 6+, Mozilla 0.9+
395 return -1; //total failure, we have no way of obtaining the key code
398 eventLeftClick: function(e) {
399 return (((e.which) && (e.which == 1)) ||
400 ((e.button) && (e.button == 1)));
403 eventRelatedTarget: function(e) {
404 return e.relatedTarget;
408 * Return the previous sibling that has the specified tagName
410 getPreviosSiblingByTagName: function(el,tagName) {
411 var sib=el.previousSibling;
413 if ((sib.tagName==tagName) && (sib.style.display!='none')) return sib;
414 sib=sib.previousSibling;
420 * Return the parent of el that has the specified tagName.
422 * @param tagName tag to search for
423 * @param className optional
425 getParentByTagName: function(el,tagName,className) {
427 tagName=tagName.toLowerCase();
429 if (par.tagName && par.tagName.toLowerCase()==tagName) {
430 if (!className || par.className.indexOf(className)>=0) return par;
438 * Wrap the children of a DOM element in a new element
439 * @param el the element whose children are to be wrapped
440 * @param cls class name of the wrapper (optional)
441 * @param id id of the wrapper (optional)
442 * @param wrapperTag type of wrapper element to be created (optional, defaults to DIV)
443 * @returns new wrapper element
445 wrapChildren: function(el,cls,id,wrapperTag) {
446 var wrapper = document.createElement(wrapperTag || 'div');
447 if (id) wrapper.id=id;
448 if (cls) wrapper.className=cls;
449 while (el.firstChild) {
450 wrapper.appendChild(el.firstChild);
452 el.appendChild(wrapper);
457 * Positions ctl over icon
458 * @param ctl (div with position:absolute)
459 * @param icon element (img, button, etc) that ctl should be displayed next to
461 positionCtlOverIcon: function(ctl,icon) {
463 var offsets=this.cumulativeOffset(icon);
464 var scrTop=this.docScrollTop();
465 var winHt=this.windowHeight();
466 if (ctl.style.display=='none') ctl.style.display='block';
467 //var correction=this.isIE ? 1 : 2; // based on a 1px border
468 var correction=2; // based on a 1px border
469 var lpad=this.nan2zero(this.getStyle(icon,'paddingLeft'));
470 ctl.style.left = (offsets.left+lpad+correction)+'px';
471 var newTop=offsets.top + correction;// + scrTop;
472 var ctlht=ctl.offsetHeight;
473 var iconht=icon.offsetHeight;
474 var margin=10; // account for shadow
475 if (newTop+iconht+ctlht+margin < winHt+scrTop) {
476 newTop+=iconht; // display below icon
478 newTop=Math.max(newTop-ctlht,scrTop); // display above icon
480 ctl.style.top = newTop+'px';
484 * Creates a form element
485 * @param parent new element will be appended to this node
486 * @param elemTag element to be created (input, button, select, textarea, ...)
487 * @param elemType for input tag this specifies the type (checkbox, radio, text, ...)
488 * @param id id for new element
489 * @param name name for new element, if not specified then name will be the same as the id
490 * @returns new element
492 createFormField: function(parent,elemTag,elemType,id,name) {
494 if (typeof name!='string') name=id;
495 if (this.isIE && this.ieVersion < 8) {
496 // IE cannot set NAME attribute on dynamically created elements
497 var s=elemTag+' id="'+id+'"';
499 s+=' type="'+elemType+'"';
501 if (elemTag.match(/^(form|input|select|textarea|object|button|img)$/)) {
502 s+=' name="'+name+'"';
504 field=document.createElement('<'+s+' />');
506 field=document.createElement(elemTag);
511 if (typeof field.name=='string') {
515 parent.appendChild(field);
520 * Adds a new option to the end of a select list
521 * @returns new option element
523 addSelectOption: function(elem,value,text) {
524 var opt=document.createElement('option');
525 if (typeof value=='string') opt.value=value;
536 * @returns the value of the specified cookie (or null if it doesn't exist)
538 getCookie: function(itemName) {
539 var arg = itemName+'=';
540 var alen = arg.length;
541 var clen = document.cookie.length;
545 if (document.cookie.substring(i, j) == arg) {
546 var endstr = document.cookie.indexOf (';', j);
548 endstr=document.cookie.length;
550 return unescape(document.cookie.substring(j, endstr));
552 i = document.cookie.indexOf(' ', i) + 1;
558 getTBody: function(tab) {
559 return tab.tBodies.length==0 ? tab.appendChild(document.createElement("tbody")) : tab.tBodies[0];
563 * Write information to a cookie.
564 * For cookies to be retained for the current session only, set daysToKeep=null.
565 * To erase a cookie, pass a negative daysToKeep value.
566 * @see <a href="http://www.quirksmode.org/js/cookies.html">Quirksmode article</a> for more information about cookies.
568 setCookie: function(itemName,itemValue,daysToKeep,cookiePath,cookieDomain) {
569 var c = itemName+"="+escape(itemValue);
570 if (typeof(daysToKeep)=='number') {
571 var date = new Date();
572 date.setTime(date.getTime()+(daysToKeep*24*60*60*1000));
573 c+="; expires="+date.toGMTString();
575 if (typeof(cookiePath)=='string') {
576 c+="; path="+cookiePath;
578 if (typeof(cookieDomain)=='string') {
579 c+="; domain="+cookieDomain;
585 /** thousands separator for number formatting */
587 /** decimal point for number formatting */
589 /** target language (2 character code) */
592 dateFmt : "mm/dd/yyyy",
594 timeFmt : "hh:nn:ss a/pm",
595 /** month name array (Jan is at index 0) */
596 monthNames: ['January','February','March','April','May','June',
597 'July','August','September','October','November','December'],
598 /** day of week array (Sunday is at index 0) */
599 dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
602 * @param monthIdx 0-11
603 * @returns month abbreviation
605 monthAbbr: function(monthIdx) {
606 return this.monthNamesShort ? this.monthNamesShort[monthIdx] : this.monthNames[monthIdx].substr(0,3);
610 * @param dayIdx 0-6 (Sunday=0)
611 * @returns day of week abbreviation
613 dayAbbr: function(dayIdx) {
614 return this.dayNamesShort ? this.dayNamesShort[dayIdx] : this.dayNames[dayIdx].substr(0,3);
617 addPhraseId: function(phraseId, phrase) {
618 this.phrasesById[phraseId]=phrase;
621 getPhraseById: function(phraseId) {
622 var phrase=this.phrasesById[phraseId];
624 alert('Error: missing phrase for '+phraseId);
627 if (arguments.length <= 1) return phrase;
629 return phrase.replace(/(\$\d)/g,
631 var idx=parseInt($1.charAt(1),10);
632 return (idx < a.length) ? a[idx] : '';
638 * Format a positive number (integer or float)
639 * @param posnum number to format
640 * @param decPlaces the number of digits to display after the decimal point
641 * @param thouSep the character to use as the thousands separator
642 * @param decPoint the character to use as the decimal point
643 * @returns formatted string
645 formatPosNumber: function(posnum,decPlaces,thouSep,decPoint) {
646 var a=posnum.toFixed(decPlaces).split(/\./);
648 var rgx = /(\d+)(\d{3})/;
649 while (rgx.test(a[0])) {
650 a[0]=a[0].replace(rgx, '$1'+thouSep+'$2');
653 return a.join(decPoint);
657 * Format a number according to the specs in fmt object.
658 * @returns string, wrapped in a span element with a class of: negNumber, zeroNumber, posNumber
659 * These classes can be set in CSS to display negative numbers in red, for example.
661 * @param n number to be formatted
662 * @param fmt may contain any of the following:<dl>
663 * <dt>multiplier </dt><dd> the original number is multiplied by this amount before formatting</dd>
664 * <dt>decPlaces </dt><dd> number of digits to the right of the decimal point</dd>
665 * <dt>decPoint </dt><dd> character to be used as the decimal point</dd>
666 * <dt>thouSep </dt><dd> character to use as the thousands separator</dd>
667 * <dt>prefix </dt><dd> string added to the beginning of the result (e.g. a currency symbol)</dd>
668 * <dt>suffix </dt><dd> string added to the end of the result (e.g. % symbol)</dd>
669 * <dt>negSign </dt><dd> specifies format for negative numbers: L=leading minus, T=trailing minus, P=parens</dd>
672 formatNumber : function(n,fmt) {
673 if (typeof n=='string') n=parseFloat(n.replace(/,/,'.'),10);
674 if (isNaN(n)) return 'NaN';
675 if (typeof fmt.multiplier=='number') n*=fmt.multiplier;
676 var decPlaces=typeof fmt.decPlaces=='number' ? fmt.decPlaces : 0;
677 var thouSep=typeof fmt.thouSep=='string' ? fmt.thouSep : this.thouSep;
678 var decPoint=typeof fmt.decPoint=='string' ? fmt.decPoint : this.decPoint;
679 var prefix=fmt.prefix || "";
680 var suffix=fmt.suffix || "";
681 var negSign=typeof fmt.negSign=='string' ? fmt.negSign : "L";
682 negSign=negSign.toUpperCase();
685 s=this.formatPosNumber(-n,decPlaces,thouSep,decPoint);
686 if (negSign=="P") s="("+s+")";
688 if (negSign=="L") s="-"+s;
689 if (negSign=="T") s+="-";
692 cls=n==0.0 ? 'zeroNumber' : 'posNumber';
693 s=prefix+this.formatPosNumber(n,decPlaces,thouSep,decPoint);
695 return "<span class='"+cls+"'>"+s+suffix+"</span>";
699 * Converts a date to a string according to specs in fmt
700 * @returns formatted string
701 * @param d date to be formatted
702 * @param fmt string specifying the output format, may be one of the following:<dl>
703 * <dt>locale or localeDateTime</dt>
704 * <dd>use javascript's built-in toLocaleString() function</dd>
705 * <dt>localeDate</dt>
706 * <dd>use javascript's built-in toLocaleDateString() function</dd>
707 * <dt>translate or translateDateTime</dt>
708 * <dd>use the formats specified in the Rico.dateFmt and Rico.timeFmt properties</dd>
709 * <dt>translateDate</dt>
710 * <dd>use the date format specified in the Rico.dateFmt property</dd>
712 * <dd>Any combination of: yyyy, yy, mmmm, mmm, mm, m, dddd, ddd, dd, d, hh, h, HH, H, nn, ss, a/p</dd>
715 formatDate : function(d,fmt) {
716 var datefmt=(typeof fmt=='string') ? fmt : 'translateDate';
719 case 'localeDateTime':
720 return d.toLocaleString();
722 return d.toLocaleDateString();
724 case 'translateDateTime':
725 datefmt=this.dateFmt+' '+this.timeFmt;
727 case 'translateDate':
728 datefmt=this.dateFmt;
731 return datefmt.replace(/(yyyy|yy|mmmm|mmm|mm|dddd|ddd|dd|hh|nn|ss|a\/p)/gi,
735 case 'yyyy': return d.getFullYear();
736 case 'yy': return d.getFullYear().toString().substr(2);
737 case 'mmmm': return Rico.monthNames[d.getMonth()];
738 case 'mmm': return Rico.monthAbbr(d.getMonth());
739 case 'mm': return Rico.zFill(d.getMonth() + 1, 2);
740 case 'm': return (d.getMonth() + 1);
741 case 'dddd': return Rico.dayNames[d.getDay()];
742 case 'ddd': return Rico.dayAbbr(d.getDay());
743 case 'dd': return Rico.zFill(d.getDate(), 2);
744 case 'd': return d.getDate();
745 case 'hh': return Rico.zFill((h = d.getHours() % 12) ? h : 12, 2);
746 case 'h': return ((h = d.getHours() % 12) ? h : 12);
747 case 'HH': return Rico.zFill(d.getHours(), 2);
748 case 'H': return d.getHours();
749 case 'nn': return Rico.zFill(d.getMinutes(), 2);
750 case 'ss': return Rico.zFill(d.getSeconds(), 2);
751 case 'a/p': return d.getHours() < 12 ? 'a' : 'p';
758 * Converts a string in ISO 8601 format to a date object.
759 * @returns date object, or false if string is not a valid date or date-time.
760 * @param string value to be converted
761 * @param offset can be used to bias the conversion and must be in minutes if provided
762 * @see Based on <a href='http://delete.me.uk/2005/03/iso8601.html'>delete.me.uk article</a>
764 setISO8601 : function (string,offset) {
765 if (!string) return false;
766 var d = string.match(/(\d\d\d\d)(?:-?(\d\d)(?:-?(\d\d)(?:[T ](\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|(?:([-+])(\d\d)(?::?(\d\d))?)?)?)?)?)?/);
767 if (!d) return false;
768 if (!offset) offset=0;
769 var date = new Date(d[1], 0, 1);
771 if (d[2]) { date.setMonth(d[2] - 1); }
772 if (d[3]) { date.setDate(d[3]); }
773 if (d[4]) { date.setHours(d[4]); }
774 if (d[5]) { date.setMinutes(d[5]); }
775 if (d[6]) { date.setSeconds(d[6]); }
776 if (d[7]) { date.setMilliseconds(Number("0." + d[7]) * 1000); }
778 if (d[10] && d[11]) {
779 offset = (Number(d[10]) * 60) + Number(d[11]);
781 offset *= ((d[9] == '-') ? 1 : -1);
782 offset -= date.getTimezoneOffset();
784 var time = (Number(date) + (offset * 60 * 1000));
785 date.setTime(Number(time));
790 * Convert date to an ISO 8601 formatted string.
791 * @param date date object to be converted
792 * @param format an integer in the range 1-6 (default is 6):<dl>
794 * <dd>YYYY (eg 1997)</dd>
795 * <dt>2 (year and month)</dt>
796 * <dd>YYYY-MM (eg 1997-07)</dd>
797 * <dt>3 (complete date)</dt>
798 * <dd>YYYY-MM-DD (eg 1997-07-16)</dd>
799 * <dt>4 (complete date plus hours and minutes)</dt>
800 * <dd>YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)</dd>
801 * <dt>5 (complete date plus hours, minutes and seconds)</dt>
802 * <dd>YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)</dd>
803 * <dt>6 (complete date plus hours, minutes, seconds and a decimal
804 * fraction of a second)</dt>
805 * <dd>YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)</dd>
807 * @see Based on: <a href='http://www.codeproject.com/jscript/dateformat.asp'>codeproject.com article</a>
809 toISO8601String : function (date, format, offset) {
810 if (!format) format=6;
814 var d = offset.match(/([-+])([0-9]{2}):([0-9]{2})/);
815 var offsetnum = (Number(d[2]) * 60) + Number(d[3]);
816 offsetnum *= ((d[1] == '-') ? -1 : 1);
817 date = new Date(Number(Number(date) + (offsetnum * 60000)));
820 var zeropad = function (num) { return ((num < 10) ? '0' : '') + num; };
822 var str = date.getUTCFullYear();
823 if (format > 1) { str += "-" + zeropad(date.getUTCMonth() + 1); }
824 if (format > 2) { str += "-" + zeropad(date.getUTCDate()); }
826 str += "T" + zeropad(date.getUTCHours()) +
827 ":" + zeropad(date.getUTCMinutes());
830 var secs = Number(date.getUTCSeconds() + "." +
831 ((date.getUTCMilliseconds() < 100) ? '0' : '') +
832 zeropad(date.getUTCMilliseconds()));
833 str += ":" + zeropad(secs);
834 } else if (format > 4) {
835 str += ":" + zeropad(date.getUTCSeconds());
838 if (format > 3) { str += offset; }
843 * Returns a new XML document object
845 createXmlDocument : function() {
846 if (document.implementation && document.implementation.createDocument) {
847 var doc = document.implementation.createDocument("", "", null);
848 // some older versions of Moz did not support the readyState property
849 // and the onreadystate event so we patch it!
850 if (doc.readyState == null) {
852 doc.addEventListener("load", function () {
854 if (typeof doc.onreadystatechange == "function") {
855 doc.onreadystatechange();
862 if (window.ActiveXObject)
863 return Rico.tryFunctions(
864 function() { return new ActiveXObject('MSXML2.DomDocument'); },
865 function() { return new ActiveXObject('Microsoft.DomDocument');},
866 function() { return new ActiveXObject('MSXML.DomDocument'); },
867 function() { return new ActiveXObject('MSXML3.DomDocument'); }
875 * Update the contents of an HTML element via an AJAX call
877 Rico.ajaxUpdater = function(elem,url,options) {
878 this.updateSend(elem,url,options);
881 Rico.ajaxUpdater.prototype = {
882 updateSend : function(elem,url,options) {
884 this.onComplete=options.onComplete;
885 options.onComplete=Rico.bind(this,'updateComplete');
886 new Rico.ajaxRequest(url,options);
889 updateComplete : function(xhr) {
890 this.element.innerHTML=xhr.responseText;
891 if (this.onComplete) this.onComplete(xhr);
895 Rico.writeDebugMsg=Rico.log; // for backwards compatibility