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 * @overview 26 * 27 */ 28 29 /** 30 * Creates a new autocomplete list. 31 * @class 32 * This class shows the members of an expanded distribution list (DL). 33 * 34 * @author Conrad Damon 35 * 36 * @param {Hash} params a hash of parameters: 37 * @param {ZmAutocompleteListView} parent autocomplete list view 38 * @param {AjxCallback} selectionCallback the callback into client to notify it that selection from extended DL happened (passed from email.js, and accessed from ZmDLAutocompleteListView.prototype._doUpdate) 39 * 40 * @extends ZmAutocompleteListView 41 */ 42 ZmDLAutocompleteListView = function(params) { 43 params.isFocusable = true; 44 ZmAutocompleteListView.call(this, params); 45 this._parentAclv = params.parentAclv; 46 this._dlScrollDiv = this.getHtmlElement(); 47 this._selectionCallback = params.selectionCallback; 48 this._expandTextId = params.expandTextId; 49 Dwt.setHandler(this._dlScrollDiv, DwtEvent.ONSCROLL, ZmDLAutocompleteListView.handleDLScroll); 50 }; 51 52 ZmDLAutocompleteListView.prototype = new ZmAutocompleteListView; 53 ZmDLAutocompleteListView.prototype.constructor = ZmDLAutocompleteListView; 54 55 ZmDLAutocompleteListView.prototype.toString = 56 function() { 57 return "ZmDLAutocompleteListView"; 58 }; 59 60 61 ZmDLAutocompleteListView.prototype.getKeyMapName = function() { 62 return ZmKeyMap.MAP_DL_ADDRESS_LIST; 63 }; 64 65 ZmDLAutocompleteListView.prototype.handleKeyAction = function(actionCode, ev) { 66 DBG.println("aif", "handle shortcut: " + actionCode); 67 68 switch (actionCode) { 69 case DwtKeyMap.SELECT_NEXT: this._setSelected(ZmAutocompleteListView.NEXT); break; 70 case DwtKeyMap.SELECT_PREV: this._setSelected(ZmAutocompleteListView.PREV); break; 71 case DwtKeyMap.ENTER: this._update(); break; 72 case DwtKeyMap.CANCEL: if (this._parentAclv && this._expandTextId) { 73 this._parentAclv._setExpandText(this._expandTextId, false); 74 } 75 this._popdown(); 76 break; 77 default: return false; 78 } 79 return true; 80 }; 81 82 83 ZmDLAutocompleteListView.prototype._set = 84 function(list, contact) { 85 86 this._removeAll(); 87 this._matches = []; 88 this._addMembers(list); 89 90 // add row for selecting all at top of list 91 var dl = appCtxt.getApp(ZmApp.CONTACTS).getDL(contact.getEmail()); 92 var numMembers = dl ? dl.total : list.length; 93 var selectId = this._getId("Row", 1); 94 if (numMembers != 1) { 95 var table = this._getTable(); 96 var row = table.insertRow(0); 97 row.className = this._origClass; 98 selectId = row.id = this._selectAllRowId = this._getId("Row", "selectAll"); 99 var cell = row.insertCell(-1); 100 cell.className = "AutocompleteMatchIcon"; 101 cell.innerHTML = AjxImg.getImageHtml("Blank16"); 102 cell = row.insertCell(-1); 103 var text = numMembers ? ZmMsg.selectAllMembers : ZmMsg.noMembers; 104 cell.innerHTML = AjxMessageFormat.format(text, [numMembers]); 105 } 106 107 AjxTimedAction.scheduleAction(new AjxTimedAction(this, 108 function() { 109 this._setSelected(selectId); 110 }), 100); 111 }; 112 113 ZmDLAutocompleteListView.prototype._addMembers = 114 function(list) { 115 116 var table = this._getTable(); 117 var len = list.length; 118 for (var i = 0; i < len; i++) { 119 var match = list[i]; 120 this._matches.push(match); 121 var rowId = match.id = this._getId("Row", this._matches.length); 122 this._addRow(table, match, rowId); 123 } 124 }; 125 126 ZmDLAutocompleteListView.prototype._addRow = 127 function(table, match, rowId) { 128 129 if (match && (match.text || match.icon)) { 130 this._matchHash[rowId] = match; 131 var row = table.insertRow(-1); 132 row.className = this._origClass; 133 row.id = rowId; 134 var cell = row.insertCell(-1); 135 cell.className = "AutocompleteMatchIcon"; 136 if (match.icon) { 137 cell.innerHTML = (match.icon.indexOf('Dwt') != -1) ? ["<div class='", match.icon, "'></div>"].join("") : 138 AjxImg.getImageHtml(match.icon); 139 } else { 140 cell.innerHTML = " "; 141 } 142 cell = row.insertCell(-1); 143 cell.innerHTML = match.text || " "; 144 } 145 }; 146 147 ZmDLAutocompleteListView.prototype._update = 148 function(context, match, ev) { 149 150 if (this._selected == this._selectAllRowId) { 151 if (!this._matchHash[this._selectAllRowId]) { 152 var callback = this._handleResponseGetAllDLMembers.bind(this, ev); 153 this._dlContact.getAllDLMembers(callback); 154 } 155 } else { 156 this._doUpdate(); 157 this.reset(true); 158 } 159 }; 160 161 ZmDLAutocompleteListView.prototype._handleResponseGetAllDLMembers = 162 function(ev, result) { 163 164 var mv = this._parentAclv._matchValue; 165 var field = (mv instanceof Array) ? mv[0] : mv; 166 if (result.list && result.list.length) { 167 // see if client wants addresses joined, or one at a time 168 if (this._parentAclv._options.massDLComplete) { 169 var match = this._matchHash[this._selectAllRowId] = new ZmAutocompleteMatch(); 170 match[field] = result.list.join(this._parentAclv._separator); 171 match.multipleAddresses = true; 172 this._doUpdate(); 173 } 174 else { 175 var match = new ZmAutocompleteMatch(); 176 for (var i = 0, len = result.list.length; i < len; i++) { 177 match[field] = result.list[i]; 178 this._doUpdate(match); 179 } 180 } 181 } 182 this.reset(true); 183 }; 184 185 ZmDLAutocompleteListView.prototype._doUpdate = 186 function(match) { 187 188 var context = null; 189 // so that address will be taken from match 190 if (this._parentAclv && this._parentAclv._currentContext) { 191 context = this._parentAclv._currentContext; 192 context.address = null; 193 } 194 match = match || this._matchHash[this._selected]; 195 if (!match) { 196 return; 197 } 198 199 if (this._selectionCallback) { 200 this._selectionCallback(match.fullAddress); 201 return; 202 } 203 204 var dlBubble = document.getElementById(this._dlBubbleId); 205 if (dlBubble && dlBubble._aifId && (!context || context.element._aifId != dlBubble._aifId)) { 206 //this is the special case the DL was pre-created with the view. In this case we might have no context. 207 // Another possible bug this fixes is if the current context is not in the same input field as the DL we are selecting from. 208 var addrInputFld = DwtControl.ALL_BY_ID[dlBubble._aifId]; 209 if (addrInputFld){ 210 var bubbleParams = { 211 address: match.fullAddress, 212 match: match, 213 noFocus: false, 214 addClass: null, 215 noParse: false 216 }; 217 addrInputFld.addBubble(bubbleParams); 218 return; 219 } 220 } 221 222 this._parentAclv._update(null, match); 223 }; 224 225 ZmDLAutocompleteListView.handleDLScroll = 226 function(ev) { 227 228 var target = DwtUiEvent.getTarget(ev); 229 var view = DwtControl.findControl(target); 230 var div = view._dlScrollDiv; 231 if (div.clientHeight == div.scrollHeight) { return; } 232 var contactDL = appCtxt.getApp(ZmApp.CONTACTS).getDL(view._dlContact.getEmail()); 233 var listSize = view.getDLSize(); 234 if (contactDL && (contactDL.more || (listSize < contactDL.list.length))) { 235 var params = {scrollDiv: div, 236 rowHeight: view._rowHeight, 237 threshold: 10, 238 limit: ZmContact.DL_PAGE_SIZE, 239 listSize: listSize}; 240 var needed = ZmListView.getRowsNeeded(params); 241 DBG.println("dl", "scroll, items needed: " + needed); 242 if (needed) { 243 DBG.println("dl", "new offset: " + listSize); 244 var respCallback = ZmDLAutocompleteListView._handleResponseDLScroll.bind(null, view); 245 view._parentAclv._dataAPI.expandDL(view._dlContact, listSize, respCallback); 246 } 247 } 248 }; 249 250 ZmDLAutocompleteListView._handleResponseDLScroll = 251 function(view, matches) { 252 view._addMembers(matches); 253 }; 254 255 ZmDLAutocompleteListView.prototype.getDLSize = 256 function() { 257 return this.size() - 1; 258 }; 259 260 // optionally removes the DL address bubble 261 ZmDLAutocompleteListView.prototype.reset = 262 function(clearDL) { 263 264 if (clearDL) { 265 var dlBubble = document.getElementById(this._dlBubbleId); 266 if (dlBubble) { 267 var addrInput = DwtControl.ALL_BY_ID[dlBubble._aifId]; 268 if (addrInput && addrInput.removeBubble) { //it's not always really addrInput - from msg/conv view it's the msg or conv view, (unlike compose view where it's really address input 269 addrInput.removeBubble(this._dlBubbleId); 270 this._dlBubbleId = null; 271 } 272 } 273 } 274 ZmAutocompleteListView.prototype.reset.call(this); 275 }; 276 277 ZmDLAutocompleteListView.prototype._popup = 278 function(loc) { 279 280 if (this.getVisible()) { return; } 281 282 loc = loc || this._getDefaultLoc(); 283 var x = loc.x; 284 var windowSize = this.shell.getSize(); 285 this.setVisible(true); 286 var curSize = this.getSize(); 287 this.setVisible(false); 288 var newX = (x + curSize.x >= windowSize.x) ? windowSize.x - curSize.x : x; 289 if (newX != x) { 290 var parentSize = this._parentAclv.getSize(); 291 this._parentAclv.setLocation(windowSize.x - (curSize.x + parentSize.x + 2), Dwt.DEFAULT); 292 loc.x = newX; 293 } 294 ZmAutocompleteListView.prototype._popup.call(this, loc); 295 this.focus(); 296 }; 297 298 ZmDLAutocompleteListView.prototype._popdown = 299 function() { 300 if (this._parentAclv) { 301 this._parentAclv._curExpanded = null; 302 } 303 ZmAutocompleteListView.prototype._popdown.call(this); 304 }; 305