1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates a new dialog that can be used to choose attendees, locations, or equipment. 26 * @constructor 27 * @class 28 * This class allows the user to search for attendees, locations, or 29 * equipment. It presents a chooser which allows the user to select items from 30 * the search results. 31 * 32 * @author Sathishkumar 33 * 34 * @param {DwtComposite} editView the edit view that pops up this dialog 35 * @param {Hash} attendees the attendees/locations/equipment 36 * @param {ZmApptComposeController} controller the appt compose controller 37 * @param {constant} type the chooser page type 38 * 39 * @extends DwtTabViewPage 40 */ 41 ZmAttendeePicker = function(editView, attendees, controller, type, dateInfo) { 42 43 DwtDialog.call(this, { 44 parent: appCtxt.getShell(), 45 title: ZmAttendeePicker.TOP_LEGEND[type], 46 id: [ 'ZmAttendeePicker', type ].join('_') 47 }); 48 49 this._attendees = attendees; 50 this._controller = controller; 51 this._editView = editView; 52 this.type = type; 53 this._dateInfo = dateInfo; 54 55 this._offset = 0; 56 this._rendered = false; 57 this._isClean = true; 58 this._searchFields = {}; 59 this._searchFieldIds = {}; 60 this._keyPressCallback = new AjxCallback(this, this._searchButtonListener); 61 this._kbMgr = appCtxt.getKeyboardMgr(); 62 this._list = new AjxVector(); 63 this.showSelect = false; 64 }; 65 66 ZmAttendeePicker.COL_LABEL = {}; 67 ZmAttendeePicker.COL_LABEL[ZmItem.F_FOLDER] = "folder"; 68 ZmAttendeePicker.COL_LABEL[ZmItem.F_NAME] = "_name"; 69 ZmAttendeePicker.COL_LABEL[ZmItem.F_EMAIL] = "email"; 70 ZmAttendeePicker.COL_LABEL[ZmItem.F_WORK_PHONE] = "AB_FIELD_workPhone"; 71 ZmAttendeePicker.COL_LABEL[ZmItem.F_HOME_PHONE] = "AB_FIELD_homePhone"; 72 ZmAttendeePicker.COL_LABEL[ZmItem.F_LOCATION] = "location"; 73 ZmAttendeePicker.COL_LABEL[ZmItem.F_CONTACT] = "contact"; 74 ZmAttendeePicker.COL_LABEL[ZmItem.F_CAPACITY] = "capacity"; 75 ZmAttendeePicker.COL_LABEL["FBSTATUS"] = "status"; 76 77 ZmAttendeePicker.COL_IMAGE = {}; 78 ZmAttendeePicker.COL_IMAGE[ZmItem.F_NOTES] = "Page"; 79 80 ZmAttendeePicker.COL_WIDTH = {}; 81 ZmAttendeePicker.COL_WIDTH[ZmItem.F_FOLDER] = ZmMsg.COLUMN_WIDTH_FOLDER_NA; 82 ZmAttendeePicker.COL_WIDTH[ZmItem.F_NAME] = ZmMsg.COLUMN_WIDTH_NAME_NA; 83 ZmAttendeePicker.COL_WIDTH[ZmItem.F_EMAIL] = null; 84 ZmAttendeePicker.COL_WIDTH[ZmItem.F_WORK_PHONE] = ZmMsg.COLUMN_WIDTH_WORK_PHONE_NA; 85 ZmAttendeePicker.COL_WIDTH[ZmItem.F_HOME_PHONE] = ZmMsg.COLUMN_WIDTH_HOME_PHONE_NA; 86 ZmAttendeePicker.COL_WIDTH[ZmItem.F_LOCATION] = null; 87 ZmAttendeePicker.COL_WIDTH[ZmItem.F_CONTACT] = ZmMsg.COLUMN_WIDTH_CONTACT_NA; 88 ZmAttendeePicker.COL_WIDTH[ZmItem.F_CAPACITY] = ZmMsg.COLUMN_WIDTH_CAPACITY_NA; 89 ZmAttendeePicker.COL_WIDTH[ZmItem.F_NOTES] = ZmMsg.COLUMN_WIDTH_NOTES_NA; 90 ZmAttendeePicker.COL_WIDTH["FBSTATUS"] = ZmMsg.COLUMN_WIDTH_FBSTATUS_NA; 91 92 ZmAttendeePicker.COLS = {}; 93 ZmAttendeePicker.COLS[ZmCalBaseItem.PERSON] = [ZmItem.F_FOLDER, ZmItem.F_NAME, ZmItem.F_EMAIL, ZmItem.F_WORK_PHONE, ZmItem.F_HOME_PHONE, "FBSTATUS"]; 94 ZmAttendeePicker.COLS[ZmCalBaseItem.LOCATION] = [ZmItem.F_NAME, ZmItem.F_LOCATION, ZmItem.F_CONTACT, ZmItem.F_CAPACITY, "FBSTATUS", ZmItem.F_NOTES]; 95 ZmAttendeePicker.COLS[ZmCalBaseItem.EQUIPMENT] = [ZmItem.F_NAME, ZmItem.F_LOCATION, ZmItem.F_CONTACT, "FBSTATUS", ZmItem.F_NOTES]; 96 97 // search fields 98 (function () { 99 var i = 1; 100 ZmAttendeePicker.SF_ATT_NAME = i++; 101 ZmAttendeePicker.SF_NAME = i++; 102 ZmAttendeePicker.SF_SOURCE = i++; 103 ZmAttendeePicker.SF_CAPACITY = i++; 104 ZmAttendeePicker.SF_DESCRIPTION = i++; 105 ZmAttendeePicker.SF_SITE = i++; 106 ZmAttendeePicker.SF_BUILDING = i++; 107 ZmAttendeePicker.SF_FLOOR = i++; 108 })(); 109 110 // search field labels 111 ZmAttendeePicker.SF_LABEL = {}; 112 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_ATT_NAME] = "find"; 113 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_NAME] = "_name"; 114 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_SOURCE] = "source"; 115 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_CAPACITY] = "minimumCapacity"; 116 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_DESCRIPTION] = "description"; 117 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_CONTACT] = "contact"; 118 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_SITE] = "site"; 119 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_BUILDING] = "building"; 120 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_FLOOR] = "floor"; 121 122 // corresponding attributes for search command 123 ZmAttendeePicker.SF_ATTR = {}; 124 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_NAME] = "fullName"; 125 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_CAPACITY] = "zimbraCalResCapacity"; 126 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_DESCRIPTION] = "description"; 127 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_CONTACT] = "zimbraCalResContactName"; 128 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_SITE] = "zimbraCalResSite"; 129 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_BUILDING] = "zimbraCalResBuilding"; 130 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_FLOOR] = "zimbraCalResFloor"; 131 132 // search field compares ops - listed here if not substring ("has") 133 ZmAttendeePicker.SF_OP = {}; 134 ZmAttendeePicker.SF_OP[ZmAttendeePicker.SF_CAPACITY] = "ge"; 135 ZmAttendeePicker.SF_OP[ZmAttendeePicker.SF_FLOOR] = "eq"; 136 137 ZmAttendeePicker.ATTRS = {}; 138 ZmAttendeePicker.ATTRS[ZmCalBaseItem.LOCATION] = 139 ["fullName", "email", "zimbraCalResLocationDisplayName", 140 "zimbraCalResCapacity", "zimbraCalResContactEmail", "description", "zimbraCalResType"]; 141 ZmAttendeePicker.ATTRS[ZmCalBaseItem.EQUIPMENT] = 142 ["fullName", "email", "zimbraCalResLocationDisplayName", 143 "zimbraCalResContactEmail", "description", "zimbraCalResType"]; 144 145 ZmAttendeePicker.SEARCH_FIELDS = {}; 146 ZmAttendeePicker.SEARCH_FIELDS[ZmCalBaseItem.PERSON] = 147 [ZmAttendeePicker.SF_ATT_NAME, ZmAttendeePicker.SF_SOURCE]; 148 ZmAttendeePicker.SEARCH_FIELDS[ZmCalBaseItem.LOCATION] = 149 [ZmAttendeePicker.SF_NAME, ZmAttendeePicker.SF_SITE, 150 ZmAttendeePicker.SF_CAPACITY, ZmAttendeePicker.SF_BUILDING, 151 ZmAttendeePicker.SF_DESCRIPTION, ZmAttendeePicker.SF_FLOOR]; 152 ZmAttendeePicker.SEARCH_FIELDS[ZmCalBaseItem.EQUIPMENT] = 153 [ZmAttendeePicker.SF_NAME, ZmAttendeePicker.SF_SITE, 154 ZmAttendeePicker.SF_DESCRIPTION, ZmAttendeePicker.SF_BUILDING, 155 ZmAttendeePicker.SF_CONTACT, ZmAttendeePicker.SF_FLOOR]; 156 157 ZmAttendeePicker.SETTINGS_SEARCH_FIELDS = {}; 158 159 ZmAttendeePicker.SORT_BY = {}; 160 ZmAttendeePicker.SORT_BY[ZmCalBaseItem.PERSON] = ZmSearch.NAME_ASC; 161 ZmAttendeePicker.SORT_BY[ZmCalBaseItem.LOCATION] = ZmSearch.NAME_ASC; 162 ZmAttendeePicker.SORT_BY[ZmCalBaseItem.EQUIPMENT] = ZmSearch.NAME_ASC; 163 164 ZmAttendeePicker.TOP_LEGEND = {}; 165 ZmAttendeePicker.TOP_LEGEND[ZmCalBaseItem.PERSON] = ZmMsg.findAttendees; 166 ZmAttendeePicker.TOP_LEGEND[ZmCalBaseItem.LOCATION] = ZmMsg.findLocations; 167 ZmAttendeePicker.TOP_LEGEND[ZmCalBaseItem.EQUIPMENT] = ZmMsg.findEquipment; 168 169 ZmAttendeePicker.SUGGEST_LEGEND = {}; 170 ZmAttendeePicker.SUGGEST_LEGEND[ZmCalBaseItem.PERSON] = ZmMsg.suggestedAttendees; 171 ZmAttendeePicker.SUGGEST_LEGEND[ZmCalBaseItem.LOCATION] = ZmMsg.suggestedLocations; 172 ZmAttendeePicker.SUGGEST_LEGEND[ZmCalBaseItem.EQUIPMENT] = ZmMsg.suggestedResources; 173 174 ZmAttendeePicker.BOTTOM_LEGEND = {}; 175 ZmAttendeePicker.BOTTOM_LEGEND[ZmCalBaseItem.PERSON] = ZmMsg.apptAttendees; 176 ZmAttendeePicker.BOTTOM_LEGEND[ZmCalBaseItem.LOCATION] = ZmMsg.apptLocations; 177 ZmAttendeePicker.BOTTOM_LEGEND[ZmCalBaseItem.EQUIPMENT] = ZmMsg.apptEquipment; 178 179 // images for the bottom fieldset legend 180 ZmAttendeePicker.ICON = {}; 181 ZmAttendeePicker.ICON[ZmCalBaseItem.PERSON] = appContextPath+"/img/hiRes/calendar/ApptMeeting.gif"; 182 ZmAttendeePicker.ICON[ZmCalBaseItem.LOCATION] = appContextPath+"/img/hiRes/calendar/Location.gif"; 183 ZmAttendeePicker.ICON[ZmCalBaseItem.EQUIPMENT] = appContextPath+"/img/hiRes/calendar/Resource.gif"; 184 185 ZmAttendeePicker.CHOOSER_HEIGHT = 300; 186 187 188 ZmAttendeePicker.prototype = new DwtDialog; 189 ZmAttendeePicker.prototype.constructor = ZmAttendeePicker; 190 191 ZmAttendeePicker.prototype.toString = 192 function() { 193 return "ZmAttendeePicker"; 194 }; 195 196 /** 197 * Done choosing addresses, add them to the appt compose view. 198 * 199 * @private 200 */ 201 ZmAttendeePicker.prototype._okButtonListener = 202 function(ev) { 203 var data = this._chooser.getItems(); 204 DwtDialog.prototype._buttonListener.call(this, ev, [data]); 205 }; 206 207 /** 208 * Call custom popdown method. 209 * 210 * @private 211 */ 212 ZmAttendeePicker.prototype._cancelButtonListener = 213 function(ev) { 214 DwtDialog.prototype._buttonListener.call(this, ev); 215 this.popdown(); 216 }; 217 218 ZmAttendeePicker.prototype.showSuggestedItems = 219 function(items) { 220 this.popup(true); 221 this._fillFreeBusy(items, AjxCallback.simpleClosure(function(items) { 222 this._chooser.setItems(items); 223 }, this)); 224 }; 225 226 ZmAttendeePicker.prototype.setLabel = 227 function(title) { 228 this.setTitle(title); 229 var sourceTitle = document.getElementById(this._searchTableId + '_legend'); 230 if(sourceTitle) sourceTitle.innerHTML = title; 231 }; 232 233 ZmAttendeePicker.prototype.popup = 234 function(showSuggestions) { 235 236 this.setLabel(showSuggestions ? ZmAttendeePicker.SUGGEST_LEGEND[this.type] : ZmAttendeePicker.TOP_LEGEND[this.type]); 237 238 DwtDialog.prototype.popup.call(this); 239 240 // Update FB status if the time is changed 241 this._setAttendees(); 242 243 if (this.type == ZmCalBaseItem.EQUIPMENT && this._dateInfo.isTimeModified) { 244 this.refreshResourcesFBStatus(); 245 this._dateInfo.isTimeModified = false; 246 } 247 248 249 }; 250 251 ZmAttendeePicker.prototype.refreshResourcesFBStatus = 252 function() { 253 var items = this._chooser.getItems(); 254 this._fillFreeBusy(items, AjxCallback.simpleClosure(function(items) { 255 this._chooser.setItems(items); 256 }, this)); 257 }; 258 259 ZmAttendeePicker.prototype.initialize = 260 function(appt, mode, isDirty, apptComposeMode) { 261 this._appt = appt; 262 this._isDirty = isDirty; 263 this._isForward = (apptComposeMode == ZmApptComposeView.FORWARD); 264 this._isProposeTime = (apptComposeMode == ZmApptComposeView.PROPOSE_TIME); 265 this._list.removeAll(); 266 267 if (this._rendered) { 268 this._chooser.reset(); 269 } else { 270 this._loadSettings(); 271 this._createPageHtml(); 272 this._addDwtObjects(); 273 this._rendered = true; 274 } 275 if (appCtxt.isOffline && this.type == ZmCalBaseItem.PERSON) { 276 this.setSelectVisibility(); 277 } 278 this._resetSelectDiv(); 279 280 // init listeners 281 this.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._okButtonListener)); 282 this.setButtonListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, this._cancelButtonListener)); 283 }; 284 285 ZmAttendeePicker.prototype.resize = 286 function() { 287 if (!this._rendered) { return; } 288 this._chooser.resize(Dwt.DEFAULT, ZmAttendeePicker.CHOOSER_HEIGHT); 289 }; 290 291 ZmAttendeePicker.prototype.cleanup = 292 function() { 293 this._chooser.reset(); 294 295 if (this._prevButton && this._nextButton) { 296 this._prevButton.setEnabled(false); 297 this._nextButton.setEnabled(false); 298 } 299 this._isClean = true; 300 this._offset = 0; 301 302 for (var i in this._searchFieldIds) { 303 var id = this._searchFieldIds[i]; 304 var el = document.getElementById(id); 305 if (el && el.value) { 306 el.value = ""; 307 } 308 } 309 }; 310 311 ZmAttendeePicker.prototype.isValid = 312 function() { 313 return true; 314 }; 315 316 /** 317 * Enables/disables multiple locations. 318 * 319 * @param {Boolean} enable if <code>true</code>, allow multiple locations 320 */ 321 ZmAttendeePicker.prototype.enableMultipleLocations = 322 function(enable) { 323 if (this._multLocsCheckboxId) { 324 var cb = document.getElementById(this._multLocsCheckboxId); 325 if (cb.checked != enable) { 326 cb.checked = enable; 327 this._chooser.setSelectStyle(cb.checked ? DwtChooser.MULTI_SELECT : DwtChooser.SINGLE_SELECT, true); 328 this.resize(); // force resize to adjust chooser layout 329 } 330 } 331 }; 332 333 ZmAttendeePicker.prototype._loadSettings = function(){ 334 335 if (!appCtxt.get(ZmSetting.CAL_SHOW_RESOURCE_TABS)) { 336 ZmAttendeePicker.TOP_LEGEND[ZmCalBaseItem.PERSON] = ZmMsg.findAttendeesRooms; 337 ZmAttendeePicker.BOTTOM_LEGEND[ZmCalBaseItem.PERSON] = ZmMsg.apptAttendeesRooms; 338 } 339 340 if (this.type === ZmCalBaseItem.LOCATION) { 341 var fields_disabled = appCtxt.get(ZmSetting.CAL_LOCATION_FIELDS_DISABLED); 342 if (fields_disabled) { 343 fields_disabled = fields_disabled.split(","); 344 var fields_disabled_mapping = { 345 CAPACITY : ZmAttendeePicker["SF_CAPACITY"], 346 DESCRIPTION : ZmAttendeePicker["SF_DESCRIPTION"], 347 SITE : ZmAttendeePicker["SF_SITE"], 348 BUILDING : ZmAttendeePicker["SF_BUILDING"], 349 FLOOR : ZmAttendeePicker["SF_FLOOR"] 350 }; 351 ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] = []; 352 //hash map of disabled fields 353 var isFieldDisabled = {}; 354 for ( var i = 0; i < fields_disabled.length; i++ ) { 355 if ( fields_disabled_mapping.hasOwnProperty(fields_disabled[i]) ) { 356 isFieldDisabled[ fields_disabled_mapping[fields_disabled[i]] ] = true; 357 } 358 } 359 var fields = ZmAttendeePicker.SEARCH_FIELDS[this.type]; 360 for ( var i = 0; i < fields.length; i++ ) { 361 if(!isFieldDisabled[fields[i]]) { 362 ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type].push(fields[i]); 363 } 364 } 365 } 366 } 367 }; 368 369 ZmAttendeePicker.prototype._createPageHtml = 370 function() { 371 this._searchTableId = Dwt.getNextId(); 372 373 this._chooserSourceListViewDivId = Dwt.getNextId(); 374 this._chooserButtonsDivId = Dwt.getNextId(); 375 this._chooserTargetListViewDivId = Dwt.getNextId(); 376 377 var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type]; 378 for (var i = 0; i < fields.length; i++) { 379 this._searchFieldIds[fields[i]] = Dwt.getNextId(); 380 } 381 382 var html = []; 383 var i = 0; 384 385 html[i++] = "<fieldset"; 386 if (AjxEnv.isMozilla) { 387 html[i++] = " style='border:1px dotted #555'"; 388 } 389 html[i++] = "><legend style='color:#555555' id='" + this._searchTableId + "_legend'>"; 390 html[i++] = ZmAttendeePicker.TOP_LEGEND[this.type]; 391 html[i++] = "</legend>"; 392 393 html[i++] = "<div style='margin-top:10px' id='"; 394 html[i++] = this._searchTableId; 395 html[i++] = "'>"; 396 397 html[i++] = "<table class='ZPropertySheet' cellspacing='6'><tr>"; 398 399 for (var j = 0; j < fields.length; j++) { 400 var isEven = ((j % 2) == 0); 401 if (isEven) { 402 html[i++] = "<tr>"; 403 } 404 var sf = fields[j]; 405 var addButton = (j == 1 || (this.type == ZmCalBaseItem.LOCATION && j === 0 && j === fields.length-1 ) ); 406 var addMultLocsCheckbox = (this.type == ZmCalBaseItem.LOCATION && j == fields.length - 1); 407 i = this._getSearchFieldHtml(sf, html, i, addButton, addMultLocsCheckbox); 408 if (!isEven || j == fields.length - 1) { 409 this._prevButtonId = Dwt.getNextId(); 410 this._nextButtonId = Dwt.getNextId(); 411 html[i++] = "<td> </td>"; 412 html[i++] = "<td id='"; 413 html[i++] = this._prevButtonId; 414 html[i++] = "'></td><td id='"; 415 html[i++] = this._nextButtonId; 416 html[i++] = "'></td>"; 417 html[i++] = "</tr>"; 418 } 419 } 420 421 html[i++] = "</table></div>"; 422 423 // placeholder for the chooser's source list view 424 html[i++] = "<div id='"; 425 html[i++] = this._chooserSourceListViewDivId; 426 html[i++] = "'></div>"; 427 html[i++] = "</fieldset>"; 428 429 // placeholder for the chooser's buttons 430 html[i++] = "<div id='"; 431 html[i++] = this._chooserButtonsDivId; 432 html[i++] = "'></div>"; 433 434 html[i++] = "<fieldset"; 435 if (AjxEnv.isMozilla) { 436 html[i++] = " style='border: 1px dotted #555555'"; 437 } 438 html[i++] = "><legend style='color:#555555'>"; 439 html[i++] = ZmAttendeePicker.BOTTOM_LEGEND[this.type]; 440 html[i++] = "</legend>"; 441 442 // placeholder for the chooser's target list view 443 html[i++] = "<div id='"; 444 html[i++] = this._chooserTargetListViewDivId; 445 html[i++] = "'></div>"; 446 html[i++] = "</fieldset>"; 447 448 this.setContent(html.join("")); 449 }; 450 451 ZmAttendeePicker.prototype._getSearchFieldHtml = 452 function(id, html, i, addButton, addMultLocsCheckbox) { 453 if (id == ZmAttendeePicker.SF_SOURCE) { 454 // no need for source select if not more than one choice to choose from 455 this.showSelect = false; 456 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) { 457 if (appCtxt.get(ZmSetting.GAL_ENABLED) || appCtxt.get(ZmSetting.SHARING_ENABLED)) 458 this.showSelect = true; 459 } 460 461 if (this.showSelect || appCtxt.isOffline) { 462 this._listSelectId = this._searchFieldIds[id]; 463 html[i++] = "<td class='ZmFieldLabelRight' id='"; 464 html[i++] = this._listSelectId+"_label"; 465 html[i++] = "'>"; 466 html[i++] = ZmMsg[ZmAttendeePicker.SF_LABEL[id]]; 467 html[i++] = ":</td><td id='"; 468 html[i++] = this._listSelectId; 469 html[i++] = "' width='130'></td>"; 470 } else { 471 html[i++] = "<td> </td>"; 472 } 473 } else { 474 html[i++] = "<td class='ZmFieldLabelRight'>"; 475 html[i++] = ZmMsg[ZmAttendeePicker.SF_LABEL[id]]; 476 html[i++] = ":</td><td>"; 477 html[i++] = "<input type='text' autocomplete='off' size=30 nowrap id='"; 478 html[i++] = this._searchFieldIds[id]; 479 html[i++] = "' />"; 480 html[i++] = "</td>"; 481 } 482 483 if (addButton) { 484 this._searchBtnTdId = Dwt.getNextId(); 485 html[i++] = "<td id='"; 486 html[i++] = this._searchBtnTdId; 487 html[i++] = "'></td>"; 488 } 489 if (addMultLocsCheckbox) { 490 this._multLocsCheckboxId = Dwt.getNextId(); 491 var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type]; 492 if (fields.length === 1) { 493 html[i++] = "<tr>"; 494 } 495 else if (fields.length === 2) { 496 html[i++] = "<tr>"; 497 html[i++] = "<td></td>"; 498 html[i++] = "<td></td>"; 499 html[i++] = "<td></td>"; 500 html[i++] = "<td></td>"; 501 html[i++] = "<td></td>"; 502 html[i++] = "<td></td>"; 503 } 504 if (fields.length % 2 === 1) { 505 html[i++] = "<td></td>"; 506 html[i++] = "<td></td>"; 507 html[i++] = "<td></td>"; 508 } 509 html[i++] = "<td><table><tr><td>"; 510 html[i++] = "<input type='checkbox' id='"; 511 html[i++] = this._multLocsCheckboxId; 512 html[i++] = "' /></td><td class='ZmFieldLabelLeft'><label for='"; 513 html[i++] = this._multLocsCheckboxId; 514 html[i++] = "'>"; 515 html[i++] = ZmMsg.allowMultipleLocations; 516 html[i++] = "</label></td></tr></table></td>"; 517 } 518 519 if (appCtxt.isOffline && this.type == ZmCalBaseItem.PERSON) { 520 this.setSelectVisibility(this.showSelect); 521 } 522 return i; 523 }; 524 525 ZmAttendeePicker.prototype._addDwtObjects = 526 function() { 527 // add search button 528 if (this._searchBtnTdId) { 529 var element = document.getElementById(this._searchBtnTdId); 530 var searchButton = this._searchButton = new DwtButton({parent:this}); 531 searchButton.setText(ZmMsg.search); 532 searchButton.addSelectionListener(new AjxListener(this, this._searchButtonListener)); 533 element.appendChild(searchButton.getHtmlElement()); 534 // attendees tab: search button enabled only if there is search field input 535 if (this.type == ZmCalBaseItem.PERSON) { 536 searchButton.setEnabled(false); 537 } 538 } 539 540 // add select menu for contact source if we need one 541 if (this.showSelect) { 542 var listSelect = document.getElementById(this._listSelectId); 543 this._selectDiv = new DwtSelect({parent:this}); 544 this._resetSelectDiv(); 545 listSelect.appendChild(this._selectDiv.getHtmlElement()); 546 this._selectDiv.addChangeListener(new AjxListener(this, this._searchTypeListener)); 547 } 548 549 // add paging buttons 550 if (this._prevButtonId && this._nextButtonId) { 551 var pageListener = new AjxListener(this, this._pageListener); 552 553 this._prevButton = new DwtButton({parent:this}); 554 this._prevButton.setImage("LeftArrow"); 555 this._prevButton.addSelectionListener(pageListener); 556 this._prevButton.reparentHtmlElement(this._prevButtonId); 557 this._prevButton.setEnabled(false); 558 559 this._nextButton = new DwtButton({parent:this}); 560 this._nextButton.setImage("RightArrow"); 561 this._nextButton.addSelectionListener(pageListener); 562 this._nextButton.reparentHtmlElement(this._nextButtonId); 563 this._nextButton.setEnabled(false); 564 } 565 566 var width = this.getSize().x; 567 // add chooser 568 this._chooser = new ZmApptChooser(this); 569 this._chooserWidth = width - 50; 570 this._chooser.resize(this._chooserWidth, ZmAttendeePicker.CHOOSER_HEIGHT); 571 572 var chooserSourceListViewDiv = document.getElementById(this._chooserSourceListViewDivId); 573 var sourceListView = this._chooser.getSourceListView(); 574 chooserSourceListViewDiv.appendChild(sourceListView); 575 var chooserButtonsDiv = document.getElementById(this._chooserButtonsDivId); 576 var buttons = this._chooser.getButtons(); 577 chooserButtonsDiv.appendChild(buttons); 578 var chooserTargetListViewDiv = document.getElementById(this._chooserTargetListViewDivId); 579 var targetListView = this._chooser.getTargetListView(); 580 chooserTargetListViewDiv.appendChild(targetListView); 581 582 // save search fields, and add handler for Return key to them 583 var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type]; 584 for (var i = 0; i < fields.length; i++) { 585 var sf = fields[i]; 586 var searchField = this._searchFields[sf] = document.getElementById(this._searchFieldIds[sf]); 587 if (searchField) { 588 searchField.onkeypress = AjxCallback.simpleClosure(this._handleKeyPress, this); 589 searchField.onkeyup = AjxCallback.simpleClosure(this._handleKeyUp, this); 590 } 591 } 592 593 if (this._multLocsCheckboxId) { 594 var cb = document.getElementById(this._multLocsCheckboxId); 595 cb.onclick = AjxCallback.simpleClosure(this._handleMultiLocsCheckbox, this); 596 } 597 }; 598 599 ZmAttendeePicker.prototype._addTabGroupMembers = 600 function(tabGroup) { 601 var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type]; 602 for (var i = 0; i < fields.length; i++) { 603 if (fields[i] != ZmAttendeePicker.SF_SOURCE) { 604 tabGroup.addMember(this._searchFields[fields[i]]); 605 } 606 } 607 }; 608 609 ZmAttendeePicker.prototype._searchButtonListener = 610 function(ev) { 611 this._list.removeAll(); 612 this._offset = 0; 613 this.searchCalendarResources(); 614 }; 615 616 ZmAttendeePicker.prototype._searchTypeListener = 617 function(ev) { 618 var oldValue = ev._args.oldValue; 619 var newValue = ev._args.newValue; 620 621 if (oldValue != newValue) { 622 this._searchButtonListener(); 623 } 624 }; 625 626 ZmAttendeePicker.prototype._pageListener = 627 function(ev) { 628 if (ev.item == this._prevButton) { 629 this._offset -= ZmContactsApp.SEARCHFOR_MAX; 630 this._showResults(true, true, this.getSubList()); // show cached results 631 } 632 else { 633 var lastId; 634 var lastSortVal; 635 this._offset += ZmContactsApp.SEARCHFOR_MAX; 636 var list = this.getSubList(); 637 if (!list) { 638 list = this._chooser.sourceListView.getList(); 639 var contact = (list.size() > 0) ? list.getLast() : null; 640 if (contact) { 641 lastId = contact.id; 642 lastSortVal = contact.sf; 643 } 644 this.searchCalendarResources(false, null, lastId, lastSortVal); 645 } else { 646 var more = this._list.hasMore; 647 if (!more) { 648 more = (this._offset+ZmContactsApp.SEARCHFOR_MAX) < this._list.size(); 649 } 650 this._showResults(true, more, list); // show cached results 651 } 652 } 653 }; 654 655 ZmAttendeePicker.prototype.getSubList = 656 function() { 657 var size = this._list.size(); 658 659 var end = (this._offset + ZmContactsApp.SEARCHFOR_MAX > size) 660 ? size : (this._offset + ZmContactsApp.SEARCHFOR_MAX); 661 662 return (this._offset < end) 663 ? (AjxVector.fromArray(this._list.getArray().slice(this._offset, end))) : null; 664 }; 665 666 /* 667 * Sets the target list to the current set of attendees. 668 */ 669 ZmAttendeePicker.prototype._setAttendees = 670 function() { 671 var attendees = this._attendees[this.type].getArray(); 672 if (attendees.length) { 673 if (this.type == ZmCalBaseItem.LOCATION && attendees.length > 1) { 674 this.enableMultipleLocations(true); 675 this.resize(); 676 } 677 this._chooser.setItems(attendees, DwtChooserListView.TARGET); 678 } 679 else { 680 this._chooser.reset(DwtChooserListView.TARGET); 681 } 682 }; 683 684 ZmAttendeePicker.prototype._resetSelectDiv = 685 function() { 686 if (this._selectDiv) { 687 var currAcct = this._editView.getCalendarAccount(); 688 689 this._selectDiv.clearOptions(); 690 this._selectDiv.addOption(ZmMsg.contacts, false, ZmContactsApp.SEARCHFOR_CONTACTS); 691 if (appCtxt.get(ZmSetting.SHARING_ENABLED, null, currAcct)) 692 this._selectDiv.addOption(ZmMsg.searchPersonalSharedContacts, false, ZmContactsApp.SEARCHFOR_PAS); 693 if (appCtxt.get(ZmSetting.GAL_ENABLED, null, currAcct)) 694 this._selectDiv.addOption(ZmMsg.GAL, true, ZmContactsApp.SEARCHFOR_GAL); 695 if (!appCtxt.get(ZmSetting.INITIALLY_SEARCH_GAL, null, currAcct) || !appCtxt.get(ZmSetting.GAL_ENABLED, null, currAcct)) { 696 this._selectDiv.setSelectedValue(ZmContactsApp.SEARCHFOR_CONTACTS); 697 } 698 } 699 }; 700 701 ZmAttendeePicker.prototype.setSelectVisibility = 702 function(showSelect) { 703 if(typeof(showSelect) == "undefined") { 704 showSelect = false; 705 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) { 706 if (appCtxt.get(ZmSetting.GAL_ENABLED) || appCtxt.get(ZmSetting.SHARING_ENABLED)) { 707 showSelect = true; 708 } 709 } 710 } 711 var listSelect = document.getElementById(this._listSelectId); 712 var selectLabel = document.getElementById(this._listSelectId+"_label"); 713 if(listSelect && selectLabel) { 714 Dwt.setDisplay(selectLabel, showSelect ? Dwt.DISPLAY_TABLE_CELL : Dwt.DISPLAY_NONE); 715 Dwt.setDisplay(listSelect, showSelect ? Dwt.DISPLAY_TABLE_CELL : Dwt.DISPLAY_NONE); 716 } 717 }; 718 719 720 ZmAttendeePicker.prototype._showResults = 721 function(isPagingSupported, more, list) { 722 if (this._prevButton && this._nextButton) { 723 // if offset is returned, then this account support gal paging 724 if (this._contactSource == ZmId.SEARCH_GAL && !isPagingSupported) { 725 this._prevButton.setEnabled(false); 726 this._nextButton.setEnabled(false); 727 } else { 728 this._prevButton.setEnabled(this._offset > 0); 729 this._nextButton.setEnabled(more); 730 } 731 } 732 733 var list1 = []; 734 var contactList = list ? list : []; 735 if (!(contactList instanceof Array)) { 736 contactList = contactList.getArray(); 737 } 738 739 for (var i = 0; i < contactList.length; i++) { 740 if (!contactList[i]) { continue; } 741 var contact = (contactList[i] && contactList[i].__contact) ? contactList[i].__contact : contactList[i]; 742 var emails = contact.isGal ? [contact.getEmail()] : contact.getEmails(); 743 if (emails && emails.length > 1) { 744 var workPhone = contact.getAttr(ZmContact.F_workPhone); 745 var homePhone = contact.getAttr(ZmContact.F_homePhone); 746 for (var j = 0; j < emails.length; j++) { 747 var clone = new ZmContact(null); 748 clone._fullName = contact.getFullName(); 749 clone.folderId = contact.folderId; 750 clone.setAttr(ZmContact.F_workPhone, workPhone); 751 clone.setAttr(ZmContact.F_homePhone, homePhone); 752 clone.setAttr(ZmContact.F_email, emails[j]); 753 list1.push(clone); 754 } 755 } else { 756 list1.push(contact); 757 } 758 } 759 760 this._fillFreeBusy(list1, AjxCallback.simpleClosure(function(list1) { this._chooser.setItems(list1); }, this)); 761 this._chooser.sourceListView.focus(); 762 }; 763 764 ZmAttendeePicker.prototype.searchCalendarResources = 765 function(defaultSearch, sortBy, lastId, lastSortVal) { 766 var currAcct = this._editView.getCalendarAccount(); 767 var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type]; 768 var conds = []; 769 var value = (this.type == ZmCalBaseItem.LOCATION) ? "Location" : "Equipment"; 770 conds.push({attr: "zimbraCalResType", op: "eq", value: value}); 771 var gotValue = false; 772 for (var i = 0; i < fields.length; i++) { 773 var sf = fields[i]; 774 var searchField = document.getElementById(this._searchFieldIds[sf]); 775 value = AjxStringUtil.trim(searchField.value); 776 if (value.length) { 777 gotValue = true; 778 var attr = ZmAttendeePicker.SF_ATTR[sf]; 779 var op = ZmAttendeePicker.SF_OP[sf] ? ZmAttendeePicker.SF_OP[sf] : "has"; 780 conds.push({attr: attr, op: op, value: value}); 781 } 782 } 783 var params = { 784 sortBy: sortBy, 785 offset: this._offset, 786 limit: ZmContactsApp.SEARCHFOR_MAX, 787 conds: conds, 788 attrs: ZmAttendeePicker.ATTRS[this.type], 789 lastId: lastId, 790 lastSortVal: lastSortVal, 791 accountName: appCtxt.isOffline ? currAcct.name : null 792 }; 793 var search = new ZmSearch(params); 794 search.execute({callback: new AjxCallback(this, this._handleResponseSearchCalendarResources, [defaultSearch])}); 795 }; 796 797 ZmAttendeePicker.prototype._getTimeFrame = 798 function() { 799 var di = {}; 800 ZmApptViewHelper.getDateInfo(this._editView, di); 801 var startDate = AjxDateUtil.simpleParseDateStr(di.startDate); 802 var endDate; 803 if (di.isAllDay) { 804 startDate.setHours(0, 0, 0, 0); 805 endDate = AjxDateUtil.simpleParseDateStr(di.endDate); 806 endDate.setHours(23, 59, 0, 0); 807 } else { 808 endDate = AjxDateUtil.simpleParseDateStr(di.endDate); 809 startDate = this._editView._startTimeSelect.getValue(startDate); 810 endDate = this._editView._endTimeSelect.getValue(endDate); 811 } 812 813 return {start:startDate, end:endDate}; 814 }; 815 816 ZmAttendeePicker.prototype._fillFreeBusy = 817 function(items, callback) { 818 819 var currAcct = this._editView.getCalendarAccount(); 820 // Bug: 48189 Don't send GetFreeBusyRequest for non-ZCS accounts. 821 if (appCtxt.isOffline && (!currAcct.isZimbraAccount || currAcct.isMain)) { 822 if (callback) { 823 callback(items); 824 } 825 return; 826 } 827 828 var tf = this._getTimeFrame(); 829 var list = (items instanceof AjxVector) ? items.getArray() : (items instanceof Array) ? items : [items]; 830 var emails = []; 831 var itemsById = {}; 832 for (var i = list.length; --i >= 0;) { 833 var item = list[i]; 834 emails[i] = item.getEmail(); 835 836 // bug: 30824 - Don't list all addresses/aliases of a resource in 837 // GetFreeBusyRequest. One should suffice. 838 if (emails[i] instanceof Array) { 839 emails[i] = emails[i][0]; 840 } 841 842 itemsById[emails[i]] = item; 843 item.__fbStatus = { txt: ZmMsg.unknown }; 844 } 845 callback(items); 846 847 if (this._freeBusyRequest) { 848 appCtxt.getRequestMgr().cancelRequest(this._freeBusyRequest, null, true); 849 } 850 this._freeBusyRequest = this._controller.getFreeBusyInfo(tf.start.getTime(), 851 tf.end.getTime(), 852 emails.join(","), 853 new AjxCallback(this, this._handleResponseFreeBusy, [itemsById]), 854 null, 855 true); 856 }; 857 858 ZmAttendeePicker.prototype._handleResponseFreeBusy = 859 function(itemsById, result) { 860 this._freeBusyRequest = null; 861 862 var args = result.getResponse().GetFreeBusyResponse.usr; 863 for (var i = args.length; --i >= 0;) { 864 var el = args[i]; 865 var id = el.id; 866 if (!id) { 867 continue; 868 } 869 var item = itemsById[id]; 870 if (!item) { 871 continue; 872 } 873 var status = ZmMsg.free; 874 item.__fbStatus.status = 0; 875 if (el.b) { 876 status = "<b style='color: red'>" + ZmMsg.busy + "</b>"; 877 item.__fbStatus.status = 1; 878 } else if (el.u) { 879 status = "<b style='color: red'>" + ZmMsg.outOfOffice + "</b>"; 880 item.__fbStatus.status = 2; 881 } else if (el.t) { 882 status = "<b style='color: orange'>" + ZmMsg.tentative + "</b>"; 883 item.__fbStatus.status = 3; 884 } 885 item.__fbStatus.txt = status; 886 this._updateStatus(item, this._chooser.sourceListView); 887 this._updateStatus(item, this._chooser.targetListView); 888 } 889 }; 890 891 ZmAttendeePicker.prototype._updateStatus = 892 function(item, view) { 893 var id = view._getFieldId(item, "FBSTATUS"), 894 element = document.getElementById(id); 895 896 if (element) { 897 element.innerHTML = item.__fbStatus.txt; 898 } 899 }; 900 901 ZmAttendeePicker.prototype._handleResponseSearchCalendarResources = 902 function(defaultSearch, result) { 903 var resp = result.getResponse(); 904 var offset = resp.getAttribute("offset"); 905 var isPagingSupported = resp.getAttribute("paginationSupported"); 906 var more = resp.getAttribute("more"); 907 var info = resp.getAttribute("info"); 908 var expanded = info && info[0].wildcard[0].expanded == "0"; 909 910 var list = resp.getResults(ZmItem.RESOURCE).getVector(); 911 if (isPagingSupported) { 912 this._list.merge(offset, list); 913 this._list.hasMore = more; 914 } 915 916 this._showResults(isPagingSupported, more, list.getArray()); 917 }; 918 919 ZmAttendeePicker.prototype._getDefaultFocusItem = 920 function() { 921 var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type]; 922 return this._searchFields[fields[0]]; 923 }; 924 925 ZmAttendeePicker.prototype._handleKeyPress = 926 function(ev) { 927 var charCode = DwtKeyEvent.getCharCode(ev); 928 if (this._keyPressCallback && (charCode == 13 || charCode == 3)) { 929 this._keyPressCallback.run(); 930 return false; 931 } 932 return true; 933 }; 934 935 ZmAttendeePicker.prototype._handleKeyUp = 936 function(ev) { 937 var field = DwtUiEvent.getTarget(ev); 938 939 return true; 940 }; 941 942 ZmAttendeePicker.prototype._handleMultiLocsCheckbox = 943 function(ev) { 944 var cb = DwtUiEvent.getTarget(ev); 945 this._chooser.setSelectStyle(cb.checked ? DwtChooser.MULTI_SELECT : DwtChooser.SINGLE_SELECT, true); 946 947 this.resize(); // force resize to adjust chooser layout 948 }; 949 950 // ********************* 951 952 /** 953 * @class 954 * This class creates a specialized chooser for the attendee picker. 955 * 956 * @param {DwtComposite} parent the attendee tab view 957 * @param {Array} buttonInfo transfer button IDs and labels 958 * 959 * @extends DwtChooser 960 * 961 * @private 962 */ 963 ZmApptChooser = function(parent, buttonInfo) { 964 var selectStyle = (parent.type == ZmCalBaseItem.LOCATION) ? DwtChooser.SINGLE_SELECT : null; 965 DwtChooser.call(this, {parent: parent, buttonInfo: buttonInfo, layoutStyle: DwtChooser.VERT_STYLE, 966 mode: DwtChooser.MODE_MOVE, selectStyle: selectStyle, allButtons: true}); 967 }; 968 969 ZmApptChooser.prototype = new DwtChooser; 970 ZmApptChooser.prototype.constructor = ZmApptChooser; 971 972 ZmApptChooser.prototype.toString = 973 function() { 974 return "ZmApptChooser"; 975 }; 976 977 // overload to handle contact groups - see bug 28398 978 ZmApptChooser.prototype.addItems = 979 function(items, view, skipNotify, id) { 980 var newList; 981 982 if (view == DwtChooserListView.TARGET) { 983 newList = []; 984 var list = (items instanceof AjxVector) ? items.getArray() : (items instanceof Array) ? items : [items]; 985 986 for (var i = 0; i < list.length; i++) { 987 var item = list[i]; 988 if (item instanceof ZmContact && item.isGroup()) { 989 var addrs = item.getGroupMembers().good.getArray(); 990 for (var j = 0; j < addrs.length; j++) { 991 var contact = new ZmContact(null); 992 contact.initFromEmail(addrs[j]); 993 newList.push(contact); 994 } 995 } else { 996 newList.push(item); 997 } 998 } 999 } else { 1000 newList = items; 1001 } 1002 if(this.parent.type == ZmCalBaseItem.LOCATION && newList.length <= 0) { 1003 this.sourceListView.setUI(null, false); 1004 } 1005 DwtChooser.prototype.addItems.call(this, newList, view, skipNotify, id); 1006 }; 1007 1008 ZmApptChooser.prototype._createSourceListView = 1009 function() { 1010 return new ZmApptChooserListView(this, DwtChooserListView.SOURCE, this.parent.type); 1011 }; 1012 1013 ZmApptChooser.prototype._createTargetListView = 1014 function() { 1015 return new ZmApptChooserListView(this, DwtChooserListView.TARGET, this.parent.type); 1016 }; 1017 1018 ZmApptChooser.prototype._notify = 1019 function(event, details) { 1020 details.type = this.parent.type; 1021 DwtChooser.prototype._notify.call(this, event, details); 1022 }; 1023 1024 /** 1025 * The item is a {@link ZmContact} or {@link ZmResource}. Its address is used for comparison. 1026 * 1027 * @param {ZmContact} item the ZmContact or ZmResource 1028 * @param {AjxVector} list list to check against 1029 * 1030 * @private 1031 */ 1032 ZmApptChooser.prototype._isDuplicate = 1033 function(item, list) { 1034 return list.containsLike(item, item.getEmail); 1035 }; 1036 1037 ZmApptChooser.prototype._reset = 1038 function(view) { 1039 if (appCtxt.isOffline && appCtxt.accountList.size() > 1 && !view) { 1040 this.parent._resetSelectDiv(); 1041 } 1042 DwtChooser.prototype._reset.apply(this, arguments); 1043 }; 1044 1045 /** 1046 * Removes items from target list, paying attention to current mode. Also handles button state. 1047 * 1048 * @param {AjxVector|array|Object|hash} list a list of items or hash of lists 1049 * @param {boolean} skipNotify if <code>true</code>, do not notify listeners 1050 */ 1051 ZmApptChooser.prototype.remove = 1052 function(list, skipNotify) { 1053 list = (list instanceof AjxVector) ? list.getArray() : (list instanceof Array) ? list : [list]; 1054 if (this._mode == DwtChooser.MODE_MOVE) { 1055 for (var i = 0; i < list.length; i++) { 1056 var itemIndex = this.sourceListView.getItemIndex(list[i]); 1057 //avoid adding duplicate entries 1058 if(itemIndex == null) { 1059 var index = this._getInsertionIndex(this.sourceListView, list[i]); 1060 this.sourceListView.addItem(list[i], index, true); 1061 } 1062 } 1063 this._sourceSize = list ? list.length : 0; 1064 } 1065 this.removeItems(list, DwtChooserListView.TARGET); 1066 }; 1067 1068 1069 /** 1070 * This class creates a specialized source list view for the contact chooser. The items 1071 * it manages are of type ZmContact or its subclass ZmResource. 1072 * 1073 * @param {DwtChooser} parent chooser that owns this list view 1074 * @param {constant} type list view type (source or target) 1075 * @param {constant} chooserType type of owning chooser (attendee/location/resource) 1076 * 1077 * @extends DwtChooserListView 1078 * 1079 * @private 1080 */ 1081 ZmApptChooserListView = function(parent, type, chooserType) { 1082 1083 this._chooserType = chooserType; 1084 DwtChooserListView.call(this, {parent:parent, type:type}); 1085 1086 this._notes = {}; 1087 }; 1088 1089 ZmApptChooserListView.prototype = new DwtChooserListView; 1090 ZmApptChooserListView.prototype.constructor = ZmApptChooserListView; 1091 1092 ZmApptChooserListView.prototype.toString = 1093 function() { 1094 return "ZmApptChooserListView"; 1095 }; 1096 1097 ZmApptChooserListView.prototype._getHeaderList = 1098 function() { 1099 var headerList = []; 1100 var cols = ZmAttendeePicker.COLS[this._chooserType]; 1101 if ( ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this._chooserType] && ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this._chooserType].length === 1 ){ 1102 var auto_width = true; 1103 } 1104 for (var i = 0; i < cols.length; i++) { 1105 var id = cols[i]; 1106 var text = ZmMsg[ZmAttendeePicker.COL_LABEL[id]]; 1107 var image = ZmAttendeePicker.COL_IMAGE[id]; 1108 var width = ( auto_width ) ? null : ZmAttendeePicker.COL_WIDTH[id]; 1109 headerList.push(new DwtListHeaderItem({field:id, text:text, icon:image, width:width, 1110 resizeable:(id == ZmItem.F_NAME)})); 1111 } 1112 1113 return headerList; 1114 }; 1115 1116 ZmApptChooserListView.prototype._getCellId = 1117 function(item, field) { 1118 return field == "FBSTATUS" ? this._getFieldId(item, field) : null; 1119 }; 1120 1121 ZmApptChooserListView.prototype._getCellContents = 1122 function(html, idx, item, field, colIdx, params) { 1123 if (field != ZmItem.F_NOTES) { 1124 html[idx++] = " "; 1125 } 1126 if (field == ZmItem.F_FOLDER) { 1127 var name = ""; 1128 if (item.isGal) { 1129 name = ZmMsg.GAL; 1130 } else { 1131 var folder = appCtxt.getById(item.folderId); 1132 name = folder ? folder.name : ""; 1133 } 1134 html[idx++] = AjxStringUtil.htmlEncode(name); 1135 } else if (field == ZmItem.F_NAME) { 1136 var name = (this._chooserType == ZmCalBaseItem.PERSON) ? item.getFullName() : item.getAttr(ZmResource.F_name); 1137 if (this._chooserType != ZmCalBaseItem.PERSON && item instanceof ZmContact) { 1138 name = item.getFullName() || item.getAttr(ZmResource.F_locationName); 1139 } 1140 html[idx++] = AjxStringUtil.htmlEncode(name); 1141 } else if (field == ZmItem.F_EMAIL) { 1142 html[idx++] = AjxStringUtil.htmlEncode(item.getEmail()); 1143 } else if (field == ZmItem.F_WORK_PHONE) { 1144 html[idx++] = AjxStringUtil.htmlEncode(item.getAttr(ZmContact.F_workPhone)); 1145 } else if (field == ZmItem.F_HOME_PHONE) { 1146 html[idx++] = AjxStringUtil.htmlEncode(item.getAttr(ZmContact.F_homePhone)); 1147 } else if (field == ZmItem.F_LOCATION) { 1148 html[idx++] = AjxStringUtil.htmlEncode(item.getAttr(ZmResource.F_locationName) || item.getFullName()); 1149 } else if (field == ZmItem.F_CONTACT) { 1150 html[idx++] = AjxStringUtil.htmlEncode(item.getAttr(ZmResource.F_contactMail)); 1151 } else if (field == ZmItem.F_CAPACITY) { 1152 html[idx++] = AjxStringUtil.htmlEncode(item.getAttr(ZmResource.F_capacity)); 1153 } else if (field == ZmItem.F_NOTES) { 1154 var notes = item.getAttr(ZmContact.F_description); 1155 if (notes) { 1156 var notesId = this._getFieldId(item, field); 1157 this._notes[notesId] = notes; 1158 html[idx++] = AjxImg.getImageHtml("Page", null, ["id='", notesId, "'"].join("")); 1159 } 1160 } else if (field == "FBSTATUS" && item.__fbStatus) { 1161 html[idx++] = item.__fbStatus.txt; 1162 } 1163 return idx; 1164 }; 1165 1166 ZmApptChooserListView.prototype._mouseOverAction = 1167 function(ev, div) { 1168 DwtListView.prototype._mouseOverAction.call(this, ev, div); 1169 var id = ev.target.id || div.id; 1170 if (!id) { return true; } 1171 1172 // check if we're hovering over a column header 1173 var type = Dwt.getAttr(div, "_type"); 1174 if (type && type == DwtListView.TYPE_HEADER_ITEM) { 1175 var hdr = this.getItemFromElement(div); 1176 if (hdr) { 1177 if (hdr._field == ZmItem.F_NOTES) { 1178 this.setToolTipContent(ZmMsg.notes); 1179 } 1180 } 1181 } else { 1182 var note = this._notes[id]; 1183 if (!note) { 1184 var item = this.getItemFromElement(div); 1185 if (item) { 1186 var notesId = this._getFieldId(item, ZmItem.F_NOTES); 1187 note = this._notes[notesId]; 1188 } 1189 } 1190 if (note) { 1191 this.setToolTipContent(AjxStringUtil.htmlEncode(note)); 1192 } 1193 } 1194 1195 return true; 1196 }; 1197