1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 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) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  * 
 27  * This file defines the Zimlet Handler Object.
 28  *
 29  */
 30 
 31 /**
 32  * @class
 33  *
 34  * This class provides the default implementation for Zimlet functions. A Zimlet developer may
 35  * wish to override some functions in order to provide custom functionality. All Zimlet Handler Objects should extend this base class.
 36  * <br />
 37  * <br />
 38  * <code>function com_zimbra_myZimlet_HandlerObject() { };</code>
 39  * <br />
 40  * <br />
 41  * <code>
 42  * com_zimbra_myZimlet_HandlerObject.prototype = new ZmZimletBase();
 43  * com_zimbra_myZimlet_HandlerObject.prototype.constructor = com_zimbra_myZimlet_HandlerObject;
 44  * </code>
 45  * 
 46  * @extends	ZmObjectHandler
 47  * @see		#init
 48  */
 49 ZmZimletBase = function() {
 50 	// do nothing
 51 };
 52 
 53 /**
 54  * This defines the Panel Menu.
 55  * 
 56  * @see #menuItemSelected}
 57  */
 58 ZmZimletBase.PANEL_MENU = 1;
 59 /**
 60  * This defines the Content Object Menu.
 61  * 
 62  * @see		#menuItemSelected}
 63  */
 64 ZmZimletBase.CONTENTOBJECT_MENU = 2;
 65 
 66 ZmZimletBase.PROXY = "/service/proxy?target=";
 67 
 68 ZmZimletBase.prototype = new ZmObjectHandler();
 69 
 70 /**
 71  * @private
 72  */
 73 ZmZimletBase.prototype._init =
 74 function(zimletContext, shell) {
 75 	DBG.println(AjxDebug.ZIMLET, "Creating zimlet " + zimletContext.name);
 76 	this._passRpcErrors = false;
 77 	this._zimletContext = zimletContext;
 78 	this._dwtShell = shell;
 79 	this._origIcon = this.xmlObj().icon;
 80 	this.__zimletEnabled = true;
 81 	this.name = this.xmlObj().name;
 82 
 83 	var contentObj = this.xmlObj("contentObject");
 84 	if (contentObj && contentObj.matchOn) {
 85 		var regExInfo = contentObj.matchOn.regex;
 86 		if(!regExInfo.attrs) {regExInfo.attrs = "ig";}
 87 		this.RE = new RegExp(regExInfo._content, regExInfo.attrs);
 88 		if (contentObj.type) {
 89 			this.type = contentObj.type;
 90 		}
 91 		ZmObjectHandler.prototype.init.call(this, this.type, contentObj["class"]);
 92 	}
 93 };
 94 
 95 /**
 96  * This method is called by the Zimlet framework to indicate that
 97  * the zimlet it being initialized. This method can be overridden to initialize the zimlet.
 98  * 
 99  */
