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