1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 /**
 26  * Creates a tab view.
 27  * @constructor
 28  * @class
 29  * This class represents a tabbed view. {@link DwtTabView} manages the z-index of the contained tabs. 
 30  * 
 31  * @param {hash}			params			a hash of parameters
 32  * @param {DwtComposite}	parent			the parent widget
 33  * @param {string}			className		the CSS class
 34  * @param {constant}		posStyle		the positioning style (see {@link DwtControl})
 35  * @param {string}			id				an explicit ID to use for the control's HTML element
 36  * 
 37  * @author Greg Solovyev
 38  * 
 39  * @extends DwtComposite
 40  */
 41 DwtTabView = function(params) {
 42 	if (arguments.length == 0) return;
 43 	params = Dwt.getParams(arguments, DwtListView.PARAMS);
 44 	params.className = params.className || "ZTabView";
 45 	params.posStyle = params.posStyle || DwtControl.ABSOLUTE_STYLE;
 46 	DwtComposite.call(this, params);
 47 
 48 	this._stateChangeEv = new DwtEvent(true);
 49 	this._stateChangeEv.item = this;
 50 
 51 	this._tabs = [];
 52 	this._tabIx = 1;
 53 	this._createHtml();
 54 
 55 	var tabGroupId = [this.toString(), this._htmlElId].join(" ");
 56 	this._tabGroup = new DwtTabGroup(tabGroupId);
 57 	this._tabGroup.addMember(this._tabBar);
 58 };
 59 
 60 DwtTabView.PARAMS = ["parent", "className", "posStyle"];
 61 
 62 DwtTabView.prototype = new DwtComposite;
 63 DwtTabView.prototype.constructor = DwtTabView;
 64 
 65 DwtTabView.prototype.isDwtTabView = true;
 66 DwtTabView.prototype.toString = function() { return "DwtTabView"; };
 67 
 68 DwtTabView.prototype.role = 'tablist';
 69 
 70 // Constants
 71 
 72 // Z-index consts for tabbed view contents are based on Dwt z-index consts
 73 DwtTabView.Z_ACTIVE_TAB	= Dwt.Z_VIEW+10;
 74 DwtTabView.Z_HIDDEN_TAB	= Dwt.Z_HIDDEN;
 75 DwtTabView.Z_TAB_PANEL	= Dwt.Z_VIEW+20;
 76 DwtTabView.Z_CURTAIN	= Dwt.Z_CURTAIN;
 77 
 78 DwtTabView.prototype.TEMPLATE = "dwt.Widgets#ZTabView";
 79 
 80 
 81 // Public methods
 82 
 83 
 84 /**
 85  * Adds a state change listener.
 86  * 
 87  * @param {AjxListener}		listener		the listener
 88  */
 89 DwtTabView.prototype.addStateChangeListener =
 90 function(listener) {
 91 	this._eventMgr.addListener(DwtEvent.STATE_CHANGE, listener);
 92 };
 93 
 94 /**
 95  * Removes a state change listener.
 96  * 
 97  * @param {AjxListener}		listener		the listener
 98  */
 99 DwtTabView.prototype.removeStateChangeListener =
