1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 * This file contains the contact picker classes. 27 * 28 */ 29 30 /** 31 * Creates a dialog that lets the user select addresses from a contact list. 32 * @constructor 33 * @class 34 * This class creates and manages a dialog that lets the user select addresses 35 * from a contact list. Two lists are maintained, one with contacts to select 36 * from, and one that contains the selected addresses. Between them are buttons 37 * to shuffle addresses back and forth between the two lists. 38 * 39 * @author Conrad Damon 40 * 41 * @param {Array} buttonInfo the transfer button IDs and labels 42 * 43 * @extends DwtDialog 44 */ 45 ZmContactPicker = function(buttonInfo) { 46 47 DwtDialog.call(this, {parent:appCtxt.getShell(), title:ZmMsg.selectAddresses, id: "ZmContactPicker"}); 48 49 this._buttonInfo = buttonInfo; 50 this._initialized = false; 51 this._emailListOffset = 0; //client side paginating over email list. Offset of current page of email addresses. Quite different than _lastServerOffset if contacts have 0 or more than 1 email addresses. 52 this._serverContactOffset = 0; //server side paginating over contact list. Offset of last contact block we got from the server (each contact could have 0, 1, or more emails so we have to keep track of this separate from the view offset. 53 this._ascending = true; //asending or descending search. Keep it stored for pagination to do the right sort. 54 this._emailList = new AjxVector(); 55 this._detailedSearch = appCtxt.get(ZmSetting.DETAILED_CONTACT_SEARCH_ENABLED); 56 this._ignoreSetDragBoundries = true; 57 58 this.setSize(Dwt.DEFAULT, this._getDialogHeight()); 59 60 this._searchErrorCallback = new AjxCallback(this, this._handleErrorSearch); 61 }; 62 63 ZmContactPicker.prototype = new DwtDialog; 64 ZmContactPicker.prototype.constructor = ZmContactPicker; 65 66 ZmContactPicker.prototype.isZmContactPicker = true; 67 ZmContactPicker.prototype.toString = function() { return "ZmContactPicker"; }; 68 69 // Consts 70 71 ZmContactPicker.DIALOG_HEIGHT = 460; 72 73 ZmContactPicker.SEARCH_BASIC = "search"; 74 ZmContactPicker.SEARCH_NAME = "name"; 75 ZmContactPicker.SEARCH_EMAIL = "email"; 76 ZmContactPicker.SEARCH_DEPT = "dept"; 77 ZmContactPicker.SEARCH_PHONETIC = "phonetic"; 78 79 ZmContactPicker.SHOW_ON_GAL = [ZmContactPicker.SEARCH_BASIC, ZmContactPicker.SEARCH_NAME, ZmContactPicker.SEARCH_EMAIL, ZmContactPicker.SEARCH_DEPT]; 80 ZmContactPicker.SHOW_ON_NONGAL = [ZmContactPicker.SEARCH_BASIC, ZmContactPicker.SEARCH_NAME, ZmContactPicker.SEARCH_PHONETIC, ZmContactPicker.SEARCH_EMAIL]; 81 ZmContactPicker.ALL = [ ZmContactPicker.SEARCH_BASIC, ZmContactPicker.SEARCH_NAME, ZmContactPicker.SEARCH_PHONETIC, ZmContactPicker.SEARCH_EMAIL, ZmContactPicker.SEARCH_DEPT ]; 82 83 // Public methods 84 85 86 /** 87 * Displays the contact picker dialog. The source list is populated with 88 * contacts, and the target list is populated with any addresses that are 89 * passed in. The address button that was used to popup the dialog is set 90 * as the active button. 91 * 92 * @param {String} buttonId the button ID of the button that called us 93 * @param {Hash} addrs a hash of 3 vectors (one for each type of address) 94 * @param {String} str initial search string 95 */ 96 ZmContactPicker.prototype.popup = 97 function(buttonId, addrs, str, account) { 98 99 if (!this._initialized) { 100 this._initialize(account); 101 this._initialized = true; 102 } 103 else if (appCtxt.multiAccounts && this._account != account) { 104 this._account = account; 105 this._resetSelectDiv(); 106 } 107 this._emailListOffset = 0; 108 109 var searchFor = this._searchInSelect ? this._searchInSelect.getValue() : ZmContactsApp.SEARCHFOR_CONTACTS; 110 111 // reset column sorting preference 112 this._chooser.sourceListView.setSortByAsc(ZmItem.F_NAME, true); 113 114 // reset button states 115 this._chooser.reset(); 116 if (buttonId) { 117 this._chooser._setActiveButton(buttonId); 118 } 119 120 // populate target list if addrs were passed in 121 if (addrs) { 122 for (var id in addrs) { 123 this._chooser.addItems(addrs[id], DwtChooserListView.TARGET, true, id); 124 } 125 } 126 127 for (var fieldId in this._searchField) { 128 var field = this._searchField[fieldId]; 129 field.disabled = false; 130 field.value = (AjxUtil.isObject(str) ? str[fieldId] : str) || ""; 131 } 132 133 // reset paging buttons 134 this._prevButton.setEnabled(false); 135 this._nextButton.setEnabled(false); 136 137 this.search(null, true, true); 138 139 DwtDialog.prototype.popup.call(this); 140 141 this._resizeChooser(); 142 143 if ((this.getLocation().x < 0 || this.getLocation().y < 0) ){ 144 // parent window size is smaller than Dialog size 145 this.setLocation(0,30); 146 var size = Dwt.getWindowSize(); 147 var currentSize = this.getSize(); 148 var dragElement = document.getElementById(this._dragHandleId); 149 DwtDraggable.setDragBoundaries(dragElement, 100 - currentSize.x, size.x - 100, 0, size.y - 100); 150 } 151 152 var focusField = this._searchField[ZmContactPicker.SEARCH_BASIC] || this._searchField[ZmContactPicker.SEARCH_NAME]; 153 appCtxt.getKeyboardMgr().grabFocus(focusField); 154 155 }; 156 157 158 ZmContactPicker.prototype._resetResults = 159 function() { 160 this._emailList.removeAll(); 161 this._serverContactOffset = 0; 162 this._emailListOffset = 0; 163 }; 164 165 /** 166 * Closes the dialog. 167 * 168 */ 169 ZmContactPicker.prototype.popdown = 170 function() { 171 // disable search field (hack to fix bleeding cursor) 172 173 for (var fieldId in this._searchField) { 174 this._searchField[fieldId].disabled = true; 175 } 176 177 this._contactSource = null; 178 this._resetResults(); 179 180 DwtDialog.prototype.popdown.call(this); 181 }; 182 183 /** 184 * Performs a search. 185 * 186 * @private 187 */ 188 ZmContactPicker.prototype.search = 189 function(colItem, ascending, firstTime, lastId, lastSortVal, offset) { 190 if (offset == undefined) { 191 //this could be a call from DwtChooserListView.prototype._sortColumn, which means we have to reset the result and both server and client pagination. 192 //In any case the results should be reset or are already reset so doesn't hurt to reset. 193 this._resetResults(); 194 } 195 196 if (ascending === null || ascending === undefined) { 197 ascending = this._ascending; 198 } 199 else { 200 this._ascending = ascending; 201 } 202 203 var query; 204 var queryHint = []; 205 var emailQueryTerm = ""; 206 var phoneticQueryTerms = []; 207 var nameQueryTerms = []; 208 var conds = []; 209 if (this._detailedSearch) { 210 var nameQuery = this.getSearchFieldValue(ZmContactPicker.SEARCH_NAME); 211 var emailQuery = this.getSearchFieldValue(ZmContactPicker.SEARCH_EMAIL); 212 var deptQuery = this.getSearchFieldValue(ZmContactPicker.SEARCH_DEPT); 213 var phoneticQuery = this.getSearchFieldValue(ZmContactPicker.SEARCH_PHONETIC); 214 var isGal = this._searchInSelect && (this._searchInSelect.getValue() == ZmContactsApp.SEARCHFOR_GAL); 215 if (nameQuery && !isGal) { 216 var nameQueryPieces = nameQuery.split(/\s+/); 217 for (var i = 0; i < nameQueryPieces.length; i++) { 218 var nameQueryPiece = nameQueryPieces[i]; 219 nameQueryTerms.push("#"+ZmContact.F_firstName + ":" + nameQueryPiece); 220 nameQueryTerms.push("#"+ZmContact.F_lastName + ":" + nameQueryPiece); 221 nameQueryTerms.push("#"+ZmContact.F_middleName + ":" + nameQueryPiece); 222 nameQueryTerms.push("#"+ZmContact.F_nickname + ":" + nameQueryPiece); 223 } 224 query = "(" + nameQueryTerms.join(" OR ") + ")"; 225 } else { 226 if (nameQuery && isGal) { 227 conds.push([{attr:ZmContact.F_firstName, op:"has", value: nameQuery}, 228 {attr:ZmContact.F_lastName, op:"has", value: nameQuery}, 229 {attr:ZmContact.F_middleName, op:"has", value: nameQuery}, 230 {attr:ZmContact.F_nickname, op:"has", value: nameQuery}, 231 {attr:ZmContact.F_phoneticFirstName, op:"has", value: nameQuery}, 232 {attr:ZmContact.F_phoneticLastName, op:"has", value: nameQuery}]); 233 } 234 query = ""; 235 } 236 if (emailQuery) { 237 if (isGal) { 238 conds.push([{attr:ZmContact.F_email, op:"has", value: emailQuery}, 239 {attr:ZmContact.F_email2, op:"has", value: emailQuery}, 240 {attr:ZmContact.F_email3, op:"has", value: emailQuery}, 241 {attr:ZmContact.F_email4, op:"has", value: emailQuery}, 242 {attr:ZmContact.F_email5, op:"has", value: emailQuery}, 243 {attr:ZmContact.F_email6, op:"has", value: emailQuery}, 244 {attr:ZmContact.F_email7, op:"has", value: emailQuery}, 245 {attr:ZmContact.F_email8, op:"has", value: emailQuery}, 246 {attr:ZmContact.F_email9, op:"has", value: emailQuery}, 247 {attr:ZmContact.F_email10, op:"has", value: emailQuery}, 248 {attr:ZmContact.F_email11, op:"has", value: emailQuery}, 249 {attr:ZmContact.F_email12, op:"has", value: emailQuery}, 250 {attr:ZmContact.F_email13, op:"has", value: emailQuery}, 251 {attr:ZmContact.F_email14, op:"has", value: emailQuery}, 252 {attr:ZmContact.F_email15, op:"has", value: emailQuery}, 253 {attr:ZmContact.F_email16, op:"has", value: emailQuery} 254 ]); 255 } else { 256 emailQueryTerm = "to:"+emailQuery+"*"; 257 } 258 } 259 if (deptQuery && isGal) { 260 conds.push({attr:ZmContact.F_department, op:"has", value: deptQuery}); 261 } 262 if (phoneticQuery && !isGal) { 263 var phoneticQueryPieces = phoneticQuery.split(/\s+/); 264 for (var i=0; i<phoneticQueryPieces.length; i++) { 265 phoneticQueryTerms.push("#"+ZmContact.F_phoneticFirstName + ":" + phoneticQueryPieces[i]); 266 phoneticQueryTerms.push("#"+ZmContact.F_phoneticLastName + ":" + phoneticQueryPieces[i]); 267 } 268 } 269 } else { 270 query = this.getSearchFieldValue(ZmContactPicker.SEARCH_BASIC); 271 } 272 273 274 if (this._searchInSelect) { 275 var searchFor = this._searchInSelect.getValue(); 276 this._contactSource = (searchFor == ZmContactsApp.SEARCHFOR_CONTACTS || searchFor == ZmContactsApp.SEARCHFOR_PAS) 277 ? ZmItem.CONTACT 278 : ZmId.SEARCH_GAL; 279 280 if (searchFor == ZmContactsApp.SEARCHFOR_PAS) { 281 queryHint.push(ZmSearchController.generateQueryForShares(ZmId.ITEM_CONTACT) || "is:local"); 282 } else if (searchFor == ZmContactsApp.SEARCHFOR_CONTACTS) { 283 queryHint.push("is:local"); 284 } else if (searchFor == ZmContactsApp.SEARCHFOR_GAL) { 285 ascending = true; 286 } 287 } else { 288 this._contactSource = appCtxt.get(ZmSetting.CONTACTS_ENABLED, null, this._account) 289 ? ZmItem.CONTACT 290 : ZmId.SEARCH_GAL; 291 292 if (this._contactSource == ZmItem.CONTACT) { 293 queryHint.push("is:local"); 294 } 295 } 296 297 if (this._contactSource == ZmItem.CONTACT && query != "" && !query.startsWith ("(")) { 298 query = query.replace(/\"/g, '\\"'); 299 query = "\"" + query + "\""; 300 } 301 if (phoneticQueryTerms.length) { 302 query = query + " (" + phoneticQueryTerms.join(" OR ") + ")"; 303 } 304 if (emailQueryTerm.length) { 305 query = query + " " + emailQueryTerm; // MUST match email term, hence AND rather than OR 306 } 307 308 if (this._searchIcon) { //does not exist in ZmGroupView case 309 this._searchIcon.className = "DwtWait16Icon"; 310 } 311 312 // XXX: line below doesn't have intended effect (turn off column sorting for GAL search) 313 if (this._chooser) { //_chooser not defined in ZmGroupView but we also do not support sorting there anyway 314 this._chooser.sourceListView.sortingEnabled = (this._contactSource == ZmItem.CONTACT); 315 } 316 317 var params = { 318 obj: this, 319 ascending: ascending, 320 query: query, 321 queryHint: queryHint.join(" "), 322 conds: conds, 323 offset: offset || 0, 324 lastId: lastId, 325 lastSortVal: lastSortVal, 326 respCallback: (new AjxCallback(this, this._handleResponseSearch, [firstTime])), 327 errorCallback: this._searchErrorCallback, 328 accountName: (this._account && this._account.name), 329 expandDL: true 330 }; 331 ZmContactsHelper.search(params); 332 }; 333 334 /** 335 * @private 336 */ 337 ZmContactPicker.prototype._contentHtml = 338 function(account) { 339 var showSelect; 340 if (appCtxt.multiAccounts) { 341 var list = appCtxt.accountList.visibleAccounts; 342 for (var i = 0; i < list.length; i++) { 343 var account = list[i]; 344 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED, null, account) && 345 (appCtxt.get(ZmSetting.GAL_ENABLED, null, account) || 346 appCtxt.get(ZmSetting.SHARING_ENABLED, null, account))) 347 { 348 showSelect = true; 349 break; 350 } 351 } 352 } else { 353 showSelect = (appCtxt.get(ZmSetting.CONTACTS_ENABLED) && 354 (appCtxt.get(ZmSetting.GAL_ENABLED) || 355 appCtxt.get(ZmSetting.SHARING_ENABLED))); 356 } 357 358 var subs = { 359 id: this._htmlElId, 360 showSelect: showSelect, 361 detailed: this._detailedSearch 362 }; 363 364 return (AjxTemplate.expand("abook.Contacts#ZmContactPicker", subs)); 365 }; 366 367 /** 368 * @private 369 */ 370 ZmContactPicker.prototype._resetSelectDiv = 371 function() { 372 this._searchInSelect.clearOptions(); 373 374 if (appCtxt.multiAccounts) { 375 var accts = appCtxt.accountList.visibleAccounts; 376 var org = ZmOrganizer.ITEM_ORGANIZER; 377 org = ZmOrganizer.ITEM_ORGANIZER[ZmItem.CONTACT]; 378 379 for (var i = 0; i < accts.length; i++) { 380 this._searchInSelect.addOption(accts[i].displayName, false, accts[i].id); 381 var folderTree = appCtxt.getFolderTree(accts[i]); 382 var data = []; 383 data = data.concat(folderTree.getByType(org)); 384 for (var j = 0; j < data.length; j++) { 385 var addrsbk = data[j]; 386 if(addrsbk.noSuchFolder) { continue; } 387 this._searchInSelect.addOption(addrsbk.getName(), false, addrsbk.id, "ImgContact"); 388 } 389 if(accts[i].isZimbraAccount && !accts[i].isMain) { 390 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED, null, this._account)) { 391 if (appCtxt.get(ZmSetting.SHARING_ENABLED, null, this._account)) 392 this._searchInSelect.addOption(ZmMsg.searchPersonalSharedContacts, false, ZmContactsApp.SEARCHFOR_PAS, "ImgContact"); 393 } 394 395 if (appCtxt.get(ZmSetting.GAL_ENABLED, null, this._account)) { 396 this._searchInSelect.addOption(ZmMsg.GAL, true, ZmContactsApp.SEARCHFOR_GAL, "ImgContact"); 397 } 398 399 if (!appCtxt.get(ZmSetting.INITIALLY_SEARCH_GAL, null, this._account) || 400 !appCtxt.get(ZmSetting.GAL_ENABLED, null, this._account)) 401 { 402 this._searchInSelect.setSelectedValue(ZmContactsApp.SEARCHFOR_CONTACTS); 403 } 404 } 405 } 406 407 for (var k = 0; k < accts.length; k++) { 408 this._searchInSelect.enableOption(accts[k].id, false); 409 } 410 } else { 411 412 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED, null, this._account)) { 413 this._searchInSelect.addOption(ZmMsg.contacts, false, ZmContactsApp.SEARCHFOR_CONTACTS); 414 415 if (appCtxt.get(ZmSetting.SHARING_ENABLED, null, this._account)) 416 this._searchInSelect.addOption(ZmMsg.searchPersonalSharedContacts, false, ZmContactsApp.SEARCHFOR_PAS); 417 } 418 419 if (appCtxt.get(ZmSetting.GAL_ENABLED, null, this._account)) { 420 this._searchInSelect.addOption(ZmMsg.GAL, true, ZmContactsApp.SEARCHFOR_GAL); 421 } 422 423 if (!appCtxt.get(ZmSetting.INITIALLY_SEARCH_GAL, null, this._account) || 424 !appCtxt.get(ZmSetting.GAL_ENABLED, null, this._account)) 425 { 426 this._searchInSelect.setSelectedValue(ZmContactsApp.SEARCHFOR_CONTACTS); 427 } 428 429 } 430 }; 431 432 ZmContactPicker.prototype.getSearchFieldValue = 433 function(fieldId) { 434 if (!fieldId && !this._detailedSearch) { 435 fieldId = ZmContactPicker.SEARCH_BASIC; 436 } 437 var field = this._searchField[fieldId]; 438 return field && AjxStringUtil.trim(field.value) || ""; 439 }; 440 441 442 ZmContactPicker.prototype._getDialogHeight = 443 function() { 444 return ZmContactPicker.DIALOG_HEIGHT - (appCtxt.isChildWindow ? 100 : 0); 445 }; 446 447 ZmContactPicker.prototype._getSectionHeight = 448 function(idSuffix) { 449 return Dwt.getSize(document.getElementById(this._htmlElId + idSuffix)).y; 450 451 }; 452 453 ZmContactPicker.prototype._resizeChooser = 454 function() { 455 456 var chooserHeight = this._getDialogHeight() 457 - this._getSectionHeight("_handle") //the header 458 - this._getSectionHeight("_searchTable") 459 - this._getSectionHeight("_paging") 460 - this._getSectionHeight("_buttonsSep") 461 - this._getSectionHeight("_buttons") 462 - 30; //still need some magic to account for some margins etc. 463 464 this._chooser.resize(this.getSize().x - 25, chooserHeight); 465 }; 466 467 /** 468 * called only when ZmContactPicker is first created. Sets up initial layout. 469 * 470 * @private 471 */ 472 ZmContactPicker.prototype._initialize = 473 function(account) { 474 475 // create static content and append to dialog parent 476 this.setContent(this._contentHtml(account)); 477 478 this._searchIcon = document.getElementById(this._htmlElId + "_searchIcon"); 479 480 // add search button 481 this._searchButton = new DwtButton({parent:this, parentElement:(this._htmlElId+"_searchButton")}); 482 this._searchButton.setText(ZmMsg.search); 483 this._searchButton.addSelectionListener(new AjxListener(this, this._searchButtonListener)); 484 485 // add select menu 486 var selectCellId = this._htmlElId + "_listSelect"; 487 var selectCell = document.getElementById(selectCellId); 488 if (selectCell) { 489 this._searchInSelect = new DwtSelect({ 490 parent: this, 491 parentElement: selectCellId, 492 id: Dwt.getNextId("ZmContactPickerSelect_"), 493 legendId: this._htmlElId + '_listSelectLbl' 494 }); 495 this._resetSelectDiv(); 496 this._searchInSelect.addChangeListener(new AjxListener(this, this._searchTypeListener)); 497 } else { 498 this.setSize("600"); 499 } 500 501 // add chooser 502 this._chooser = new ZmContactChooser({parent:this, buttonInfo:this._buttonInfo}); 503 this._chooser.reparentHtmlElement(this._htmlElId + "_chooser"); 504 505 // add paging buttons 506 var pageListener = new AjxListener(this, this._pageListener); 507 this._prevButton = new DwtButton({parent:this, parentElement:(this._htmlElId+"_pageLeft")}); 508 this._prevButton.setText(ZmMsg.previous); 509 this._prevButton.setImage("LeftArrow"); 510 this._prevButton.addSelectionListener(pageListener); 511 512 this._nextButton = new DwtButton({parent:this, style:DwtLabel.IMAGE_RIGHT, parentElement:(this._htmlElId+"_pageRight")}); 513 this._nextButton.setText(ZmMsg.next); 514 this._nextButton.setImage("RightArrow"); 515 this._nextButton.addSelectionListener(pageListener); 516 517 var pageContainer = document.getElementById(this._htmlElId + "_paging"); 518 if (pageContainer) { 519 Dwt.setSize(pageContainer, this._chooser.sourceListView.getSize().x); 520 } 521 522 // init listeners 523 this.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._okButtonListener)); 524 this.setButtonListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, this._cancelButtonListener)); 525 526 var fieldMap = {}; 527 var rowMap = {}; 528 this.mapFields(fieldMap, rowMap); 529 530 this._searchField = {}; 531 for (var fieldId in fieldMap) { 532 var field = Dwt.byId(fieldMap[fieldId]); 533 if (field) { 534 this._searchField[fieldId] = field; 535 Dwt.setHandler(field, DwtEvent.ONKEYUP, ZmContactPicker._keyPressHdlr); 536 } 537 } 538 539 this._searchRow = {}; 540 for (var rowId in rowMap) { 541 var row = Dwt.byId(rowMap[rowId]); 542 if (row) { 543 this._searchRow[rowId] = row; 544 } 545 } 546 this._updateSearchRows(this._searchInSelect && this._searchInSelect.getValue() || ZmContactsApp.SEARCHFOR_CONTACTS); 547 this._keyPressCallback = new AjxCallback(this, this._searchButtonListener); 548 this.sharedContactGroups = []; 549 550 //add tabgroups for keyboard navigation 551 this._tabGroup = new DwtTabGroup(this.toString()); 552 this._tabGroup.removeAllMembers(); 553 for (var i = 0; i < ZmContactPicker.ALL.length; i++) { 554 field = Dwt.byId(fieldMap[ZmContactPicker.ALL[i]]); 555 if (Dwt.getVisible(field)) { 556 this._tabGroup.addMember(field); 557 } 558 } 559 this._tabGroup.addMember(this._searchButton); 560 this._tabGroup.addMember(this._searchInSelect); 561 this._tabGroup.addMember(this._chooser.getTabGroupMember()); 562 this._tabGroup.addMember(this._prevButton); 563 this._tabGroup.addMember(this._nextButton); 564 for (var i = 0; i < this._buttonList.length; i++) { 565 this._tabGroup.addMember(this._button[this._buttonList[i]]); 566 } 567 }; 568 569 ZmContactPicker.prototype.mapFields = 570 function(fieldMap, rowMap) { 571 if (this._detailedSearch) { 572 fieldMap[ZmContactPicker.SEARCH_NAME] = this._htmlElId + "_searchNameField"; 573 fieldMap[ZmContactPicker.SEARCH_EMAIL] = this._htmlElId + "_searchEmailField"; 574 fieldMap[ZmContactPicker.SEARCH_DEPT] = this._htmlElId + "_searchDepartmentField"; 575 fieldMap[ZmContactPicker.SEARCH_PHONETIC] = this._htmlElId + "_searchPhoneticField"; 576 rowMap[ZmContactPicker.SEARCH_NAME] = this._htmlElId + "_searchNameRow"; 577 rowMap[ZmContactPicker.SEARCH_PHONETIC] = this._htmlElId + "_searchPhoneticRow"; 578 rowMap[ZmContactPicker.SEARCH_EMAIL] = this._htmlElId + "_searchEmailRow"; 579 rowMap[ZmContactPicker.SEARCH_DEPT] = this._htmlElId + "_searchDepartmentRow"; 580 } 581 else { 582 fieldMap[ZmContactPicker.SEARCH_BASIC] = this._htmlElId + "_searchField"; 583 rowMap[ZmContactPicker.SEARCH_BASIC] = this._htmlElId + "_searchRow"; 584 } 585 }; 586 587 // Listeners 588 589 /** 590 * @private 591 */ 592 ZmContactPicker.prototype._searchButtonListener = 593 function(ev) { 594 this._resetResults(); 595 this.search(); 596 }; 597 598 /** 599 * @private 600 */ 601 ZmContactPicker.prototype._handleResponseSearch = 602 function(firstTime, result) { 603 var resp = result.getResponse(); 604 var serverHasMore = resp.getAttribute("more"); 605 var serverPaginationSupported = resp.getAttribute("paginationSupported") !== false; //if it's not specified (such as the case of SearchResponse, i.e. not Gal) it IS supported. 606 this._serverHasMoreAndPaginationSupported = serverHasMore && serverPaginationSupported; 607 var offset = resp.getAttribute("offset"); 608 this._serverContactOffset = offset || 0; 609 var info = resp.getAttribute("info"); 610 var expanded = info && info[0].wildcard[0].expanded == "0"; 611 612 //the check for firstTime is so when the picker is popped up we probably don't want to overwhelm them with a warning message. So only show it if the user plays with the picker, using the drop-down or the search box. 613 if (!firstTime && !serverPaginationSupported && (serverHasMore || expanded)) { //no idea what the expanded case is 614 var d = appCtxt.getMsgDialog(); 615 d.setMessage(ZmMsg.errorSearchNotExpanded); 616 d.popup(); 617 if (expanded) { return; } 618 } 619 620 // this method will expand the list depending on the number of email 621 // addresses per contact. 622 var emailArray = ZmContactsHelper._processSearchResponse(resp, this._includeContactsWithNoEmail); //this._includeContactsWithNoEmail - true in the ZmGroupView case 623 var emailList = AjxVector.fromArray(emailArray); 624 625 if (serverPaginationSupported) { 626 this._emailList.addList(emailArray); //this internally calls concat. we do not need "merge" here because we use the _serverContactOffset as a marker of where to search next, never searching a block we already did. 627 } 628 else { 629 this._emailList = emailList; 630 } 631 var list = this.getSubList(); 632 if (this.toString() === "ZmContactPicker") { 633 list = this.loadSharedGroupContacts(list) || list; 634 } 635 this._showResults(list); 636 637 }; 638 639 ZmContactPicker.prototype._showResults = 640 function(aList) { 641 var list = aList || this.getSubList(); 642 // special case 1 - search forward another server block, to fill up a page. Could search several times. 643 if (list.size() < ZmContactsApp.SEARCHFOR_MAX && this._serverHasMoreAndPaginationSupported) { 644 this.search(null, null, null, null, null, this._serverContactOffset + ZmContactsApp.SEARCHFOR_MAX); //search another page 645 return; 646 } 647 648 if (this._searchIcon) { //does not exist in ZmGroupView case 649 this._searchIcon.className = ""; 650 } 651 this._searchButton.setEnabled(true); 652 653 // special case 2 - no results, and no more to search (that was covered in special case 1) - so display the "no results" text. 654 if (list.size() == 0 && this._emailListOffset == 0) { 655 this._setResultsInView(list); //empty the list 656 this._nextButton.setEnabled(false); 657 this._prevButton.setEnabled(false); 658 this._setNoResultsHtml(); 659 return; 660 } 661 662 // special case 3 - If the AB ends with a long list of contacts w/o addresses, 663 // we may get an empty list. If that's the case, roll back the offset 664 // not 100% sure this case could still happen after all my changes but it was there in the code, so I keep it just in case. 665 if (list.size() == 0) { 666 this._emailListOffset -= ZmContactsApp.SEARCHFOR_MAX; 667 this._emailListOffset = Math.max(0, this._emailListOffset); 668 } 669 670 var more = this._serverHasMoreAndPaginationSupported //we can get more from the server 671 || (this._emailListOffset + ZmContactsApp.SEARCHFOR_MAX) < this._emailList.size(); //or we have more on the client we didn't yet show 672 this._prevButton.setEnabled(this._emailListOffset > 0); 673 this._nextButton.setEnabled(more); 674 675 this._resetSearchColHeaders(); // bug #2269 - enable/disable sort column per type of search 676 this._setResultsInView(list); 677 }; 678 679 ZmContactPicker.prototype.loadSharedGroupContacts = 680 function(aList) { 681 682 var listLen, 683 listArray, 684 contact, 685 item, 686 i, 687 j, 688 k, 689 sharedContactGroupArray, 690 len1, 691 jsonObj, 692 batchRequest, 693 request, 694 response; 695 696 listArray = aList.getArray(); 697 listLen = aList.size(); 698 sharedContactGroupArray = []; 699 700 for (i = 0 ; i < listLen; i++) { 701 item = listArray[i]; 702 contact = item.__contact; 703 if (contact.isGroup() && contact.isShared()) { 704 if (this.sharedContactGroups.indexOf(item.value) !== -1) { 705 return; 706 } 707 this.sharedContactGroups.push(item.value); 708 sharedContactGroupArray.push(item.value); 709 } 710 } 711 712 len1 = sharedContactGroupArray.length; 713 jsonObj = {BatchRequest:{GetContactsRequest:[],_jsns:"urn:zimbra", onerror:'continue'}}; 714 batchRequest = jsonObj.BatchRequest; 715 request = batchRequest.GetContactsRequest; 716 717 for (j = 0,k =0; j < len1; j++) { 718 request.push({ cn: {id: sharedContactGroupArray[j]}, _jsns: 'urn:zimbraMail', derefGroupMember: '1', requestId: k++ }); 719 } 720 var respCallback = new AjxCallback(this, this.handleSharedContactResponse,[aList]); 721 response = appCtxt.getAppController().sendRequest({ 722 jsonObj:jsonObj, 723 asyncMode:true, 724 callback:respCallback 725 }); 726 }; 727 728 ZmContactPicker.prototype.handleSharedContactResponse = 729 function(aList,response) { 730 731 var contactResponse, 732 contactResponseLength, 733 listArray, 734 listArrayLength, 735 sharedGroupMembers, 736 i, 737 j, 738 k, 739 resp, 740 contact, 741 member, 742 isGal, 743 memberContact, 744 loadMember, 745 listArrElement, 746 sharedGroupMembers; 747 748 if (response && response.getResponse() && response.getResponse().BatchResponse) { 749 contactResponse = response.getResponse().BatchResponse.GetContactsResponse; 750 } 751 if (!contactResponse) { 752 return; 753 } 754 contactResponseLength = contactResponse.length; 755 listArray = aList.getArray(); 756 listArrayLength = aList.size(); 757 758 for (k= 0; k < listArrayLength; k++) { 759 sharedGroupMembers = []; 760 for (j = 0; j < contactResponseLength; j++) { 761 resp = contactResponse[j]; 762 contact = resp.cn[0]; 763 764 if (contact.m) { 765 for (i = 0; i < contact.m.length; i++) { 766 member = contact.m[i]; 767 isGal = false; 768 if (member.type == ZmContact.GROUP_GAL_REF) { 769 isGal = true; 770 } 771 if (member.cn && member.cn.length > 0) { 772 memberContact = member.cn[0]; 773 memberContact.ref = memberContact.ref || (isGal && member.value); 774 loadMember = ZmContact.createFromDom(memberContact, {list: this.list, isGal: isGal}); 775 loadMember.isDL = isGal && loadMember.attr[ZmContact.F_type] === "group"; 776 appCtxt.cacheSet(member.value, loadMember); 777 listArrElement = listArray[k]; 778 if (listArrElement.value === contact.id) { 779 sharedGroupMembers.push( '"'+loadMember.getFullName()+'"' +' <' + loadMember.getEmails() +'>;' ); // Updating the original list with shared members of shared contact group that comes in 'contactResponse'. 780 aList._array[k].address = sharedGroupMembers.join(""); 781 } 782 } 783 } 784 ZmContact.prototype._loadFromDom(contact); 785 } 786 } 787 } 788 this._showResults(aList); // As async = true, when the response has come, again we render/update the list with contactResponse shared contact members, 789 }; 790 791 792 /** 793 * extracted this so it can be used in ZmGroupView where this is different. 794 * @param list 795 */ 796 ZmContactPicker.prototype._setResultsInView = 797 function(list) { 798 this._chooser.setItems(list); 799 }; 800 801 /** 802 * extracted this so it can be used in ZmGroupView where this is different. 803 * @param list 804 */ 805 ZmContactPicker.prototype._setNoResultsHtml = 806 function(list) { 807 this._chooser.sourceListView._setNoResultsHtml(); 808 }; 809 810 811 ZmContactPicker.prototype._updateSearchRows = 812 function(searchFor) { 813 var fieldIds = (searchFor == ZmContactsApp.SEARCHFOR_GAL) ? ZmContactPicker.SHOW_ON_GAL : ZmContactPicker.SHOW_ON_NONGAL; 814 for (var fieldId in this._searchRow) { 815 Dwt.setVisible(this._searchRow[fieldId], AjxUtil.indexOf(fieldIds, fieldId)!=-1); 816 } 817 for (var fieldId in this._searchField) { 818 var field = this._searchField[fieldId]; 819 if (this._tabGroup.contains(field)) 820 this._tabGroup.removeMember(field); 821 } 822 for (var i=0; i<fieldIds.length; i++) { 823 this._tabGroup.addMember(this._searchField[fieldIds[i]]); 824 } 825 826 this._resizeChooser(); 827 }; 828 829 /** 830 * @private 831 */ 832 ZmContactPicker.prototype._handleErrorSearch = 833 function() { 834 this._searchButton.setEnabled(true); 835 return false; 836 }; 837 838 /** 839 * @private 840 */ 841 ZmContactPicker.prototype._pageListener = 842 function(ev) { 843 if (ev.item == this._prevButton) { 844 this._emailListOffset -= ZmContactsApp.SEARCHFOR_MAX; 845 this._emailListOffset = Math.max(0, this._emailListOffset); 846 } 847 else { 848 this._emailListOffset += ZmContactsApp.SEARCHFOR_MAX; 849 } 850 this._showResults(); 851 }; 852 853 /** 854 * Gets a sub-list of contacts. 855 * 856 * @return {AjxVector} a vector of {ZmContact} objects 857 */ 858 ZmContactPicker.prototype.getSubList = 859 function() { 860 var size = this._emailList.size(); 861 862 var end = this._emailListOffset + ZmContactsApp.SEARCHFOR_MAX; 863 864 if (end > size) { 865 end = size; 866 } 867 868 var a = (this._emailListOffset < end) ? this._emailList.getArray().slice(this._emailListOffset, end) : []; 869 return AjxVector.fromArray(a); 870 }; 871 872 /** 873 * @private 874 */ 875 ZmContactPicker.prototype._searchTypeListener = 876 function(ev) { 877 var oldValue = ev._args.oldValue; 878 var newValue = ev._args.newValue; 879 880 if (oldValue != newValue) { 881 this._updateSearchRows(newValue); 882 this._searchButtonListener(); 883 } 884 }; 885 886 /** 887 * @private 888 */ 889 ZmContactPicker.prototype._resetSearchColHeaders = 890 function () { 891 var slv = this._chooser.sourceListView; 892 var tlv = this._chooser.targetListView; 893 slv.headerColCreated = false; 894 tlv.headerColCreated = false; 895 var isGal = this._searchInSelect && (this._searchInSelect.getValue() == ZmContactsApp.SEARCHFOR_GAL); 896 897 // find the participant column 898 var part = 0; 899 for (var i = 0; i < slv._headerList.length; i++) { 900 var field = slv._headerList[i]._field; 901 if (field == ZmItem.F_NAME) { 902 part = i; 903 } 904 if (field == ZmItem.F_DEPARTMENT) { 905 slv._headerList[i]._visible = isGal && this._detailedSearch; 906 } 907 } 908 909 var sortable = isGal ? null : ZmItem.F_NAME; 910 slv._headerList[part]._sortable = sortable; 911 slv.createHeaderHtml(sortable); 912 913 for (i = 0; i < tlv._headerList.length; i++) { 914 if (tlv._headerList[i]._field == ZmItem.F_DEPARTMENT) { 915 tlv._headerList[i]._visible = isGal && this._detailedSearch; 916 } 917 } 918 tlv.createHeaderHtml(); 919 }; 920 921 /** 922 * Done choosing addresses, add them to the compose form. 923 * 924 * @private 925 */ 926 ZmContactPicker.prototype._okButtonListener = 927 function(ev) { 928 var data = this._chooser.getItems(); 929 DwtDialog.prototype._buttonListener.call(this, ev, [data]); 930 }; 931 932 /** 933 * Call custom popdown method. 934 * 935 * @private 936 */ 937 ZmContactPicker.prototype._cancelButtonListener = 938 function(ev) { 939 DwtDialog.prototype._buttonListener.call(this, ev); 940 this.popdown(); 941 }; 942 943 /** 944 * @private 945 */ 946 ZmContactPicker._keyPressHdlr = 947 function(ev) { 948 var stb = DwtControl.getTargetControl(ev); 949 var charCode = DwtKeyEvent.getCharCode(ev); 950 if (stb._keyPressCallback && (charCode == 13 || charCode == 3)) { 951 stb._keyPressCallback.run(); 952 return false; 953 } 954 return true; 955 }; 956 957 958 /***********************************************************************************/ 959 960 /** 961 * Creates a contact chooser. 962 * @class 963 * This class creates a specialized chooser for the contact picker. 964 * 965 * @param {DwtComposite} parent the contact picker 966 * @param {Array} buttonInfo transfer button IDs and labels 967 * 968 * @extends DwtChooser 969 * 970 * @private 971 */ 972 ZmContactChooser = function(params) { 973 DwtChooser.call(this, params); 974 }; 975 976 ZmContactChooser.prototype = new DwtChooser; 977 ZmContactChooser.prototype.constructor = ZmContactChooser; 978 979 /** 980 * @private 981 */ 982 ZmContactChooser.prototype._createSourceListView = 983 function() { 984 return new ZmContactChooserSourceListView(this); 985 }; 986 987 /** 988 * @private 989 */ 990 ZmContactChooser.prototype._createTargetListView = 991 function() { 992 return new ZmContactChooserTargetListView(this, (this._buttonInfo.length > 1)); 993 }; 994 995 /** 996 * The item is a AjxEmailAddress. Its address is used for comparison. 997 * 998 * @param {AjxEmailAddress} item an email address 999 * @param {AjxVector} list list to check in 1000 * 1001 * @private 1002 */ 1003 ZmContactChooser.prototype._isDuplicate = 1004 function(item, list) { 1005 return list.containsLike(item, item.getAddress); 1006 }; 1007 1008 /***********************************************************************************/ 1009 1010 /** 1011 * Creates a source list view. 1012 * @class 1013 * This class creates a specialized source list view for the contact chooser. 1014 * 1015 * @param {DwtComposite} parent the contact picker 1016 * 1017 * @extends DwtChooserListView 1018 * 1019 * @private 1020 */ 1021 ZmContactChooserSourceListView = function(parent) { 1022 DwtChooserListView.call(this, {parent:parent, type:DwtChooserListView.SOURCE}); 1023 this.setScrollStyle(Dwt.CLIP); 1024 }; 1025 1026 ZmContactChooserSourceListView.prototype = new DwtChooserListView; 1027 ZmContactChooserSourceListView.prototype.constructor = ZmContactChooserSourceListView; 1028 1029 /** 1030 * Returns a string representation of the object. 1031 * 1032 * @return {String} a string representation of the object 1033 * @private 1034 */ 1035 ZmContactChooserSourceListView.prototype.toString = 1036 function() { 1037 return "ZmContactChooserSourceListView"; 1038 }; 1039 1040 ZmContactChooserSourceListView.prototype.getToolTipContent = 1041 function(ev) { 1042 1043 if (this._hoveredItem) { 1044 var ttParams = { 1045 address: this._hoveredItem.address, 1046 contact: this._hoveredItem.__contact, 1047 ev: ev, 1048 noRightClick: true 1049 }; 1050 var ttCallback = new AjxCallback(this, 1051 function(callback) { 1052 appCtxt.getToolTipMgr().getToolTip(ZmToolTipMgr.PERSON, ttParams, callback); 1053 }); 1054 return {callback:ttCallback}; 1055 } 1056 else { 1057 return ""; 1058 } 1059 }; 1060 1061 /** 1062 * @private 1063 */ 1064 ZmContactChooserSourceListView.prototype._getHeaderList = 1065 function() { 1066 var headerList = []; 1067 headerList.push(new DwtListHeaderItem({field:ZmItem.F_TYPE, icon:"Folder", width:ZmMsg.COLUMN_WIDTH_FOLDER_CN})); 1068 headerList.push(new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg._name, width:ZmMsg.COLUMN_WIDTH_NAME_CN, resizeable: true})); 1069 headerList.push(new DwtListHeaderItem({field:ZmItem.F_DEPARTMENT, text:ZmMsg.department, width:ZmMsg.COLUMN_WIDTH_DEPARTMENT_CN, resizeable: true})); 1070 headerList.push(new DwtListHeaderItem({field:ZmItem.F_EMAIL, text:ZmMsg.email, resizeable: true})); 1071 1072 1073 return headerList; 1074 }; 1075 1076 1077 // Override of DwtListView.prototype._resetColWidth to set width; without overrriding causes vertical scrollbars to disapper 1078 // on header resize 1079 ZmContactChooserSourceListView.prototype._resetColWidth = 1080 function() { 1081 1082 if (!this.headerColCreated) { return; } 1083 1084 var lastColIdx = this._getLastColumnIndex(); 1085 if (lastColIdx) { 1086 var lastCol = this._headerList[lastColIdx]; 1087 var lastCell = document.getElementById(lastCol._id); 1088 if (lastCell) { 1089 var div = lastCell.firstChild; 1090 lastCell.style.width = div.style.width = (lastCol._width || ""); 1091 } 1092 } 1093 }; 1094 1095 /** 1096 * override for scrollbars in IE 1097 * @param headerIdx 1098 */ 1099 ZmContactChooserSourceListView.prototype._calcRelativeWidth = 1100 function(headerIdx) { 1101 var column = this._headerList[headerIdx]; 1102 if (!column._width || (column._width && column._width == "auto")) { 1103 var cell = document.getElementById(column._id); 1104 // UGH: clientWidth is 5px more than HTML-width (20px for IE to deal with scrollbars) 1105 return (cell) ? (cell.clientWidth - (AjxEnv.isIE ? Dwt.SCROLLBAR_WIDTH : 5)) : null; 1106 } 1107 return column._width; 1108 }; 1109 1110 /** 1111 * @private 1112 */ 1113 ZmContactChooserSourceListView.prototype._mouseOverAction = 1114 function(ev, div) { 1115 DwtChooserListView.prototype._mouseOverAction.call(this, ev, div); 1116 var id = ev.target.id || div.id; 1117 var item = this.getItemFromElement(div); 1118 this._hoveredItem = (id && item) ? item : null; 1119 return true; 1120 }; 1121 1122 /** 1123 * @private 1124 */ 1125 ZmContactChooserSourceListView.prototype._getCellContents = 1126 function(html, idx, item, field, colIdx, params) { 1127 if (field == ZmItem.F_EMAIL && AjxEnv.isIE) { 1128 var maxWidth = AjxStringUtil.getWidth(item.address); 1129 html[idx++] = "<div style='float; left; overflow: visible; width: " + maxWidth + ";'>"; 1130 idx = ZmContactsHelper._getEmailField(html, idx, item, field, colIdx, params); 1131 html[idx++] = "</div>"; 1132 } 1133 else { 1134 idx = ZmContactsHelper._getEmailField(html, idx, item, field, colIdx, params); 1135 } 1136 return idx; 1137 }; 1138 1139 /** 1140 * Returns a string of any extra attributes to be used for the TD. 1141 * 1142 * @param item [object] item to render 1143 * @param field [constant] column identifier 1144 * @param params [hash]* hash of optional params 1145 * 1146 * @private 1147 */ 1148 ZmContactChooserSourceListView.prototype._getCellAttrText = 1149 function(item, field, params) { 1150 if (field == ZmItem.F_EMAIL) { 1151 return "style='position: relative; overflow: visible;'"; 1152 } 1153 }; 1154 1155 /***********************************************************************************/ 1156 1157 /** 1158 * Creates the target list view. 1159 * @class 1160 * This class creates a specialized target list view for the contact chooser. 1161 * 1162 * @param {DwtComposite} parent the contact picker 1163 * @param {constant} showType the show type 1164 * @extends DwtChooserListView 1165 * 1166 * @private 1167 */ 1168 ZmContactChooserTargetListView = function(parent, showType) { 1169 this._showType = showType; // call before base class since base calls getHeaderList 1170 1171 DwtChooserListView.call(this, {parent:parent, type:DwtChooserListView.TARGET}); 1172 1173 this.setScrollStyle(Dwt.CLIP); 1174 }; 1175 1176 ZmContactChooserTargetListView.prototype = new DwtChooserListView; 1177 ZmContactChooserTargetListView.prototype.constructor = ZmContactChooserTargetListView; 1178 1179 /** 1180 * Returns a string representation of the object. 1181 * 1182 * @return {String} a string representation of the object 1183 */ 1184 ZmContactChooserTargetListView.prototype.toString = 1185 function() { 1186 return "ZmContactChooserTargetListView"; 1187 }; 1188 1189 /** 1190 * @private 1191 */ 1192 ZmContactChooserTargetListView.prototype._getHeaderList = 1193 function() { 1194 var headerList = []; 1195 var view = this._view; 1196 if (this._showType) { 1197 headerList.push(new DwtListHeaderItem({field:ZmItem.F_TYPE, icon:"ContactsPicker", width:ZmMsg.COLUMN_WIDTH_TYPE_CN})); 1198 } 1199 headerList.push(new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg._name, width:ZmMsg.COLUMN_WIDTH_NAME_CN, resizeable: true})); 1200 headerList.push(new DwtListHeaderItem({field:ZmItem.F_DEPARTMENT, text:ZmMsg.department, width:ZmMsg.COLUMN_WIDTH_DEPARTMENT_CN, resizeable: true})); 1201 headerList.push(new DwtListHeaderItem({field:ZmItem.F_EMAIL, text:ZmMsg.email, resizeable: true})); 1202 1203 return headerList; 1204 }; 1205 1206 ZmContactChooserTargetListView.prototype._mouseOverAction = 1207 ZmContactChooserSourceListView.prototype._mouseOverAction; 1208 1209 /** 1210 * The items are AjxEmailAddress objects. 1211 * 1212 * @private 1213 */ 1214 ZmContactChooserTargetListView.prototype._getCellContents = 1215 function(html, idx, item, field, colIdx, params) { 1216 if (field == ZmItem.F_TYPE) { 1217 item.setType(item._buttonId); 1218 html[idx++] = ZmMsg[item.getTypeAsString()]; 1219 html[idx++] = ":"; 1220 } 1221 else if (field == ZmItem.F_EMAIL && AjxEnv.isIE) { 1222 var maxWidth = AjxStringUtil.getWidth(item.address) + 10; 1223 html[idx++] = "<div style='float; left; width: " + maxWidth + ";'>"; 1224 idx = ZmContactsHelper._getEmailField(html, idx, item, field, colIdx, params); 1225 html[idx++] = "</div>"; 1226 } 1227 else { 1228 idx = ZmContactsHelper._getEmailField(html, idx, item, field, colIdx); 1229 } 1230 return idx; 1231 }; 1232 1233 1234 // Override of DwtListView.prototype._resetColWidth to set width; without overrriding causes vertical scrollbars to disapper 1235 // on header resize 1236 ZmContactChooserTargetListView.prototype._resetColWidth = 1237 function() { 1238 1239 if (!this.headerColCreated) { return; } 1240 1241 var lastColIdx = this._getLastColumnIndex(); 1242 1243 1244 if (lastColIdx) { 1245 var lastCol = this._headerList[lastColIdx]; 1246 var lastCell = document.getElementById(lastCol._id); 1247 if (lastCell) { 1248 var div = lastCell.firstChild; 1249 lastCell.style.width = div.style.width = (lastCol._width || ""); 1250 } 1251 } 1252 }; 1253 1254 /** 1255 * override for scrollbars in IE 1256 * @param headerIdx 1257 */ 1258 ZmContactChooserTargetListView.prototype._calcRelativeWidth = 1259 function(headerIdx) { 1260 var column = this._headerList[headerIdx]; 1261 if (!column._width || (column._width && column._width == "auto")) { 1262 var cell = document.getElementById(column._id); 1263 // UGH: clientWidth is 5px more than HTML-width (20px for IE to deal with scrollbars) 1264 return (cell) ? (cell.clientWidth - (AjxEnv.isIE ? Dwt.SCROLLBAR_WIDTH : 5)) : null; 1265 } 1266 return column._width; 1267 }; 1268 1269 /** 1270 * Returns a string of any extra attributes to be used for the TD. 1271 * 1272 * @param item [object] item to render 1273 * @param field [constant] column identifier 1274 * @param params [hash]* hash of optional params 1275 * 1276 * @private 1277 */ 1278 ZmContactChooserTargetListView.prototype._getCellAttrText = 1279 function(item, field, params) { 1280 if (field == ZmItem.F_EMAIL) { 1281 return "style='position: relative; overflow: visible;'"; 1282 } 1283 }; 1284