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 
 28 /**
 29  * Creates a dialog for choosing a tag.
 30  * @class
 31  * This class presents the user with their list of tags in a tree view so that they
 32  * can choose one. There is a text input that can be used to filter the list.
 33  *
 34  * @param	{DwtControl}	parent		the parent
 35  * @param	{String}	className		the class name
 36  * 
 37  * @extends	ZmDialog
 38  */
 39 ZmPickTagDialog = function(parent, className) {
 40 
 41 	var newButton = new DwtDialog_ButtonDescriptor(ZmPickTagDialog.NEW_BUTTON, ZmMsg._new, DwtDialog.ALIGN_LEFT);
 42 	var params = {parent:parent, className:className, title:ZmMsg.pickATag, extraButtons:[newButton], id: "ZmPickTagDialog"};
 43 	ZmDialog.call(this, params);
 44 
 45 	this._createControls();
 46 	this._setNameField(this._inputDivId);
 47 	this._tagTreeChangeListener = new AjxListener(this, this._handleTagTreeChange);
 48 	appCtxt.getTagTree().addChangeListener(this._tagTreeChangeListener);
 49 	this.registerCallback(ZmPickTagDialog.NEW_BUTTON, this._showNewDialog, this);
 50 	this._creatingTag = false;
 51 	this._treeViewListener = new AjxListener(this, this._treeViewSelectionListener);
 52 	this._lastVal = "";
 53 };
 54 
 55 ZmPickTagDialog.prototype = new ZmDialog;
 56 ZmPickTagDialog.prototype.constructor = ZmPickTagDialog;
 57 
 58 ZmPickTagDialog.NEW_BUTTON = DwtDialog.LAST_BUTTON + 1;
 59 
 60 ZmPickTagDialog.prototype.toString = 
 61 function() {
 62 	return "ZmPickTagDialog";
 63 };
 64 
 65 /**
 66  * Pops-up the dialog.
 67  * 
 68  * @param	{Hash}	params		a hash of parameters
 69  * @param	{ZmAccount}	params.account		the account
 70  */
 71 ZmPickTagDialog.prototype.popup = 
 72 function(params) {
 73 
 74 	if (appCtxt.isChildWindow) {
 75 		return; //disable for now.
 76 	}
 77 
 78 	params = params || {};
 79 	this._account = params.account;
 80 
 81 	if (appCtxt.multiAccounts && params.account) {
 82 		appCtxt.getTagTree().removeChangeListener(this._tagTreeChangeListener);
 83 		appCtxt.getTagTree(params.account).addChangeListener(this._tagTreeChangeListener);
 84 	}
 85 
 86 	// all this is done here instead of in the constructor due to multi-account issues
 87 	var overviewId = (appCtxt.multiAccounts && params.account)
 88 		? ([this.toString(), "-", params.account.name].join("")) : this.toString();
 89 
 90 	var ovParams = {
 91 		overviewId:			overviewId,
 92 		treeIds:			[ZmOrganizer.TAG],
 93 		fieldId:			this._tagTreeDivId,
 94 		account:			params.account
 95 	};
 96 	this._setOverview(ovParams, true);
 97 	this._tagTreeView = this._getOverview().getTreeView(ZmOrganizer.TAG);
 98 	this._tagTreeView.removeSelectionListener(this._treeViewListener);
 99 	this._tagTreeView.addSelectionListener(this._treeViewListener);
100 	var rootId = ZmOrganizer.getSystemId(ZmOrganizer.ID_ROOT);
101 	var root = this._tagTreeView.getTreeItemById(rootId);
102 	if (root) {
103 		root.enableSelection(false);
104 	}
105 
106 	this._loadTags();	// item list for this account's tree view will be cached after the first time
107 	this._resetTreeView();
108 	this._focusElement = this._inputField;
109 	this._inputField.setValue("");
110 	var tags = appCtxt.getTagTree(this._account).asList();
111 	if (tags.length == 1) {
112 		this._tagTreeView.setSelected(tags[0], true, true);
113 	}
114 	this.setButtonEnabled(DwtDialog.OK_BUTTON, this._tagTreeView.getSelected());
115 
116 	ZmDialog.prototype.popup.apply(this, arguments);
117 };
118 
119 ZmPickTagDialog.prototype._contentHtml = 
120 function() {
121 	this._tagTreeDivId = this._htmlElId + "_tagTreeDivId";
122 	this._inputDivId = this._htmlElId + "_inputDivId";
123 
124 	return AjxTemplate.expand("share.Widgets#ZmPickTagDialog", {id:this._htmlElId});
125 };
126 
127 ZmPickTagDialog.prototype._createControls =
128 function() {
129 	this._inputField = new DwtInputField({parent: this});
130 	document.getElementById(this._inputDivId).appendChild(this._inputField.getHtmlElement());
131 	this._inputField.addListener(DwtEvent.ONKEYUP, new AjxListener(this, this._handleKeyUp));
132 };
133 
134 ZmPickTagDialog.prototype._showNewDialog = 
135 function() {
136 	var dialog = appCtxt.getNewTagDialog();
137 	dialog.reset();
138 	dialog.registerCallback(DwtDialog.OK_BUTTON, this._newCallback, this);
139 	dialog.popup(null, this._account);
140 };
141 
142 ZmPickTagDialog.prototype._newCallback = 
143 function(parent, name) {
144 	appCtxt.getNewTagDialog().popdown();
145 	var ttc = this._opc.getTreeController(ZmOrganizer.TAG);
146 	ttc._doCreate(parent, name);
147 	this._creatingTag = true;
148 };
149 
150 ZmPickTagDialog.prototype._loadTags =
151 function() {
152 	this._tags = [];
153 	var items = this._tagTreeView.getTreeItemList();
154 	for (var i = 0, len = items.length; i < len; i++) {
155 		var tag = items[i].getData(Dwt.KEY_OBJECT);
156 		if (tag.id != ZmOrganizer.ID_ROOT) {
157 			this._tags.push({id:tag.id, name:tag.getName(false, null, true, true).toLowerCase()});
158 		}
159 	}
160 };
161 
162 ZmPickTagDialog.prototype._handleTagTreeChange =
163 function(ev) {
164 	// TODO - listener for changing tags
165 	if (ev.event == ZmEvent.E_CREATE && this._creatingTag) {
166 		var tag = ev.getDetail("organizers")[0];
167 		this._tagTreeView.setSelected(tag, true);
168 		this._creatingTag = false;
169 	}
170 	this._loadTags();
171 };
172 
173 ZmPickTagDialog.prototype._okButtonListener = 
174 function(ev) {
175 	var selectedTag = this._tagTreeView.getSelected();
176 	if (!selectedTag) {
177 		return;
178 	}
179 	DwtDialog.prototype._buttonListener.call(this, ev, [selectedTag]);
180 };
181 
182 ZmPickTagDialog.prototype._handleKeyUp =
183 function(ev) {
184 
185 	var key = DwtKeyEvent.getCharCode(ev);
186 	if (key === DwtKeyEvent.KEY_TAB) {
187 		return;
188 	}
189     else if (key === DwtKeyEvent.KEY_ARROW_DOWN) {
190 		this._overview[this._curOverviewId].focus();
191 		return;
192 	}
193 	
194 	var num = 0, firstMatch;
195 	var value = this._inputField.getValue().toLowerCase();
196 	if (value == this._lastVal) { return; }
197 	for (var i = 0, len = this._tags.length; i < len; i++) {
198 		var tagInfo = this._tags[i];
199 		var ti = this._tagTreeView.getTreeItemById(tagInfo.id);
200 		if (ti) {
201 			var matches = (tagInfo.name.indexOf(value) == 0);
202 			ti.setVisible(matches);
203 			if (matches && !firstMatch) {
204 				firstMatch = tagInfo.id;
205 			}
206 		}
207 	}
208 
209 	if (firstMatch) {
210 		this._tagTreeView.setSelected(appCtxt.getById(firstMatch), true, true);
211 	}
212 	else {
213 		this._tagTreeView.deselectAll();
214 	}
215 	this.setButtonEnabled(DwtDialog.OK_BUTTON, firstMatch);
216 
217 	this._lastVal = value;
218 };
219 
220 ZmPickTagDialog.prototype._resetTreeView =
221 function() {
222 	// make all tree items visible (in case there was prior filtering)
223 	for (var i = 0, len = this._tags.length; i < len; i++) {
224 		var ti = this._tagTreeView.getTreeItemById(this._tags[i].id);
225 		if (ti) {
226 			ti.setVisible(true);
227 		}
228 	}
229 
230 	this._tagTreeView.getHeaderItem().setExpanded(true, false, true);
231 };
232 
233 ZmPickTagDialog.prototype._treeViewSelectionListener =
234 function(ev) {
235 
236 	if (ev.detail === DwtTree.ITEM_DESELECTED) {
237 		this._inputField.setValue("");
238 		this.setButtonEnabled(DwtDialog.OK_BUTTON, false);
239 		return;
240 	}
241 
242 	if (ev.detail !== DwtTree.ITEM_SELECTED && ev.detail !== DwtTree.ITEM_DBL_CLICKED){
243 		return;
244 	}
245 
246 	if (!ev.item.isSelectionEnabled()) {
247 		return;
248 	}
249 
250 	var tag = ev.item.getData(Dwt.KEY_OBJECT);
251 	if (tag) {
252 		this.setButtonEnabled(DwtDialog.OK_BUTTON, true);
253 		var value = tag.getName(false, null, true, true);
254 		this._lastVal = value.toLowerCase();
255 		this._inputField.setValue(value);
256 		if (ev.detail == DwtTree.ITEM_DBL_CLICKED || ev.enter) {
257 			this._okButtonListener();
258 		}
259 	}
260 };
261 
262 ZmPickTagDialog.prototype._getTabGroupMembers =
263 function() {
264 	return [this._inputField, this._overview[this._curOverviewId]];
265 };
266 
267 ZmPickTagDialog.prototype._enterListener =
268 function(ev) {
269 	this._okButtonListener.call(this, ev);
270 };
271