100 function(listener) {
101 	this._eventMgr.removeListener(DwtEvent.STATE_CHANGE, listener);
102 };
103 
104 DwtTabView.prototype.getTabGroupMember = function() {
105 	return this._tabGroup;
106 };
107 
108 /**
109  * Adds a tab.
110  * 
111  * @param {string}						title					the text for the tab button
112  * @param {DwtTabViewPage|AjxCallback}	tabViewOrCallback		an instance of the tab view page or callback that returns an instance of {@link DwtTabViewPage}
113  *
114  * @return {string}		key for the added tab	This key can be used to retrieve the tab using {@link #getTab}
115  * 
116  * @see		#getTab
117  */
118 DwtTabView.prototype.addTab =
119 function (title, tabViewOrCallback, buttonId, index) {
120 	var tabKey = this._tabIx++;	
121 
122 	// create tab entry
123 	this._tabs[tabKey] = {
124 		title: title,
125 		button: this._tabBar.addButton(tabKey, title, buttonId, index)
126 	};
127 
128 	// add the page
129 	this.setTabView(tabKey, tabViewOrCallback);
130 
131 	// show the first tab
132 	if (tabKey==1) {
133 		var tabView = this.getTabView(tabKey);
134 		if(tabView) {
135 			tabViewOrCallback.showMe();
136 		}
137 		this._currentTabKey = tabKey;		
138 		this.switchToTab(tabKey);
139 	}
140 	// hide all the other tabs
141 	else if (tabViewOrCallback && !(tabViewOrCallback instanceof AjxCallback)) {
142 		tabViewOrCallback.hideMe();
143 		Dwt.setVisible(tabViewOrCallback.getHtmlElement(), false);
144 	}
145 
146 	this._tabBar.addSelectionListener(tabKey, new AjxListener(this, DwtTabView.prototype._tabButtonListener));
147 
148 	return tabKey;
149 };
150 
151 DwtTabView.prototype.enable =
152 function(enable) {
153 	for (var i in this._tabs) {
154 		var button = this._tabs[i].button;
155 		if (button) {
156 			button.setEnabled(enable);
157 		}
158 	}
159 };
160 
161 /**
162  * Gets the current tab.
163  * 
164  * @return {string}		the tab key
165  */
166 DwtTabView.prototype.getCurrentTab =
167 function() {
168 	return this._currentTabKey;
169 };
170 
171 /**
172  * Gets the tab count.
173  * 
174  * @return {number}		the number of tabs
175  */
176 DwtTabView.prototype.getNumTabs =
177 function() {
178 	return (this._tabs.length - 1);
179 };
180 
181 /**
182  * Gets the tab.
183  * 
184  * @param {string}		tabKey		the key for the tab
185  *
186  * @return {DwtTabViewPage}	the view tab
187  * 
188  * @see		#addTab
189  */
190 DwtTabView.prototype.getTab =
191 function (tabKey) {
192 	return (this._tabs && this._tabs[tabKey])
193 		? this._tabs[tabKey]
194 		: null;
195 };
196 
197 /**
198  * Gets the tab bar.
199  * 
200  * @return {DwtTabBar}		the tab bar
201  */
202 DwtTabView.prototype.getTabBar = function() {
203 	return this._tabBar;
204 };
205 
206 /**
207  * Gets the tab title.
208  * 
209  * @param {string}		tabKey		the tab key
210  *
211  * @return {string}		the title
212  */
213 DwtTabView.prototype.getTabTitle =
214 function(tabKey) {
215 	return (this._tabs && this._tabs[tabKey])
216 		? this._tabs[tabKey]["title"]
217 		: null;
218 };
219 
220 /**
221  * Gets the tab button.
222  * 
223  * @param {string}		tabKey		the tab key
224  *
225  * @return {DwtTabButton}		the tab button
226  */
227 DwtTabView.prototype.getTabButton =
228 function(tabKey) {
229 	return (this._tabs && this._tabs[tabKey])
230 		? this._tabs[tabKey]["button"]
231 		: null;
232 };
233 
234 /**
235  * Sets the tab view.
236  * 
237  * @param {string}						tabKey		the tab key
238  * @param {DwtTabViewPage|AjxCallback}	tabView		 an instance of the tab view page or callback that returns an instance of {@link DwtTabViewPage}
239  */
240 DwtTabView.prototype.setTabView =
241 function(tabKey, tabView) {
242 	var tab = this.getTab(tabKey);
243 	tab.view = tabView;
244 	if (tabView && !(tabView instanceof AjxCallback)) {
245 		this._pageEl.appendChild(tabView.getHtmlElement());
246 		tabView._tabKey = tabKey;
247 		if (tabKey == this._currentTabKey) {
248 			var tabGroup = tabView.getTabGroupMember();
249 			this._tabGroup.replaceMember(tab.tabGroup, tabGroup);
250 			tab.tabGroup = tabGroup;
251 		}
252 	}
253 };
254 
255 /**
256  * Gets the tab view.
257  * 
258  * @param {string}		tabKey		the tab key
259  *
260  * @return {DwtTabViewPage}		the tab view page
261  */
262 DwtTabView.prototype.getTabView =
263 function(tabKey) {
264 	var tab = this.getTab(tabKey);
265 	var tabView = tab && tab.view;
266 	if (tabView instanceof AjxCallback) {
267 		var callback = tabView;
268 		tabView = callback.run(tabKey);
269 		this.setTabView(tabKey, tabView);
270 		var size = this._getTabSize();
271 		tabView.setSize(size.x, size.y);
272 		tabView.setAttribute('aria-labelledby', tab.button.getHTMLElId());
273 	}
274 	return tabView;
275 };
276 
277 /**
278  * Switches to the tab view.
279  * 
280  * @param {string}		tabKey		the tab key
281  */
282 DwtTabView.prototype.switchToTab = 
283 function(tabKey) {
284 	var ntab = this.getTab(tabKey);
285 	if(ntab) {
286 		// remove old tab from tab-group
287 		var otab = this.getTab(this._currentTabKey);
288 		if (otab) {
289 			this._tabGroup.removeMember(otab.tabGroup);
290 		}
291 		// switch tab
292 		this._showTab(tabKey);
293 		this._tabBar.openTab(tabKey);
294 		// add new tab to tab-group
295 		if (!ntab.tabGroup && ntab.view) {
296 			ntab.tabGroup = ntab.view.getTabGroupMember();
297 		}
298 		this._tabGroup.addMember(ntab.tabGroup);
299 		// notify change
300 		if (this._eventMgr.isListenerRegistered(DwtEvent.STATE_CHANGE)) {
301 			this._eventMgr.notifyListeners(DwtEvent.STATE_CHANGE, this._stateChangeEv);
302 		}
303 	}
304 };
305 
306 DwtTabView.prototype.setBounds =
307 function(x, y, width, height) {
308 	DwtComposite.prototype.setBounds.call(this, x, y, width, height);
309 	this._resetTabSizes(width, height);
310 };
311 
312 DwtTabView.prototype.getActiveView =
313 function() {
314 	return this._tabs[this._currentTabKey].view;
315 };
316 
317 DwtTabView.prototype.getKeyMapName =
318 function() {
319 	return DwtKeyMap.MAP_TAB_VIEW;
320 };
321 
322 DwtTabView.prototype.resetKeyBindings =
323 function() {
324 	var kbm = this.shell.getKeyboardMgr();
325 	if (kbm.isEnabled()) {
326 		var kmm = kbm.__keyMapMgr;
327 		if (!kmm) { return; }
328 		var num = this.getNumTabs();
329 		var seqs = kmm.getKeySequences(DwtKeyMap.MAP_TAB_VIEW, "GoToTab");
330 		for (var k = 0; k < seqs.length; k++) {
331 			var ks = seqs[k];
332 			for (var i = 1; i <= num; i++) {
333 				var keycode = 48 + i;
334 				var newKs = ks.replace(/NNN/, keycode);
335 				kmm.setMapping(DwtKeyMap.MAP_TAB_VIEW, newKs, "GoToTab" + i);
336 			}
337 		}
338 		kmm.reloadMap(DwtKeyMap.MAP_TAB_VIEW);
339 	}
340 };
341 
342 DwtTabView.prototype.handleKeyAction =
343 function(actionCode) {
344 	DBG.println(AjxDebug.DBG3, "DwtTabView.handleKeyAction");
345 
346 	switch (actionCode) {
347 
348 		case DwtKeyMap.NEXT_TAB:
349 			var curTab = this.getCurrentTab();
350 			if (curTab < this.getNumTabs()) {
351 				this.switchToTab(curTab + 1);
352 			}
353 			break;
354 			
355 		case DwtKeyMap.PREV_TAB:
356 			var curTab = this.getCurrentTab();
357 			if (curTab > 1) {
358 				this.switchToTab(curTab - 1);
359 			}
360 			break;
361 		
362 		default:
363 			// Handle action code like "GoToTab3"
364 			var m = actionCode.match(DwtKeyMap.GOTO_TAB_RE);
365 			if (m && m.length) {
366 				var idx = m[1];
367 				if ((idx <= this.getNumTabs()) && (idx != this.getCurrentTab())) {
368 					this.switchToTab(idx);
369 				}
370 			} else {
371 				return false;
372 			}
373 	}
374 	return true;
375 };
376 
377 
378 // Protected methods
379 
380 DwtTabView.prototype._resetTabSizes =
381 function (width, height) {
382 	if (this._tabs && this._tabs.length) {
383 		for (var curTabKey in this._tabs) {
384 			var tabView = this._tabs[curTabKey].view;
385 			if (tabView && !(tabView instanceof AjxCallback)) {
386 				var contentHeight;
387 				contentHeight = contentHeight || height - Dwt.getSize(this._tabBarEl).y;
388 				tabView.resetSize(width, contentHeight);
389 			}	
390 		}
391 	}		
392 };
393 
394 DwtTabView.prototype._getTabSize =
395 function() {
396 	var size = this.getSize();
397 	var width = size.x || this.getHtmlElement().clientWidth;
398 	var height = size.y || this.getHtmlElement().clientHeight;
399 	var tabBarSize = this._tabBar.getSize();
400 	var tabBarHeight = tabBarSize.y || this._tabBar.getHtmlElement().clientHeight;
401 
402 	return new DwtPoint(width, (height - tabBarHeight));
403 };
404 
405 DwtTabView.prototype._createHtml =
406 function(templateId) {
407 	this._createHtmlFromTemplate(templateId || this.TEMPLATE, {id:this._htmlElId});
408 };
409 
410 DwtTabView.prototype._createHtmlFromTemplate =
411 function(templateId, data) {
412 	DwtComposite.prototype._createHtmlFromTemplate.call(this, templateId, data);
413 
414 	this._tabBarEl = document.getElementById(data.id+"_tabbar");
415 	this._tabBar = new DwtTabBar(this);
416 	this._tabBar.reparentHtmlElement(this._tabBarEl);
417 	this._pageEl = document.getElementById(data.id+"_page");
418 };
419 
420 DwtTabView.prototype._showTab = 
421 function(tabKey) {
422 	if (this._tabs && this._tabs[tabKey]) {
423 		this._currentTabKey = tabKey;
424 		this._hideAllTabs();						// hide all the tabs
425 		var tabView = this.getTabView(tabKey);		// make this tab visible
426 		if (tabView) {
427 			tabView.setVisible(true);
428 			tabView.showMe();
429 		}
430 	}
431 };
432 
433 DwtTabView.prototype._hideAllTabs = 
434 function() {
435 	if (this._tabs && this._tabs.length) {
436 		for (var curTabKey in this._tabs) {
437 			var tabView = this._tabs[curTabKey].view;
438 			if (tabView && !(tabView instanceof AjxCallback)) {
439 				tabView.hideMe();
440 				//this._tabs[curTabKey]["view"].setZIndex(DwtTabView.Z_HIDDEN_TAB);
441 				Dwt.setVisible(tabView.getHtmlElement(), false);
442 			}	
443 		}
444 	}
445 };
446 
447 DwtTabView.prototype._tabButtonListener = 
448 function (ev) {
449 	this.switchToTab(ev.item.getData("tabKey"));
450 };
451 
452 
453 //
454 // Class
455 //
456 
457 /**
458  * Creates a tab view page.
459  * @constructor
460  * @class
461  * DwtTabViewPage abstract class for a page in a tabbed view.
462  * Tab pages are responsible for creating their own HTML and populating/collecting
463  * data to/from any form fields that they display.
464  * 
465  * @param {DwtComposite}	parent			the parent widget
466  * @param {string}			className		the CSS class
467  * @param {constant}		posStyle		the positioning style (see {@link DwtControl})
468  * 
469  * @extends DwtPropertyPage
470  */
471 DwtTabViewPage = function(parent, className, posStyle, id) {
472 	if (arguments.length == 0) return;
473 	var params = Dwt.getParams(arguments, DwtTabViewPage.PARAMS);
474 	params.className = params.className || "ZTabPage";
475 	params.posStyle = params.posStyle || DwtControl.ABSOLUTE_STYLE;
476 	params.id = id || null;
477 	this._rendered = true; // by default UI creation is not lazy
478 
479 	DwtPropertyPage.call(this, params);
480 
481 	this._createHtml();
482 	this.getHtmlElement().style.overflowY = "auto";
483 	this.getHtmlElement().style.overflowX = "visible";
484 	if (params.contentTemplate) {
485 		this.getContentHtmlElement().innerHTML = AjxTemplate.expand(params.contentTemplate, this._htmlElId);
486 	}
487 };
488 
489 DwtTabViewPage.prototype = new DwtPropertyPage;
490 DwtTabViewPage.prototype.constructor = DwtTabViewPage;
491 
492 DwtTabViewPage.prototype.isDwtTabViewPage = true;
493 DwtTabViewPage.prototype.toString = function() { return "DwtTabViewPage"; };
494 
495 DwtTabViewPage.prototype.role = 'tabpanel';
496 
497 
498 DwtTabViewPage.prototype.TEMPLATE = "dwt.Widgets#ZTabPage";
499 
500 DwtTabViewPage.PARAMS = DwtPropertyPage.PARAMS.concat("contentTemplate");
501 
502 // Public methods
503 
504 /**
505  * Gets the content HTML element.
506  * 
507  * @return {Element}	the element
508  */
509 DwtTabViewPage.prototype.getContentHtmlElement =
510 function() {
511 	return this._contentEl || this.getHtmlElement();
512 };
513 
514 /**
515  * Shows the tab view page.
516  * 
517  */
518 DwtTabViewPage.prototype.showMe =
519 function() {
520 	this.setZIndex(DwtTabView.Z_ACTIVE_TAB);
521 	this.setAttribute('aria-selected', true);
522 	if (this.parent.getHtmlElement().offsetHeight > 80) { 						// if parent visible, use offsetHeight
523 		this._contentEl.style.height=this.parent.getHtmlElement().offsetHeight-80;
524 	} else {
525 		var parentHeight = parseInt(this.parent.getHtmlElement().style.height);	// if parent not visible, resize page to fit parent
526 		var units = AjxStringUtil.getUnitsFromSizeString(this.parent.getHtmlElement().style.height);
527 		if (parentHeight > 80) {
528 			this._contentEl.style.height = (Number(parentHeight-80).toString() + units);
529 		}
530 	}
531 
532 	this._contentEl.style.width = this.parent.getHtmlElement().style.width;	// resize page to fit parent
533 };
534 
535 /**
536  * Hides the tab view page.
537  */
538 DwtTabViewPage.prototype.hideMe = 
539 function() {
540 	this.setZIndex(DwtTabView.Z_HIDDEN_TAB);
541 	this.setAttribute('aria-selected', false);
542 };
543 
544 /**
545  * Resets the size.
546  * 
547  * @param {number|string}		newWidth		the width of the control (for example: 100, "100px", "75%", {@link Dwt.DEFAULT})
548  * @param {number|string}		newHeight		the height of the control (for example: 100, "100px", "75%", {@link Dwt.DEFAULT})
549  */
550 DwtTabViewPage.prototype.resetSize =
551 function(newWidth, newHeight) {
552 	this.setSize(newWidth, newHeight);
553 };
554 
555 
556 // Protected methods
557 
558 DwtTabViewPage.prototype._createHtml =
559 function(templateId) {
560 	this._createHtmlFromTemplate(templateId || this.TEMPLATE, {id:this._htmlElId});
561 };
562 
563 DwtTabViewPage.prototype._createHtmlFromTemplate =
564 function(templateId, data) {
565 	DwtPropertyPage.prototype._createHtmlFromTemplate.call(this, templateId, data);
566 	this._contentEl = document.getElementById(data.id+"_content") || this.getHtmlElement();
567 };
568 
569 
570 //
571 // Class
572 //
573 
574 /**
575  * Creates a tab bar.
576  * @constructor
577  * @class
578  * This class represents the tab bar, which is effectively a tool bar.
579  * 
580  * @param {DwtComposite}		parent			the parent widget
581  * @param {string}				tabCssClass		the tab CSS class
582  * @param {string}				btnCssClass		the button CSS class
583  * 
584  * @extends DwtToolBar
585  */
586 DwtTabBar = function(parent, tabCssClass, btnCssClass) {
587 	if (arguments.length == 0) return;
588 
589 	this._buttons = [];
590 	this._btnStyle = btnCssClass || "ZTab"; 									// REVISIT: not used
591 	this._btnImage = null;
592 	this._currentTabKey = 1;
593 	var myClass = tabCssClass || "ZTabBar";
594 
595 	DwtToolBar.call(this, {parent:parent, className:myClass, posStyle:DwtControl.STATIC_STYLE});
596 
597 	//Temp solution for bug 55391 
598 	//It is caused by float attribute in the td. The best solution is just as the main tab. No td
599 	//wrap the div. And this modification shouldn't affect the subclasses DwtTabBarFloat, otherwise, the _CASE_ 
600 	//xform item will be affect.
601 	//To do: modify it as the same as main tab
602 	if(AjxEnv.isFirefox){
603 		if(this._prefixEl && this.constructor == DwtTabBar)
604 			this._prefixEl.style.cssFloat = "none";
605 	}
606 };
607 
608 DwtTabBar.prototype = new DwtToolBar;
609 DwtTabBar.prototype.constructor = DwtTabBar;
610 
611 
612 // Constants
613 
614 DwtTabBar.prototype.TEMPLATE = "dwt.Widgets#ZTabBar";
615 
616 
617 // Public methods
618 
619 DwtTabBar.prototype.toString =
620 function() {
621 	return "DwtTabBar";
622 };
623 
624 /**
625  * Gets the current tab.
626  * 
627  * @return {string}		the tab key
628  */
629 DwtTabBar.prototype.getCurrentTab =
630 function() {
631 	return this._currentTabKey;
632 };
633 
634 /**
635  * Adds a state change listener.
636  * 
637  * @param {AjxListener}		listener		the listener
638  */
639 DwtTabBar.prototype.addStateChangeListener =
640 function(listener) {
641 	this._eventMgr.addListener(DwtEvent.STATE_CHANGE, listener);
642 };
643 
644 /**
645  * Removes a state change listener.
646  * 
647  * @param {AjxListener}		listener		the listener
648  */
649 DwtTabBar.prototype.removeStateChangeListener = 
650 function(listener) {
651 	this._eventMgr.removeListener(DwtEvent.STATE_CHANGE, listener);
652 };
653 
654 /**
655  * Adds a selection listener.
656  * 
657  * @param {string}			tabKey		the id used to create tab button in {@link DwtTabBar.addButton}
658  * @param {AjxListener}		listener	the listener
659  */
660 DwtTabBar.prototype.addSelectionListener =
661 function(tabKey, listener) {
662 	this._buttons[tabKey].addSelectionListener(listener);
663 };
664 
665 /**
666  * Removes a selection listener.
667  * 
668  * @param {string}			tabKey		the id used to create tab button in {@link DwtTabBar.addButton}
669  * @param {AjxListener}		listener	the listener
670  */
671 DwtTabBar.prototype.removeSelectionListener =
672 function(tabKey, listener) {
673 	this._buttons[tabKey].removeSelectionListener(listener);
674 };
675 
676 /**
677  * Adds a button.
678  * 
679  * @param {string}		tabKey		the the tab key
680  * @param {string}		tabTitle	the tab title
681  * @param {string}		id			the id
682  * @param {number}		index		the index
683  *
684  * @return {DwtTabButton}		the newly added button
685  */
686 DwtTabBar.prototype.addButton =
687 function(tabKey, tabTitle, id, index) {
688 	var b = this._buttons[tabKey] = new DwtTabButton(this, id, index);
689 	
690 	this._buttons[tabKey].addSelectionListener(new AjxListener(this, DwtTabBar._setActiveTab));
691 
692 	if (this._btnImage != null) {
693 		b.setImage(this._btnImage);
694 	}
695 
696 	if (tabTitle != null) {
697 		b.setText(tabTitle);
698 	}
699 
700 	b.setEnabled(true);
701 	b.setData("tabKey", tabKey);
702 
703 	if (parseInt(tabKey) == 1) {
704 		this.openTab(tabKey, true);
705 	}
706 
707 	// make sure that new button is selected properly
708 	var sindex = this.__getButtonIndex(this._currentTabKey);
709 	if (sindex != -1) {
710 		var nindex = this.__getButtonIndex(tabKey);
711 		if (nindex == sindex + 1) {
712 			Dwt.addClass(b.getHtmlElement(), DwtTabBar.SELECTED_NEXT);
713 		}
714 	}
715 
716 	return b;
717 };
718 
719 /**
720  * Gets the button.
721  * 
722  * @param {string}		tabKey		the id used to create tab button in {@link DwtTabBar.addButton}
723  *
724  * @return {DwtTabButton}		the button
725  */
726 DwtTabBar.prototype.getButton = 
727 function (tabKey) {
728 	return (this._buttons[tabKey])
729 		? this._buttons[tabKey]
730 		: null;
731 };
732 
733 /**
734  * Opens the tab.
735  *
736  * @param {string}		tabKey			the id used to create tab button in {@link DwtTabBar.addButton}
737  * @param {boolean}		skipNotify		if <code>true</code>, do not notify listeners
738  */
739 DwtTabBar.prototype.openTab = 
740 function(tabK, skipNotify) {
741 	this._currentTabKey = tabK;
742 	var cnt = this._buttons.length;
743 
744 	for (var ix = 0; ix < cnt; ix ++) {
745 		if (ix==tabK) { continue; }
746 
747 		var button = this._buttons[ix];
748 		if (button) {
749 			this.__markPrevNext(ix, false);
750 			button.setClosed();
751 		}
752 	}
753 
754 	var button = this._buttons[tabK];
755 	if (button) {
756 		button.setOpen();
757 		this.__markPrevNext(tabK, true);
758 	}
759 
760 	if (!skipNotify && this._eventMgr.isListenerRegistered(DwtEvent.STATE_CHANGE)) {
761 		this._eventMgr.notifyListeners(DwtEvent.STATE_CHANGE, this._stateChangeEv);
762 	}
763 };
764 
765 /**
766  * Greg Solovyev 1/4/2005 
767  * changed ev.target.offsetParent.offsetParent to
768  * lookup for the table up the elements stack, because the mouse down event may come from the img elements 
769  * as well as from the td elements.
770  * 
771  * @private
772  */
773 DwtTabBar._setActiveTab =
774 function(ev) {
775 	var tabK;
776 	if (ev && ev.item) {
777 		tabK=ev.item.getData("tabKey");
778 	} else if (ev && ev.target) {
779 		var elem = ev.target;
780 		while (elem.tagName != "TABLE" && elem.offsetParent )
781 			elem = elem.offsetParent;
782 
783 		tabK = elem.getAttribute("tabKey");
784 		if (tabK == null)
785 			return false;
786 	} else {
787 		return false;
788 	}
789 	this.openTab(tabK);
790 };
791 
792 
793 //
794 // Class
795 //
796 
797 /**
798  * Creates a tab button (i.e. a tab in a tab view).
799  * @constructor
800  * @class
801  * This class represents the tab in a tab view.
802  * 
803  * @param {DwtComposite}		parent			the parent widget
804  * @param {string}				id				the id
805  * @param {number}				index			the index
806  * @param {string}				className		the style class name
807  * 
808  * @extends DwtButton
809  */
810 DwtTabButton = function(parent, id, index, className) {
811 	if (arguments.length == 0) return;
812 	var tabStyle = className || "ZTab";
813 	DwtButton.call(this, {parent:parent, className:tabStyle, id:id, index:index});
814 };
815 
816 DwtTabButton.prototype = new DwtButton;
817 DwtTabButton.prototype.constructor = DwtTabButton;
818 
819 DwtTabButton.prototype.TEMPLATE = "dwt.Widgets#ZTab";
820 
821 DwtTabButton.prototype.isDwtTabButton = true;
822 DwtTabButton.prototype.toString = function() { return "DwtTabButton"; };
823 
824 DwtTabButton.prototype.role = 'tab';
825 
826 // Public methods
827 
828 
829 /**
830  * Changes the visual appearance to active tab.
831  */
832 DwtTabButton.prototype.setOpen = 
833 function() {
834 	this._isSelected = true;
835 	this.setDisplayState(DwtControl.SELECTED);
836 };
837 
838 /**
839  * Changes the visual appearance to inactive tab.
840  */
841 DwtTabButton.prototype.setClosed =
842 function() {
843 	this._isSelected = false;
844 	this.setDisplayState(DwtControl.NORMAL);
845 };
846 
847 DwtTabButton.prototype.setDisplayState = function(state) {
848 	if (this._isSelected && state != DwtControl.SELECTED) {
849 		state = [ DwtControl.SELECTED, state ].join(" ");
850 	}
851 	DwtButton.prototype.setDisplayState.call(this, state);
852 };
853 
854 
855 /**
856  * @class
857  * @constructor
858  * 
859  * @param {DwtComposite}		parent			the parent widget
860  * @param {string}				tabCssClass		the tab CSS class
861  * @param {string}				btnCssClass		the button CSS class
862  *
863  * @extends DwtTabButton
864  * 
865  * @private
866  */
867 DwtTabBarFloat = function(parent, tabCssClass, btnCssClass) {
868 	if (arguments.length == 0) return;
869 	DwtTabBar.call(this,parent,tabCssClass,btnCssClass)
870 };
871 
872 DwtTabBarFloat.prototype = new DwtTabBar;
873 DwtTabBarFloat.prototype.constructor = DwtTabBarFloat;
874 
875 DwtTabBarFloat.prototype.TEMPLATE = "dwt.Widgets#ZTabBarFloat";
876 
877 /**
878  * Adds a button.
879  * 
880  * @param {string}		tabKey			the the tab key
881  * @param {string}		tabTitle		the tab title
882  * @param {string}		id				the id
883  * 
884  * @return {DwtTabButton}		the newly added button
885  */
886 DwtTabBarFloat.prototype.addButton =
887 function(tabKey, tabTitle, id) {
888 	var b = this._buttons[tabKey] = new DwtTabButtonFloat(this, id);
889 	
890 	this._buttons[tabKey].addSelectionListener(new AjxListener(this, DwtTabBar._setActiveTab));
891 
892 	if (this._btnImage != null) {
893 		b.setImage(this._btnImage);
894 	}
895 
896 	if (tabTitle != null) {
897 		b.setText(tabTitle);
898 	}
899 
900 	b.setEnabled(true);
901 	b.setData("tabKey", tabKey);
902 
903 	if (parseInt(tabKey) == 1) {
904 		this.openTab(tabKey, true);
905 	}
906 
907 	// make sure that new button is selected properly
908 	var sindex = this.__getButtonIndex(this._currentTabKey);
909 	if (sindex != -1) {
910 		var nindex = this.__getButtonIndex(tabKey);
911 		if (nindex == sindex + 1) {
912 			Dwt.addClass(b.getHtmlElement(), DwtTabBar.SELECTED_NEXT);
913 		}
914 	}
915 
916 	return b;
917 };
918 
919 DwtTabButtonFloat = function(parent, id) {
920 	DwtTabButton.call(this, parent,id, undefined, "ZTab");
921 };
922 
923 DwtTabButtonFloat.prototype = new DwtTabButton;
924 DwtTabButtonFloat.prototype.constructor = DwtTabButtonFloat;
925 
926 DwtTabButtonFloat.prototype.TEMPLATE = "dwt.Widgets#ZTabFloat";
927