2 * Copyright 2005 Sabre Airline Solutions
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
5 * file except in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11 * either express or implied. See the License for the specific language governing permissions
12 * and limitations under the License.
15 if (typeof Rico=='undefined') throw("Cannot find the Rico object");
16 if (typeof Prototype=='undefined') throw("Rico requires the Prototype JavaScript framework");
17 Rico.prototypeVersion = parseFloat(Prototype.Version.split(".")[0] + "." + Prototype.Version.split(".")[1]);
18 if (Rico.prototypeVersion < 1.3) throw("Rico requires Prototype JavaScript framework version 1.3 or greater");
24 * Finds all immediate children of e with tagName
25 * @param e DOM node or node id
26 * @param tagName tag name to search for (case-insensative)
27 * @returns array of matching elements
29 getDirectChildrenByTag: function(e, tagName) {
30 tagName=tagName.toLowerCase();
31 return $(e).childElements().inject([],function(result,child) {
32 if (child.tagName && child.tagName.toLowerCase()==tagName) result.push(child);
37 * Returns a new XML document object
39 createXmlDocument : function() {
40 if (document.implementation && document.implementation.createDocument) {
41 var doc = document.implementation.createDocument("", "", null);
42 // some older versions of Moz did not support the readyState property
43 // and the onreadystate event so we patch it!
44 if (doc.readyState == null) {
46 doc.addEventListener("load", function () {
48 if (typeof doc.onreadystatechange == "function") {
49 doc.onreadystatechange();
57 if (window.ActiveXObject)
59 function() { return new ActiveXObject('MSXML2.DomDocument'); },
60 function() { return new ActiveXObject('Microsoft.DomDocument');},
61 function() { return new ActiveXObject('MSXML.DomDocument'); },
62 function() { return new ActiveXObject('MSXML3.DomDocument'); }
69 * Return text within an html element
71 * @param xImg true to exclude img tag info
72 * @param xForm true to exclude input, select, and textarea tags
73 * @param xClass exclude elements with a class name of xClass
75 getInnerText: function(el,xImg,xForm,xClass) {
77 case 'string': return el;
78 case 'undefined': return el;
79 case 'number': return el.toString();
81 var cs = el.childNodes;
84 for (var i = 0; i < l; i++) {
85 switch (cs[i].nodeType) {
86 case 1: //ELEMENT_NODE
87 if (Element.getStyle(cs[i],'display')=='none') continue;
88 if (xClass && Element.hasClassName(cs[i],xClass)) continue;
89 switch (cs[i].tagName.toLowerCase()) {
90 case 'img': if (!xImg) str += cs[i].alt || cs[i].title || cs[i].src; break;
91 case 'input': if (cs[i].type=='hidden') continue;
93 case 'textarea': if (!xForm) str += $F(cs[i]) || ''; break;
94 default: str += this.getInnerText(cs[i],xImg,xForm,xClass); break;
98 str += cs[i].nodeValue;
106 * Return value of a node in an XML response.
107 * For Konqueror 3.5, isEncoded must be true.
109 getContentAsString: function( parentNode, isEncoded ) {
110 if (isEncoded) return this._getEncodedContent(parentNode);
111 if (typeof parentNode.xml != 'undefined') return this._getContentAsStringIE(parentNode);
112 return this._getContentAsStringMozilla(parentNode);
115 _getEncodedContent: function(parentNode) {
116 if (parentNode.innerHTML) return parentNode.innerHTML;
117 switch (parentNode.childNodes.length) {
119 case 1: return parentNode.firstChild.nodeValue;
120 default: return parentNode.childNodes[1].nodeValue;
124 _getContentAsStringIE: function(parentNode) {
126 for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
127 var n = parentNode.childNodes[i];
128 contentStr += (n.nodeType == 4) ? n.nodeValue : n.xml;
133 _getContentAsStringMozilla: function(parentNode) {
134 var xmlSerializer = new XMLSerializer();
136 for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
137 var n = parentNode.childNodes[i];
138 if (n.nodeType == 4) { // CDATA node
139 contentStr += n.nodeValue;
142 contentStr += xmlSerializer.serializeToString(n);
149 * @deprecated Will be removed in Rico 3
151 docElement: function() {
152 return (document.compatMode && document.compatMode.indexOf("CSS")!=-1) ? document.documentElement : document.getElementsByTagName("body")[0];
156 * @returns available height, excluding scrollbar & margin
157 * @deprecated Use Prototype's document.viewport.getHeight instead
159 windowHeight: function() {
160 if (document.viewport) {
161 // use prototype 1.6 function
162 return document.viewport.getHeight();
164 return window.innerHeight? window.innerHeight : this.docElement().clientHeight;
169 * @returns available width, excluding scrollbar & margin
170 * @deprecated Use Prototype's document.viewport.getWidth instead
172 windowWidth: function() {
173 if (document.viewport) {
174 // use prototype 1.6 function
175 return document.viewport.getWidth();
177 return this.docElement().clientWidth;
182 * @deprecated Use Prototype's document.viewport.getScrollOffsets instead
184 docScrollLeft: function() {
185 if ( window.pageXOffset ) {
186 return window.pageXOffset;
187 } else if ( document.documentElement && document.documentElement.scrollLeft ) {
188 return document.documentElement.scrollLeft;
189 } else if ( document.body ) {
190 return document.body.scrollLeft;
197 * @deprecated Use Prototype's document.viewport.getScrollOffsets instead
199 docScrollTop: function() {
200 if ( window.pageYOffset ) {
201 return window.pageYOffset;
202 } else if ( document.documentElement && document.documentElement.scrollTop ) {
203 return document.documentElement.scrollTop;
204 } else if ( document.body ) {
205 return document.body.scrollTop;
212 * @param n a number (or a string to be converted using parseInt)
213 * @returns the integer value of n, or 0 if n is not a number
215 nan2zero: function(n) {
216 if (typeof(n)=='string') n=parseInt(n,10);
217 return isNaN(n) || typeof(n)=='undefined' ? 0 : n;
221 * @param e event object
222 * @returns the key code stored in the event
224 eventKey: function(e) {
225 if( typeof( e.keyCode ) == 'number' ) {
226 return e.keyCode; //DOM
227 } else if( typeof( e.which ) == 'number' ) {
228 return e.which; //NS 4 compatible
229 } else if( typeof( e.charCode ) == 'number' ) {
230 return e.charCode; //also NS 6+, Mozilla 0.9+
232 return -1; //total failure, we have no way of obtaining the key code
236 * Return the previous sibling that has the specified tagName
238 getPreviosSiblingByTagName: function(el,tagName) {
239 var sib=el.previousSibling;
241 if ((sib.tagName==tagName) && (sib.style.display!='none')) return sib;
242 sib=sib.previousSibling;
248 * Return the parent of el that has the specified tagName.
250 * @param tagName tag to search for
251 * @param className optional
253 getParentByTagName: function(el,tagName,className) {
255 tagName=tagName.toLowerCase();
257 if (par.tagName && par.tagName.toLowerCase()==tagName) {
258 if (!className || par.className.indexOf(className)>=0) return par;
266 * Wrap the children of a DOM element in a new element
267 * @param el the element whose children are to be wrapped
268 * @param cls class name of the wrapper (optional)
269 * @param id id of the wrapper (optional)
270 * @param wrapperTag type of wrapper element to be created (optional, defaults to DIV)
271 * @returns new wrapper element
273 wrapChildren: function(el,cls,id,wrapperTag) {
274 var wrapper = document.createElement(wrapperTag || 'div');
275 if (id) wrapper.id=id;
276 if (cls) wrapper.className=cls;
277 while (el.firstChild) {
278 wrapper.appendChild(el.firstChild);
280 el.appendChild(wrapper);
285 * Format a positive number (integer or float)
286 * @param posnum number to format
287 * @param decPlaces the number of digits to display after the decimal point
288 * @param thouSep the character to use as the thousands separator
289 * @param decPoint the character to use as the decimal point
290 * @returns formatted string
292 formatPosNumber: function(posnum,decPlaces,thouSep,decPoint) {
293 var a=posnum.toFixed(decPlaces).split(/\./);
295 var rgx = /(\d+)(\d{3})/;
296 while (rgx.test(a[0])) {
297 a[0]=a[0].replace(rgx, '$1'+thouSep+'$2');
300 return a.join(decPoint);
304 * Post condition - if childNodes[n] is refChild, than childNodes[n+1] is newChild.
305 * @deprecated Use Prototype's Element#insert instead
307 DOMNode_insertAfter: function(newChild,refChild) {
308 var parentx=refChild.parentNode;
309 if (parentx.lastChild==refChild) {
310 return parentx.appendChild(newChild);
312 return parentx.insertBefore(newChild,refChild.nextSibling);
317 * Positions ctl over icon
318 * @param ctl (div with position:absolute)
319 * @param icon element (img, button, etc) that ctl should be displayed next to
321 positionCtlOverIcon: function(ctl,icon) {
322 var offsets=Position.page(icon);
323 var scrTop=this.docScrollTop();
324 var winHt=this.windowHeight();
325 if (ctl.style.display=='none') ctl.style.display='block';
326 var correction=Prototype.Browser.IE ? 1 : 2; // based on a 1px border
327 var lpad=this.nan2zero(Element.getStyle(icon,'padding-left'));
328 ctl.style.left = (offsets[0]+lpad+correction)+'px';
329 var newTop=offsets[1] + correction + scrTop;
330 var ctlht=ctl.offsetHeight;
331 var iconht=icon.offsetHeight;
332 var margin=10; // account for shadow
333 if (newTop+iconht+ctlht+margin < winHt+scrTop) {
334 newTop+=iconht; // display below icon
336 newTop=Math.max(newTop-ctlht,scrTop); // display above icon
338 ctl.style.top = newTop+'px';
342 * Creates a form element
343 * @param parent new element will be appended to this node
344 * @param elemTag element to be created (input, button, select, textarea, ...)
345 * @param elemType for input tag this specifies the type (checkbox, radio, text, ...)
346 * @param id id for new element
347 * @param name name for new element, if not specified then name will be the same as the id
348 * @returns new element
350 createFormField: function(parent,elemTag,elemType,id,name) {
352 if (typeof name!='string') name=id;
353 if (Prototype.Browser.IE) {
354 // IE cannot set NAME attribute on dynamically created elements
355 var s=elemTag+' id="'+id+'"';
357 s+=' type="'+elemType+'"';
359 if (elemTag.match(/^(form|input|select|textarea|object|button|img)$/)) {
360 s+=' name="'+name+'"';
362 field=document.createElement('<'+s+' />');
364 field=document.createElement(elemTag);
369 if (typeof field.name=='string') {
373 parent.appendChild(field);
378 * Adds a new option to the end of a select list
379 * @returns new option element
381 addSelectOption: function(elem,value,text) {
382 var opt=document.createElement('option');
383 if (typeof value=='string') opt.value=value;
385 if (Prototype.Browser.IE) {
394 * @returns the value of the specified cookie (or null if it doesn't exist)
396 getCookie: function(itemName) {
397 var arg = itemName+'=';
398 var alen = arg.length;
399 var clen = document.cookie.length;
403 if (document.cookie.substring(i, j) == arg) {
404 var endstr = document.cookie.indexOf (';', j);
406 endstr=document.cookie.length;
408 return unescape(document.cookie.substring(j, endstr));
410 i = document.cookie.indexOf(' ', i) + 1;
417 * Write information to a cookie.
418 * For cookies to be retained for the current session only, set daysToKeep=null.
419 * To erase a cookie, pass a negative daysToKeep value.
420 * @see <a href="http://www.quirksmode.org/js/cookies.html">Quirksmode article</a> for more information about cookies.
422 setCookie: function(itemName,itemValue,daysToKeep,cookiePath,cookieDomain) {
423 var c = itemName+"="+escape(itemValue);
424 if (typeof(daysToKeep)=='number') {
425 var date = new Date();
426 date.setTime(date.getTime()+(daysToKeep*24*60*60*1000));
427 c+="; expires="+date.toGMTString();
429 if (typeof(cookiePath)=='string') {
430 c+="; path="+cookiePath;
432 if (typeof(cookieDomain)=='string') {
433 c+="; domain="+cookieDomain;
441 if (!RicoTranslate) {
443 /** @namespace Translation helper object. Values are set by loading one of the ricoLocale_xx.js files. */
444 var RicoTranslate = {
447 /** thousands separator for number formatting */
449 /** decimal point for number formatting */
451 /** target language (2 character code) */
453 re : /^(\W*)\b(.*)\b(\W*)$/,
455 dateFmt : "mm/dd/yyyy",
457 timeFmt : "hh:nn:ss a/pm",
458 /** month name array (Jan is at index 0) */
459 monthNames: ['January','February','March','April','May','June',
460 'July','August','September','October','November','December'],
461 /** day of week array (Sunday is at index 0) */
462 dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
465 * @param monthIdx 0-11
466 * @returns 3 character abbreviation
468 monthAbbr: function(monthIdx) {
469 return this.monthNames[monthIdx].substr(0,3);
473 * @param dayIdx 0-6 (Sunday=0)
474 * @returns 3 character day of week abbreviation
476 dayAbbr: function(dayIdx) {
477 return this.dayNames[dayIdx].substr(0,3);
481 * @deprecated Use addPhraseId instead
483 addPhrase: function(fromPhrase, toPhrase) {
484 this.phrases[fromPhrase]=toPhrase;
488 * @deprecated Use getPhraseById instead
489 * @param fromPhrase may contain multiple words/phrases separated by tabs
490 * and each portion will be looked up separately.
491 * Punctuation & spaces at the beginning or
492 * ending of a phrase are ignored.
494 getPhrase: function(fromPhrase) {
495 var words=fromPhrase.split(/\t/);
496 var transWord,translated = '';
497 for (var i=0; i<words.length; i++) {
498 if (this.re.exec(words[i])) {
499 transWord=this.phrases[RegExp.$2];
500 translated += (typeof transWord=='string') ? RegExp.$1+transWord+RegExp.$3 : words[i];
502 translated += words[i];
508 addPhraseId: function(phraseId, phrase) {
509 this.phrasesById[phraseId]=phrase;
512 getPhraseById: function(phraseId) {
513 var phrase=this.phrasesById[phraseId];
515 alert('Error: missing phrase for '+phraseId);
518 if (arguments.length <= 1) return phrase;
520 return phrase.replace(/(\$\d)/g,
522 var idx=parseInt($1.charAt(1),10);
523 return (idx < a.length) ? a[idx] : '';
532 if (!Date.prototype.formatDate) {
534 * Converts a date to a string according to specs in fmt
535 * @returns formatted string
536 * @param fmt string specifying the output format, may be one of the following:<dl>
537 * <dt>locale or localeDateTime</dt>
538 * <dd>use javascript's built-in toLocaleString() function</dd>
539 * <dt>localeDate</dt>
540 * <dd>use javascript's built-in toLocaleDateString() function</dd>
541 * <dt>translate or translateDateTime</dt>
542 * <dd>use the date and time format specified in the RicoTranslate object</dd>
543 * <dt>translateDate</dt>
544 * <dd>use the date format specified in the RicoTranslate object</dd>
546 * <dd>Any combination of: yyyy, yy, mmmm, mmm, mm, m, hh, h, HH, H, nn, ss, a/p</dd>
549 Date.prototype.formatDate = function(fmt) {
551 var datefmt=(typeof fmt=='string') ? fmt : 'translateDate';
554 case 'localeDateTime':
555 return d.toLocaleString();
557 return d.toLocaleDateString();
559 case 'translateDateTime':
560 datefmt=RicoTranslate.dateFmt+' '+RicoTranslate.timeFmt;
562 case 'translateDate':
563 datefmt=RicoTranslate.dateFmt;
566 return datefmt.replace(/(yyyy|yy|mmmm|mmm|mm|dddd|ddd|dd|hh|nn|ss|a\/p)/gi,
570 case 'yyyy': return d.getFullYear();
571 case 'yy': return d.getFullYear().toString().substr(2);
572 case 'mmmm': return RicoTranslate.monthNames[d.getMonth()];
573 case 'mmm': return RicoTranslate.monthAbbr(d.getMonth());
574 case 'mm': return (d.getMonth() + 1).toPaddedString(2);
575 case 'm': return (d.getMonth() + 1);
576 case 'dddd': return RicoTranslate.dayNames[d.getDay()];
577 case 'ddd': return RicoTranslate.dayAbbr(d.getDay());
578 case 'dd': return d.getDate().toPaddedString(2);
579 case 'd': return d.getDate();
580 case 'hh': return ((h = d.getHours() % 12) ? h : 12).toPaddedString(2);
581 case 'h': return ((h = d.getHours() % 12) ? h : 12);
582 case 'HH': return d.getHours().toPaddedString(2);
583 case 'H': return d.getHours();
584 case 'nn': return d.getMinutes().toPaddedString(2);
585 case 'ss': return d.getSeconds().toPaddedString(2);
586 case 'a/p': return d.getHours() < 12 ? 'a' : 'p';
593 if (!Date.prototype.setISO8601) {
595 * Converts a string in ISO 8601 format to a date object.
596 * @returns true if string is a valid date or date-time.
597 * @param string value to be converted
598 * @param offset can be used to bias the conversion and must be in minutes if provided
599 * @see Based on <a href='http://delete.me.uk/2005/03/iso8601.html'>delete.me.uk article</a>
601 Date.prototype.setISO8601 = function (string,offset) {
602 if (!string) return false;
603 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))?)?)?)?)?)?/);
604 if (!d) return false;
605 if (!offset) offset=0;
606 var date = new Date(d[1], 0, 1);
608 if (d[2]) { date.setMonth(d[2] - 1); }
609 if (d[3]) { date.setDate(d[3]); }
610 if (d[4]) { date.setHours(d[4]); }
611 if (d[5]) { date.setMinutes(d[5]); }
612 if (d[6]) { date.setSeconds(d[6]); }
613 if (d[7]) { date.setMilliseconds(Number("0." + d[7]) * 1000); }
615 if (d[10] && d[11]) {
616 offset = (Number(d[10]) * 60) + Number(d[11]);
618 offset *= ((d[9] == '-') ? 1 : -1);
619 offset -= date.getTimezoneOffset();
621 var time = (Number(date) + (offset * 60 * 1000));
622 this.setTime(Number(time));
627 if (!Date.prototype.toISO8601String) {
629 * Convert date to an ISO 8601 formatted string.
630 * @param format an integer in the range 1-6 (default is 6):<dl>
632 * <dd>YYYY (eg 1997)</dd>
633 * <dt>2 (year and month)</dt>
634 * <dd>YYYY-MM (eg 1997-07)</dd>
635 * <dt>3 (complete date)</dt>
636 * <dd>YYYY-MM-DD (eg 1997-07-16)</dd>
637 * <dt>4 (complete date plus hours and minutes)</dt>
638 * <dd>YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)</dd>
639 * <dt>5 (complete date plus hours, minutes and seconds)</dt>
640 * <dd>YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)</dd>
641 * <dt>6 (complete date plus hours, minutes, seconds and a decimal
642 * fraction of a second)</dt>
643 * <dd>YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)</dd>
645 * @see Based on: <a href='http://www.codeproject.com/jscript/dateformat.asp'>codeproject.com article</a>
647 Date.prototype.toISO8601String = function (format, offset) {
648 if (!format) format=6;
654 var d = offset.match(/([-+])([0-9]{2}):([0-9]{2})/);
655 var offsetnum = (Number(d[2]) * 60) + Number(d[3]);
656 offsetnum *= ((d[1] == '-') ? -1 : 1);
657 date = new Date(Number(Number(this) + (offsetnum * 60000)));
660 var zeropad = function (num) { return ((num < 10) ? '0' : '') + num; };
662 var str = date.getUTCFullYear();
663 if (format > 1) { str += "-" + zeropad(date.getUTCMonth() + 1); }
664 if (format > 2) { str += "-" + zeropad(date.getUTCDate()); }
666 str += "T" + zeropad(date.getUTCHours()) +
667 ":" + zeropad(date.getUTCMinutes());
670 var secs = Number(date.getUTCSeconds() + "." +
671 ((date.getUTCMilliseconds() < 100) ? '0' : '') +
672 zeropad(date.getUTCMilliseconds()));
673 str += ":" + zeropad(secs);
674 } else if (format > 4) {
675 str += ":" + zeropad(date.getUTCSeconds());
678 if (format > 3) { str += offset; }
683 if (!String.prototype.toISO8601Date) {
685 * Convert string in ISO 8601 format to a date
686 * @returns new date object
688 String.prototype.toISO8601Date = function() {
690 return d.setISO8601(this) ? d : null;
694 if (!String.prototype.formatDate) {
696 * Format string containing a date
697 * @see Date#formatDate
699 String.prototype.formatDate = function(fmt) {
700 var s=this.replace(/-/g,'/');
702 return isNaN(d) ? this : d.formatDate(fmt);
706 if (!Number.prototype.formatNumber) {
708 * Format a number according to the specs in fmt object.
709 * @returns string, wrapped in a span element with a class of: negNumber, zeroNumber, posNumber
710 * These classes can be set in CSS to display negative numbers in red, for example.
712 * @param fmt may contain any of the following:<dl>
713 * <dt>multiplier </dt><dd> the original number is multiplied by this amount before formatting</dd>
714 * <dt>decPlaces </dt><dd> number of digits to the right of the decimal point</dd>
715 * <dt>decPoint </dt><dd> character to be used as the decimal point</dd>
716 * <dt>thouSep </dt><dd> character to use as the thousands separator</dd>
717 * <dt>prefix </dt><dd> string added to the beginning of the result (e.g. a currency symbol)</dd>
718 * <dt>suffix </dt><dd> string added to the end of the result (e.g. % symbol)</dd>
719 * <dt>negSign </dt><dd> specifies format for negative numbers: L=leading minus, T=trailing minus, P=parens</dd>
722 Number.prototype.formatNumber = function(fmt) {
723 if (isNaN(this)) return 'NaN';
725 if (typeof fmt.multiplier=='number') n*=fmt.multiplier;
726 var decPlaces=typeof fmt.decPlaces=='number' ? fmt.decPlaces : 0;
727 var thouSep=typeof fmt.thouSep=='string' ? fmt.thouSep : RicoTranslate.thouSep;
728 var decPoint=typeof fmt.decPoint=='string' ? fmt.decPoint : RicoTranslate.decPoint;
729 var prefix=fmt.prefix || "";
730 var suffix=fmt.suffix || "";
731 var negSign=typeof fmt.negSign=='string' ? fmt.negSign : "L";
732 negSign=negSign.toUpperCase();
735 s=RicoUtil.formatPosNumber(-n,decPlaces,thouSep,decPoint);
736 if (negSign=="P") s="("+s+")";
738 if (negSign=="L") s="-"+s;
739 if (negSign=="T") s+="-";
742 cls=n==0.0 ? 'zeroNumber' : 'posNumber';
743 s=prefix+RicoUtil.formatPosNumber(n,decPlaces,thouSep,decPoint);
745 return "<span class='"+cls+"'>"+s+suffix+"</span>";
749 if (!String.prototype.formatNumber) {
751 * Take a string that can be converted via parseFloat
752 * and format it according to the specs in fmt object.
753 * Number in string may use a period or comma as the decimal point,
754 * but should not contain any thousands separator.
756 String.prototype.formatNumber = function(fmt) {
757 var n=parseFloat(this.replace(/,/,'.'));
758 return isNaN(n) ? this : n.formatNumber(fmt);
762 Rico.Shim = Class.create();
763 /** @lends Rico.Shim# */
764 if (Prototype.Browser.IE) {
765 Rico.Shim.prototype = {
767 * @class Fixes select control bleed-thru on floating divs in IE. Used by Rico.Popup.
768 * @see Based on <a href='http://www.dotnetjunkies.com/WebLog/jking/archive/2003/07/21/488.aspx'>technique published by Joe King</a>
771 initialize: function(DivRef) {
772 this.ifr = document.createElement('iframe');
773 this.ifr.style.position="absolute";
774 this.ifr.style.display = "none";
775 this.ifr.style.top = '0px';
776 this.ifr.style.left = '0px';
777 this.ifr.src="javascript:false;";
778 DivRef.parentNode.appendChild(this.ifr);
783 this.ifr.style.display = "none";
787 this.ifr.style.top = this.DivRef.style.top;
788 this.ifr.style.left = this.DivRef.style.left;
792 this.ifr.style.width = this.DivRef.offsetWidth;
793 this.ifr.style.height = this.DivRef.offsetHeight;
795 this.ifr.style.zIndex = this.DivRef.currentStyle.zIndex - 1;
796 this.ifr.style.display = "block";
800 Rico.Shim.prototype = {
802 initialize: function() {},
813 Rico.Shadow = Class.create(
814 /** @lends Rico.Shadow# */
817 * @class Creates a shadow for positioned elements. Used by Rico.Popup.
818 * Uses blur filter in IE, and alpha-transparent png images for all other browsers.
819 * @see Based on <a href='http://www.positioniseverything.net/articles/dropshadows.html'>positioniseverything article</a>
822 initialize: function(DivRef) {
823 this.div = document.createElement('div');
824 this.div.style.position="absolute";
825 this.div.style.top='0px';
826 this.div.style.left='0px';
827 if (typeof this.div.style.filter=='undefined') {
828 new Image().src = Rico.imgDir+"shadow.png";
829 new Image().src = Rico.imgDir+"shadow_ur.png";
830 new Image().src = Rico.imgDir+"shadow_ll.png";
834 this.div.style.backgroundColor='#888';
835 this.div.style.filter='progid:DXImageTransform.Microsoft.Blur(makeShadow=1, shadowOpacity=0.3, pixelRadius=3)';
836 this.offset=0; // MS blur filter already does offset
838 this.div.style.display = "none";
839 DivRef.parentNode.appendChild(this.div);
843 createShadow: function() {
844 var tab = document.createElement('table');
845 tab.style.height='100%';
846 tab.style.width='100%';
850 var tr1=tab.insertRow(-1);
851 tr1.style.height='8px';
852 var td11=tr1.insertCell(-1);
853 td11.style.width='8px';
854 var td12=tr1.insertCell(-1);
855 td12.style.background="transparent url("+Rico.imgDir+"shadow_ur.png"+") no-repeat right bottom";
857 var tr2=tab.insertRow(-1);
858 var td21=tr2.insertCell(-1);
859 td21.style.background="transparent url("+Rico.imgDir+"shadow_ll.png"+") no-repeat right bottom";
860 var td22=tr2.insertCell(-1);
861 td22.style.background="transparent url("+Rico.imgDir+"shadow.png"+") no-repeat right bottom";
863 this.div.appendChild(tab);
867 this.div.style.display = "none";
871 this.div.style.top = (parseInt(this.DivRef.style.top || '0',10)+this.offset)+'px';
872 this.div.style.left = (parseInt(this.DivRef.style.left || '0',10)+this.offset)+'px';
876 this.div.style.width = this.DivRef.offsetWidth + 'px';
877 this.div.style.height= this.DivRef.offsetHeight + 'px';
879 this.div.style.zIndex= parseInt(Element.getStyle(this.DivRef,'z-index'),10) - 1;
880 this.div.style.display = "block";
885 Rico.Popup = Class.create(
886 /** @lends Rico.Popup# */
889 * @class Class to manage pop-up div windows.
891 * @param options object may contain any of the following:<dl>
892 * <dt>hideOnEscape</dt><dd> hide popup when escape key is pressed? default=true</dd>
893 * <dt>hideOnClick </dt><dd> hide popup when mouse button is clicked? default=true</dd>
894 * <dt>ignoreClicks</dt><dd> if true, mouse clicks within the popup are not allowed to bubble up to parent elements</dd>
895 * <dt>position </dt><dd> defaults to absolute</dd>
896 * <dt>shadow </dt><dd> display shadow with popup? default=true</dd>
897 * <dt>margin </dt><dd> number of pixels to allow for shadow, default=6</dd>
898 * <dt>zIndex </dt><dd> which layer? default=1</dd>
899 * <dt>overflow </dt><dd> how to handle content that overflows div? default=auto</dd>
900 * <dt>canDragFunc </dt><dd> boolean value (or function that returns a boolean) indicating if it is ok to drag/reposition popup, default=false</dd>
902 * @param DivRef if supplied, then setDiv() is called at the end of initialization
904 initialize: function(options,DivRef,closeFunc) {
908 ignoreClicks : false,
909 position : 'absolute',
916 Object.extend(this.options, options || {});
917 if (DivRef) this.setDiv(DivRef,closeFunc);
921 * Apply popup behavior to a div that already exists in the DOM
922 * @param DivRef div element in the DOM
923 * @param closeFunc optional callback function when popup is closed
925 setDiv: function(DivRef,closeFunc) {
926 this.divPopup=$(DivRef);
927 var position=this.options.position == 'auto' ? Element.getStyle(this.divPopup,'position').toLowerCase() : this.options.position;
928 if (!this.divPopup || position != 'absolute') return;
929 this.closeFunc=closeFunc || this.closePopup.bindAsEventListener(this);
930 this.shim=new Rico.Shim(this.divPopup);
931 if (this.options.shadow)
932 this.shadow=new Rico.Shadow(this.divPopup);
933 if (this.options.hideOnClick)
934 Event.observe(document,"click", this.closeFunc);
935 if (this.options.hideOnEscape)
936 Event.observe(document,"keyup", this._checkKey.bindAsEventListener(this));
937 if (this.options.canDragFunc)
938 Event.observe(this.titleDiv || this.divPopup, "mousedown", this.startDrag.bind(this));
939 if (this.options.ignoreClicks || this.options.canDragFunc) this.ignoreClicks();
943 * create popup div and insert content
945 createPopup: function(parentElem, content, ht, wi, className, closeFunc) {
946 var div = document.createElement('div');
947 div.style.position=this.options.position;
948 div.style.zIndex=this.options.zIndex;
949 div.style.overflow=this.options.overflow;
951 div.style.left='0px';
954 div.className=className || 'ricoPopup';
955 if (content) div.innerHTML=content;
956 parentElem.appendChild(div);
957 this.setDiv(div,closeFunc);
959 if (this.options.canDragFunc===true)
960 this.options.canDragFunc=this.safeDragTest.bind(this);
964 * @private Fixes problems with IE when clicking on the scrollbar
965 * Not required when calling createWindow because dragging is only applied to the title bar
967 safeDragTest: function(elem,event) {
968 return (elem.componentFromPoint && elem.componentFromPoint(event.clientX,event.clientY)!='') ? false : elem==this.divPopup;
972 * Create popup div with a title bar.
973 * height (ht) and width (wi) parameters are required and apply to the content (title adds extra height)
975 createWindow: function(title, content, ht, wi, className) {
976 var div = document.createElement('div');
977 this.titleDiv = document.createElement('div');
978 this.contentDiv = document.createElement('div');
979 this.titleDiv.className='ricoTitle';
980 this.titleDiv.innerHTML=title;
981 this.titleDiv.style.position='relative';
982 var img = document.createElement('img');
983 img.src=Rico.imgDir+"close.gif";
984 img.title=RicoTranslate.getPhraseById('close');
985 img.style.cursor='pointer';
986 img.style.position='absolute';
987 img.style.right='0px';
988 this.titleDiv.appendChild(img);
989 this.contentDiv.className='ricoContent';
990 this.contentDiv.innerHTML=content;
991 this.contentDiv.style.height=ht;
992 this.contentDiv.style.width=wi;
993 this.contentDiv.style.overflow=this.options.overflow;
994 div.style.position=this.options.position;
995 div.style.zIndex=this.options.zIndex;
997 div.style.left='0px';
998 div.style.display='none';
999 div.className=className || 'ricoWindow';
1000 div.appendChild(this.titleDiv);
1001 div.appendChild(this.contentDiv);
1002 document.body.appendChild(div);
1004 Event.observe(img,"click", this.closePopup.bindAsEventListener(this));
1008 ignoreClicks: function() {
1009 Event.observe(this.divPopup,"click", this._ignoreClick.bindAsEventListener(this));
1012 _ignoreClick: function(e) {
1013 if (e.stopPropagation)
1014 e.stopPropagation();
1016 e.cancelBubble = true;
1020 // event handler to process keyup events (hide menu on escape key)
1021 _checkKey: function(e) {
1022 if (RicoUtil.eventKey(e)==27) this.closeFunc();
1027 * Move popup to specified position
1029 move: function(left,top) {
1030 if (typeof left=='number') this.divPopup.style.left=left+'px';
1031 if (typeof top=='number') this.divPopup.style.top=top+'px';
1032 if (this.shim) this.shim.move();
1033 if (this.shadow) this.shadow.move();
1037 startDrag : function(event){
1038 var elem=Event.element(event);
1039 var canDrag=typeof(this.options.canDragFunc)=='function' ? this.options.canDragFunc(elem,event) : this.options.canDragFunc;
1040 if (!canDrag) return;
1041 this.divPopup.style.cursor='move';
1042 this.lastMouseX = event.clientX;
1043 this.lastMouseY = event.clientY;
1044 this.dragHandler = this.drag.bindAsEventListener(this);
1045 this.dropHandler = this.endDrag.bindAsEventListener(this);
1046 Event.observe(document, "mousemove", this.dragHandler);
1047 Event.observe(document, "mouseup", this.dropHandler);
1052 drag : function(event){
1053 var newLeft = parseInt(this.divPopup.style.left,10) + event.clientX - this.lastMouseX;
1054 var newTop = parseInt(this.divPopup.style.top,10) + event.clientY - this.lastMouseY;
1055 this.move(newLeft, newTop);
1056 this.lastMouseX = event.clientX;
1057 this.lastMouseY = event.clientY;
1062 endDrag : function(){
1063 this.divPopup.style.cursor='';
1064 Event.stopObserving(document, "mousemove", this.dragHandler);
1065 Event.stopObserving(document, "mouseup", this.dropHandler);
1066 this.dragHandler=null;
1067 this.dropHandler=null;
1071 * Display popup at specified position
1073 openPopup: function(left,top) {
1074 this.divPopup.style.display="block";
1075 if (typeof left=='number') this.divPopup.style.left=left+'px';
1076 if (typeof top=='number') this.divPopup.style.top=top+'px';
1077 if (this.shim) this.shim.show();
1078 if (this.shadow) this.shadow.show();
1084 closePopup: function() {
1085 if (this.dragHandler) this.endDrag();
1086 if (this.shim) this.shim.hide();
1087 if (this.shadow) this.shadow.hide();
1088 this.divPopup.style.display="none";
1093 Rico.includeLoaded('ricoCommon.js');