1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 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 an overview, which holds tree views.
 28  *
 29  */
 30 
 31 /**
 32  * @class
 33  * Creates an overview. An overview is a {@link DwtComposite} that holds tree views.
 34  * 
 35  * @author Conrad Damon
 36  *
 37  * @param {Hash}	params 				a hash of parameters
 38  * @param	{String}	params.id 	the id for the HTML element
 39  * @param	{String}	params.overviewId 	the overview id
 40  * @param	{String}	params.containerId 	the overview container id (multi-account)
 41  * @param	{Array}	params.treeIds an array of organizer types that may be displayed in this overview
 42  * @param	{ZmZimbraAccount}	params.account		the account this overview belongs to
 43  * @param	{DwtControl}	params.parent			the containing widget
 44  * @param	{String}	params.overviewClass		the class name for overview DIV
 45  * @param	{constant}	params.posStyle				the positioning style for overview DIV
 46  * @param	{constant}	params.scroll				the scrolling style for overview DIV
 47  * @param	{Boolean}	params.selectionSupported <code>true</code> left-click selection is supported
 48  * @param	{Boolean}	params.actionSupported		<code>true</code> if right-click action menus are supported
 49  * @param	{Boolean}	params.dndSupported			<code>true</code> if drag-and-drop is supported
 50  * @param	{String}	params.headerClass			the class name for header item
 51  * @param	{Boolean}	params.showUnread			if <code>true</code>, unread counts will be shown
 52  * @param	{Boolean}	params.showNewButtons		if <code>true</code>, tree headers may have buttons for creating new organizers
 53  * @param	{constant}	params.treeStyle			the default display style for tree views
 54  * @param	{Boolean}	params.isCheckedByDefault	the default state for "checked" display style
 55  * @param	{Boolean}	params.noTooltips			if <code>true</code>, do not show toolt ips for tree items
 56  * @param	{Boolean}	params.skipImplicit			if <code>true</code>, do not save implicit prefs of expanded/collapsed node status for this overview (see ZmDialog.prototype._setOverview)
 57  * @param	{Boolean}	params.dynamicWidth			if <code>true</code>, the width is dynamic, i.e. the width is auto instead of fixed. Used for ZmDolderChooser so far.
 58  * @param {ZmOverviewController}	controller			the overview controller
 59  * 
 60  * @extends	DwtComposite
 61  */
 62 ZmOverview = function(params, controller) {
 63 
 64 	var overviewClass = params.overviewClass ? params.overviewClass : "ZmOverview";
 65 	params.id = params.id || ZmId.getOverviewId(params.overviewId);
 66 	DwtComposite.call(this, {parent:params.parent, className:overviewClass, posStyle:params.posStyle, id:params.id});
 67 
 68 	this._controller = controller;
 69 
 70 	this.setScrollStyle(params.scroll || Dwt.SCROLL_Y);
 71 
 72 	this.overviewId			= params.overviewId;
 73 	this.containerId		= params.containerId;
 74 	this.account			= params.account;
 75 	this.selectionSupported	= params.selectionSupported;
 76 	this.actionSupported	= params.actionSupported;
 77 	this.dynamicWidth		= params.dynamicWidth;
 78 	this.dndSupported		= params.dndSupported;
 79 	this.headerClass		= params.headerClass;
 80 	this.showUnread			= params.showUnread;
 81 	this.showNewButtons		= params.showNewButtons;
 82 	this.treeStyle			= params.treeStyle;
 83 	this.isCheckedByDefault = params.isCheckedByDefault;
 84 	this.noTooltips			= params.noTooltips;
 85 	this.isAppOverview		= params.isAppOverview;
 86 	this.skipImplicit 		= params.skipImplicit;
 87 	this.appName            = params.appName;
 88 
 89 	this._treeIds			= [];
 90 	this._treeHash			= {};
 91 	this._treeParents		= {};
 92 
 93 	// Create a parent div for each overview tree.
 94 	var doc = document;
 95 	var element = this.getHtmlElement();
 96 	if (params.treeIds) {
 97 		for (var i = 0, count = params.treeIds.length; i < count; i++) {
 98 			var div = doc.createElement("DIV");
 99 			var treeId = params.treeIds[i];
100 			this._treeParents[treeId] = div.id = [this.overviewId, treeId].join("-parent-");
101 			element.appendChild(div);
102 		}
103 	}
104 
105 	if (this.dndSupported) {
106 		this._scrollableContainerId = this.containerId || this.overviewId;
107 		var container = this.containerId ? document.getElementById(this.containerId) : this.getHtmlElement();
108 		var params = {container:container, threshold:15, amount:5, interval:10, id:this._scrollableContainerId};
109 		this._dndScrollCallback = new AjxCallback(null, DwtControl._dndScrollCallback, [params]);
110 	}
111 
112 	this.setAttribute('aria-label', ZmMsg.overviewLabel);
113 
114     // Let overview be a single tab stop, then manage focus among items using arrow keys
115     this.tabGroupMember = this;
116 };
117 
118 ZmOverview.prototype = new DwtComposite;
119 ZmOverview.prototype.constructor = ZmOverview;
120 
121 ZmOverview.prototype.isZmOverview = true;
122 ZmOverview.prototype.toString = function() { return "ZmOverview"; };
123 
124 ZmOverview.prototype.role = "navigation";
125 
126 
127 /**
128  * Gets the parent element for the given tree id.
129  * 
130  * @param	{String}	treeId		the tree id
131  * @return	{Object}	the tree parent element
132  */
133 ZmOverview.prototype.getTreeParent =
134 function(treeId) {
135 	return this._treeParents[treeId];
136 };
137 
138 /**
139  * Displays the given list of tree views in this overview.
140  *
141  * @param {Array}	treeIds		an array of organizer ids
142  * @param {Hash}	omit		the hash of organizer ids to ignore
143  */
144 ZmOverview.prototype.set =
145 function(treeIds, omit) {
146 	if (treeIds && treeIds.length) {
147 		for (var i = 0; i < treeIds.length; i++) {
148 			this.setTreeView(treeIds[i], omit);
149 		}
150 	}
151 };
152 
153 /**
154  * Sets the given tree view. Its tree controller is responsible for using the appropriate
155  * data tree to populate the tree view. The tree controller will be lazily created if
156  * necessary. The tree view is cleared before it is set. The tree view inherits options
157  * from this overview.
158  * 
159  * @param {String}	treeId	the organizer ID
160  * @param {Hash}	omit	a hash of organizer ids to ignore
161  */
162 ZmOverview.prototype.setTreeView = function(treeId, omit) {
163 
164 	if (!appCtxt.checkPrecondition(ZmOrganizer.PRECONDITION[treeId])) {
165 		return;
166 	}
167 
168 	AjxDispatcher.require(ZmOrganizer.ORG_PACKAGE[treeId]);
169 	var treeController = this._controller.getTreeController(treeId);
170 	if (!treeController) { return; }
171 	if (this._treeHash[treeId]) {
172 		treeController.clearTreeView(this.overviewId);
173 	} else {
174 		this._treeIds.push(treeId);
175 	}
176 	var params = {
177 		overviewId:		this.overviewId,
178 		omit:			omit,
179 		showUnread:		this.showUnread,
180 		account:		this.account
181 	};
182 	this._treeHash[treeId] = treeController.show(params); // render tree view
183 };
184 
185 ZmOverview.prototype.clearChangeListener = function(treeIds) {
186 	// Added for the attachMail zimlet, operating in a child window.  This clears the listeners added to
187 	// the parent window trees (which causes problems in IE when the child window closes).  See Bugs
188 	// 99453 and 99913
189 	for (var i = 0; i < treeIds.length; i++) {
190 		var treeController = this._controller.getTreeController(treeIds[i]);
191 		var changeListener = treeController._getTreeChangeListener();
192 		if (changeListener) {
193 			var folderTree = appCtxt.getFolderTree();
194 			if (folderTree) {
195 				folderTree.removeChangeListener(changeListener);
196 			}
197 		}
198 	}
199 }
200 
201 /**
202  * Gets the tree view.
203  * 
204  * @param	{String}	treeId		the tree id
205  * @return	{Object}	the tree view
206  */
207 ZmOverview.prototype.getTreeView =
208 function(treeId) {
209 	return this._treeHash[treeId];
210 };
211 
212 /**
213  * Gets the tree views.
214  * 
215  * @return	{Array}	an array of tree ids
216  */
217 ZmOverview.prototype.getTreeViews =
218 function() {
219 	return this._treeIds;
220 };
221 
222 /**
223  * Searches the tree views for the tree item whose data object has the given ID and type.
224  * 
225  * @param {int}	id			the id to look for
226  * @param {constant}	type			the item must also have this type
227  * @return	{Object}	the item or <code>null</code> if not found
228  */
229 ZmOverview.prototype.getTreeItemById =
230 function(id, type) {
231 	if (!id) { return null; }
232 	for (var i = 0; i < this._treeIds.length; i++) {
233 		var treeView = this._treeHash[this._treeIds[i]];
234 		if (treeView) {
235 			var item = treeView.getTreeItemById && treeView.getTreeItemById(id);
236 			if (item && (!type || (this._treeIds[i] == type))) {
237 				return item;
238 			}
239 		}
240 	}
241 	return null;
242 };
243 
244 /**
245 * Returns the first selected item within this overview.
246 * 
247 * @param	{Boolean}	typeOnly	if <code>true</code>, return the type only
248 * @return	{Object}	the item (or type if <code>typeOnly</code>) or <code>null</code> if not found
249 */
250 ZmOverview.prototype.getSelected =
251 function(typeOnly) {
252 	for (var i = 0; i < this._treeIds.length; i++) {
253 		var treeView = this._treeHash[this._treeIds[i]];
254 		if (treeView) {
255 			var item = treeView.getSelected();
256 			if (item) {
257 				return typeOnly ? treeView.type : item;
258 			} // otherwise continue with other treeviews to look for selected item
259 		}
260 	}
261 	return null;
262 };
263 
264 ZmOverview.prototype.deselectAllTreeViews =
265 function() {
266 	for (var i = 0; i < this._treeIds.length; i++) {
267 		var treeView = this._treeHash[this._treeIds[i]];
268 		if (treeView) {
269 			treeView.deselectAll();
270 		}
271 	}
272 };
273 
274 
275 /**
276  * Selects the item with the given ID within the given tree in this overview.
277  *
278  * @param {String}	id	the item id
279  * @param {constant}	type	the tree type
280  */
281 ZmOverview.prototype.setSelected =
282 function(id, type) {
283 	var ti, treeView;
284 	if (type) {
285 		treeView = this._treeHash[type];
286 		ti = treeView && treeView.getTreeItemById(id);
287 	} else {
288 		for (var type in this._treeHash) {
289 			treeView = this._treeHash[type];
290 			ti = treeView && treeView.getTreeItemById(id);
291 			if (ti) { break; }
292 		}
293 	}
294 
295 	if (ti && (this._selectedTreeItem != ti)) {
296 		treeView.setSelected(id, true, true);
297 	}
298 	this.itemSelected(ti);
299 };
300 
301 /**
302  * Given a tree item, de-selects all items in the overview's
303  * other tree views, enforcing single selection within the overview.
304  * Passing a null argument will clear selection in all tree views.
305  *
306  * @param {DwtTreeItem}	treeItem		the tree item
307  */
308 ZmOverview.prototype.itemSelected =
309 function(treeItem) {
310 	if (appCtxt.multiAccounts && treeItem) {
311 		var name = this.overviewId.substring(0, this.overviewId.indexOf(":"));
312 		var container = this._controller.getOverviewContainer(name);
313 		if (container) {
314 			container.deselectAll(this);
315 		}
316 	}
317 
318 	if (this._selectedTreeItem && (this._selectedTreeItem._tree != (treeItem && treeItem._tree))) {
319 		this._selectedTreeItem._tree.deselectAll();
320 	}
321 
322 	this._selectedTreeItem = treeItem;
323 };
324 
325 /**
326  * Clears the tree views.
327  */
328 ZmOverview.prototype.clear =
329 function() {
330 	for (var i = 0; i < this._treeIds.length; i++) {
331 		var treeId = this._treeIds[i];
332 		if (this._treeHash[treeId]) {
333 			var treeController = this._controller.getTreeController(treeId);
334 			treeController.clearTreeView(this.overviewId);
335 			delete this._treeHash[treeId];
336 		}
337 	}
338 };
339 
340 ZmOverview.prototype.clearSelection =
341 function() {
342 	if (this._selectedTreeItem) {
343 		this._selectedTreeItem._tree.deselectAll();
344 	}
345 };
346 
347 /**
348  * @private
349  */
350 ZmOverview.prototype._initialize =
351 function() {
352 	// do nothing. 
353 	// - called by DwtTreeItem b/c it thinks its adding another tree item
354 };
355 
356 /**
357  * @private
358  */
359 ZmOverview.prototype.focus = function() {
360 
361 	var item = this._selectedTreeItem;
362 	if (!item) {
363 		var tree = this._treeHash[this._treeIds[0]];
364 		if (tree) {
365 			item = tree._getNextTreeItem(true);
366 		}
367 	}
368 
369     if (item) {
370         item.focus();
371         item._tree.setSelection(item, false, true);
372         return item;
373     }
374 };
375 
376 /**
377  * @private
378  */
379 ZmOverview.prototype.blur = function() {
380 
381 	var item = this._selectedTreeItem;
382 	if (item) {
383 		item._blur();
384 	}
385 };
386 
387 /**
388  * Returns the next/previous selectable tree item within this overview, starting with the
389  * tree immediately after/before the given one. Used to handle tree item selection that
390  * spans trees.
391  *
392  * @param {Boolean}	next		if <code>true</code>, look for next item as opposed to previous item
393  * @param {ZmTreeView}	tree		the tree that we are just leaving
394  *
395  * @private
396  */
397 ZmOverview.prototype._getNextTreeItem =
398 function(next, tree) {
399 
400 	for (var i = 0; i < this._treeIds.length; i++) {
401 		if (this._treeHash[this._treeIds[i]] == tree) {
402 			break;
403 		}
404 	}
405 
406 	var nextItem = null;
407 	var idx = next ? i + 1 : i - 1;
408 	tree = this._treeHash[this._treeIds[idx]];
409 	while (tree) {
410 		nextItem = DwtTree.prototype._getNextTreeItem.call(tree, next);
411 		if (nextItem) {
412 			break;
413 		}
414 		idx = next ? idx + 1 : idx - 1;
415 		tree = this._treeHash[this._treeIds[idx]];
416 	}
417 
418 	return nextItem;
419 };
420