OSDN Git Service

git-svn-id: https://svn.sourceforge.jp/svnroot/nucleus-jp/plugin@1020 1ca29b6e-896d...
[nucleus-jp/nucleus-plugins.git] / NP_TrackBack / branches / DOM-branch / trackback / js / rico / ricoCommon.js
1 /**
2   *
3   *  Copyright 2005 Sabre Airline Solutions
4   *
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
7   *
8   *         http://www.apache.org/licenses/LICENSE-2.0
9   *
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.
14   **/
15
16 if (typeof Rico=='undefined')
17   throw("Cannot find the Rico object");
18 if (typeof Prototype=='undefined')
19   throw("Rico requires the Prototype JavaScript framework");
20 Rico.prototypeVersion = parseFloat(Prototype.Version.split(".")[0] + "." + Prototype.Version.split(".")[1]);
21 if (Rico.prototypeVersion < 1.3)
22   throw("Rico requires Prototype JavaScript framework version 1.3 or greater");
23
24 /** @singleton */
25 var RicoUtil = {
26
27    getDirectChildrenByTag: function(e, tagName) {
28       var kids = new Array();
29       var allKids = e.childNodes;
30       tagName=tagName.toLowerCase();
31       for( var i = 0 ; i < allKids.length ; i++ )
32          if ( allKids[i] && allKids[i].tagName && allKids[i].tagName.toLowerCase() == tagName )
33             kids.push(allKids[i]);
34       return kids;
35    },
36
37    createXmlDocument : function() {
38       if (document.implementation && document.implementation.createDocument) {
39          var doc = document.implementation.createDocument("", "", null);
40
41          if (doc.readyState == null) {
42             doc.readyState = 1;
43             doc.addEventListener("load", function () {
44                doc.readyState = 4;
45                if (typeof doc.onreadystatechange == "function")
46                   doc.onreadystatechange();
47             }, false);
48          }
49
50          return doc;
51       }
52
53       if (window.ActiveXObject)
54           return Try.these(
55             function() { return new ActiveXObject('MSXML2.DomDocument')   },
56             function() { return new ActiveXObject('Microsoft.DomDocument')},
57             function() { return new ActiveXObject('MSXML.DomDocument')    },
58             function() { return new ActiveXObject('MSXML3.DomDocument')   }
59           ) || false;
60
61       return null;
62    },
63
64    getInnerText: function(el) {
65      if (typeof el == "string") return el;
66      if (typeof el == "undefined") { return el };
67      var cs = el.childNodes;
68      var l = cs.length;
69      var str = "";
70      for (var i = 0; i < l; i++) {
71        switch (cs[i].nodeType) {
72          case 1: //ELEMENT_NODE
73            if (Element.getStyle(cs[i],'display')=='none') continue;
74            switch (cs[i].tagName.toLowerCase()) {
75              case 'img':   str += cs[i].alt || cs[i].title || cs[i].src; break;
76              case 'input': if (cs[i].type=='hidden') continue;
77              case 'select':
78              case 'textarea': str += $F(cs[i]) || ''; break;
79              default:      str += this.getInnerText(cs[i]); break;
80            }
81            break;
82          case 3: //TEXT_NODE
83            str += cs[i].nodeValue;
84            break;
85        }
86      }
87      return str;
88    },
89
90    // For Konqueror 3.5, isEncoded must be true
91    getContentAsString: function( parentNode, isEncoded ) {
92       if (isEncoded) return this._getEncodedContent(parentNode);
93       if (typeof parentNode.xml != 'undefined') return this._getContentAsStringIE(parentNode);
94       return this._getContentAsStringMozilla(parentNode);
95    },
96
97    _getEncodedContent: function(parentNode) {
98       if (parentNode.innerHTML) return parentNode.innerHTML;
99       switch (parentNode.childNodes.length) {
100         case 0:  return "";
101         case 1:  return parentNode.firstChild.nodeValue;
102         default: return parentNode.childNodes[1].nodeValue;
103       }
104    },
105
106   _getContentAsStringIE: function(parentNode) {
107      var contentStr = "";
108      for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
109          var n = parentNode.childNodes[i];
110          if (n.nodeType == 4) {
111              contentStr += n.nodeValue;
112          }
113          else {
114            contentStr += n.xml;
115        }
116      }
117      return contentStr;
118   },
119
120   _getContentAsStringMozilla: function(parentNode) {
121      var xmlSerializer = new XMLSerializer();
122      var contentStr = "";
123      for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
124           var n = parentNode.childNodes[i];
125           if (n.nodeType == 4) { // CDATA node
126               contentStr += n.nodeValue;
127           }
128           else {
129             contentStr += xmlSerializer.serializeToString(n);
130         }
131      }
132      return contentStr;
133   },
134
135   docElement: function() {
136     return (document.compatMode && document.compatMode.indexOf("CSS")!=-1) ? document.documentElement : document.getElementsByTagName("body")[0];
137   },
138
139 /**
140  * return available height - excludes scrollbar & margin
141  */
142   windowHeight: function() {
143     return window.innerHeight? window.innerHeight : this.docElement().clientHeight;
144     //return this.docElement().clientHeight;
145   },
146
147 /**
148  * return available width - excludes scrollbar & margin
149  */
150   windowWidth: function() {
151     return this.docElement().clientWidth;
152   },
153
154   docScrollLeft: function() {
155      if ( window.pageXOffset )
156         return window.pageXOffset;
157      else if ( document.documentElement && document.documentElement.scrollLeft )
158         return document.documentElement.scrollLeft;
159      else if ( document.body )
160         return document.body.scrollLeft;
161      else
162         return 0;
163   },
164
165   docScrollTop: function() {
166      if ( window.pageYOffset )
167         return window.pageYOffset;
168      else if ( document.documentElement && document.documentElement.scrollTop )
169         return document.documentElement.scrollTop;
170      else if ( document.body )
171         return document.body.scrollTop;
172      else
173         return 0;
174   },
175
176   nan2zero: function(n) {
177     if (typeof(n)=='string') n=parseInt(n);
178     return isNaN(n) || typeof(n)=='undefined' ? 0 : n;
179   },
180
181   eventKey: function(e) {
182     if( typeof( e.keyCode ) == 'number'  ) {
183       return e.keyCode; //DOM
184     } else if( typeof( e.which ) == 'number' ) {
185       return e.which;   //NS 4 compatible
186     } else if( typeof( e.charCode ) == 'number'  ) {
187       return e.charCode; //also NS 6+, Mozilla 0.9+
188     }
189     return -1;  //total failure, we have no way of obtaining the key code
190   },
191
192 /**
193  * Return the previous sibling that has the specified tagName
194  */
195    getPreviosSiblingByTagName: function(el,tagName) {
196         var sib=el.previousSibling;
197         while (sib) {
198                 if ((sib.tagName==tagName) && (sib.style.display!='none')) return sib;
199                 sib=sib.previousSibling;
200         }
201         return null;
202    },
203
204 /**
205  * Return the parent HTML element that has the specified tagName.
206  * @param className optional
207  */
208    getParentByTagName: function(el,tagName,className) {
209         var par=el;
210         tagName=tagName.toLowerCase();
211         while (par) {
212                 if (par.tagName && par.tagName.toLowerCase()==tagName)
213         if (!className || par.className.indexOf(className)>=0) return par;
214                 par=par.parentNode;
215         }
216         return null;
217    },
218
219 /**
220  * Wrap the children of a DOM element in a new element
221  * @param el the element whose children are to be wrapped
222  * @param cls class name of the wrapper (optional)
223  * @param id id of the wrapper (optional)
224  * @param wrapperTag type of wrapper element to be created (optional, defaults to DIV)
225  */
226   wrapChildren: function(el,cls,id,wrapperTag) {
227     var tag=wrapperTag || 'div';
228     var wrapper = document.createElement(tag);
229     if (id) wrapper.id=id;
230     if (cls) wrapper.className=cls;
231     while (el.firstChild)
232       wrapper.appendChild(el.firstChild);
233     el.appendChild(wrapper);
234     return wrapper;
235   },
236
237 /**
238  * Format a positive number
239  * @param decPlaces the number of digits to display after the decimal point
240  * @param thouSep the character to use as the thousands separator
241  * @param decPoint the character to use as the decimal point
242  */
243   formatPosNumber: function(posnum,decPlaces,thouSep,decPoint) {
244     var a=posnum.toFixed(decPlaces).split(/\./);
245     if (thouSep) {
246       var rgx = /(\d+)(\d{3})/;
247       while (rgx.test(a[0]))
248         a[0]=a[0].replace(rgx, '$1'+thouSep+'$2');
249     }
250     return a.join(decPoint);
251   },
252
253 /**
254  * Post condition - if childNodes[n] is refChild, than childNodes[n+1] is newChild.
255  */
256   DOMNode_insertAfter: function(newChild,refChild) {
257     var parentx=refChild.parentNode;
258     if(parentx.lastChild==refChild) { return parentx.appendChild(newChild);}
259     else {return parentx.insertBefore(newChild,refChild.nextSibling);}
260   },
261
262   positionCtlOverIcon: function(ctl,icon) {
263     if (ctl.style.display=='none') ctl.style.display='block';
264     var offsets=Position.page(icon);
265     var correction=Prototype.Browser.IE ? 1 : 2;  // based on a 1px border
266     var lpad=this.nan2zero(Element.getStyle(icon,'padding-left'))
267     ctl.style.left = (offsets[0]+lpad+correction)+'px';
268     var scrTop=this.docScrollTop();
269     var newTop=offsets[1] + correction + scrTop;
270     var ctlht=ctl.offsetHeight;
271     var iconht=icon.offsetHeight;
272     if (newTop+iconht+ctlht < this.windowHeight()+scrTop)
273       newTop+=iconht;  // display below icon
274     else
275       newTop=Math.max(newTop-ctlht,scrTop);  // display above icon
276     ctl.style.top = newTop+'px';
277   },
278
279   createFormField: function(parent,elemTag,elemType,id,name) {
280     if (typeof name!='string') name=id;
281     if (Prototype.Browser.IE) {
282       // IE cannot set NAME attribute on dynamically created elements
283       var s=elemTag+' id="'+id+'"';
284       if (elemType) s+=' type="'+elemType+'"';
285       if (elemTag.match(/^(form|input|select|textarea|object|button|img)$/)) s+=' name="'+name+'"';
286       var field=document.createElement('<'+s+' />');
287     } else {
288       var field=document.createElement(elemTag);
289       if (elemType) field.type=elemType;
290       field.id=id;
291       if (typeof field.name=='string') field.name=name;
292     }
293     parent.appendChild(field);
294     return field;
295   },
296
297 /**
298  * Gets the value of the specified cookie
299  */
300   getCookie: function(itemName) {
301     var arg = itemName+'=';
302     var alen = arg.length;
303     var clen = document.cookie.length;
304     var i = 0;
305     while (i < clen) {
306       var j = i + alen;
307       if (document.cookie.substring(i, j) == arg) {
308         var endstr = document.cookie.indexOf (';', j);
309         if (endstr == -1) endstr=document.cookie.length;
310         return unescape(document.cookie.substring(j, endstr));
311       }
312       i = document.cookie.indexOf(' ', i) + 1;
313       if (i == 0) break;
314     }
315     return null;
316   },
317
318 /**
319  * Write information to cookie.
320  * For cookies to be retained for the current session only, set daysToKeep=null.
321  * To erase a cookie, pass a negative daysToKeep value.
322  */
323   setCookie: function(itemName,itemValue,daysToKeep,cookiePath,cookieDomain) {
324         var c = itemName+"="+escape(itemValue);
325         if (typeof(daysToKeep)=='number') {
326                 var date = new Date();
327                 date.setTime(date.getTime()+(daysToKeep*24*60*60*1000));
328                 c+="; expires="+date.toGMTString();
329         }
330         if (typeof(cookiePath)=='string') c+="; path="+cookiePath;
331         if (typeof(cookieDomain)=='string') c+="; domain="+cookieDomain;
332     document.cookie = c;
333   }
334
335 };
336
337
338 // Translation helper object
339 /** @singleton */
340 var RicoTranslate = {
341   phrases : {},
342   thouSep : ",",
343   decPoint: ".",
344   langCode: "en",
345   re      : /^(\W*)\b(.*)\b(\W*)$/,
346   dateFmt : "mm/dd/yyyy",
347   timeFmt : "hh:nn:ss a/pm",
348   monthNames: ['January','February','March','April','May','June',
349                'July','August','September','October','November','December'],
350   dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
351
352   addPhrase: function(fromPhrase, toPhrase) {
353     this.phrases[fromPhrase]=toPhrase;
354   },
355
356 /**
357  * fromPhrase may contain multiple words/phrases separated by tabs
358  * and each portion will be looked up separately.
359  * Punctuation & spaces at the beginning or
360  * ending of a phrase are ignored.
361  */
362   getPhrase: function(fromPhrase) {
363     var words=fromPhrase.split(/\t/);
364     var transWord,translated = '';
365     for (var i=0; i<words.length; i++) {
366       if (this.re.exec(words[i])) {
367         transWord=this.phrases[RegExp.$2];
368         translated += (typeof transWord=='string') ? RegExp.$1+transWord+RegExp.$3 : words[i];
369       } else {
370         translated += words[i];
371       }
372     }
373     return translated;
374   }
375 }
376
377
378 if (!Date.prototype.formatDate) {
379   Date.prototype.formatDate = function(fmt) {
380     var d=this;
381     var datefmt=(typeof fmt=='string') ? datefmt=fmt : 'translateDate';
382     switch (datefmt) {
383       case 'locale':
384       case 'localeDateTime':
385         return d.toLocaleString();
386       case 'localeDate':
387         return d.toLocaleDateString();
388       case 'translate':
389       case 'translateDateTime':
390         datefmt=RicoTranslate.dateFmt+' '+RicoTranslate.timeFmt;
391         break;
392       case 'translateDate':
393         datefmt=RicoTranslate.dateFmt;
394         break;
395     }
396     return datefmt.replace(/(yyyy|mmmm|mmm|mm|dddd|ddd|dd|hh|nn|ss|a\/p)/gi,
397       function($1) {
398         switch ($1.toLowerCase()) {
399         case 'yyyy': return d.getFullYear();
400         case 'mmmm': return RicoTranslate.monthNames[d.getMonth()];
401         case 'mmm':  return RicoTranslate.monthNames[d.getMonth()].substr(0, 3);
402         case 'mm':   return (d.getMonth() + 1).toPaddedString(2);
403         case 'm':    return (d.getMonth() + 1);
404         case 'dddd': return RicoTranslate.dayNames[d.getDay()];
405         case 'ddd':  return RicoTranslate.dayNames[d.getDay()].substr(0, 3);
406         case 'dd':   return d.getDate().toPaddedString(2);
407         case 'd':    return d.getDate();
408         case 'hh':   return ((h = d.getHours() % 12) ? h : 12).toPaddedString(2);
409         case 'h':    return ((h = d.getHours() % 12) ? h : 12);
410         case 'HH':   return d.getHours().toPaddedString(2);
411         case 'H':    return d.getHours();
412         case 'nn':   return d.getMinutes().toPaddedString(2);
413         case 'ss':   return d.getSeconds().toPaddedString(2);
414         case 'a/p':  return d.getHours() < 12 ? 'a' : 'p';
415         }
416       }
417     );
418   }
419 }
420
421 if (!Date.prototype.setISO8601) {
422 /**
423  * Converts a string in ISO 8601 format to a date object.
424  * Returns true if string is a valid date or date-time.
425  * Based on info at http://delete.me.uk/2005/03/iso8601.html
426  */
427   Date.prototype.setISO8601 = function (string) {
428     if (!string) return false;
429     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))?)?)?)?)?)?/);
430     if (!d) return false;
431     var offset = 0;
432     var date = new Date(d[1], 0, 1);
433
434     if (d[2]) { date.setMonth(d[2] - 1); }
435     if (d[3]) { date.setDate(d[3]); }
436     if (d[4]) { date.setHours(d[4]); }
437     if (d[5]) { date.setMinutes(d[5]); }
438     if (d[6]) { date.setSeconds(d[6]); }
439     if (d[7]) { date.setMilliseconds(Number("0." + d[7]) * 1000); }
440     if (d[8]) {
441         if (d[10] && d[11]) offset = (Number(d[10]) * 60) + Number(d[11]);
442         offset *= ((d[9] == '-') ? 1 : -1);
443         offset -= date.getTimezoneOffset();
444     }
445     var time = (Number(date) + (offset * 60 * 1000));
446     this.setTime(Number(time));
447     return true;
448   }
449 }
450
451 if (!Date.prototype.toISO8601String) {
452 /**
453  * Convert date to an ISO 8601 formatted string.
454  * <p>format is an integer in the range 1-6:<dl>
455  * <dt>1 (year)</dt>
456  *   <dd>YYYY (eg 1997)</dd>
457  * <dt>2 (year and month)</dt>
458  *   <dd>YYYY-MM (eg 1997-07)</dd>
459  * <dt>3 (complete date)</dt>
460  *   <dd>YYYY-MM-DD (eg 1997-07-16)</dd>
461  * <dt>4 (complete date plus hours and minutes)</dt>
462  *   <dd>YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)</dd>
463  * <dt>5 (complete date plus hours, minutes and seconds)</dt>
464  *   <dd>YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)</dd>
465  * <dt>6 (complete date plus hours, minutes, seconds and a decimal
466  *   fraction of a second)</dt>
467  *   <dd>YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)</dd>
468  *</dl>
469  * Based on: http://www.codeproject.com/jscript/dateformat.asp
470  */
471   Date.prototype.toISO8601String = function (format, offset) {
472     if (!format) { var format = 6; }
473     if (!offset) {
474         var offset = 'Z';
475         var date = this;
476     } else {
477         var d = offset.match(/([-+])([0-9]{2}):([0-9]{2})/);
478         var offsetnum = (Number(d[2]) * 60) + Number(d[3]);
479         offsetnum *= ((d[1] == '-') ? -1 : 1);
480         var date = new Date(Number(Number(this) + (offsetnum * 60000)));
481     }
482
483     var zeropad = function (num) { return ((num < 10) ? '0' : '') + num; }
484
485     var str = "";
486     str += date.getUTCFullYear();
487     if (format > 1) { str += "-" + zeropad(date.getUTCMonth() + 1); }
488     if (format > 2) { str += "-" + zeropad(date.getUTCDate()); }
489     if (format > 3) {
490         str += "T" + zeropad(date.getUTCHours()) +
491                ":" + zeropad(date.getUTCMinutes());
492     }
493     if (format > 5) {
494         var secs = Number(date.getUTCSeconds() + "." +
495                    ((date.getUTCMilliseconds() < 100) ? '0' : '') +
496                    zeropad(date.getUTCMilliseconds()));
497         str += ":" + zeropad(secs);
498     } else if (format > 4) { str += ":" + zeropad(date.getUTCSeconds()); }
499
500     if (format > 3) { str += offset; }
501     return str;
502   }
503 }
504
505 if (!String.prototype.formatDate) {
506   String.prototype.formatDate = function(fmt) {
507     var s=this.replace(/-/g,'/');
508     var d = new Date(s);
509     return isNaN(d) ? this : d.formatDate(fmt);
510   }
511 }
512
513 if (!Number.prototype.formatNumber) {
514 /**
515  * Format a number according to the specs in assoc array 'fmt'.
516  * Result is a string, wrapped in a span element with a class of: negNumber, zeroNumber, posNumber
517  * These classes can be set in CSS to display negative numbers in red, for example.
518  *
519  * <p>fmt may contain:<dl>
520  *   <dt>multiplier </dt><dd> the original number is multiplied by this amount before formatting</dd>
521  *   <dt>decPlaces  </dt><dd> number of digits to the right of the decimal point</dd>
522  *   <dt>decPoint   </dt><dd> character to be used as the decimal point</dd>
523  *   <dt>thouSep    </dt><dd> character to use as the thousands separator</dd>
524  *   <dt>prefix     </dt><dd> string added to the beginning of the result (e.g. a currency symbol)</dd>
525  *   <dt>suffix     </dt><dd> string added to the end of the result (e.g. % symbol)</dd>
526  *   <dt>negSign    </dt><dd> specifies format for negative numbers: L=leading minus, T=trailing minus, P=parens</dd>
527  *</dl>
528  */
529   Number.prototype.formatNumber = function(fmt) {
530     if (isNaN(this)) return 'NaN';
531     var n=this;
532     if (typeof fmt.multiplier=='number') n*=fmt.multiplier;
533     var decPlaces=typeof fmt.decPlaces=='number' ? fmt.decPlaces : 0;
534     var thouSep=typeof fmt.thouSep=='string' ? fmt.thouSep : RicoTranslate.thouSep;
535     var decPoint=typeof fmt.decPoint=='string' ? fmt.decPoint : RicoTranslate.decPoint;
536     var prefix=fmt.prefix || "";
537     var suffix=fmt.suffix || "";
538     var negSign=typeof fmt.negSign=='string' ? fmt.negSign : "L";
539     negSign=negSign.toUpperCase();
540     var s,cls;
541     if (n<0.0) {
542       s=RicoUtil.formatPosNumber(-n,decPlaces,thouSep,decPoint);
543       if (negSign=="P") s="("+s+")";
544       s=prefix+s;
545       if (negSign=="L") s="-"+s;
546       if (negSign=="T") s+="-";
547       cls='negNumber';
548     } else {
549       cls=n==0.0 ? 'zeroNumber' : 'posNumber';
550       s=prefix+RicoUtil.formatPosNumber(n,decPlaces,thouSep,decPoint);
551     }
552     return "<span class='"+cls+"'>"+s+suffix+"</span>";
553   }
554 }
555
556 if (!String.prototype.formatNumber) {
557 /**
558  * Take a string that can be converted via parseFloat
559  * and format it according to the specs in assoc array 'fmt'.
560  */
561   String.prototype.formatNumber = function(fmt) {
562     var n=parseFloat(this);
563     return isNaN(n) ? this : n.formatNumber(fmt);
564   }
565 }
566
567 /**
568  * Fix select control bleed-thru on floating divs in IE.
569  * Based on technique published by Joe King at:
570  * http://dotnetjunkies.com/WebLog/jking/archive/2003/10/30/2975.aspx
571  */
572 Rico.Shim = Class.create();
573
574 if (Prototype.Browser.IE) {
575   Rico.Shim.prototype = {
576
577     initialize: function(DivRef) {
578       this.ifr = document.createElement('iframe');
579       this.ifr.style.position="absolute";
580       this.ifr.style.display = "none";
581       this.ifr.src="javascript:false;";
582       DivRef.parentNode.appendChild(this.ifr);
583       this.DivRef=DivRef;
584     },
585
586     hide: function() {
587       this.ifr.style.display = "none";
588     },
589
590     show: function() {
591       this.ifr.style.width   = this.DivRef.offsetWidth;
592       this.ifr.style.height  = this.DivRef.offsetHeight;
593       this.ifr.style.top     = this.DivRef.style.top;
594       this.ifr.style.left    = this.DivRef.style.left;
595       this.ifr.style.zIndex  = this.DivRef.currentStyle.zIndex - 1;
596       this.ifr.style.display = "block";
597     }
598   }
599 } else {
600   Rico.Shim.prototype = {
601     initialize: function() {},
602     hide: function() {},
603     show: function() {}
604   }
605 }
606
607
608 /**
609  * Rico.Shadow is intended for positioned elements.
610  * Uses blur filter in IE, and alpha-transparent png images for all other browsers.
611  * Based on: http://www.positioniseverything.net/articles/dropshadows.html
612  */
613 Rico.Shadow = Class.create();
614
615 Rico.Shadow.prototype = {
616
617   initialize: function(DivRef) {
618     this.div = document.createElement('div');
619     this.div.style.position="absolute";
620     if (typeof this.div.style.filter=='undefined') {
621       new Image().src = Rico.imgDir+"shadow.png";
622       new Image().src = Rico.imgDir+"shadow_ur.png";
623       new Image().src = Rico.imgDir+"shadow_ll.png";
624       this.createShadow();
625       this.offset=5;
626     } else {
627       this.div.style.backgroundColor='#888';
628       this.div.style.filter='progid:DXImageTransform.Microsoft.Blur(makeShadow=1, shadowOpacity=0.3, pixelRadius=3)';
629       this.offset=0; // MS blur filter already does offset
630     }
631     this.div.style.display = "none";
632     DivRef.parentNode.appendChild(this.div);
633     this.DivRef=DivRef;
634   },
635
636   createShadow: function() {
637     var tab = document.createElement('table');
638     tab.style.height='100%';
639     tab.style.width='100%';
640     tab.cellSpacing=0;
641     tab.dir='ltr';
642
643     var tr1=tab.insertRow(-1);
644     tr1.style.height='8px';
645     var td11=tr1.insertCell(-1);
646     td11.style.width='8px';
647     var td12=tr1.insertCell(-1);
648     td12.style.background="transparent url("+Rico.imgDir+"shadow_ur.png"+") no-repeat right bottom"
649
650     var tr2=tab.insertRow(-1);
651     var td21=tr2.insertCell(-1);
652     td21.style.background="transparent url("+Rico.imgDir+"shadow_ll.png"+") no-repeat right bottom"
653     var td22=tr2.insertCell(-1);
654     td22.style.background="transparent url("+Rico.imgDir+"shadow.png"+") no-repeat right bottom"
655
656     this.div.appendChild(tab);
657   },
658
659   hide: function() {
660     this.div.style.display = "none";
661   },
662
663   show: function() {
664     this.div.style.width = this.DivRef.offsetWidth + 'px';
665     this.div.style.height= this.DivRef.offsetHeight + 'px';
666     this.div.style.top   = (parseInt(this.DivRef.style.top)+this.offset)+'px';
667     this.div.style.left  = (parseInt(this.DivRef.style.left)+this.offset)+'px';
668     this.div.style.zIndex= parseInt(Element.getStyle(this.DivRef,'z-index')) - 1;
669     this.div.style.display = "block";
670   }
671 }
672
673
674 Rico.Popup = Class.create();
675
676 Rico.Popup.prototype = {
677
678   initialize: function(options,DivRef) {
679     this.options = {
680       hideOnEscape  : true,
681       hideOnClick   : true,
682       ignoreClicks  : false,
683       position      : 'absolute',
684       shadow        : true
685     }
686     Object.extend(this.options, options || {});
687     if (DivRef) this.setDiv(DivRef);
688   },
689
690   setDiv: function(DivRef,closeFunc) {
691     this.divPopup=$(DivRef);
692     var position=this.options.position == 'auto' ? Element.getStyle(this.divPopup,'position').toLowerCase() : this.options.position;
693     if (!this.divPopup || position != 'absolute') return;
694     this.closeFunc=closeFunc || this.closePopup.bindAsEventListener(this);
695     this.shim=new Rico.Shim(this.divPopup);
696     if (this.options.shadow)
697       this.shadow=new Rico.Shadow(this.divPopup);
698     if (this.options.hideOnClick)
699       Event.observe(document,"click", this.closeFunc);
700     if (this.options.hideOnEscape)
701       Event.observe(document,"keyup", this._checkKey.bindAsEventListener(this));
702     if (this.options.ignoreClicks) this.ignoreClicks();
703   },
704   
705   ignoreClicks: function() {
706     Event.observe(this.divPopup,"click", this._ignoreClick.bindAsEventListener(this));
707   },
708
709   _ignoreClick: function(e) {
710     if (e.stopPropagation)
711       e.stopPropagation();
712     else
713       e.cancelBubble = true;
714     return true;
715   },
716   
717   // event handler to process keyup events (hide menu on escape key)
718   _checkKey: function(e) {
719     if (RicoUtil.eventKey(e)==27) this.closeFunc();
720     return true;
721   },
722
723   openPopup: function(left,top) {
724     if (typeof left=='number') this.divPopup.style.left=left+'px';
725     if (typeof top=='number') this.divPopup.style.top=top+'px';
726     this.divPopup.style.display="block";
727     if (this.shim) this.shim.show();
728     if (this.shadow) this.shadow.show();
729   },
730
731   closePopup: function() {
732     if (this.shim) this.shim.hide();
733     if (this.shadow) this.shadow.hide();
734     this.divPopup.style.display="none"
735   }
736
737 }
738
739 Rico.includeLoaded('ricoCommon.js');