1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 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) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file contains the address book tree controller class. 27 * 28 */ 29 30 /** 31 * Creates an address book tree controller. 32 * @class 33 * This class is a controller for the tree view used by the address book 34 * application. This class uses the support provided by {@link ZmOperation}. 35 * 36 * @author Parag Shah 37 * 38 * @extends ZmFolderTreeController 39 */ 40 ZmAddrBookTreeController = function() { 41 42 ZmFolderTreeController.call(this, ZmOrganizer.ADDRBOOK); 43 44 this._listeners[ZmOperation.NEW_ADDRBOOK] = this._newListener.bind(this); 45 this._listeners[ZmOperation.SHARE_ADDRBOOK] = this._shareAddrBookListener.bind(this); 46 47 this._app = appCtxt.getApp(ZmApp.CONTACTS); 48 }; 49 50 ZmAddrBookTreeController.prototype = new ZmFolderTreeController; 51 ZmAddrBookTreeController.prototype.constructor = ZmAddrBookTreeController; 52 53 ZmAddrBookTreeController.prototype.isZmAddrBookTreeController = true; 54 ZmAddrBookTreeController.prototype.toString = function() { return "ZmAddrBookTreeController"; }; 55 56 // Public methods 57 58 /** 59 * Shows the controller and returns the resulting tree view. 60 * 61 * @param {Hash} params a hash of parameters 62 * @return {ZmTreeView} the tree view 63 */ 64 ZmAddrBookTreeController.prototype.show = 65 function(params) { 66 params.include = {}; 67 params.include[ZmFolder.ID_TRASH] = true; 68 params.showUnread = false; 69 var treeView = ZmFolderTreeController.prototype.show.call(this, params); 70 71 // contacts app has its own Trash folder so listen for change events 72 var trash = this.getDataTree().getById(ZmFolder.ID_TRASH); 73 if (trash) { 74 trash.addChangeListener(new AjxListener(this, this._trashChangeListener, treeView)); 75 } 76 77 return treeView; 78 }; 79 80 /** 81 * @private 82 */ 83 ZmAddrBookTreeController.prototype._trashChangeListener = 84 function(treeView, ev) { 85 var organizers = ev.getDetail("organizers"); 86 if (!organizers && ev.source) { 87 organizers = [ev.source]; 88 } 89 90 // handle one organizer at a time 91 for (var i = 0; i < organizers.length; i++) { 92 var organizer = organizers[i]; 93 94 if (organizer.id == ZmFolder.ID_TRASH && 95 ev.event == ZmEvent.E_MODIFY) 96 { 97 var fields = ev.getDetail("fields"); 98 if (fields && (fields[ZmOrganizer.F_TOTAL] || fields[ZmOrganizer.F_SIZE])) { 99 var ti = treeView.getTreeItemById(organizer.id); 100 if (ti) ti.setToolTipContent(organizer.getToolTip(true)); 101 } 102 } 103 } 104 }; 105 106 /** 107 * Enables/disables operations based on the given organizer ID. 108 * 109 * @private 110 */ 111 ZmAddrBookTreeController.prototype.resetOperations = 112 function(parent, type, id) { 113 var deleteText = ZmMsg.del; 114 var addrBook = appCtxt.getById(id); 115 var nId = addrBook ? addrBook.nId : ZmOrganizer.normalizeId(id); 116 var isTrash = (nId == ZmFolder.ID_TRASH); 117 118 var isDLs = (nId == ZmFolder.ID_DLS); 119 120 this.setVisibleIfExists(parent, ZmOperation.EMPTY_FOLDER, nId == ZmFolder.ID_TRASH); 121 122 if (isTrash) { 123 parent.enableAll(false); 124 parent.enable(ZmOperation.DELETE_WITHOUT_SHORTCUT, false); 125 var hasContent = ((addrBook.numTotal > 0) || (addrBook.children && (addrBook.children.size() > 0))); 126 parent.enable(ZmOperation.EMPTY_FOLDER,hasContent); 127 parent.getOp(ZmOperation.EMPTY_FOLDER).setText(ZmMsg.emptyTrash); 128 } 129 else if (isDLs) { 130 parent.enableAll(false); 131 } 132 else { 133 parent.enableAll(true); 134 if (addrBook) { 135 136 parent.enable([ZmOperation.NEW_ADDRBOOK], !addrBook.isReadOnly()); 137 138 if (addrBook.isSystem() || appCtxt.isExternalAccount()) { 139 parent.enable([ZmOperation.DELETE_WITHOUT_SHORTCUT, ZmOperation.RENAME_FOLDER], false); 140 } else if (addrBook.link) { 141 parent.enable([ZmOperation.SHARE_ADDRBOOK], !addrBook.link || addrBook.isAdmin()); 142 } 143 if (appCtxt.isOffline) { 144 var acct = addrBook.getAccount(); 145 parent.enable([ZmOperation.SHARE_ADDRBOOK], !acct.isMain && acct.isZimbraAccount); 146 } 147 } 148 } 149 150 if (addrBook) { 151 parent.enable(ZmOperation.EXPAND_ALL, (addrBook.size() > 0)); 152 } 153 154 var op = parent.getOp(ZmOperation.DELETE_WITHOUT_SHORTCUT); 155 if (op) { 156 op.setText(deleteText); 157 } 158 this._enableRecoverDeleted(parent, isTrash); 159 160 // we always enable sharing in case we're in multi-mbox mode 161 this._resetButtonPerSetting(parent, ZmOperation.SHARE_ADDRBOOK, appCtxt.get(ZmSetting.SHARING_ENABLED)); 162 }; 163 164 /** 165 * override to take care of not allowing dropping DLs do folders 166 * @param ev 167 * @private 168 */ 169 ZmAddrBookTreeController.prototype._dropListener = 170 function(ev) { 171 var items = AjxUtil.toArray(ev.srcData.data); 172 for (var i = 0; i < items.length; i++) { 173 var item = items[i]; 174 if (!item.isZmContact) { 175 continue; 176 } 177 if (item.isDistributionList()) { 178 ev.doIt = false; 179 return; 180 } 181 } 182 // perform default action 183 ZmFolderTreeController.prototype._dropListener.apply(this, arguments); 184 }; 185 186 187 // Protected methods 188 189 /** 190 * @private 191 */ 192 ZmAddrBookTreeController.prototype._getAllowedSubTypes = 193 function() { 194 var types = {}; 195 types[ZmOrganizer.SEARCH] = true; 196 types[this.type] = true; 197 return types; 198 }; 199 200 ZmAddrBookTreeController.prototype._getSearchTypes = 201 function(ev) { 202 return [ZmItem.CONTACT]; 203 }; 204 205 /** 206 * Returns a list of desired header action menu operations. 207 * 208 * @private 209 */ 210 ZmAddrBookTreeController.prototype._getHeaderActionMenuOps = 211 function() { 212 var ops = null; 213 if (appCtxt.get(ZmSetting.NEW_ADDR_BOOK_ENABLED)) { 214 ops = [ZmOperation.NEW_ADDRBOOK, ZmOperation.FIND_SHARES]; 215 } 216 return ops; 217 }; 218 219 /** 220 * Returns a list of desired action menu operations. 221 * 222 * @private 223 */ 224 ZmAddrBookTreeController.prototype._getActionMenuOps = function() { 225 226 var ops = []; 227 if (appCtxt.get(ZmSetting.NEW_ADDR_BOOK_ENABLED)) { 228 ops.push(ZmOperation.NEW_ADDRBOOK); 229 } 230 ops.push( 231 ZmOperation.EMPTY_FOLDER, 232 ZmOperation.RECOVER_DELETED_ITEMS, 233 ZmOperation.SHARE_ADDRBOOK, 234 ZmOperation.DELETE_WITHOUT_SHORTCUT, 235 ZmOperation.RENAME_FOLDER, 236 ZmOperation.EDIT_PROPS, 237 ZmOperation.EXPAND_ALL 238 ); 239 240 return ops; 241 }; 242 243 /** 244 * Returns a title for moving a folder. 245 * 246 * @private 247 */ 248 ZmAddrBookTreeController.prototype._getMoveDialogTitle = 249 function() { 250 return AjxMessageFormat.format(ZmMsg.moveAddrBook, this._pendingActionData.name); 251 }; 252 253 /** 254 * Returns the dialog for organizer creation. 255 * 256 * @private 257 */ 258 ZmAddrBookTreeController.prototype._getNewDialog = 259 function() { 260 return appCtxt.getNewAddrBookDialog(); 261 }; 262 263 264 // Listeners 265 266 /** 267 * @private 268 */ 269 ZmAddrBookTreeController.prototype._shareAddrBookListener = 270 function(ev) { 271 this._pendingActionData = this._getActionedOrganizer(ev); 272 appCtxt.getSharePropsDialog().popup(ZmSharePropsDialog.NEW, this._pendingActionData); 273 }; 274 275 ZmAddrBookTreeController.dlFolderClicked = 276 function() { 277 var request = { 278 _jsns: "urn:zimbraAccount", 279 "ownerOf": 1, 280 attrs: "zimbraDistributionListUnsubscriptionPolicy,zimbraDistributionListSubscriptionPolicy,zimbraHideInGal" 281 }; 282 283 var jsonObj = {GetAccountDistributionListsRequest: request}; 284 var respCallback = ZmAddrBookTreeController._handleAccountDistributionListResponse; 285 appCtxt.getAppController().sendRequest({jsonObj: jsonObj, asyncMode: true, callback: respCallback}); 286 }; 287 288 /** 289 * Called when a left click occurs (by the tree view listener). The folder that 290 * was clicked may be a search, since those can appear in the folder tree. The 291 * appropriate search will be performed. 292 * 293 * @param {ZmOrganizer} folder the folder or search that was clicked 294 * 295 * @private 296 */ 297 ZmAddrBookTreeController.prototype._itemClicked = 298 function(folder) { 299 if (folder.id == ZmFolder.ID_DLS) { 300 ZmAddrBookTreeController.dlFolderClicked(); 301 } 302 else if (folder.type == ZmOrganizer.SEARCH) { 303 // if the clicked item is a search (within the folder tree), hand 304 // it off to the search tree controller 305 var stc = this._opc.getTreeController(ZmOrganizer.SEARCH); 306 stc._itemClicked(folder); 307 } 308 else { 309 var capp = appCtxt.getApp(ZmApp.CONTACTS); 310 capp.currentSearch = null; 311 var query = capp.currentQuery = folder.createQuery(); 312 var sc = appCtxt.getSearchController(); 313 sc.setDefaultSearchType(ZmItem.CONTACT); 314 var acct = folder.getAccount(); 315 var params = { 316 query: query, 317 searchFor: ZmItem.CONTACT, 318 fetch: true, 319 sortBy: ZmSearch.NAME_ASC, 320 callback: new AjxCallback(this, this._handleSearchResponse, [folder]), 321 accountName: (acct && acct.name) 322 }; 323 sc.search(params); 324 325 if (folder.id != ZmFolder.ID_TRASH) { 326 var clc = AjxDispatcher.run("GetContactListController"); 327 var view = clc.getCurrentView(); 328 if (view) { 329 view.getAlphabetBar().reset(); 330 } 331 } 332 } 333 }; 334 335 /** 336 * @private 337 */ 338 ZmAddrBookTreeController.prototype._handleSearchResponse = 339 function(folder, result) { 340 // bug fix #19307 - Trash is special when in Contacts app since it 341 // is a FOLDER type in ADDRBOOK tree. So reset selection if clicked 342 if (folder.nId == ZmFolder.ID_TRASH) { 343 this._treeView[this._app.getOverviewId()].setSelected(folder, true); 344 } 345 }; 346 347 /** 348 * @private 349 */ 350 ZmAddrBookTreeController._handleAccountDistributionListResponse = 351 function(result) { 352 353 var contactList = new ZmContactList(null, true, ZmItem.CONTACT); 354 var dls = result._data.GetAccountDistributionListsResponse.dl; 355 if (dls) { 356 for (var i = 0; i < dls.length; i++) { 357 var dl = dls[i]; 358 var attrs = dl._attrs; 359 attrs.email = dl.name; // in this case the email comes in the "name" property. 360 attrs.type = "group"; 361 362 attrs[ZmContact.F_dlDisplayName] = dl.d || dl.name; 363 contactList.addFromDom(dl); 364 } 365 } 366 var clc = AjxDispatcher.run("GetContactListController"); 367 clc.show(contactList, true, ZmFolder.ID_DLS); 368 }; 369