restructure webif, add automake/autoconf
[enigma2-plugins.git] / webinterface / src / web-data / rico.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
17 //-------------------- rico.js
18 var Rico = {
19   Version: '1.1.2',
20   prototypeVersion: parseFloat(Prototype.Version.split(".")[0] + "." + Prototype.Version.split(".")[1])
21 }
22
23 if((typeof Prototype=='undefined') || Rico.prototypeVersion < 1.3)
24       throw("Rico requires the Prototype JavaScript framework >= 1.3");
25
26 Rico.ArrayExtensions = new Array();
27
28 if (Object.prototype.extend) {
29    Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Object.prototype.extend;
30 }else{
31   Object.prototype.extend = function(object) {
32     return Object.extend.apply(this, [this, object]);
33   }
34   Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Object.prototype.extend;
35 }
36
37 if (Array.prototype.push) {
38    Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.push;
39 }
40
41 if (!Array.prototype.remove) {
42    Array.prototype.remove = function(dx) {
43       if( isNaN(dx) || dx > this.length )
44          return false;
45       for( var i=0,n=0; i<this.length; i++ )
46          if( i != dx )
47             this[n++]=this[i];
48       this.length-=1;
49    };
50   Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.remove;
51 }
52
53 if (!Array.prototype.removeItem) {
54    Array.prototype.removeItem = function(item) {
55       for ( var i = 0 ; i < this.length ; i++ )
56          if ( this[i] == item ) {
57             this.remove(i);
58             break;
59          }
60    };
61   Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.removeItem;
62 }
63
64 if (!Array.prototype.indices) {
65    Array.prototype.indices = function() {
66       var indexArray = new Array();
67       for ( index in this ) {
68          var ignoreThis = false;
69          for ( var i = 0 ; i < Rico.ArrayExtensions.length ; i++ ) {
70             if ( this[index] == Rico.ArrayExtensions[i] ) {
71                ignoreThis = true;
72                break;
73             }
74          }
75          if ( !ignoreThis )
76             indexArray[ indexArray.length ] = index;
77       }
78       return indexArray;
79    }
80   Rico.ArrayExtensions[ Rico.ArrayExtensions.length ] = Array.prototype.indices;
81 }
82
83 // Create the loadXML method and xml getter for Mozilla
84 if ( window.DOMParser &&
85           window.XMLSerializer &&
86           window.Node && Node.prototype && Node.prototype.__defineGetter__ ) {
87
88    if (!Document.prototype.loadXML) {
89       Document.prototype.loadXML = function (s) {
90          var doc2 = (new DOMParser()).parseFromString(s, "text/xml");
91          while (this.hasChildNodes())
92             this.removeChild(this.lastChild);
93
94          for (var i = 0; i < doc2.childNodes.length; i++) {
95             this.appendChild(this.importNode(doc2.childNodes[i], true));
96          }
97       };
98         }
99
100         Document.prototype.__defineGetter__( "xml",
101            function () {
102                    return (new XMLSerializer()).serializeToString(this);
103            }
104          );
105 }
106
107 document.getElementsByTagAndClassName = function(tagName, className) {
108   if ( tagName == null )
109      tagName = '*';
110
111   var children = document.getElementsByTagName(tagName) || document.all;
112   var elements = new Array();
113
114   if ( className == null )
115     return children;
116
117   for (var i = 0; i < children.length; i++) {
118     var child = children[i];
119     var classNames = child.className.split(' ');
120     for (var j = 0; j < classNames.length; j++) {
121       if (classNames[j] == className) {
122         elements.push(child);
123         break;
124       }
125     }
126   }
127
128   return elements;
129 }
130
131
132 //-------------------- ricoAccordion.js
133 Rico.Accordion = Class.create();
134
135 Rico.Accordion.prototype = {
136
137    initialize: function(container, options) {
138       this.container            = $(container);
139       this.lastExpandedTab      = null;
140       this.accordionTabs        = new Array();
141       this.setOptions(options);
142       this._attachBehaviors();
143       if(!container) return;
144
145       this.container.style.borderBottom = '1px solid ' + this.options.borderColor;
146       // validate onloadShowTab
147        if (this.options.onLoadShowTab >= this.accordionTabs.length)
148         this.options.onLoadShowTab = 0;
149
150       // set the initial visual state...
151       for ( var i=0 ; i < this.accordionTabs.length ; i++ )
152       {
153         if (i != this.options.onLoadShowTab){
154          this.accordionTabs[i].collapse();
155          this.accordionTabs[i].content.style.display = 'none';
156         }
157       }
158       this.lastExpandedTab = this.accordionTabs[this.options.onLoadShowTab];
159       if (this.options.panelHeight == 'auto'){
160           var tabToCheck = (this.options.onloadShowTab === 0)? 1 : 0;
161           var titleBarSize = parseInt(RicoUtil.getElementsComputedStyle(this.accordionTabs[tabToCheck].titleBar, 'height'));
162           if (isNaN(titleBarSize))
163             titleBarSize = this.accordionTabs[tabToCheck].titleBar.offsetHeight;
164           
165           var totalTitleBarSize = this.accordionTabs.length * titleBarSize;
166           var parentHeight = parseInt(RicoUtil.getElementsComputedStyle(this.container.parentNode, 'height'));
167           if (isNaN(parentHeight))
168             parentHeight = this.container.parentNode.offsetHeight;
169           
170           this.options.panelHeight = parentHeight - totalTitleBarSize-2;
171       }
172       
173       this.lastExpandedTab.content.style.height = this.options.panelHeight + "px";
174       this.lastExpandedTab.showExpanded();
175       this.lastExpandedTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
176
177    },
178
179    setOptions: function(options) {
180       this.options = {
181          expandedBg          : '#63699c',
182          hoverBg             : '#63699c',
183          collapsedBg         : '#6b79a5',
184          expandedTextColor   : '#ffffff',
185          expandedFontWeight  : 'bold',
186          hoverTextColor      : '#ffffff',
187          collapsedTextColor  : '#ced7ef',
188          collapsedFontWeight : 'normal',
189          hoverTextColor      : '#ffffff',
190          borderColor         : '#1f669b',
191          panelHeight         : 200,
192          onHideTab           : null,
193          onShowTab           : null,
194          onLoadShowTab       : 0
195       }
196       Object.extend(this.options, options || {});
197    },
198
199    showTabByIndex: function( anIndex, animate ) {
200       var doAnimate = arguments.length == 1 ? true : animate;
201       this.showTab( this.accordionTabs[anIndex], doAnimate );
202    },
203
204    showTab: function( accordionTab, animate ) {
205      if ( this.lastExpandedTab == accordionTab )
206         return;
207
208       var doAnimate = arguments.length == 1 ? true : animate;
209
210       if ( this.options.onHideTab )
211          this.options.onHideTab(this.lastExpandedTab);
212
213       this.lastExpandedTab.showCollapsed(); 
214       var accordion = this;
215       var lastExpandedTab = this.lastExpandedTab;
216
217       this.lastExpandedTab.content.style.height = (this.options.panelHeight - 1) + 'px';
218       accordionTab.content.style.display = '';
219
220       accordionTab.titleBar.style.fontWeight = this.options.expandedFontWeight;
221
222       if ( doAnimate ) {
223          new Rico.Effect.AccordionSize( this.lastExpandedTab.content,
224                                    accordionTab.content,
225                                    1,
226                                    this.options.panelHeight,
227                                    100, 10,
228                                    { complete: function() {accordion.showTabDone(lastExpandedTab)} } );
229          this.lastExpandedTab = accordionTab;
230       }
231       else {
232          this.lastExpandedTab.content.style.height = "1px";
233          accordionTab.content.style.height = this.options.panelHeight + "px";
234          this.lastExpandedTab = accordionTab;
235          this.showTabDone(lastExpandedTab);
236       }
237    },
238
239    showTabDone: function(collapsedTab) {
240       collapsedTab.content.style.display = 'none';
241       this.lastExpandedTab.showExpanded();
242       if ( this.options.onShowTab )
243          this.options.onShowTab(this.lastExpandedTab);
244    },
245
246    _attachBehaviors: function() {
247       var panels = this._getDirectChildrenByTag(this.container, 'DIV');
248       for ( var i = 0 ; i < panels.length ; i++ ) {
249
250          var tabChildren = this._getDirectChildrenByTag(panels[i],'DIV');
251          if ( tabChildren.length != 2 )
252             continue; // unexpected
253
254          var tabTitleBar   = tabChildren[0];
255          var tabContentBox = tabChildren[1];
256          this.accordionTabs.push( new Rico.Accordion.Tab(this,tabTitleBar,tabContentBox) );
257       }
258    },
259
260    _getDirectChildrenByTag: function(e, tagName) {
261       var kids = new Array();
262       var allKids = e.childNodes;
263       for( var i = 0 ; i < allKids.length ; i++ )
264          if ( allKids[i] && allKids[i].tagName && allKids[i].tagName == tagName )
265             kids.push(allKids[i]);
266       return kids;
267    }
268
269 };
270
271 Rico.Accordion.Tab = Class.create();
272
273 Rico.Accordion.Tab.prototype = {
274
275    initialize: function(accordion, titleBar, content) {
276       this.accordion = accordion;
277       this.titleBar  = titleBar;
278       this.content   = content;
279       this._attachBehaviors();
280    },
281
282    collapse: function() {
283       this.showCollapsed();
284       this.content.style.height = "1px";
285    },
286
287    showCollapsed: function() {
288       this.expanded = false;
289       this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
290       this.titleBar.style.color           = this.accordion.options.collapsedTextColor;
291       this.titleBar.style.fontWeight      = this.accordion.options.collapsedFontWeight;
292       this.content.style.overflow = "hidden";
293    },
294
295    showExpanded: function() {
296       this.expanded = true;
297       this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
298       this.titleBar.style.color           = this.accordion.options.expandedTextColor;
299       this.content.style.overflow         = "auto";
300    },
301
302    titleBarClicked: function(e) {
303       if ( this.accordion.lastExpandedTab == this )
304          return;
305       this.accordion.showTab(this);
306    },
307
308    hover: function(e) {
309                 this.titleBar.style.backgroundColor = this.accordion.options.hoverBg;
310                 this.titleBar.style.color           = this.accordion.options.hoverTextColor;
311    },
312
313    unhover: function(e) {
314       if ( this.expanded ) {
315          this.titleBar.style.backgroundColor = this.accordion.options.expandedBg;
316          this.titleBar.style.color           = this.accordion.options.expandedTextColor;
317       }
318       else {
319          this.titleBar.style.backgroundColor = this.accordion.options.collapsedBg;
320          this.titleBar.style.color           = this.accordion.options.collapsedTextColor;
321       }
322    },
323
324    _attachBehaviors: function() {
325       this.content.style.border = "1px solid " + this.accordion.options.borderColor;
326       this.content.style.borderTopWidth    = "0px";
327       this.content.style.borderBottomWidth = "0px";
328       this.content.style.margin            = "0px";
329
330       this.titleBar.onclick     = this.titleBarClicked.bindAsEventListener(this);
331       this.titleBar.onmouseover = this.hover.bindAsEventListener(this);
332       this.titleBar.onmouseout  = this.unhover.bindAsEventListener(this);
333    }
334
335 };
336
337
338 //-------------------- ricoAjaxEngine.js
339 Rico.AjaxEngine = Class.create();
340
341 Rico.AjaxEngine.prototype = {
342
343    initialize: function() {
344       this.ajaxElements = new Array();
345       this.ajaxObjects  = new Array();
346       this.requestURLS  = new Array();
347       this.options = {};
348    },
349
350    registerAjaxElement: function( anId, anElement ) {
351       if ( !anElement )
352          anElement = $(anId);
353       this.ajaxElements[anId] = anElement;
354    },
355
356    registerAjaxObject: function( anId, anObject ) {
357       this.ajaxObjects[anId] = anObject;
358    },
359
360    registerRequest: function (requestLogicalName, requestURL) {
361       this.requestURLS[requestLogicalName] = requestURL;
362    },
363
364    sendRequest: function(requestName, options) {
365       // Allow for backwards Compatibility
366       if ( arguments.length >= 2 )
367        if (typeof arguments[1] == 'string')
368          options = {parameters: this._createQueryString(arguments, 1)};
369       this.sendRequestWithData(requestName, null, options);
370    },
371
372    sendRequestWithData: function(requestName, xmlDocument, options) {
373       var requestURL = this.requestURLS[requestName];
374       if ( requestURL == null )
375          return;
376
377       // Allow for backwards Compatibility
378       if ( arguments.length >= 3 )
379         if (typeof arguments[2] == 'string')
380           options.parameters = this._createQueryString(arguments, 2);
381
382       new Ajax.Request(requestURL, this._requestOptions(options,xmlDocument));
383    },
384
385    sendRequestAndUpdate: function(requestName,container,options) {
386       // Allow for backwards Compatibility
387       if ( arguments.length >= 3 )
388         if (typeof arguments[2] == 'string')
389           options.parameters = this._createQueryString(arguments, 2);
390
391       this.sendRequestWithDataAndUpdate(requestName, null, container, options);
392    },
393
394    sendRequestWithDataAndUpdate: function(requestName,xmlDocument,container,options) {
395       var requestURL = this.requestURLS[requestName];
396       if ( requestURL == null )
397          return;
398
399       // Allow for backwards Compatibility
400       if ( arguments.length >= 4 )
401         if (typeof arguments[3] == 'string')
402           options.parameters = this._createQueryString(arguments, 3);
403
404       var updaterOptions = this._requestOptions(options,xmlDocument);
405
406       new Ajax.Updater(container, requestURL, updaterOptions);
407    },
408
409    // Private -- not part of intended engine API --------------------------------------------------------------------
410
411    _requestOptions: function(options,xmlDoc) {
412       var requestHeaders = ['X-Rico-Version', Rico.Version ];
413       var sendMethod = 'post';
414       if ( xmlDoc == null )
415         if (Rico.prototypeVersion < 1.4)
416         requestHeaders.push( 'Content-type', 'text/xml' );
417       else
418           sendMethod = 'get';
419       (!options) ? options = {} : '';
420
421       if (!options._RicoOptionsProcessed){
422       // Check and keep any user onComplete functions
423         if (options.onComplete)
424              options.onRicoComplete = options.onComplete;
425         // Fix onComplete
426         if (options.overrideOnComplete)
427           options.onComplete = options.overrideOnComplete;
428         else
429           options.onComplete = this._onRequestComplete.bind(this);
430         options._RicoOptionsProcessed = true;
431       }
432
433      // Set the default options and extend with any user options
434      this.options = {
435                      requestHeaders: requestHeaders,
436                      parameters:     options.parameters,
437                      postBody:       xmlDoc,
438                      method:         sendMethod,
439                      onComplete:     options.onComplete
440                     };
441      // Set any user options:
442      Object.extend(this.options, options);
443      return this.options;
444    },
445
446    _createQueryString: function( theArgs, offset ) {
447       var queryString = ""
448       for ( var i = offset ; i < theArgs.length ; i++ ) {
449           if ( i != offset )
450             queryString += "&";
451
452           var anArg = theArgs[i];
453
454           if ( anArg.name != undefined && anArg.value != undefined ) {
455             queryString += anArg.name +  "=" + escape(anArg.value);
456           }
457           else {
458              var ePos  = anArg.indexOf('=');
459              var argName  = anArg.substring( 0, ePos );
460              var argValue = anArg.substring( ePos + 1 );
461              queryString += argName + "=" + escape(argValue);
462           }
463       }
464       return queryString;
465    },
466
467    _onRequestComplete : function(request) {
468       if(!request)
469           return;
470       // User can set an onFailure option - which will be called by prototype
471       if (request.status != 200)
472         return;
473
474       var response = request.responseXML.getElementsByTagName("ajax-response");
475       if (response == null || response.length != 1)
476          return;
477       this._processAjaxResponse( response[0].childNodes );
478       
479       // Check if user has set a onComplete function
480       var onRicoComplete = this.options.onRicoComplete;
481       if (onRicoComplete != null)
482           onRicoComplete();
483    },
484
485    _processAjaxResponse: function( xmlResponseElements ) {
486       for ( var i = 0 ; i < xmlResponseElements.length ; i++ ) {
487          var responseElement = xmlResponseElements[i];
488
489          // only process nodes of type element.....
490          if ( responseElement.nodeType != 1 )
491             continue;
492
493          var responseType = responseElement.getAttribute("type");
494          var responseId   = responseElement.getAttribute("id");
495
496          if ( responseType == "object" )
497             this._processAjaxObjectUpdate( this.ajaxObjects[ responseId ], responseElement );
498          else if ( responseType == "element" )
499             this._processAjaxElementUpdate( this.ajaxElements[ responseId ], responseElement );
500          else
501             alert('unrecognized AjaxResponse type : ' + responseType );
502       }
503    },
504
505    _processAjaxObjectUpdate: function( ajaxObject, responseElement ) {
506       ajaxObject.ajaxUpdate( responseElement );
507    },
508
509    _processAjaxElementUpdate: function( ajaxElement, responseElement ) {
510       ajaxElement.innerHTML = RicoUtil.getContentAsString(responseElement);
511    }
512
513 }
514
515 var ajaxEngine = new Rico.AjaxEngine();
516
517
518 //-------------------- ricoColor.js
519 Rico.Color = Class.create();
520
521 Rico.Color.prototype = {
522
523    initialize: function(red, green, blue) {
524       this.rgb = { r: red, g : green, b : blue };
525    },
526
527    setRed: function(r) {
528       this.rgb.r = r;
529    },
530
531    setGreen: function(g) {
532       this.rgb.g = g;
533    },
534
535    setBlue: function(b) {
536       this.rgb.b = b;
537    },
538
539    setHue: function(h) {
540
541       // get an HSB model, and set the new hue...
542       var hsb = this.asHSB();
543       hsb.h = h;
544
545       // convert back to RGB...
546       this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
547    },
548
549    setSaturation: function(s) {
550       // get an HSB model, and set the new hue...
551       var hsb = this.asHSB();
552       hsb.s = s;
553
554       // convert back to RGB and set values...
555       this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
556    },
557
558    setBrightness: function(b) {
559       // get an HSB model, and set the new hue...
560       var hsb = this.asHSB();
561       hsb.b = b;
562
563       // convert back to RGB and set values...
564       this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
565    },
566
567    darken: function(percent) {
568       var hsb  = this.asHSB();
569       this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
570    },
571
572    brighten: function(percent) {
573       var hsb  = this.asHSB();
574       this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
575    },
576
577    blend: function(other) {
578       this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
579       this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
580       this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
581    },
582
583    isBright: function() {
584       var hsb = this.asHSB();
585       return this.asHSB().b > 0.5;
586    },
587
588    isDark: function() {
589       return ! this.isBright();
590    },
591
592    asRGB: function() {
593       return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
594    },
595
596    asHex: function() {
597       return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
598    },
599
600    asHSB: function() {
601       return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
602    },
603
604    toString: function() {
605       return this.asHex();
606    }
607
608 };
609
610 Rico.Color.createFromHex = function(hexCode) {
611   if(hexCode.length==4) {
612     var shortHexCode = hexCode; 
613     var hexCode = '#';
614     for(var i=1;i<4;i++) hexCode += (shortHexCode.charAt(i) + 
615 shortHexCode.charAt(i));
616   }
617    if ( hexCode.indexOf('#') == 0 )
618       hexCode = hexCode.substring(1);
619    var red   = hexCode.substring(0,2);
620    var green = hexCode.substring(2,4);
621    var blue  = hexCode.substring(4,6);
622    return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
623 }
624
625 /**
626  * Factory method for creating a color from the background of
627  * an HTML element.
628  */
629 Rico.Color.createColorFromBackground = function(elem) {
630
631    var actualColor = RicoUtil.getElementsComputedStyle($(elem), "backgroundColor", "background-color");
632
633    if ( actualColor == "transparent" && elem.parentNode )
634       return Rico.Color.createColorFromBackground(elem.parentNode);
635
636    if ( actualColor == null )
637       return new Rico.Color(255,255,255);
638
639    if ( actualColor.indexOf("rgb(") == 0 ) {
640       var colors = actualColor.substring(4, actualColor.length - 1 );
641       var colorArray = colors.split(",");
642       return new Rico.Color( parseInt( colorArray[0] ),
643                             parseInt( colorArray[1] ),
644                             parseInt( colorArray[2] )  );
645
646    }
647    else if ( actualColor.indexOf("#") == 0 ) {
648       return Rico.Color.createFromHex(actualColor);
649    }
650    else
651       return new Rico.Color(255,255,255);
652 }
653
654 Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
655
656    var red   = 0;
657         var green = 0;
658         var blue  = 0;
659
660    if (saturation == 0) {
661       red = parseInt(brightness * 255.0 + 0.5);
662            green = red;
663            blue = red;
664         }
665         else {
666       var h = (hue - Math.floor(hue)) * 6.0;
667       var f = h - Math.floor(h);
668       var p = brightness * (1.0 - saturation);
669       var q = brightness * (1.0 - saturation * f);
670       var t = brightness * (1.0 - (saturation * (1.0 - f)));
671
672       switch (parseInt(h)) {
673          case 0:
674             red   = (brightness * 255.0 + 0.5);
675             green = (t * 255.0 + 0.5);
676             blue  = (p * 255.0 + 0.5);
677             break;
678          case 1:
679             red   = (q * 255.0 + 0.5);
680             green = (brightness * 255.0 + 0.5);
681             blue  = (p * 255.0 + 0.5);
682             break;
683          case 2:
684             red   = (p * 255.0 + 0.5);
685             green = (brightness * 255.0 + 0.5);
686             blue  = (t * 255.0 + 0.5);
687             break;
688          case 3:
689             red   = (p * 255.0 + 0.5);
690             green = (q * 255.0 + 0.5);
691             blue  = (brightness * 255.0 + 0.5);
692             break;
693          case 4:
694             red   = (t * 255.0 + 0.5);
695             green = (p * 255.0 + 0.5);
696             blue  = (brightness * 255.0 + 0.5);
697             break;
698           case 5:
699             red   = (brightness * 255.0 + 0.5);
700             green = (p * 255.0 + 0.5);
701             blue  = (q * 255.0 + 0.5);
702             break;
703             }
704         }
705
706    return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
707 }
708
709 Rico.Color.RGBtoHSB = function(r, g, b) {
710
711    var hue;
712    var saturation;
713    var brightness;
714
715    var cmax = (r > g) ? r : g;
716    if (b > cmax)
717       cmax = b;
718
719    var cmin = (r < g) ? r : g;
720    if (b < cmin)
721       cmin = b;
722
723    brightness = cmax / 255.0;
724    if (cmax != 0)
725       saturation = (cmax - cmin)/cmax;
726    else
727       saturation = 0;
728
729    if (saturation == 0)
730       hue = 0;
731    else {
732       var redc   = (cmax - r)/(cmax - cmin);
733         var greenc = (cmax - g)/(cmax - cmin);
734         var bluec  = (cmax - b)/(cmax - cmin);
735
736         if (r == cmax)
737            hue = bluec - greenc;
738         else if (g == cmax)
739            hue = 2.0 + redc - bluec;
740       else
741            hue = 4.0 + greenc - redc;
742
743         hue = hue / 6.0;
744         if (hue < 0)
745            hue = hue + 1.0;
746    }
747
748    return { h : hue, s : saturation, b : brightness };
749 }
750
751
752 //-------------------- ricoCorner.js
753 Rico.Corner = {
754
755    round: function(e, options) {
756       var e = $(e);
757       this._setOptions(options);
758
759       var color = this.options.color;
760       if ( this.options.color == "fromElement" )
761          color = this._background(e);
762
763       var bgColor = this.options.bgColor;
764       if ( this.options.bgColor == "fromParent" )
765          bgColor = this._background(e.offsetParent);
766
767       this._roundCornersImpl(e, color, bgColor);
768    },
769
770    _roundCornersImpl: function(e, color, bgColor) {
771       if(this.options.border)
772          this._renderBorder(e,bgColor);
773       if(this._isTopRounded())
774          this._roundTopCorners(e,color,bgColor);
775       if(this._isBottomRounded())
776          this._roundBottomCorners(e,color,bgColor);
777    },
778
779    _renderBorder: function(el,bgColor) {
780       var borderValue = "1px solid " + this._borderColor(bgColor);
781       var borderL = "border-left: "  + borderValue;
782       var borderR = "border-right: " + borderValue;
783       var style   = "style='" + borderL + ";" + borderR +  "'";
784       el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>"
785    },
786
787    _roundTopCorners: function(el, color, bgColor) {
788       var corner = this._createCorner(bgColor);
789       for(var i=0 ; i < this.options.numSlices ; i++ )
790          corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
791       el.style.paddingTop = 0;
792       el.insertBefore(corner,el.firstChild);
793    },
794
795    _roundBottomCorners: function(el, color, bgColor) {
796       var corner = this._createCorner(bgColor);
797       for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- )
798          corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
799       el.style.paddingBottom = 0;
800       el.appendChild(corner);
801    },
802
803    _createCorner: function(bgColor) {
804       var corner = document.createElement("div");
805       corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
806       return corner;
807    },
808
809    _createCornerSlice: function(color,bgColor, n, position) {
810       var slice = document.createElement("span");
811
812       var inStyle = slice.style;
813       inStyle.backgroundColor = color;
814       inStyle.display  = "block";
815       inStyle.height   = "1px";
816       inStyle.overflow = "hidden";
817       inStyle.fontSize = "1px";
818
819       var borderColor = this._borderColor(color,bgColor);
820       if ( this.options.border && n == 0 ) {
821          inStyle.borderTopStyle    = "solid";
822          inStyle.borderTopWidth    = "1px";
823          inStyle.borderLeftWidth   = "0px";
824          inStyle.borderRightWidth  = "0px";
825          inStyle.borderBottomWidth = "0px";
826          inStyle.height            = "0px"; // assumes css compliant box model
827          inStyle.borderColor       = borderColor;
828       }
829       else if(borderColor) {
830          inStyle.borderColor = borderColor;
831          inStyle.borderStyle = "solid";
832          inStyle.borderWidth = "0px 1px";
833       }
834
835       if ( !this.options.compact && (n == (this.options.numSlices-1)) )
836          inStyle.height = "2px";
837
838       this._setMargin(slice, n, position);
839       this._setBorder(slice, n, position);
840       return slice;
841    },
842
843    _setOptions: function(options) {
844       this.options = {
845          corners : "all",
846          color   : "fromElement",
847          bgColor : "fromParent",
848          blend   : true,
849          border  : false,
850          compact : false
851       }
852       Object.extend(this.options, options || {});
853
854       this.options.numSlices = this.options.compact ? 2 : 4;
855       if ( this._isTransparent() )
856          this.options.blend = false;
857    },
858
859    _whichSideTop: function() {
860       if ( this._hasString(this.options.corners, "all", "top") )
861          return "";
862
863       if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 )
864          return "";
865
866       if (this.options.corners.indexOf("tl") >= 0)
867          return "left";
868       else if (this.options.corners.indexOf("tr") >= 0)
869           return "right";
870       return "";
871    },
872
873    _whichSideBottom: function() {
874       if ( this._hasString(this.options.corners, "all", "bottom") )
875          return "";
876
877       if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 )
878          return "";
879
880       if(this.options.corners.indexOf("bl") >=0)
881          return "left";
882       else if(this.options.corners.indexOf("br")>=0)
883          return "right";
884       return "";
885    },
886
887    _borderColor : function(color,bgColor) {
888       if ( color == "transparent" )
889          return bgColor;
890       else if ( this.options.border )
891          return this.options.border;
892       else if ( this.options.blend )
893          return this._blend( bgColor, color );
894       else
895          return "";
896    },
897
898
899    _setMargin: function(el, n, corners) {
900       var marginSize = this._marginSize(n);
901       var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
902
903       if ( whichSide == "left" ) {
904          el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
905       }
906       else if ( whichSide == "right" ) {
907          el.style.marginRight = marginSize + "px"; el.style.marginLeft  = "0px";
908       }
909       else {
910          el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
911       }
912    },
913
914    _setBorder: function(el,n,corners) {
915       var borderSize = this._borderSize(n);
916       var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
917       if ( whichSide == "left" ) {
918          el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
919       }
920       else if ( whichSide == "right" ) {
921          el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth  = "0px";
922       }
923       else {
924          el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
925       }
926       if (this.options.border != false)
927         el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
928    },
929
930    _marginSize: function(n) {
931       if ( this._isTransparent() )
932          return 0;
933
934       var marginSizes          = [ 5, 3, 2, 1 ];
935       var blendedMarginSizes   = [ 3, 2, 1, 0 ];
936       var compactMarginSizes   = [ 2, 1 ];
937       var smBlendedMarginSizes = [ 1, 0 ];
938
939       if ( this.options.compact && this.options.blend )
940          return smBlendedMarginSizes[n];
941       else if ( this.options.compact )
942          return compactMarginSizes[n];
943       else if ( this.options.blend )
944          return blendedMarginSizes[n];
945       else
946          return marginSizes[n];
947    },
948
949    _borderSize: function(n) {
950       var transparentBorderSizes = [ 5, 3, 2, 1 ];
951       var blendedBorderSizes     = [ 2, 1, 1, 1 ];
952       var compactBorderSizes     = [ 1, 0 ];
953       var actualBorderSizes      = [ 0, 2, 0, 0 ];
954
955       if ( this.options.compact && (this.options.blend || this._isTransparent()) )
956          return 1;
957       else if ( this.options.compact )
958          return compactBorderSizes[n];
959       else if ( this.options.blend )
960          return blendedBorderSizes[n];
961       else if ( this.options.border )
962          return actualBorderSizes[n];
963       else if ( this._isTransparent() )
964          return transparentBorderSizes[n];
965       return 0;
966    },
967
968    _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) return true; return false; },
969    _blend: function(c1, c2) { var cc1 = Rico.Color.createFromHex(c1); cc1.blend(Rico.Color.createFromHex(c2)); return cc1; },
970    _background: function(el) { try { return Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
971    _isTransparent: function() { return this.options.color == "transparent"; },
972    _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
973    _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
974    _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
975 }
976
977
978 //-------------------- ricoDragAndDrop.js
979 Rico.DragAndDrop = Class.create();
980
981 Rico.DragAndDrop.prototype = {
982
983    initialize: function() {
984       this.dropZones                = new Array();
985       this.draggables               = new Array();
986       this.currentDragObjects       = new Array();
987       this.dragElement              = null;
988       this.lastSelectedDraggable    = null;
989       this.currentDragObjectVisible = false;
990       this.interestedInMotionEvents = false;
991       this._mouseDown = this._mouseDownHandler.bindAsEventListener(this);
992       this._mouseMove = this._mouseMoveHandler.bindAsEventListener(this);
993       this._mouseUp = this._mouseUpHandler.bindAsEventListener(this);
994    },
995
996    registerDropZone: function(aDropZone) {
997       this.dropZones[ this.dropZones.length ] = aDropZone;
998    },
999
1000    deregisterDropZone: function(aDropZone) {
1001       var newDropZones = new Array();
1002       var j = 0;
1003       for ( var i = 0 ; i < this.dropZones.length ; i++ ) {
1004          if ( this.dropZones[i] != aDropZone )
1005             newDropZones[j++] = this.dropZones[i];
1006       }
1007
1008       this.dropZones = newDropZones;
1009    },
1010
1011    clearDropZones: function() {
1012       this.dropZones = new Array();
1013    },
1014
1015    registerDraggable: function( aDraggable ) {
1016       this.draggables[ this.draggables.length ] = aDraggable;
1017       this._addMouseDownHandler( aDraggable );
1018    },
1019
1020    clearSelection: function() {
1021       for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
1022          this.currentDragObjects[i].deselect();
1023       this.currentDragObjects = new Array();
1024       this.lastSelectedDraggable = null;
1025    },
1026
1027    hasSelection: function() {
1028       return this.currentDragObjects.length > 0;
1029    },
1030
1031    setStartDragFromElement: function( e, mouseDownElement ) {
1032       this.origPos = RicoUtil.toDocumentPosition(mouseDownElement);
1033       this.startx = e.screenX - this.origPos.x
1034       this.starty = e.screenY - this.origPos.y
1035       //this.startComponentX = e.layerX ? e.layerX : e.offsetX;
1036       //this.startComponentY = e.layerY ? e.layerY : e.offsetY;
1037       //this.adjustedForDraggableSize = false;
1038
1039       this.interestedInMotionEvents = this.hasSelection();
1040       this._terminateEvent(e);
1041    },
1042
1043    updateSelection: function( draggable, extendSelection ) {
1044       if ( ! extendSelection )
1045          this.clearSelection();
1046
1047       if ( draggable.isSelected() ) {
1048          this.currentDragObjects.removeItem(draggable);
1049          draggable.deselect();
1050          if ( draggable == this.lastSelectedDraggable )
1051             this.lastSelectedDraggable = null;
1052       }
1053       else {
1054          this.currentDragObjects[ this.currentDragObjects.length ] = draggable;
1055          draggable.select();
1056          this.lastSelectedDraggable = draggable;
1057       }
1058    },
1059
1060    _mouseDownHandler: function(e) {
1061       if ( arguments.length == 0 )
1062          e = event;
1063
1064       // if not button 1 ignore it...
1065       var nsEvent = e.which != undefined;
1066       if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
1067          return;
1068
1069       var eventTarget      = e.target ? e.target : e.srcElement;
1070       var draggableObject  = eventTarget.draggable;
1071
1072       var candidate = eventTarget;
1073       while (draggableObject == null && candidate.parentNode) {
1074          candidate = candidate.parentNode;
1075          draggableObject = candidate.draggable;
1076       }
1077    
1078       if ( draggableObject == null )
1079          return;
1080
1081       this.updateSelection( draggableObject, e.ctrlKey );
1082
1083       // clear the drop zones postion cache...
1084       if ( this.hasSelection() )
1085          for ( var i = 0 ; i < this.dropZones.length ; i++ )
1086             this.dropZones[i].clearPositionCache();
1087
1088       this.setStartDragFromElement( e, draggableObject.getMouseDownHTMLElement() );
1089    },
1090
1091
1092    _mouseMoveHandler: function(e) {
1093       var nsEvent = e.which != undefined;
1094       if ( !this.interestedInMotionEvents ) {
1095          //this._terminateEvent(e);
1096          return;
1097       }
1098
1099       if ( ! this.hasSelection() )
1100          return;
1101
1102       if ( ! this.currentDragObjectVisible )
1103          this._startDrag(e);
1104
1105       if ( !this.activatedDropZones )
1106          this._activateRegisteredDropZones();
1107
1108       //if ( !this.adjustedForDraggableSize )
1109       //   this._adjustForDraggableSize(e);
1110
1111       this._updateDraggableLocation(e);
1112       this._updateDropZonesHover(e);
1113
1114       this._terminateEvent(e);
1115    },
1116
1117    _makeDraggableObjectVisible: function(e)
1118    {
1119       if ( !this.hasSelection() )
1120          return;
1121
1122       var dragElement;
1123       if ( this.currentDragObjects.length > 1 )
1124          dragElement = this.currentDragObjects[0].getMultiObjectDragGUI(this.currentDragObjects);
1125       else
1126          dragElement = this.currentDragObjects[0].getSingleObjectDragGUI();
1127
1128       // go ahead and absolute position it...
1129       if ( RicoUtil.getElementsComputedStyle(dragElement, "position")  != "absolute" )
1130          dragElement.style.position = "absolute";
1131
1132       // need to parent him into the document...
1133       if ( dragElement.parentNode == null || dragElement.parentNode.nodeType == 11 )
1134          document.body.appendChild(dragElement);
1135
1136       this.dragElement = dragElement;
1137       this._updateDraggableLocation(e);
1138
1139       this.currentDragObjectVisible = true;
1140    },
1141
1142    /**
1143    _adjustForDraggableSize: function(e) {
1144       var dragElementWidth  = this.dragElement.offsetWidth;
1145       var dragElementHeight = this.dragElement.offsetHeight;
1146       if ( this.startComponentX > dragElementWidth )
1147          this.startx -= this.startComponentX - dragElementWidth + 2;
1148       if ( e.offsetY ) {
1149          if ( this.startComponentY > dragElementHeight )
1150             this.starty -= this.startComponentY - dragElementHeight + 2;
1151       }
1152       this.adjustedForDraggableSize = true;
1153    },
1154    **/
1155
1156    _leftOffset: function(e) {
1157            return e.offsetX ? document.body.scrollLeft : 0
1158         },
1159
1160    _topOffset: function(e) {
1161            return e.offsetY ? document.body.scrollTop:0
1162         },
1163
1164                 
1165    _updateDraggableLocation: function(e) {
1166       var dragObjectStyle = this.dragElement.style;
1167       dragObjectStyle.left = (e.screenX + this._leftOffset(e) - this.startx) + "px"
1168       dragObjectStyle.top  = (e.screenY + this._topOffset(e) - this.starty) + "px";
1169    },
1170
1171    _updateDropZonesHover: function(e) {
1172       var n = this.dropZones.length;
1173       for ( var i = 0 ; i < n ; i++ ) {
1174          if ( ! this._mousePointInDropZone( e, this.dropZones[i] ) )
1175             this.dropZones[i].hideHover();
1176       }
1177
1178       for ( var i = 0 ; i < n ; i++ ) {
1179          if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
1180             if ( this.dropZones[i].canAccept(this.currentDragObjects) )
1181                this.dropZones[i].showHover();
1182          }
1183       }
1184    },
1185
1186    _startDrag: function(e) {
1187       for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
1188          this.currentDragObjects[i].startDrag();
1189
1190       this._makeDraggableObjectVisible(e);
1191    },
1192
1193    _mouseUpHandler: function(e) {
1194       if ( ! this.hasSelection() )
1195          return;
1196
1197       var nsEvent = e.which != undefined;
1198       if ( (nsEvent && e.which != 1) || (!nsEvent && e.button != 1))
1199          return;
1200
1201       this.interestedInMotionEvents = false;
1202
1203       if ( this.dragElement == null ) {
1204          this._terminateEvent(e);
1205          return;
1206       }
1207
1208       if ( this._placeDraggableInDropZone(e) )
1209          this._completeDropOperation(e);
1210       else {
1211          this._terminateEvent(e);
1212          new Rico.Effect.Position( this.dragElement,
1213                               this.origPos.x,
1214                               this.origPos.y,
1215                               200,
1216                               20,
1217                               { complete : this._doCancelDragProcessing.bind(this) } );
1218       }
1219
1220      Event.stopObserving(document.body, "mousemove", this._mouseMove);
1221      Event.stopObserving(document.body, "mouseup",  this._mouseUp);
1222    },
1223
1224    _retTrue: function () {
1225       return true;
1226    },
1227
1228    _completeDropOperation: function(e) {
1229       if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() ) {
1230          if ( this.dragElement.parentNode != null )
1231             this.dragElement.parentNode.removeChild(this.dragElement);
1232       }
1233
1234       this._deactivateRegisteredDropZones();
1235       this._endDrag();
1236       this.clearSelection();
1237       this.dragElement = null;
1238       this.currentDragObjectVisible = false;
1239       this._terminateEvent(e);
1240    },
1241
1242    _doCancelDragProcessing: function() {
1243       this._cancelDrag();
1244
1245         if ( this.dragElement != this.currentDragObjects[0].getMouseDownHTMLElement() && this.dragElement)
1246            if ( this.dragElement.parentNode != null )
1247               this.dragElement.parentNode.removeChild(this.dragElement);
1248
1249
1250       this._deactivateRegisteredDropZones();
1251       this.dragElement = null;
1252       this.currentDragObjectVisible = false;
1253    },
1254
1255    _placeDraggableInDropZone: function(e) {
1256       var foundDropZone = false;
1257       var n = this.dropZones.length;
1258       for ( var i = 0 ; i < n ; i++ ) {
1259          if ( this._mousePointInDropZone( e, this.dropZones[i] ) ) {
1260             if ( this.dropZones[i].canAccept(this.currentDragObjects) ) {
1261                this.dropZones[i].hideHover();
1262                this.dropZones[i].accept(this.currentDragObjects);
1263                foundDropZone = true;
1264                break;
1265             }
1266          }
1267       }
1268
1269       return foundDropZone;
1270    },
1271
1272    _cancelDrag: function() {
1273       for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
1274          this.currentDragObjects[i].cancelDrag();
1275    },
1276
1277    _endDrag: function() {
1278       for ( var i = 0 ; i < this.currentDragObjects.length ; i++ )
1279          this.currentDragObjects[i].endDrag();
1280    },
1281
1282    _mousePointInDropZone: function( e, dropZone ) {
1283
1284       var absoluteRect = dropZone.getAbsoluteRect();
1285
1286       return e.clientX  > absoluteRect.left + this._leftOffset(e) &&
1287              e.clientX  < absoluteRect.right + this._leftOffset(e) &&
1288              e.clientY  > absoluteRect.top + this._topOffset(e)   &&
1289              e.clientY  < absoluteRect.bottom + this._topOffset(e);
1290    },
1291
1292    _addMouseDownHandler: function( aDraggable )
1293    {
1294        htmlElement  = aDraggable.getMouseDownHTMLElement();
1295       if ( htmlElement  != null ) { 
1296          htmlElement.draggable = aDraggable;
1297          Event.observe(htmlElement , "mousedown", this._onmousedown.bindAsEventListener(this));
1298          Event.observe(htmlElement, "mousedown", this._mouseDown);
1299       }
1300    },
1301
1302    _activateRegisteredDropZones: function() {
1303       var n = this.dropZones.length;
1304       for ( var i = 0 ; i < n ; i++ ) {
1305          var dropZone = this.dropZones[i];
1306          if ( dropZone.canAccept(this.currentDragObjects) )
1307             dropZone.activate();
1308       }
1309
1310       this.activatedDropZones = true;
1311    },
1312
1313    _deactivateRegisteredDropZones: function() {
1314       var n = this.dropZones.length;
1315       for ( var i = 0 ; i < n ; i++ )
1316          this.dropZones[i].deactivate();
1317       this.activatedDropZones = false;
1318    },
1319
1320    _onmousedown: function () {
1321      Event.observe(document.body, "mousemove", this._mouseMove);
1322      Event.observe(document.body, "mouseup",  this._mouseUp);
1323    },
1324
1325    _terminateEvent: function(e) {
1326       if ( e.stopPropagation != undefined )
1327          e.stopPropagation();
1328       else if ( e.cancelBubble != undefined )
1329          e.cancelBubble = true;
1330
1331       if ( e.preventDefault != undefined )
1332          e.preventDefault();
1333       else
1334          e.returnValue = false;
1335    },
1336
1337
1338            initializeEventHandlers: function() {
1339               if ( typeof document.implementation != "undefined" &&
1340                  document.implementation.hasFeature("HTML",   "1.0") &&
1341                  document.implementation.hasFeature("Events", "2.0") &&
1342                  document.implementation.hasFeature("CSS",    "2.0") ) {
1343                  document.addEventListener("mouseup",   this._mouseUpHandler.bindAsEventListener(this),  false);
1344                  document.addEventListener("mousemove", this._mouseMoveHandler.bindAsEventListener(this), false);
1345               }
1346               else {
1347                  document.attachEvent( "onmouseup",   this._mouseUpHandler.bindAsEventListener(this) );
1348                  document.attachEvent( "onmousemove", this._mouseMoveHandler.bindAsEventListener(this) );
1349               }
1350            }
1351         }
1352
1353         var dndMgr = new Rico.DragAndDrop();
1354         dndMgr.initializeEventHandlers();
1355
1356
1357 //-------------------- ricoDraggable.js
1358 Rico.Draggable = Class.create();
1359
1360 Rico.Draggable.prototype = {
1361
1362    initialize: function( type, htmlElement ) {
1363       this.type          = type;
1364       this.htmlElement   = $(htmlElement);
1365       this.selected      = false;
1366    },
1367
1368    /**
1369     *   Returns the HTML element that should have a mouse down event
1370     *   added to it in order to initiate a drag operation
1371     *
1372     **/
1373    getMouseDownHTMLElement: function() {
1374       return this.htmlElement;
1375    },
1376
1377    select: function() {
1378       this.selected = true;
1379
1380       if ( this.showingSelected )
1381          return;
1382
1383       var htmlElement = this.getMouseDownHTMLElement();
1384
1385       var color = Rico.Color.createColorFromBackground(htmlElement);
1386       color.isBright() ? color.darken(0.033) : color.brighten(0.033);
1387
1388       this.saveBackground = RicoUtil.getElementsComputedStyle(htmlElement, "backgroundColor", "background-color");
1389       htmlElement.style.backgroundColor = color.asHex();
1390       this.showingSelected = true;
1391    },
1392
1393    deselect: function() {
1394       this.selected = false;
1395       if ( !this.showingSelected )
1396          return;
1397
1398       var htmlElement = this.getMouseDownHTMLElement();
1399
1400       htmlElement.style.backgroundColor = this.saveBackground;
1401       this.showingSelected = false;
1402    },
1403
1404    isSelected: function() {
1405       return this.selected;
1406    },
1407
1408    startDrag: function() {
1409    },
1410
1411    cancelDrag: function() {
1412    },
1413
1414    endDrag: function() {
1415    },
1416
1417    getSingleObjectDragGUI: function() {
1418       return this.htmlElement;
1419    },
1420
1421    getMultiObjectDragGUI: function( draggables ) {
1422       return this.htmlElement;
1423    },
1424
1425    getDroppedGUI: function() {
1426       return this.htmlElement;
1427    },
1428
1429    toString: function() {
1430       return this.type + ":" + this.htmlElement + ":";
1431    }
1432
1433 }
1434
1435
1436 //-------------------- ricoDropzone.js
1437 Rico.Dropzone = Class.create();
1438
1439 Rico.Dropzone.prototype = {
1440
1441    initialize: function( htmlElement ) {
1442       this.htmlElement  = $(htmlElement);
1443       this.absoluteRect = null;
1444    },
1445
1446    getHTMLElement: function() {
1447       return this.htmlElement;
1448    },
1449
1450    clearPositionCache: function() {
1451       this.absoluteRect = null;
1452    },
1453
1454    getAbsoluteRect: function() {
1455       if ( this.absoluteRect == null ) {
1456          var htmlElement = this.getHTMLElement();
1457          var pos = RicoUtil.toViewportPosition(htmlElement);
1458
1459          this.absoluteRect = {
1460             top:    pos.y,
1461             left:   pos.x,
1462             bottom: pos.y + htmlElement.offsetHeight,
1463             right:  pos.x + htmlElement.offsetWidth
1464          };
1465       }
1466       return this.absoluteRect;
1467    },
1468
1469    activate: function() {
1470       var htmlElement = this.getHTMLElement();
1471       if (htmlElement == null  || this.showingActive)
1472          return;
1473
1474       this.showingActive = true;
1475       this.saveBackgroundColor = htmlElement.style.backgroundColor;
1476
1477       var fallbackColor = "#ffea84";
1478       var currentColor = Rico.Color.createColorFromBackground(htmlElement);
1479       if ( currentColor == null )
1480          htmlElement.style.backgroundColor = fallbackColor;
1481       else {
1482          currentColor.isBright() ? currentColor.darken(0.2) : currentColor.brighten(0.2);
1483          htmlElement.style.backgroundColor = currentColor.asHex();
1484       }
1485    },
1486
1487    deactivate: function() {
1488       var htmlElement = this.getHTMLElement();
1489       if (htmlElement == null || !this.showingActive)
1490          return;
1491
1492       htmlElement.style.backgroundColor = this.saveBackgroundColor;
1493       this.showingActive = false;
1494       this.saveBackgroundColor = null;
1495    },
1496
1497    showHover: function() {
1498       var htmlElement = this.getHTMLElement();
1499       if ( htmlElement == null || this.showingHover )
1500          return;
1501
1502       this.saveBorderWidth = htmlElement.style.borderWidth;
1503       this.saveBorderStyle = htmlElement.style.borderStyle;
1504       this.saveBorderColor = htmlElement.style.borderColor;
1505
1506       this.showingHover = true;
1507       htmlElement.style.borderWidth = "1px";
1508       htmlElement.style.borderStyle = "solid";
1509       //htmlElement.style.borderColor = "#ff9900";
1510       htmlElement.style.borderColor = "#ffff00";
1511    },
1512
1513    hideHover: function() {
1514       var htmlElement = this.getHTMLElement();
1515       if ( htmlElement == null || !this.showingHover )
1516          return;
1517
1518       htmlElement.style.borderWidth = this.saveBorderWidth;
1519       htmlElement.style.borderStyle = this.saveBorderStyle;
1520       htmlElement.style.borderColor = this.saveBorderColor;
1521       this.showingHover = false;
1522    },
1523
1524    canAccept: function(draggableObjects) {
1525       return true;
1526    },
1527
1528    accept: function(draggableObjects) {
1529       var htmlElement = this.getHTMLElement();
1530       if ( htmlElement == null )
1531          return;
1532
1533       n = draggableObjects.length;
1534       for ( var i = 0 ; i < n ; i++ )
1535       {
1536          var theGUI = draggableObjects[i].getDroppedGUI();
1537          if ( RicoUtil.getElementsComputedStyle( theGUI, "position" ) == "absolute" )
1538          {
1539             theGUI.style.position = "static";
1540             theGUI.style.top = "";
1541             theGUI.style.top = "";
1542          }
1543          htmlElement.appendChild(theGUI);
1544       }
1545    }
1546 }
1547
1548
1549 //-------------------- ricoEffects.js
1550
1551 Rico.Effect = {};
1552
1553 Rico.Effect.SizeAndPosition = Class.create();
1554 Rico.Effect.SizeAndPosition.prototype = {
1555
1556    initialize: function(element, x, y, w, h, duration, steps, options) {
1557       this.element = $(element);
1558       this.x = x;
1559       this.y = y;
1560       this.w = w;
1561       this.h = h;
1562       this.duration = duration;
1563       this.steps    = steps;
1564       this.options  = arguments[7] || {};
1565
1566       this.sizeAndPosition();
1567    },
1568
1569    sizeAndPosition: function() {
1570       if (this.isFinished()) {
1571          if(this.options.complete) this.options.complete(this);
1572          return;
1573       }
1574
1575       if (this.timer)
1576          clearTimeout(this.timer);
1577
1578       var stepDuration = Math.round(this.duration/this.steps) ;
1579
1580       // Get original values: x,y = top left corner;  w,h = width height
1581       var currentX = this.element.offsetLeft;
1582       var currentY = this.element.offsetTop;
1583       var currentW = this.element.offsetWidth;
1584       var currentH = this.element.offsetHeight;
1585
1586       // If values not set, or zero, we do not modify them, and take original as final as well
1587       this.x = (this.x) ? this.x : currentX;
1588       this.y = (this.y) ? this.y : currentY;
1589       this.w = (this.w) ? this.w : currentW;
1590       this.h = (this.h) ? this.h : currentH;
1591
1592       // how much do we need to modify our values for each step?
1593       var difX = this.steps >  0 ? (this.x - currentX)/this.steps : 0;
1594       var difY = this.steps >  0 ? (this.y - currentY)/this.steps : 0;
1595       var difW = this.steps >  0 ? (this.w - currentW)/this.steps : 0;
1596       var difH = this.steps >  0 ? (this.h - currentH)/this.steps : 0;
1597
1598       this.moveBy(difX, difY);
1599       this.resizeBy(difW, difH);
1600
1601       this.duration -= stepDuration;
1602       this.steps--;
1603
1604       this.timer = setTimeout(this.sizeAndPosition.bind(this), stepDuration);
1605    },
1606
1607    isFinished: function() {
1608       return this.steps <= 0;
1609    },
1610
1611    moveBy: function( difX, difY ) {
1612       var currentLeft = this.element.offsetLeft;
1613       var currentTop  = this.element.offsetTop;
1614       var intDifX     = parseInt(difX);
1615       var intDifY     = parseInt(difY);
1616
1617       var style = this.element.style;
1618       if ( intDifX != 0 )
1619          style.left = (currentLeft + intDifX) + "px";
1620       if ( intDifY != 0 )
1621          style.top  = (currentTop + intDifY) + "px";
1622    },
1623
1624    resizeBy: function( difW, difH ) {
1625       var currentWidth  = this.element.offsetWidth;
1626       var currentHeight = this.element.offsetHeight;
1627       var intDifW       = parseInt(difW);
1628       var intDifH       = parseInt(difH);
1629
1630       var style = this.element.style;
1631       if ( intDifW != 0 )
1632          style.width   = (currentWidth  + intDifW) + "px";
1633       if ( intDifH != 0 )
1634          style.height  = (currentHeight + intDifH) + "px";
1635    }
1636 }
1637
1638 Rico.Effect.Size = Class.create();
1639 Rico.Effect.Size.prototype = {
1640
1641    initialize: function(element, w, h, duration, steps, options) {
1642       new Rico.Effect.SizeAndPosition(element, null, null, w, h, duration, steps, options);
1643   }
1644 }
1645
1646 Rico.Effect.Position = Class.create();
1647 Rico.Effect.Position.prototype = {
1648
1649    initialize: function(element, x, y, duration, steps, options) {
1650       new Rico.Effect.SizeAndPosition(element, x, y, null, null, duration, steps, options);
1651   }
1652 }
1653
1654 Rico.Effect.Round = Class.create();
1655 Rico.Effect.Round.prototype = {
1656
1657    initialize: function(tagName, className, options) {
1658       var elements = document.getElementsByTagAndClassName(tagName,className);
1659       for ( var i = 0 ; i < elements.length ; i++ )
1660          Rico.Corner.round( elements[i], options );
1661    }
1662 };
1663
1664 Rico.Effect.FadeTo = Class.create();
1665 Rico.Effect.FadeTo.prototype = {
1666
1667    initialize: function( element, opacity, duration, steps, options) {
1668       this.element  = $(element);
1669       this.opacity  = opacity;
1670       this.duration = duration;
1671       this.steps    = steps;
1672       this.options  = arguments[4] || {};
1673       this.fadeTo();
1674    },
1675
1676    fadeTo: function() {
1677       if (this.isFinished()) {
1678          if(this.options.complete) this.options.complete(this);
1679          return;
1680       }
1681
1682       if (this.timer)
1683          clearTimeout(this.timer);
1684
1685       var stepDuration = Math.round(this.duration/this.steps) ;
1686       var currentOpacity = this.getElementOpacity();
1687       var delta = this.steps > 0 ? (this.opacity - currentOpacity)/this.steps : 0;
1688
1689       this.changeOpacityBy(delta);
1690       this.duration -= stepDuration;
1691       this.steps--;
1692
1693       this.timer = setTimeout(this.fadeTo.bind(this), stepDuration);
1694    },
1695
1696    changeOpacityBy: function(v) {
1697       var currentOpacity = this.getElementOpacity();
1698       var newOpacity = Math.max(0, Math.min(currentOpacity+v, 1));
1699       this.element.ricoOpacity = newOpacity;
1700
1701       this.element.style.filter = "alpha(opacity:"+Math.round(newOpacity*100)+")";
1702       this.element.style.opacity = newOpacity; /*//*/;
1703    },
1704
1705    isFinished: function() {
1706       return this.steps <= 0;
1707    },
1708
1709    getElementOpacity: function() {
1710       if ( this.element.ricoOpacity == undefined ) {
1711          var opacity = RicoUtil.getElementsComputedStyle(this.element, 'opacity');
1712          this.element.ricoOpacity = opacity != undefined ? opacity : 1.0;
1713       }
1714       return parseFloat(this.element.ricoOpacity);
1715    }
1716 }
1717
1718 Rico.Effect.AccordionSize = Class.create();
1719
1720 Rico.Effect.AccordionSize.prototype = {
1721
1722    initialize: function(e1, e2, start, end, duration, steps, options) {
1723       this.e1       = $(e1);
1724       this.e2       = $(e2);
1725       this.start    = start;
1726       this.end      = end;
1727       this.duration = duration;
1728       this.steps    = steps;
1729       this.options  = arguments[6] || {};
1730
1731       this.accordionSize();
1732    },
1733
1734    accordionSize: function() {
1735
1736       if (this.isFinished()) {
1737          // just in case there are round errors or such...
1738          this.e1.style.height = this.start + "px";
1739          this.e2.style.height = this.end + "px";
1740
1741          if(this.options.complete)
1742             this.options.complete(this);
1743          return;
1744       }
1745
1746       if (this.timer)
1747          clearTimeout(this.timer);
1748
1749       var stepDuration = Math.round(this.duration/this.steps) ;
1750
1751       var diff = this.steps > 0 ? (parseInt(this.e1.offsetHeight) - this.start)/this.steps : 0;
1752       this.resizeBy(diff);
1753
1754       this.duration -= stepDuration;
1755       this.steps--;
1756
1757       this.timer = setTimeout(this.accordionSize.bind(this), stepDuration);
1758    },
1759
1760    isFinished: function() {
1761       return this.steps <= 0;
1762    },
1763
1764    resizeBy: function(diff) {
1765       var h1Height = this.e1.offsetHeight;
1766       var h2Height = this.e2.offsetHeight;
1767       var intDiff = parseInt(diff);
1768       if ( diff != 0 ) {
1769          this.e1.style.height = (h1Height - intDiff) + "px";
1770          this.e2.style.height = (h2Height + intDiff) + "px";
1771       }
1772    }
1773
1774 };
1775
1776
1777 //-------------------- ricoLiveGrid.js
1778 // Rico.LiveGridMetaData -----------------------------------------------------
1779
1780 Rico.LiveGridMetaData = Class.create();
1781
1782 Rico.LiveGridMetaData.prototype = {
1783
1784    initialize: function( pageSize, totalRows, columnCount, options ) {
1785       this.pageSize  = pageSize;
1786       this.totalRows = totalRows;
1787       this.setOptions(options);
1788       this.ArrowHeight = 16;
1789       this.columnCount = columnCount;
1790    },
1791
1792    setOptions: function(options) {
1793       this.options = {
1794          largeBufferSize    : 7.0,   // 7 pages
1795          nearLimitFactor    : 0.2    // 20% of buffer
1796       };
1797       Object.extend(this.options, options || {});
1798    },
1799
1800    getPageSize: function() {
1801       return this.pageSize;
1802    },
1803
1804    getTotalRows: function() {
1805       return this.totalRows;
1806    },
1807
1808    setTotalRows: function(n) {
1809       this.totalRows = n;
1810    },
1811
1812    getLargeBufferSize: function() {
1813       return parseInt(this.options.largeBufferSize * this.pageSize);
1814    },
1815
1816    getLimitTolerance: function() {
1817       return parseInt(this.getLargeBufferSize() * this.options.nearLimitFactor);
1818    }
1819 };
1820
1821 // Rico.LiveGridScroller -----------------------------------------------------
1822
1823 Rico.LiveGridScroller = Class.create();
1824
1825 Rico.LiveGridScroller.prototype = {
1826
1827    initialize: function(liveGrid, viewPort) {
1828       this.isIE = navigator.userAgent.toLowerCase().indexOf("msie") >= 0;
1829       this.liveGrid = liveGrid;
1830       this.metaData = liveGrid.metaData;
1831       this.createScrollBar();
1832       this.scrollTimeout = null;
1833       this.lastScrollPos = 0;
1834       this.viewPort = viewPort;
1835       this.rows = new Array();
1836    },
1837
1838    isUnPlugged: function() {
1839       return this.scrollerDiv.onscroll == null;
1840    },
1841
1842    plugin: function() {
1843       this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
1844    },
1845
1846    unplug: function() {
1847       this.scrollerDiv.onscroll = null;
1848    },
1849
1850    sizeIEHeaderHack: function() {
1851       if ( !this.isIE ) return;
1852       var headerTable = $(this.liveGrid.tableId + "_header");
1853       if ( headerTable )
1854          headerTable.rows[0].cells[0].style.width =
1855             (headerTable.rows[0].cells[0].offsetWidth + 1) + "px";
1856    },
1857
1858    createScrollBar: function() {
1859       var visibleHeight = this.liveGrid.viewPort.visibleHeight();
1860       // create the outer div...
1861       this.scrollerDiv  = document.createElement("div");
1862       var scrollerStyle = this.scrollerDiv.style;
1863       scrollerStyle.borderRight = this.liveGrid.options.scrollerBorderRight;
1864       scrollerStyle.position    = "relative";
1865       scrollerStyle.left        = this.isIE ? "-6px" : "-3px";
1866       scrollerStyle.width       = "19px";
1867       scrollerStyle.height      = visibleHeight + "px";
1868       scrollerStyle.overflow    = "auto";
1869
1870       // create the inner div...
1871       this.heightDiv = document.createElement("div");
1872       this.heightDiv.style.width  = "1px";
1873
1874       this.heightDiv.style.height = parseInt(visibleHeight *
1875                         this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ;
1876       this.scrollerDiv.appendChild(this.heightDiv);
1877       this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
1878
1879      var table = this.liveGrid.table;
1880      table.parentNode.parentNode.insertBefore( this.scrollerDiv, table.parentNode.nextSibling );
1881           var eventName = this.isIE ? "mousewheel" : "DOMMouseScroll";
1882           Event.observe(table, eventName, 
1883                         function(evt) {
1884                            if (evt.wheelDelta>=0 || evt.detail < 0) //wheel-up
1885                               this.scrollerDiv.scrollTop -= (2*this.viewPort.rowHeight);
1886                            else
1887                               this.scrollerDiv.scrollTop += (2*this.viewPort.rowHeight);
1888                            this.handleScroll(false);
1889                         }.bindAsEventListener(this), 
1890                         false);
1891      },
1892
1893    updateSize: function() {
1894       var table = this.liveGrid.table;
1895       var visibleHeight = this.viewPort.visibleHeight();
1896       this.heightDiv.style.height = parseInt(visibleHeight *
1897                                   this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px";
1898    },
1899
1900    rowToPixel: function(rowOffset) {
1901       return (rowOffset / this.metaData.getTotalRows()) * this.heightDiv.offsetHeight
1902    },
1903    
1904    moveScroll: function(rowOffset) {
1905       this.scrollerDiv.scrollTop = this.rowToPixel(rowOffset);
1906       if ( this.metaData.options.onscroll )
1907          this.metaData.options.onscroll( this.liveGrid, rowOffset );
1908    },
1909
1910    handleScroll: function() {
1911      if ( this.scrollTimeout )
1912          clearTimeout( this.scrollTimeout );
1913
1914     var scrollDiff = this.lastScrollPos-this.scrollerDiv.scrollTop;
1915     if (scrollDiff != 0.00) {
1916        var r = this.scrollerDiv.scrollTop % this.viewPort.rowHeight;
1917        if (r != 0) {
1918           this.unplug();
1919           if ( scrollDiff < 0 ) {
1920              this.scrollerDiv.scrollTop += (this.viewPort.rowHeight-r);
1921           } else {
1922              this.scrollerDiv.scrollTop -= r;
1923           }
1924           this.plugin();
1925        }
1926     }
1927     var contentOffset = parseInt(this.scrollerDiv.scrollTop / this.viewPort.rowHeight);
1928     this.liveGrid.requestContentRefresh(contentOffset);
1929     this.viewPort.scrollTo(this.scrollerDiv.scrollTop);
1930
1931     if ( this.metaData.options.onscroll )
1932        this.metaData.options.onscroll( this.liveGrid, contentOffset );
1933
1934     this.scrollTimeout = setTimeout(this.scrollIdle.bind(this), 1200 );
1935     this.lastScrollPos = this.scrollerDiv.scrollTop;
1936
1937    },
1938
1939    scrollIdle: function() {
1940       if ( this.metaData.options.onscrollidle )
1941          this.metaData.options.onscrollidle();
1942    }
1943 };
1944
1945 // Rico.LiveGridBuffer -----------------------------------------------------
1946
1947 Rico.LiveGridBuffer = Class.create();
1948
1949 Rico.LiveGridBuffer.prototype = {
1950
1951    initialize: function(metaData, viewPort) {
1952       this.startPos = 0;
1953       this.size     = 0;
1954       this.metaData = metaData;
1955       this.rows     = new Array();
1956       this.updateInProgress = false;
1957       this.viewPort = viewPort;
1958       this.maxBufferSize = metaData.getLargeBufferSize() * 2;
1959       this.maxFetchSize = metaData.getLargeBufferSize();
1960       this.lastOffset = 0;
1961    },
1962
1963    getBlankRow: function() {
1964       if (!this.blankRow ) {
1965          this.blankRow = new Array();
1966          for ( var i=0; i < this.metaData.columnCount ; i++ ) 
1967             this.blankRow[i] = "&nbsp;";
1968      }
1969      return this.blankRow;
1970    },
1971
1972    loadRows: function(ajaxResponse) {
1973       var rowsElement = ajaxResponse.getElementsByTagName('rows')[0];
1974       this.updateUI = rowsElement.getAttribute("update_ui") == "true"
1975       var newRows = new Array()
1976       var trs = rowsElement.getElementsByTagName("tr");
1977       for ( var i=0 ; i < trs.length; i++ ) {
1978          var row = newRows[i] = new Array(); 
1979          var cells = trs[i].getElementsByTagName("td");
1980          for ( var j=0; j < cells.length ; j++ ) {
1981             var cell = cells[j];
1982             var convertSpaces = cell.getAttribute("convert_spaces") == "true";
1983             var cellContent = RicoUtil.getContentAsString(cell);
1984             row[j] = convertSpaces ? this.convertSpaces(cellContent) : cellContent;
1985             if (!row[j]) 
1986                row[j] = '&nbsp;';
1987          }
1988       }
1989       return newRows;
1990    },
1991       
1992    update: function(ajaxResponse, start) {
1993      var newRows = this.loadRows(ajaxResponse);
1994       if (this.rows.length == 0) { // initial load
1995          this.rows = newRows;
1996          this.size = this.rows.length;
1997          this.startPos = start;
1998          return;
1999       }
2000       if (start > this.startPos) { //appending
2001          if (this.startPos + this.rows.length < start) {
2002             this.rows =  newRows;
2003             this.startPos = start;//
2004          } else {
2005               this.rows = this.rows.concat( newRows.slice(0, newRows.length));
2006             if (this.rows.length > this.maxBufferSize) {
2007                var fullSize = this.rows.length;
2008                this.rows = this.rows.slice(this.rows.length - this.maxBufferSize, this.rows.length)
2009                this.startPos = this.startPos +  (fullSize - this.rows.length);
2010             }
2011          }
2012       } else { //prepending
2013          if (start + newRows.length < this.startPos) {
2014             this.rows =  newRows;
2015          } else {
2016             this.rows = newRows.slice(0, this.startPos).concat(this.rows);
2017             if (this.rows.length > this.maxBufferSize) 
2018                this.rows = this.rows.slice(0, this.maxBufferSize)
2019          }
2020          this.startPos =  start;
2021       }
2022       this.size = this.rows.length;
2023    },
2024    
2025    clear: function() {
2026       this.rows = new Array();
2027       this.startPos = 0;
2028       this.size = 0;
2029    },
2030
2031    isOverlapping: function(start, size) {
2032       return ((start < this.endPos()) && (this.startPos < start + size)) || (this.endPos() == 0)
2033    },
2034
2035    isInRange: function(position) {
2036       return (position >= this.startPos) && (position + this.metaData.getPageSize() <= this.endPos()); 
2037              //&& this.size()  != 0;
2038    },
2039
2040    isNearingTopLimit: function(position) {
2041       return position - this.startPos < this.metaData.getLimitTolerance();
2042    },
2043
2044    endPos: function() {
2045       return this.startPos + this.rows.length;
2046    },
2047    
2048    isNearingBottomLimit: function(position) {
2049       return this.endPos() - (position + this.metaData.getPageSize()) < this.metaData.getLimitTolerance();
2050    },
2051
2052    isAtTop: function() {
2053       return this.startPos == 0;
2054    },
2055
2056    isAtBottom: function() {
2057       return this.endPos() == this.metaData.getTotalRows();
2058    },
2059
2060    isNearingLimit: function(position) {
2061       return ( !this.isAtTop()    && this.isNearingTopLimit(position)) ||
2062              ( !this.isAtBottom() && this.isNearingBottomLimit(position) )
2063    },
2064
2065    getFetchSize: function(offset) {
2066       var adjustedOffset = this.getFetchOffset(offset);
2067       var adjustedSize = 0;
2068       if (adjustedOffset >= this.startPos) { //apending
2069          var endFetchOffset = this.maxFetchSize  + adjustedOffset;
2070          if (endFetchOffset > this.metaData.totalRows)
2071             endFetchOffset = this.metaData.totalRows;
2072          adjustedSize = endFetchOffset - adjustedOffset;  
2073                         if(adjustedOffset == 0 && adjustedSize < this.maxFetchSize){
2074                            adjustedSize = this.maxFetchSize;
2075                         }
2076       } else {//prepending
2077          var adjustedSize = this.startPos - adjustedOffset;
2078          if (adjustedSize > this.maxFetchSize)
2079             adjustedSize = this.maxFetchSize;
2080       }
2081       return adjustedSize;
2082    }, 
2083
2084    getFetchOffset: function(offset) {
2085       var adjustedOffset = offset;
2086       if (offset > this.startPos)  //apending
2087          adjustedOffset = (offset > this.endPos()) ? offset :  this.endPos(); 
2088       else { //prepending
2089          if (offset + this.maxFetchSize >= this.startPos) {
2090             var adjustedOffset = this.startPos - this.maxFetchSize;
2091             if (adjustedOffset < 0)
2092                adjustedOffset = 0;
2093          }
2094       }
2095       this.lastOffset = adjustedOffset;
2096       return adjustedOffset;
2097    },
2098
2099    getRows: function(start, count) {
2100       var begPos = start - this.startPos
2101       var endPos = begPos + count
2102
2103       // er? need more data...
2104       if ( endPos > this.size )
2105          endPos = this.size
2106
2107       var results = new Array()
2108       var index = 0;
2109       for ( var i=begPos ; i < endPos; i++ ) {
2110          results[index++] = this.rows[i]
2111       }
2112       return results
2113    },
2114
2115    convertSpaces: function(s) {
2116       return s.split(" ").join("&nbsp;");
2117    }
2118
2119 };
2120
2121
2122 //Rico.GridViewPort --------------------------------------------------
2123 Rico.GridViewPort = Class.create();
2124
2125 Rico.GridViewPort.prototype = {
2126
2127    initialize: function(table, rowHeight, visibleRows, buffer, liveGrid) {
2128       this.lastDisplayedStartPos = 0;
2129       this.div = table.parentNode;
2130       this.table = table
2131       this.rowHeight = rowHeight;
2132       this.div.style.height = (this.rowHeight * visibleRows) + "px";
2133       this.div.style.overflow = "hidden";
2134       this.buffer = buffer;
2135       this.liveGrid = liveGrid;
2136       this.visibleRows = visibleRows + 1;
2137       this.lastPixelOffset = 0;
2138       this.startPos = 0;
2139    },
2140
2141    populateRow: function(htmlRow, row) {
2142       for (var j=0; j < row.length; j++) {
2143          htmlRow.cells[j].innerHTML = row[j]
2144       }
2145    },
2146    
2147    bufferChanged: function() {
2148       this.refreshContents( parseInt(this.lastPixelOffset / this.rowHeight));
2149    },
2150    
2151    clearRows: function() {
2152       if (!this.isBlank) {
2153          this.liveGrid.table.className = this.liveGrid.options.loadingClass;
2154          for (var i=0; i < this.visibleRows; i++)
2155             this.populateRow(this.table.rows[i], this.buffer.getBlankRow());
2156          this.isBlank = true;
2157       }
2158    },
2159    
2160    clearContents: function() {   
2161       this.clearRows();
2162       this.scrollTo(0);
2163       this.startPos = 0;
2164       this.lastStartPos = -1;   
2165    },
2166    
2167    refreshContents: function(startPos) {
2168       if (startPos == this.lastRowPos && !this.isPartialBlank && !this.isBlank) {
2169          return;
2170       }
2171       if ((startPos + this.visibleRows < this.buffer.startPos)  
2172           || (this.buffer.startPos + this.buffer.size < startPos) 
2173           || (this.buffer.size == 0)) {
2174          this.clearRows();
2175          return;
2176       }
2177       this.isBlank = false;
2178       var viewPrecedesBuffer = this.buffer.startPos > startPos
2179       var contentStartPos = viewPrecedesBuffer ? this.buffer.startPos: startPos; 
2180       var contentEndPos = (this.buffer.startPos + this.buffer.size < startPos + this.visibleRows) 
2181                                  ? this.buffer.startPos + this.buffer.size
2182                                  : startPos + this.visibleRows;
2183       var rowSize = contentEndPos - contentStartPos;
2184       var rows = this.buffer.getRows(contentStartPos, rowSize ); 
2185       var blankSize = this.visibleRows - rowSize;
2186       var blankOffset = viewPrecedesBuffer ? 0: rowSize;
2187       var contentOffset = viewPrecedesBuffer ? blankSize: 0;
2188
2189       for (var i=0; i < rows.length; i++) {//initialize what we have
2190         this.populateRow(this.table.rows[i + contentOffset], rows[i]);
2191       }
2192       for (var i=0; i < blankSize; i++) {// blank out the rest 
2193         this.populateRow(this.table.rows[i + blankOffset], this.buffer.getBlankRow());
2194       }
2195       this.isPartialBlank = blankSize > 0;
2196       this.lastRowPos = startPos;
2197
2198        this.liveGrid.table.className = this.liveGrid.options.tableClass;
2199        // Check if user has set a onRefreshComplete function
2200        var onRefreshComplete = this.liveGrid.options.onRefreshComplete;
2201        if (onRefreshComplete != null)
2202            onRefreshComplete();
2203    },
2204
2205    scrollTo: function(pixelOffset) {      
2206       if (this.lastPixelOffset == pixelOffset)
2207          return;
2208
2209       this.refreshContents(parseInt(pixelOffset / this.rowHeight))
2210       this.div.scrollTop = pixelOffset % this.rowHeight        
2211       
2212       this.lastPixelOffset = pixelOffset;
2213    },
2214    
2215    visibleHeight: function() {
2216       return parseInt(RicoUtil.getElementsComputedStyle(this.div, 'height'));
2217    }
2218
2219 };
2220
2221
2222 Rico.LiveGridRequest = Class.create();
2223 Rico.LiveGridRequest.prototype = {
2224    initialize: function( requestOffset, options ) {
2225       this.requestOffset = requestOffset;
2226    }
2227 };
2228
2229 // Rico.LiveGrid -----------------------------------------------------
2230
2231 Rico.LiveGrid = Class.create();
2232
2233 Rico.LiveGrid.prototype = {
2234
2235    initialize: function( tableId, visibleRows, totalRows, url, options, ajaxOptions ) {
2236
2237      this.options = {
2238                 tableClass:           $(tableId).className,
2239                 loadingClass:         $(tableId).className,
2240                 scrollerBorderRight: '1px solid #ababab',
2241                 bufferTimeout:        20000,
2242                 sortAscendImg:        'images/sort_asc.gif',
2243                 sortDescendImg:       'images/sort_desc.gif',
2244                 sortImageWidth:       9,
2245                 sortImageHeight:      5,
2246                 ajaxSortURLParms:     [],
2247                 onRefreshComplete:    null,
2248                 requestParameters:    null,
2249                 inlineStyles:         true
2250                 };
2251       Object.extend(this.options, options || {});
2252
2253       this.ajaxOptions = {parameters: null};
2254       Object.extend(this.ajaxOptions, ajaxOptions || {});
2255
2256       this.tableId     = tableId; 
2257       this.table       = $(tableId);
2258
2259       this.addLiveGridHtml();
2260
2261       var columnCount  = this.table.rows[0].cells.length;
2262       this.metaData    = new Rico.LiveGridMetaData(visibleRows, totalRows, columnCount, options);
2263       this.buffer      = new Rico.LiveGridBuffer(this.metaData);
2264
2265       var rowCount = this.table.rows.length;
2266       this.viewPort =  new Rico.GridViewPort(this.table, 
2267                                             this.table.offsetHeight/rowCount,
2268                                             visibleRows,
2269                                             this.buffer, this);
2270       this.scroller    = new Rico.LiveGridScroller(this,this.viewPort);
2271       this.options.sortHandler = this.sortHandler.bind(this);
2272
2273       if ( $(tableId + '_header') )
2274          this.sort = new Rico.LiveGridSort(tableId + '_header', this.options)
2275
2276       this.processingRequest = null;
2277       this.unprocessedRequest = null;
2278
2279       this.initAjax(url);
2280       if ( this.options.prefetchBuffer || this.options.prefetchOffset > 0) {
2281          var offset = 0;
2282          if (this.options.offset ) {
2283             offset = this.options.offset;            
2284             this.scroller.moveScroll(offset);
2285             this.viewPort.scrollTo(this.scroller.rowToPixel(offset));            
2286          }
2287          if (this.options.sortCol) {
2288              this.sortCol = options.sortCol;
2289              this.sortDir = options.sortDir;
2290          }
2291          this.requestContentRefresh(offset);
2292       }
2293    },
2294
2295    addLiveGridHtml: function() {
2296      // Check to see if need to create a header table.
2297      if (this.table.getElementsByTagName("thead").length > 0){
2298        // Create Table this.tableId+'_header'
2299        var tableHeader = this.table.cloneNode(true);
2300        tableHeader.setAttribute('id', this.tableId+'_header');
2301        tableHeader.setAttribute('class', this.table.className+'_header');
2302
2303        // Clean up and insert
2304        for( var i = 0; i < tableHeader.tBodies.length; i++ ) 
2305        tableHeader.removeChild(tableHeader.tBodies[i]);
2306        this.table.deleteTHead();
2307        this.table.parentNode.insertBefore(tableHeader,this.table);
2308      }
2309
2310     new Insertion.Before(this.table, "<div id='"+this.tableId+"_container'></div>");
2311     this.table.previousSibling.appendChild(this.table);
2312     new Insertion.Before(this.table,"<div id='"+this.tableId+"_viewport' style='float:left;'></div>");
2313     this.table.previousSibling.appendChild(this.table);
2314    },
2315
2316
2317    resetContents: function() {
2318       this.scroller.moveScroll(0);
2319       this.buffer.clear();
2320       this.viewPort.clearContents();
2321    },
2322    
2323    sortHandler: function(column) {
2324            if(!column) return ;
2325       this.sortCol = column.name;
2326       this.sortDir = column.currentSort;
2327
2328       this.resetContents();
2329       this.requestContentRefresh(0) 
2330    },
2331
2332    adjustRowSize: function() {
2333           
2334         },
2335         
2336    setTotalRows: function( newTotalRows ) {
2337       this.resetContents();
2338       this.metaData.setTotalRows(newTotalRows);
2339       this.scroller.updateSize();
2340    },
2341
2342    initAjax: function(url) {
2343       ajaxEngine.registerRequest( this.tableId + '_request', url );
2344       ajaxEngine.registerAjaxObject( this.tableId + '_updater', this );
2345    },
2346
2347    invokeAjax: function() {
2348    },
2349
2350    handleTimedOut: function() {
2351       //server did not respond in 4 seconds... assume that there could have been
2352       //an error or something, and allow requests to be processed again...
2353       this.processingRequest = null;
2354       this.processQueuedRequest();
2355    },
2356
2357    fetchBuffer: function(offset) {
2358       if ( this.buffer.isInRange(offset) &&
2359          !this.buffer.isNearingLimit(offset)) {
2360          return;
2361          }
2362       if (this.processingRequest) {
2363           this.unprocessedRequest = new Rico.LiveGridRequest(offset);
2364          return;
2365       }
2366       var bufferStartPos = this.buffer.getFetchOffset(offset);
2367       this.processingRequest = new Rico.LiveGridRequest(offset);
2368       this.processingRequest.bufferOffset = bufferStartPos;   
2369       var fetchSize = this.buffer.getFetchSize(offset);
2370       var partialLoaded = false;
2371       
2372       var queryString
2373       if (this.options.requestParameters)
2374          queryString = this._createQueryString(this.options.requestParameters, 0);
2375
2376         queryString = (queryString == null) ? '' : queryString+'&';
2377         queryString  = queryString+'id='+this.tableId+'&page_size='+fetchSize+'&offset='+bufferStartPos;
2378         if (this.sortCol)
2379             queryString = queryString+'&sort_col='+escape(this.sortCol)+'&sort_dir='+this.sortDir;
2380
2381         this.ajaxOptions.parameters = queryString;
2382
2383        ajaxEngine.sendRequest( this.tableId + '_request', this.ajaxOptions );
2384
2385        this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), this.options.bufferTimeout);
2386
2387    },
2388
2389    setRequestParams: function() {
2390       this.options.requestParameters = [];
2391       for ( var i=0 ; i < arguments.length ; i++ )
2392          this.options.requestParameters[i] = arguments[i];
2393    },
2394
2395    requestContentRefresh: function(contentOffset) {
2396       this.fetchBuffer(contentOffset);
2397    },
2398
2399    ajaxUpdate: function(ajaxResponse) {
2400       try {
2401          clearTimeout( this.timeoutHandler );
2402          this.buffer.update(ajaxResponse,this.processingRequest.bufferOffset);
2403          this.viewPort.bufferChanged();
2404       }
2405       catch(err) {}
2406       finally {this.processingRequest = null; }
2407       this.processQueuedRequest();
2408    },
2409
2410    _createQueryString: function( theArgs, offset ) {
2411       var queryString = ""
2412       if (!theArgs)
2413           return queryString;
2414
2415       for ( var i = offset ; i < theArgs.length ; i++ ) {
2416           if ( i != offset )
2417             queryString += "&";
2418
2419           var anArg = theArgs[i];
2420
2421           if ( anArg.name != undefined && anArg.value != undefined ) {
2422             queryString += anArg.name +  "=" + escape(anArg.value);
2423           }
2424           else {
2425              var ePos  = anArg.indexOf('=');
2426              var argName  = anArg.substring( 0, ePos );
2427              var argValue = anArg.substring( ePos + 1 );
2428              queryString += argName + "=" + escape(argValue);
2429           }
2430       }
2431       return queryString;
2432    },
2433
2434    processQueuedRequest: function() {
2435       if (this.unprocessedRequest != null) {
2436          this.requestContentRefresh(this.unprocessedRequest.requestOffset);
2437          this.unprocessedRequest = null
2438       }
2439    }
2440 };
2441
2442
2443 //-------------------- ricoLiveGridSort.js
2444 Rico.LiveGridSort = Class.create();
2445
2446 Rico.LiveGridSort.prototype = {
2447
2448    initialize: function(headerTableId, options) {
2449       this.headerTableId = headerTableId;
2450       this.headerTable   = $(headerTableId);
2451       this.options = options;
2452       this.setOptions();
2453       this.applySortBehavior();
2454
2455       if ( this.options.sortCol ) {
2456          this.setSortUI( this.options.sortCol, this.options.sortDir );
2457       }
2458    },
2459
2460    setSortUI: function( columnName, sortDirection ) {
2461       var cols = this.options.columns;
2462       for ( var i = 0 ; i < cols.length ; i++ ) {
2463          if ( cols[i].name == columnName ) {
2464             this.setColumnSort(i, sortDirection);
2465             break;
2466          }
2467       }
2468    },
2469
2470    setOptions: function() {
2471       // preload the images...
2472       new Image().src = this.options.sortAscendImg;
2473       new Image().src = this.options.sortDescendImg;
2474
2475       this.sort = this.options.sortHandler;
2476       if ( !this.options.columns )
2477          this.options.columns = this.introspectForColumnInfo();
2478       else {
2479          // allow client to pass { columns: [ ["a", true], ["b", false] ] }
2480          // and convert to an array of Rico.TableColumn objs...
2481          this.options.columns = this.convertToTableColumns(this.options.columns);
2482       }
2483    },
2484
2485    applySortBehavior: function() {
2486       var headerRow   = this.headerTable.rows[0];
2487       var headerCells = headerRow.cells;
2488       for ( var i = 0 ; i < headerCells.length ; i++ ) {
2489          this.addSortBehaviorToColumn( i, headerCells[i] );
2490       }
2491    },
2492
2493    addSortBehaviorToColumn: function( n, cell ) {
2494       if ( this.options.columns[n].isSortable() ) {
2495          cell.id            = this.headerTableId + '_' + n;
2496          cell.style.cursor  = 'pointer';
2497          cell.onclick       = this.headerCellClicked.bindAsEventListener(this);
2498          cell.innerHTML     = cell.innerHTML + '<span id="' + this.headerTableId + '_img_' + n + '">'
2499                            + '&nbsp;&nbsp;&nbsp;</span>';
2500       }
2501    },
2502
2503    // event handler....
2504    headerCellClicked: function(evt) {
2505       var eventTarget = evt.target ? evt.target : evt.srcElement;
2506       var cellId = eventTarget.id;
2507       var columnNumber = parseInt(cellId.substring( cellId.lastIndexOf('_') + 1 ));
2508       var sortedColumnIndex = this.getSortedColumnIndex();
2509       if ( sortedColumnIndex != -1 ) {
2510          if ( sortedColumnIndex != columnNumber ) {
2511             this.removeColumnSort(sortedColumnIndex);
2512             this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
2513          }
2514          else
2515             this.toggleColumnSort(sortedColumnIndex);
2516       }
2517       else
2518          this.setColumnSort(columnNumber, Rico.TableColumn.SORT_ASC);
2519
2520       if (this.options.sortHandler) {
2521          this.options.sortHandler(this.options.columns[columnNumber]);
2522       }
2523    },
2524
2525    removeColumnSort: function(n) {
2526       this.options.columns[n].setUnsorted();
2527       this.setSortImage(n);
2528    },
2529
2530    setColumnSort: function(n, direction) {
2531         if(isNaN(n)) return ;
2532       this.options.columns[n].setSorted(direction);
2533       this.setSortImage(n);
2534    },
2535
2536    toggleColumnSort: function(n) {
2537       this.options.columns[n].toggleSort();
2538       this.setSortImage(n);
2539    },
2540
2541    setSortImage: function(n) {
2542       var sortDirection = this.options.columns[n].getSortDirection();
2543
2544       var sortImageSpan = $( this.headerTableId + '_img_' + n );
2545       if ( sortDirection == Rico.TableColumn.UNSORTED )
2546          sortImageSpan.innerHTML = '&nbsp;&nbsp;';
2547       else if ( sortDirection == Rico.TableColumn.SORT_ASC )
2548          sortImageSpan.innerHTML = '&nbsp;&nbsp;<img width="'  + this.options.sortImageWidth    + '" ' +
2549                                                      'height="'+ this.options.sortImageHeight   + '" ' +
2550                                                      'src="'   + this.options.sortAscendImg + '"/>';
2551       else if ( sortDirection == Rico.TableColumn.SORT_DESC )
2552          sortImageSpan.innerHTML = '&nbsp;&nbsp;<img width="'  + this.options.sortImageWidth    + '" ' +
2553                                                      'height="'+ this.options.sortImageHeight   + '" ' +
2554                                                      'src="'   + this.options.sortDescendImg + '"/>';
2555    },
2556
2557    getSortedColumnIndex: function() {
2558       var cols = this.options.columns;
2559       for ( var i = 0 ; i < cols.length ; i++ ) {
2560          if ( cols[i].isSorted() )
2561             return i;
2562       }
2563
2564       return -1;
2565    },
2566
2567    introspectForColumnInfo: function() {
2568       var columns = new Array();
2569       var headerRow   = this.headerTable.rows[0];
2570       var headerCells = headerRow.cells;
2571       for ( var i = 0 ; i < headerCells.length ; i++ )
2572          columns.push( new Rico.TableColumn( this.deriveColumnNameFromCell(headerCells[i],i), true ) );
2573       return columns;
2574    },
2575
2576    convertToTableColumns: function(cols) {
2577       var columns = new Array();
2578       for ( var i = 0 ; i < cols.length ; i++ )
2579          columns.push( new Rico.TableColumn( cols[i][0], cols[i][1] ) );
2580       return columns;
2581    },
2582
2583    deriveColumnNameFromCell: function(cell,columnNumber) {
2584       var cellContent = cell.innerText != undefined ? cell.innerText : cell.textContent;
2585       return cellContent ? cellContent.toLowerCase().split(' ').join('_') : "col_" + columnNumber;
2586    }
2587 };
2588
2589 Rico.TableColumn = Class.create();
2590
2591 Rico.TableColumn.UNSORTED  = 0;
2592 Rico.TableColumn.SORT_ASC  = "ASC";
2593 Rico.TableColumn.SORT_DESC = "DESC";
2594
2595 Rico.TableColumn.prototype = {
2596    initialize: function(name, sortable) {
2597       this.name        = name;
2598       this.sortable    = sortable;
2599       this.currentSort = Rico.TableColumn.UNSORTED;
2600    },
2601
2602    isSortable: function() {
2603       return this.sortable;
2604    },
2605
2606    isSorted: function() {
2607       return this.currentSort != Rico.TableColumn.UNSORTED;
2608    },
2609
2610    getSortDirection: function() {
2611       return this.currentSort;
2612    },
2613
2614    toggleSort: function() {
2615       if ( this.currentSort == Rico.TableColumn.UNSORTED || this.currentSort == Rico.TableColumn.SORT_DESC )
2616          this.currentSort = Rico.TableColumn.SORT_ASC;
2617       else if ( this.currentSort == Rico.TableColumn.SORT_ASC )
2618          this.currentSort = Rico.TableColumn.SORT_DESC;
2619    },
2620
2621    setUnsorted: function(direction) {
2622       this.setSorted(Rico.TableColumn.UNSORTED);
2623    },
2624
2625    setSorted: function(direction) {
2626       // direction must by one of Rico.TableColumn.UNSORTED, .SORT_ASC, or .SORT_DESC...
2627       this.currentSort = direction;
2628    }
2629
2630 };
2631
2632
2633 //-------------------- ricoUtil.js
2634 var RicoUtil = {
2635
2636    getElementsComputedStyle: function ( htmlElement, cssProperty, mozillaEquivalentCSS) {
2637       if ( arguments.length == 2 )
2638          mozillaEquivalentCSS = cssProperty;
2639
2640       var el = $(htmlElement);
2641       if ( el.currentStyle )
2642          return el.currentStyle[cssProperty];
2643       else
2644          return document.defaultView.getComputedStyle(el, null).getPropertyValue(mozillaEquivalentCSS);
2645    },
2646
2647    createXmlDocument : function() {
2648       if (document.implementation && document.implementation.createDocument) {
2649          var doc = document.implementation.createDocument("", "", null);
2650
2651          if (doc.readyState == null) {
2652             doc.readyState = 1;
2653             doc.addEventListener("load", function () {
2654                doc.readyState = 4;
2655                if (typeof doc.onreadystatechange == "function")
2656                   doc.onreadystatechange();
2657             }, false);
2658          }
2659
2660          return doc;
2661       }
2662
2663       if (window.ActiveXObject)
2664           return Try.these(
2665             function() { return new ActiveXObject('MSXML2.DomDocument')   },
2666             function() { return new ActiveXObject('Microsoft.DomDocument')},
2667             function() { return new ActiveXObject('MSXML.DomDocument')    },
2668             function() { return new ActiveXObject('MSXML3.DomDocument')   }
2669           ) || false;
2670
2671       return null;
2672    },
2673
2674    getContentAsString: function( parentNode ) {
2675       return parentNode.xml != undefined ? 
2676          this._getContentAsStringIE(parentNode) :
2677          this._getContentAsStringMozilla(parentNode);
2678    },
2679
2680   _getContentAsStringIE: function(parentNode) {
2681      var contentStr = "";
2682      for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
2683          var n = parentNode.childNodes[i];
2684          if (n.nodeType == 4) {
2685              contentStr += n.nodeValue;
2686          }
2687          else {
2688            contentStr += n.xml;
2689        }
2690      }
2691      return contentStr;
2692   },
2693
2694   _getContentAsStringMozilla: function(parentNode) {
2695      var xmlSerializer = new XMLSerializer();
2696      var contentStr = "";
2697      for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) {
2698           var n = parentNode.childNodes[i];
2699           if (n.nodeType == 4) { // CDATA node
2700               contentStr += n.nodeValue;
2701           }
2702           else {
2703             contentStr += xmlSerializer.serializeToString(n);
2704         }
2705      }
2706      return contentStr;
2707   },
2708
2709    toViewportPosition: function(element) {
2710       return this._toAbsolute(element,true);
2711    },
2712
2713    toDocumentPosition: function(element) {
2714       return this._toAbsolute(element,false);
2715    },
2716
2717    /**
2718     *  Compute the elements position in terms of the window viewport
2719     *  so that it can be compared to the position of the mouse (dnd)
2720     *  This is additions of all the offsetTop,offsetLeft values up the
2721     *  offsetParent hierarchy, ...taking into account any scrollTop,
2722     *  scrollLeft values along the way...
2723     *
2724     * IE has a bug reporting a correct offsetLeft of elements within a
2725     * a relatively positioned parent!!!
2726     **/
2727    _toAbsolute: function(element,accountForDocScroll) {
2728
2729       if ( navigator.userAgent.toLowerCase().indexOf("msie") == -1 )
2730          return this._toAbsoluteMozilla(element,accountForDocScroll);
2731
2732       var x = 0;
2733       var y = 0;
2734       var parent = element;
2735       while ( parent ) {
2736
2737          var borderXOffset = 0;
2738          var borderYOffset = 0;
2739          if ( parent != element ) {
2740             var borderXOffset = parseInt(this.getElementsComputedStyle(parent, "borderLeftWidth" ));
2741             var borderYOffset = parseInt(this.getElementsComputedStyle(parent, "borderTopWidth" ));
2742             borderXOffset = isNaN(borderXOffset) ? 0 : borderXOffset;
2743             borderYOffset = isNaN(borderYOffset) ? 0 : borderYOffset;
2744          }
2745
2746          x += parent.offsetLeft - parent.scrollLeft + borderXOffset;
2747          y += parent.offsetTop - parent.scrollTop + borderYOffset;
2748          parent = parent.offsetParent;
2749       }
2750
2751       if ( accountForDocScroll ) {
2752          x -= this.docScrollLeft();
2753          y -= this.docScrollTop();
2754       }
2755
2756       return { x:x, y:y };
2757    },
2758
2759    /**
2760     *  Mozilla did not report all of the parents up the hierarchy via the
2761     *  offsetParent property that IE did.  So for the calculation of the
2762     *  offsets we use the offsetParent property, but for the calculation of
2763     *  the scrollTop/scrollLeft adjustments we navigate up via the parentNode
2764     *  property instead so as to get the scroll offsets...
2765     *
2766     **/
2767    _toAbsoluteMozilla: function(element,accountForDocScroll) {
2768       var x = 0;
2769       var y = 0;
2770       var parent = element;
2771       while ( parent ) {
2772          x += parent.offsetLeft;
2773          y += parent.offsetTop;
2774          parent = parent.offsetParent;
2775       }
2776
2777       parent = element;
2778       while ( parent &&
2779               parent != document.body &&
2780               parent != document.documentElement ) {
2781          if ( parent.scrollLeft  )
2782             x -= parent.scrollLeft;
2783          if ( parent.scrollTop )
2784             y -= parent.scrollTop;
2785          parent = parent.parentNode;
2786       }
2787
2788       if ( accountForDocScroll ) {
2789          x -= this.docScrollLeft();
2790          y -= this.docScrollTop();
2791       }
2792
2793       return { x:x, y:y };
2794    },
2795
2796    docScrollLeft: function() {
2797       if ( window.pageXOffset )
2798          return window.pageXOffset;
2799       else if ( document.documentElement && document.documentElement.scrollLeft )
2800          return document.documentElement.scrollLeft;
2801       else if ( document.body )
2802          return document.body.scrollLeft;
2803       else
2804          return 0;
2805    },
2806
2807    docScrollTop: function() {
2808       if ( window.pageYOffset )
2809          return window.pageYOffset;
2810       else if ( document.documentElement && document.documentElement.scrollTop )
2811          return document.documentElement.scrollTop;
2812       else if ( document.body )
2813          return document.body.scrollTop;
2814       else
2815          return 0;
2816    }
2817
2818 };