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 * Creates a list view for time suggestions 26 * @constructor 27 * @class 28 * 29 * @author Sathishkumar Sugumaran 30 * 31 * @param parent [ZmScheduleAssistantView] the smart scheduler view 32 * @param controller [ZmApptComposeController] the appt compose controller 33 * @param apptEditView [ZmApptEditView] the appt edit view 34 */ 35 ZmTimeSuggestionView = function(parent, controller, apptEditView) { 36 ZmSuggestionsView.call(this, parent, controller, apptEditView, ZmId.VIEW_SUGGEST_TIME_PANE, true); 37 this._sectionHeaderHtml = {}; 38 } 39 ZmTimeSuggestionView.prototype = new ZmSuggestionsView; 40 ZmTimeSuggestionView.prototype.constructor = ZmTimeSuggestionView; 41 42 ZmTimeSuggestionView.prototype.toString = 43 function() { 44 return "ZmTimeSuggestionView"; 45 } 46 47 ZmTimeSuggestionView._VALUE = 'value'; 48 ZmTimeSuggestionView._ITEM_INFO = 'iteminfo'; 49 ZmTimeSuggestionView.SHOW_MORE_VALUE = '-1'; 50 ZmTimeSuggestionView.F_LABEL = 'ts'; 51 ZmTimeSuggestionView.COL_NAME = "t"; 52 53 ZmTimeSuggestionView.prototype.set = 54 function(params) { 55 this._totalUsers = params.totalUsers; 56 this._totalLocations = params.totalLocations; 57 this._duration = params.duration; 58 this._startDate = params.timeFrame.start; 59 60 ZmSuggestionsView.prototype.set.call(this, params); 61 }; 62 63 ZmTimeSuggestionView.prototype._createItemHtml = 64 function (item) { 65 var id = this.associateItemWithElement(item, null, null, null); 66 67 var attendeeImage = "AttendeeOrange"; 68 var locationImage = "LocationRed"; 69 70 if(item.availableUsers == this._totalUsers) attendeeImage = "AttendeeGreen"; 71 72 if(item.availableUsers < Math.ceil(this._totalUsers/2)) attendeeImage = "AttendeeRed"; 73 if(item.availableLocations >0) locationImage = "LocationGreen"; 74 75 var params = { 76 id: id, 77 item: item, 78 timeLabel: AjxDateFormat.getTimeInstance(AjxDateFormat.SHORT).format(new Date(item.startTime)), 79 locationCountStr: item.availableLocations, 80 attendeeImage: attendeeImage, 81 locationImage: locationImage, 82 totalUsers: this._totalUsers, 83 totalLocations: this._totalLocations 84 }; 85 return AjxTemplate.expand("calendar.Appointment#TimeSuggestion", params); 86 }; 87 88 ZmTimeSuggestionView.prototype._getHeaderList = 89 function() { 90 this._headerItem = (new DwtListHeaderItem({field:ZmTimeSuggestionView.COL_NAME, text:' '})); 91 return [ 92 this._headerItem 93 ]; 94 }; 95 96 ZmTimeSuggestionView.prototype._itemSelected = 97 function(itemDiv, ev) { 98 ZmListView.prototype._itemSelected.call(this, itemDiv, ev); 99 100 var item = this.getItemFromElement(itemDiv); 101 if(item) { 102 this._editView.setDate(new Date(item.startTime), new Date(item.endTime)); 103 //user clicked the link directly 104 if (ev.target && (ev.target.className == "FakeAnchor" || ev.target.className == "ImgLocationGreen" || ev.target.className == "ImgLocationRed")) { 105 var menu = this._createLocationsMenu(item); 106 menu.popup(0, ev.docX, ev.docY); 107 } 108 } 109 }; 110 111 ZmTimeSuggestionView.prototype.getToolTipContent = 112 function(ev) { 113 var div = this.getTargetItemDiv(ev); 114 if (!div) { return; } 115 var id = ev.target.id || div.id; 116 if (!id) { return ""; } 117 118 var tooltip; 119 var item = this.getItemFromElement(div); 120 if(item) { 121 var params = {item:item, ev:ev, div:div}; 122 tooltip = this._getToolTip(params); 123 } 124 return tooltip; 125 }; 126 127 ZmTimeSuggestionView.prototype._getToolTip = 128 function(params) { 129 var tooltip, target = params.ev.target, item = params.item; 130 131 if(!item) return; 132 133 //show all unavailable attendees on tooltip 134 if(item.availableUsers < this._totalUsers) { 135 136 //get unavailable attendees from available & total attendees list 137 var freeUsers = [], busyUsers = [], attendee; 138 for (var i = item.attendees.length; --i >=0;) { 139 attendee = this._items[item.attendees[i]]; 140 freeUsers[attendee] = true; 141 } 142 143 var attendees = this._editView.getAttendees(ZmCalBaseItem.PERSON).getArray(); 144 var attEmail; 145 146 var organizer = this._editView.getOrganizer(); 147 var orgEmail = organizer.getEmail(); 148 if (orgEmail instanceof Array) { 149 orgEmail = orgEmail[0]; 150 } 151 if(!freeUsers[orgEmail]) { 152 busyUsers.push(organizer.getAttendeeText()); 153 } 154 155 for (var i = 0; i < attendees.length; i++) { 156 attendee = attendees[i]; 157 attEmail = attendees[i].getEmail(); 158 if (attEmail instanceof Array) { 159 attEmail = attEmail[0]; 160 } 161 if(!freeUsers[attEmail]) { 162 busyUsers.push(attendee.getAttendeeText()); 163 } 164 } 165 166 if(busyUsers.length) tooltip = AjxTemplate.expand("calendar.Appointment#SuggestionTooltip", {attendees: busyUsers}) 167 } 168 return tooltip; 169 }; 170 171 //obsolete - will be removed as a part of clean up process 172 ZmTimeSuggestionView.prototype.switchLocationSelect = 173 function(item, id, ev) { 174 var locId = id + "_loc"; 175 176 var locationC = document.getElementById(locId); 177 if(!locationC) return; 178 179 var roomsAvailable = (item.locations.length > 0); 180 181 if(!this._locSelect && !roomsAvailable) { 182 return; 183 } 184 185 if(roomsAvailable) locationC.innerHTML = ""; 186 187 if(!this._locSelect) { 188 this._locSelect = new DwtSelect({parent:this, parentElement: locId}); 189 this._locSelect.addChangeListener(new AjxListener(this, this._locationListener)); 190 this._locSelect.dynamicButtonWidth(); 191 }else { 192 if(roomsAvailable) this._locSelect.reparentHtmlElement(locId); 193 this._locSelect.clearOptions(); 194 if(this._locSelect.itemId != id) this._restorePrevLocationInfo(); 195 } 196 197 this._locSelect.itemId = id; 198 this._locSelect.itemInfo = item; 199 200 var location, name, locationObj; 201 for (var i = item.locations.length; --i >=0;) { 202 location = this._items[item.locations[i]]; 203 locationObj = this.parent.getLocationByEmail(location); 204 name = location; 205 if(locationObj) { 206 name = locationObj.getAttr(ZmResource.F_locationName) || locationObj.getAttr(ZmResource.F_name); 207 } 208 this._locSelect.addOption(name, false, location); 209 210 if(item.locations.length - i > 20) { 211 this._locSelect.addOption(ZmMsg.showMore, false, ZmTimeSuggestionView.SHOW_MORE_VALUE); 212 break; 213 } 214 } 215 216 //user clicked the link directly 217 if (ev.target && (ev.target.className == "FakeAnchor")) { 218 this._locSelect.popup(); 219 } 220 221 this.handleLocationOverflow(); 222 }; 223 224 ZmTimeSuggestionView.prototype._createLocationsMenu = 225 function(item) { 226 var menu = this._locationsMenu = new ZmPopupMenu(this, null, null, this._controller); 227 var listener = new AjxListener(this, this._locationsMenuListener); 228 229 var location, name, locationObj; 230 for (var i = item.locations.length; --i >=0;) { 231 location = this._items[item.locations[i]]; 232 locationObj = this.parent.getLocationByEmail(location); 233 name = location; 234 if(locationObj) { 235 name = locationObj.getAttr(ZmResource.F_name) || locationObj.getAttr(ZmResource.F_locationName); 236 } 237 238 var mi = menu.createMenuItem(location, {style:DwtMenuItem.RADIO_STYLE, text: name}); 239 mi.addSelectionListener(listener); 240 mi.setData(ZmTimeSuggestionView._VALUE, location); 241 242 if(item.locations.length - i > 20) { 243 mi = menu.createMenuItem(ZmTimeSuggestionView.SHOW_MORE_VALUE, {style:DwtMenuItem.RADIO_STYLE, text: ZmMsg.showMore}); 244 mi.addSelectionListener(listener); 245 mi.setData(ZmTimeSuggestionView._VALUE, ZmTimeSuggestionView.SHOW_MORE_VALUE); 246 mi.setData(ZmTimeSuggestionView._ITEM_INFO, item); 247 break; 248 } 249 } 250 251 return menu; 252 }; 253 254 ZmTimeSuggestionView.prototype._locationsMenuListener = 255 function(ev) { 256 257 var id = ev.item.getData(ZmTimeSuggestionView._VALUE) 258 259 if(id == ZmTimeSuggestionView.SHOW_MORE_VALUE) { 260 var itemInfo = ev.item.getData(ZmTimeSuggestionView._ITEM_INFO); 261 if(itemInfo) this.showMore(itemInfo); 262 return; 263 } 264 265 var itemIndex = this._itemIndex[id]; 266 var location = this._items[itemIndex]; 267 if(location) { 268 var locationObj = this.parent.getLocationByEmail(location); 269 this._editView.updateLocation(locationObj); 270 } 271 }; 272 273 ZmTimeSuggestionView.prototype.handleLocationOverflow = 274 function() { 275 var locTxt = this._locSelect.getText(); 276 if(locTxt && locTxt.length > 15) { 277 locTxt = locTxt.substring(0, 15) + '...'; 278 this._locSelect.setText(locTxt); 279 } 280 }; 281 282 //obsolete - will be removed as a part of clean up process 283 ZmTimeSuggestionView.prototype._restorePrevLocationInfo = 284 function() { 285 var prevId = this._locSelect.itemId; 286 var prevItemDiv = document.getElementById(prevId); 287 var prevItem = prevItemDiv ? this.getItemFromElement(prevItemDiv) : null; 288 if(prevItem) { 289 var prevLoc = document.getElementById(prevId + '_loc'); 290 prevLoc.innerHTML = '<span class="FakeAnchor">' + AjxMessageFormat.format(ZmMsg.availableRoomsCount, [prevItem.availableLocations]) + '</span>'; 291 } 292 }; 293 294 ZmTimeSuggestionView.prototype._locationListener = 295 function() { 296 var id = this._locSelect.getValue(); 297 298 if(id == ZmTimeSuggestionView.SHOW_MORE_VALUE) { 299 this.showMore(this._locSelect.itemInfo); 300 return; 301 } 302 303 var itemIndex = this._itemIndex[id]; 304 var location = this._items[itemIndex]; 305 if(location) { 306 var locationObj = this.parent.getLocationByEmail(location); 307 this._editView.updateLocation(locationObj); 308 } 309 this.handleLocationOverflow(); 310 }; 311 312 ZmTimeSuggestionView.prototype.setNoAttendeesHtml = 313 function() { 314 this.removeAll(); 315 var div = document.createElement("div"); 316 div.innerHTML = AjxTemplate.expand("calendar.Appointment#TimeSuggestion-NoAttendees"); 317 this._addRow(div); 318 }; 319 320 ZmTimeSuggestionView.prototype._setNoResultsHtml = 321 function() { 322 var div = document.createElement("div"); 323 var subs = { 324 message: this._getNoResultsMessage(), 325 type: this.type, 326 id: this.getHTMLElId() 327 }; 328 div.innerHTML = AjxTemplate.expand("calendar.Appointment#TimeSuggestion-NoSuggestions", subs); 329 this._addRow(div); 330 331 //add event handlers for no results action link 332 this._searchAllId = this.getHTMLElId() + "_showall"; 333 this._searchAllLink = document.getElementById(this._searchAllId); 334 if(this._searchAllLink) { 335 this._searchAllLink._viewId = AjxCore.assignId(this); 336 Dwt.setHandler(this._searchAllLink, DwtEvent.ONCLICK, AjxCallback.simpleClosure(ZmTimeSuggestionView._onClick, this, this._searchAllLink)); 337 } 338 }; 339 340 ZmTimeSuggestionView.prototype.setShowSuggestionsHTML = 341 function(date) { 342 if(this._date && this._date == date) { 343 return; 344 } 345 this._date = date; 346 this.removeAll(); 347 var div = document.createElement("div"); 348 var params = [ 349 '<span class="FakeAnchor" id="' + this.getHTMLElId() + '_showsuggestions">', 350 '</span>', 351 date 352 ]; 353 var subs = { 354 message: AjxMessageFormat.format(ZmMsg.showSuggestionsFor, params), 355 id: this.getHTMLElId() 356 }; 357 div.innerHTML = AjxTemplate.expand("calendar.Appointment#TimeSuggestion-ShowSuggestions", subs); 358 this._addRow(div); 359 360 //add event handlers for showing link 361 this._suggestId = this.getHTMLElId() + "_showsuggestions"; 362 this._suggestLink = document.getElementById(this._suggestId); 363 if(this._suggestLink) { 364 this._suggestLink._viewId = AjxCore.assignId(this); 365 Dwt.setHandler(this._suggestLink, DwtEvent.ONCLICK, AjxCallback.simpleClosure(ZmTimeSuggestionView._onClick, this, this._suggestLink)); 366 } 367 }; 368 369 ZmTimeSuggestionView.prototype._getNoResultsMessage = 370 function() { 371 var durationStr = AjxDateUtil.computeDuration(this._duration); 372 return AjxMessageFormat.format(this._showOnlyGreenSuggestions ? ZmMsg.noGreenSuggestionsFound : ZmMsg.noSuggestionsFound, [this._startDate, durationStr]); 373 }; 374 375 ZmTimeSuggestionView.prototype.showMore = 376 function(locationInfo) { 377 378 var location, name, locationObj, items = new AjxVector(); 379 for (var i = locationInfo.locations.length; --i >=0;) { 380 location = this._items[locationInfo.locations[i]]; 381 locationObj = this.parent.getLocationByEmail(location); 382 if(locationObj) items.add(locationObj) 383 } 384 385 var attendeePicker = this._editView.getAttendeePicker(ZmCalBaseItem.LOCATION); 386 attendeePicker.showSuggestedItems(items); 387 }; 388 389 ZmTimeSuggestionView.prototype._getHeaderColor = 390 function(item) { 391 var className = (item.availableUsers == this._totalUsers) ? "GreenLight" : "OrangeLight"; 392 if(item.availableUsers < Math.ceil(this._totalUsers/2)) className = "RedLight"; 393 return className; 394 }; 395 396 ZmTimeSuggestionView.prototype._renderListSectionHdr = 397 function(hdrKey, item) { 398 if(!this._sectionHeaderHtml[hdrKey]) { 399 var htmlArr = []; 400 var idx = 0; 401 htmlArr[idx++] = "<table width=100% class='ZmTimeSuggestionView-Column "; 402 htmlArr[idx++] = this._getHeaderColor(item); 403 htmlArr[idx++] = "'><tr>"; 404 htmlArr[idx++] = "<td><div class='DwtListHeaderItem-label'>"; 405 htmlArr[idx++] = AjxMessageFormat.format(ZmMsg.availableCount, [item.availableUsers, this._totalUsers]); 406 htmlArr[idx++] = "</div></td>"; 407 htmlArr[idx++] = "</tr></table>"; 408 this._sectionHeaderHtml[hdrKey] = htmlArr.join(""); 409 } 410 411 return this._sectionHeaderHtml[hdrKey]; 412 }; 413 414 ZmTimeSuggestionView.prototype._getHeaderKey = 415 function(item) { 416 return item.availableUsers + '-' + this._totalUsers; 417 } 418 419 ZmTimeSuggestionView._onClick = 420 function(el, ev) { 421 var edv = AjxCore.objectWithId(el._viewId); 422 if (edv) { 423 edv._handleOnClick(el); 424 } 425 }; 426 427 ZmTimeSuggestionView.prototype._handleOnClick = 428 function(el) { 429 if(!el || !el.id) return; 430 // figure out which input field was clicked 431 if (el.id == this._searchAllId) { 432 this.parent.suggestAction(true, true); 433 }else if (el.id == this._suggestId) { 434 this.parent.overrideManualSuggestion(true); 435 this.parent.suggestAction(true, false); 436 } 437 };