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  * @overview
 26  * 
 27  * This file defines the zimbra application chooser.
 28  *
 29  */
 30 
 31 /**
 32  * @class
 33  * This class represents a zimbra application chooser. The chooser is the "tab application" toolbar shown
 34  * in the Zimbra Web Client. The toolbar buttons are represented as "tabs".
 35  * 
 36  * @param	{Hash}	params		a hash of parameters
 37  * 
 38  * @extends	ZmToolBar
 39  */
 40 ZmAppChooser = function(params) {
 41 
 42 	params.className = params.className || "ZmAppChooser";
 43 	params.width = appCtxt.getSkinHint("appChooser", "fullWidth") ? "100%" : null;
 44 
 45 	ZmToolBar.call(this, params);
 46 
 47     Dwt.setLocation(this.getHtmlElement(), Dwt.LOC_NOWHERE, Dwt.LOC_NOWHERE);
 48 
 49 	this.setScrollStyle(Dwt.CLIP);
 50 
 51 	this._buttonListener = new AjxListener(this, this._handleButton);
 52     this._initOverFlowTabs();
 53 	var buttons = params.buttons;
 54 	for (var i = 0; i < buttons.length; i++) {
 55 		var id = buttons[i];
 56 		if (id == ZmAppChooser.SPACER) {
 57 			this.addSpacer(ZmAppChooser.SPACER_HEIGHT);
 58 		} else {
 59 			this._createButton(id);
 60 		}
 61 	}
 62 
 63 	this._createPrecedenceList();
 64 	this._inited = true;
 65 };
 66 
 67 ZmAppChooser.prototype = new ZmToolBar;
 68 ZmAppChooser.prototype.constructor = ZmAppChooser;
 69 ZmAppChooser.prototype.role = "tablist";
 70 
 71 ZmAppChooser.prototype._initOverFlowTabs =
 72 function(){
 73     this._leftOverflow = document.getElementById("moreTabsLeftContainer");
 74     this._rightOverflow = document.getElementById("moreTabsRightContainer");
 75     if (this._leftOverflow) {
 76 		this._leftOverflow.onclick = this._showLeftTab.bind(this);
 77 	}
 78 	if (this._rightOverflow) {
 79     	this._rightOverflow.onclick = this._showRightTab.bind(this);
 80 	}
 81     this._leftBtnIndex = -1;
 82     this._deletedButtons = [];
 83 };
 84 
 85 ZmAppChooser.prototype._showLeftTab =
 86 function(){
 87     var items = this.getItems();
 88     if(this._leftBtnIndex > -1){
 89         items[this._leftBtnIndex].setVisible(true);
 90         this._leftBtnIndex--;
 91     }
 92     this._checkArrowVisibility();
 93 };
 94 
 95 ZmAppChooser.prototype._showRightTab =
 96 function(){
 97     var items = this.getItems();
 98     this._leftBtnIndex++;
 99     items[this._leftBtnIndex].setVisible(false);
100     this._checkArrowVisibility();
101 };
102 
103 ZmAppChooser.prototype._showTab =
104 function(id, ev){
105     var button = this._buttons[id];
106     this._moreTabsBtn.getMenu().popdown();
107     if (!button) return;
108     if (!button.getVisible()){ // Left side
109         var found = false;
110         for (var index in this._buttons){
111             if (!found && this._buttons[index].getHTMLElId() == button.getHTMLElId())
112               found = true;
113             else if (this._buttons[index].getVisible())
114                 break;
115             if (found){
116                 this._buttons[index].setVisible(true)
117                 this._leftBtnIndex--;
118             }
119         }
120     }else { // Right side
121         while(this._isTabOverflow(button.getHtmlElement())){
122             this._showRightTab();
123         }
124     }
125     this._checkArrowVisibility();
126     appCtxt.getAppController()._appButtonListener(ev);
127 
128 
129 };
130 
131 ZmAppChooser.prototype._attachMoreTabMenuItems =
132 function(menu){
133 
134     for (var deletedIndex=0; deletedIndex < this._deletedButtons.length; deletedIndex++){
135         var mi = menu.getItemById(ZmOperation.MENUITEM_ID, this._deletedButtons[deletedIndex] + "_menu");
136         if (mi) {
137             menu.removeChild(mi);
138             mi.dispose();
139         }
140     }
141 
142     this._deletedButtons = [];
143     for(var index in this._buttons){
144         var item = menu.getItemById(ZmOperation.MENUITEM_ID, index + "_menu");
145         if (item){
146             if (item.getText() != this._buttons[index].getText()){
147                 item.setText(this._buttons[index].getText());
148             }
149         } else {
150             var mi = new DwtMenuItem({parent:menu, style:DwtMenuItem.CASCADE_STYLE, id: index + "_menu"});
151             mi.setData(ZmOperation.MENUITEM_ID, index + "_menu" );
152             mi.setData(Dwt.KEY_ID, index);
153             mi.addSelectionListener(this._showTab.bind(this, index));
154             mi.setText(this._buttons[index].getText());
155         }
156     }
157 
158     if(menu.getHtmlElement().style.width == "0px"){
159         this._moreTabsBtn.popup();
160     }
161 };
162 
163 ZmAppChooser.prototype._showOverflowTabsMenu =
164 function(){
165     var menu = new DwtMenu({parent:this._moreTabsBtn});
166     menu.addPopupListener(new AjxListener(this, this._attachMoreTabMenuItems,[menu]));
167     return menu;
168 };
169 
170 
171 ZmAppChooser.prototype._checkArrowVisibility =
172 function(){
173     var items = this.getItems();
174     if (this._leftBtnIndex < 0)
175         this._setArrowVisibility(this._leftOverflow, "none");
176     else
177         this._setArrowVisibility(this._leftOverflow, "");
178 
179     if (!this._isTabOverflow(items[items.length -1].getHtmlElement())){
180         this._setArrowVisibility(this._rightOverflow, "none");
181     }else{
182         this._setArrowVisibility(this._rightOverflow, "");
183     }
184     this._adjustWidth();
185 };
186 
187 
188 
189 ZmAppChooser.prototype._setArrowVisibility =
190 function(element, option){
191 	if (!element) {
192 		return;
193 	}
194     element.style.display = option|| "";
195     var display = ((this._leftOverflow && this._leftOverflow.style.display == "none") && 
196 					(this._rightOverflow && this._rightOverflow.style.display == "none")) ? "none" : "";
197 	var moreTabsMenu = document.getElementById("moreTabsMenu");
198 	if (moreTabsMenu) {
199     	moreTabsMenu.style.display = display;
200 	}
201     if (display != "none" && !this._moreTabsBtn && moreTabsMenu ){
202         var containerEl = moreTabsMenu;
203         var button = new DwtToolBarButton({parent:DwtShell.getShell(window), id: "moreTabsMenuBtn", style:"background:none no-repeat scroll 0 0 transparent; border: none"});
204         button.setToolTipContent(ZmMsg.more, true);
205         button.setText("");
206         button.reparentHtmlElement(moreTabsMenu);
207         button.setMenu(new AjxListener(this, this._showOverflowTabsMenu));
208         this._moreTabsBtn =  button;
209     }
210 };
211 
212 /**
213  * Returns a string representation of the object.
214  * 
215  * @return		{String}		a string representation of the object
216  */
217 ZmAppChooser.prototype.toString =
218 function() {
219 	return "ZmAppChooser";
220 };
221 
222 //
223 // Constants
224 //
225 
226 ZmAppChooser.SPACER								= "spacer";
227 ZmAppChooser.B_HELP								= "Help";
228 ZmAppChooser.B_LOGOUT							= "Logout";
229 
230 ZmApp.CHOOSER_SORT[ZmAppChooser.SPACER]			= 160;
231 ZmApp.CHOOSER_SORT[ZmAppChooser.B_HELP]			= 170;
232 ZmApp.CHOOSER_SORT[ZmAppChooser.B_LOGOUT]		= 190;
233 
234 // hard code help/logout since they are not real "apps"
235 ZmApp.ICON[ZmAppChooser.B_HELP]					= "Help";
236 ZmApp.ICON[ZmAppChooser.B_LOGOUT]				= "Logoff";
237 ZmApp.CHOOSER_TOOLTIP[ZmAppChooser.B_HELP]		= "goToHelp";
238 ZmApp.CHOOSER_TOOLTIP[ZmAppChooser.B_LOGOUT]	= "logOff";
239 
240 ZmAppChooser.SPACER_HEIGHT = 10;
241 
242 //
243 // Data
244 //
245 
246 ZmAppChooser.prototype.TEMPLATE = "share.Widgets#ZmAppChooser";
247 ZmAppChooser.prototype.ITEM_TEMPLATE = "share.Widgets#ZmAppChooserItem";
248 ZmAppChooser.prototype.SPACER_TEMPLATE = "dwt.Widgets#ZmAppChooserSpacer";
249 
250 //
251 // Public methods
252 //
253 /**
254  * Adds a selection listener.
255  * 
256  * @param	{AjxListener}	listener	the listener
257  */
258 ZmAppChooser.prototype.addSelectionListener =
259 function(listener) {
260 	this.addListener(DwtEvent.SELECTION, listener);
261 };
262 
263 ZmAppChooser.prototype._checkTabOverflowAdd =
264 function(button) {
265     var display = "none";
266     if (this._isTabOverflow(button)){
267         display = "";
268     }
269     this._setArrowVisibility(this._rightOverflow, display);
270     this._adjustWidth();
271 };
272 
273 ZmAppChooser.prototype._isTabOverflow =
274 function(tab){
275         var tabPos = tab.offsetLeft + tab.clientWidth + 30;
276         if (!this._refElement){
277             this._refElement = document.getElementById(this._refElementId);
278         }
279         var container = this._refElement && this._refElement.parentNode;
280 
281         if (!container) return false;
282         var offsetWidth = container.offsetWidth;
283         return (offsetWidth < tabPos);
284 };
285 
286 ZmAppChooser.prototype._adjustWidth =
287 function(){
288     var container = this._refElement && this._refElement.parentNode;
289 	if (container && container.offsetWidth >= 30) {
290     	this._refElement.style.maxWidth = this._refElement.style.width = container.offsetWidth - 30 + "px";
291     	this._refElement.style.overflow = "hidden";
292 	}
293 
294 };
295 
296 ZmAppChooser.prototype._checkTabOverflowDelete =
297 function(index){
298     var items = this.getItems();
299     if(this._isTabOverflow(items[items.length - 1])){
300       return;
301     }
302     this._showLeftTab();
303 }
304 
305 
306 /**
307  * Adds a button to the toolbar.
308  * 
309  * @param	{String}	id		the button id
310  * @param	{Hash}		params		a hash of parameters
311  * @param	{String}	params.text			the text
312  * @param	{String}	params.image		the image
313  * @param	{int}	params.index		the index
314  * @param	{String}	params.tooltip		the tool top
315  * @param	{String}	params.textPrecedence	the image precedence
316  * @param	{String}	params.imagePrecedence		the image precedence
317  * 
318  * @return	{ZmAppButton}		the newly created button
319  */
320 ZmAppChooser.prototype.addButton =
321 function(id, params) {
322 
323 	var buttonParams = {
324 		parent:		this,
325 		id:			ZmId.getButtonId(ZmId.APP, id),
326 		text:		params.text,
327 		image:		params.image,
328 		leftImage:	params.leftImage,
329 		rightImage:	params.rightImage,
330 		index:		params.index
331 	};
332     buttonParams.style = params.style ? params.style : DwtLabel.IMAGE_LEFT;
333     var button = new ZmAppButton(buttonParams);
334 	button.setToolTipContent(params.tooltip, true);
335 	button.textPrecedence = params.textPrecedence;
336 	button.imagePrecedence = params.imagePrecedence;
337 	button.setData(Dwt.KEY_ID, id);
338 	button.addSelectionListener(this._buttonListener);
339 	this._buttons[id] = button;
340     this._checkTabOverflowAdd(button.getHtmlElement());
341 	return button;
342 };
343 
344 /**
345  * Removes a button.
346  * 
347  * @param	{String}	id		the id of the button to remove
348  */
349 ZmAppChooser.prototype.removeButton =
350 function(id) {
351 	var button = this._buttons[id];
352 	if (button) {
353         var index = this.__getButtonIndex(id);
354 		button.dispose();
355 		this._buttons[id] = null;
356 		delete this._buttons[id];
357         if (this._moreTabsBtn &&
358             this._moreTabsBtn.getMenu() &&
359             this._moreTabsBtn.getMenu().getItemCount() > 0){
360             this._deletedButtons.push(id);
361         }
362         this._checkTabOverflowDelete(index);
363 	}
364 };
365 
366 /**
367  * Replaces a button.
368  * 
369  * @param	{String}	oldId		the old button id
370  * @param	{String}	newId		the new button id
371  * @param	{Hash}		params		a hash of parameters
372  * @param	{String}	params.text		the text
373  * @param	{String}	params.image	the image
374  * @param	{int}	params.index		the index
375  * @param	{String}	params.tooltip			the tool tip
376  * @param	{String}	params.textPrecedence	the text display precedence
377  * @param	{String}	params.imagePrecedence	the image display precedence
378  * 
379  * @return	{ZmAppButton}		the newly created button
380  */
381 ZmAppChooser.prototype.replaceButton =
382 function(oldId, newId, params) {
383 	if (!this._buttons[oldId]) { return null; }
384 	params.index = this.__getButtonIndex(oldId);
385 	this.removeButton(oldId);
386 	return this.addButton(newId, params);
387 };
388 
389 ZmAppChooser.prototype.getButton =
390 function(id) {
391 	return this._buttons[id];
392 };
393 
394 /**
395  * Sets the specified button as selected.
396  * 
397  * @param	{String}	id		the button id
398  */
399 ZmAppChooser.prototype.setSelected =
400 function(id) {
401 	var oldBtn = this._buttons[this._selectedId];
402 	if (this._selectedId && oldBtn) {
403         this.__markPrevNext(this._selectedId, false);
404 		oldBtn.setSelected(false);
405     }
406 
407 	var newBtn = this._buttons[id];
408 	if (newBtn) {
409 		newBtn.setSelected(true);
410 		this.setAttribute('aria-activedescendant', newBtn.getHTMLElId());
411 
412 		if (newBtn._toggleText != null && newBtn._toggleText != "") {
413 			// hide text for previously selected button first
414 			if (oldBtn) {
415 				oldBtn._toggleText = (oldBtn._toggleText != null && oldBtn._toggleText != "")
416 					? oldBtn._toggleText : oldBtn.getText();
417 				oldBtn.setText("");
418 			}
419 
420 			// reset original text for  newly selected button
421 			newBtn.setText(newBtn._toggleText);
422 			newBtn._toggleText = null;
423 		}
424 	}
425 
426 	this._selectedId = id;
427 };
428 
429 /**
430  * @private
431  */
432 ZmAppChooser.prototype._createButton =
433 function(id) {
434 	this.addButton(id, {text:ZmMsg[ZmApp.NAME[id]] || ZmApp.NAME[id], image:ZmApp.ICON[id], tooltip:ZmMsg[ZmApp.CHOOSER_TOOLTIP[id]],
435 						textPrecedence:ZmApp.TEXT_PRECEDENCE[id], imagePrecedence:ZmApp.IMAGE_PRECEDENCE[id]});
436 };
437 
438 /**
439  * @private
440  */
441 ZmAppChooser.prototype._handleButton =
442 function(evt) {
443 	this.notifyListeners(DwtEvent.SELECTION, evt);
444 };
445