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 };