1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 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 group view classes. 27 */ 28 29 /** 30 * Creates the group view. 31 * @class 32 * This class represents the contact group view. 33 * 34 * @param {DwtComposite} parent the parent 35 * @param {ZmContactController} controller the controller 36 * 37 * @constructor 38 * 39 * @extends DwtComposite 40 */ 41 ZmGroupView = function(parent, controller) { 42 if (arguments.length == 0) return; 43 DwtComposite.call(this, {parent:parent, className:"ZmContactView", posStyle:DwtControl.ABSOLUTE_STYLE}); 44 this.setScrollStyle(Dwt.CLIP); //default is clip, for regular group it's fine. (otherwise there's always a scroll for no reason, not sure why). For DL we change in "set" 45 46 this._controller = controller; 47 48 this._view = ZmId.VIEW_GROUP; 49 50 this._tagList = appCtxt.getTagTree(); 51 this._tagList.addChangeListener(new AjxListener(this, this._tagChangeListener)); 52 53 this._changeListener = new AjxListener(this, this._groupChangeListener); 54 this._detailedSearch = appCtxt.get(ZmSetting.DETAILED_CONTACT_SEARCH_ENABLED); 55 56 /* following few are used in ZmContactPicker methods we delegate to from here */ 57 this._ascending = true; 58 this._emailList = new AjxVector(); 59 this._includeContactsWithNoEmail = true; 60 61 this._groupMemberMods = {}; 62 this._tabGroup = new DwtTabGroup(this._htmlElId); 63 64 }; 65 66 ZmGroupView.prototype = new DwtComposite; 67 ZmGroupView.prototype.constructor = ZmGroupView; 68 ZmGroupView.prototype.isZmGroupView = true; 69 70 71 /** 72 * Returns a string representation of the object. 73 * 74 * @return {String} a string representation of the object 75 */ 76 ZmGroupView.prototype.toString = 77 function() { 78 return "ZmGroupView"; 79 }; 80 81 ZmGroupView.DIALOG_X = 50; 82 ZmGroupView.DIALOG_Y = 100; 83 84 ZmGroupView.MAIL_POLICY_ANYONE = "ANYONE"; 85 ZmGroupView.MAIL_POLICY_MEMBERS = "MEMBERS"; 86 ZmGroupView.MAIL_POLICY_INTERNAL = "INTERNAL"; 87 ZmGroupView.MAIL_POLICY_SPECIFIC = "SPECIFIC"; 88 89 ZmGroupView.GRANTEE_TYPE_USER = "usr"; 90 ZmGroupView.GRANTEE_TYPE_GUEST = "gst"; // an external user. This is returned by GetDistributionListResponse for a non-internal user. Could be a mix of this and "usr" 91 ZmGroupView.GRANTEE_TYPE_EMAIL = "email"; //this covers both guest and user when setting rights via the setRights op of DistributionListActionRequest 92 ZmGroupView.GRANTEE_TYPE_GROUP = "grp"; 93 ZmGroupView.GRANTEE_TYPE_ALL = "all"; 94 ZmGroupView.GRANTEE_TYPE_PUBLIC = "pub"; 95 96 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP = []; 97 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP[ZmGroupView.GRANTEE_TYPE_USER] = ZmGroupView.MAIL_POLICY_SPECIFIC; 98 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP[ZmGroupView.GRANTEE_TYPE_GUEST] = ZmGroupView.MAIL_POLICY_SPECIFIC; 99 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP[ZmGroupView.GRANTEE_TYPE_GROUP] = ZmGroupView.MAIL_POLICY_MEMBERS; 100 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP[ZmGroupView.GRANTEE_TYPE_ALL] = ZmGroupView.MAIL_POLICY_INTERNAL; 101 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP[ZmGroupView.GRANTEE_TYPE_PUBLIC] = ZmGroupView.MAIL_POLICY_ANYONE; 102 103 // 104 // Public methods 105 // 106 107 // need this since contact view now derives from list controller 108 ZmGroupView.prototype.getList = function() { return null; } 109 110 /** 111 * Gets the contact. 112 * 113 * @return {ZmContact} the contact 114 */ 115 ZmGroupView.prototype.getContact = 116 function() { 117 return this._contact; 118 }; 119 120 /** 121 * Gets the controller. 122 * 123 * @return {ZmContactController} the controller 124 */ 125 ZmGroupView.prototype.getController = 126 function() { 127 return this._controller; 128 }; 129 130 // Following two overrides are a hack to allow this view to pretend it's a list view 131 ZmGroupView.prototype.getSelection = function() { 132 return this.getContact(); 133 }; 134 135 ZmGroupView.prototype.getSelectionCount = function() { 136 return 1; 137 }; 138 139 ZmGroupView.prototype.isDistributionList = 140 function() { 141 return this._contact.isDistributionList(); 142 }; 143 144 ZmGroupView.prototype.set = 145 function(contact, isDirty) { 146 this._attr = {}; 147 148 if (this._contact) { 149 this._contact.removeChangeListener(this._changeListener); 150 } 151 contact.addChangeListener(this._changeListener); 152 this._contact = this._item = contact; 153 154 if (!this._htmlInitialized) { 155 this._createHtml(); 156 this._addWidgets(); 157 this._installKeyHandlers(); 158 this._tabGroup.addMember(this._getTabGroupMembers()); 159 } 160 161 this._setFields(); 162 this._emailListOffset = 0; 163 this._isDirty = isDirty; 164 165 if (contact.isDistributionList()) { 166 if (this._usernameEditable) { 167 this._groupNameInput.addListener(DwtEvent.ONBLUR, this._controller.updateTabTitle.bind(this._controller)); 168 } 169 if (this._domainEditable) { 170 document.getElementById(this._groupNameDomainId).onblur = this._controller.updateTabTitle.bind(this._controller); 171 } 172 } 173 else { 174 this._groupNameInput.addListener(DwtEvent.ONBLUR, this._controller.updateTabTitle.bind(this._controller)); 175 } 176 177 this.search(null, null, true); 178 }; 179 180 /** 181 * this is called from ZmContactController.prototype._postShowCallback 182 */ 183 ZmGroupView.prototype.postShow = 184 function() { 185 if (this._contact.isDistributionList()) { 186 this._dlMembersTabView.showMe(); //have to call it now so it's sized correctly. 187 } 188 }; 189 190 ZmGroupView.prototype.getModifiedAttrs = 191 function() { 192 if (!this.isDirty()) return null; 193 194 var mods = this._attr = []; 195 196 // get field values 197 var groupName = this._getGroupName(); 198 var folderId = this._getFolderId(); 199 200 if (this.isDistributionList()) { 201 var dlInfo = this._contact.dlInfo; 202 if (groupName != this._contact.getEmail()) { 203 mods[ZmContact.F_email] = groupName; 204 } 205 if (dlInfo.displayName != this._getDlDisplayName()) { 206 mods[ZmContact.F_dlDisplayName] = this._getDlDisplayName(); 207 } 208 if (dlInfo.description != this._getDlDesc()) { 209 mods[ZmContact.F_dlDesc] = this._getDlDesc(); 210 } 211 if (dlInfo.hideInGal != this._getDlHideInGal()) { 212 mods[ZmContact.F_dlHideInGal] = this._getDlHideInGal() ? "TRUE" : "FALSE"; 213 } 214 if (dlInfo.notes != this._getDlNotes()) { 215 mods[ZmContact.F_dlNotes] = this._getDlNotes(); 216 } 217 if (dlInfo.subscriptionPolicy != this._getDlSubscriptionPolicy()) { 218 mods[ZmContact.F_dlSubscriptionPolicy] = this._getDlSubscriptionPolicy(); 219 } 220 if (dlInfo.unsubscriptionPolicy != this._getDlUnsubscriptionPolicy()) { 221 mods[ZmContact.F_dlUnsubscriptionPolicy] = this._getDlUnsubscriptionPolicy(); 222 } 223 if (!AjxUtil.arrayCompare(dlInfo.owners, this._getDlOwners())) { 224 mods[ZmContact.F_dlListOwners] = this._getDlOwners(); 225 } 226 if (dlInfo.mailPolicy != this._getDlMailPolicy() 227 || (this._getDlMailPolicy() == ZmGroupView.MAIL_POLICY_SPECIFIC 228 && !AjxUtil.arrayCompare(dlInfo.mailPolicySpecificMailers, this._getDlSpecificMailers()))) { 229 mods[ZmContact.F_dlMailPolicy] = this._getDlMailPolicy(); 230 mods[ZmContact.F_dlMailPolicySpecificMailers] = this._getDlSpecificMailers(); 231 } 232 233 if (this._groupMemberMods) { 234 mods[ZmContact.F_groups] = this._getModifiedMembers(); 235 this._groupMemberMods = {}; //empty the mods 236 } 237 238 return mods; 239 } 240 241 // creating new contact (possibly some fields - but not ID - prepopulated) 242 if (this._contact.id == null || (this._contact.isGal && !this.isDistributionList())) { 243 mods[ZmContact.F_folderId] = folderId; 244 mods[ZmContact.F_fileAs] = ZmContact.computeCustomFileAs(groupName); 245 mods[ZmContact.F_nickname] = groupName; 246 mods[ZmContact.F_groups] = this._getGroupMembers(); 247 mods[ZmContact.F_type] = "group"; 248 } 249 else { 250 // modifying existing contact 251 if (!this.isDistributionList() && this._contact.getFileAs() != groupName) { 252 mods[ZmContact.F_fileAs] = ZmContact.computeCustomFileAs(groupName); 253 mods[ZmContact.F_nickname] = groupName; 254 } 255 256 if (this._groupMemberMods) { 257 mods[ZmContact.F_groups] = this._getModifiedMembers(); 258 this._groupMemberMods = {}; //empty the mods 259 } 260 261 var oldFolderId = this._contact.addrbook ? this._contact.addrbook.id : ZmFolder.ID_CONTACTS; 262 if (folderId != oldFolderId) { 263 mods[ZmContact.F_folderId] = folderId; 264 } 265 } 266 267 return mods; 268 }; 269 270 ZmGroupView.prototype._getModifiedMembers = 271 function() { 272 var modifiedMembers = []; 273 for (var id in this._groupMemberMods) { 274 if (this._groupMemberMods[id].op) { 275 modifiedMembers.push(this._groupMemberMods[id]); 276 } 277 } 278 return modifiedMembers; 279 }; 280 281 282 ZmGroupView.prototype._getFullName = 283 function() { 284 return this._getGroupName(); 285 }; 286 287 ZmGroupView.prototype._getGroupDomainName = 288 function() { 289 if (this.isDistributionList()) { 290 return this._domainEditable 291 ? AjxStringUtil.trim(document.getElementById(this._groupNameDomainId).value) 292 : this._emailDomain; 293 } 294 return AjxStringUtil.trim(document.getElementById(this._groupNameDomainId).value); 295 }; 296 297 ZmGroupView.prototype._getGroupName = 298 function() { 299 if (this.isDistributionList()) { 300 var username = this._getDlAddressLocalPart(); 301 return username + "@" + this._getGroupDomainName(); 302 } 303 return AjxStringUtil.trim(this._groupNameInput.getValue()); 304 }; 305 306 ZmGroupView.prototype._getDlAddressLocalPart = 307 function() { 308 return this._usernameEditable ? AjxStringUtil.trim(this._groupNameInput.getValue()) : this._emailUsername; 309 }; 310 311 ZmGroupView.prototype._getDlDisplayName = 312 function() { 313 return AjxStringUtil.trim(document.getElementById(this._dlDisplayNameId).value); 314 }; 315 316 ZmGroupView.prototype._getDlDesc = 317 function() { 318 return AjxStringUtil.trim(document.getElementById(this._dlDescId).value); 319 }; 320 321 ZmGroupView.prototype._getDlNotes = 322 function() { 323 return AjxStringUtil.trim(document.getElementById(this._dlNotesId).value); 324 }; 325 326 ZmGroupView.prototype._getDlHideInGal = 327 function() { 328 return document.getElementById(this._dlHideInGalId).checked; 329 }; 330 331 ZmGroupView.prototype._getDlSpecificMailers = 332 function() { 333 return this._getUserList(this._dlListSpecificMailersId); 334 }; 335 336 ZmGroupView.prototype._getDlOwners = 337 function() { 338 return this._getUserList(this._dlListOwnersId); 339 }; 340 341 ZmGroupView.prototype._getUserList = 342 function(fldId) { 343 var users = AjxStringUtil.trim(document.getElementById(fldId).value).split(";"); 344 var retUsers = []; 345 for (var i = 0; i < users.length; i++) { 346 var user = AjxStringUtil.trim(users[i]); 347 if (user != "") { 348 retUsers.push(user); 349 } 350 } 351 return retUsers; 352 }; 353 354 355 ZmGroupView.prototype._getDlSubscriptionPolicy = 356 function() { 357 return this._getDlPolicy(this._dlSubscriptionPolicyId, this._subsPolicyOpts); 358 }; 359 360 ZmGroupView.prototype._getDlUnsubscriptionPolicy = 361 function() { 362 return this._getDlPolicy(this._dlUnsubscriptionPolicyId, this._subsPolicyOpts); 363 }; 364 365 ZmGroupView.prototype._getDlMailPolicy = 366 function() { 367 return this._getDlPolicy(this._dlMailPolicyId, this._mailPolicyOpts); 368 }; 369 370 ZmGroupView.prototype._getDlPolicy = 371 function(fldId, opts) { 372 for (var i = 0; i < opts.length; i++) { 373 var opt = opts[i]; 374 if (document.getElementById(fldId[opt]).checked) { 375 return opt; 376 } 377 } 378 }; 379 380 381 ZmGroupView.prototype.isEmpty = 382 function(checkEither) { 383 var groupName = this._getGroupName(); 384 var members = ( this._groupMembersListView.getList() && this._groupMembersListView.getList().size() > 0 ); 385 386 return checkEither 387 ? (groupName == "" || !members ) 388 : (groupName == "" && !members ); 389 }; 390 391 392 ZmGroupView.prototype.isValidDlName = 393 function() { 394 if (!this.isDistributionList()) { 395 return true; 396 } 397 if (!this._usernameEditable) { 398 return true; //to be on the safe and clear side. no need to check. 399 } 400 var account = this._getDlAddressLocalPart(); 401 return AjxEmailAddress.accountPat.test(account); 402 }; 403 404 ZmGroupView.prototype.isValidDlDomainName = 405 function() { 406 if (!this.isDistributionList()) { 407 return true; 408 } 409 if (!this._domainEditable) { 410 return true; //this takes care of a "vanilla" owner with no create rights. 411 } 412 413 var domain = this._getGroupDomainName(); 414 return this._allowedDomains[domain]; 415 }; 416 417 ZmGroupView.prototype.isValidOwners = 418 function() { 419 if (!this.isDistributionList()) { 420 return true; 421 } 422 return this._getDlOwners().length > 0 423 }; 424 425 426 ZmGroupView.prototype.isValidMailPolicy = 427 function() { 428 if (!this.isDistributionList()) { 429 return true; 430 } 431 return this._getDlMailPolicy() != ZmGroupView.MAIL_POLICY_SPECIFIC || this._getDlSpecificMailers().length > 0; 432 }; 433 434 435 436 ZmGroupView.prototype.isValid = 437 function() { 438 // check for required group name 439 if (this.isDirty() && this.isEmpty(true)) { 440 return false; 441 } 442 if (!this.isValidDlName()) { 443 return false; 444 } 445 if (!this.isValidDlDomainName()) { 446 return false; 447 } 448 if (!this.isValidOwners()) { 449 return false; 450 } 451 if (!this.isValidMailPolicy()) { 452 return false; 453 } 454 return true; 455 }; 456 457 //todo - really not sure why this way of having 3 methods with parallel values conditions is used here this way. I just continued to build on what was there, but should check if it can be simplified. 458 ZmGroupView.prototype.getInvalidItems = 459 function() { 460 if (this.isValid()) { 461 return []; 462 } 463 var items = []; 464 if (!this.isValidDlName()) { 465 items.push("dlName"); 466 } 467 if (this.isEmpty(true)) { 468 items.push("members"); 469 } 470 if (!this.isValidDlDomainName()) { 471 items.push("dlDomainName"); 472 } 473 if (!this.isValidOwners()) { 474 items.push("owners"); 475 } 476 if (!this.isValidMailPolicy()) { 477 items.push("mailPolicy"); 478 } 479 return items; 480 }; 481 482 ZmGroupView.prototype.getErrorMessage = function(id) { 483 if (this.isValid()) { 484 return null; 485 } 486 if (id == "members") { 487 return this.isDistributionList() ? ZmMsg.errorMissingDlMembers : ZmMsg.errorMissingGroup; 488 } 489 if (id == "dlName") { 490 return ZmMsg.dlInvalidName; 491 } 492 if (id == "dlDomainName") { 493 return ZmMsg.dlInvalidDomainName; 494 } 495 if (id == "owners") { 496 return ZmMsg.dlInvalidOwners; 497 } 498 if (id == "mailPolicy") { 499 return ZmMsg.dlInvalidMailPolicy; 500 } 501 502 }; 503 504 ZmGroupView.prototype.enableInputs = 505 function(bEnable) { 506 if (this.isDistributionList()) { 507 if (this._usernameEditable) { 508 document.getElementById(this._groupNameId).disabled = !bEnable; 509 } 510 if (this._domainEditable) { 511 document.getElementById(this._groupNameDomainId).disabled = !bEnable; 512 } 513 document.getElementById(this._dlDisplayNameId).disabled = !bEnable; 514 document.getElementById(this._dlDescId).disabled = !bEnable; 515 document.getElementById(this._dlHideInGalId).disabled = !bEnable; 516 document.getElementById(this._dlNotesId).disabled = !bEnable; 517 for (var i = 0; i < this._subsPolicyOpts.length; i++) { 518 var opt = this._subsPolicyOpts[i]; 519 document.getElementById(this._dlSubscriptionPolicyId[opt]).disabled = !bEnable; 520 document.getElementById(this._dlUnsubscriptionPolicyId[opt]).disabled = !bEnable; 521 } 522 document.getElementById(this._dlListOwnersId).disabled = !bEnable; 523 } 524 else { 525 document.getElementById(this._groupNameId).disabled = !bEnable; 526 } 527 if (!this._noManualEntry) { 528 this._groupMembers.disabled = !bEnable; 529 } 530 for (var fieldId in this._searchField) { 531 this._searchField[fieldId].disabled = !bEnable; 532 } 533 }; 534 535 ZmGroupView.prototype.isDirty = 536 function() { 537 return this._isDirty; 538 }; 539 540 ZmGroupView.prototype.getTitle = 541 function() { 542 return [ZmMsg.zimbraTitle, this.isDistributionList() ? ZmMsg.distributionList : ZmMsg.group].join(": "); 543 }; 544 545 ZmGroupView.prototype.setSize = 546 function(width, height) { 547 // overloaded since base class calls sizeChildren which we dont care about 548 DwtComposite.prototype.setSize.call(this, width, height); 549 }; 550 551 ZmGroupView.prototype.setBounds = 552 function(x, y, width, height) { 553 DwtComposite.prototype.setBounds.call(this, x, y, width, height); 554 if(this._addNewField){ 555 Dwt.setSize(this._addNewField, Dwt.DEFAULT, 50); 556 } 557 this._groupMembersListView.setSize(Dwt.DEFAULT, height-150); 558 559 var headerTableHeight = Dwt.getSize(this._headerRow).y; 560 var tabBarHeight = this._tabBar ? Dwt.getSize(this._tabBar).y : 0; //only DL 561 var searchFieldsRowHeight = Dwt.getSize(this._searchFieldsRow).y; 562 var manualAddRowHeight = Dwt.getSize(this._manualAddRow).y; 563 var navButtonsRowHeight = Dwt.getSize(this._navButtonsRow).y; 564 var listHeight = height - headerTableHeight - tabBarHeight - searchFieldsRowHeight - manualAddRowHeight - navButtonsRowHeight - 40; 565 this._listview.setSize(Dwt.DEFAULT, listHeight); 566 }; 567 568 ZmGroupView.prototype.cleanup = 569 function() { 570 for (var fieldId in this._searchField) { 571 this._searchField[fieldId].value = ""; 572 } 573 this._listview.removeAll(true); 574 this._groupMembersListView.removeAll(true); 575 this._addButton.setEnabled(false); 576 this._addAllButton.setEnabled(false); 577 this._prevButton.setEnabled(false); 578 this._nextButton.setEnabled(false); 579 if (this._addNewField) { 580 this._addNewField.value = ''; 581 } 582 }; 583 584 585 // Private methods 586 587 ZmGroupView.prototype._setFields = 588 function() { 589 // bug fix #35059 - always reset search-in select since non-zimbra accounts don't support GAL 590 if (appCtxt.isOffline && appCtxt.accountList.size() > 1 && this._searchInSelect) { 591 this._searchInSelect.clearOptions(); 592 this._resetSearchInSelect(); 593 } 594 595 this._setGroupName(); 596 if (this.isDistributionList()) { 597 this._setDlFields(); 598 } 599 this._setGroupMembers(); 600 this._setTags(); 601 }; 602 603 ZmGroupView.prototype._setTitle = 604 function(title) { 605 var div = document.getElementById(this._titleId); 606 var fileAs = title || this._contact.getFileAs(); 607 div.innerHTML = AjxStringUtil.htmlEncode(fileAs) || (this._contact.id ? " " : ZmMsg.newGroup); 608 }; 609 610 ZmGroupView.prototype._getTagCell = 611 function() { 612 return document.getElementById(this._tagsId); 613 }; 614 615 ZmGroupView.prototype.getSearchFieldValue = 616 function(fieldId) { 617 if (!fieldId && !this._detailedSearch) { 618 fieldId = ZmContactPicker.SEARCH_BASIC; 619 } 620 var field = this._searchField[fieldId]; 621 return field && AjxStringUtil.trim(field.value) || ""; 622 }; 623 624 ZmGroupView.prototype._createHtml = 625 function() { 626 this._headerRowId = this._htmlElId + "_headerRow"; 627 this._titleId = this._htmlElId + "_title"; 628 this._tagsId = this._htmlElId + "_tags"; 629 this._groupNameId = this._htmlElId + "_groupName"; 630 631 if (this.isDistributionList()) { 632 this._groupNameDomainId = this._htmlElId + "_groupNameDomain"; 633 this._allowedDomains = appCtxt.createDistListAllowedDomainsMap; 634 this._emailDomain = appCtxt.createDistListAllowedDomains[0]; 635 this._emailUsername = ""; 636 var email = this._contact.getEmail(); 637 if (email) { 638 var temp = email.split("@"); 639 this._emailUsername = temp[0]; 640 this._emailDomain = temp[1]; 641 } 642 var isCreatingNew = !email; 643 var domainCount = appCtxt.createDistListAllowedDomains.length; 644 this._domainEditable = (domainCount > 1) && (isCreatingNew || this._allowedDomains[this._emailDomain]); //since a rename from one domain to another is like deleting on one and creating on other, both require createDistList right on the domains 645 this._usernameEditable = isCreatingNew || this._allowedDomains[this._emailDomain]; 646 647 this._dlDisplayNameId = this._htmlElId + "_dlDisplayName"; 648 this._dlDescId = this._htmlElId + "_dlDesc"; 649 this._dlHideInGalId = this._htmlElId + "_dlHideInGal"; 650 this._dlNotesId = this._htmlElId + "_dlNotes"; 651 this._subsPolicyOpts = [ZmContactSplitView.SUBSCRIPTION_POLICY_ACCEPT, 652 ZmContactSplitView.SUBSCRIPTION_POLICY_APPROVAL, 653 ZmContactSplitView.SUBSCRIPTION_POLICY_REJECT]; 654 this._dlSubscriptionPolicyId = {}; 655 this._dlUnsubscriptionPolicyId = {}; 656 for (var i = 0; i < this._subsPolicyOpts.length; i++) { 657 var opt = this._subsPolicyOpts[i]; 658 this._dlSubscriptionPolicyId[opt] = this._htmlElId + "_dlSubscriptionPolicy" + opt; //_dlSubscriptionPolicyACCEPT / APPROVAL / REJECT 659 this._dlUnsubscriptionPolicyId[opt] = this._htmlElId + "_dlUnsubscriptionPolicy" + opt; //_dlUnsubscriptionPolicyACCEPT / APPROVAL / REJECT 660 } 661 this._mailPolicyOpts = [ZmGroupView.MAIL_POLICY_ANYONE, 662 ZmGroupView.MAIL_POLICY_MEMBERS, 663 ZmGroupView.MAIL_POLICY_INTERNAL, 664 ZmGroupView.MAIL_POLICY_SPECIFIC]; 665 this._dlMailPolicyId = {}; 666 for (i = 0; i < this._mailPolicyOpts.length; i++) { 667 opt = this._mailPolicyOpts[i]; 668 this._dlMailPolicyId[opt] = this._htmlElId + "_dlMailPolicy" + opt; //_dlMailPolicyANYONE / etc 669 } 670 this._dlListSpecificMailersId = this._htmlElId + "_dlListSpecificMailers"; 671 672 this._dlListOwnersId = this._htmlElId + "_dlListOwners"; 673 674 // create auto-completer 675 var params = { 676 dataClass: appCtxt.getAutocompleter(), 677 matchValue: ZmAutocomplete.AC_VALUE_EMAIL, 678 keyUpCallback: ZmGroupView._onKeyUp, 679 contextId: this.toString() 680 }; 681 this._acAddrSelectList = new ZmAutocompleteListView(params); 682 if (appCtxt.multiAccounts) { 683 var acct = object.account || appCtxt.accountList.mainAccount; 684 this._acAddrSelectList.setActiveAccount(acct); 685 } 686 } 687 this._searchFieldId = this._htmlElId + "_searchField"; 688 689 var showSearchIn = false; 690 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) { 691 if (appCtxt.get(ZmSetting.GAL_ENABLED) || appCtxt.get(ZmSetting.SHARING_ENABLED)) 692 showSearchIn = true; 693 } 694 var params = this._templateParams = { 695 id: this._htmlElId, 696 showSearchIn: showSearchIn, 697 detailed: this._detailedSearch, 698 contact: this._contact, 699 isEdit: true, 700 usernameEditable: this._usernameEditable, 701 domainEditable: this._domainEditable, 702 username: this._emailUsername, 703 domain: this._emailDomain, 704 addrbook: this._contact.getAddressBook() 705 }; 706 707 if (this.isDistributionList()) { 708 this.getHtmlElement().innerHTML = AjxTemplate.expand("abook.Contacts#DlView", params); 709 this._tabViewContainerId = this._htmlElId + "_tabViewContainer"; 710 var tabViewContainer = document.getElementById(this._tabViewContainerId); 711 this._tabView = new DwtTabView({parent: this, posStyle: Dwt.STATIC_STYLE, id: this._htmlElId + "_tabView"}); 712 this._tabView.reparentHtmlElement(tabViewContainer); 713 this._dlMembersTabView = new ZmDlMembersTabView(this); 714 this._tabView.addTab(ZmMsg.dlMembers, this._dlMembersTabView); 715 this._dlPropertiesTabView = new ZmDlPropertiesTabView(this); 716 this._tabView.addTab(ZmMsg.dlProperties, this._dlPropertiesTabView); 717 } 718 else { 719 this.getHtmlElement().innerHTML = AjxTemplate.expand("abook.Contacts#GroupView", params); 720 } 721 722 this._headerRow = document.getElementById(this._headerRowId); 723 this._tabBar = document.getElementById(this._htmlElId + "_tabView_tabbar"); //only for DLs 724 this._searchFieldsRow = document.getElementById(this._htmlElId + "_searchFieldsRow"); 725 this._manualAddRow = document.getElementById(this._htmlElId + "_manualAddRow"); 726 this._navButtonsRow = document.getElementById(this._htmlElId + "_navButtonsRow"); 727 728 this._htmlInitialized = true; 729 }; 730 731 ZmGroupView.prototype._addWidgets = 732 function() { 733 if (!this.isDistributionList() || this._usernameEditable) { 734 this._groupNameInput = new DwtInputField({parent:this, size: this.isDistributionList() ? 20: 40, inputId: this._htmlElId + "_groupName"}); 735 this._groupNameInput.setHint(this.isDistributionList() ? ZmMsg.distributionList : ZmMsg.groupNameLabel); 736 this._groupNameInput.reparentHtmlElement(this._htmlElId + "_groupNameParent"); 737 } 738 739 this._groupMembers = document.getElementById(this._htmlElId + "_groupMembers"); 740 this._noManualEntry = this._groupMembers.disabled; // see bug 23858 741 742 // add select menu 743 var selectId = this._htmlElId + "_listSelect"; 744 var selectCell = document.getElementById(selectId); 745 if (selectCell) { 746 this._searchInSelect = new DwtSelect({parent:this}); 747 this._resetSearchInSelect(); 748 this._searchInSelect.reparentHtmlElement(selectId); 749 this._searchInSelect.addChangeListener(new AjxListener(this, this._searchTypeListener)); 750 } 751 752 // add "Search" button 753 this._searchButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_searchButton")}); 754 this._searchButton.setText(ZmMsg.search); 755 this._searchButton.addSelectionListener(new AjxListener(this, this._searchButtonListener)); 756 757 // add list view for search results 758 this._listview = new ZmGroupListView(this); 759 this._listview.reparentHtmlElement(this._htmlElId + "_listView"); 760 this._listview.addSelectionListener(new AjxListener(this, this._selectionListener)); 761 this._listview.setUI(null, true); // renders headers and empty list 762 this._listview._initialized = true; 763 764 // add list view for group memebers 765 this._groupMembersListView = new ZmGroupMembersListView(this); 766 this._groupMembersListView.reparentHtmlElement(this._htmlElId + "_groupMembers"); 767 this._groupMembersListView.addSelectionListener(new AjxListener(this, this._groupMembersSelectionListener)); 768 this._groupMembersListView.setUI(null, true); 769 this._groupMembersListView._initialized = true; 770 771 var addListener = new AjxListener(this, this._addListener); 772 // add "Add" button 773 this._addButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_addButton")}); 774 this._addButton.setText(ZmMsg.add); 775 this._addButton.addSelectionListener(addListener); 776 this._addButton.setEnabled(false); 777 this._addButton.setImage("LeftArrow"); 778 779 // add "Add All" button 780 this._addAllButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_addAllButton")}); 781 this._addAllButton.setText(ZmMsg.addAll); 782 this._addAllButton.addSelectionListener(addListener); 783 this._addAllButton.setEnabled(false); 784 this._addAllButton.setImage("LeftArrow"); 785 786 var pageListener = new AjxListener(this, this._pageListener); 787 // add paging buttons 788 this._prevButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_prevButton")}); 789 this._prevButton.setImage("LeftArrow"); 790 this._prevButton.addSelectionListener(pageListener); 791 this._prevButton.setEnabled(false); 792 793 this._nextButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_nextButton")}); 794 this._nextButton.setImage("RightArrow"); 795 this._nextButton.addSelectionListener(pageListener); 796 this._nextButton.setEnabled(false); 797 798 this._locationButton = new DwtButton({parent:this, parentElement: (this._htmlElId + "_LOCATION_FOLDER")}); 799 this._locationButton.setImage("ContactsFolder"); 800 this._locationButton.setEnabled(this._contact && !this._contact.isShared() && !this._contact.isDistributionList()); 801 this._locationButton.addSelectionListener(new AjxListener(this, this._handleFolderButton)); 802 var folderOrId = this._contact && this._contact.getAddressBook(); 803 if (!folderOrId) { 804 var overview = appCtxt.getApp(ZmApp.CONTACTS).getOverview(); 805 folderOrId = overview && overview.getSelected(); 806 if (folderOrId && folderOrId.type != ZmOrganizer.ADDRBOOK) { 807 folderOrId = null; 808 } 809 if (!this.isDistributionList() && folderOrId && folderOrId.id && folderOrId.id == ZmFolder.ID_DLS) { //can't create under Distribution Lists virtual folder 810 folderOrId = null; 811 } 812 } 813 814 this._setLocationFolder(folderOrId); 815 816 817 // add New Button 818 this._addNewField = document.getElementById(this._htmlElId + "_addNewField"); 819 if (this._addNewField) { 820 this._addNewButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_addNewButton")}); 821 this._addNewButton.setText(ZmMsg.add); 822 this._addNewButton.addSelectionListener(new AjxListener(this, this._addNewListener)); 823 this._addNewButton.setImage("LeftArrow"); 824 } 825 826 var fieldMap = {}; 827 var rowMap = {}; 828 ZmContactPicker.prototype.mapFields.call(this, fieldMap, rowMap); 829 830 this._searchField = {}; 831 for (var fieldId in fieldMap) { 832 var field = Dwt.byId(fieldMap[fieldId]); 833 if (field) this._searchField[fieldId] = field; 834 } 835 836 this._searchRow = {}; 837 for (var rowId in rowMap) { 838 row = Dwt.byId(rowMap[rowId]); 839 if (row) this._searchRow[rowId] = row; 840 } 841 this._updateSearchRows(this._searchInSelect && this._searchInSelect.getValue() || ZmContactsApp.SEARCHFOR_CONTACTS); 842 }; 843 844 ZmGroupView.prototype._installKeyHandlers = 845 function() { 846 847 if (this.isDistributionList()) { 848 if (this._usernameEditable) { 849 var groupName = document.getElementById(this._groupNameId); 850 Dwt.setHandler(groupName, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp); 851 Dwt.associateElementWithObject(groupName, this); 852 } 853 if (this._domainEditable) { 854 var groupNameDomain = document.getElementById(this._groupNameDomainId); 855 Dwt.setHandler(groupNameDomain, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp); 856 Dwt.associateElementWithObject(groupNameDomain, this); 857 } 858 859 var dlDisplayName = document.getElementById(this._dlDisplayNameId); 860 Dwt.setHandler(dlDisplayName, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp); 861 Dwt.associateElementWithObject(dlDisplayName, this); 862 863 var dlDesc = document.getElementById(this._dlDescId); 864 Dwt.setHandler(dlDesc, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp); 865 Dwt.associateElementWithObject(dlDesc, this); 866 867 var dlHideInGal = document.getElementById(this._dlHideInGalId); 868 Dwt.setHandler(dlHideInGal, DwtEvent.ONCHANGE, ZmGroupView._onChange); 869 Dwt.associateElementWithObject(dlHideInGal, this); 870 871 var dlNotes = document.getElementById(this._dlNotesId); 872 Dwt.setHandler(dlNotes, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp); 873 Dwt.associateElementWithObject(dlNotes, this); 874 875 for (var i = 0; i < this._subsPolicyOpts.length; i++) { 876 var opt = this._subsPolicyOpts[i]; 877 var policy = document.getElementById(this._dlSubscriptionPolicyId[opt]); 878 Dwt.setHandler(policy, DwtEvent.ONCHANGE, ZmGroupView._onChange); 879 Dwt.associateElementWithObject(policy, this); 880 881 policy = document.getElementById(this._dlUnsubscriptionPolicyId[opt]); 882 Dwt.setHandler(policy, DwtEvent.ONCHANGE, ZmGroupView._onChange); 883 Dwt.associateElementWithObject(policy, this); 884 } 885 886 var dlListOwners = document.getElementById(this._dlListOwnersId); 887 Dwt.associateElementWithObject(dlListOwners, this); 888 if (this._acAddrSelectList) { 889 this._acAddrSelectList.handle(dlListOwners); 890 } 891 else { 892 Dwt.setHandler(dlListOwners, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp); 893 } 894 895 for (i = 0; i < this._mailPolicyOpts.length; i++) { 896 opt = this._mailPolicyOpts[i]; 897 policy = document.getElementById(this._dlMailPolicyId[opt]); 898 Dwt.setHandler(policy, DwtEvent.ONCHANGE, ZmGroupView._onChange); 899 Dwt.associateElementWithObject(policy, this); 900 } 901 var dlListSpecificMailers = document.getElementById(this._dlListSpecificMailersId); 902 Dwt.associateElementWithObject(dlListSpecificMailers, this); 903 if (this._acAddrSelectList) { 904 this._acAddrSelectList.handle(dlListSpecificMailers); 905 } 906 else { 907 Dwt.setHandler(dlListSpecificMailers, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp); 908 } 909 910 } 911 else { 912 var groupName = document.getElementById(this._groupNameId); 913 Dwt.setHandler(groupName, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp); 914 Dwt.associateElementWithObject(groupName, this); 915 } 916 917 if (!this._noManualEntry) { 918 Dwt.setHandler(this._groupMembers, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp); 919 Dwt.associateElementWithObject(this._groupMembers, this); 920 } 921 922 for (var fieldId in this._searchField) { 923 var searchField = this._searchField[fieldId]; 924 Dwt.setHandler(searchField, DwtEvent.ONKEYPRESS, ZmGroupView._keyPressHdlr); 925 Dwt.associateElementWithObject(searchField, this); 926 } 927 }; 928 929 /** 930 * very important method to have in order for the tab group (and tabbing) to be set up correctly (called from ZmBaseController.prototype._initializeTabGroup) 931 */ 932 ZmGroupView.prototype.getTabGroupMember = function() { 933 return this._tabGroup; 934 }; 935 936 ZmGroupView.prototype._getTabGroupMembers = 937 function() { 938 var fields = []; 939 if (this.isDistributionList()) { 940 if (this._usernameEditable) { 941 fields.push(document.getElementById(this._groupNameId)); 942 } 943 if (this._domainEditable) { 944 fields.push(document.getElementById(this._groupNameDomainId)); 945 } 946 fields.push(document.getElementById(this._dlDisplayNameId)); 947 fields.push(document.getElementById(this._dlDescId)); 948 fields.push(document.getElementById(this._dlHideInGalId)); 949 for (var i = 0; i < this._mailPolicyOpts.length; i++) { 950 var opt = this._mailPolicyOpts[i]; 951 fields.push(document.getElementById(this._dlMailPolicyId[opt])); 952 } 953 954 for (var i = 0; i < this._subsPolicyOpts.length; i++) { 955 var opt = this._subsPolicyOpts[i]; 956 fields.push(document.getElementById(this._dlSubscriptionPolicyId[opt])); 957 } 958 for (i = 0; i < this._subsPolicyOpts.length; i++) { 959 opt = this._subsPolicyOpts[i]; 960 fields.push(document.getElementById(this._dlUnsubscriptionPolicyId[opt])); 961 } 962 fields.push(document.getElementById(this._dlNotesId)); 963 } 964 else { 965 fields.push(document.getElementById(this._groupNameId)); 966 } 967 if (!this._noManualEntry) { 968 fields.push(this._groupMembers); 969 } 970 for (var fieldId in this._searchField) { 971 fields.push(this._searchField[fieldId]); 972 } 973 fields.push(this._searchButton); 974 fields.push(this._searchInSelect); 975 976 return fields; 977 }; 978 979 ZmGroupView.prototype._getDefaultFocusItem = 980 function() { 981 if (this.isDistributionList()) { 982 if (this._usernameEditable) { 983 return document.getElementById(this._groupNameId); 984 } 985 if (this._domainEditable) { 986 return document.getElementById(this._groupNameDomainId); 987 } 988 return document.getElementById(this._dlDisplayNameId); 989 } 990 return document.getElementById(this._groupNameId); 991 }; 992 993 ZmGroupView.prototype._getGroupMembers = 994 function() { 995 return this._groupMembersListView.getList().getArray(); 996 }; 997 998 ZmGroupView.prototype._getFolderId = 999 function() { 1000 return this._folderId || ZmFolder.ID_CONTACTS; 1001 }; 1002 1003 ZmGroupView.prototype._setGroupMembers = 1004 function() { 1005 var members = this._contact.getAllGroupMembers(); 1006 if (!members) { 1007 return; 1008 } 1009 this._groupMembersListView.set(AjxVector.fromArray(members)); //todo? 1010 }; 1011 1012 ZmGroupView.prototype._setGroupName = 1013 function() { 1014 if (this.isDistributionList()) { 1015 if (this._domainEditable) { 1016 var groupNameDomain = document.getElementById(this._groupNameDomainId); 1017 groupNameDomain.value = this._emailDomain; 1018 } 1019 if (this._usernameEditable) { 1020 this._groupNameInput.setValue(this._emailUsername); 1021 } 1022 return; 1023 } 1024 var groupName = document.getElementById(this._groupNameId); 1025 if (!groupName) { 1026 return; 1027 } 1028 1029 this._groupNameInput.setValue(this._contact.getFileAs()); 1030 }; 1031 1032 ZmGroupView.prototype._setDlFields = 1033 function() { 1034 var displayName = document.getElementById(this._dlDisplayNameId); 1035 var dlInfo = this._contact.dlInfo; 1036 displayName.value = dlInfo.displayName || ""; 1037 1038 var desc = document.getElementById(this._dlDescId); 1039 desc.value = dlInfo.description || ""; 1040 1041 var hideInGal = document.getElementById(this._dlHideInGalId); 1042 hideInGal.checked = dlInfo.hideInGal; 1043 1044 //set the default only in temporary var so it will be saved later as modification, even if user doesn't change. 1045 //this is for the new DL case 1046 var subsPolicy = dlInfo.subscriptionPolicy || ZmContactSplitView.SUBSCRIPTION_POLICY_ACCEPT; 1047 var unsubsPolicy = dlInfo.unsubscriptionPolicy || ZmContactSplitView.SUBSCRIPTION_POLICY_ACCEPT; 1048 for (var i = 0; i < this._subsPolicyOpts.length; i++) { 1049 var opt = this._subsPolicyOpts[i]; 1050 var subsPolicyOpt = document.getElementById(this._dlSubscriptionPolicyId[opt]); 1051 subsPolicyOpt.checked = subsPolicy == opt; 1052 1053 var unsubsPolicyOpt = document.getElementById(this._dlUnsubscriptionPolicyId[opt]); 1054 unsubsPolicyOpt.checked = unsubsPolicy == opt; 1055 } 1056 var mailPolicy = dlInfo.mailPolicy || ZmGroupView.MAIL_POLICY_ANYONE; 1057 for (i = 0; i < this._mailPolicyOpts.length; i++) { 1058 opt = this._mailPolicyOpts[i]; 1059 var mailPolicyOpt = document.getElementById(this._dlMailPolicyId[opt]); 1060 mailPolicyOpt.checked = mailPolicy == opt; 1061 } 1062 if (dlInfo.mailPolicy == ZmGroupView.MAIL_POLICY_SPECIFIC) { 1063 var listSpecificMailers = document.getElementById(this._dlListSpecificMailersId); 1064 listSpecificMailers.value = dlInfo.mailPolicySpecificMailers.join("; "); 1065 if (listSpecificMailers.value.length > 0) { 1066 listSpecificMailers.value += ";"; //so it's ready to add more by user. 1067 } 1068 } 1069 1070 var listOwners = document.getElementById(this._dlListOwnersId); 1071 listOwners.value = dlInfo.owners.join("; "); 1072 if (listOwners.value.length > 0) { 1073 listOwners.value += ";"; //so it's ready to add more by user. 1074 } 1075 1076 var notes = document.getElementById(this._dlNotesId); 1077 notes.value = dlInfo.notes || ""; 1078 1079 }; 1080 1081 1082 ZmGroupView.prototype._resetSearchInSelect = 1083 function() { 1084 this._searchInSelect.addOption(ZmMsg.contacts, true, ZmContactsApp.SEARCHFOR_CONTACTS); 1085 if (appCtxt.get(ZmSetting.SHARING_ENABLED)) { 1086 this._searchInSelect.addOption(ZmMsg.searchPersonalSharedContacts, false, ZmContactsApp.SEARCHFOR_PAS); 1087 } 1088 if (appCtxt.get(ZmSetting.GAL_ENABLED) && appCtxt.getActiveAccount().isZimbraAccount) { 1089 this._searchInSelect.addOption(ZmMsg.GAL, true, ZmContactsApp.SEARCHFOR_GAL); 1090 } 1091 if (!appCtxt.get(ZmSetting.INITIALLY_SEARCH_GAL) || !appCtxt.get(ZmSetting.GAL_ENABLED)) { 1092 this._searchInSelect.setSelectedValue(ZmContactsApp.SEARCHFOR_CONTACTS); 1093 } 1094 }; 1095 1096 ZmGroupView.prototype._setLocationFolder = function(organizerOrId) { 1097 if (organizerOrId) { 1098 var organizer = organizerOrId instanceof ZmOrganizer ? organizerOrId : appCtxt.getById(organizerOrId); 1099 } 1100 if (!organizer || organizer.isReadOnly()) { 1101 //default to the main contacts folder 1102 organizer = appCtxt.getById(ZmOrganizer.ID_ADDRBOOK); 1103 } 1104 1105 this._locationButton.setText(organizer.getName()); 1106 this._folderId = organizer.id; 1107 }; 1108 1109 ZmGroupView.prototype._handleFolderButton = function(ev) { 1110 var dialog = appCtxt.getChooseFolderDialog(); 1111 dialog.registerCallback(DwtDialog.OK_BUTTON, new AjxCallback(this, this._handleChooseFolder)); 1112 var params = { 1113 overviewId: dialog.getOverviewId(ZmApp.CONTACTS), 1114 title: ZmMsg.chooseAddrBook, 1115 treeIds: [ZmOrganizer.ADDRBOOK], 1116 skipReadOnly: true, 1117 skipRemote: false, 1118 noRootSelect: true, 1119 appName: ZmApp.CONTACTS 1120 }; 1121 params.omit = {}; 1122 params.omit[ZmFolder.ID_TRASH] = true; 1123 dialog.popup(params); 1124 }; 1125 1126 /** 1127 * @private 1128 */ 1129 ZmGroupView.prototype._handleChooseFolder = function(organizer) { 1130 var dialog = appCtxt.getChooseFolderDialog(); 1131 dialog.popdown(); 1132 this._isDirty = true; 1133 this._setLocationFolder(organizer); 1134 }; 1135 1136 ZmGroupView.prototype._setTags = 1137 function() { 1138 var tagCell = this._getTagCell(); 1139 if (!tagCell) { return; } 1140 1141 tagCell.innerHTML = ZmTagsHelper.getTagsHtml(this._contact, this); 1142 }; 1143 1144 // Consistent spot to locate various dialogs 1145 ZmGroupView.prototype._getDialogXY = 1146 function() { 1147 if (this.isDistributionList()) { 1148 // the scrolling messes up the calculation of Dwt.toWindow. This however seems to work fine, the dialog is just a little higher than other cases 1149 return new DwtPoint(ZmGroupView.DIALOG_X, ZmGroupView.DIALOG_Y); 1150 } 1151 var loc = Dwt.toWindow(this.getHtmlElement(), 0, 0); 1152 return new DwtPoint(loc.x + ZmGroupView.DIALOG_X, loc.y + ZmGroupView.DIALOG_Y); 1153 }; 1154 1155 // Listeners 1156 1157 ZmGroupView.prototype._groupMembersSelectionListener = 1158 function(ev){ 1159 var selection = this._groupMembersListView.getSelection(); 1160 if (ev && ev.target && this._groupMembersListView.delButtons[ev.target.id]) { 1161 this._delListener(ev); 1162 } 1163 else if (ev && ev.target && this._groupMembersListView.quickAddButtons[ev.target.id]) { 1164 if (AjxUtil.isArray(selection)) { 1165 var address = selection[0].address || selection[0]; 1166 this.quickAddContact(address); 1167 } 1168 } 1169 1170 }; 1171 1172 ZmGroupView.prototype._selectionListener = 1173 function(ev) { 1174 var selection = this._listview.getSelection(); 1175 1176 if (ev.detail == DwtListView.ITEM_DBL_CLICKED) { 1177 this._addItems(selection); 1178 } else { 1179 this._addButton.setEnabled(selection.length > 0); 1180 } 1181 }; 1182 1183 ZmGroupView.prototype._selectChangeListener = 1184 function(ev) { 1185 this._attr[ZmContact.F_folderId] = ev._args.newValue; 1186 this._isDirty = true; 1187 }; 1188 1189 ZmGroupView.prototype._searchTypeListener = 1190 function(ev) { 1191 var oldValue = ev._args.oldValue; 1192 var newValue = ev._args.newValue; 1193 1194 if (oldValue != newValue) { 1195 this._updateSearchRows(newValue); 1196 this._searchButtonListener(); 1197 } 1198 }; 1199 1200 ZmGroupView.prototype._delListener = 1201 function(ev){ 1202 1203 var items = this._groupMembersListView.getSelection(); 1204 var selectedDomItems = this._groupMembersListView.getSelectedItems(); 1205 1206 while (selectedDomItems.get(0)) { 1207 this._groupMembersListView.removeItem(selectedDomItems.get(0)); 1208 } 1209 1210 for (var i = 0; i < items.length; i++) { 1211 var item = items[i]; 1212 this._groupMembersListView.getList().remove(item); 1213 var contact = item.__contact; 1214 var type = item.type; 1215 var value = item.groupRefValue || item.value; 1216 1217 //var value = item.value || (contact ? contact.getId(!contact.isGal) : item); 1218 1219 if (!this._groupMemberMods[value]) { 1220 this._groupMemberMods[value] = {op : "-", value : value, email: item.address, type : type}; 1221 } 1222 else { 1223 this._groupMemberMods[value] = {}; 1224 } 1225 } 1226 1227 this._groupMembersSelectionListener(); 1228 this._isDirty = true; 1229 }; 1230 1231 ZmGroupView.prototype._addNewListener = 1232 function(ev){ 1233 var emailStr = this._addNewField.value; 1234 if (!emailStr || emailStr == '') { return; } 1235 1236 var allArray = AjxEmailAddress.parseEmailString(emailStr).all.getArray(); //in bug 38907 it was changed to "all" instead of "good". No idea why. So we can now add bad email addresses. Is that on purpose? 1237 var addrs = []; 1238 for (var i = 0; i < allArray.length; i++) { 1239 addrs.push(ZmContactsHelper._wrapInlineContact(allArray[i].address)); //might be better way to do this, we recreate the AjxEmailAddress just to add the "value" and "type" and maybe "id" attributes. 1240 } 1241 1242 addrs = ZmGroupView._dedupe(addrs, this._groupMembersListView.getList().getArray()); 1243 this._addToMembers(addrs); 1244 1245 this._addNewField.value = ''; 1246 }; 1247 1248 ZmGroupView.prototype._addListener = 1249 function(ev) { 1250 var list = (ev.dwtObj == this._addButton) 1251 ? this._listview.getSelection() 1252 : this._listview.getList().getArray(); 1253 1254 this._addItems(list); 1255 }; 1256 1257 ZmGroupView.prototype._addItems = 1258 function(list) { 1259 if (list.length == 0) { return; } 1260 1261 // we have to walk the results in case we hit a group which needs to be split 1262 var items = []; 1263 for (var i = 0; i < list.length; i++) { 1264 var item = list[i]; 1265 var contact = item.__contact; 1266 if (item.isGroup && !contact.isDistributionList()) { 1267 var groupMembers = contact.attr[ZmContact.F_groups]; 1268 for (var j = 0; j < groupMembers.length; j++) { 1269 var value = groupMembers[j].value; 1270 var memberContact = ZmContact.getContactFromCache(value); 1271 var obj; 1272 if (memberContact) { 1273 obj = ZmContactsHelper._wrapContact(memberContact); 1274 } 1275 else { 1276 obj = ZmContactsHelper._wrapInlineContact(value); 1277 } 1278 if (obj) { 1279 items.push(obj); 1280 } 1281 } 1282 } 1283 else { 1284 items.push(list[i]); 1285 if (contact.isGal) { 1286 appCtxt.cacheSet(contact.ref, contact); //not sure why we do this. just seems like maybe we should do this elsewhere in more consistent way. 1287 } 1288 } 1289 } 1290 1291 items = ZmGroupView._dedupe(items, this._groupMembersListView.getList().getArray()); 1292 if (items.length > 0) { 1293 this._addToMembers(items); 1294 } 1295 }; 1296 1297 ZmGroupView.prototype._addToMembers = 1298 function(items){ 1299 var userZid = appCtxt.accountList.mainAccount.id; 1300 for (var i = 0; i < items.length; i++) { 1301 var item = items[i]; 1302 var type = item.type; 1303 var value = item.value; 1304 var email = item.address; 1305 var obj = this._groupMemberMods[value]; 1306 if (!obj) { 1307 if (type === ZmContact.GROUP_CONTACT_REF && value && value.indexOf(":") === -1 ) { 1308 value = userZid + ":" + value; 1309 } 1310 this._groupMemberMods[value] = {op : "+", value : value, type : type, email: email}; 1311 } 1312 else if (obj.op == "-") { 1313 //contact is already in the group, clear the value 1314 this._groupMemberMods[value] = {}; 1315 } 1316 } 1317 var membersList = this._groupMembersListView.getList(); 1318 items = items.concat(membersList ? membersList.getArray() : []); 1319 this._isDirty = true; 1320 1321 this._groupMembersListView.set(AjxVector.fromArray(items)); 1322 }; 1323 1324 /** 1325 * Returns the items from newItems that are not in list, and also not duplicates within newItems (i.e. returns one of each) 1326 * 1327 * @param newItems {Array} array of items to be added to the target list 1328 * @param list {Array} the target list as an array of items 1329 * @return {Array} uniqueNewItems the unique new items (items that are not in the list or duplicates in the newItems) 1330 * @private 1331 */ 1332 ZmGroupView._dedupe = 1333 function(newItems, list) { 1334 1335 AjxUtil.dedup(newItems, function(item) { 1336 return item.type + "$" + item.value; 1337 }); 1338 1339 var uniqueNewItems = []; 1340 1341 for (var i = 0; i < newItems.length; i++) { 1342 var newItem = newItems[i]; 1343 var found = false; 1344 for (var j = 0; j < list.length; j++) { 1345 var item = list[j]; 1346 if (newItem.type == item.type && newItem.value == item.value) { 1347 found = true; 1348 break; 1349 } 1350 } 1351 if (!found) { 1352 uniqueNewItems.push(newItem); 1353 } 1354 } 1355 1356 return uniqueNewItems; 1357 }; 1358 1359 ZmGroupView.prototype._setGroupMemberValue = 1360 function(value, append) { 1361 if (this._noManualEntry) { 1362 this._groupMembers.disabled = false; 1363 } 1364 1365 if (append) { 1366 this._groupMembers.value += value; 1367 } else { 1368 this._groupMembers.value = value; 1369 } 1370 1371 if (this._noManualEntry) { 1372 this._groupMembers.disabled = true; 1373 } 1374 }; 1375 1376 1377 /** 1378 * called from ZmContactPicker.prototype._showResults 1379 * @param list 1380 */ 1381 ZmGroupView.prototype._setResultsInView = 1382 function(list) { 1383 var arr = list.getArray(); 1384 this._listview.setItems(arr); 1385 this._addButton.setEnabled(arr.length > 0); 1386 this._addAllButton.setEnabled(arr.length > 0); 1387 }; 1388 1389 /** 1390 * called from ZmContactPicker.prototype._showResults 1391 */ 1392 ZmGroupView.prototype._setNoResultsHtml = 1393 function(list) { 1394 //no need to do anything here. the setItems called from _setResultsInView sets the "no results found" if list is empty. (via call to addItems) 1395 }; 1396 1397 /** 1398 * reuse functions from ZmContactPicker, some called from ZmContactPicker code we re-use here. 1399 */ 1400 ZmGroupView.prototype.search = ZmContactPicker.prototype.search; 1401 ZmGroupView.prototype._handleResponseSearch = ZmContactPicker.prototype._handleResponseSearch; 1402 ZmGroupView.prototype._resetResults = ZmContactPicker.prototype._resetResults; 1403 ZmGroupView.prototype._searchButtonListener = ZmContactPicker.prototype._searchButtonListener; 1404 ZmGroupView.prototype._pageListener = ZmContactPicker.prototype._pageListener; 1405 ZmGroupView.prototype._showResults = ZmContactPicker.prototype._showResults; 1406 ZmGroupView.prototype.getSubList = ZmContactPicker.prototype.getSubList; 1407 1408 1409 ZmGroupView.prototype._tagChangeListener = function(ev) { 1410 if (ev.type != ZmEvent.S_TAG) { return; } 1411 1412 var fields = ev.getDetail("fields"); 1413 var changed = fields && (fields[ZmOrganizer.F_COLOR] || fields[ZmOrganizer.F_NAME]); 1414 if ((ev.event == ZmEvent.E_MODIFY && changed) || ev.event == ZmEvent.E_DELETE || ev.event == ZmEvent.MODIFY) { 1415 this._setTags(); 1416 } 1417 }; 1418 1419 ZmGroupView.prototype._groupChangeListener = function(ev) { 1420 if (ev.type != ZmEvent.S_CONTACT) return; 1421 if (ev.event == ZmEvent.E_TAGS || ev.event == ZmEvent.E_REMOVE_ALL) { 1422 this._setTags(); 1423 } 1424 }; 1425 1426 ZmGroupView.prototype._updateSearchRows = 1427 function(searchFor) { 1428 var fieldIds = (searchFor == ZmContactsApp.SEARCHFOR_GAL) ? ZmContactPicker.SHOW_ON_GAL : ZmContactPicker.SHOW_ON_NONGAL; 1429 for (var fieldId in this._searchRow) { 1430 Dwt.setVisible(this._searchRow[fieldId], AjxUtil.indexOf(fieldIds, fieldId)!=-1); 1431 } 1432 }; 1433 1434 ZmGroupView.prototype._resetSearchColHeaders = 1435 function() { 1436 var lv = this._listview; 1437 lv.headerColCreated = false; 1438 var isGal = this._searchInSelect && (this._searchInSelect.getValue() == ZmContactsApp.SEARCHFOR_GAL); 1439 1440 for (var i = 0; i < lv._headerList.length; i++) { 1441 var field = lv._headerList[i]._field; 1442 if (field == ZmItem.F_DEPARTMENT) { 1443 lv._headerList[i]._visible = isGal && this._detailedSearch; 1444 } 1445 } 1446 1447 var sortable = isGal ? null : ZmItem.F_NAME; 1448 lv.createHeaderHtml(sortable); 1449 }; 1450 1451 ZmGroupView.prototype._checkItemCount = 1452 function() { 1453 this._listview._checkItemCount(); 1454 }; 1455 1456 ZmGroupView.prototype._handleResponseCheckReplenish = 1457 function(skipSelection) { 1458 this._listview._handleResponseCheckReplenish(skipSelection); 1459 }; 1460 1461 // Static methods 1462 1463 ZmGroupView._onKeyUp = 1464 function(ev) { 1465 ev = DwtUiEvent.getEvent(ev); 1466 1467 var key = DwtKeyEvent.getCharCode(ev); 1468 if (DwtKeyMapMgr.hasModifier(ev) || DwtKeyMap.IS_MODIFIER[key] || key === DwtKeyEvent.KEY_TAB) { return; } 1469 1470 var e = DwtUiEvent.getTarget(ev); 1471 var view = e ? Dwt.getObjectFromElement(e) : null; 1472 if (view) { 1473 view._isDirty = true; 1474 } 1475 1476 return true; 1477 }; 1478 1479 ZmGroupView._onChange = 1480 function(ev) { 1481 ev = DwtUiEvent.getEvent(ev); 1482 1483 var e = DwtUiEvent.getTarget(ev); 1484 var view = e ? Dwt.getObjectFromElement(e) : null; 1485 if (view) { 1486 view._isDirty = true; 1487 } 1488 1489 return true; 1490 }; 1491 1492 ZmGroupView._keyPressHdlr = 1493 function(ev) { 1494 ev = DwtUiEvent.getEvent(ev); 1495 if (DwtKeyMapMgr.hasModifier(ev)) { return; } 1496 1497 var e = DwtUiEvent.getTarget(ev); 1498 var view = e ? Dwt.getObjectFromElement(e) : null; 1499 if (view) { 1500 var charCode = DwtKeyEvent.getCharCode(ev); 1501 if (charCode == 13 || charCode == 3) { 1502 view._searchButtonListener(ev); 1503 return false; 1504 } 1505 } 1506 return true; 1507 }; 1508 1509 ZmGroupView.prototype.quickAddContact = 1510 function(email) { 1511 var quickAdd = appCtxt.getContactQuickAddDialog(); 1512 quickAdd.setFields(email); 1513 var saveCallback = new AjxCallback(this, this._handleQuickAddContact); 1514 quickAdd.popup(saveCallback); 1515 }; 1516 1517 ZmGroupView.prototype._handleQuickAddContact = 1518 function(result) { 1519 var resp = AjxUtil.get(result, "_data", "BatchResponse", "CreateContactResponse"); 1520 var contact = resp ? ZmContact.createFromDom(resp[0].cn[0], {}) : null; 1521 if (!contact) { 1522 return; 1523 } 1524 var selection = this._groupMembersListView.getSelection(); 1525 var selectedItem = selection[0]; 1526 var value = selectedItem.value; 1527 if (!this._groupMemberMods[value]) { 1528 this._groupMemberMods[value] = {op : "-", value : value, type : selectedItem.type}; 1529 } 1530 else { 1531 this._groupMemberMods[value] = {}; 1532 } 1533 var domList = this._groupMembersListView.getSelectedItems(); 1534 this._groupMembersListView.removeItem(domList.get(0)); 1535 this._groupMembersListView.getList().remove(selectedItem); 1536 1537 var obj = ZmContactsHelper._wrapContact(contact); 1538 if (obj) { 1539 this._addItems([obj]); 1540 } 1541 }; 1542 1543 /** 1544 * Creates a group list view for search results 1545 * @constructor 1546 * @class 1547 * 1548 * @param {ZmGroupView} parent containing widget 1549 * 1550 * @extends DwtListView 1551 * 1552 * @private 1553 */ 1554 ZmGroupListView = function(parent) { 1555 if (arguments.length == 0) { return; } 1556 DwtListView.call(this, {parent:parent, className:"DwtChooserListView ZmEditGroupContact", 1557 headerList:this._getHeaderList(parent), view:this._view, posStyle: Dwt.RELATIVE_STYLE}); 1558 Dwt.setScrollStyle(this._elRef, Dwt.CLIP); 1559 }; 1560 1561 ZmGroupListView.prototype = new DwtListView; 1562 ZmGroupListView.prototype.constructor = ZmGroupListView; 1563 1564 ZmGroupListView.prototype.setItems = 1565 function(items) { 1566 this._resetList(); 1567 this.addItems(items); 1568 var list = this.getList(); 1569 if (list && list.size() > 0) { 1570 this.setSelection(list.get(0)); 1571 } 1572 }; 1573 1574 ZmGroupListView.prototype._getHeaderList = 1575 function() { 1576 return [ 1577 (new DwtListHeaderItem({field:ZmItem.F_TYPE, icon:"Contact", width:ZmMsg.COLUMN_WIDTH_TYPE_CN})), 1578 (new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg._name, width:ZmMsg.COLUMN_WIDTH_NAME_CN, resizeable: true})), 1579 (new DwtListHeaderItem({field:ZmItem.F_EMAIL, text:ZmMsg.email})) 1580 ]; 1581 }; 1582 1583 ZmGroupListView.prototype._getCellContents = 1584 function(html, idx, item, field, colIdx, params) { 1585 return ZmContactsHelper._getEmailField(html, idx, item, field, colIdx, params); 1586 }; 1587 1588 ZmGroupListView.prototype._itemClicked = 1589 function(clickedEl, ev) { 1590 // Ignore right-clicks, we don't support action menus 1591 if (!ev.shiftKey && !ev.ctrlKey && ev.button == DwtMouseEvent.RIGHT) { return; } 1592 1593 DwtListView.prototype._itemClicked.call(this, clickedEl, ev); 1594 }; 1595 1596 ZmGroupListView.prototype._mouseDownAction = 1597 function(ev, div) { 1598 return !Dwt.ffScrollbarCheck(ev); 1599 }; 1600 1601 ZmGroupListView.prototype._mouseUpAction = 1602 function(ev, div) { 1603 return !Dwt.ffScrollbarCheck(ev); 1604 }; 1605 1606 //stub method 1607 ZmGroupListView.prototype._checkItemCount = 1608 function() { 1609 return true; 1610 }; 1611 1612 //stub method 1613 ZmGroupListView.prototype._handleResponseCheckReplenish = 1614 function() { 1615 return true; 1616 }; 1617 1618 /** 1619 * Creates a group members list view 1620 * @constructor 1621 * @class 1622 * 1623 * @param {ZmGroupView} parent containing widget 1624 * 1625 * @extends ZmGroupListView 1626 * 1627 * 1628 * @private 1629 */ 1630 ZmGroupMembersListView = function (parent) { 1631 if (arguments.length == 0) { return; } 1632 ZmGroupListView.call(this, parent); 1633 this._list = new AjxVector(); 1634 // hash of delete icon IDs 1635 this.delButtons = {}; 1636 this.quickAddButtons = {}; 1637 }; 1638 1639 ZmGroupMembersListView.prototype = new ZmGroupListView; 1640 ZmGroupMembersListView.prototype.constructor = ZmGroupMembersListView; 1641 1642 ZmGroupMembersListView.prototype._getHeaderList = 1643 function() { 1644 return [(new DwtListHeaderItem({field:ZmItem.F_EMAIL, text:ZmMsg.membersLabel, view:this._view}))]; 1645 }; 1646 1647 ZmGroupMembersListView.prototype._getCellContents = 1648 function(html, idx, item, field, colIdx, params) { 1649 if (field == ZmItem.F_EMAIL) { 1650 var data = {}; 1651 data.isEdit = true; 1652 data.delButtonId = Dwt.getNextId("DelContact_"); 1653 this.delButtons[data.delButtonId] = true; 1654 var contact = item.__contact; 1655 var addr = item.address; 1656 1657 if (contact && !this.parent.isDistributionList()) { 1658 data.imageUrl = contact.getImageUrl(); 1659 data.email = AjxStringUtil.htmlEncode(contact.getEmail()); 1660 data.title = AjxStringUtil.htmlEncode(contact.getAttr(ZmContact.F_jobTitle)); 1661 data.phone = AjxStringUtil.htmlEncode(contact.getPhone()); 1662 data.imgClassName = contact.getIconLarge(); 1663 var isPhonetic = appCtxt.get(ZmSetting.PHONETIC_CONTACT_FIELDS); 1664 var fullnameHtml= contact.getFullNameForDisplay(isPhonetic); 1665 if (!isPhonetic) { 1666 fullnameHtml = AjxStringUtil.htmlEncode(fullnameHtml); 1667 } 1668 data.fullName = fullnameHtml; 1669 } 1670 else { 1671 data.imgClassName = "PersonInline_48"; 1672 data.email = AjxStringUtil.htmlEncode(addr); 1673 if (!this.parent.isDistributionList()) { 1674 data.isInline = true; 1675 data.quickAddId = Dwt.getNextId("QuickAdd_"); 1676 this.quickAddButtons[data.quickAddId] = true; 1677 } 1678 } 1679 html[idx++] = AjxTemplate.expand("abook.Contacts#SplitView_group", data); 1680 1681 } 1682 return idx; 1683 }; 1684 1685 1686 // override from base class since it is handled differently 1687 ZmGroupMembersListView.prototype._getItemId = 1688 function(item) { 1689 return (item && item.id) ? item.id : Dwt.getNextId(); 1690 }; 1691 1692 /** 1693 * @class 1694 * 1695 * @param {DwtControl} parent the parent (dialog) 1696 * @param {String} className the class name 1697 * 1698 * @extends DwtTabViewPage 1699 */ 1700 ZmDlPropertiesTabView = function(parent, className) { 1701 if (arguments.length == 0) return; 1702 1703 DwtTabViewPage.call(this, parent, className, Dwt.ABSOLUTE_STYLE); 1704 1705 this.setScrollStyle(Dwt.SCROLL); 1706 1707 var htmlEl = this.getHtmlElement(); 1708 htmlEl.style.top = this.parent._tabView.getY() + this.parent._tabView._tabBar.getH() + "px"; 1709 htmlEl.style.bottom = 0; 1710 1711 }; 1712 1713 ZmDlPropertiesTabView.prototype = new DwtTabViewPage; 1714 1715 ZmDlPropertiesTabView.prototype.toString = function() { 1716 return "ZmDlPropertiesTabView"; 1717 }; 1718 1719 ZmDlPropertiesTabView.prototype._createHtml = 1720 function () { 1721 DwtTabViewPage.prototype._createHtml.call(this); 1722 this.getHtmlElement().innerHTML = AjxTemplate.expand("abook.Contacts#DlPropertiesView", this.parent._templateParams); 1723 }; 1724 1725 /** 1726 * @class 1727 * 1728 * @param {DwtControl} parent the parent (dialog) 1729 * @param {String} className the class name 1730 * 1731 * @extends DwtTabViewPage 1732 */ 1733 ZmDlMembersTabView = function(parent, className) { 1734 if (arguments.length == 0) return; 1735 1736 DwtTabViewPage.call(this, parent, className, Dwt.RELATIVE_STYLE); 1737 1738 }; 1739 1740 ZmDlMembersTabView.prototype = new DwtTabViewPage; 1741 1742 ZmDlMembersTabView.prototype.toString = function() { 1743 return "ZmDlMembersTabView"; 1744 }; 1745 1746 ZmDlMembersTabView.prototype._createHtml = 1747 function () { 1748 DwtTabViewPage.prototype._createHtml.call(this); 1749 this.getHtmlElement().innerHTML = AjxTemplate.expand("abook.Contacts#GroupViewMembers", this.parent._templateParams); 1750 }; 1751 1752