2 * (c) 2005-2009 Richard Cowin (http://openrico.org)
3 * (c) 2005-2009 Matt Brown (http://dowdybrown.com)
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
6 * file except in compliance with the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software distributed under the
11 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12 * either express or implied. See the License for the specific language governing permissions
13 * and limitations under the License.
16 Rico.applyShadow = function(elem,shadowFlag) {
17 if (typeof shadowFlag=='undefined') shadowFlag=true;
18 var tab = document.createElement("table");
22 tab.style.margin='0px';
23 tab.style.width='auto';
24 var tbody=Rico.getTBody(tab);
25 var r=tbody.insertRow(-1);
26 var c0=r.insertCell(-1);
27 c0.style.padding='0px';
29 if (Rico.isIE && Rico.ieVersion < 7) {
30 tab.style.filter="progid:DXImageTransform.Microsoft.Shadow(color=#666666,direction=130)";
32 var c=r.insertCell(-1);
34 c.style.padding='0px';
35 c.style.background="transparent url("+Rico.imgDir+"shadow_r.png) no-repeat 0px 3px";
36 r=tbody.insertRow(-1);
39 c.style.padding='0px';
40 c.innerHTML="<div style='height: 8px; font-size: 1px; position: relative;'><div style='height: 8px; width: 8px; position: absolute; top: 0px; left: 5px; background: transparent url("+Rico.imgDir+"shadow_ll.png) no-repeat top right;'></div><div style='height: 8px; margin: 0px 8px 0px 13px; background: transparent url("+Rico.imgDir+"shadow_l.png) repeat-x;'></div><div style='height: 8px; width: 8px; position: absolute; top: 0px; right: 0px; background: transparent url("+Rico.imgDir+"shadow_lr.png) no-repeat top left;'></div></div>";
43 if (elem && elem.parentNode) {
44 elem.parentNode.replaceChild(tab, elem);
50 Rico.Popup = function(containerDiv,options) {
51 this.initialize(containerDiv,options);
54 Rico.Popup.prototype = {
56 * @class Class to manage pop-up div windows.
58 * @param options object may contain any of the following:<dl>
59 * <dt>hideOnEscape</dt><dd> hide popup when escape key is pressed? default=true</dd>
60 * <dt>hideOnClick </dt><dd> hide popup when mouse button is clicked? default=true</dd>
61 * <dt>ignoreClicks</dt><dd> if true, mouse clicks within the popup are not allowed to bubble up to parent elements</dd>
62 * <dt>position </dt><dd> defaults to absolute, use "auto" to auto-detect</dd>
63 * <dt>shadow </dt><dd> display shadow with popup? default=true</dd>
64 * <dt>zIndex </dt><dd> which layer? default=1</dd>
65 * <dt>canDrag </dt><dd> boolean value (or function that returns a boolean) indicating if it is ok to drag/reposition popup, default=false</dd>
66 * <dt>onClose </dt><dd> function to call when the popup is closed</dd>
68 * @param containerDiv if supplied, then setDiv() is called at the end of initialization
70 initialize: function(containerDiv,options) {
75 position : 'absolute',
82 if (containerDiv) this.setDiv(containerDiv,options);
85 createContainer: function(options) {
86 this.setDiv(document.createElement('div'), options);
87 if (options && options.parent) {
88 options.parent.appendChild(this.container);
90 document.getElementsByTagName("body")[0].appendChild(this.container);
95 * Apply popup behavior to a div that already exists in the DOM
96 * @param containerDiv div element (or element id) in the DOM. If null, then the div is created automatically.
98 setDiv: function(containerDiv,options) {
99 Rico.extend(this.options, options || {});
100 this.container=Rico.$(containerDiv);
101 if (this.options.position == 'auto') {
102 this.position=Rico.getStyle(this.container,'position').toLowerCase();
104 this.position=this.container.style.position=this.options.position;
106 if (this.position != 'absolute') {
107 this.content=this.container;
110 if (this.options.zIndex >= 0) this.container.style.zIndex=this.options.zIndex;
111 this.closeFunc=this.options.closeFunc || Rico.bind(this,'closePopup');
112 //this.container.style.overflow='hidden';
113 this.container.style.top='0px';
114 this.container.style.left='0px';
115 this.container.style.display='none';
117 var tab=Rico.applyShadow(null,this.options.shadow);
118 tab.style.position='relative';
119 this.contentCell=tab.rows[0].cells[0];
121 if (Rico.isIE && Rico.ieVersion < 7) {
122 // create iframe shim
123 this.ifr = document.createElement('iframe');
124 this.ifr.style.position="absolute";
125 this.ifr.style.top = '0px';
126 this.ifr.style.left = '0px';
127 this.ifr.style.width = '2000px';
128 this.ifr.style.height = '2000px';
129 this.ifr.style.zIndex = -1;
130 this.ifr.frameBorder = 0;
131 this.ifr.src="javascript:false;";
132 this.contentCell.appendChild(this.ifr);
134 this.content=this.contentCell.appendChild(document.createElement('div'));
135 this.content.className='RicoPopupContent';
137 while (this.container.firstChild) {
138 this.content.appendChild(this.container.firstChild);
140 this.container.appendChild(tab);
142 if (this.options.hideOnClick)
143 Rico.eventBind(document,"click", Rico.eventHandle(this,'_docClick'));
144 if (this.options.hideOnEscape)
145 Rico.eventBind(document,"keyup", Rico.eventHandle(this,'_checkKey'));
146 this.dragEnabled=false;
147 this.mousedownHandler = Rico.eventHandle(this,'_startDrag');
148 this.dragHandler = Rico.eventHandle(this,'_drag');
149 this.dropHandler = Rico.eventHandle(this,'_endDrag');
150 if (this.options.canDrag) this.enableDragging();
151 if (this.options.ignoreClicks || this.options.canDrag) this.ignoreClicks();
154 clearContent: function() {
155 this.content.innerHTML="";
158 setContent: function(content) {
159 this.content.innerHTML=content;
162 enableDragging: function() {
163 if (!this.dragEnabled && this.options.dragElement) {
164 Rico.eventBind(this.options.dragElement, "mousedown", this.mousedownHandler);
165 this.dragEnabled=true;
167 return this.dragEnabled;
170 disableDragging: function() {
171 if (!this.dragEnabled) return;
172 Rico.eventUnbind(this.options.dragElement, "mousedown", this.mousedownHandler);
173 this.dragEnabled=false;
176 setZ: function(zIndex) {
177 this.container.style.zIndex=zIndex;
181 ignoreClicks: function() {
182 Rico.eventBind(this.container,"click", Rico.eventHandle(this,'_ignoreClick'));
185 _ignoreClick: function(e) {
186 if (e.stopPropagation)
189 e.cancelBubble = true;
193 // event handler to process keyup events (hide menu on escape key)
194 _checkKey: function(e) {
195 if (Rico.eventKey(e)!=27 || !this.visible()) return true;
196 //alert('closing popup: '+this.container.className);
202 _docClick: function(e) {
208 * Move popup to specified position
210 move: function(left,top) {
211 if (typeof left=='number') this.container.style.left=left+'px';
212 if (typeof top=='number') this.container.style.top=top+'px';
215 _startDrag : function(event){
216 var elem=Rico.eventElement(event);
217 this.container.style.cursor='move';
218 this.lastMouse = Rico.eventClient(event);
219 Rico.eventBind(document, "mousemove", this.dragHandler);
220 Rico.eventBind(document, "mouseup", this.dropHandler);
221 Rico.eventStop(event);
224 _drag : function(event){
225 var newMouse = Rico.eventClient(event);
226 var newLeft = parseInt(this.container.style.left,10) + newMouse.x - this.lastMouse.x;
227 var newTop = parseInt(this.container.style.top,10) + newMouse.y - this.lastMouse.y;
228 this.move(newLeft, newTop);
229 this.lastMouse = newMouse;
230 Rico.eventStop(event);
233 _endDrag : function(){
234 this.container.style.cursor='';
235 Rico.eventUnbind(document, "mousemove", this.dragHandler);
236 Rico.eventUnbind(document, "mouseup", this.dropHandler);
240 * Display popup at specified position
242 openPopup: function(left,top) {
243 this.container.style.display=this.position=='absolute' ? "block" : Rico.isIE && Rico.ieVersion<8 ? "inline" : "inline-block";
244 if (typeof left=='number') this.container.style.left=left+'px';
245 if (typeof top=='number') this.container.style.top=top+'px';
246 if (this.container.id) Rico.log('openPopup '+this.container.id+' at '+left+','+top);
249 centerPopup: function() {
251 var msgWidth=this.container.offsetWidth;
252 var msgHeight=this.container.offsetHeight;
253 var divwi=this.container.parentNode.offsetWidth;
254 var divht=this.container.parentNode.offsetHeight;
255 this.move(parseInt((divwi-msgWidth)/2,10), parseInt((divht-msgHeight)/2,10));
258 visible: function() {
259 return Rico.visible(this.container);
265 closePopup: function() {
266 if (!Rico.visible(this.container)) return;
267 if (this.container.id) Rico.log('closePopup '+this.container.id);
268 if (this.dragEnabled) this._endDrag();
269 this.container.style.display="none";
270 if (this.options.onClose) this.options.onClose();
275 Rico.closeButton = function(handle) {
276 var a = document.createElement('a');
277 a.className='RicoCloseAnchor';
278 if (Rico.theme.closeAnchor) Rico.addClass(a,Rico.theme.closeAnchor);
279 var span = a.appendChild(document.createElement('span'));
280 span.title=Rico.getPhraseById('close');
281 new Rico.HoverSet([a]);
282 Rico.addClass(span,Rico.theme.close || 'RicoClose');
283 Rico.eventBind(a,"click", handle);
287 Rico.floatButton = function(buttonName, handle, title) {
288 var a=document.createElement("a");
289 a.className='RicoButtonAnchor'
290 Rico.addClass(a,Rico.theme.buttonAnchor || 'RicoButtonAnchorNative');
291 var span=a.appendChild(document.createElement("span"));
292 if (title) span.title=title;
293 span.className=Rico.theme[buttonName.toLowerCase()] || 'Rico'+buttonName;
294 Rico.eventBind(a,"click", handle, false);
295 new Rico.HoverSet([a]);
299 Rico.clearButton = function(handle) {
300 var span=document.createElement("span");
301 span.title=Rico.getPhraseById('clear');
302 span.className='ricoClear';
303 Rico.addClass(span, Rico.theme.clear || 'ricoClearNative');
304 span.style.display='inline-block';
305 span.style.cursor='pointer';
306 Rico.eventBind(span,"click", handle);
310 Rico.Window = function(title, options, contentParam) {
311 this.initialize(title, options, contentParam);
314 Rico.Window.prototype = {
317 * Create popup div with a title bar.
319 initialize: function(title, options, contentParam) {
320 options=options || {overflow:'auto'};
321 Rico.extend(this, new Rico.Popup());
323 this.titleDiv = document.createElement('div');
324 this.options.canDrag=true;
325 this.options.dragElement=this.titleDiv;
326 this.options.hideOnClick=false;
327 this.createContainer(options);
328 this.content.appendChild(this.titleDiv);
329 contentParam=Rico.$(contentParam);
330 this.contentDiv=contentParam || document.createElement('div');
331 this.content.appendChild(this.contentDiv);
334 this.titleDiv.className='ricoTitle';
335 if (Rico.theme.dialogTitle) Rico.addClass(this.titleDiv,Rico.theme.dialogTitle);
336 this.titleDiv.style.position='relative';
337 this.titleContent = document.createElement('span');
338 this.titleContent.className='ricoTitleSpan';
339 this.titleDiv.appendChild(this.titleContent);
340 this.titleDiv.appendChild(Rico.closeButton(Rico.eventHandle(this,'closePopup')));
341 if (!title && contentParam) {
342 title=contentParam.title;
343 contentParam.title='';
345 this.setTitle(title || ' ');
347 // create content area
348 this.contentDiv.className='ricoContent';
349 if (Rico.theme.dialogContent) Rico.addClass(this.contentDiv,Rico.theme.dialogContent);
350 this.contentDiv.style.position='relative';
351 if (options.height) this.contentDiv.style.height=options.height;
352 if (options.width) this.contentDiv.style.width=options.width;
353 if (options.overflow) this.contentDiv.style.overflow=options.overflow;
354 Rico.addClass(this.content,'ricoWindow');
355 if (Rico.theme.dialog) Rico.addClass(this.content,Rico.theme.dialog);
357 // fix float'ed content in IE
358 this.titleDiv.style.zoom=1;
359 this.contentDiv.style.zoom=1;
362 this.content=this.contentDiv;
365 setTitle: function(title) {
366 this.titleContent.innerHTML=title;
372 Rico.Menu = function(options) {
373 this.initialize(options);
376 Rico.Menu.prototype = {
378 * @class Implements popup menus and submenus
379 * @extends Rico.Popup
382 initialize: function(options) {
383 Rico.extend(this, new Rico.Popup());
384 Rico.extend(this.options, {
388 if (typeof options=='string')
389 this.options.width=options;
391 Rico.extend(this.options, options || {});
393 this.highlightElem=null;
394 new Image().src = Rico.imgDir+'left_b.gif';
395 new Image().src = Rico.imgDir+'right_b.gif';
398 createDiv: function(parentNode) {
399 if (this.container) return;
400 var options={closeFunc:Rico.bind(this,'cancelmenu')};
401 if (parentNode) options.parent=parentNode;
402 this.createContainer(options);
403 this.content.className = Rico.isWebKit ? 'ricoMenuSafari' : 'ricoMenu';
404 this.content.style.width=this.options.width;
405 this.direction=Rico.getStyle(this.container,'direction') || 'ltr';
406 this.direction=this.direction.toLowerCase(); // ltr or rtl
411 showmenu: function(e,hideFunc){
413 this.hideFunc=hideFunc;
414 if (this.content.childNodes.length==0) {
418 var mousePos = Rico.eventClient(e);
419 this.openmenu(mousePos.x,mousePos.y,0,0);
422 openmenu: function(x,y,clickItemWi,clickItemHt,noOffset) {
423 var newLeft=x + (noOffset ? 0 : Rico.docScrollLeft());
424 this.container.style.visibility="hidden";
425 this.container.style.display="block";
426 var w=this.container.offsetWidth;
427 var cw=this.contentCell.offsetWidth;
428 //window.status='openmenu: newLeft='+newLeft+' width='+w+' clickItemWi='+clickItemWi+' windowWi='+Rico.windowWidth();
429 if (this.direction == 'rtl') {
430 if (newLeft > w+clickItemWi) newLeft-=cw+clickItemWi;
432 if (x+w > Rico.windowWidth()) newLeft-=cw+clickItemWi-2;
434 var scrTop=Rico.docScrollTop();
435 var newTop=y + (noOffset ? 0 : scrTop);
436 if (y+this.container.offsetHeight-scrTop > Rico.windowHeight())
437 newTop=Math.max(newTop-this.contentCell.offsetHeight+clickItemHt,0);
438 this.openPopup(newLeft,newTop);
439 this.container.style.visibility ="visible";
443 clearMenu: function() {
445 this.defaultAction=null;
449 addMenuHeading: function(hdg) {
450 var el=document.createElement('div');
452 el.className='ricoMenuHeading';
453 this.content.appendChild(el);
456 addMenuBreak: function() {
457 var brk=document.createElement('div');
458 brk.className="ricoMenuBreak";
459 this.content.appendChild(brk);
462 addSubMenuItem: function(menutext, submenu, translate) {
463 var dir=this.direction=='rtl' ? 'left' : 'right';
464 var a=this.addMenuItem(menutext,null,true,null,translate);
465 a.className='ricoSubMenu';
466 a.style.backgroundImage='url('+Rico.imgDir+dir+'_b.gif)';
467 a.style.backgroundRepeat='no-repeat';
468 a.style.backgroundPosition=dir;
469 a.RicoSubmenu=submenu;
470 Rico.eventBind(a,"mouseover", Rico.eventHandle(this,'showSubMenu'));
471 Rico.eventBind(a,"mouseout", Rico.eventHandle(this,'subMenuOut'));
474 showSubMenu: function(e) {
475 if (this.openSubMenu) this.hideSubMenu();
476 var a=Rico.eventElement(e);
477 this.openSubMenu=a.RicoSubmenu;
478 this.openMenuAnchor=a;
479 if (a.className=='ricoSubMenu') a.className='ricoSubMenuOpen';
480 a.RicoSubmenu.openmenu(parseInt(this.container.style.left)+a.offsetWidth, parseInt(this.container.style.top)+a.offsetTop, a.offsetWidth-2, a.offsetHeight+2,true);
483 subMenuOut: function(e) {
484 if (!this.openSubMenu) return;
486 var elem=Rico.eventElement(e);
487 var reltg = Rico.eventRelatedTarget(e) || e.toElement;
489 while (reltg != null && reltg != this.openSubMenu.div)
490 reltg=reltg.parentNode;
492 if (reltg == this.openSubMenu.div) return;
496 hideSubMenu: function() {
497 if (this.openMenuAnchor) {
498 this.openMenuAnchor.className='ricoSubMenu';
499 this.openMenuAnchor=null;
501 if (this.openSubMenu) {
502 this.openSubMenu.hidemenu();
503 this.openSubMenu=null;
507 addMenuItemId: function(phraseId,action,enabled,title,target) {
508 if ( arguments.length < 3 ) enabled=true;
509 this.addMenuItem(Rico.getPhraseById(phraseId),action,enabled,title,target);
512 // if action is a string, then it is assumed to be a URL and the target parm can be used indicate which window gets the content
513 // action can also be a function (optionally wrapped using Rico.bind),
514 // action can also be a Rico.eventHandle, but set target='event' in this case
515 addMenuItem: function(menutext,action,enabled,title,target) {
516 if (arguments.length >= 3 && !enabled && !this.options.showDisabled) return null;
518 var a = document.createElement(typeof action=='string' ? 'a' : 'div');
519 if ( arguments.length < 3 || enabled ) {
520 if (typeof action=='string') {
522 if (target) a.target = target;
523 } else if (target=='event') {
524 Rico.eventBind(a,"click", action);
528 a.className = 'enabled';
529 if (this.defaultAction==null) this.defaultAction=action;
532 a.className = 'disabled';
534 a.innerHTML = menutext;
535 if (typeof title=='string')
537 a=this.content.appendChild(a);
538 Rico.eventBind(a,"mouseover", Rico.eventHandle(this,'mouseOver'));
539 Rico.eventBind(a,"mouseout", Rico.eventHandle(this,'mouseOut'));
543 mouseOver: function(e) {
544 if (this.highlightElem && this.highlightElem.className=='enabled-hover') {
545 // required for Safari
546 this.highlightElem.className='enabled';
547 this.highlightElem=null;
549 var elem=Rico.eventElement(e);
550 if (this.openMenuAnchor && this.openMenuAnchor!=elem)
552 if (elem.className=='enabled') {
553 elem.className='enabled-hover';
554 this.highlightElem=elem;
558 mouseOut: function(e) {
559 var elem=Rico.eventElement(e);
560 if (elem.className=='enabled-hover') elem.className='enabled';
561 if (this.highlightElem==elem) this.highlightElem=null;
564 cancelmenu: function() {
565 if (!this.visible()) return;
566 if (this.hideFunc) this.hideFunc();
571 hidemenu: function() {
572 if (this.openSubMenu) this.openSubMenu.hidemenu();
579 Rico.SelectionSet = function(selectionSet, options) {
580 this.initialize(selectionSet, options);
583 Rico.SelectionSet.prototype = {
587 * @param selectionSet collection of DOM elements (or a CSS selection string)
588 * @param options object may contain any of the following:<dl>
589 * <dt>selectedClass</dt><dd>class name to add when element is selected, default is "selected"</dd>
590 * <dt>selectNode </dt><dd>optional function that returns the element to be selected</dd>
591 * <dt>onSelect </dt><dd>optional function that gets called when element is selected</dd>
592 * <dt>onFirstSelect</dt><dd>optional function that gets called the first time element is selected</dd>
593 * <dt>noDefault </dt><dd>when true, no element in the set is initially selected, default is false</dd>
594 * <dt>selectedIndex</dt><dd>index of the element that should be initially selected, default is 0</dd>
595 * <dt>cookieName </dt><dd>optional name of cookie to use to remember selected element. If specified, and the cookie exists, then the cookie value overrides selectedIndex.</dd>
596 * <dt>cookieDays </dt><dd>specifies how long cookie should persist (in days). If unspecified, then the cookie persists for the current session.</dd>
597 * <dt>cookiePath </dt><dd>optional cookie path</dd>
598 * <dt>cookieDomain </dt><dd>optional cookie domain</dd>
601 initialize: function(selectionSet, options){
602 Rico.log('SelectionSet#initialize');
603 this.options = options || {};
604 if (typeof selectionSet == 'string')
605 selectionSet = Rico.select(selectionSet);
606 this.previouslySelected = [];
607 this.selectionSet = [];
608 this.selectedClassName = this.options.selectedClass || Rico.theme.selected || "selected";
609 this.selectNode = this.options.selectNode || function(e){return e;};
610 this.onSelect = this.options.onSelect;
611 this.onFirstSelect = this.options.onFirstSelect;
612 this.clickHandler = Rico.bind(this,'selectIndex');
613 this.selectedIndex=-1;
614 for (var i=0; i<selectionSet.length; i++)
615 this.add(selectionSet[i]);
616 if (!this.options.noDefault) {
617 var cookieIndex=this.options.cookieName ? this.getCookie() : 0;
618 this.selectIndex(cookieIndex || this.options.selectedIndex || 0);
621 getCookie: function() {
622 var cookie = Rico.getCookie(this.options.cookieName);
623 if (!cookie) return 0;
624 var index = parseInt(cookie);
625 return index < this.selectionSet.length ? index : 0;
628 this.previouslySelected = [];
629 this._notifySelected(this.selectedIndex);
631 clearSelected: function() {
633 Rico.removeClass(this.selectNode(this.selected), this.selectedClassName);
635 getIndex: function(element) {
636 for (var i=0; i<this.selectionSet.length; i++) {
637 if (element == this.selectionSet[i]) return i;
641 select: function(element){
642 if (this.selected == element) return;
643 var i=this.getIndex(element);
644 if (i >= 0) this.selectIndex(i);
646 _notifySelected: function(index){
647 if (index < 0) return;
648 var element = this.selectionSet[index];
649 if (this.options.cookieName)
650 Rico.setCookie(this.options.cookieName, index, this.options.cookieDays, this.options.cookiePath, this.options.cookieDomain);
651 if (this.onFirstSelect && !this.previouslySelected[index]){
652 this.onFirstSelect(element, index);
653 this.previouslySelected[index] = true;
657 this.onSelect(index);
660 selectIndex: function(index){
661 if (this.selectedIndex == index || index >= this.selectionSet.length) return;
662 this.clearSelected();
663 this._notifySelected(index);
664 this.selectedIndex = index;
665 this.selected=this.selectionSet[index].element;
666 Rico.addClass(this.selectNode(this.selected), this.selectedClassName);
668 nextSelectIndex: function(){
669 return (this.selectedIndex + 1) % this.selectionSet.length;
671 nextSelectItem: function(){
672 return this.selectionSet[this.nextSelectIndex()];
674 selectNext: function(){
675 this.selectIndex(this.nextSelectIndex());
678 var index=this.selectionSet.length;
679 this.selectionSet[index] = new Rico._SelectionItem(item,index,this.clickHandler);
681 remove: function(item){
682 if (item==this.selected) this.clearSelected();
683 var i=this.getIndex(item);
685 this.selectionSet[i].remove();
686 this.selectionSet.splice(i,1);
688 removeAll: function(){
689 this.clearSelected();
690 while (this.selectionSet.length > 0) {
691 this.selectionSet.pop().remove();
697 Rico._SelectionItem=function(element,index,callback) {
698 this.add(element,index,callback);
701 Rico._SelectionItem.prototype = {
702 add: function(element,index,callback) {
703 this.element=element;
705 this.callback=callback;
706 this.handle=Rico.eventHandle(this,'click');
707 Rico.eventBind(element, "click", this.handle);
710 click: function(ev) {
711 this.callback(this.index);
715 Rico.eventUnbind(this.element, "click", this.handle);
720 Rico.HoverSet = function(hoverSet, options) {
721 this.initialize(hoverSet, options);
724 Rico.HoverSet.prototype = {
728 * @param hoverSet collection of DOM elements
729 * @param options object may contain any of the following:<dl>
730 * <dt>hoverClass</dt><dd> class name to add when mouse is over element, default is "hover"</dd>
731 * <dt>hoverNodes</dt><dd> optional function to select/filter which nodes are in the set</dd>
734 initialize: function(hoverSet, options){
735 Rico.log('HoverSet#initialize');
736 options = options || {};
737 this.hoverClass = options.hoverClass || Rico.theme.hover || "hover";
738 this.hoverFunc = options.hoverNodes || function(e){return [e];};
740 if (!hoverSet) return;
741 for (var i=0; i<hoverSet.length; i++)
742 this.add(hoverSet[i]);
744 add: function(item) {
745 this.hoverSet.push(new Rico._HoverItem(item,this.hoverFunc,this.hoverClass));
747 removeAll: function(){
748 while (this.hoverSet.length > 0) {
749 this.hoverSet.pop().remove();
755 Rico._HoverItem=function(element,selectFunc,hoverClass) {
756 this.add(element,selectFunc,hoverClass);
759 Rico._HoverItem.prototype = {
760 add: function(element,selectFunc,hoverClass) {
761 this.element=element;
762 this.selectFunc=selectFunc;
763 this.hoverClass=hoverClass;
764 this.movehandle=Rico.eventHandle(this,'move');
765 this.outhandle=Rico.eventHandle(this,'mouseout');
766 Rico.eventBind(element, "mousemove", this.movehandle);
767 Rico.eventBind(element, "mouseout", this.outhandle);
771 var elems=this.selectFunc(this.element);
772 for (var i=0; i<elems.length; i++)
773 Rico.addClass(elems[i],this.hoverClass);
776 mouseout: function(ev) {
777 var elems=this.selectFunc(this.element);
778 for (var i=0; i<elems.length; i++)
779 Rico.removeClass(elems[i],this.hoverClass);
783 Rico.eventUnbind(element, "mousemove", this.movehandle);
784 Rico.eventUnbind(element, "mouseout", this.outhandle);
791 Rico.Effect.easeIn = function(step){
792 return Math.sqrt(step);
794 Rico.Effect.easeOut = function(step){
799 /** @class core methods for transition effects */
800 Rico.ContentTransitionBase = function() {};
801 Rico.ContentTransitionBase.prototype = {
802 initBase: function(titles, contents, options) {
803 Rico.log('ContentTransitionBase#initBase');
804 if (typeof titles == 'string')
805 titles = Rico.select(titles);
806 if (typeof contents == 'string')
807 contents = Rico.select(contents);
809 this.options = options || {};
810 this.titles = titles;
811 this.contents = contents;
812 this.hoverSet = new Rico.HoverSet(titles, options);
813 for (var i=0; i<contents.length; i++) {
814 if (contents[i]) Rico.hide(contents[i]);
816 this.selectionSet = new Rico.SelectionSet(titles, Rico.extend(options, {onSelect: Rico.bind(this,'_finishSelect')}));
819 this.selectionSet.reset();
821 select: function(index) {
822 this.selectionSet.selectIndex(index);
824 _finishSelect: function(index) {
825 Rico.log('ContentTransitionBase#_finishSelect');
826 var panel = this.contents[index];
828 alert('Internal error: no panel @index='+index);
831 if ( this.selected == panel) return;
832 if (this.transition){
834 this.transition(panel);
836 panel.style.display='block';
839 if (this.selected) Rico.hide(this.selected);
840 panel.style.display='block';
842 this.selected = panel;
844 addBase: function(title, content){
845 this.titles.push(title);
846 this.contents.push(content);
847 this.hoverSet.add(title);
848 this.selectionSet.add(title);
850 //this.selectionSet.select(title);
852 removeBase: function(title){},
853 removeAll: function(){
854 this.hoverSet.removeAll();
855 this.selectionSet.removeAll();
861 * @class Implements accordion effect
862 * @see Rico.ContentTransitionBase#initialize for construction parameters
863 * @extends Rico.ContentTransitionBase
865 Rico.Accordion = function(element, options) {
866 this.initialize(element, options);
869 Rico.Accordion.prototype = Rico.extend(new Rico.ContentTransitionBase(),
870 /** @lends Rico.Accordion# */
872 initialize: function(element, options) {
873 element=Rico.$(element);
874 element.style.overflow='hidden';
875 element.className=options.accClass || Rico.theme.accordion || "Rico_accordion";
876 if (typeof options.panelWidth=='number') options.panelWidth+="px";
877 if (options.panelWidth) element.style.width = options.panelWidth;
878 var panels=Rico.getDirectChildrenByTag(element,'div');
879 var items,titles=[], contents=[];
880 for (var i=0; i<panels.length; i++) {
881 items=Rico.getDirectChildrenByTag(panels[i],'div');
882 if (items.length>=2) {
883 items[0].className=options.titleClass || Rico.theme.accTitle || "Rico_accTitle";
884 items[1].className=options.contentClass || Rico.theme.accContent || "Rico_accContent";
885 titles.push(items[0]);
886 contents.push(items[1]);
887 var a=Rico.wrapChildren(items[0],'','','a');
888 a.href="javascript:void(0)";
891 Rico.log('creating Rico.Accordion for '+element.id+' with '+titles.length+' panels');
892 this.initBase(titles, contents, options);
893 this.selected.style.height = this.options.panelHeight + "px";
894 this.totSteps=(typeof options.duration =='number' ? options.duration : 200)/25;
896 transition: function(p){
897 if (!this.options.noAnimate) {
898 this.closing=this.selected;
901 this.timer=setInterval(Rico.bind(this,'step'),25);
903 p.style.height = this.options.panelHeight + "px";
904 if (this.selected) Rico.hide(this.selected);
905 p.style.display='block';
910 var oheight=Math.round(this.curStep/this.totSteps*this.options.panelHeight);
911 this.opening.style.height=oheight+'px';
912 this.closing.style.height=(this.options.panelHeight - oheight)+'px';
913 if (this.curStep==1) {
914 this.opening.style.paddingTop=this.opening.style.paddingBottom='0px';
915 this.opening.style.display='block';
917 if (this.curStep==this.totSteps) {
918 clearInterval(this.timer);
919 this.opening.style.paddingTop=this.opening.style.paddingBottom='';
920 Rico.hide(this.closing);
923 setPanelHeight: function(h) {
924 this.options.panelHeight = h;
925 this.selected.style.height = this.options.panelHeight + "px";
931 * @class Implements tabbed panel effect
932 * @see Rico.ContentTransitionBase#initialize for construction parameters
933 * @extends Rico.ContentTransitionBase
935 Rico.TabbedPanel = function(element, options) {
936 this.initialize(element, options);
939 Rico.TabbedPanel.prototype = Rico.extend(new Rico.ContentTransitionBase(),
941 initialize: function(element, options) {
942 element=Rico.$(element);
943 options=options || {};
944 if (typeof options.panelWidth=='number') options.panelWidth+="px";
945 if (typeof options.panelHeight=='number') options.panelHeight+="px";
946 element.className=options.tabClass || Rico.theme.tabPanel || "Rico_tabPanel";
947 if (options.panelWidth) element.style.width = options.panelWidth;
949 var allKids = element.childNodes;
950 for( var i = 0 ; i < allKids.length ; i++ ) {
951 if (allKids[i] && allKids[i].tagName && allKids[i].tagName.match(/^div|ul$/i))
952 items.push(allKids[i]);
954 if (items.length < 2) return;
955 var childTag=items[0].tagName.toLowerCase()=='ul' ? 'li' : 'div';
956 items[0].className=options.navContainerClass || Rico.theme.tabNavContainer || "Rico_tabNavContainer";
957 items[0].style.listStyle='none';
958 items[1].className=options.contentContainerClass || Rico.theme.tabContentContainer || "Rico_tabContentContainer";
959 var titles=Rico.getDirectChildrenByTag(items[0], childTag);
960 var contents=Rico.getDirectChildrenByTag(items[1],'div');
961 if (!options.corners) options.corners='top';
962 for (var i=0; i<titles.length; i++) {
963 titles[i].className=options.titleClass || Rico.theme.tabTitle || "Rico_tabTitle";
964 var a=Rico.wrapChildren(titles[i],'','','a');
965 a.href="javascript:void(0)";
966 contents[i].className=options.contentClass || Rico.theme.tabContent || "Rico_tabContent";
967 if (options.panelHeight) contents[i].style.overflow='auto';
968 if (options.corners!='none') {
969 if (options.panelHdrWidth) titles[i].style.width=options.panelHdrWidth;
970 Rico.Corner.round(titles[i], Rico.theme.tabCornerOptions || options);
973 options.selectedClass=Rico.theme.tabSelected || 'selected';
974 this.initBase(titles, contents, options);
975 if (this.selected) this.transition(this.selected);
977 transition: function(p){
978 Rico.log('TabbedPanel#transition '+typeof(p));
979 if (this.selected) Rico.hide(this.selected);
981 if (this.options.panelHeight) p.style.height = this.options.panelHeight;
991 round: function(e, options) {
995 bgColor : "fromParent",
997 nativeCorners: false // only use native corners
999 Rico.extend(this.options, options || {});
1001 this._roundCornersGecko(e);
1002 else if (typeof(Rico.getStyle(e,'-webkit-border-radius'))=='string')
1003 this._roundCornersWebKit(e);
1004 else if (!this.options.nativeCorners)
1005 this._roundCornersImpl(e);
1008 _roundCornersImpl: function(e) {
1009 var bgColor = this.options.bgColor == "fromParent" ? this._background(e.parentNode) : this.options.bgColor;
1010 e.style.position='relative';
1011 //this.options.numSlices = this.options.compact ? 2 : 4;
1012 if (this._hasString(this.options.corners, "all", "top", "tl")) this._createCorner(e,'top','left',bgColor);
1013 if (this._hasString(this.options.corners, "all", "top", "tr")) this._createCorner(e,'top','right',bgColor);
1014 if (this._hasString(this.options.corners, "all", "bottom", "bl")) this._createCorner(e,'bottom','left',bgColor);
1015 if (this._hasString(this.options.corners, "all", "bottom", "br")) this._createCorner(e,'bottom','right',bgColor);
1018 _roundCornersGecko: function(e) {
1019 var radius=this.options.compact ? '4px' : '8px';
1020 if (this._hasString(this.options.corners, "all"))
1021 Rico.setStyle(e, {MozBorderRadius:radius});
1023 if (this._hasString(this.options.corners, "top", "tl")) Rico.setStyle(e, {MozBorderRadiusTopleft:radius});
1024 if (this._hasString(this.options.corners, "top", "tr")) Rico.setStyle(e, {MozBorderRadiusTopright:radius});
1025 if (this._hasString(this.options.corners, "bottom", "bl")) Rico.setStyle(e, {MozBorderRadiusBottomleft:radius});
1026 if (this._hasString(this.options.corners, "bottom", "br")) Rico.setStyle(e, {MozBorderRadiusBottomright:radius});
1030 _roundCornersWebKit: function(e) {
1031 var radius=this.options.compact ? '4px' : '8px';
1032 if (this._hasString(this.options.corners, "all"))
1033 Rico.setStyle(e, {WebkitBorderRadius:radius});
1035 if (this._hasString(this.options.corners, "top", "tl")) Rico.setStyle(e, {WebkitBorderTopLeftRadius:radius});
1036 if (this._hasString(this.options.corners, "top", "tr")) Rico.setStyle(e, {WebkitBorderTopRightRadius:radius});
1037 if (this._hasString(this.options.corners, "bottom", "bl")) Rico.setStyle(e, {WebkitBorderBottomLeftRadius:radius});
1038 if (this._hasString(this.options.corners, "bottom", "br")) Rico.setStyle(e, {WebkitBorderBottomRightRadius:radius});
1042 _createCorner: function(elem,tb,lr,bgColor) {
1043 //alert('Corner: '+tb+' '+lr+' bgColor='+typeof(bgColor));
1044 var corner = document.createElement("div");
1045 corner.className='ricoCorner';
1046 Rico.setStyle(corner,{width:'6px', height:'5px'});
1047 var borderStyle = Rico.getStyle(elem,'border-'+tb+'-style');
1048 var borderColor = borderStyle=='none' ? bgColor : Rico.getStyle(elem,'border-'+tb+'-color');
1049 //alert('Corner: '+tb+' '+borderStyle+borderColor+' '+);
1050 var pos=borderStyle=='none' ? '0px' : '-1px';
1051 corner.style[tb]=pos;
1052 corner.style[lr]=Rico.isIE && Rico.ieVersion<7 && lr=='right' && borderStyle!='none' ? '-2px' : '-1px';
1053 //corner.style[lr]='-1px';
1054 elem.appendChild(corner);
1055 var marginSizes = [ 0, 2, 3, 4, 4 ];
1056 if (tb=='bottom') marginSizes.reverse();
1057 var borderVal= borderStyle=='none' ? '0px none' : '1px solid '+borderColor;
1058 var d= lr=='left' ? 'Right' : 'Left';
1059 for (var i=0; i<marginSizes.length; i++) {
1060 var slice = document.createElement("div");
1061 Rico.setStyle(slice,{backgroundColor:bgColor,height:'1px'});
1062 slice.style['margin'+d]=marginSizes[i]+'px';
1063 slice.style['border'+d]=borderVal;
1064 corner.appendChild(slice);
1068 _background: function(elem) {
1070 var actualColor = Rico.getStyle(elem, "backgroundColor");
1072 // if color is tranparent, check parent
1073 // Safari returns "rgba(0, 0, 0, 0)", which means transparent
1074 if ( actualColor.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i) && elem.parentNode )
1075 return this._background(elem.parentNode);
1077 return actualColor == null ? "#ffffff" : actualColor;
1083 _hasString: function(str) {
1084 for(var i=1 ; i<arguments.length ; i++) {
1085 if (str.indexOf(arguments[i]) >= 0) return true;
1092 Rico.toColorPart = function(c) {
1093 return Rico.zFill(c, 2, 16);
1097 Rico.Color = function(red, green, blue) {
1098 this.initialize(red, green, blue);
1101 Rico.Color.prototype = {
1103 * @class Methods to manipulate color values.
1105 * @param red integer (0-255)
1106 * @param green integer (0-255)
1107 * @param blue integer (0-255)
1109 initialize: function(red, green, blue) {
1110 this.rgb = { r: red, g : green, b : blue };
1113 setRed: function(r) {
1117 setGreen: function(g) {
1121 setBlue: function(b) {
1125 setHue: function(h) {
1127 // get an HSB model, and set the new hue...
1128 var hsb = this.asHSB();
1131 // convert back to RGB...
1132 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
1135 setSaturation: function(s) {
1136 // get an HSB model, and set the new hue...
1137 var hsb = this.asHSB();
1140 // convert back to RGB and set values...
1141 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
1144 setBrightness: function(b) {
1145 // get an HSB model, and set the new hue...
1146 var hsb = this.asHSB();
1149 // convert back to RGB and set values...
1150 this.rgb = Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
1153 darken: function(percent) {
1154 var hsb = this.asHSB();
1155 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
1158 brighten: function(percent) {
1159 var hsb = this.asHSB();
1160 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
1163 blend: function(other) {
1164 this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
1165 this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
1166 this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
1169 isBright: function() {
1170 var hsb = this.asHSB();
1171 return this.asHSB().b > 0.5;
1174 isDark: function() {
1175 return ! this.isBright();
1179 return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
1183 return "#" + Rico.toColorPart(this.rgb.r) + Rico.toColorPart(this.rgb.g) + Rico.toColorPart(this.rgb.b);
1187 return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
1190 toString: function() {
1191 return this.asHex();
1197 * Factory method for creating a color from an RGB string
1198 * @param hexCode a 3 or 6 digit hex string, optionally preceded by a # symbol
1199 * @returns a Rico.Color object
1201 Rico.Color.createFromHex = function(hexCode) {
1202 if(hexCode.length==4) {
1203 var shortHexCode = hexCode;
1205 for(var i=1;i<4;i++)
1206 hexCode += (shortHexCode.charAt(i) + shortHexCode.charAt(i));
1208 if ( hexCode.indexOf('#') == 0 )
1209 hexCode = hexCode.substring(1);
1210 if (!hexCode.match(/^[0-9A-Fa-f]{6}$/)) return null;
1211 var red = hexCode.substring(0,2);
1212 var green = hexCode.substring(2,4);
1213 var blue = hexCode.substring(4,6);
1214 return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
1218 * Retrieves the background color of an HTML element
1219 * @param elem the DOM element whose background color should be retreived
1220 * @returns a Rico.Color object
1222 Rico.Color.createColorFromBackground = function(elem) {
1224 if (!elem.style) return new Rico.Color(255,255,255);
1225 var actualColor = Rico.getStyle(elem, "background-color");
1227 // if color is tranparent, check parent
1228 // Safari returns "rgba(0, 0, 0, 0)", which means transparent
1229 if ( actualColor.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i) && elem.parentNode )
1230 return Rico.Color.createColorFromBackground(elem.parentNode);
1232 if (actualColor == null) return new Rico.Color(255,255,255);
1234 if ( actualColor.indexOf("rgb(") == 0 ) {
1235 var colors = actualColor.substring(4, actualColor.length - 1 );
1236 var colorArray = colors.split(",");
1237 return new Rico.Color( parseInt( colorArray[0],10 ),
1238 parseInt( colorArray[1],10 ),
1239 parseInt( colorArray[2],10 ) );
1242 else if ( actualColor.indexOf("#") == 0 ) {
1243 return Rico.Color.createFromHex(actualColor);
1246 return new Rico.Color(255,255,255);
1250 * Converts hue/saturation/brightness to RGB
1251 * @returns a 3-element object: r=red, g=green, b=blue.
1253 Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
1259 if (saturation == 0) {
1260 red = parseInt(brightness * 255.0 + 0.5,10);
1265 var h = (hue - Math.floor(hue)) * 6.0;
1266 var f = h - Math.floor(h);
1267 var p = brightness * (1.0 - saturation);
1268 var q = brightness * (1.0 - saturation * f);
1269 var t = brightness * (1.0 - (saturation * (1.0 - f)));
1271 switch (parseInt(h,10)) {
1273 red = (brightness * 255.0 + 0.5);
1274 green = (t * 255.0 + 0.5);
1275 blue = (p * 255.0 + 0.5);
1278 red = (q * 255.0 + 0.5);
1279 green = (brightness * 255.0 + 0.5);
1280 blue = (p * 255.0 + 0.5);
1283 red = (p * 255.0 + 0.5);
1284 green = (brightness * 255.0 + 0.5);
1285 blue = (t * 255.0 + 0.5);
1288 red = (p * 255.0 + 0.5);
1289 green = (q * 255.0 + 0.5);
1290 blue = (brightness * 255.0 + 0.5);
1293 red = (t * 255.0 + 0.5);
1294 green = (p * 255.0 + 0.5);
1295 blue = (brightness * 255.0 + 0.5);
1298 red = (brightness * 255.0 + 0.5);
1299 green = (p * 255.0 + 0.5);
1300 blue = (q * 255.0 + 0.5);
1305 return { r : parseInt(red,10), g : parseInt(green,10) , b : parseInt(blue,10) };
1309 * Converts RGB value to hue/saturation/brightness
1310 * @param r integer (0-255)
1311 * @param g integer (0-255)
1312 * @param b integer (0-255)
1313 * @returns a 3-element object: h=hue, s=saturation, b=brightness.
1314 * (unlike some HSB documentation which states hue should be a value 0-360, this routine returns hue values from 0 to 1.0)
1316 Rico.Color.RGBtoHSB = function(r, g, b) {
1322 var cmax = (r > g) ? r : g;
1326 var cmin = (r < g) ? r : g;
1330 brightness = cmax / 255.0;
1332 saturation = (cmax - cmin)/cmax;
1336 if (saturation == 0)
1339 var redc = (cmax - r)/(cmax - cmin);
1340 var greenc = (cmax - g)/(cmax - cmin);
1341 var bluec = (cmax - b)/(cmax - cmin);
1344 hue = bluec - greenc;
1346 hue = 2.0 + redc - bluec;
1348 hue = 4.0 + greenc - redc;
1355 return { h : hue, s : saturation, b : brightness };
1359 * Returns a new XML document object
1361 Rico.createXmlDocument = function() {
1362 if (document.implementation && document.implementation.createDocument) {
1363 var doc = document.implementation.createDocument("", "", null);
1364 // some older versions of Moz did not support the readyState property
1365 // and the onreadystate event so we patch it!
1366 if (doc.readyState == null) {
1368 doc.addEventListener("load", function () {
1370 if (typeof doc.onreadystatechange == "function") {
1371 doc.onreadystatechange();
1378 if (window.ActiveXObject)
1379 return Rico.tryFunctions(
1380 function() { return new ActiveXObject('MSXML2.DomDocument'); },
1381 function() { return new ActiveXObject('Microsoft.DomDocument');},
1382 function() { return new ActiveXObject('MSXML.DomDocument'); },
1383 function() { return new ActiveXObject('MSXML3.DomDocument'); }
1388 Rico.includeLoaded('ricoUI.js');