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 * This file contains the contact split view class. 27 */ 28 29 /** 30 * Creates a contact split view. 31 * @class 32 * This class represents the contact split view. 33 * 34 * @param {Hash} params a hash of parameters 35 * @extends DwtComposite 36 */ 37 ZmContactSplitView = function(params) { 38 if (arguments.length == 0) { return; } 39 40 params.className = params.className || "ZmContactSplitView"; 41 params.posStyle = params.posStyle || Dwt.ABSOLUTE_STYLE; 42 params.id = Dwt.getNextId('ZmContactSplitView_'); 43 DwtComposite.call(this, params); 44 45 this._controller = params.controller; 46 this.setScrollStyle(Dwt.CLIP); 47 48 this._changeListener = new AjxListener(this, this._contactChangeListener); 49 50 this._initialize(params.controller, params.dropTgt); 51 52 var folderTree = appCtxt.getFolderTree(); 53 if (folderTree) { 54 folderTree.addChangeListener(new AjxListener(this, this._addrbookTreeListener)); 55 } 56 57 ZmTagsHelper.setupListeners(this); 58 59 }; 60 61 ZmContactSplitView.prototype = new DwtComposite; 62 ZmContactSplitView.prototype.constructor = ZmContactSplitView; 63 64 ZmContactSplitView.prototype.isZmContactSplitView = true 65 ZmContactSplitView.prototype.toString = function() { return "ZmContactSplitView"; }; 66 67 // Consts 68 ZmContactSplitView.ALPHABET_HEIGHT = 35; 69 70 ZmContactSplitView.NUM_DL_MEMBERS = 10; // number of distribution list members to show initially 71 72 ZmContactSplitView.LIST_MIN_WIDTH = 100; 73 ZmContactSplitView.CONTENT_MIN_WIDTH = 200; 74 75 ZmContactSplitView.SUBSCRIPTION_POLICY_ACCEPT = "ACCEPT"; 76 ZmContactSplitView.SUBSCRIPTION_POLICY_REJECT = "REJECT"; 77 ZmContactSplitView.SUBSCRIPTION_POLICY_APPROVAL = "APPROVAL"; 78 79 /** 80 * Gets the list view. 81 * 82 * @return {ZmContactSimpleView} the list view 83 */ 84 ZmContactSplitView.prototype.getListView = 85 function() { 86 return this._listPart; 87 }; 88 89 /** 90 * Gets the controller. 91 * 92 * @return {ZmContactController} the controller 93 */ 94 ZmContactSplitView.prototype.getController = 95 function() { 96 return this._controller; 97 }; 98 99 /** 100 * Gets the alphabet bar. 101 * 102 * @return {ZmContactAlphabetBar} the alphabet bar 103 */ 104 ZmContactSplitView.prototype.getAlphabetBar = 105 function() { 106 return this._alphabetBar; 107 }; 108 109 /** 110 * Sets the view size. 111 * 112 * @param {int} width the width (in pixels) 113 * @param {int} height the height (in pixels) 114 */ 115 ZmContactSplitView.prototype.setSize = 116 function(width, height) { 117 DwtComposite.prototype.setSize.call(this, width, height); 118 this._sizeChildren(width, height); 119 }; 120 121 /** 122 * Gets the title. 123 * 124 * @return {String} the title 125 */ 126 ZmContactSplitView.prototype.getTitle = 127 function() { 128 return [ZmMsg.zimbraTitle, this._controller.getApp().getDisplayName()].join(": "); 129 }; 130 131 /** 132 * Gets the size limit. 133 * 134 * @param {int} offset the offset 135 * @return {int} the size 136 */ 137 ZmContactSplitView.prototype.getLimit = 138 function(offset) { 139 return this._listPart.getLimit(offset); 140 }; 141 142 /** 143 * Sets the contact. 144 * 145 * @param {ZmContact} contact the contact 146 * @param {Boolean} isGal <code>true</code> if is GAL 147 * 148 */ 149 ZmContactSplitView.prototype.setContact = 150 function(contact, isGal) { 151 if (contact.isDistributionList() || !isGal) { 152 // Remove and re-add listeners for current contact if exists 153 if (this._contact) { 154 this._contact.removeChangeListener(this._changeListener); 155 } 156 contact.addChangeListener(this._changeListener); 157 } 158 159 var oldContact = this._contact; 160 this._contact = this._item = contact; 161 162 if (this._contact.isLoaded) { 163 this._setContact(contact, isGal, oldContact); 164 } else { 165 var callback = new AjxCallback(this, this._handleResponseLoad, [isGal, oldContact]); 166 var errorCallback = new AjxCallback(this, this._handleErrorLoad); 167 this._contact.load(callback, errorCallback, null, contact.isGroup()); 168 } 169 }; 170 171 ZmContactSplitView.expandDL = 172 function(viewId, expand) { 173 var view = DwtControl.fromElementId(viewId); 174 if (view) { 175 view._setContact(view._contact, true, null, expand); 176 } 177 }; 178 179 ZmContactSplitView.handleDLScroll = 180 function(ev) { 181 182 var target = DwtUiEvent.getTarget(ev); 183 var view = DwtControl.findControl(target); 184 if (!view) { return; } 185 var div = view._dlScrollDiv; 186 if (div.clientHeight == div.scrollHeight) { return; } 187 var contactDL = appCtxt.getApp(ZmApp.CONTACTS).getDL(view._dlContact.getEmail()); 188 var listSize = view.getDLSize(); 189 if (contactDL && (contactDL.more || (listSize < contactDL.list.length))) { 190 var params = {scrollDiv: div, 191 rowHeight: view._rowHeight, 192 threshold: 10, 193 limit: ZmContact.DL_PAGE_SIZE, 194 listSize: listSize}; 195 var needed = ZmListView.getRowsNeeded(params); 196 DBG.println("dl", "scroll, items needed: " + needed); 197 if (needed) { 198 DBG.println("dl", "new offset: " + listSize); 199 var respCallback = new AjxCallback(null, ZmContactSplitView._handleResponseDLScroll, [view]); 200 view._dlContact.getDLMembers(listSize, null, respCallback); 201 } 202 } 203 }; 204 205 ZmContactSplitView._handleResponseDLScroll = 206 function(view, result) { 207 208 var list = result.list; 209 if (!(list && list.length)) { return; } 210 211 var html = []; 212 view._listPart._getImageHtml(html, 0, null); 213 var subs = {first: false, html: html}; 214 var row = document.getElementById(view._dlLastRowId); 215 var table = row && document.getElementById(view._detailsId); 216 if (row) { 217 var rowIndex = row.rowIndex + 1; 218 for (var i = 0, len = list.length; i < len; i++) { 219 view._distributionList.list.push(list[i]); 220 subs.value = view._objectManager.findObjects(list[i], false, ZmObjectManager.EMAIL); 221 var rowIdText = ""; 222 var newRow = table.insertRow(rowIndex + i); 223 if (i == len - 1) { 224 newRow.id = view._dlLastRowId = Dwt.getNextId(); 225 } 226 newRow.valign = "top"; 227 newRow.innerHTML = AjxTemplate.expand("abook.Contacts#SplitView_dlmember-expanded", subs); 228 } 229 // view._dlScrollDiv.scrollTop = 0; 230 } 231 DBG.println("dl", table.rows.length + " rows"); 232 }; 233 234 ZmContactSplitView.prototype.getDLSize = 235 function() { 236 return this._distributionList && this._distributionList.list.length; 237 238 }; 239 240 /** 241 * @private 242 */ 243 ZmContactSplitView.prototype._handleResponseLoad = 244 function(isGal, oldContact, resp, contact) { 245 if (contact.id == this._contact.id) { 246 this._setContact(this._contact, isGal, oldContact); 247 } 248 }; 249 250 /** 251 * @private 252 */ 253 ZmContactSplitView.prototype._handleErrorLoad = 254 function(ex) { 255 this.clear(); 256 // TODO - maybe display some kind of error? 257 }; 258 259 /** 260 * Clears the view. 261 * 262 */ 263 ZmContactSplitView.prototype.clear = 264 function() { 265 var groupDiv = document.getElementById(this._contactBodyId); 266 if (groupDiv) { 267 groupDiv.innerHTML = ""; 268 } 269 270 this._contactView.clear(); 271 this._clearTags(); 272 }; 273 274 /** 275 * Enables the alphabet bar. 276 * 277 * @param {Boolean} enable if <code>true</code>, enable the alphabet bar 278 */ 279 ZmContactSplitView.prototype.enableAlphabetBar = 280 function(enable) { 281 if (this._alphabetBar) 282 this._alphabetBar.enable(enable); 283 }; 284 285 /** 286 * shows/hides the alphabet bar. 287 * 288 * @param {Boolean} visible if <code>true</code>, show the alphabet bar 289 */ 290 ZmContactSplitView.prototype.showAlphabetBar = 291 function(visible) { 292 if (this._alphabetBar) { 293 this._alphabetBar.setVisible(visible); 294 } 295 }; 296 297 298 /** 299 * @private 300 */ 301 ZmContactSplitView.prototype._initialize = 302 function(controller, dropTgt) { 303 this.getHtmlElement().innerHTML = AjxTemplate.expand("abook.Contacts#SplitView", {id:this._htmlElId}); 304 305 // alphabet bar based on *optional* existence in template and msg properties 306 var alphaDivId = this._htmlElId + "_alphabetbar"; 307 var alphaDiv = document.getElementById(alphaDivId); 308 if (alphaDiv && ZmMsg.alphabet && ZmMsg.alphabet.length>0) { 309 this._alphabetBar = new ZmContactAlphabetBar(this); 310 this._alphabetBar.reparentHtmlElement(alphaDivId); 311 } 312 313 var splitviewCellId = this._htmlElId + "_splitview"; 314 this._splitviewCell = document.getElementById(splitviewCellId); 315 316 // create listview based on *required* existence in template 317 var listviewCellId = this._htmlElId + "_listview"; 318 this._listviewCell = document.getElementById(listviewCellId); 319 this._listPart = new ZmContactSimpleView({parent:this, controller:controller, dropTgt:dropTgt}); 320 this._listPart.reparentHtmlElement(listviewCellId); 321 322 var sashCellId = this._htmlElId + "_sash"; 323 this._sash = new DwtSash(this, DwtSash.HORIZONTAL_STYLE, null, 5, Dwt.ABSOLUTE_STYLE); 324 this._sash.registerCallback(this._sashCallback, this); 325 this._sash.replaceElement(sashCellId, false, true); 326 327 var contentCellId = this._htmlElId + "_contentCell"; 328 this._contentCell = document.getElementById(contentCellId); 329 330 // define well-known Id's 331 this._iconCellId = this._htmlElId + "_icon"; 332 this._titleCellId = this._htmlElId + "_title"; 333 this._tagCellId = this._htmlElId + "_tags_contact"; 334 this._contactBodyId = this._htmlElId + "_body"; 335 this._contentId = this._htmlElId + "_content"; 336 this._detailsId = this._htmlElId + "_details"; 337 338 // create an empty slate 339 this._contactView = new ZmContactView({ parent: this, controller: this._controller }); 340 this._contactView.reparentHtmlElement(this._contentId); 341 this._objectManager = new ZmObjectManager(this._contactView); 342 this._contentCell.style.right = "0px"; 343 344 this._tabGroup = new DwtTabGroup('ZmContactSplitView'); 345 this._tabGroup.addMember(this._contactView.getTabGroupMember()); 346 }; 347 348 ZmContactSplitView.prototype.getTabGroupMember = function() { 349 return this._tabGroup; 350 }; 351 352 /** 353 * @private 354 */ 355 ZmContactSplitView.prototype._tabStateChangeListener = 356 function(ev) { 357 this._setContact(this._contact, this._isGalSearch); 358 }; 359 360 /** 361 * @private 362 */ 363 ZmContactSplitView.prototype._sizeChildren = 364 function(width, height) { 365 366 // Using toWindow instead of getY because getY calls Dwt.getLocation 367 // which returns NaN if "top" is not set or is "auto" 368 var listPartOffset = Dwt.toWindow(this._listPart.getHtmlElement(), 0, 0); 369 var fudge = listPartOffset.y - this.getY(); 370 371 this._listPart.setSize(Dwt.DEFAULT, height - fudge); 372 373 fudge = this._contactView.getY() - this.getY(); 374 this._contactView.setSize(Dwt.DEFAULT, height - fudge); 375 }; 376 377 /** 378 * @private 379 */ 380 ZmContactSplitView.prototype._contactChangeListener = 381 function(ev) { 382 if (ev.type != ZmEvent.S_CONTACT || 383 ev.source != this._contact || 384 ev.event == ZmEvent.E_DELETE) 385 { 386 return; 387 } 388 389 this._setContact(ev.source); 390 }; 391 392 /** 393 * @private 394 */ 395 ZmContactSplitView.prototype._addrbookTreeListener = 396 function(ev, treeView) { 397 if (!this._contact) { return; } 398 399 var fields = ev.getDetail("fields"); 400 if (ev.event == ZmEvent.E_MODIFY && fields && fields[ZmOrganizer.F_COLOR]) { 401 var organizers = ev.getDetail("organizers"); 402 if (!organizers && ev.source) { 403 organizers = [ev.source]; 404 } 405 406 for (var i = 0; i < organizers.length; i++) { 407 var organizer = organizers[i]; 408 var folderId = this._contact.isShared() 409 ? appCtxt.getById(this._contact.folderId).id 410 : this._contact.folderId; 411 412 if (organizer.id == folderId) { 413 this._setTags(); 414 } 415 } 416 } 417 }; 418 419 /** 420 * @private 421 */ 422 ZmContactSplitView.prototype._setContact = 423 function(contact, isGal, oldContact, expandDL, isBack) { 424 425 //first gather the dl info and dl members. Those are async requests so calling back here after 426 //it is done with isBack set to true. 427 if (contact.isDistributionList() && !isBack) { 428 var callbackHere = this._setContact.bind(this, contact, isGal, oldContact, expandDL, true); 429 contact.gatherExtraDlStuff(callbackHere); 430 return; 431 } 432 433 var addrBook = contact.getAddressBook(); 434 var color = addrBook ? addrBook.color : ZmOrganizer.DEFAULT_COLOR[ZmOrganizer.ADDRBOOK]; 435 var subs = { 436 id: this._htmlElId, 437 contact: contact, 438 addrbook: addrBook, 439 contactHdrClass: (ZmOrganizer.COLOR_TEXT[color] + "Bg"), 440 isInTrash: (addrBook && addrBook.isInTrash()) 441 }; 442 443 if (contact.isGroup()) { 444 this._objectManager.reset(); 445 446 if (addrBook) { 447 subs.folderIcon = addrBook.getIcon(); 448 subs.folderName = addrBook.getName(); 449 } 450 451 if (contact.isDistributionList()) { 452 var dlInfo = subs.dlInfo = contact.dlInfo; 453 } 454 subs.groupMembers = contact.getAllGroupMembers(); 455 subs.findObjects = this._objectManager.findObjects.bind(this._objectManager); 456 457 this._resetVisibility(true); 458 459 this._contactView.createHtml("abook.Contacts#SplitViewGroup", subs); 460 461 if (contact.isDistributionList()) { 462 if (this._subscriptionButton) { 463 this._subscriptionButton.dispose(); 464 } 465 this._subscriptionButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_subscriptionButton")}); 466 this._subscriptionButton.setEnabled(true); 467 this._subscriptionMsg = document.getElementById(this._htmlElId + "_subscriptionMsg"); 468 this._updateSubscriptionButtonAndMsg(contact); 469 var subListener = new AjxListener(this, this._subscriptionListener, contact); 470 this._subscriptionButton.addSelectionListener(subListener); 471 } 472 473 var size = this.getSize(); 474 this._sizeChildren(size.x, size.y); 475 } else { 476 subs.view = this; 477 subs.isGal = isGal; 478 subs.findObjects = this._objectManager.findObjects.bind(this._objectManager); 479 subs.attrs = contact.getNormalizedAttrs(); 480 subs.expandDL = expandDL; 481 482 if (contact.isDL && contact.canExpand) { 483 this._dlContact = contact; 484 this._dlScrollDiv = this._dlScrollDiv || document.getElementById(this._contentId); 485 var respCallback = new AjxCallback(this, this._showDL, [subs]); 486 contact.getDLMembers(0, null, respCallback); 487 return; 488 } 489 this._showContact(subs); 490 } 491 492 this._setTags(); 493 Dwt.setLoadedTime("ZmContactItem"); 494 }; 495 496 ZmContactSplitView.prototype.dispose = 497 function() { 498 ZmTagsHelper.disposeListeners(this); 499 DwtComposite.prototype.dispose.apply(this, arguments); 500 }; 501 502 503 ZmContactSplitView.prototype._showContact = 504 function(subs) { 505 this._objectManager.reset(); 506 this._resetVisibility(false); 507 508 subs.defaultImageUrl = ZmZimbraMail.DEFAULT_CONTACT_ICON; 509 510 this._contactView.createHtml("abook.Contacts#SplitView_content", subs); 511 512 // notify zimlets that a new contact is being shown. 513 appCtxt.notifyZimlets("onContactView", [subs.contact, this._htmlElId]); 514 }; 515 516 ZmContactSplitView.prototype._subscriptionListener = 517 function(contact, ev) { 518 var subscribe = !contact.dlInfo.isMember; 519 this._subscriptionButton.setEnabled(false); 520 var respHandler = this._handleSubscriptionResponse.bind(this, contact, subscribe); 521 contact.toggleSubscription(respHandler); 522 }; 523 524 ZmContactSplitView.prototype._handleSubscriptionResponse = 525 function(contact, subscribe, result) { 526 var status = result._data.SubscribeDistributionListResponse.status; 527 var subscribed = status == "subscribed"; 528 var unsubscribed = status == "unsubscribed"; 529 var awaitingApproval = status == "awaiting_approval"; 530 this._subscriptionButton.setEnabled(!awaitingApproval); 531 if (!awaitingApproval) { 532 contact.dlInfo.isMember = subscribed; 533 } 534 if (subscribed || unsubscribed) { 535 contact.clearDlInfo(); 536 contact._notify(ZmEvent.E_MODIFY); 537 } 538 var msg = subscribed ? ZmMsg.dlSubscribed 539 : unsubscribed ? ZmMsg.dlUnsubscribed 540 : awaitingApproval && subscribe ? ZmMsg.dlSubscriptionRequested 541 : awaitingApproval && !subscribe ? ZmMsg.dlUnsubscriptionRequested 542 : ""; //should not happen. Keep this as separate case for ease of debug when it does happen somehow. 543 var dlg = appCtxt.getMsgDialog(); 544 var name = contact.getEmail(); 545 dlg.setMessage(AjxMessageFormat.format(msg, name), DwtMessageDialog.INFO_STYLE); 546 dlg.popup(); 547 548 }; 549 550 ZmContactSplitView.prototype._updateSubscriptionButtonAndMsg = 551 function(contact) { 552 var dlInfo = contact.dlInfo; 553 var policy = dlInfo.isMember ? dlInfo.unsubscriptionPolicy : dlInfo.subscriptionPolicy; 554 if (policy == ZmContactSplitView.SUBSCRIPTION_POLICY_REJECT) { 555 this._subscriptionButton.setVisible(false); 556 } 557 else { 558 this._subscriptionButton.setVisible(true); 559 this._subscriptionButton.setText(dlInfo.isMember ? ZmMsg.dlUnsubscribe: ZmMsg.dlSubscribe); 560 } 561 var statusMsg = dlInfo.isOwner && dlInfo.isMember ? ZmMsg.youAreOwnerAndMember 562 : dlInfo.isOwner ? ZmMsg.youAreOwner 563 : dlInfo.isMember ? ZmMsg.youAreMember 564 : ""; 565 if (statusMsg != '') { 566 statusMsg = "<li>" + statusMsg + "</li>"; 567 } 568 var actionMsg; 569 if (!dlInfo.isMember) { 570 actionMsg = policy == ZmContactSplitView.SUBSCRIPTION_POLICY_APPROVAL ? ZmMsg.dlSubscriptionRequiresApproval 571 : policy == ZmContactSplitView.SUBSCRIPTION_POLICY_REJECT ? ZmMsg.dlSubscriptionNotAllowed 572 : ""; 573 } 574 else { 575 actionMsg = policy == ZmContactSplitView.SUBSCRIPTION_POLICY_APPROVAL ? ZmMsg.dlUnsubscriptionRequiresApproval 576 : policy == ZmContactSplitView.SUBSCRIPTION_POLICY_REJECT ? ZmMsg.dlUnsubscriptionNotAllowed 577 : ""; 578 579 } 580 if (actionMsg != '') { 581 actionMsg = "<li>" + actionMsg + "</li>"; 582 } 583 this._subscriptionMsg.innerHTML = statusMsg + actionMsg; 584 585 }; 586 587 // returns an object with common properties used for displaying a contact field 588 ZmContactSplitView._getListData = 589 function(data, label, objectType) { 590 var itemListData = { 591 id: data.id, 592 attrs: data.attrs, 593 labelId: data.id + '_' + label.replace(/[^\w]/g,""), 594 label: label, 595 first: true 596 }; 597 if (objectType) { 598 itemListData.findObjects = data.findObjects; 599 itemListData.objectType = objectType; 600 } 601 itemListData.isDL = data.contact.isDL; 602 603 return itemListData; 604 }; 605 606 ZmContactSplitView._showContactList = 607 function(data, names, typeFunc, hideType) { 608 609 data.names = names; 610 var html = []; 611 for (var i = 0; i < names.length; i++) { 612 var name = names[i]; 613 data.name = name; 614 data.type = (typeFunc && typeFunc(data, name)) || ZmMsg["AB_FIELD_" + name]; 615 data.type = hideType ? "" : data.type; 616 html.push(ZmContactSplitView._showContactListItem(data)); 617 } 618 619 return html.join(""); 620 }; 621 622 ZmContactSplitView._showContactListItem = 623 function(data) { 624 625 var isEmail = (data.objectType == ZmObjectManager.EMAIL); 626 var i = 0; 627 var html = []; 628 while (true) { 629 data.name1 = ++i > 1 || ZmContact.IS_ADDONE[data.name] ? data.name + i : data.name; 630 var values = data.attrs[data.name1]; 631 if (!values) { break; } 632 data.name1 = AjxStringUtil.htmlEncode(data.name1); 633 data.type = AjxStringUtil.htmlEncode(data.type); 634 values = AjxUtil.toArray(values); 635 for (var j=0; j<values.length; j++) { 636 var value = values[j]; 637 if (!isEmail) { 638 value = AjxStringUtil.htmlEncode(value); 639 } 640 if (ZmContact.IS_DATE[data.name]) { 641 var date = ZmEditContactViewOther.parseDate(value); 642 if (date) { 643 var includeYear = date.getFullYear() != 0; 644 var formatter = includeYear ? 645 AjxDateFormat.getDateInstance(AjxDateFormat.LONG) : new AjxDateFormat(ZmMsg.formatDateLongNoYear); 646 value = formatter.format(date); 647 } 648 } 649 if (data.findObjects) { 650 value = data.findObjects(value, data.objectType); 651 } 652 if (data.encode) { 653 value = data.encode(value); 654 } 655 data.value = value; 656 657 html.push(AjxTemplate.expand("#SplitView_list_item", data)); 658 } 659 data.first = false; 660 } 661 662 return html.join(""); 663 }; 664 665 ZmContactSplitView.showContactEmails = 666 function(data) { 667 var itemListData = ZmContactSplitView._getListData(data, ZmMsg.emailLabel, ZmObjectManager.EMAIL); 668 var typeFunc = function(data, name) { return data.isDL && ZmMsg.distributionList; }; 669 return ZmContactSplitView._showContactList(itemListData, ZmEditContactView.LISTS.EMAIL.attrs, typeFunc, !data.isDL); 670 }; 671 672 ZmContactSplitView.showContactPhones = 673 function(data) { 674 var itemListData = ZmContactSplitView._getListData(data, ZmMsg.phoneLabel, ZmObjectManager.PHONE); 675 return ZmContactSplitView._showContactList(itemListData, ZmEditContactView.LISTS.PHONE.attrs); 676 }; 677 678 ZmContactSplitView.showContactIMs = 679 function(data) { 680 681 var itemListData = ZmContactSplitView._getListData(data, ZmMsg.imLabel); 682 return ZmContactSplitView._showContactList(itemListData, ZmEditContactView.LISTS.IM.attrs); 683 }; 684 685 ZmContactSplitView.showContactAddresses = 686 function(data) { 687 688 var itemListData = ZmContactSplitView._getListData(data, ZmMsg.addressLabel); 689 var types = {"work":ZmMsg.work, "home":ZmMsg.home, "other":ZmMsg.other}; 690 var prefixes = ZmContact.ADDR_PREFIXES; 691 var suffixes = ZmContact.ADDR_SUFFIXES; 692 var html = []; 693 for (var i = 0; i < prefixes.length; i++) { 694 var count = 0; 695 var prefix = prefixes[i]; 696 itemListData.type = types[prefix] || prefix; 697 while (true) { 698 count++; 699 itemListData.address = null; 700 for (var j = 0; j < suffixes.length; j++) { 701 var suffix = suffixes[j]; 702 var name = [prefix, suffix, count > 1 ? count : ""].join(""); 703 var value = data.attrs[name]; 704 if (!value) { continue; } 705 value = AjxStringUtil.htmlEncode(value); 706 if (!itemListData.address) { 707 itemListData.address = {}; 708 } 709 itemListData.address[suffix] = value.replace(/\n/g,"<br/>"); 710 } 711 if (!itemListData.address) { break; } 712 itemListData.name = [prefix, "Address", count > 1 ? count : ""].join(""); 713 html.push(AjxTemplate.expand("#SplitView_address_value", itemListData)); 714 itemListData.first = false; 715 } 716 } 717 718 return html.join(""); 719 }; 720 721 ZmContactSplitView.showContactUrls = 722 function(data) { 723 var itemListData = ZmContactSplitView._getListData(data, ZmMsg.urlLabel, ZmObjectManager.URL); 724 var typeFunc = function(data, name) { return ZmMsg["AB_FIELD_" + name.replace("URL", "")]; }; 725 return ZmContactSplitView._showContactList(itemListData, ZmEditContactView.LISTS.URL.attrs, typeFunc); 726 }; 727 728 ZmContactSplitView.showContactOther = 729 function(data) { 730 731 var itemListData = ZmContactSplitView._getListData(data, ZmMsg.otherLabel); 732 itemListData.findObjects = data.findObjects; 733 var html = []; 734 html.push(ZmContactSplitView._showContactList(itemListData, ZmEditContactView.LISTS.OTHER.attrs)); 735 736 // find unknown attributes 737 var attrs = {}; 738 for (var a in itemListData.attrs) { 739 var aname = ZmContact.getPrefix(a); 740 if (aname in ZmContact.IS_IGNORE) { continue; } 741 attrs[aname] = true; 742 } 743 for (var id in ZmEditContactView.ATTRS) { 744 delete attrs[ZmEditContactView.ATTRS[id]]; 745 } 746 for (var id in ZmEditContactView.LISTS) { 747 var list = ZmEditContactView.LISTS[id]; 748 if (!list.attrs) { continue; } 749 for (var i = 0; i < list.attrs.length; i++) { 750 delete attrs[list.attrs[i]]; 751 } 752 } 753 var prefixes = ZmContact.ADDR_PREFIXES; 754 var suffixes = ZmContact.ADDR_SUFFIXES; 755 for (var i = 0; i < prefixes.length; i++) { 756 for (var j = 0; j < suffixes.length; j++) { 757 delete attrs[prefixes[i] + suffixes[j]]; 758 } 759 } 760 761 // display custom 762 for (var a in attrs) { 763 if (a === "notesHtml") { continue; } 764 itemListData.name = a; 765 itemListData.type = AjxStringUtil.capitalizeWords(AjxStringUtil.fromMixed(a)); 766 html.push(ZmContactSplitView._showContactListItem(itemListData)); 767 } 768 769 return html.join(""); 770 }; 771 772 ZmContactSplitView.showContactNotes = 773 function(data) { 774 775 var itemListData = ZmContactSplitView._getListData(data, ZmMsg.notesLabel); 776 itemListData.encode = AjxStringUtil.nl2br; 777 itemListData.name = ZmContact.F_notes; 778 itemListData.names = [ZmContact.F_notes]; 779 return ZmContactSplitView._showContactListItem(itemListData); 780 }; 781 782 ZmContactSplitView.showContactDLMembers = 783 function(data) { 784 785 var html = []; 786 var itemData = {contact:data.contact}; 787 if (data.dl) { 788 var list = data.dl.list; 789 var canExpand = data.contact.canExpand && (list.length > ZmContactSplitView.NUM_DL_MEMBERS || data.dl.more); 790 var lv = data.view._listPart; 791 var id = lv._expandId = Dwt.getNextId(); 792 var tdStyle = "", onclick = ""; 793 var list1 = []; 794 var len = Math.min(list.length, ZmContactSplitView.NUM_DL_MEMBERS); 795 for (var i = 0; i < len; i++) { 796 list1.push(data.findObjects ? data.findObjects(list[i], ZmObjectManager.EMAIL) : list[i]); 797 } 798 if (canExpand) { 799 if (!data.expandDL) { 800 list1.push(" ... "); 801 } 802 tdStyle = "style='cursor:pointer;'"; 803 var viewId = '"' + data.id + '"'; 804 var doExpand = data.expandDL ? "false" : "true"; 805 onclick = "onclick='ZmContactSplitView.expandDL(" + viewId + ", " + doExpand + ");'"; 806 } 807 itemData.value = list1.join(", "); 808 itemData.expandTdText = [tdStyle, onclick].join(" "); 809 if (!data.expandDL) { 810 itemData.html = []; 811 lv._getImageHtml(itemData.html, 0, canExpand ? "NodeCollapsed" : null, id); 812 html.push("<tr valign='top'>"); 813 html.push(AjxTemplate.expand("abook.Contacts#SplitView_dlmember-collapsed", itemData)); 814 html.push("</tr>"); 815 } else { 816 itemData.first = true; 817 for (var i = 0, len = list.length; i < len; i++) { 818 itemData.value = list[i]; 819 if (data.findObjects) { 820 itemData.value = data.findObjects(itemData.value, ZmObjectManager.EMAIL); 821 } 822 itemData.html = []; 823 lv._getImageHtml(itemData.html, 0, itemData.first ? "NodeExpanded" : null, id); 824 var rowIdText = ""; 825 if (i == len - 1) { 826 var rowId = data.view._dlLastRowId = Dwt.getNextId(); 827 rowIdText = "id='" + rowId + "'"; 828 } 829 html.push("<tr valign='top' " + rowIdText + ">"); 830 html.push(AjxTemplate.expand("abook.Contacts#SplitView_dlmember-expanded", itemData)); 831 html.push("</tr>"); 832 itemData.first = false; 833 } 834 } 835 } 836 837 return html.join(""); 838 }; 839 840 /** 841 * Displays contact group 842 * @param data {object} 843 * @return html {String} html representation of group 844 */ 845 ZmContactSplitView.showContactGroup = 846 function(data) { 847 var html = []; 848 if (!AjxUtil.isArray(data.groupMembers)) { 849 return ""; 850 } 851 for (var i = 0; i < data.groupMembers.length; i++) { 852 var member = data.groupMembers[i]; 853 var itemListData = {}; 854 var contact = member.__contact; 855 if (contact) { 856 itemListData.imageUrl = contact.getImageUrl(); 857 itemListData.defaultImageUrl = ZmZimbraMail.DEFAULT_CONTACT_ICON; 858 itemListData.imgClassName = contact.getIconLarge(); 859 itemListData.email = data.findObjects(contact.getEmail(), ZmObjectManager.EMAIL, true); 860 itemListData.title = data.findObjects(contact.getAttr(ZmContact.F_jobTitle), ZmObjectManager.TITLE, true); 861 itemListData.phone = data.findObjects(contact.getPhone(), ZmObjectManager.PHONE, true); 862 var isPhonetic = appCtxt.get(ZmSetting.PHONETIC_CONTACT_FIELDS); 863 var fullnameHtml = contact.getFullNameForDisplay(isPhonetic); 864 if (!isPhonetic) { 865 fullnameHtml = AjxStringUtil.htmlEncode(fullnameHtml); 866 } 867 itemListData.fullName = fullnameHtml; 868 } 869 else { 870 itemListData.imgClassName = "PersonInline_48"; 871 itemListData.email = data.findObjects(member.value, ZmObjectManager.EMAIL, true); 872 } 873 html.push(AjxTemplate.expand("abook.Contacts#SplitView_group", itemListData)); 874 } 875 return html.join(""); 876 877 }; 878 879 ZmContactSplitView.prototype._showDL = 880 function(subs, result) { 881 882 subs.dl = this._distributionList = result; 883 this._showContact(subs); 884 this._setTags(); 885 if (!this._rowHeight) { 886 var table = document.getElementById(this._detailsId); 887 if (table) { 888 this._rowHeight = Dwt.getSize(table.rows[0]).y; 889 } 890 } 891 892 if (subs.expandDL) { 893 Dwt.setHandler(this._dlScrollDiv, DwtEvent.ONSCROLL, ZmContactSplitView.handleDLScroll); 894 } 895 }; 896 897 /** 898 * @private 899 */ 900 ZmContactSplitView.prototype._resetVisibility = 901 function(isGroup) { 902 }; 903 904 /** 905 * @private 906 */ 907 ZmContactSplitView.prototype._setTags = 908 function() { 909 //use the helper to get the tags. 910 var tagsHtml = ZmTagsHelper.getTagsHtml(this._item, this); 911 this._setTagsHtml(tagsHtml); 912 }; 913 914 /** 915 * @private 916 */ 917 ZmContactSplitView.prototype._clearTags = 918 function() { 919 this._setTagsHtml(""); 920 }; 921 922 /** 923 * note this is called from ZmTagsHelper 924 * @param html 925 */ 926 ZmContactSplitView.prototype._setTagsHtml = 927 function(html) { 928 var tagCell = document.getElementById(this._tagCellId); 929 if (!tagCell) { return; } 930 tagCell.innerHTML = html; 931 }; 932 933 934 ZmContactSplitView.prototype._sashCallback = function(delta) { 935 var sashWidth = this._sash.getSize().x; 936 var totalWidth = Dwt.getSize(this._splitviewCell).x; 937 938 var origListWidth = this._listPart.getSize().x; 939 var newListWidth = origListWidth + delta; 940 var newContentPos = newListWidth + sashWidth; 941 var newContentWidth = totalWidth - newContentPos; 942 943 if (delta < 0 && newListWidth <= ZmContactSplitView.LIST_MIN_WIDTH) { 944 newListWidth = ZmContactSplitView.LIST_MIN_WIDTH; 945 newContentPos = newListWidth + sashWidth; 946 newContentWidth = totalWidth - newContentPos; 947 } else if (delta > 0 && newContentWidth <= ZmContactSplitView.CONTENT_MIN_WIDTH) { 948 newContentWidth = ZmContactSplitView.CONTENT_MIN_WIDTH; 949 newContentPos = totalWidth - newContentWidth; 950 newListWidth = newContentPos - sashWidth; 951 } 952 953 delta = newListWidth - origListWidth; 954 955 this._listPart.setSize(newListWidth, Dwt.DEFAULT); 956 Dwt.setBounds(this._contentCell, newContentPos, Dwt.DEFAULT, newContentWidth, Dwt.DEFAULT); 957 958 return delta; 959 }; 960 961 /** 962 * View for displaying the contact information. Provides events for enabling text selection. 963 * @param {Object} params hash of params: 964 * parent parent control 965 * controller owning controller 966 */ 967 ZmContactView = function(params) { 968 DwtComposite.call(this, {parent:params.parent}); 969 this._controller = params.controller; 970 this._tabGroup = new DwtTabGroup('ZmContactView'); 971 this.addListener(DwtEvent.ONSELECTSTART, this._selectStartListener.bind(this)); 972 this._setMouseEventHdlrs(); 973 }; 974 ZmContactView.prototype = new DwtControl; 975 ZmContactView.prototype.constructor = ZmContactView; 976 ZmContactView.prototype.isZmContactView = true; 977 ZmContactView.prototype.role = 'document'; 978 ZmContactView.prototype.toString = function() { return "ZmContactView"; }; 979 980 ZmContactView.prototype.getTabGroupMember = 981 function() { 982 return this._tabGroup; 983 }; 984 985 ZmContactView.prototype._selectStartListener = 986 function(ev) { 987 // reset mouse event to propagate event to browser (allows text selection) 988 ev._stopPropagation = false; 989 ev._returnValue = true; 990 }; 991 992 ZmContactView.prototype.clear = function() { 993 this.getTabGroupMember().removeAllMembers(); 994 Dwt.removeChildren(this.getHtmlElement()); 995 }; 996 997 ZmContactView.prototype.createHtml = function(templateid, subs) { 998 this._createHtmlFromTemplate(templateid, subs); 999 1000 // add the header row and all objects to the tab order 1001 var rows = Dwt.byClassName('rowValue', this.getHtmlElement()); 1002 1003 this.getTabGroupMember().removeAllMembers(); 1004 this.getTabGroupMember().addMember(rows[0]); 1005 1006 AjxUtil.foreach(rows, this._makeRowFocusable.bind(this)); 1007 }; 1008 1009 ZmContactView.prototype._makeRowFocusable = function(row) { 1010 this._makeFocusable(row); 1011 1012 var objects = Dwt.byClassName('Object', row); 1013 1014 for (var i = 0; i < objects.length; i++) { 1015 this._makeFocusable(objects[i]); 1016 this.getTabGroupMember().addMember(objects[i]); 1017 1018 objects[i].setAttribute('aria-describedby', row.getAttribute('aria-labelledby')); 1019 } 1020 }; 1021 1022 /** 1023 * Creates a simple view. 1024 * @class 1025 * This class represents a simple contact list view (contains only full name). 1026 * 1027 * @param {Hash} params a hash of parameters 1028 * @extends ZmContactsBaseView 1029 */ 1030 ZmContactSimpleView = function(params) { 1031 1032 if (arguments.length == 0) { return; } 1033 1034 this._view = params.view = params.controller.getCurrentViewId(); 1035 params.className = "ZmContactSimpleView"; 1036 ZmContactsBaseView.call(this, params); 1037 1038 this._normalClass = DwtListView.ROW_CLASS + " SimpleContact"; 1039 this._selectedClass = [DwtListView.ROW_CLASS, DwtCssStyle.SELECTED].join("-"); 1040 }; 1041 1042 ZmContactSimpleView.prototype = new ZmContactsBaseView; 1043 ZmContactSimpleView.prototype.constructor = ZmContactSimpleView; 1044 1045 ZmContactSimpleView.prototype.isZmContactSimpleView = true; 1046 ZmContactSimpleView.prototype.toString = function() { return "ZmContactSimpleView"; }; 1047 1048 /** 1049 * Sets the list. 1050 * 1051 * @param {ZmContactList} list the list 1052 * @param {String} defaultColumnSort the sort field 1053 * @param {String} folderId the folder id 1054 * @param {Boolean} isSearchResults is this a search tab? 1055 */ 1056 ZmContactSimpleView.prototype.set = 1057 function(list, defaultColumnSort, folderId, isSearchResults) { 1058 var fid = folderId || this._controller.getFolderId(); 1059 ZmContactsBaseView.prototype.set.call(this, list, defaultColumnSort, fid); 1060 1061 if (!(this._list instanceof AjxVector) || this._list.size() == 0) { 1062 this.parent.clear(); 1063 } 1064 1065 this.parent.showAlphabetBar(!isSearchResults); 1066 this.parent.enableAlphabetBar(fid != ZmOrganizer.ID_DLS); 1067 }; 1068 1069 /** 1070 * Sets the selection. 1071 * 1072 * @param {Object} item the item 1073 * @param {Boolean} skipNotify <code>true</code> to skip notification 1074 */ 1075 ZmContactSimpleView.prototype.setSelection = 1076 function(item, skipNotify) { 1077 // clear the right, content pane if no item to select 1078 if (!item) { 1079 this.parent.clear(); 1080 } 1081 1082 ZmContactsBaseView.prototype.setSelection.call(this, item, skipNotify); 1083 }; 1084 1085 /** 1086 * @private 1087 */ 1088 ZmContactSimpleView.prototype._setNoResultsHtml = 1089 function() { 1090 1091 var div = document.createElement("div"); 1092 1093 var isSearch = this._controller._contactSearchResults; 1094 if (isSearch){ 1095 isSearch = !(this._controller._currentSearch && this._controller._currentSearch.folderId); 1096 } 1097 //bug:28365 Show custom "No Results" for Search. 1098 if ((isSearch || this._folderId == ZmFolder.ID_TRASH) && AjxTemplate.getTemplate("abook.Contacts#SimpleView-NoResults-Search")) { 1099 div.innerHTML = AjxTemplate.expand("abook.Contacts#SimpleView-NoResults-Search"); 1100 } else { 1101 // Shows "No Results", unless the skin has overridden to show links to plaxo. 1102 div.innerHTML = AjxTemplate.expand("abook.Contacts#SimpleView-NoResults"); 1103 } 1104 this._addRow(div); 1105 1106 this.parent.clear(); 1107 }; 1108 1109 /** 1110 * @private 1111 */ 1112 ZmContactSimpleView.prototype._changeListener = 1113 function(ev) { 1114 ZmContactsBaseView.prototype._changeListener.call(this, ev); 1115 1116 // bug fix #14874 - if moved to trash, show strike-thru 1117 var folderId = this._controller.getFolderId(); 1118 if (!folderId && ev.event == ZmEvent.E_MOVE) { 1119 var contact = ev._details.items[0]; 1120 var folder = appCtxt.getById(contact.folderId); 1121 var row = this._getElement(contact, ZmItem.F_ITEM_ROW); 1122 if (row) { 1123 row.className = (folder && folder.isInTrash()) ? "Trash" : ""; 1124 } 1125 } 1126 }; 1127 1128 /** 1129 * @private 1130 */ 1131 ZmContactSimpleView.prototype._modifyContact = 1132 function(ev) { 1133 ZmContactsBaseView.prototype._modifyContact.call(this, ev); 1134 1135 if (ev.getDetail("fileAsChanged")) { 1136 var selected = this.getSelection()[0]; 1137 this._layout(); 1138 this.setSelection(selected, true); 1139 } 1140 }; 1141 1142 /** 1143 * @private 1144 */ 1145 ZmContactSimpleView.prototype._layout = 1146 function() { 1147 // explicitly remove each child (setting innerHTML causes mem leak) 1148 while (this._parentEl.hasChildNodes()) { 1149 cDiv = this._parentEl.removeChild(this._parentEl.firstChild); 1150 this._data[cDiv.id] = null; 1151 } 1152 1153 var now = new Date(); 1154 var size = this._list.size(); 1155 for (var i = 0; i < size; i++) { 1156 var item = this._list.get(i); 1157 var div = item ? this._createItemHtml(item, {now:now}) : null; 1158 if (div) { 1159 this._addRow(div); 1160 } 1161 } 1162 }; 1163 1164 ZmContactSimpleView.prototype.useListElement = 1165 function() { 1166 return true; 1167 } 1168 1169 /** 1170 * A contact is normally displayed in a list view with no headers, and shows 1171 * just an icon and name. 1172 * 1173 * @param {ZmContact} contact the contact to display 1174 * @param {Hash} params a hash of optional parameters 1175 * 1176 * @private 1177 */ 1178 ZmContactSimpleView.prototype._createItemHtml = 1179 function(contact, params, asHtml, count) { 1180 1181 params = params || {}; 1182 1183 var htmlArr = []; 1184 var idx = 0; 1185 if (!params.isDragProxy) { 1186 params.divClass = this._normalClass; 1187 } 1188 if (asHtml) { 1189 idx = this._getDivHtml(contact, params, htmlArr, idx, count); 1190 } else { 1191 var div = this._getDiv(contact, params); 1192 } 1193 var folder = this._folderId && appCtxt.getById(this._folderId); 1194 if (div) { 1195 if (params.isDragProxy) { 1196 div.style.width = "175px"; 1197 div.style.padding = "4px"; 1198 } 1199 } 1200 1201 idx = this._getRow(htmlArr, idx, contact, params); 1202 1203 // checkbox selection 1204 if (appCtxt.get(ZmSetting.SHOW_SELECTION_CHECKBOX)) { 1205 idx = this._getImageHtml(htmlArr, idx, "CheckboxUnchecked", this._getFieldId(contact, ZmItem.F_SELECTION)); 1206 } 1207 1208 // icon 1209 htmlArr[idx++] = AjxImg.getImageHtml(contact.getIcon(folder), null, "id=" + this._getFieldId(contact, "type"),null, null, ["ZmContactIcon"]); 1210 1211 // file as 1212 htmlArr[idx++] = "<div id='" + this._getFieldId(contact, "fileas") + "'>"; 1213 htmlArr[idx++] = AjxStringUtil.htmlEncode(contact.getFileAs() || contact.getFileAsNoName()); 1214 htmlArr[idx++] = "</div>"; 1215 htmlArr[idx++] = "<div class='ZmListFlagsWrapper'>"; 1216 1217 if (!params.isDragProxy) { 1218 // if read only, show lock icon in place of the tag column since we dont 1219 // currently support tags for "read-only" contacts (i.e. shares) 1220 var isLocked = folder ? folder.link && folder.isReadOnly() : contact.isLocked(); 1221 if (isLocked) { 1222 htmlArr[idx++] = AjxImg.getImageHtml("ReadOnly"); 1223 } else if (!contact.isReadOnly() && appCtxt.get(ZmSetting.TAGGING_ENABLED)) { 1224 // otherwise, show tag if there is one 1225 idx = this._getImageHtml(htmlArr, idx, contact.getTagImageInfo(), this._getFieldId(contact, ZmItem.F_TAG), ["Tag"]); 1226 } 1227 } 1228 1229 htmlArr[idx++] = "</div></div></li>"; 1230 1231 if (div) { 1232 div.innerHTML = htmlArr.join(""); 1233 return div; 1234 } else { 1235 return htmlArr.join(""); 1236 } 1237 }; 1238 1239 /** 1240 * @private 1241 */ 1242 ZmContactSimpleView.prototype._getToolTip = 1243 function(params) { 1244 1245 var ttParams = { 1246 contact: params.item, 1247 ev: params.ev 1248 }; 1249 var ttCallback = new AjxCallback(this, 1250 function(callback) { 1251 appCtxt.getToolTipMgr().getToolTip(ZmToolTipMgr.PERSON, ttParams, callback); 1252 }); 1253 return {callback:ttCallback}; 1254 }; 1255 1256 /** 1257 * @private 1258 */ 1259 ZmContactSimpleView.prototype._getDateToolTip = 1260 function(item, div) { 1261 div._dateStr = div._dateStr || this._getDateToolTipText(item.modified, ["<b>", ZmMsg.lastModified, "</b><br>"].join("")); 1262 return div._dateStr; 1263 }; 1264