100 ZmZimletBase.prototype.init = function() {};
101 
102 /**
103  * Returns a string representation of the zimlet.
104  * 
105  * @return		{string}		a string representation of the zimlet
106  */
107 ZmZimletBase.prototype.toString =
108 function() {
109 	return this.name;
110 };
111 
112 /**
113  * Gets the shell for the zimlet.
114  * 
115  * @return	{DwtShell}		the shell
116  */
117 ZmZimletBase.prototype.getShell =
118 function() {
119 	return this._dwtShell;
120 };
121 
122 /**
123  * Adds an item to the search toolbar drop-down. A listener (if specified)
124  * will be called when the item is selected.
125  * 
126  * @param	{string}	icon		the icon (style class) to use or <code>null</code> for no icon
127  * @param	{string}	label		the label for the item
128  * @param	{AjxListener}	listener		the listener or <code>null</code> for none
129  * @param	{string}	id			the unique id of the item to add
130  * @return	{ZmButtonToolBar}	<code>null</code> if item not created
131  */
132 ZmZimletBase.prototype.addSearchDomainItem =
133 function(icon, label, listener, id) {
134 	var searchToolbar = appCtxt.getSearchController().getSearchToolbar();
135 	return searchToolbar ? searchToolbar.createCustomSearchBtn(icon, label, listener, id) : null;
136 };
137 
138 /**
139  * Gets the text field value entered in the search bar.
140  * 
141  * @return	{string}		the search field value or <code>null</code> for none
142  */ 
143 ZmZimletBase.prototype.getSearchQuery =
144 function() {
145 	var searchToolbar = appCtxt.getSearchController().getSearchToolbar();
146 	return searchToolbar ? searchToolbar.getSearchFieldValue() : null;
147 };
148 
149 /**
150  * Gets the zimlet manager.
151  * 
152  * @return	{ZmZimletMgr}		the zimlet manager
153  */
154 ZmZimletBase.prototype.getZimletManager =
155 function() {
156 	return appCtxt.getZimletMgr();
157 };
158 
159 /**
160  * @private
161  */
162 ZmZimletBase.prototype.xmlObj =
163 function(key) {
164 	return !key ? this._zimletContext : this._zimletContext.getVal(key);
165 };
166 
167 /**
168  * Gets the zimlet context.
169  * 
170  * @return	{ZimletContext}	the context
171  */
172 ZmZimletBase.prototype.getZimletContext =
173 function() {
174 	return	this._zimletContext;
175 }
176 
177 /*
178  * 
179  *  Panel Item Methods
180  *  
181  */
182 
183 /**
184  * This method is called when an item is dragged on the Zimlet drop target
185  * in the panel. This method is only called for the valid types that the
186  * Zimlet accepts as defined by the <code><dragSource></code> Zimlet Definition File XML.
187  *
188  * @param	{ZmAppt|ZmConv|ZmContact|ZmFolder|ZmMailMsg|ZmTask}	zmObject		the dragged object
189  * @return	{boolean}	<code>true</code> if the drag should be allowed; otherwise, <code>false</code>
190  */
191 ZmZimletBase.prototype.doDrag =
192 function(zmObject) {
193 	return true;
194 };
195 
196 /**
197  * This method is called when an item is dropped on the Zimlet in the panel.
198  * 
199  * @param	{ZmAppt|ZmConv|ZmContact|ZmFolder|ZmMailMsg|ZmTask}	zmObject		the dropped object
200  */
201 ZmZimletBase.prototype.doDrop =
202 function(zmObject) {};
203 
204 /**
205  * @private
206  */
207 ZmZimletBase.prototype._dispatch =
208 function(handlerName) {
209 	var params = [];
210 	var obj;
211 	var url;
212 	for (var i = 1; i < arguments.length; ++i) {
213 		params[i-1] = arguments[i];
214 	}
215 	// create a canvas if so was specified
216 	var canvas;
217 	switch (handlerName) {
218 	    case "singleClicked":
219 	    case "doubleClicked":
220 		// the panel item was clicked
221 		obj = this.xmlObj("zimletPanelItem")
222 			[handlerName == "singleClicked" ? "onClick" : "onDoubleClick"];
223 		if (!obj) {
224 			break;
225 		}
226 		url = obj.actionUrl;
227 		if (url) {
228 			url = this.xmlObj().makeURL(url);
229 		}
230 		if (obj && (obj.canvas || url)) {
231 			canvas = this.makeCanvas(obj.canvas, url);
232 		}
233 		break;
234 
235 	    case "doDrop":
236 		obj = params[1]; // the dragSrc that matched
237 		if (!obj)
238 			break;
239 		if (obj.canvas) {
240 			canvas = obj.canvas[0];
241 		}
242 		url = obj.actionUrl;
243 		if (url && canvas) {
244 			// params[0] is the dropped object
245 			url = this.xmlObj().makeURL(url[0], params[0]);
246 			canvas = this.makeCanvas(canvas, url);
247 			return "";
248 		}
249 		break;
250 	}
251 	if (canvas) {
252 		params.push(canvas);
253 	}
254 	return this.xmlObj().callHandler(handlerName, params);
255 };
256 
257 /**
258  * This method gets called when a single-click is performed.
259  *
260  * @param	{Object}	canvas		the canvas
261  * @see		#doubleClicked
262  */
263 ZmZimletBase.prototype.singleClicked = function(canvas) {};
264 
265 /**
266  * This method gets called when a double-click is performed. By default, this method
267  * will create the default property editor for editing user properties.
268  * 
269  * @param	{Object}	canvas		the canvas
270  * @see		#singleClicked
271  * @see		#createPropertyEditor
272  */
273 ZmZimletBase.prototype.doubleClicked =
274 function(canvas) {
275 	this.createPropertyEditor();
276 };
277 
278 /*
279  *
280  * Application hook methods.
281  * 
282  */
283 
284 /**
285  * This method is called by the Zimlet framework when a user clicks-on a message in the mail application.
286  * 
287  * @param	{ZmMailMsg}		msg		the clicked message
288  * @param	{ZmMailMsg}		oldMsg	the previous clicked message or <code>null</code> if this is the first message clicked
289  * @param   {ZmMailMsgView} msgView the view that displays the message
290  */
291 ZmZimletBase.prototype.onMsgView = function(msg, oldMsg, msgView) {};
292 
293 /**
294  * This method is called by the Zimlet framework when a user clicks-on a message in either the message or conversation view).
295  * 
296  * @param	{ZmMailMsg}			msg			the clicked message
297  * @param	{ZmObjectManager}	objMgr		the object manager
298  */
299 ZmZimletBase.prototype.onFindMsgObjects = function(msg, objMgr) {};
300 
301 /**
302  * This method is called by the Zimlet framework when a contact is clicked-on in the contact list view.
303  * 
304  * @param	{ZmContact}		contact		the contact being viewed
305  * @param	{string}		elementId	the element Id
306  */
307 ZmZimletBase.prototype.onContactView = function(contact, elementId) {};
308 
309 /**
310  * This method is called by the Zimlet framework when a contact is edited.
311  * 
312  * @param	{ZmEditContactView}	view	the edit contact view
313  * @param	{ZmContact}		contact		the contact being edited
314  * @param	{string}		elementId	the element Id
315  */
316 ZmZimletBase.prototype.onContactEdit = function(view, contact, elementId) {};
317 
318 /**
319  * This method is called by the Zimlet framework when application toolbars are initialized.
320  * 
321  * @param	{ZmApp}				app				the application
322  * @param	{ZmButtonToolBar}	toolbar			the toolbar
323  * @param	{ZmController}		controller		the application controller
324  * @param	{string}			viewId			the view Id
325  */
326 ZmZimletBase.prototype.initializeToolbar = function(app, toolbar, controller, viewId) {};
327 
328 /**
329  * This method is called by the Zimlet framework when showing an application view.
330  * 
331  * @param	{string}		view		the name of the view
332  */
333 ZmZimletBase.prototype.onShowView = function(view) {};
334 
335 /**
336  * This method is called by the Zimlet framework when a search is performed.
337  * 
338  * @param	{string}		queryStr		the search query string
339  */
340 ZmZimletBase.prototype.onSearch = function(queryStr) {};
341 
342 /**
343  * This method is called by the Zimlet framework when the search button is clicked.
344  * 
345  * @param	{string}		queryStr		the search query string
346  * @see		#onKeyPressSearchField
347  */
348 ZmZimletBase.prototype.onSearchButtonClick = function(queryStr) {};
349 
350 /**
351  * This method is called by the Zimlet framework when enter is pressed in the search field.
352  * 
353  * @param	{string}		queryStr		the search query string
354  * @see		#onSearchButtonClick
355  */
356 ZmZimletBase.prototype.onKeyPressSearchField = function(queryStr) {};
357 
358 /**
359  * This method gets called by the Zimlet framework when the action menu is initialized on the from/sender of an email message.
360  * 
361  * @param	{ZmController}		controller		the controller
362  * @param	{ZmActionMenu}		actionMenu		the action menu
363  */
364 ZmZimletBase.prototype.onParticipantActionMenuInitialized = function(controller, actionMenu) {};
365 
366 /**
367  * This method gets called by the Zimlet framework when the action menu is initialized
368  * on the subject/fragment of an email message.
369  * 
370  * <p>
371  * This method is called twice:
372  * <ul>
373  * <li>The first-time a right-click is performed on a message in Conversation View.</li>
374  * <li>The first-time a right-click is performed on a message in Message View.</li>
375  * </ul>
376  * </p>
377  * 
378  * @param	{ZmController}		controller		the controller
379  * @param	{ZmActionMenu}		actionMenu		the action menu
380  */
381 ZmZimletBase.prototype.onActionMenuInitialized = function(controller, actionMenu) {};
382 
383 /**
384  * This method is called by the Zimlet framework when an email message is flagged.
385  * 
386  * @param	{ZmMailMsg[]|ZmConv[]}		items		an array of items
387  * @param	{boolean}		on		<code>true</code> if the flag is being set; <code>false</code> if the flag is being unset
388  */
389 ZmZimletBase.prototype.onMailFlagClick = function(items, on) {};
390 
391 /**
392  * This method is called by the Zimlet framework when an email message is tagged.
393  * 
394  * @param	{ZmMailMsg[]|ZmConv[]}		items		an array of items
395  * @param	{ZmTag}			tag			the tag
396  * @param	{boolean}		doTag		<code>true</code> if the tag is being set; <code>false</code> if the tag is being removed
397  */
398 ZmZimletBase.prototype.onTagAction = function(items, tag, doTag) {};
399 
400 /**
401  * This method is called by the Zimlet framework when a message is about to be sent.
402  * 
403  * <p>
404  * To fail the error check, the zimlet must return a <code>boolAndErrorMsgArray</code> array
405  * with the following syntax:
406  * <br />
407  * <br />
408  * <code>{hasError:<true or false>, errorMsg:<error msg>, zimletName:<zimlet name>}</code>
409  *</p>
410  *
411  * @param	{ZmMailMsg}		msg		the message
412  * @param	{array}		boolAndErrorMsgArray	an array of error messages, if any
413  */
414 ZmZimletBase.prototype.emailErrorCheck = function(msg, boolAndErrorMsgArray) {};
415 
416 /**
417  * This method is called by the Zimlet framework when adding a signature to an email message.
418  * 
419  * <p>
420  * To append extra signature information, the zimlet should push text into the <code>bufferArray</code>.
421  * 
422  * <pre>
423  * bufferArray.push("Have fun, write a Zimlet!");
424  * </pre>
425  * </p>
426  * 
427  * @param	{ZmMailMsg}		contact		the clicked message
428  * @param	{ZmMailMsg}		oldMsg	the previous clicked message or <code>null</code> if this is the first message clicked
429  */
430 ZmZimletBase.prototype.appendExtraSignature = function(bufferArray) {};
431 
432 /**
433  * This method is called by the Zimlet framework when the message confirmation dialog is presented.
434  * 
435  * @param	{ZmMailConfirmView}		confirmView		the confirm view
436  * @param	{ZmMailMsg}		msg		the message
437  */
438 ZmZimletBase.prototype.onMailConfirm = function(confirmView, msg) {};
439 
440 /*
441  * 
442  * Portlet methods
443  */
444 
445 /**
446  * This method is called by the Zimlet framework when the portlet is created.
447  * 
448  * @param	{ZmPortlet}	portlet		the portlet
449  */
450 ZmZimletBase.prototype.portletCreated =
451 function(portlet) {
452     DBG.println("portlet created: " + portlet.id);
453 };
454 
455 /**
456  * This method is called by the Zimlet framework when the portlet is refreshed.
457  * 
458  * @param	{ZmPortlet}	portlet		the portlet
459  */
460 ZmZimletBase.prototype.portletRefreshed =
461 function(portlet) {
462 	DBG.println("portlet refreshed: " + portlet.id);
463 };
464 
465 /*
466  * 
467  * Content Object methods
468  * 
469  */
470 
471 /**
472  * This method is called when content (e.g. a mail message) is being parsed.
473  * The match method may be called multiple times for a given piece of content and
474  * should apply the pattern matching as defined for a given zimlet <code><regex></code>.
475  * Zimlets should also use the "g" option when constructing their <code><regex></code>.
476  *
477  * <p>
478  * The return should be an array in the form:
479  *  
480  * <pre>
481  * result[0...n] // should be matched string(s)
482  * result.index // should be location within line where match occurred
483  * result.input // should be the input parameter content
484  * </pre>
485  * </p>
486  * 
487  * @param	{string}	content		the content line to perform a match against
488  * @param	{number}	startIndex	the start index (i.e. where to begin the search)
489  * @return	{array}	the matching content object from the <code>startIndex</code> if the content matched the specified zimlet handler regular expression; otherwise <code>null</code>
490  */
491 ZmZimletBase.prototype.match =
492 function(content, startIndex) {
493 	if(!this.RE) {return null;}
494 	this.RE.lastIndex = startIndex;
495 	var ret = this.RE.exec(content);
496 	if (ret) {
497 		ret.context = ret;
498 	}
499 	return ret;
500 };
501 
502 /**
503  * This method is called when a zimlet content object is clicked.
504  *
505  * @param	{Object}		spanElement		the enclosing span element
506  * @param	{string}		contentObjText	the content object text
507  * @param	{array}		matchContent	the match content
508  * @param	{DwtMouseEvent}	event			the mouse click event
509  */
510 ZmZimletBase.prototype.clicked =
511 function(spanElement, contentObjText, matchContext, event) {
512 	var c = this.xmlObj("contentObject.onClick");
513 	if (c && c.actionUrl) {
514 		var obj = this._createContentObj(contentObjText, matchContext);
515         var x = event.docX;
516         var y = event.docY;
517         this.xmlObj().handleActionUrl(c.actionUrl, c.canvas, obj, null, x, y);
518 	}
519 };
520 
521 /**
522  * This method is called when the tool tip is popping-up.
523  *
524  * @param	{Object}	spanElement		the enclosing span element
525  * @param	{string}	contentObjText	the content object text
526  * @param	{array}		matchContent	the matched content
527  * @param	{Object}	canvas			the canvas
528  */
529 ZmZimletBase.prototype.toolTipPoppedUp =
530 function(spanElement, contentObjText, matchContext, canvas) {
531 	var c = this.xmlObj("contentObject");
532 	if (c && c.toolTip) {
533 		var obj = this._createContentObj(contentObjText, matchContext);
534 
535 		var txt;
536 		if (c.toolTip instanceof Object && c.toolTip.actionUrl) {
537 		    this.xmlObj().handleActionUrl(c.toolTip.actionUrl, [{type:"tooltip"}], obj, canvas);
538 		    // XXX the tooltip needs "some" text on it initially, otherwise it wouldn't resize afterwards.
539 		    txt = ZmMsg.zimletFetchingTooltipData;
540 		} else {
541 			// If it's an email address just use the address value.
542 			if (obj.objectContent instanceof AjxEmailAddress) {obj.objectContent = obj.objectContent.address;}
543 			txt = this.xmlObj().process(c.toolTip, obj);
544 		}
545 		canvas.innerHTML = txt;
546 
547 		Dwt.setSize(canvas, parseInt(c.toolTip.width) || Dwt.DEFAULT, parseInt(c.toolTip.height) || Dwt.DEFAULT);
548 		
549 		if (this._isTooltipSticky()) {
550 			Dwt.setHandler(canvas, DwtEvent.ONCLICK, AjxCallback.simpleClosure(this.setTooltipSticky, this, [true]));
551 
552 			var omem = DwtOutsideMouseEventMgr.INSTANCE;
553 			omem.startListening({
554 				id: "ZimletTooltip",
555 				elementId: canvas.id,
556 				outsideListener: new AjxListener(this, this.setTooltipSticky, [false, true])
557 			});
558 
559 		}
560 	}
561 };
562 
563 /**
564  * This method is called when a sticky tooltip is clicked, when clicking outside
565  * a sticky tooltip, or when a zimlet wants to stick or unstick a tooltip.
566  * To explicitly dismiss a sticky tooltip, this method should be called with parameters (false, true)
567  *
568  * @param	{boolean}	sticky		Whether stickiness should be applied or removed
569  * @param	{boolean}	popdown		(Optional) Pop down the tooltip after removing stickiness
570  */
571 ZmZimletBase.prototype.setTooltipSticky =
572 function(sticky, popdown) {
573 	var shell = DwtShell.getShell(window);
574 	var tooltip = shell.getToolTip();
575 	tooltip.setSticky(sticky);
576 	if (!sticky && popdown) {
577 		tooltip.popdown();
578 	}
579 };
580 
581 /**
582  * This method is called when the tool tip is popping-down.
583  *
584  * @param	{Object}		spanElement		the enclosing span element
585  * @param	{string}		contentObjText	the content object text
586  * @param	{array}		matchContent	the matched content
587  * @param	{Object}	canvas			the canvas
588  * @return	{string}	<code>null</code> if the tool tip may be popped-down; otherwise, a string indicating why the tool tip should not be popped-down
589  */
590 ZmZimletBase.prototype.toolTipPoppedDown =
591 function(spanElement, contentObjText, matchContext, canvas) {
592 	var omem = DwtOutsideMouseEventMgr.INSTANCE;
593 	omem.stopListening({
594 		id: "ZimletTooltip",
595 		elementId: canvas ? canvas.id : ""
596 	});
597 };
598 
599 /**
600  * @private
601  */
602 ZmZimletBase.prototype.getActionMenu =
603 function(obj, span, context) {
604 	if (this._zimletContext._contentActionMenu instanceof AjxCallback) {
605 		this._zimletContext._contentActionMenu = this._zimletContext._contentActionMenu.run();
606 	}
607 	this._actionObject = obj;
608 	this._actionSpan = span;
609 	this._actionContext = context;
610 	return this._zimletContext._contentActionMenu;
611 };
612 
613 /*
614  *
615  * Common methods
616  * 
617  */
618 
619 /**
620  * This method is called when a context menu item is selected.
621  * 
622  * @param	{ZmZimletBase.PANEL_MENU|ZmZimletBase.CONTENTOBJECT_MENU}	contextMenu		the context menu
623  * @param	{string}		menuItemId		the selected menu item Id
624  * @param	{Object}		spanElement		the enclosing span element
625  * @param	{string}		contentObjText	the content object text
626  * @param	{Object}		canvas		the canvas
627  */
628 ZmZimletBase.prototype.menuItemSelected =
629 function(contextMenu, menuItemId, spanElement, contentObjText, canvas) {};
630 
631 /**
632  * This method is called if there are <code><userProperties></code> elements specified in the
633  * Zimlet Definition File. When the zimlet panel item is double-clicked, the property
634  * editor will be presented to the user.
635  * 
636  * <p>
637  * This method creates the property editor for the set of <code><property></code> elements defined
638  * in the <code><userProperties></code> element. The default implementation of this
639  * method will auto-create a property editor based on the attributes of the user properties.
640  * </p>
641  * <p>
642  * Override this method if a custom property editor is required.
643  * </p>
644  * 
645  * @param	{AjxCallback}	callback	the callback method for saving user properties
646  */
647 ZmZimletBase.prototype.createPropertyEditor =
648 function(callback) {
649 	var userprop = this.xmlObj().userProperties;
650 
651 	if (!userprop || !userprop.length) {return;}
652 
653     for (var i = 0; i < userprop.length; ++i) {
654         userprop[i].label = this._zimletContext.processMessage(userprop[i].label);
655         if (userprop[i].type == "enum") {
656         	var items = userprop[i].item;
657         	for (var j=0; items != null && j < items.length; j++) {
658         		if (items[j] == null)
659         			continue;
660         		var item = items[j];
661         		item.label = this._zimletContext.processMessage(item.label);
662         	}
663         }
664 	}
665 
666 	if (!this._dlg_propertyEditor) {
667 		var view = new DwtComposite(this.getShell());
668 		var pe = this._propertyEditor = new DwtPropertyEditor(view, true);
669 		pe.initProperties(userprop);
670 		var dialog_args = {
671 			title : this._zimletContext.processMessage(this.xmlObj("description")) + " preferences",
672 			view  : view
673 		};
674 		var dlg = this._dlg_propertyEditor = this._createDialog(dialog_args);
675 		pe.setFixedLabelWidth();
676 		pe.setFixedFieldWidth();
677 		dlg.setButtonListener(DwtDialog.OK_BUTTON,
678 				      new AjxListener(this, function() {
679 					      this.saveUserProperties(callback);
680 				      }));
681 	}
682 	this._dlg_propertyEditor.popup();
683 };
684 
685 
686 /*
687  *
688  * Helper methods
689  * 
690  */
691 
692 
693 /**
694  * Displays the specified error message in the standard error dialog.
695  * 
696  * @param	{string}	msg		the error message to display
697  * @param	{string}	data	the error message details
698  * @param	{string}	title	the error message dialog title
699  */
700 ZmZimletBase.prototype.displayErrorMessage = function(msg, data, title) {
701 
702     appCtxt.showError({
703         errMsg:     msg,
704         details:    data,
705         title:      title || AjxMessageFormat.format(ZmMsg.zimletErrorTitle, this.xmlObj().label)
706     });
707 };
708 
709 /**
710  * Displays the specified status message.
711  * 
712  * @param	{string}	msg		the status message to display
713  */
714 ZmZimletBase.prototype.displayStatusMessage =
715 function(msg) {
716 	appCtxt.setStatusMsg(msg);
717 };
718 
719 /**
720  * Gets the fully qualified resource Url.
721  *
722  * @param	{string}	resourceName	the resource name
723  * @return	{string}	the fully qualified resource Url
724  */
725 ZmZimletBase.prototype.getResource =
726 function(resourceName) {
727 	return this.xmlObj().getUrl() + resourceName;
728 };
729 
730 /**
731  * @private
732  */
733 ZmZimletBase.prototype.getType =
734 function() {
735 	return this.type;
736 };
737 
738 /**
739  * This method is called when a request finishes.
740  * 
741  * @param	{AjxCallback}	callback	the callback method or <code>null</code> for none
742  * @param	{boolean}	passErrors	<code>true</code> to pass errors to the error display; <code>null</code> or <code>false</code> otherwise
743  * @see		#sendRequest()
744  * @private
745  */
746 ZmZimletBase.prototype.requestFinished =
747 function(callback, passErrors, xmlargs) {
748 	this.resetIcon();
749 	if (!(passErrors || this._passRpcErrors) && !xmlargs.success) {
750 		this.displayErrorMessage("We could not connect to the remote server, or an error was returned.<br />Error code: " + xmlargs.status, xmlargs.text);
751 	} else if (callback)
752 		// Since we don't know for sure if we got an XML in return, it
753 		// wouldn't be too wise to create an AjxXmlDoc here.  Let's
754 		// just report the text and the Zimlet should know what to do
755 		callback.run(xmlargs);
756 };
757 
758 /**
759  * Sends the request content (via Ajax) to the specified server.
760  * 
761  * @param	{string}	requestStr		the request content to send
762  * @param	{string}	serverURL		the server url
763  * @param	{string[]}	requestHeaders	the request headers (may be <code>null</code>)
764  * @param	{AjxCallback}	callback	the callback for asynchronous requests or <code>null</code> for none
765  * @param	{boolean}	useGet		<code>true</code> to use HTTP GET; <code>null</code> or <code>false</code> otherwise
766  * @param	{boolean}	passErrors	<code>true</code> to pass errors; <code>null</code> or <code>false</code> otherwise
767  * @return	{Object}	the return value
768  */
769 ZmZimletBase.prototype.sendRequest =
770 function(requestStr, serverURL, requestHeaders, callback, useGet, passErrors) {
771 	if (passErrors == null)
772 		passErrors = false;
773 	if (requestStr instanceof AjxSoapDoc)
774 		requestStr = [ '<?xml version="1.0" encoding="utf-8" ?>',
775 			       requestStr.getXml() ].join("");
776 	this.setBusyIcon();
777 	serverURL = ZmZimletBase.PROXY + AjxStringUtil.urlComponentEncode(serverURL);
778 	var our_callback = new AjxCallback(this, this.requestFinished, [ callback, passErrors ]);
779 	return AjxRpc.invoke(requestStr, serverURL, requestHeaders, our_callback, useGet);
780 };
781 
782 /**
783  * Enables the specified context menu item.
784  * 
785  * @param	{ZmZimletBase.PANEL_MENU|ZmZimletBase.CONTENTOBJECT_MENU}	contextMenu		the context menu
786  * @param	{string}		menuItemId		the menu item Id
787  * @param	{boolean}		enabled			<code>true</code> to enable the menu item; <code>false</code> to disable the menu item
788  */
789 ZmZimletBase.prototype.enableContextMenuItem =
790 function(contextMenu, menuItemId, enabled) {};
791 
792 /**
793  * Gets the configuration property.
794  * 
795  * @param	{string}		propertyName	the name of the property to retrieve
796  * @return	{string}	the value of the property or <code>null</code> if no such property exists
797  */
798 ZmZimletBase.prototype.getConfigProperty =
799 function(propertyName) {};
800 
801 /**
802  * Gets the user property.
803  * 
804  * @param	{string}	propertyName the name of the property to retrieve
805  * @return	{string}	the value of the property or <code>null</code> if no such property exists 
806  */
807 ZmZimletBase.prototype.getUserProperty =
808 function(propertyName) {
809 	return this.xmlObj().getPropValue(propertyName);
810 };
811 
812 /**
813  * Sets the value of a given user property
814  * 
815  * @param	{string}	propertyName	the name of the property
816  * @param	{string}	value			the property value
817  * @param	{boolean}	save			if <code>true</code>, the property will be saved (along with any other modified properties) 
818  * @param	{AjxCallback}	callback	the callback to invoke after the user properties save
819  * @throws	ZimletException		if no such property exists or if the value is not valid for the property type
820  * @see		#saveUserProperties
821  */
822 ZmZimletBase.prototype.setUserProperty =
823 function(propertyName, value, save, callback) {
824 	this.xmlObj().setPropValue(propertyName, value);
825 	if (save)
826 		this.saveUserProperties(callback);
827 };
828 
829 /**
830  * This method is called by the zimlet framework prior to user properties being saved.
831  *
832  * @param	{array}	props		an array of objects with the following properties:
833  * <ul>
834  * <li>props[...].label {string} the property label</li>
835  * <li>props[...].name {string} the property name</li>
836  * <li>props[...].type {string} the property type</li>
837  * <li>props[...].value {string} the property value</li>
838  * </ul>
839  * @return	{boolean}	<code>true</code> if properties are valid; otherwise, <code>false</code> or {String} if an error message will be displayed in the standard error dialog.
840  */
841 ZmZimletBase.prototype.checkProperties =
842 function(props) {
843 	return true;
844 };
845 
846 /**
847  * Sets the busy icon. The Zimlet framework usually calls this method during SOAP
848  * calls to provide some end-user feedback.
849  * 
850  * The default is a animated icon.
851  * 
852  * @private
853  */
854 ZmZimletBase.prototype.setBusyIcon =
855 function() {
856 	this.setIcon("ZimbraIcon DwtWait16Icon");
857 };
858 
859 /**
860  * This Zimlet hook allows Zimlets to set custom headers to outgoing emails.
861  * To set a custom header, they need to push header name and header value to
862  * customMimeHeaders array.
863  *  Example:
864  *  customHeaders.push({name:"header1", _content:"headerValue"});
865  *
866  *  Note: Header name ("header1" in this case) MUST be one of the valid/allowed values of
867  *  zimbraCustomMimeHeaderNameAllowed global-config property (set by admin)
868  * @param {array} customMimeHeaders The array containing all custom headers
869  *
870  */
871 ZmZimletBase.prototype.addCustomMimeHeaders =
872 function(customMimeHeaders) {
873 	//Example:
874 	//customMimeHeaders.push({name:"header1", _content:"headerValue"});
875 };
876 
877 /**
878  * Sets the zimlet icon in the panel.
879  * 
880  * @param	{string}	icon		the icon (style class) for the zimlet
881  * @private
882  */
883 ZmZimletBase.prototype.setIcon =
884 function(icon) {
885 	if (!this.xmlObj("zimletPanelItem"))
886 		return;
887 	this.xmlObj().icon = icon;
888     var treeItem;
889     if (appCtxt.multiAccounts) {
890         //get overview from the overview container
891         var container = appCtxt.getCurrentApp().getOverviewContainer();
892         var overviewId = appCtxt.getOverviewId([container.containerId, ZmOrganizer.LABEL[ZmOrganizer.ZIMLET]], null);
893         var ov = container.getOverview(overviewId);
894         treeItem = ov && ov.getTreeItemById && ov.getTreeItemById(this.xmlObj().getOrganizer().id);
895     } else {
896         var treeView = appCtxt.getAppViewMgr().getViewComponent(ZmAppViewMgr.C_TREE);
897         treeItem = treeView && treeView.getTreeItemById && treeView.getTreeItemById(this.xmlObj().getOrganizer().id);
898     }
899 
900 	if (treeItem) {
901 		treeItem.setImage(icon);
902 	}
903 };
904 
905 /**
906  * Resets the zimlet icon to the one specified in the Zimlet Definition File (if originally set).
907  * 
908  * @private
909  */
910 ZmZimletBase.prototype.resetIcon =
911 function() {
912 	this.setIcon(this._origIcon);
913 };
914 
915 
916 /**
917  * Reset the toolbar
918  *
919  * @param	{ZmButtonToolBar|ZmActionMenu}  parent  the toolbar or action menu
920  * @param	{int}   enable  number of items selected
921  */
922 ZmZimletBase.prototype.resetToolbarOperations =
923 function(parent, num){};
924 
925 
926 /**
927  * Saves the user properties.
928  * 
929  * @param	{AjxCallback}	callback		the callback to invoke after the save
930  * @return	{string}		an empty string or an error message
931  */
932 ZmZimletBase.prototype.saveUserProperties =
933 function(callback) {
934 	var soapDoc = AjxSoapDoc.create("ModifyPropertiesRequest", "urn:zimbraAccount");
935 
936 	var props = this.xmlObj().userProperties;
937 	var check = this.checkProperties(props);
938 
939 	if (!check)
940 		return "";
941 	if (typeof check == "string")
942 		return this.displayErrorMessage(check);
943 
944 	if (this._propertyEditor)
945 		if (!this._propertyEditor.validateData())
946 			return "";
947 
948 	// note that DwtPropertyEditor actually works on the original
949 	// properties object, which means that we already have the edited data
950 	// in the xmlObj :-) However, the props. dialog will be dismissed if
951 	// present.
952 	for (var i = 0; i < props.length; ++i) {
953 		var p = soapDoc.set("prop", props[i].value);
954 		p.setAttribute("zimlet", this.xmlObj("name"));
955 		p.setAttribute("name", props[i].name);
956 	}
957 
958 	var ajxcallback = null;
959 	if (callback)
960 		ajxcallback = new AjxCallback(this, function(result) {
961 			// TODO: handle errors
962 			callback.run();
963 		});
964 	var params = { soapDoc: soapDoc, callback: ajxcallback, asyncMode: true, sensitive:true};
965 	appCtxt.getAppController().sendRequest(params);
966 	if (this._dlg_propertyEditor) {
967 		this._dlg_propertyEditor.popdown();
968 		// force the dialog to be reconstructed next time
969 		this._dlg_propertyEditor.dispose();
970 		this._propertyEditor = null;
971 		this._dlg_propertyEditor = null;
972 	}
973 	return "";
974 };
975 
976 /**
977  * Gets the user property info for the specified property.
978  * 
979  * @param	{string}	propertyName		the property
980  * @return	{string}	the value of the user property
981  */
982 ZmZimletBase.prototype.getUserPropertyInfo =
983 function(propertyName) {
984 	return this.xmlObj().getProp(propertyName);
985 };
986 
987 /**
988  * Gets the message property.
989  * 
990  * @param	{string}	msg		the message
991  * @return	{string}	the message property or <code>"???" + msg + "???"</code> if not found
992  */
993 ZmZimletBase.prototype.getMessage =
994 function(msg) {
995 	//Missing properties should not be catastrophic.
996 	var p = window[this.xmlObj().name];
997 	return p ? p[msg] : '???'+msg+'???';
998 };
999 
1000 /**
1001  * Gets the message properties.
1002  * 
1003  * @return	{string[]}		an array of message properties
1004  */
1005 ZmZimletBase.prototype.getMessages =
1006 function() {
1007 	return window[this.xmlObj().name] || {};
1008 };
1009 
1010 /**
1011  * @private
1012  */
1013 ZmZimletBase.prototype.getConfig =
1014 function(configName) {
1015 	return this.xmlObj().getConfig(configName);
1016 };
1017 
1018 /**
1019  * @private
1020  */
1021 ZmZimletBase.prototype.getBoolConfig =
1022 function(key, defaultValue) {
1023 	var val = AjxStringUtil.trim(this.getConfig(key));
1024 	if (val != null) {
1025 		if (arguments.length < 2)
1026 			defaultValue = false;
1027 		if (defaultValue) {
1028 			// the default is TRUE, check if explicitely disabled
1029 			val = !/^(0|false|off|no)$/i.test(val);
1030 		} else {
1031 			// default FALSE, check if explicitely enabled
1032 			val = /^(1|true|on|yes)$/i.test(val);
1033 		}
1034 	} else {
1035 		val = defaultValue;
1036 	}
1037 	return val;
1038 };
1039 
1040 /**
1041  * @private
1042  */
1043 ZmZimletBase.prototype.setEnabled =
1044 function(enabled) {
1045 	if (arguments.length == 0)
1046 		enabled = true;
1047 	this.__zimletEnabled = enabled;
1048 };
1049 
1050 /**
1051  * @private
1052  */
1053 ZmZimletBase.prototype.getEnabled =
1054 function() {
1055 	return this.__zimletEnabled;
1056 };
1057 
1058 /**
1059  * Gets the current username.
1060  *
1061  * @return	{string}		the current username
1062  */
1063 ZmZimletBase.prototype.getUsername =
1064 function() {
1065 	return appCtxt.get(ZmSetting.USERNAME);
1066 };
1067 
1068 /**
1069  * Gets the current user id.
1070  *
1071  * @return	{string}	the current user id
1072  */
1073 ZmZimletBase.prototype.getUserID =
1074 function() {
1075 	return appCtxt.get(ZmSetting.USERID);
1076 };
1077 
1078 /**
1079  * Creates DOM safe ids.
1080  * 
1081  * @private
1082  */
1083 ZmZimletBase.encodeId =
1084 function(s) {
1085 	return s.replace(/[^A-Za-z0-9]/g, "");
1086 };
1087 
1088 /**
1089  * @private
1090  */
1091 ZmZimletBase.prototype.hoverOver =
1092 function(object, context, x, y, span) {
1093 	var shell = DwtShell.getShell(window);
1094 	var tooltip = shell.getToolTip();
1095 	tooltip.setContent('<div id="zimletTooltipDiv"/>', true);
1096 	this.toolTipPoppedUp(span, object, context, document.getElementById("zimletTooltipDiv"), tooltip);
1097 	tooltip.popup(x, y, true, !this._isTooltipSticky(), null, null, new AjxCallback(this, this.hoverOut, object, context, span));
1098 };
1099 
1100 /**
1101  * @private
1102  */
1103 ZmZimletBase.prototype.hoverOut =
1104 function(object, context, span) {
1105 	var shell = DwtShell.getShell(window);
1106 	var tooltip = shell.getToolTip();
1107 	if (!tooltip.getHovered()) {
1108 		tooltip.popdown();
1109 		this.toolTipPoppedDown(span, object, context, document.getElementById("zimletTooltipDiv"));
1110 	}
1111 };
1112 
1113 /**
1114  * @private
1115  */
1116 ZmZimletBase.prototype.makeCanvas =
1117 function(canvasData, url, x, y) {
1118 	if(canvasData && canvasData.length)
1119         canvasData = canvasData[0];    
1120     var canvas = null;
1121 	var div;
1122 
1123 	div = document.createElement("div");
1124 	div.id = "zimletCanvasDiv";
1125 
1126 	// HACK #1: if an actionUrl was specified and there's no <canvas>, we
1127 	// assume a <canvas type="window">
1128 	if (!canvasData && url)
1129 		canvasData = { type: "window" };
1130 
1131 	// HACK #2: some folks insist on using "style" instead of "type". ;-)
1132 	if (canvasData.style && !canvasData.type)
1133 		canvasData.type = canvasData.style;
1134 
1135 	switch (canvasData.type) {
1136 	    case "window":
1137 		var browserUrl = url;
1138 		if (browserUrl == null)
1139 			browserUrl = appContextPath+"/public/blank.html";
1140 		var contentObject = this.xmlObj("contentObject");
1141         if(contentObject && !canvasData.width && contentObject.onClick ) {
1142             if(contentObject.onClick.canvas.props == "")
1143                 canvas = window.open(browserUrl);
1144             else if(contentObject.onClick.canvas.props != "")
1145                 canvas = window.open(browserUrl, this.xmlObj("name"), contentObject.onClick.canvas.props);
1146         }
1147         else{
1148             var props = canvasData.props ? [ canvasData.props ] : [ "toolbar=yes,location=yes,status=yes,menubar=yes,scrollbars=yes,resizable=yes"];
1149             if (canvasData.width)
1150                 props.push("width=" + canvasData.width);
1151             if (canvasData.height)
1152                 props.push("height=" + canvasData.height);
1153             props = props.join(",");
1154             canvas = window.open(browserUrl, this.xmlObj("name"), props);
1155         }
1156         if (!url) {
1157 			// TODO: add div element in the window.
1158 			//canvas.document.getHtmlElement().appendChild(div);
1159 		}
1160 		break;
1161 
1162 	    case "dialog":
1163 		var view = new DwtComposite(this.getShell());
1164 		if (canvasData.width)
1165 			view.setSize(canvasData.width, Dwt.DEFAULT);
1166 		if (canvasData.height)
1167 			view.setSize(Dwt.DEFAULT, canvasData.height);
1168 		var title = canvasData.title || ("Zimlet dialog (" + this.xmlObj("description") + ")");
1169 		title = this._zimletContext.processMessage(title);
1170 		canvas = this._createDialog({ view: view, title: title });
1171 		canvas.view = view;
1172 		if (url) {
1173 			// create an IFRAME here to open the given URL
1174 			var el = document.createElement("iframe");
1175 			el.src = url;
1176 			var sz = view.getSize();
1177 			if (!AjxEnv.isIE) {
1178 				// substract default frame borders
1179 				sz.x -= 4;
1180 				sz.y -= 4;
1181 			}
1182 			el.style.width = sz.x + "px";
1183 			el.style.height = sz.y + "px";
1184 			view.getHtmlElement().appendChild(el);
1185 			canvas.iframe = el;
1186 		} else {
1187 			view.getHtmlElement().appendChild(div);
1188 		}
1189 		canvas.popup();
1190 		break;
1191 
1192         case "tooltip":
1193         var shell = DwtShell.getShell(window);
1194 	    var canvas = shell.getToolTip();
1195 	    canvas.setContent('<div id="zimletTooltipDiv" />', true);
1196         var el = document.createElement("iframe");
1197         el.setAttribute("width",canvasData.width);
1198         el.setAttribute("height",canvasData.height);
1199         el.setAttribute("style","border:0px");        
1200         el.src = url;
1201         document.getElementById("zimletTooltipDiv").appendChild(el);
1202         canvas.popup(x, y, true);
1203         break;
1204     }
1205 	return canvas;
1206 };
1207 
1208 /**
1209  * This method will apply and XSL transformation to an XML document. For example, content
1210  * returned from a services call.
1211  * 
1212  * @param	{string}	xsltUrl		the URL to the XSLT style sheet
1213  * @param	{string|AjxXmlDoc}	doc		the XML document to apply the style sheet
1214  * @return	{AjxXmlDoc}	the XML document representing the transformed document
1215  */
1216 ZmZimletBase.prototype.applyXslt =
1217 function(xsltUrl, doc) {
1218 	var xslt = this.xmlObj().getXslt(xsltUrl);
1219 	if (!xslt) {
1220 		throw new Error("Cannot create XSLT engine: "+xsltUrl);
1221 	}
1222 	if (doc instanceof AjxXmlDoc) {
1223 		doc = doc.getDoc();
1224 	}
1225 	var ret = xslt.transformToDom(doc);
1226 	return AjxXmlDoc.createFromDom(ret);
1227 };
1228 
1229 /**
1230  * Creates a "tab" application and registers this zimlet to
1231  * receive {@link #appActive} and {@link #appLaunch} events.
1232  * 
1233  * @param	{string}	label	the label to use on the application tab
1234  * @param	{string}	image	the image (style class) to use on the application tab
1235  * @param	{string}	tooltip	the tool tip to display when hover-over the application tab
1236  * @param	{number}		[index]	the index to insert the tab (must be > 0). 0 is first location. Default is last location.
1237  * @param	{constant}	style	the button positioning style (see {@link DwtControl})
1238  * @return	{string}	the name of the newly created application
1239  */
1240 ZmZimletBase.prototype.createApp =
1241 function(label, image, tooltip, index, style) {
1242 
1243 	AjxDispatcher.require("ZimletApp");
1244 
1245 	var appName = [this.name, Dwt.getNextId()].join("_");
1246 	var controller = appCtxt.getAppController();
1247 
1248 	var params = {
1249 			text:label,
1250 			image:image,
1251 			tooltip:tooltip,
1252 			style: style
1253 		};
1254 	
1255 	if (index != null && index >= 0)
1256 		params.index = index;
1257 
1258 	controller.getAppChooser().addButton(appName, params);
1259 
1260 	// TODO: Do we have to call ZmApp.registerApp?
1261 
1262 	var app = new ZmZimletApp(appName, this, DwtShell.getShell(window));
1263 	controller.addApp(app);
1264 
1265 	return appName;
1266 };
1267 
1268 /**
1269  * This method gets called each time the "tab" application is opened or closed.
1270  * 
1271  * @param	{string} appName        the application name
1272  * @param	{boolean} active        if <code>true</code>, the application status is open; otherwise, <code>false</code>
1273  * @see		#createApp
1274  */
1275 ZmZimletBase.prototype.appActive = function(appName, active) { };
1276 
1277 /**
1278  * This method gets called when the "tab" application is opened for the first time.
1279  * 
1280  * @param    {string} appName        the application name
1281  * @see		#createApp
1282  */
1283 ZmZimletBase.prototype.appLaunch = function(appName) { };
1284 
1285 /**
1286  * This method by the Zimlet framework when an application button is pressed.
1287  * 
1288  * @param	{string} id        the id of the application button
1289  */
1290 ZmZimletBase.prototype.onSelectApp = function(id) { };
1291 
1292 /**
1293  * This method by the Zimlet framework when an application action occurs.
1294  * 
1295  * @param	{string}	type        the type of action (for example: "app", "menuitem", "treeitem")
1296  * @param	{string}	action		the action
1297  * @param	{string}	currentViewId		the current view Id
1298  * @param	{string}	lastViewId		the last view Id
1299  */
1300 ZmZimletBase.prototype.onAction = function(id, action, currentViewId, lastViewId) { };
1301 
1302 /*
1303  *
1304  * Internal functions -- overriding is not recommended
1305  * 
1306  */
1307 
1308 /**
1309  * Creates the object that describes the match, and is passed around to url generation routines
1310  * 
1311  * @private
1312  */
1313 ZmZimletBase.prototype._createContentObj =
1314 function(contentObjText, matchContext) {
1315 	var obj = { objectContent: contentObjText };
1316 	if (matchContext && (matchContext instanceof Array)) {
1317 		for (var i = 0; i < matchContext.length; ++i) {
1318 			obj["$"+i] = matchContext[i];
1319 		}
1320 	}
1321 	return obj;
1322 };
1323 
1324 /**
1325  * @private
1326  */
1327 ZmZimletBase.prototype._createDialog =
1328 function(params) {
1329 	params.parent = this.getShell();
1330 	return new ZmDialog(params);
1331 };
1332 
1333 /**
1334  * Overrides default ZmObjectHandler methods for Zimlet API compat
1335  * 
1336  * @private
1337  */
1338 ZmZimletBase.prototype._getHtmlContent =
1339 function(html, idx, obj, context) {
1340 	if (obj instanceof AjxEmailAddress) {
1341 		obj = obj.address;
1342 	}
1343 	var contentObj = this.xmlObj().getVal("contentObject");
1344 	if(contentObj && contentObj.onClick) {
1345  		html[idx++] = AjxStringUtil.htmlEncode(obj);
1346 	} else {
1347 		html[idx++] = AjxStringUtil.htmlEncode(obj, true);
1348 	}
1349 	return idx;
1350 };
1351 
1352 /**
1353  * Gets the mail messages for the conversation.
1354  * 
1355  * @param	{AjxCallback}		callback		the callback method
1356  * @param	{ZmConv}		conv			the conversation
1357  */
1358 ZmZimletBase.prototype.getMsgsForConv =
1359 function(callback, convObj){
1360 
1361 	if (convObj instanceof Array) {
1362 		convObj = convObj[0];
1363 	}
1364 	var convListController = AjxDispatcher.run("GetConvListController");
1365 	var convList = convListController.getList();
1366 	var conv = convList.getById(convObj.id);
1367 	
1368 	var ajxCallback = new AjxCallback(this, this._handleTranslatedConv, [callback, conv]);
1369 	conv.loadMsgs({fetchAll:true}, ajxCallback);
1370 };
1371 
1372 /**
1373  * @private
1374  */
1375 ZmZimletBase.prototype._handleTranslatedConv =
1376 function(callback, conv) {
1377 	if (callback) {
1378 		callback.run(ZmZimletContext._translateZMObject(conv.msgs.getArray()));
1379 	}
1380 };
1381 
1382 /**
1383  * @private
1384  *
1385  * Does the config say the tooltip should be sticky?
1386  */
1387 ZmZimletBase.prototype._isTooltipSticky =
1388 function() {
1389 	var c = this.xmlObj("contentObject");
1390 	return (c && c.toolTip && c.toolTip.sticky && c.toolTip.sticky.toLowerCase() === "true");
1391 };
1392 
1393