1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2010, 2011, 2012, 2013, 2014, 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, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Subclass of ZmAutocompleteListView so we can customize the "listview" 26 * 27 * @param params 28 */ 29 ZmPeopleAutocompleteListView = function(params) { 30 ZmAutocompleteListView.call(this, params); 31 32 this.addClassName("ZmPeopleAutocompleteListView"); 33 this.setScrollStyle(DwtControl.CLIP); 34 }; 35 36 ZmPeopleAutocompleteListView.prototype = new ZmAutocompleteListView; 37 ZmPeopleAutocompleteListView.prototype.constructor = ZmPeopleAutocompleteListView; 38 39 40 // Consts 41 42 ZmPeopleAutocompleteListView.ACTION_MESSAGE = "message"; 43 ZmPeopleAutocompleteListView.ACTION_IM = "IM"; 44 ZmPeopleAutocompleteListView.ACTION_CALL = "call"; 45 ZmPeopleAutocompleteListView.ACTION_APPT = "appt"; 46 ZmPeopleAutocompleteListView.NO_RESULTS = "no-results"; 47 48 49 // Public methods 50 51 ZmPeopleAutocompleteListView.prototype.toString = 52 function() { 53 return "ZmPeopleAutocompleteListView"; 54 }; 55 56 57 // protected methods 58 59 // Creates the list and its member elements based on the matches we have. Each match becomes a 60 // row. The first match is automatically selected. 61 ZmPeopleAutocompleteListView.prototype._set = 62 function(list) { 63 var table = this._getTable(); 64 this._matches = list; 65 66 for (var i = 0; i < list.length; i++) { 67 var match = list[i]; 68 if (match && (match.text || match.icon)) { 69 var rowId = match.id = this._getId("Row", i); 70 this._matchHash[rowId] = match; 71 } 72 73 var rowId = this._getId("Row", i); 74 var contact = match.item; 75 var data = { 76 id: this._htmlElId, 77 rowId: rowId, 78 fullName: contact.getFullName(), 79 title: contact.getAttr(ZmContact.F_jobTitle), 80 email: contact.getEmail(), 81 phone: contact.getAttr(ZmContact.F_workPhone), 82 photoFileName: contact.getAttr("photoFileName") 83 }; 84 85 // zimlet support 86 appCtxt.notifyZimlets("onPeopleSearchData", [data]); 87 88 89 var rowHtml = AjxTemplate.expand("share.Widgets#ZmPeopleAutocompleteListView", data); 90 91 var row = Dwt.parseHtmlFragment(rowHtml, true); 92 var tbody = document.createElement("tbody"); 93 tbody.appendChild(row); 94 var rowEl = table.appendChild(tbody); 95 96 if (data.email){ 97 var emailTxt = new DwtText({parent:this, parentElement:rowId + "-email", index:0, id:"NewMsg", className:"FakeAnchor"}); 98 emailTxt.isLinkText = true; 99 emailTxt.setText(data.email); 100 emailTxt.addListener(DwtEvent.ONMOUSEDOWN, new AjxListener(this, this._peopleItemListener)); 101 emailTxt.addListener(DwtEvent.ONMOUSEOVER, new AjxListener(this, this.peopleItemMouseOverListener)); 102 emailTxt.addListener(DwtEvent.ONMOUSEOUT, new AjxListener(this, this.peopleItemMouseOutListener)); 103 } 104 105 if (data.fullName){ 106 var nameTxt = new DwtText({parent:this, parentElement:rowId + "-fullName", index:0, id:"NewContact", className:"ZmPeopleSearch-fullname"}); 107 nameTxt.isLinkText = true; 108 nameTxt.setText(data.fullName); 109 nameTxt.addListener(DwtEvent.ONMOUSEDOWN, new AjxListener(this, this._peopleItemListener)); 110 nameTxt.addListener(DwtEvent.ONMOUSEOVER, new AjxListener(this, this.peopleItemMouseOverListener)); 111 nameTxt.addListener(DwtEvent.ONMOUSEOUT, new AjxListener(this, this.peopleNameMouseOutListener)); 112 } 113 Dwt.associateElementWithObject(row, contact, "contact"); 114 // ask zimlets if they want to make data into links 115 appCtxt.notifyZimlets("onPeopleSearchShow", [this, contact, rowId]); 116 117 if (i==0) 118 this._setSelected(rowId); 119 120 } 121 122 //leave this out as part of bug 50692 123 /* 124 //fetch free/busy info for all results; 125 if (list.length > 0) { 126 AjxTimedAction.scheduleAction(new AjxTimedAction(this, this._getFreeBusyInfo, [list]), 100); 127 } */ 128 }; 129 130 /* 131 Called by zimlet to clear existing text when DwtText item is created. 132 */ 133 ZmPeopleAutocompleteListView.prototype._clearText = 134 function(id) { 135 if (document.getElementById(id)!=null) 136 document.getElementById(id).innerHTML=""; 137 }; 138 139 ZmPeopleAutocompleteListView.prototype._showNoResults = 140 function() { 141 var table = this._getTable(); 142 var data = { id: this._htmlElId, rowId: ZmPeopleAutocompleteListView.NO_RESULTS }; 143 var rowHtml = AjxTemplate.expand("share.Widgets#ZmPeopleAutocompleteListView-NoResults", data); 144 var tbody = document.createElement("tbody"); 145 tbody.appendChild(Dwt.parseHtmlFragment(rowHtml, true)); 146 table.appendChild(tbody); 147 148 this.show(true); 149 }; 150 151 ZmPeopleAutocompleteListView.prototype._setSelected = 152 function(id) { 153 if (id && typeof id == "string") { 154 id = id.split("-")[0]; 155 } 156 157 if (id == ZmPeopleAutocompleteListView.NO_RESULTS || id == this.getHtmlElement().id) { return; } 158 159 if (id == ZmAutocompleteListView.NEXT || id == ZmAutocompleteListView.PREV) { 160 var table = document.getElementById(this._tableId); 161 var rows = table && table.rows; 162 id = this._getRowId(rows, id, rows.length); 163 if (!id) { return; } 164 } 165 166 var rowEl = document.getElementById(id); 167 if (rowEl) { 168 this._activeContact = Dwt.getObjectFromElement(rowEl, "contact"); 169 } 170 171 ZmAutocompleteListView.prototype._setSelected.apply(this, arguments); 172 }; 173 174 ZmPeopleAutocompleteListView.prototype._getFreeBusyInfo = 175 function(list) { 176 var emailList = []; 177 var emailHash = {}; 178 for (var i = 0; i < list.length; i++) { 179 var match = list[i]; 180 emailList.push(match.email); 181 emailHash[match.email] = match.id; 182 } 183 184 var now = new Date(); 185 var jsonObj = {GetFreeBusyRequest:{_jsns:"urn:zimbraMail"}}; 186 var request = jsonObj.GetFreeBusyRequest; 187 request.s = now.getTime(); 188 request.e = now.getTime() + (5*60*1000); // next 5 mins 189 request.name = emailList.join(","); 190 191 return appCtxt.getAppController().sendRequest({ 192 jsonObj: jsonObj, 193 asyncMode: true, 194 callback: (new AjxCallback(this, this._handleFreeBusyResponse, [emailHash])), 195 noBusyOverlay: true 196 }); 197 }; 198 199 ZmPeopleAutocompleteListView.prototype._handleFreeBusyResponse = 200 function(emailHash, result) { 201 if (!this.getVisible()) { return; } 202 203 var fb = result.getResponse().GetFreeBusyResponse.usr; 204 for (var i = 0; i < fb.length; i++) { 205 var id = fb[i].id; 206 var el = id && (document.getElementById(emailHash[id] + "-freebusy")); 207 var td = document.createElement("td"); 208 td.innerHTML="- "; 209 td.className="ZmPeopleSearch-busy"; 210 el.parentNode.insertBefore(td, el); 211 var text = new DwtText({parent:this, parentElement:el, index:1, id:"NewAppt", className:"FakeAnchor"}); 212 text.isLinkText = true; 213 text.addListener(DwtEvent.ONMOUSEDOWN, new AjxListener(this, this._peopleItemListener)); 214 text.addListener(DwtEvent.ONMOUSEOVER, new AjxListener(this, this.peopleItemMouseOverListener)); 215 text.addListener(DwtEvent.ONMOUSEOUT, new AjxListener(this, this.peopleItemMouseOutListener)); 216 217 if (el && fb[i].b) { 218 text.setText("Busy"); 219 }else if(el) { 220 text.setText("Available"); 221 } 222 } 223 }; 224 225 ZmPeopleAutocompleteListView.prototype._removeAll = 226 function() { 227 var table = this._getTable(); 228 for (var i = table.rows.length - 1; i >= 0; i--) { 229 var row = table.rows[i]; 230 var contact = Dwt.getObjectFromElement(row, "contact"); 231 if (contact) { 232 Dwt.disassociateElementFromObject(row, contact, "contact"); 233 } 234 } 235 236 this._activeContact = null; 237 238 ZmAutocompleteListView.prototype._removeAll.apply(this, arguments); 239 }; 240 241 242 ZmPeopleAutocompleteListView.prototype._listSelectionListener = 243 function(ev) { 244 }; 245 246 ZmPeopleAutocompleteListView.prototype._peopleItemListener = 247 function(ev){ 248 if (!this._activeContact) { 249 return; 250 } 251 252 var target = DwtUiEvent.getTargetWithProp(ev, "id"); 253 var action = ""; 254 if (target && target.id) 255 action = target.id.split("_")[0]; //ids are inserted by DwtText, clean up as necessary 256 257 switch (action){ 258 case "NewMsg": 259 var params = {action:ZmOperation.NEW_MESSAGE, toOverride: new AjxEmailAddress(this._activeContact.getEmail(), 260 AjxEmailAddress.TO, this._activeContact.getFullName())}; 261 AjxDispatcher.run("Compose", params); 262 break; 263 264 case "NewAppt": 265 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]); 266 var cc = AjxDispatcher.run("GetCalController"); 267 var appt = cc.newApptObject((new Date())); 268 appt.setAttendees([this._activeContact.getEmail()], ZmCalBaseItem.PERSON); 269 cc.newAppointment(appt); 270 break; 271 272 case "NewContact": 273 AjxDispatcher.require(["ContactsCore", "Contacts"]); 274 var cc = AjxDispatcher.run("GetContactListController"); 275 var list = new ZmContactList((new ZmSearch()), true); 276 list.add(this._activeContact); 277 cc.show(list, true); 278 break; 279 } 280 281 this.show(false); 282 283 }; 284 285 ZmPeopleAutocompleteListView.prototype.peopleItemMouseOverListener = 286 function(ev){ 287 var target = DwtUiEvent.getTargetWithProp(ev, "id"); 288 target.className="ZmPeopleSearchText-hover"; 289 }; 290 291 ZmPeopleAutocompleteListView.prototype.peopleItemMouseOutListener = 292 function(ev){ 293 var target = DwtUiEvent.getTargetWithProp(ev, "id"); 294 target.className="FakeAnchor"; 295 }; 296 297 ZmPeopleAutocompleteListView.prototype.peopleNameMouseOutListener = 298 function(ev){ 299 var target = DwtUiEvent.getTargetWithProp(ev, "id"); 300 target.className="ZmPeopleSearch-fullname"; 301 }; 302 303 304 ZmPeopleAutocompleteListView._outsideMouseDownListener = 305 function(ev) { 306 var curList = ZmAutocompleteListView._activeAcList; 307 if (curList) { 308 var obj = DwtControl.getTargetControl(ev); 309 if (obj && obj.parent && obj.parent == curList._toolbar) { 310 return; 311 } 312 } 313 314 ZmAutocompleteListView._outsideMouseDownListener(ev); 315 ZmPeopleAutocompleteListView.prototype._listSelectionListener(ev); 316 317 318 }; 319 320 ZmPeopleAutocompleteListView.prototype._addMouseDownListener = 321 function() { 322 DwtEventManager.addListener(DwtEvent.ONMOUSEDOWN, ZmPeopleAutocompleteListView._outsideMouseDownListener); 323 this.shell._setEventHdlrs([DwtEvent.ONMOUSEDOWN]); 324 this.shell.addListener(DwtEvent.ONMOUSEDOWN, this._outsideListener); 325 }; 326 327 ZmPeopleAutocompleteListView.prototype._removeMouseDownListener = 328 function() { 329 DwtEventManager.removeListener(DwtEvent.ONMOUSEDOWN, ZmPeopleAutocompleteListView._outsideMouseDownListener); 330 this.shell._setEventHdlrs([DwtEvent.ONMOUSEDOWN], true); 331 this.shell.removeListener(DwtEvent.ONMOUSEDOWN, this._outsideListener); 332 }; 333 334 /* 335 Display message to user that more results are available than fit in the current display 336 @param {int} availHeight available height of display 337 */ 338 ZmPeopleAutocompleteListView.prototype._showMoreResultsText = 339 function (availHeight) { 340 var rowNum = this._getNumberofAllowedRows(availHeight); 341 var textPos = rowNum > 1 ? rowNum-1 : 0; 342 var rowEl = this._getTable().rows[textPos]; 343 var rowCell = Dwt.parseHtmlFragment(AjxTemplate.expand("share.Widgets#ZmPeopleAutocompleteListView-MoreResults"), true); 344 rowEl.parentNode.insertBefore(rowCell, rowEl); 345 //remove rows below text so they are not displayed 346 this._removeRows(rowNum); 347 }; 348 349 /* remove rows from bottom to index number */ 350 ZmPeopleAutocompleteListView.prototype._removeRows = 351 function(idx) { 352 this._matches = null; 353 var table = this._getTable(); 354 for (var i = table.rows.length - 1; i >= 0 && i >= idx; i--) { 355 var row = table.rows[i]; 356 if (row != this._waitingRow) { 357 table.deleteRow(i); 358 } 359 } 360 }; 361 362 /* 363 Get the number of rows within the available height 364 @param {int} availHeight available height for display 365 @return {int} return the number of rows 366 */ 367 ZmPeopleAutocompleteListView.prototype._getNumberofAllowedRows = 368 function(availHeight) { 369 var rowCount = 0; 370 var totalHeight = 0; 371 for(var i = 0; i< this._getTable().rows.length; i++){ 372 var row = this._getTable().rows[i]; 373 totalHeight += Dwt.getSize(row).y; 374 if (totalHeight < availHeight){ 375 rowCount++; 376 } else { 377 break; 378 } 379 } 380 381 return rowCount; 382 383 };