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