1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2004, 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) 2004, 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 * This file contains the contact list controller class. 27 */ 28 29 /** 30 * Creates an empty contact list controller. 31 * @class 32 * This class manages list views of contacts. So far there are two different list 33 * views, one that shows the contacts in a traditional list format, and the other 34 * which shows them as business cards. Since there are two views, we need to keep 35 * track of which is the current view. 36 * 37 * @author Roland Schemers 38 * @author Conrad Damon 39 * 40 * @param {DwtControl} container the containing shell 41 * @param {ZmApp} app the containing application 42 * @param {constant} type type of controller 43 * @param {string} sessionId the session id 44 * @param {ZmSearchResultsController} searchResultsController containing controller 45 * 46 * @extends ZmListController 47 */ 48 ZmContactListController = function(container, contactsApp, type, sessionId, searchResultsController) { 49 50 if (arguments.length == 0) { return; } 51 ZmListController.apply(this, arguments); 52 53 this._viewFactory = {}; 54 this._viewFactory[ZmId.VIEW_CONTACT_SIMPLE] = ZmContactSplitView; 55 56 if (this.supportsDnD()) { 57 this._dragSrc = new DwtDragSource(Dwt.DND_DROP_MOVE); 58 this._dragSrc.addDragListener(this._dragListener.bind(this)); 59 } 60 61 this._listChangeListener = this._handleListChange.bind(this); 62 63 this._listeners[ZmOperation.EDIT] = this._editListener.bind(this); 64 this._listeners[ZmOperation.PRINT] = null; // override base class to do nothing 65 this._listeners[ZmOperation.PRINT_CONTACT] = this._printListener.bind(this); 66 this._listeners[ZmOperation.PRINT_ADDRBOOK] = this._printAddrBookListener.bind(this); 67 this._listeners[ZmOperation.NEW_GROUP] = this._groupListener.bind(this); 68 69 this._parentView = {}; 70 }; 71 72 ZmContactListController.prototype = new ZmListController; 73 ZmContactListController.prototype.constructor = ZmContactListController; 74 75 ZmContactListController.prototype.isZmContactListController = true; 76 ZmContactListController.prototype.toString = function() { return "ZmContactListController"; }; 77 78 ZmContactListController.ICON = {}; 79 ZmContactListController.ICON[ZmId.VIEW_CONTACT_SIMPLE] = "ListView"; 80 81 ZmContactListController.MSG_KEY = {}; 82 ZmContactListController.MSG_KEY[ZmId.VIEW_CONTACT_SIMPLE] = "contactList"; 83 84 ZmContactListController.SEARCH_TYPE_CANONICAL = 1 << 0; 85 ZmContactListController.SEARCH_TYPE_GAL = 1 << 1; 86 ZmContactListController.SEARCH_TYPE_NEW = 1 << 2; 87 ZmContactListController.SEARCH_TYPE_ANYWHERE = 1 << 3; 88 89 ZmContactListController.VIEWS = [ZmId.VIEW_CONTACT_SIMPLE]; 90 91 // Public methods 92 93 /** 94 * Shows the search results. 95 * 96 * @param {Object} searchResult the search results 97 * @param {Boolean} isGalSearch <code>true</code> if results from GAL search 98 * @param {String} folderId the folder id 99 */ 100 ZmContactListController.prototype.show = 101 function(searchResult, isGalSearch, folderId) { 102 103 this._searchType = isGalSearch 104 ? ZmContactListController.SEARCH_TYPE_GAL 105 : ZmContactListController.SEARCH_TYPE_CANONICAL; 106 107 this._folderId = folderId; 108 var selectedContacts; 109 110 if (searchResult.isZmContactList) { 111 this.setList(searchResult); // set as canonical list of contacts 112 this._list._isShared = false; // this list is not a search of shared items 113 selectedContacts = this._listView[this._currentViewId] && this._listView[this._currentViewId].getSelection(); 114 this._contactSearchResults = false; 115 } 116 else if (searchResult.isZmSearchResult) { 117 this._searchType |= ZmContactListController.SEARCH_TYPE_NEW; 118 this.setList(searchResult.getResults(ZmItem.CONTACT)); 119 120 // HACK - find out if user did a "is:anywhere" search (for printing) 121 if (searchResult.search && searchResult.search.isAnywhere()) { 122 this._searchType |= ZmContactListController.SEARCH_TYPE_ANYWHERE; 123 } 124 125 if (searchResult.search && searchResult.search.userText && this.getCurrentView()) { 126 this.getCurrentView().getAlphabetBar().reset(); 127 } 128 129 if (isGalSearch) { 130 this._list = this._list || new ZmContactList(searchResult.search, true); 131 this._list._isShared = false; 132 this._list.isGalPagingSupported = AjxUtil.isSpecified(searchResult.getAttribute("offset")); 133 } else { 134 // find out if we just searched for a shared address book 135 var addrbook = folderId ? appCtxt.getById(folderId) : null; 136 this._list._isShared = addrbook ? addrbook.link : false; 137 } 138 139 this._list.setHasMore(searchResult.getAttribute("more")); 140 141 selectedContacts = this._listView[this._currentViewId] && this._listView[this._currentViewId].getSelection(); 142 ZmListController.prototype.show.apply(this, [searchResult, this._currentViewId]); 143 this._contactSearchResults = true; 144 } 145 146 // reset offset if list view has been created 147 var view = this._currentViewId; 148 if (this._listView[view]) { 149 this._listView[view].offset = 0; 150 } 151 this.switchView(view, true); 152 153 if (selectedContacts && selectedContacts.length && this._listView[view]) { 154 this._listView[view].setSelection(selectedContacts[0]); 155 } 156 }; 157 158 159 160 /** 161 * Change how contacts are displayed. There are two views: the "simple" view 162 * shows a list of contacts on the left and the selected contact on the right; 163 * the "cards" view shows contacts as business cards. 164 * 165 * @param {constant} view the view to show 166 * @param {Boolean} force if <code>true</code>, render view even if it's the current view 167 * @param {Boolean} initialized if <code>true</code>, app has been initialized 168 * @param {Boolean} stageView if <code>true</code>, stage the view but don't push it 169 */ 170 ZmContactListController.prototype.switchView = 171 function(view, force, initialized, stageView) { 172 if (view && ((view != this._currentViewId) || force)) { 173 this._currentViewId = view; 174 DBG.timePt("setting up view", true); 175 this._setup(view); 176 DBG.timePt("done setting up view"); 177 178 var elements = this.getViewElements(view, this._parentView[view]); 179 180 // call initialize before _setView since we havent set the new view yet 181 if (!initialized) { 182 this._initializeAlphabetBar(view); 183 } 184 185 this._setView({ view: view, 186 viewType: this._currentViewType, 187 noPush: this.isSearchResults, 188 elements: elements, 189 isAppView: true, 190 stageView: stageView}); 191 if (this.isSearchResults) { 192 // if we are switching views, make sure app view mgr is up to date on search view's components 193 appCtxt.getAppViewMgr().setViewComponents(this.searchResultsController.getCurrentViewId(), elements, true); 194 } 195 this._resetNavToolBarButtons(); 196 197 // HACK: reset search toolbar icon (its a hack we're willing to live with) 198 if (this.isGalSearch() && !this._list.isGalPagingSupported) { 199 appCtxt.getSearchController().setDefaultSearchType(ZmId.SEARCH_GAL); 200 if (this._list.hasMore()) { 201 var d = appCtxt.getMsgDialog(); 202 d.setMessage(ZmMsg.errorSearchNotExpanded); 203 d.popup(); 204 } 205 } 206 207 this._setTabGroup(this._tabGroups[view]); 208 209 if (!initialized) { 210 var list = this._listView[view].getList(); 211 if (list) { 212 this._listView[view].setSelection(list.get(0)); 213 } 214 } 215 } 216 }; 217 218 /** 219 * Gets the folder id. 220 * 221 * @return {String} the folder id 222 */ 223 ZmContactListController.prototype.getFolderId = 224 function() { 225 return this._folderId; 226 }; 227 228 /** 229 * Checks if the search is a GAL search. 230 * 231 * @return {Boolean} <code>true</code> if GAL search 232 */ 233 ZmContactListController.prototype.isGalSearch = 234 function() { 235 return ((this._searchType & ZmContactListController.SEARCH_TYPE_GAL) != 0); 236 }; 237 238 /** 239 * Returns the split view. 240 * 241 * @return {ZmContactSplitView} the split view 242 */ 243 ZmContactListController.prototype.getCurrentView = 244 function() { 245 return this._parentView[this._currentViewId]; 246 }; 247 ZmContactListController.prototype.getParentView = ZmContactListController.prototype.getCurrentView; 248 249 /** 250 * Search the alphabet. 251 * 252 * @param {String} letter the letter 253 * @param {String} endLetter the end letter 254 */ 255 ZmContactListController.prototype.searchAlphabet = 256 function(letter, endLetter) { 257 var folderId = this._folderId || ZmFolder.ID_CONTACTS; 258 var folder = appCtxt.getById(folderId); 259 var query = folder ? folder.createQuery() : null; 260 261 if (query) { 262 var params = { 263 query: query, 264 types: [ZmItem.CONTACT], 265 offset: 0, 266 limit: (this._listView[this._currentViewId].getLimit()), 267 lastId: 0, 268 lastSortVal: letter, 269 endSortVal: endLetter 270 }; 271 appCtxt.getSearchController().search(params); 272 } 273 }; 274 275 /** 276 * @private 277 */ 278 ZmContactListController.prototype._getMoreSearchParams = 279 function(params) { 280 params.endSortVal = this._activeSearch && this._activeSearch.search && this._activeSearch.search.endSortVal; 281 }; 282 283 ZmContactListController.prototype.getKeyMapName = 284 function() { 285 return ZmKeyMap.MAP_CONTACTS; 286 }; 287 288 ZmContactListController.prototype.handleKeyAction = 289 function(actionCode) { 290 DBG.println(AjxDebug.DBG3, "ZmContactListController.handleKeyAction"); 291 var isExternalAccount = appCtxt.isExternalAccount(); 292 var isWebClientOffline = appCtxt.isWebClientOffline(); 293 switch (actionCode) { 294 295 case ZmKeyMap.EDIT: 296 if (isExternalAccount || isWebClientOffline) { break; } 297 this._editListener(); 298 break; 299 300 case ZmKeyMap.PRINT: 301 if (appCtxt.get(ZmSetting.PRINT_ENABLED) && !isWebClientOffline) { 302 this._printListener(); 303 } 304 break; 305 306 case ZmKeyMap.PRINT_ALL: 307 if (appCtxt.get(ZmSetting.PRINT_ENABLED) && !isWebClientOffline) { 308 this._printAddrBookListener(); 309 } 310 break; 311 312 case ZmKeyMap.NEW_MESSAGE: 313 if (isExternalAccount) { break; } 314 this._composeListener(); 315 break; 316 317 default: 318 return ZmListController.prototype.handleKeyAction.call(this, actionCode); 319 } 320 return true; 321 }; 322 323 /** 324 * @private 325 */ 326 ZmContactListController.prototype.mapSupported = 327 function(map) { 328 return (map == "list"); 329 }; 330 331 332 // Private and protected methods 333 334 335 /** 336 * @private 337 */ 338 ZmContactListController.prototype._getToolBarOps = 339 function() { 340 var toolbarOps = []; 341 toolbarOps.push(ZmOperation.EDIT, 342 ZmOperation.SEP, 343 ZmOperation.DELETE, ZmOperation.SEP, 344 ZmOperation.MOVE_MENU, ZmOperation.TAG_MENU, ZmOperation.SEP, 345 ZmOperation.PRINT); 346 return toolbarOps; 347 }; 348 349 /** 350 * @private 351 */ 352 ZmContactListController.prototype._getSecondaryToolBarOps = 353 function() { 354 if (appCtxt.isExternalAccount()) { return []; } 355 var list = [ZmOperation.SEARCH_MENU]; 356 357 if (appCtxt.get(ZmSetting.MAIL_ENABLED)) { 358 list.push(ZmOperation.NEW_MESSAGE); 359 } 360 361 list.push(ZmOperation.SEP, ZmOperation.CONTACTGROUP_MENU); 362 // list.push(ZmOperation.QUICK_COMMANDS); 363 364 return list; 365 }; 366 367 /** 368 * @private 369 */ 370 ZmContactListController.prototype._getActionMenuOps = 371 function() { 372 var list = this._participantOps(); 373 list.push(ZmOperation.SEP, 374 ZmOperation.CONTACTGROUP_MENU, 375 ZmOperation.TAG_MENU, 376 ZmOperation.DELETE, 377 ZmOperation.MOVE, 378 ZmOperation.PRINT_CONTACT); 379 // list.push(ZmOperation.QUICK_COMMANDS); 380 381 return list; 382 }; 383 384 ZmContactListController.getDefaultViewType = 385 function() { 386 return ZmId.VIEW_CONTACT_SIMPLE; 387 }; 388 ZmContactListController.prototype.getDefaultViewType = ZmContactListController.getDefaultViewType; 389 390 /** 391 * @private 392 */ 393 ZmContactListController.prototype._createNewView = 394 function(view) { 395 var params = {parent:this._container, posStyle:Dwt.ABSOLUTE_STYLE, 396 controller:this, dropTgt:this._dropTgt}; 397 var viewType = this.getCurrentViewType(); 398 this._parentView[view] = new this._viewFactory[viewType](params); 399 var listView = this._parentView[view].getListView(); 400 if (this._dragSrc) { 401 listView.setDragSource(this._dragSrc); 402 } 403 404 return listView; 405 }; 406 407 /** 408 * @private 409 */ 410 ZmContactListController.prototype._getTagMenuMsg = 411 function(num) { 412 return AjxMessageFormat.format(ZmMsg.AB_TAG_CONTACTS, num); 413 }; 414 415 /** 416 * @private 417 */ 418 ZmContactListController.prototype._getMoveDialogTitle = 419 function(num) { 420 return AjxMessageFormat.format(ZmMsg.AB_MOVE_CONTACTS, num); 421 }; 422 423 /** 424 * @private 425 */ 426 ZmContactListController.prototype._getMoveParams = 427 function(dlg) { 428 var params = ZmListController.prototype._getMoveParams.apply(this, arguments); 429 params.hideNewButton = !appCtxt.get(ZmSetting.NEW_ADDR_BOOK_ENABLED); 430 var omit = {}; 431 var folderTree = appCtxt.getFolderTree(); 432 if (!folderTree) { return params; } 433 var folders = folderTree.getByType(ZmOrganizer.ADDRBOOK); 434 for (var i = 0; i < folders.length; i++) { 435 var folder = folders[i]; 436 if (folder.link && folder.isReadOnly()) { 437 omit[folder.id] = true; 438 } 439 } 440 params.omit = omit; 441 params.description = ZmMsg.targetAddressBook; 442 443 return params; 444 }; 445 446 /** 447 * @private 448 */ 449 ZmContactListController.prototype._getSearchFolderId = 450 function() { 451 return this._folderId; 452 }; 453 454 /** 455 * @private 456 */ 457 ZmContactListController.prototype._initializeToolBar = 458 function(view) { 459 if (!this._toolbar[view]) { 460 ZmListController.prototype._initializeToolBar.call(this, view); 461 var tb = this._toolbar[view]; 462 // this._setupViewMenu(view, true); 463 this._setupPrintMenu(view); 464 tb.addFiller(); 465 this._initializeNavToolBar(view); 466 this._setupContactGroupMenu(tb); 467 appCtxt.notifyZimlets("initializeToolbar", [this._app, this._toolbar[view], this, view], {waitUntilLoaded:true}); 468 } else { 469 // this._setupViewMenu(view, false); 470 this._setupDeleteButton(this._toolbar[view]); 471 } 472 }; 473 474 ZmContactListController.prototype._initializeTabGroup = 475 function(view) { 476 if (this._tabGroups[view]) { return; } 477 478 ZmListController.prototype._initializeTabGroup.call(this, view); 479 480 var tg = this._tabGroups[view]; 481 482 tg.addMemberBefore(this._parentView[view].getAlphabetBar(), 483 this._view[view].getTabGroupMember()); 484 485 tg.addMember(this._parentView[view].getTabGroupMember()); 486 } 487 488 // If we're in the Trash folder, change the "Delete" button tooltip 489 ZmContactListController.prototype._setupDeleteButton = function(parent) { 490 var folder = this._getSearchFolder(); 491 var inTrashFolder = (folder && folder.nId == ZmFolder.ID_TRASH); 492 var tooltip = inTrashFolder ? ZmMsg.deletePermanentTooltip : ZmMsg.deleteTooltip; 493 var deleteButton = parent.getButton(ZmOperation.DELETE); 494 if(deleteButton){ 495 deleteButton.setToolTipContent(ZmOperation.getToolTip(ZmOperation.DELETE, this.getKeyMapName(), tooltip)); 496 } 497 }; 498 499 500 /** 501 * @private 502 */ 503 ZmContactListController.prototype._initializeNavToolBar = 504 function(view) { 505 this._toolbar[view].addOp(ZmOperation.TEXT); 506 var text = this._itemCountText[view] = this._toolbar[view].getButton(ZmOperation.TEXT); 507 text.addClassName("itemCountText"); 508 }; 509 510 /** 511 * @private 512 */ 513 ZmContactListController.prototype._initializeActionMenu = 514 function(view) { 515 ZmListController.prototype._initializeActionMenu.call(this); 516 517 var mi = this._actionMenu.getItemById(ZmOperation.KEY_ID, ZmOperation.PRINT_CONTACT); 518 if (mi) { 519 mi.setText(ZmMsg.print); 520 } 521 522 ZmOperation.setOperation(this._actionMenu, ZmOperation.CONTACT, ZmOperation.EDIT_CONTACT); 523 this._setupContactGroupMenu(this._actionMenu); 524 525 }; 526 527 ZmContactListController.prototype.getSearchFromText = 528 function() { 529 return ZmMsg.findEmailFromContact; 530 }; 531 532 ZmContactListController.prototype.getSearchToText = 533 function() { 534 return ZmMsg.findEmailToContact; 535 }; 536 537 /** 538 * @private 539 */ 540 ZmContactListController.prototype._initializeAlphabetBar = 541 function(view) { 542 if (view == this._currentViewId) { return; } 543 544 var pv = this._parentView[this._currentViewId]; 545 var alphaBar = pv ? pv.getAlphabetBar() : null; 546 var current = alphaBar ? alphaBar.getCurrent() : null; 547 var idx = current ? current.getAttribute("_idx") : null; 548 if (idx) { 549 var newAlphaBar = this._parentView[view].getAlphabetBar(); 550 if (newAlphaBar) 551 newAlphaBar.setButtonByIndex(idx); 552 } 553 }; 554 555 /** 556 * Load contacts into the given view and perform layout. 557 * 558 * @private 559 */ 560 ZmContactListController.prototype._setViewContents = 561 function(view) { 562 DBG.timePt("setting list"); 563 this._list.removeChangeListener(this._listChangeListener); 564 this._list.addChangeListener(this._listChangeListener); 565 this._listView[view].set(this._list, null, this._folderId, this.isSearchResults); 566 DBG.timePt("done setting list"); 567 }; 568 569 ZmContactListController.prototype._handleSyncAll = 570 function() { 571 //doesn't do anything now after I removed the appCtxt.get(ZmSetting.GET_MAIL_ACTION) == ZmSetting.GETMAIL_ACTION_DEFAULT preference stuff 572 }; 573 574 ZmContactListController.prototype._syncAllListener = 575 function(view) { 576 var callback = new AjxCallback(this, this._handleSyncAll); 577 appCtxt.accountList.syncAll(callback); 578 }; 579 580 ZmContactListController.prototype.runRefresh = 581 function() { 582 583 if (!appCtxt.isOffline) { 584 return; 585 } 586 //should only happen in ZD 587 588 this._syncAllListener(); 589 }; 590 591 592 ZmContactListController.prototype._sendReceiveListener = 593 function(ev) { 594 var account = appCtxt.accountList.getAccount(ev.item.getData(ZmOperation.MENUITEM_ID)); 595 if (account) { 596 account.sync(); 597 } 598 }; 599 600 ZmContactListController.prototype._handleListChange = 601 function(ev) { 602 if (ev.event == ZmEvent.E_MODIFY || ev.event == ZmEvent.E_CREATE) { 603 if (!ev.getDetail("visible")) { 604 return; 605 } 606 var items = ev.getDetail("items"); 607 var item = items && items.length && items[0]; 608 if (item instanceof ZmContact && this._currentViewType == ZmId.VIEW_CONTACT_SIMPLE && item.folderId == this._folderId) { 609 var alphaBar = this._parentView[this._currentViewId].getAlphabetBar(); 610 //only set the view if the contact is in the list 611 if(!alphaBar || alphaBar.isItemInAlphabetLetter(item)) { 612 this._parentView[this._currentViewId].setContact(item, this.isGalSearch()); 613 } 614 } 615 } 616 }; 617 618 /** 619 * Create menu for View button and add listeners. 620 * 621 * @private 622 */ 623 ZmContactListController.prototype._setupViewMenu = 624 function(view, firstTime) { 625 var btn; 626 627 if (firstTime) { 628 btn = this._toolbar[view].getButton(ZmOperation.VIEW_MENU); 629 var menu = btn.getMenu(); 630 if (!menu) { 631 menu = new ZmPopupMenu(btn); 632 btn.setMenu(menu); 633 for (var i = 0; i < ZmContactListController.VIEWS.length; i++) { 634 var id = ZmContactListController.VIEWS[i]; 635 var mi = menu.createMenuItem(id, {image:ZmContactListController.ICON[id], 636 text:ZmMsg[ZmContactListController.MSG_KEY[id]], 637 style:DwtMenuItem.RADIO_STYLE}); 638 mi.setData(ZmOperation.MENUITEM_ID, id); 639 mi.addSelectionListener(this._listeners[ZmOperation.VIEW]); 640 if (id == view) 641 mi.setChecked(true, true); 642 } 643 } 644 } else { 645 // always set the switched view to be the checked menu item 646 btn = this._toolbar[view].getButton(ZmOperation.VIEW_MENU); 647 var menu = btn ? btn.getMenu() : null; 648 var mi = menu ? menu.getItemById(ZmOperation.MENUITEM_ID, view) : null; 649 if (mi) { mi.setChecked(true, true); } 650 } 651 652 // always reset the view menu button icon to reflect the current view 653 btn.setImage(ZmContactListController.ICON[view]); 654 }; 655 656 /** 657 * @private 658 */ 659 ZmContactListController.prototype._setupPrintMenu = 660 function(view) { 661 var printButton = this._toolbar[view].getButton(ZmOperation.PRINT); 662 if (!printButton) { return; } 663 664 printButton.setToolTipContent(ZmMsg.printMultiTooltip); 665 printButton.noMenuBar = true; 666 var menu = new ZmPopupMenu(printButton); 667 printButton.setMenu(menu); 668 669 var id = ZmOperation.PRINT_CONTACT; 670 var mi = menu.createMenuItem(id, {image:ZmOperation.getProp(id, "image"), text:ZmMsg[ZmOperation.getProp(id, "textKey")]}); 671 mi.setData(ZmOperation.MENUITEM_ID, id); 672 mi.addSelectionListener(this._listeners[ZmOperation.PRINT_CONTACT]); 673 674 id = ZmOperation.PRINT_ADDRBOOK; 675 mi = menu.createMenuItem(id, {image:ZmOperation.getProp(id, "image"), text:ZmMsg[ZmOperation.getProp(id, "textKey")]}); 676 mi.setData(ZmOperation.MENUITEM_ID, id); 677 mi.addSelectionListener(this._listeners[ZmOperation.PRINT_ADDRBOOK]); 678 }; 679 680 /** 681 * Resets the available options on a toolbar or action menu. 682 * 683 * @private 684 */ 685 ZmContactListController.prototype._resetOperations = 686 function(parent, num) { 687 688 ZmBaseController.prototype._resetOperations.call(this, parent, num); 689 690 var printMenuItem; 691 if (parent instanceof ZmButtonToolBar) { 692 var printButton = parent.getButton(ZmOperation.PRINT); 693 var printMenu = printButton && printButton.getMenu(); 694 if (printMenu) { 695 printMenuItem = printMenu.getItem(1); 696 printMenuItem.setText(ZmMsg.printResults); 697 } 698 } 699 700 this._setContactGroupMenu(parent); 701 702 var printOp = (parent instanceof ZmActionMenu) ? ZmOperation.PRINT_CONTACT : ZmOperation.PRINT; 703 704 705 var isDl = this._folderId == ZmFolder.ID_DLS || 706 num == 1 && this._listView[this._currentViewId].getSelection()[0].isDistributionList(); 707 708 parent.enable(printOp, !isDl); 709 710 parent.enable(ZmOperation.NEW_MESSAGE, num > 0 && !appCtxt.isExternalAccount()); 711 712 var folder = this._folderId && appCtxt.getById(this._folderId); 713 714 parent.enable([ZmOperation.CONTACTGROUP_MENU], num > 0 && !isDl && !appCtxt.isExternalAccount()); 715 var contactGroupMenu = this._getContactGroupMenu(parent); 716 if (contactGroupMenu) { 717 contactGroupMenu.setNewDisabled(folder && folder.isReadOnly()); 718 } 719 appCtxt.notifyZimlets("resetContactListToolbarOperations",[parent, num]); 720 if (!this.isGalSearch()) { 721 parent.enable([ZmOperation.SEARCH_MENU, ZmOperation.BROWSE, ZmOperation.NEW_MENU, ZmOperation.VIEW_MENU], true); 722 723 // a valid folderId means user clicked on an addrbook 724 if (folder) { 725 var isShare = folder.link; 726 var isInTrash = folder.isInTrash(); 727 var canEdit = !folder.isReadOnly(); 728 729 parent.enable([ZmOperation.TAG_MENU], canEdit && num > 0); 730 parent.enable([ZmOperation.DELETE, ZmOperation.MOVE, ZmOperation.MOVE_MENU], canEdit && num > 0); 731 parent.enable([ZmOperation.EDIT, ZmOperation.CONTACT], canEdit && num == 1 && !isInTrash); 732 733 734 if (printMenuItem) { 735 var text = isShare ? ZmMsg.printResults : ZmMsg.printAddrBook; 736 printMenuItem.setText(text); 737 } 738 } else { 739 // otherwise, must be a search 740 var contact = this._listView[this._currentViewId].getSelection()[0]; 741 var canEdit = (num == 1 && !contact.isReadOnly() && !ZmContact.isInTrash(contact)); 742 parent.enable([ZmOperation.DELETE, ZmOperation.MOVE, ZmOperation.MOVE_MENU, ZmOperation.TAG_MENU], num > 0); 743 parent.enable([ZmOperation.EDIT, ZmOperation.CONTACT], canEdit); 744 } 745 } else { 746 // gal contacts cannot be tagged/moved/deleted 747 parent.enable([ZmOperation.PRINT, ZmOperation.PRINT_CONTACT, ZmOperation.MOVE, ZmOperation.MOVE_MENU, ZmOperation.TAG_MENU], false); 748 parent.enable([ZmOperation.SEARCH_MENU, ZmOperation.BROWSE, ZmOperation.NEW_MENU, ZmOperation.VIEW_MENU], true); 749 parent.enable(ZmOperation.CONTACT, num == 1); 750 var selection = this._listView[this._currentViewId].getSelection(); 751 var canEdit = false; 752 if (num == 1) { 753 var contact = selection[0]; 754 var isDL = contact && contact.isDistributionList(); 755 canEdit = isDL && contact.dlInfo && contact.dlInfo.isOwner; 756 } 757 parent.enable([ZmOperation.EDIT], canEdit); //not sure what is this ZmOperation.CONTACT exactly, but it's used as the "edit group" below. 758 parent.enable([ZmOperation.CONTACT], isDL ? canEdit : num == 1); 759 var canDelete = ZmContactList.deleteGalItemsAllowed(selection); 760 761 parent.enable([ZmOperation.DELETE], canDelete); 762 } 763 764 //this._resetQuickCommandOperations(parent); 765 766 var selection = this._listView[this._currentViewId].getSelection(); 767 var contact = (selection.length == 1) ? selection[0] : null; 768 769 var searchEnabled = num === 1 && !appCtxt.isExternalAccount() && !contact.isGroup() && contact.getEmail(); //contact.getEmail() comes from bug 72446. I had to refactor but want to keep reference to bug in this comment 770 parent.enable([ZmOperation.SEARCH_MENU, ZmOperation.BROWSE], searchEnabled); 771 772 if (parent instanceof ZmPopupMenu) { 773 this._setContactText(contact); 774 775 var tagMenu = parent.getMenuItem(ZmOperation.TAG_MENU); 776 if (tagMenu) { 777 tagMenu.setText(contact && contact.isGroup() ? ZmMsg.AB_TAG_GROUP : ZmMsg.AB_TAG_CONTACT); 778 } 779 } 780 781 if (appCtxt.isExternalAccount()) { 782 parent.enable( 783 [ 784 ZmOperation.MOVE, 785 ZmOperation.EDIT, 786 ZmOperation.CONTACT, 787 ZmOperation.MOVE_MENU, 788 ZmOperation.CONTACTGROUP_MENU, 789 ZmOperation.DELETE, 790 ZmOperation.SEARCH_MENU, 791 ZmOperation.NEW_MESSAGE 792 ], 793 false 794 ); 795 parent.setItemVisible(ZmOperation.TAG_MENU, false); 796 } 797 798 if (appCtxt.isWebClientOffline()) { 799 parent.enable( 800 [ 801 ZmOperation.ACTIONS_MENU, 802 ZmOperation.MOVE, 803 ZmOperation.EDIT, 804 ZmOperation.CONTACT, 805 ZmOperation.MOVE_MENU, 806 ZmOperation.CONTACTGROUP_MENU, 807 ZmOperation.DELETE, 808 ZmOperation.TAG_MENU, 809 ZmOperation.PRINT, 810 ZmOperation.PRINT_CONTACT, 811 ZmOperation.SEND_CONTACTS_IN_EMAIL 812 ], 813 false 814 ); 815 } 816 }; 817 818 819 // List listeners 820 821 822 /** 823 * @private 824 * return the contact for which to do the action 825 * @param {Boolean} isToolbar - true if the action is from the toolbar. false/null if it's from right-click action 826 */ 827 ZmContactListController.prototype._getActionContact = function(isToolbar) { 828 829 /* 830 if you read this and don't understand why I don't do the same as in _composeListener. It's because 831 in DwtListView.prototype.getSelection, the _rightSelItem is not set for a submenu, so the right clicked item is not the selection returned. 832 This approach of specifically specifying if it's from the toolbar (isToolbar) is more explicit, less fragile and works. 833 */ 834 if (isToolbar) { 835 var selection = this._listView[this._currentViewId].getSelection(); 836 if (selection.length != 1) { 837 return null; 838 } 839 return selection[0]; 840 } 841 if (this._actionEv) { 842 return this._actionEv.contact; 843 } 844 }; 845 846 847 /** 848 * From Search based on email address 849 * 850 * @private 851 */ 852 ZmContactListController.prototype._searchListener = function(addrType, isToolbar, ev) { 853 854 var contact = this._getActionContact(isToolbar); 855 if (!contact) { 856 return; 857 } 858 859 var addresses = contact.getEmails(), 860 srchCtlr = appCtxt.getSearchController(); 861 862 if (addrType === AjxEmailAddress.FROM) { 863 srchCtlr.fromSearch(addresses); 864 } 865 else if (addrType === AjxEmailAddress.TO) { 866 srchCtlr.toSearch(addresses); 867 } 868 }; 869 870 /** 871 * Double click displays a contact. 872 * 873 * @private 874 */ 875 ZmContactListController.prototype._listSelectionListener = 876 function(ev) { 877 Dwt.setLoadingTime("ZmContactItem"); 878 ZmListController.prototype._listSelectionListener.call(this, ev); 879 880 if (ev.detail == DwtListView.ITEM_SELECTED) { 881 // this._resetNavToolBarButtons(); 882 if (this._currentViewType == ZmId.VIEW_CONTACT_SIMPLE) { 883 this._parentView[this._currentViewId].setContact(ev.item, this.isGalSearch()); 884 } 885 } else if (ev.detail == DwtListView.ITEM_DBL_CLICKED) { 886 var folder = appCtxt.getById(ev.item.folderId); 887 if (ev.item.isDistributionList() && ev.item.dlInfo.isOwner) { 888 this._editListener.call(this, ev); 889 return; 890 } 891 if (!this.isGalSearch() && (!folder || (!folder.isReadOnly() && !folder.isInTrash())) && !appCtxt.isWebClientOffline()) { 892 AjxDispatcher.run("GetContactController").show(ev.item); 893 } 894 } 895 }; 896 897 /** 898 * @private 899 */ 900 ZmContactListController.prototype._newListener = 901 function(ev, op, params) { 902 if (!ev && !op) { return; } 903 op = op || ev.item.getData(ZmOperation.KEY_ID); 904 if (op == ZmOperation.NEW_MESSAGE) { 905 this._composeListener(ev); 906 }else{ 907 ZmListController.prototype._newListener.call(this, ev, op, params); 908 } 909 }; 910 911 /** 912 * Compose message to participant. 913 * 914 * @private 915 */ 916 ZmContactListController.prototype._composeListener = 917 function(ev) { 918 919 var selection = this._listView[this._currentViewId].getSelection(); 920 if (selection.length == 0 && this._actionEv) { 921 selection.push(this._actionEv.contact); 922 } 923 var emailStr = '', contact, email; 924 for (var i = 0; i < selection.length; i++){ 925 contact = selection[i]; 926 if (contact.isGroup() && !contact.isDistributionList()) { 927 var members = contact.getGroupMembers().good; 928 if (members.size()) { 929 emailStr += members.toString(AjxEmailAddress.SEPARATOR) + AjxEmailAddress.SEPARATOR; 930 } 931 } 932 else { 933 var addr = new AjxEmailAddress(contact.getEmail(), AjxEmailAddress.TO, contact.getFullName()); 934 emailStr += addr.toString() + AjxEmailAddress.SEPARATOR; 935 } 936 } 937 938 AjxDispatcher.run("Compose", {action: ZmOperation.NEW_MESSAGE, inNewWindow: this._app._inNewWindow(ev), 939 toOverride: emailStr}); 940 }; 941 942 /** 943 * Get info on selected contact to provide context for action menu. 944 * 945 * @private 946 */ 947 ZmContactListController.prototype._listActionListener = 948 function(ev) { 949 ZmListController.prototype._listActionListener.call(this, ev); 950 this._actionEv.contact = ev.item; 951 var actionMenu = this.getActionMenu(); 952 if (!this._actionEv.contact.getEmail() && actionMenu) { 953 var menuItem = actionMenu.getMenuItem(ZmOperation.SEARCH_MENU); 954 if (menuItem) { 955 menuItem.setEnabled(false); 956 } 957 } 958 actionMenu.popup(0, ev.docX, ev.docY); 959 if (ev.ersatz) { 960 // menu popped up via keyboard nav 961 actionMenu.setSelectedItem(0); 962 } 963 }; 964 965 966 /** 967 * @private 968 */ 969 ZmContactListController.prototype._dropListener = 970 function(ev) { 971 var view = this._listView[this._currentViewId]; 972 //var item = view.getTargetItem(ev); - this didn't seem to return any item in my tests, while the below (copied from ZmListController.prototype._dropListener) does, and thus solves the gal issue for DLs as well. 973 var div = view.getTargetItemDiv(ev.uiEvent); 974 var item = view.getItemFromElement(div); 975 976 // only tags can be dropped on us 977 if (ev.action == DwtDropEvent.DRAG_ENTER) { 978 if (item && (item.type == ZmItem.CONTACT) && (item.isGal || item.isShared())) { 979 ev.doIt = false; // can't tag a GAL or shared contact 980 view.dragSelect(div); 981 return; 982 } 983 } 984 ZmListController.prototype._dropListener.call(this, ev); 985 }; 986 987 /** 988 * @private 989 */ 990 ZmContactListController.prototype._editListener = 991 function(ev, contact) { 992 contact = contact || this._listView[this._currentViewId].getSelection()[0]; 993 AjxDispatcher.run("GetContactController").show(contact, false); 994 }; 995 996 /** 997 * @private 998 */ 999 ZmContactListController.prototype._printListener = 1000 function(ev) { 1001 1002 var contacts = this._listView[this._currentViewId].getSelection(); 1003 var ids = []; 1004 for (var i = 0; i < contacts.length; i++) { 1005 if (contacts[i].isDistributionList()) { 1006 continue; //don't print DLs 1007 } 1008 ids.push(contacts[i].id); 1009 } 1010 if (ids.length == 0) { 1011 return; 1012 } 1013 1014 var url = "/h/printcontacts?id=" + ids.join(","); 1015 if (this.isGalSearch()) { 1016 url = "/h/printcontacts?id=" + ids.join("&id="); 1017 url = url + "&st=gal"; 1018 var query = this._currentSearch && this._currentSearch.query; 1019 if (query && contacts.length > 1) 1020 url += "&sq="+query; 1021 else if(contacts.length==1) 1022 url += "&sq=" + contacts[0].getFileAs(); 1023 } 1024 if (appCtxt.isOffline) { 1025 var folderId = this._folderId || ZmFolder.ID_CONTACTS; 1026 var acctName = appCtxt.getById(folderId).getAccount().name; 1027 url += "&acct=" + acctName ; 1028 } 1029 window.open(appContextPath+url, "_blank"); 1030 }; 1031 1032 /** 1033 * @private 1034 */ 1035 ZmContactListController.prototype._printAddrBookListener = 1036 function(ev) { 1037 var url; 1038 if (this._folderId && !this._list._isShared) { 1039 url = "/h/printcontacts?sfi=" + this._folderId; 1040 } else { 1041 var contacts = ((this._searchType & ZmContactListController.SEARCH_TYPE_ANYWHERE) != 0) 1042 ? AjxDispatcher.run("GetContacts") 1043 : this._list; 1044 1045 var ids = []; 1046 var list = contacts.getArray(); 1047 for (var i = 0; i < list.length; i++) { 1048 ids.push(list[i].id); 1049 } 1050 // XXX: won't this run into GET limits for large addrbooks? would be better to have 1051 // URL that prints all contacts (maybe "id=all") 1052 url = "/h/printcontacts"; 1053 if (this.isGalSearch()) { 1054 url += "?id=" + ids.join("&id="); 1055 } else { 1056 url += "?id=" + ids.join(","); 1057 } 1058 } 1059 if (this.isGalSearch()) { 1060 url = url + "&st=gal"; 1061 var query = this._currentSearch && this._currentSearch.query; 1062 if (query && list && list.length > 1) 1063 url += "&sq="+query; 1064 else if (list && list.length == 1) 1065 url += "&sq="+list[0].getFileAs(); 1066 } 1067 if (appCtxt.isOffline) { 1068 var folderId = this._folderId || ZmFolder.ID_CONTACTS; 1069 var acctName = appCtxt.getById(folderId).getAccount().name; 1070 url += "&acct=" + acctName ; 1071 } 1072 window.open(appContextPath+url, "_blank"); 1073 }; 1074 1075 1076 // Callbacks 1077 1078 /** 1079 * @private 1080 */ 1081 ZmContactListController.prototype._preShowCallback = 1082 function(view) { 1083 if ((this._searchType & ZmContactListController.SEARCH_TYPE_NEW) != 0) { 1084 this._searchType &= ~ZmContactListController.SEARCH_TYPE_NEW; 1085 } else { 1086 this._resetNavToolBarButtons(view); 1087 } 1088 1089 return true; 1090 }; 1091 1092 /** 1093 * @private 1094 */ 1095 ZmContactListController.prototype._doMove = 1096 function(items, folder, attrs, isShiftKey) { 1097 1098 items = AjxUtil.toArray(items); 1099 1100 var move = []; 1101 var copy = []; 1102 var moveFromGal = []; 1103 for (var i = 0; i < items.length; i++) { 1104 var item = items[i]; 1105 if (item.isGal) { 1106 moveFromGal.push(item); 1107 } else if (!item.folderId || item.folderId != folder.id) { 1108 if (!this._isItemMovable(item, isShiftKey, folder)) { 1109 copy.push(item); 1110 } else { 1111 move.push(item); 1112 } 1113 } 1114 } 1115 1116 var moveOutFolder = appCtxt.getById(this.getFolderId()); 1117 var outOfTrash = (moveOutFolder && moveOutFolder.isInTrash() && !folder.isInTrash()); 1118 1119 var allDoneCallback = this._getAllDoneCallback(); 1120 if (move.length) { 1121 var params = {items:move, folder:folder, attrs:attrs, outOfTrash:outOfTrash}; 1122 var list = params.list = this._getList(params.items); 1123 this._setupContinuation(this._doMove, [folder, attrs, isShiftKey], params, allDoneCallback); 1124 list = outOfTrash ? this._list : list; 1125 list.moveItems(params); 1126 } 1127 1128 if (copy.length) { 1129 var params = {items:copy, folder:folder, attrs:attrs}; 1130 var list = params.list = this._getList(params.items); 1131 this._setupContinuation(this._doMove, [folder, attrs, isShiftKey], params, allDoneCallback); 1132 list = outOfTrash ? this._list : list; 1133 list.copyItems(params); 1134 } 1135 1136 if (moveFromGal.length) { 1137 var batchCmd = new ZmBatchCommand(true, null, true); 1138 for (var j = 0; j < moveFromGal.length; j++) { 1139 var contact = moveFromGal[j]; 1140 contact.attr[ZmContact.F_folderId] = folder.id; 1141 batchCmd.add(new AjxCallback(contact, contact.create, [contact.attr])); 1142 } 1143 batchCmd.run(new AjxCallback(this, this._handleMoveFromGal)); 1144 } 1145 }; 1146 1147 /** 1148 * @private 1149 */ 1150 ZmContactListController.prototype._handleMoveFromGal = 1151 function(result) { 1152 var resp = result.getResponse().BatchResponse.CreateContactResponse; 1153 if (resp != null && resp.length > 0) { 1154 var msg = AjxMessageFormat.format(ZmMsg.itemCopied, resp.length); 1155 appCtxt.getAppController().setStatusMsg(msg); 1156 } 1157 }; 1158 1159 /** 1160 * @private 1161 */ 1162 ZmContactListController.prototype._doDelete = 1163 function(items, hardDelete, attrs) { 1164 ZmListController.prototype._doDelete.call(this, items, hardDelete, attrs); 1165 for (var i=0; i<items.length; i++) { 1166 appCtxt.getApp(ZmApp.CONTACTS).updateIdHash(items[i], true); 1167 } 1168 // if more contacts to show, 1169 var size = this._listView[this._currentViewId].getSelectedItems().size(); 1170 if (size == 0) { 1171 // and if in split view allow split view to clear 1172 if (this._currentViewType == ZmId.VIEW_CONTACT_SIMPLE) 1173 this._listView[this._currentViewId].parent.clear(); 1174 1175 this._resetOperations(this._toolbar[this._currentViewId], 0); 1176 } 1177 }; 1178 1179 /** 1180 * @private 1181 */ 1182 ZmContactListController.prototype._moveListener = 1183 function(ev) { 1184 ZmListController.prototype._moveListener.call(this, ev); 1185 }; 1186 1187 /** 1188 * @private 1189 */ 1190 ZmContactListController.prototype._checkReplenish = 1191 function() { 1192 // reset the listview 1193 var lv = this._listView[this._currentViewId]; 1194 lv.set(this._list); 1195 lv._setNextSelection(); 1196 }; 1197 1198 1199 ZmContactListController.prototype._getContactGroupMenu = 1200 function(parent) { 1201 var menu = parent instanceof ZmButtonToolBar ? parent.getActionsMenu() : parent; 1202 return menu ? menu.getContactGroupMenu() : null; 1203 }; 1204 1205 1206 ZmContactListController.prototype._setContactGroupMenu = 1207 function(parent) { 1208 if (!parent || appCtxt.isExternalAccount()) { return; } 1209 1210 var groupMenu = this._getContactGroupMenu(parent); 1211 if (!groupMenu) { 1212 return; 1213 } 1214 var items = this.getItems(); 1215 items = AjxUtil.toArray(items); 1216 var contacts = this._getContactsFromCache(); 1217 var contactGroups = this._filterGroups(contacts); 1218 var sortedGroups = this._sortContactGroups(contactGroups); 1219 groupMenu.set(items, sortedGroups, this._folderId == ZmFolder.ID_DLS); //disabled "new" from this for DLs folder. 1220 }; 1221 1222 ZmContactListController.prototype._setupContactGroupMenu = 1223 function(parent) { 1224 if (!parent) return; 1225 var groupMenu = this._getContactGroupMenu(parent); 1226 if (groupMenu) { 1227 groupMenu.addSelectionListener(this._listeners[ZmOperation.NEW_GROUP]); 1228 } 1229 }; 1230 1231 /** 1232 * handles updating the group item data 1233 * @param ev 1234 */ 1235 ZmContactListController.prototype._contactListChange = 1236 function(ev) { 1237 if (ev && ev.source && ev.type == ZmId.ITEM_CONTACT) { 1238 var item = ev.source; 1239 var id = DwtId.WIDGET_ITEM + "__" + this._currentViewId + "__" + ev.source.id; 1240 var view = this._listView[this._currentViewId]; 1241 view._setItemData(null, "item", item, id); 1242 } 1243 1244 1245 }; 1246 1247 ZmContactListController.prototype._groupListener = 1248 function(ev, items) { 1249 1250 if (this.isCurrent()) { 1251 var groupEvent = ev.getData(ZmContactGroupMenu.KEY_GROUP_EVENT); 1252 var groupAdded = ev.getData(ZmContactGroupMenu.KEY_GROUP_ADDED); 1253 items = items || this.getItems(); 1254 if (groupEvent == ZmEvent.E_MODIFY) { 1255 var mods = {}; 1256 var groupId = ev.getData(Dwt.KEY_OBJECT).id; 1257 var group = appCtxt.getApp(ZmApp.CONTACTS).getContactList().getById(groupId); 1258 if (group) { 1259 group.addChangeListener(this._contactListChange.bind(this), 0);//update the group data 1260 var modifiedGroups = this._getGroupMembers(items, group); 1261 if (modifiedGroups) { 1262 mods[ZmContact.F_groups] = modifiedGroups; 1263 } 1264 this._doModify(group, mods); 1265 this._menuPopdownActionListener(); 1266 var idx = this._list.getIndexById(group.id); 1267 if (idx != null) { 1268 this._resetSelection(idx); 1269 } 1270 } 1271 } 1272 else if (groupEvent == ZmEvent.E_CREATE) { 1273 this._pendingActionData = items; 1274 var newContactGroupDialog = appCtxt.getNewContactGroupDialog(); 1275 if (!this._newContactGroupCb) { 1276 this._newContactGroupCb = new AjxCallback(this, this._newContactGroupCallback); 1277 } 1278 ZmController.showDialog(newContactGroupDialog, this._newContactGroupCb); 1279 newContactGroupDialog.registerCallback(DwtDialog.CANCEL_BUTTON, this._clearDialog, this, newContactGroupDialog); 1280 } 1281 } 1282 }; 1283 1284 ZmContactListController.prototype._newContactGroupCallback = 1285 function(params) { 1286 var groupName = params.name; 1287 appCtxt.getNewContactGroupDialog().popdown(); 1288 var items = this.getItems(); 1289 var mods = {}; 1290 mods[ZmContact.F_groups] = this._getGroupMembers(items); 1291 mods[ZmContact.F_folderId] = this._folderId; 1292 mods[ZmContact.F_fileAs] = ZmContact.computeCustomFileAs(groupName); 1293 mods[ZmContact.F_nickname] = groupName; 1294 mods[ZmContact.F_type] = "group"; 1295 this._doCreate(this._list, mods); 1296 this._pendingActionData = null; 1297 this._menuPopdownActionListener(); 1298 }; 1299 1300 //methods for dealing with contact groups 1301 ZmContactListController.prototype._getGroupMembers = 1302 function(items, group) { 1303 var mods = {}; 1304 var newMembers = {}; 1305 var groupId = []; 1306 var memberType; 1307 var obj = {}; 1308 var id, contact; 1309 1310 for (var i=0; i<items.length; i++) { 1311 if (items[i].isDistributionList() || !items[i].isGroup()) { 1312 obj = this._createContactRefObj(items[i], group); 1313 if (obj.value) { 1314 newMembers[obj.value] = obj; 1315 } 1316 } 1317 else { 1318 var groups = items[i].attr[ZmContact.F_groups]; //getAttr only returns first value in array 1319 if (!groups) { 1320 obj = this._createContactRefObj(items[i], group); 1321 if (obj.value) { 1322 newMembers[obj.value] = obj; 1323 } 1324 } 1325 else { 1326 for (var j=0; j <groups.length; j++) { 1327 id = groups[j].value; 1328 contact = ZmContact.getContactFromCache(id); 1329 if (contact) { 1330 memberType = contact.isGal ? ZmContact.GROUP_GAL_REF : ZmContact.GROUP_CONTACT_REF; 1331 obj = {value : contact.isGal ? contact.ref : id, type : memberType}; 1332 if (group) { 1333 obj.op = "+"; 1334 } 1335 newMembers[id] = obj; 1336 } 1337 else if (groups[j].type == ZmContact.GROUP_INLINE_REF) { 1338 obj = {value: groups[j].value, type : ZmContact.GROUP_INLINE_REF}; 1339 if (group) { 1340 obj.op = "+"; 1341 } 1342 newMembers[id] = obj; 1343 } 1344 } 1345 } 1346 } 1347 } 1348 var newMembersArr = []; 1349 for (var id in newMembers) { 1350 newMembersArr.push(newMembers[id]); 1351 } 1352 if (group) { 1353 //handle potential duplicates 1354 var groupArr = group.attr[ZmContact.F_groups]; 1355 var noDups = []; 1356 var found = false; 1357 for (var i=0; i<newMembersArr.length; i++) { 1358 found = false; 1359 for (var j=0; j<groupArr.length && !found; j++) { 1360 if (newMembersArr[i].value == groupArr[j].value) { 1361 found = true; 1362 } 1363 } 1364 if (!found) { 1365 noDups.push(newMembersArr[i]); 1366 } 1367 } 1368 return noDups; 1369 } 1370 else { 1371 return newMembersArr; 1372 } 1373 }; 1374 1375 ZmContactListController.prototype._createContactRefObj = 1376 function(contactToAdd, group) { 1377 var obj = {}; 1378 var memberType = contactToAdd.isGal ? ZmContact.GROUP_GAL_REF : ZmContact.GROUP_CONTACT_REF; 1379 var id = memberType == ZmContact.GROUP_CONTACT_REF ? contactToAdd.getId(true) : (contactToAdd.ref || contactToAdd.id); 1380 if (id) { 1381 var obj = {value: id, type: memberType}; 1382 if (group) { 1383 obj.op = "+"; //modifying group with new member 1384 } 1385 } 1386 return obj; 1387 1388 }; 1389 1390 ZmContactListController.prototype._getContactsFromCache = 1391 function() { 1392 var contactList = appCtxt.getApp(ZmApp.CONTACTS).getContactList(); 1393 if (contactList){ 1394 return contactList.getIdHash(); 1395 } 1396 return {}; 1397 }; 1398 1399 ZmContactListController.prototype._sortContactGroups = 1400 function(contactGroups) { 1401 var sortByNickname = function(a, b) { 1402 var aNickname = ZmContact.getAttr(a, "nickname"); 1403 var bNickname = ZmContact.getAttr(b, "nickname"); 1404 1405 if (!aNickname || !bNickname) { 1406 return 0; 1407 } 1408 1409 if (aNickname.toLowerCase() > bNickname.toLowerCase()) 1410 return 1; 1411 if (aNickname.toLowerCase() < bNickname.toLowerCase()) 1412 return -1; 1413 1414 return 0; 1415 }; 1416 1417 return contactGroups.sort(sortByNickname); 1418 }; 1419 1420 ZmContactListController.prototype._filterGroups = 1421 function(contacts) { 1422 var groups = []; 1423 for (var id in contacts) { 1424 var typeAttr = ZmContact.getAttr(contacts[id], "type"); 1425 if (typeAttr && typeAttr.toUpperCase() == ZmItem.GROUP.toUpperCase()) { 1426 groups.push(contacts[id]); 1427 } 1428 } 1429 return groups; 1430 }; 1431 1432 /** 1433 * @private 1434 */ 1435 ZmContactListController.prototype._paginate = 1436 function(view, forward, loadIndex, limit) { 1437 if (this._list.isGal && !this._list.isGalPAgingSupported) { 1438 return; 1439 } 1440 ZmListController.prototype._paginate.call(this, view, forward, loadIndex, limit); 1441 }; 1442 1443 /** 1444 * @private 1445 */ 1446 ZmContactListController.prototype._getDefaultFocusItem = 1447 function() { 1448 return this.getCurrentView().getListView(); 1449 }; 1450