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 if (shadowFlag) Rico.addClass(elem,'ricoShadow');
22 // ensure popups/windows get closed in the right order when the user hits escape key
23 Rico._OpenPopupList = [];
24 Rico._RemoveOpenPopup = function(popup) {
25 if (popup.openIndex >= 0 && popup.openIndex < Rico._OpenPopupList.length) Rico._OpenPopupList.splice(popup.openIndex,1);
28 Rico._AddOpenPopup = function(popup) {
29 popup.openIndex = Rico._OpenPopupList.push(popup) - 1;
31 Rico._checkEscKey = function(e) {
32 if (Rico.eventKey(e) != 27) return true;
33 while (Rico._OpenPopupList.length > 0) {
34 var popup = Rico._OpenPopupList.pop();
35 if (popup && popup.visible()) {
44 Rico.eventBind(document,"keyup", Rico.eventHandle(Rico,'_checkEscKey'));
47 Rico.Popup = function(containerDiv,options) {
48 this.initialize(containerDiv,options);
51 Rico.Popup.prototype = {
53 * @class Class to manage pop-up div windows.
55 * @param options object may contain any of the following:<dl>
56 * <dt>hideOnClick </dt><dd> hide popup when mouse button is clicked? default=true</dd>
57 * <dt>ignoreClicks</dt><dd> if true, mouse clicks within the popup are not allowed to bubble up to parent elements</dd>
58 * <dt>position </dt><dd> defaults to absolute, use "auto" to auto-detect</dd>
59 * <dt>shadow </dt><dd> display shadow with popup? default=true</dd>
60 * <dt>zIndex </dt><dd> which layer? default=1</dd>
61 * <dt>canDrag </dt><dd> boolean value (or function that returns a boolean) indicating if it is ok to drag/reposition popup, default=false</dd>
62 * <dt>onClose </dt><dd> function to call when the popup is closed</dd>
64 * @param containerDiv if supplied, then setDiv() is called at the end of initialization
66 initialize: function(containerDiv,options) {
70 position : 'absolute',
78 if (containerDiv) this.setDiv(containerDiv,options);
81 createContainer: function(options) {
82 this.setDiv(document.createElement('div'), options);
83 if (options && options.parent) {
84 options.parent.appendChild(this.container);
86 document.getElementsByTagName("body")[0].appendChild(this.container);
91 * Apply popup behavior to a div that already exists in the DOM
92 * @param containerDiv div element (or element id) in the DOM. If null, then the div is created automatically.
94 setDiv: function(containerDiv,options) {
95 Rico.extend(this.options, options || {});
96 this.container=Rico.$(containerDiv);
97 if (this.options.position == 'auto') {
98 this.position=Rico.getStyle(this.container,'position').toLowerCase();
100 this.position=this.container.style.position=this.options.position;
102 this.content=document.createElement('div');
103 while (this.container.firstChild) {
104 this.content.appendChild(this.container.firstChild);
106 this.container.appendChild(this.content);
107 this.content.className='RicoPopupContent';
108 if (this.position != 'absolute') return;
110 if (this.options.closeFunc) {
111 this.closeFunc=this.options.closeFunc;
114 this.closeFunc=function() { self.closePopup(); };
116 this.container.style.top='0px';
117 this.container.style.left='0px';
118 this.container.style.display='none';
119 if (this.options.zIndex >= 0) this.container.style.zIndex=this.options.zIndex;
121 if (Rico.isIE && Rico.ieVersion < 7 && this.options.shim!==false) {
122 this.content.style.position='relative';
123 this.content.style.zIndex=2;
124 // create iframe shim
125 this.ifr = document.createElement('iframe');
126 this.ifr.className='RicoShim';
127 this.ifr.frameBorder=0;
128 this.ifr.src="javascript:'';";
129 this.container.appendChild(this.ifr);
131 Rico.applyShadow(this.container,this.options.shadow);
133 if (this.options.hideOnClick)
134 Rico.eventBind(document,"click", Rico.eventHandle(this,'_docClick'));
135 this.dragEnabled=false;
136 this.mousedownHandler = Rico.eventHandle(this,'_startDrag');
137 this.dragHandler = Rico.eventHandle(this,'_drag');
138 this.dropHandler = Rico.eventHandle(this,'_endDrag');
139 if (this.options.canDrag) this.enableDragging();
140 if (this.options.ignoreClicks || this.options.canDrag) this.ignoreClicks();
143 clearContent: function() {
144 this.content.innerHTML="";
147 setContent: function(content) {
148 this.content.innerHTML=content;
151 enableDragging: function() {
152 if (!this.dragEnabled && this.options.dragElement) {
153 Rico.eventBind(this.options.dragElement, "mousedown", this.mousedownHandler);
154 this.dragEnabled=true;
156 return this.dragEnabled;
159 disableDragging: function() {
160 if (!this.dragEnabled) return;
161 Rico.eventUnbind(this.options.dragElement, "mousedown", this.mousedownHandler);
162 this.dragEnabled=false;
165 setZ: function(zIndex) {
166 this.container.style.zIndex=zIndex;
170 ignoreClicks: function() {
171 Rico.eventBind(this.container,"click", Rico.eventHandle(this,'_ignoreClick'));
174 _ignoreClick: function(e) {
175 if (e.stopPropagation)
178 e.cancelBubble = true;
182 _docClick: function(e) {
188 * Move popup to specified position
190 move: function(left,top) {
191 if (typeof left=='number') this.container.style.left=left+'px';
192 if (typeof top=='number') this.container.style.top=top+'px';
195 _startDrag : function(event){
196 var elem=Rico.eventElement(event);
197 this.container.style.cursor='move';
198 this.lastMouse = Rico.eventClient(event);
199 Rico.eventBind(document, "mousemove", this.dragHandler);
200 Rico.eventBind(document, "mouseup", this.dropHandler);
201 Rico.eventStop(event);
204 _drag : function(event){
205 var newMouse = Rico.eventClient(event);
206 var newLeft = parseInt(this.container.style.left,10) + newMouse.x - this.lastMouse.x;
207 var newTop = parseInt(this.container.style.top,10) + newMouse.y - this.lastMouse.y;
208 this.move(newLeft, newTop);
209 this.lastMouse = newMouse;
210 Rico.eventStop(event);
213 _endDrag : function(){
214 this.container.style.cursor='';
215 Rico.eventUnbind(document, "mousemove", this.dragHandler);
216 Rico.eventUnbind(document, "mouseup", this.dropHandler);
220 * Display popup at specified position
222 openPopup: function(left,top) {
224 this.container.style.display='';
225 if (this.container.id) Rico.log('openPopup '+this.container.id+' at '+left+','+top);
226 Rico._AddOpenPopup(this);
229 centerPopup: function() {
231 var msgWidth=this.container.offsetWidth;
232 var msgHeight=this.container.offsetHeight;
233 var divwi=this.container.parentNode.offsetWidth;
234 var divht=this.container.parentNode.offsetHeight;
235 this.move(parseInt(Math.max((divwi-msgWidth)/2,0),10), parseInt(Math.max((divht-msgHeight)/2,0),10));
238 visible: function() {
239 return Rico.visible(this.container);
245 closePopup: function() {
246 Rico._RemoveOpenPopup(this);
247 if (!Rico.visible(this.container)) return;
248 if (this.container.id) Rico.log('closePopup '+this.container.id);
249 if (this.dragEnabled) this._endDrag();
250 this.container.style.display="none";
251 if (this.options.onClose) this.options.onClose();
256 Rico.closeButton = function(handle) {
257 var a = document.createElement('a');
258 a.className='RicoCloseAnchor';
259 if (Rico.theme.closeAnchor) Rico.addClass(a,Rico.theme.closeAnchor);
260 var span = a.appendChild(document.createElement('span'));
261 span.title=Rico.getPhraseById('close');
262 new Rico.HoverSet([a],{hoverClass: Rico.theme.hover || 'ricoCloseHover'});
263 Rico.addClass(span,Rico.theme.close || 'rico-icon RicoClose');
264 Rico.eventBind(a,"click", handle);
268 Rico.floatButton = function(buttonName, handle, title) {
269 var a=document.createElement("a");
270 a.className='RicoButtonAnchor'
271 Rico.addClass(a,Rico.theme.buttonAnchor || 'RicoButtonAnchorNative');
272 var span=a.appendChild(document.createElement("span"));
273 if (title) span.title=title;
274 span.className=Rico.theme[buttonName.toLowerCase()] || 'rico-icon Rico'+buttonName;
275 Rico.eventBind(a,"click", handle, false);
276 new Rico.HoverSet([a]);
280 Rico.clearButton = function(handle) {
281 var span=document.createElement("span");
282 span.title=Rico.getPhraseById('clear');
283 span.className='ricoClear';
284 Rico.addClass(span, Rico.theme.clear || 'rico-icon ricoClearNative');
285 Rico.eventBind(span,"click", handle);
289 Rico.Window = function(title, options, contentParam) {
290 this.initialize(title, options, contentParam);
293 Rico.Window.prototype = {
296 * Create popup div with a title bar.
298 initialize: function(title, options, contentParam) {
299 options=options || {overflow:'auto'};
300 Rico.extend(this, new Rico.Popup());
302 this.titleDiv = document.createElement('div');
303 this.options.canDrag=true;
304 this.options.dragElement=this.titleDiv;
305 this.createContainer(options);
306 this.content.appendChild(this.titleDiv);
307 contentParam=Rico.$(contentParam);
308 this.contentDiv=contentParam || document.createElement('div');
309 this.content.appendChild(this.contentDiv);
312 this.titleDiv.className='ricoTitle';
313 if (Rico.theme.dialogTitle) Rico.addClass(this.titleDiv,Rico.theme.dialogTitle);
314 this.titleDiv.style.position='relative';
315 this.titleContent = document.createElement('span');
316 this.titleContent.className='ricoTitleSpan';
317 this.titleDiv.appendChild(this.titleContent);
318 this.titleDiv.appendChild(Rico.closeButton(Rico.eventHandle(this,'closeFunc')));
319 if (!title && contentParam) {
320 title=contentParam.title;
321 contentParam.title='';
323 this.setTitle(title || ' ');
325 // create content area
326 this.contentDiv.className='ricoContent';
327 if (Rico.theme.dialogContent) Rico.addClass(this.contentDiv,Rico.theme.dialogContent);
328 this.contentDiv.style.position='relative';
329 if (options.height) this.contentDiv.style.height=options.height;
330 if (options.width) this.contentDiv.style.width=options.width;
331 if (options.overflow) this.contentDiv.style.overflow=options.overflow;
332 Rico.addClass(this.content,'ricoWindow');
333 if (Rico.theme.dialog) Rico.addClass(this.container,Rico.theme.dialog);
336 // fix float'ed content in IE
337 this.titleDiv.style.zoom=1;
338 this.contentDiv.style.zoom=1;
341 this.content=this.contentDiv;
344 setTitle: function(title) {
345 this.titleContent.innerHTML=title;
351 Rico.Menu = function(options) {
352 this.initialize(options);
355 Rico.Menu.prototype = {
357 * @class Implements popup menus and submenus
358 * @extends Rico.Popup
361 initialize: function(options) {
362 Rico.extend(this, new Rico.Popup());
363 Rico.extend(this.options, {
365 arrowColor : "b", // for submenus: b=black, w=white
366 showDisabled : false,
369 if (typeof options=='string')
370 this.options.width=options;
372 Rico.extend(this.options, options || {});
374 this.highlightElem=null;
377 createDiv: function(parentNode) {
378 if (this.container) return;
380 var options={ closeFunc: function() { self.cancelmenu(); } };
381 if (parentNode) options.parent=parentNode;
382 this.createContainer(options);
383 this.content.className = Rico.isWebKit ? 'ricoMenuSafari' : 'ricoMenu';
384 this.content.style.width=this.options.width;
385 this.direction=Rico.direction(this.container);
390 showmenu: function(e,hideFunc){
392 this.hideFunc=hideFunc;
393 if (this.content.childNodes.length==0) {
397 var mousePos = Rico.eventClient(e);
398 this.openmenu(mousePos.x,mousePos.y,0,0);
401 openmenu: function(x,y,clickItemWi,clickItemHt,noOffset) {
402 var newLeft=x + (noOffset ? 0 : Rico.docScrollLeft());
403 this.container.style.visibility="hidden";
404 this.container.style.display="block";
405 var w=this.container.offsetWidth;
406 var cw=this.content.offsetWidth;
407 //window.status='openmenu: newLeft='+newLeft+' width='+w+' clickItemWi='+clickItemWi+' windowWi='+Rico.windowWidth();
408 if (this.direction == 'rtl') {
409 if (newLeft > w+clickItemWi) newLeft-=cw+clickItemWi;
411 if (x+w > Rico.windowWidth()) newLeft-=cw+clickItemWi-2;
413 var scrTop=Rico.docScrollTop();
414 var newTop=y + (noOffset ? 0 : scrTop);
415 if (y+this.container.offsetHeight-scrTop > Rico.windowHeight())
416 newTop=Math.max(newTop-this.content.offsetHeight+clickItemHt,0);
417 this.openPopup(newLeft,newTop);
418 this.container.style.visibility ="visible";
422 clearMenu: function() {
424 this.defaultAction=null;
428 addMenuHeading: function(hdg) {
429 var el=document.createElement('div');
431 el.className='ricoMenuHeading';
432 this.content.appendChild(el);
435 addMenuBreak: function() {
436 var brk=document.createElement('div');
437 brk.className="ricoMenuBreak";
438 this.content.appendChild(brk);
441 addSubMenuItem: function(menutext, submenu, translate) {
442 var dir=this.direction=='rtl' ? 'left' : 'right';
443 var a=this.addMenuItem(menutext,null,true,null,translate);
444 a.className='ricoSubMenu';
445 var arrowdiv = a.appendChild(document.createElement('div'));
446 arrowdiv.className='rico-icon rico-'+dir+'-'+this.options.arrowColor;
447 Rico.setStyle(arrowdiv,{position:'absolute',top:'2px'});
448 arrowdiv.style[dir]='0px';
449 a.RicoSubmenu=submenu;
450 Rico.eventBind(a,"mouseover", Rico.eventHandle(this,'showSubMenu'));
451 //Rico.eventBind(a,"mouseout", Rico.eventHandle(this,'subMenuOut'));
454 showSubMenu: function(e) {
455 if (this.openSubMenu) this.hideSubMenu();
456 var a=Rico.eventElement(e);
457 if (!a.RicoSubmenu) a=a.parentNode; // event can happen on arrow div
458 if (!a.RicoSubmenu) return;
459 this.openSubMenu=a.RicoSubmenu;
460 this.openMenuAnchor=a;
461 if (Rico.hasClass(a,'ricoSubMenu')) {
462 Rico.removeClass(a,'ricoSubMenu');
463 Rico.addClass(a,'ricoSubMenuOpen');
465 a.RicoSubmenu.openmenu(parseInt(this.container.style.left)+a.offsetWidth, parseInt(this.container.style.top)+a.offsetTop, a.offsetWidth-2, a.offsetHeight+2,true);
469 subMenuOut: function(e) {
470 if (!this.openSubMenu) return;
472 var elem=Rico.eventElement(e);
473 var reltg = Rico.eventRelatedTarget(e) || e.toElement;
475 while (reltg != null && reltg != this.openSubMenu.div)
476 reltg=reltg.parentNode;
478 if (reltg == this.openSubMenu.div) return;
483 hideSubMenu: function() {
484 if (this.openMenuAnchor) {
485 Rico.removeClass(this.openMenuAnchor,'ricoSubMenuOpen');
486 Rico.addClass(this.openMenuAnchor,'ricoSubMenu');
487 this.openMenuAnchor=null;
489 if (this.openSubMenu) {
490 this.openSubMenu.hidemenu();
491 this.openSubMenu=null;
495 addMenuItemId: function(phraseId,action,enabled,title,target) {
496 if ( arguments.length < 3 ) enabled=true;
497 this.addMenuItem(Rico.getPhraseById(phraseId),action,enabled,title,target);
500 // 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
501 // action can also be a function
502 // action can also be a Rico.eventHandle, but set target='event' in this case
503 addMenuItem: function(menutext,action,enabled,title,target) {
504 if (arguments.length >= 3 && !enabled && !this.options.showDisabled) return null;
506 var a = document.createElement(typeof action=='string' ? 'a' : 'div');
507 if ( arguments.length < 3 || enabled ) {
508 if (typeof action=='string') {
510 if (target) a.target = target;
511 } else if (target=='event') {
512 Rico.eventBind(a,"click", action);
516 a.className = 'enabled';
517 if (this.defaultAction==null) this.defaultAction=action;
520 a.className = 'disabled';
522 a.innerHTML = menutext;
523 if (typeof title=='string')
525 a=this.content.appendChild(a);
526 Rico.eventBind(a,"mouseover", Rico.eventHandle(this,'mouseOver'));
527 Rico.eventBind(a,"mouseout", Rico.eventHandle(this,'mouseOut'));
531 mouseOver: function(e) {
532 if (this.highlightElem && this.highlightElem.className=='enabled-hover') {
533 // required for Safari
534 this.highlightElem.className='enabled';
535 this.highlightElem=null;
537 var elem=Rico.eventElement(e);
538 if (elem.parentNode == this.openMenuAnchor) elem=elem.parentNode;
539 if (this.openMenuAnchor && this.openMenuAnchor!=elem)
541 if (elem.className=='enabled') {
542 elem.className='enabled-hover';
543 this.highlightElem=elem;
547 mouseOut: function(e) {
548 var elem=Rico.eventElement(e);
549 if (elem.className=='enabled-hover') elem.className='enabled';
550 if (this.highlightElem==elem) this.highlightElem=null;
553 cancelmenu: function() {
554 if (!this.visible()) return;
555 if (this.hideFunc) this.hideFunc();
560 hidemenu: function() {
561 if (this.openSubMenu) this.openSubMenu.hidemenu();
568 Rico.SelectionSet = function(selectionSet, options) {
569 this.initialize(selectionSet, options);
572 Rico.SelectionSet.prototype = {
576 * @param selectionSet collection of DOM elements (or a CSS selection string)
577 * @param options object may contain any of the following:<dl>
578 * <dt>selectedClass</dt><dd>class name to add when element is selected, default is "selected"</dd>
579 * <dt>selectNode </dt><dd>optional function that returns the element to be selected</dd>
580 * <dt>onSelect </dt><dd>optional function that gets called when element is selected</dd>
581 * <dt>onFirstSelect</dt><dd>optional function that gets called the first time element is selected</dd>
582 * <dt>noDefault </dt><dd>when true, no element in the set is initially selected, default is false</dd>
583 * <dt>selectedIndex</dt><dd>index of the element that should be initially selected, default is 0</dd>
584 * <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>
585 * <dt>cookieDays </dt><dd>specifies how long cookie should persist (in days). If unspecified, then the cookie persists for the current session.</dd>
586 * <dt>cookiePath </dt><dd>optional cookie path</dd>
587 * <dt>cookieDomain </dt><dd>optional cookie domain</dd>
590 initialize: function(selectionSet, options){
591 Rico.log('SelectionSet#initialize');
592 this.options = options || {};
593 if (typeof selectionSet == 'string')
594 selectionSet = Rico.select(selectionSet);
595 this.previouslySelected = [];
596 this.selectionSet = [];
597 this.selectedClassName = this.options.selectedClass || Rico.theme.selected || "selected";
598 this.selectNode = this.options.selectNode || function(e){return e;};
599 this.onSelect = this.options.onSelect;
600 this.onFirstSelect = this.options.onFirstSelect;
602 this.clickHandler = function(idx) { self.selectIndex(idx); };
603 this.selectedIndex=-1;
604 for (var i=0; i<selectionSet.length; i++)
605 this.add(selectionSet[i]);
606 if (!this.options.noDefault) {
607 var cookieIndex=this.options.cookieName ? this.getCookie() : 0;
608 this.selectIndex(cookieIndex || this.options.selectedIndex || 0);
611 getCookie: function() {
612 var cookie = Rico.getCookie(this.options.cookieName);
613 if (!cookie) return 0;
614 var index = parseInt(cookie);
615 return index < this.selectionSet.length ? index : 0;
618 this.previouslySelected = [];
619 this._notifySelected(this.selectedIndex);
621 clearSelected: function() {
623 Rico.removeClass(this.selectNode(this.selected), this.selectedClassName);
625 getIndex: function(element) {
626 for (var i=0; i<this.selectionSet.length; i++) {
627 if (element == this.selectionSet[i]) return i;
631 select: function(element){
632 if (this.selected == element) return;
633 var i=this.getIndex(element);
634 if (i >= 0) this.selectIndex(i);
636 _notifySelected: function(index){
637 if (index < 0) return;
638 var element = this.selectionSet[index];
639 if (this.options.cookieName)
640 Rico.setCookie(this.options.cookieName, index, this.options.cookieDays, this.options.cookiePath, this.options.cookieDomain);
641 if (this.onFirstSelect && !this.previouslySelected[index]){
642 this.onFirstSelect(element, index);
643 this.previouslySelected[index] = true;
647 this.onSelect(index);
650 selectIndex: function(index){
651 if (this.selectedIndex == index || index >= this.selectionSet.length) return;
652 this.clearSelected();
653 this._notifySelected(index);
654 this.selectedIndex = index;
655 this.selected=this.selectionSet[index].element;
656 Rico.addClass(this.selectNode(this.selected), this.selectedClassName);
658 nextSelectIndex: function(){
659 return (this.selectedIndex + 1) % this.selectionSet.length;
661 nextSelectItem: function(){
662 return this.selectionSet[this.nextSelectIndex()];
664 selectNext: function(){
665 this.selectIndex(this.nextSelectIndex());
668 var index=this.selectionSet.length;
669 this.selectionSet[index] = new Rico._SelectionItem(item,index,this.clickHandler);
671 remove: function(item){
672 if (item==this.selected) this.clearSelected();
673 var i=this.getIndex(item);
675 this.selectionSet[i].remove();
676 this.selectionSet.splice(i,1);
678 removeAll: function(){
679 this.clearSelected();
680 while (this.selectionSet.length > 0) {
681 this.selectionSet.pop().remove();
687 Rico._SelectionItem=function(element,index,callback) {
688 this.add(element,index,callback);
691 Rico._SelectionItem.prototype = {
692 add: function(element,index,callback) {
693 this.element=element;
695 this.callback=callback;
696 this.handle=Rico.eventHandle(this,'click');
697 Rico.eventBind(element, "click", this.handle);
700 click: function(ev) {
701 this.callback(this.index);
705 Rico.eventUnbind(this.element, "click", this.handle);
710 Rico.HoverSet = function(hoverSet, options) {
711 this.initialize(hoverSet, options);
714 Rico.HoverSet.prototype = {
718 * @param hoverSet collection of DOM elements
719 * @param options object may contain any of the following:<dl>
720 * <dt>hoverClass</dt><dd> class name to add when mouse is over element, default is "hover"</dd>
721 * <dt>hoverNodes</dt><dd> optional function to select/filter which nodes are in the set</dd>
724 initialize: function(hoverSet, options){
725 Rico.log('HoverSet#initialize');
726 options = options || {};
727 this.hoverClass = options.hoverClass || Rico.theme.hover || "hover";
728 this.hoverFunc = options.hoverNodes || function(e){return [e];};
730 if (!hoverSet) return;
731 for (var i=0; i<hoverSet.length; i++)
732 this.add(hoverSet[i]);
734 add: function(item) {
735 this.hoverSet.push(new Rico._HoverItem(item,this.hoverFunc,this.hoverClass));
737 removeAll: function(){
738 while (this.hoverSet.length > 0) {
739 this.hoverSet.pop().remove();
745 Rico._HoverItem=function(element,selectFunc,hoverClass) {
746 this.add(element,selectFunc,hoverClass);
749 Rico._HoverItem.prototype = {
750 add: function(element,selectFunc,hoverClass) {
751 this.element=element;
752 this.selectFunc=selectFunc;
753 this.hoverClass=hoverClass;
754 this.movehandle=Rico.eventHandle(this,'move');
755 this.outhandle=Rico.eventHandle(this,'mouseout');
756 Rico.eventBind(element, "mousemove", this.movehandle);
757 Rico.eventBind(element, "mouseout", this.outhandle);
761 var elems=this.selectFunc(this.element);
762 for (var i=0; i<elems.length; i++)
763 Rico.addClass(elems[i],this.hoverClass);
766 mouseout: function(ev) {
767 var elems=this.selectFunc(this.element);
768 for (var i=0; i<elems.length; i++)
769 Rico.removeClass(elems[i],this.hoverClass);
773 Rico.eventUnbind(element, "mousemove", this.movehandle);
774 Rico.eventUnbind(element, "mouseout", this.outhandle);
779 /** @class core methods for transition effects */
780 Rico.ContentTransitionBase = function() {};
781 Rico.ContentTransitionBase.prototype = {
782 initBase: function(titles, contents, options) {
783 this.options = options || {};
784 this.titles = titles;
785 this.contents = contents;
786 this.hoverSet = new Rico.HoverSet(titles, options);
787 for (var i=0; i<contents.length; i++) {
788 if (contents[i]) Rico.hide(contents[i]);
791 this.selectionSet = new Rico.SelectionSet(titles, Rico.extend(options, { onSelect: function(idx) { self._finishSelect(idx); } }));
794 this.selectionSet.reset();
796 select: function(index) {
797 this.selectionSet.selectIndex(index);
799 _finishSelect: function(index) {
800 Rico.log('ContentTransitionBase#_finishSelect');
801 var panel = this.contents[index];
803 alert('Internal error: no panel @index='+index);
806 if ( this.selected == panel) return;
807 if (this.transition){
809 this.transition(panel);
811 panel.style.display='block';
814 if (this.selected) Rico.hide(this.selected);
815 panel.style.display='block';
817 this.selected = panel;
819 addBase: function(title, content){
820 this.titles.push(title);
821 this.contents.push(content);
822 this.hoverSet.add(title);
823 this.selectionSet.add(title);
825 //this.selectionSet.select(title);
827 removeAll: function(){
828 this.hoverSet.removeAll();
829 this.selectionSet.removeAll();
835 * @class Implements accordion effect
836 * @see Rico.ContentTransitionBase#initialize for construction parameters
837 * @extends Rico.ContentTransitionBase
839 Rico.Accordion = function(element, options) {
840 this.initialize(element, options);
843 Rico.Accordion.prototype = Rico.extend(new Rico.ContentTransitionBase(),
844 /** @lends Rico.Accordion# */
846 initialize: function(element, options) {
847 element=Rico.$(element);
848 element.style.overflow='hidden';
849 element.className=options.accClass || Rico.theme.accordion || "Rico_accordion";
850 if (typeof options.panelWidth=='number') options.panelWidth+="px";
851 if (options.panelWidth) element.style.width = options.panelWidth;
852 var panels=Rico.getDirectChildrenByTag(element,'div');
853 var items,titles=[], contents=[];
854 for (var i=0; i<panels.length; i++) {
855 items=Rico.getDirectChildrenByTag(panels[i],'div');
856 if (items.length>=2) {
857 items[0].className=options.titleClass || Rico.theme.accTitle || "Rico_accTitle";
858 items[1].className=options.contentClass || Rico.theme.accContent || "Rico_accContent";
859 titles.push(items[0]);
860 contents.push(items[1]);
861 var a=Rico.wrapChildren(items[0],'','','a');
862 a.href="javascript:void(0)";
865 Rico.log('creating Rico.Accordion for '+element.id+' with '+titles.length+' panels');
866 this.initBase(titles, contents, options);
867 this.selected.style.height = this.options.panelHeight + "px";
868 this.totSteps=(typeof options.duration =='number' ? options.duration : 200)/25;
870 transition: function(p){
871 if (!this.options.noAnimate) {
872 this.closing=this.selected;
876 this.timer=setInterval(function() { self.step(); },25);
878 p.style.height = this.options.panelHeight + "px";
879 if (this.selected) Rico.hide(this.selected);
880 p.style.display='block';
885 var oheight=Math.round(this.curStep/this.totSteps*this.options.panelHeight);
886 this.opening.style.height=oheight+'px';
887 this.closing.style.height=(this.options.panelHeight - oheight)+'px';
888 if (this.curStep==1) {
889 this.opening.style.paddingTop=this.opening.style.paddingBottom='0px';
890 this.opening.style.display='block';
892 if (this.curStep==this.totSteps) {
893 clearInterval(this.timer);
894 this.opening.style.paddingTop=this.opening.style.paddingBottom='';
895 Rico.hide(this.closing);
898 setPanelHeight: function(h) {
899 this.options.panelHeight = h;
900 this.selected.style.height = this.options.panelHeight + "px";
906 * @class Implements tabbed panel effect
907 * @see Rico.ContentTransitionBase#initialize for construction parameters
908 * @extends Rico.ContentTransitionBase
910 Rico.TabbedPanel = function(element, options) {
911 this.initialize(element, options);
914 Rico.TabbedPanel.prototype = Rico.extend(new Rico.ContentTransitionBase(),
916 initialize: function(element, options) {
917 element=Rico.$(element);
918 options=options || {};
919 if (typeof options.panelWidth=='number') options.panelWidth+="px";
920 if (typeof options.panelHeight=='number') options.panelHeight+="px";
921 element.className=options.tabClass || Rico.theme.tabPanel || "Rico_tabPanel";
922 if (options.panelWidth) element.style.width = options.panelWidth;
924 var allKids = element.childNodes;
925 for( var i = 0 ; i < allKids.length ; i++ ) {
926 if (allKids[i] && allKids[i].tagName && allKids[i].tagName.match(/^div|ul$/i))
927 items.push(allKids[i]);
929 if (items.length < 2) return;
930 var childTag=items[0].tagName.toLowerCase()=='ul' ? 'li' : 'div';
931 items[0].className=options.navContainerClass || Rico.theme.tabNavContainer || "Rico_tabNavContainer";
932 items[0].style.listStyle='none';
933 items[1].className=options.contentContainerClass || Rico.theme.tabContentContainer || "Rico_tabContentContainer";
934 var titles=Rico.getDirectChildrenByTag(items[0], childTag);
935 var contents=Rico.getDirectChildrenByTag(items[1],'div');
936 var direction=Rico.direction(element);
937 if (!options.corners) options.corners='top';
938 for (var i=0; i<titles.length; i++) {
939 if (direction == 'rtl') Rico.setStyle(titles[i], {'float':'right'});
940 titles[i].className=options.titleClass || Rico.theme.tabTitle || "Rico_tabTitle";
941 var a=Rico.wrapChildren(titles[i],'','','a');
942 a.href="javascript:void(0)";
943 contents[i].className=options.contentClass || Rico.theme.tabContent || "Rico_tabContent";
944 if (options.panelHeight) contents[i].style.overflow='auto';
945 if (options.corners!='none') {
946 if (options.panelHdrWidth) titles[i].style.width=options.panelHdrWidth;
947 Rico.Corner.round(titles[i], Rico.theme.tabCornerOptions || options);
950 options.selectedClass=Rico.theme.tabSelected || 'selected';
951 this.initBase(titles, contents, options);
952 if (this.selected) this.transition(this.selected);
954 transition: function(p){
955 Rico.log('TabbedPanel#transition '+typeof(p));
956 if (this.selected) Rico.hide(this.selected);
958 if (this.options.panelHeight) p.style.height = this.options.panelHeight;
968 round: function(e, options) {
972 bgColor : "fromParent",
974 nativeCorners: false // only use native corners?
976 Rico.extend(this.options, options || {});
977 if (typeof(Rico.getStyle(e,'border-radius'))=='string')
978 this._roundCornersStdCss(e);
979 else if (typeof(Rico.getStyle(e,'-webkit-border-radius'))=='string')
980 this._roundCornersWebKit(e);
981 else if (typeof(Rico.getStyle(e,'-moz-border-radius'))=='string')
982 this._roundCornersMoz(e);
983 else if (!this.options.nativeCorners)
984 this._roundCornersImpl(e);
987 _roundCornersStdCss: function(e) {
988 var radius=this.options.compact ? '4px' : '8px';
989 if (this._hasString(this.options.corners, "all"))
990 Rico.setStyle(e, {borderRadius:radius});
992 if (this._hasString(this.options.corners, "top", "tl")) Rico.setStyle(e, {borderTopLeftRadius:radius});
993 if (this._hasString(this.options.corners, "top", "tr")) Rico.setStyle(e, {borderTopRightRadius:radius});
994 if (this._hasString(this.options.corners, "bottom", "bl")) Rico.setStyle(e, {borderBottomLeftRadius:radius});
995 if (this._hasString(this.options.corners, "bottom", "br")) Rico.setStyle(e, {borderBottomRightRadius:radius});
999 _roundCornersWebKit: function(e) {
1000 var radius=this.options.compact ? '4px' : '8px';
1001 if (this._hasString(this.options.corners, "all"))
1002 Rico.setStyle(e, {WebkitBorderRadius:radius});
1004 if (this._hasString(this.options.corners, "top", "tl")) Rico.setStyle(e, {WebkitBorderTopLeftRadius:radius});
1005 if (this._hasString(this.options.corners, "top", "tr")) Rico.setStyle(e, {WebkitBorderTopRightRadius:radius});
1006 if (this._hasString(this.options.corners, "bottom", "bl")) Rico.setStyle(e, {WebkitBorderBottomLeftRadius:radius});
1007 if (this._hasString(this.options.corners, "bottom", "br")) Rico.setStyle(e, {WebkitBorderBottomRightRadius:radius});
1011 _roundCornersMoz: function(e) {
1012 var radius=this.options.compact ? '4px' : '8px';
1013 if (this._hasString(this.options.corners, "all"))
1014 Rico.setStyle(e, {MozBorderRadius:radius});
1016 if (this._hasString(this.options.corners, "top", "tl")) Rico.setStyle(e, {MozBorderRadiusTopleft:radius});
1017 if (this._hasString(this.options.corners, "top", "tr")) Rico.setStyle(e, {MozBorderRadiusTopright:radius});
1018 if (this._hasString(this.options.corners, "bottom", "bl")) Rico.setStyle(e, {MozBorderRadiusBottomleft:radius});
1019 if (this._hasString(this.options.corners, "bottom", "br")) Rico.setStyle(e, {MozBorderRadiusBottomright:radius});
1023 _roundCornersImpl: function(e) {
1024 var bgColor = this.options.bgColor == "fromParent" ? this._background(e.parentNode) : this.options.bgColor;
1025 e.style.position='relative';
1026 //this.options.numSlices = this.options.compact ? 2 : 4;
1027 if (this._hasString(this.options.corners, "all", "top", "tl")) this._createCorner(e,'top','left',bgColor);
1028 if (this._hasString(this.options.corners, "all", "top", "tr")) this._createCorner(e,'top','right',bgColor);
1029 if (this._hasString(this.options.corners, "all", "bottom", "bl")) this._createCorner(e,'bottom','left',bgColor);
1030 if (this._hasString(this.options.corners, "all", "bottom", "br")) this._createCorner(e,'bottom','right',bgColor);
1033 _createCorner: function(elem,tb,lr,bgColor) {
1034 //alert('Corner: '+tb+' '+lr+' bgColor='+typeof(bgColor));
1035 var corner = document.createElement("div");
1036 corner.className='ricoCorner';
1037 Rico.setStyle(corner,{width:'6px', height:'5px'});
1038 var borderStyle = Rico.getStyle(elem,'border-'+tb+'-style');
1039 var borderColor = borderStyle=='none' ? bgColor : Rico.getStyle(elem,'border-'+tb+'-color');
1040 //alert('Corner: '+tb+' '+borderStyle+borderColor+' '+);
1041 var pos=borderStyle=='none' ? '0px' : '-1px';
1042 corner.style[tb]=pos;
1043 corner.style[lr]=Rico.isIE && Rico.ieVersion<7 && lr=='right' && borderStyle!='none' ? '-2px' : '-1px';
1044 //corner.style[lr]='-1px';
1045 elem.appendChild(corner);
1046 var marginSizes = [ 0, 2, 3, 4, 4 ];
1047 if (tb=='bottom') marginSizes.reverse();
1048 var borderVal= borderStyle=='none' ? '0px none' : '1px solid '+borderColor;
1049 var d= lr=='left' ? 'Right' : 'Left';
1050 for (var i=0; i<marginSizes.length; i++) {
1051 var slice = document.createElement("div");
1052 Rico.setStyle(slice,{backgroundColor:bgColor,height:'1px'});
1053 slice.style['margin'+d]=marginSizes[i]+'px';
1054 slice.style['border'+d]=borderVal;
1055 corner.appendChild(slice);
1059 _background: function(elem) {
1061 var actualColor = Rico.getStyle(elem, "backgroundColor");
1063 // if color is tranparent, check parent
1064 // Safari returns "rgba(0, 0, 0, 0)", which means transparent
1065 if ( actualColor.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i) && elem.parentNode )
1066 return this._background(elem.parentNode);
1068 return actualColor == null ? "#ffffff" : actualColor;
1074 _hasString: function(str) {
1075 for(var i=1 ; i<arguments.length ; i++) {
1076 if (str.indexOf(arguments[i]) >= 0) return true;
1083 Rico.toColorPart = function(c) {
1084 return Rico.zFill(c, 2, 16);
1088 Rico.Color = function(red, green, blue) {
1089 this.initialize(red, green, blue);
1092 Rico.Color.prototype = {
1094 * @class Methods to manipulate color values.
1096 * @param red integer (0-255)
1097 * @param green integer (0-255)
1098 * @param blue integer (0-255)
1100 initialize: function(red, green, blue) {
1101 this.rgb = { r: red, g : green, b : blue };
1104 setRed: function(r) {
1108 setGreen: function(g) {
1112 setBlue: function(b) {
1116 setHue: function(h) {
1118 // get an HSB model, and set the new hue...
1119 var hsb = this.asHSB();
1122 // convert back to RGB...
1123 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
1126 setSaturation: function(s) {
1127 // get an HSB model, and set the new hue...
1128 var hsb = this.asHSB();
1131 // convert back to RGB and set values...
1132 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
1135 setBrightness: function(b) {
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 darken: function(percent) {
1145 var hsb = this.asHSB();
1146 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
1149 brighten: function(percent) {
1150 var hsb = this.asHSB();
1151 this.rgb = Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
1154 blend: function(other) {
1155 this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
1156 this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
1157 this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
1160 isBright: function() {
1161 var hsb = this.asHSB();
1162 return this.asHSB().b > 0.5;
1165 isDark: function() {
1166 return ! this.isBright();
1170 return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
1174 return "#" + Rico.toColorPart(this.rgb.r) + Rico.toColorPart(this.rgb.g) + Rico.toColorPart(this.rgb.b);
1178 return Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
1181 toString: function() {
1182 return this.asHex();
1188 * Factory method for creating a color from an RGB string
1189 * @param hexCode a 3 or 6 digit hex string, optionally preceded by a # symbol
1190 * @returns a Rico.Color object
1192 Rico.Color.createFromHex = function(hexCode) {
1193 if(hexCode.length==4) {
1194 var shortHexCode = hexCode;
1196 for(var i=1;i<4;i++)
1197 hexCode += (shortHexCode.charAt(i) + shortHexCode.charAt(i));
1199 if ( hexCode.indexOf('#') == 0 )
1200 hexCode = hexCode.substring(1);
1201 if (!hexCode.match(/^[0-9A-Fa-f]{6}$/)) return null;
1202 var red = hexCode.substring(0,2);
1203 var green = hexCode.substring(2,4);
1204 var blue = hexCode.substring(4,6);
1205 return new Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
1209 * Retrieves the background color of an HTML element
1210 * @param elem the DOM element whose background color should be retreived
1211 * @returns a Rico.Color object
1213 Rico.Color.createColorFromBackground = function(elem) {
1215 if (!elem.style) return new Rico.Color(255,255,255);
1216 var actualColor = Rico.getStyle(elem, "background-color");
1218 // if color is tranparent, check parent
1219 // Safari returns "rgba(0, 0, 0, 0)", which means transparent
1220 if ( actualColor.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i) && elem.parentNode )
1221 return Rico.Color.createColorFromBackground(elem.parentNode);
1223 if (actualColor == null) return new Rico.Color(255,255,255);
1225 if ( actualColor.indexOf("rgb(") == 0 ) {
1226 var colors = actualColor.substring(4, actualColor.length - 1 );
1227 var colorArray = colors.split(",");
1228 return new Rico.Color( parseInt( colorArray[0],10 ),
1229 parseInt( colorArray[1],10 ),
1230 parseInt( colorArray[2],10 ) );
1233 else if ( actualColor.indexOf("#") == 0 ) {
1234 return Rico.Color.createFromHex(actualColor);
1237 return new Rico.Color(255,255,255);
1241 * Converts hue/saturation/brightness to RGB
1242 * @returns a 3-element object: r=red, g=green, b=blue.
1244 Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
1250 if (saturation == 0) {
1251 red = parseInt(brightness * 255.0 + 0.5,10);
1256 var h = (hue - Math.floor(hue)) * 6.0;
1257 var f = h - Math.floor(h);
1258 var p = brightness * (1.0 - saturation);
1259 var q = brightness * (1.0 - saturation * f);
1260 var t = brightness * (1.0 - (saturation * (1.0 - f)));
1262 switch (parseInt(h,10)) {
1264 red = (brightness * 255.0 + 0.5);
1265 green = (t * 255.0 + 0.5);
1266 blue = (p * 255.0 + 0.5);
1269 red = (q * 255.0 + 0.5);
1270 green = (brightness * 255.0 + 0.5);
1271 blue = (p * 255.0 + 0.5);
1274 red = (p * 255.0 + 0.5);
1275 green = (brightness * 255.0 + 0.5);
1276 blue = (t * 255.0 + 0.5);
1279 red = (p * 255.0 + 0.5);
1280 green = (q * 255.0 + 0.5);
1281 blue = (brightness * 255.0 + 0.5);
1284 red = (t * 255.0 + 0.5);
1285 green = (p * 255.0 + 0.5);
1286 blue = (brightness * 255.0 + 0.5);
1289 red = (brightness * 255.0 + 0.5);
1290 green = (p * 255.0 + 0.5);
1291 blue = (q * 255.0 + 0.5);
1296 return { r : parseInt(red,10), g : parseInt(green,10) , b : parseInt(blue,10) };
1300 * Converts RGB value to hue/saturation/brightness
1301 * @param r integer (0-255)
1302 * @param g integer (0-255)
1303 * @param b integer (0-255)
1304 * @returns a 3-element object: h=hue, s=saturation, b=brightness.
1305 * (unlike some HSB documentation which states hue should be a value 0-360, this routine returns hue values from 0 to 1.0)
1307 Rico.Color.RGBtoHSB = function(r, g, b) {
1313 var cmax = (r > g) ? r : g;
1317 var cmin = (r < g) ? r : g;
1321 brightness = cmax / 255.0;
1323 saturation = (cmax - cmin)/cmax;
1327 if (saturation == 0)
1330 var redc = (cmax - r)/(cmax - cmin);
1331 var greenc = (cmax - g)/(cmax - cmin);
1332 var bluec = (cmax - b)/(cmax - cmin);
1335 hue = bluec - greenc;
1337 hue = 2.0 + redc - bluec;
1339 hue = 4.0 + greenc - redc;
1346 return { h : hue, s : saturation, b : brightness };
1350 * Returns a new XML document object
1352 Rico.createXmlDocument = function() {
1353 if (document.implementation && document.implementation.createDocument) {
1354 var doc = document.implementation.createDocument("", "", null);
1355 // some older versions of Moz did not support the readyState property
1356 // and the onreadystate event so we patch it!
1357 if (doc.readyState == null) {
1359 doc.addEventListener("load", function () {
1361 if (typeof doc.onreadystatechange == "function") {
1362 doc.onreadystatechange();
1369 if (window.ActiveXObject)
1370 return Rico.tryFunctions(
1371 function() { return new ActiveXObject('MSXML2.DomDocument'); },
1372 function() { return new ActiveXObject('Microsoft.DomDocument');},
1373 function() { return new ActiveXObject('MSXML.DomDocument'); },
1374 function() { return new ActiveXObject('MSXML3.DomDocument'); }