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