1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 */ 27 28 /** 29 * @class 30 * Manages an undo stack (ZmActionStack) and the "Undo" toast messages 31 * Optimally there should only be one object of this class, globally reachable 32 */ 33 ZmActionController = function() { 34 35 this._actionStack = new ZmActionStack(1); 36 this._statusTransitions = ZmActionController._substituteTransitions(appCtxt.getSkinHint("toast", "transitions") || ZmToast.DEFAULT_TRANSITIONS); 37 this._outsideListener = new AjxListener(this, ZmActionController.prototype._outsideMouseDownListener); 38 appCtxt.getKeyboardMgr().addListener(DwtEvent.ONKEYDOWN, this._outsideListener); 39 }; 40 41 ZmActionController.prototype.toString = 42 function() { 43 return "ZmActionController"; 44 }; 45 46 /** 47 * Logs a raw action, dismissing any current undo message, interpreting the params and creates a ZmAction object that is pushed onto the stack and returned 48 * See also @link ZmActionStack.prototype.logAction 49 * 50 * @param {String} op operation to perform. Currently supported are "move", "trash", "spam" and "!spam" 51 * @param {Hash} [attrs] attributes for the operation. Pretty much the same as what the backend expects, e.g. "l" for the destination folderId of a move 52 53 * @param {String} [items] array of items to perform the action for. Valid types are specified in ZmActionStack.validTypes. Only one of [items],[item],[ids] or [id] should be specified; the first one found is used, ignoring the rest. 54 * @param {String} [item] item to perform the action for, if there is only one item. Accomplishes the same as putting the item in an array and giving it as [items] 55 * @param {String} [ids] array of ids of items to perform the action for. 56 * @param {String} [id] id of item to perform the action for, if there is only one. Accomplishes the same as putting the id in an array and giving it as [ids]. 57 */ 58 ZmActionController.prototype.actionPerformed = function(params) { 59 var logElement = this._actionStack.logAction(params); 60 if (logElement) { 61 this.dismiss(); 62 logElement.onComplete(new AjxCallback(this, this._handleActionComplete)); 63 } 64 return logElement; 65 }; 66 67 ZmActionController.prototype._handleActionComplete = function(action) { 68 this._active = true; 69 }; 70 71 /** 72 * Returns the HTML code for a link that will undo the specified action 73 * @param {ZmAction} actionElement undoable action to be called when the link is pressed 74 * @param {String} [text] optional custom text for the link body. Defaults to ZmMsg.undo 75 */ 76 ZmActionController.prototype.getUndoLink = function(actionElement, text) { 77 if (actionElement instanceof ZmAction) { 78 var undoId = ZmActionController._registerCallback(new AjxCallback(this, this.undoCurrent)); 79 return ["<a onclick='ZmActionController.callRegisteredCallback(",undoId,")' href='javascript:;' class='undo'>", text || ZmMsg.undo, "</a>"].join(""); 80 } else return ""; // We can't risk displaying "null" or "undefined" wherever this function is called from, and an empty string is just as falsy 81 }; 82 83 ZmActionController.prototype.getStatusTransitions = function() { 84 return this._statusTransitions; 85 }; 86 87 /** 88 * Undoes the specified action 89 * @param {ZmAction} action the action to undo 90 */ 91 ZmActionController.prototype.undo = function(action) { 92 if (this._active) { 93 this.dismiss(); 94 action.undo(); 95 } 96 }; 97 98 ZmActionController.prototype.undoCurrent = function() { 99 if (this._actionStack.canUndo()) { 100 if (this._actionStack.actionIsComplete()) { 101 if (this._active) { 102 this.dismiss(); 103 } 104 this._actionStack.undo(); 105 } else { 106 this._actionStack.onComplete(new AjxCallback(this,function(action) { 107 // We must call this.undo on a timeout to allow the status message to transition in before dismissing 108 setTimeout(AjxCallback.simpleClosure(this.undo, this, action), 0); 109 })); 110 } 111 } 112 }; 113 114 ZmActionController.prototype.onPopup = function() { 115 var omem = appCtxt.getOutsideMouseEventMgr(); 116 var omemParams = { 117 id: "ZmActionController", 118 obj: this, 119 elementId: ZmId.TOAST, 120 outsideListener: this._outsideListener 121 } 122 omem.startListening(omemParams); 123 }; 124 125 /** 126 * Dismisses the popped up toast 127 */ 128 ZmActionController.prototype.dismiss = function() { 129 appCtxt.dismissStatusMsg(true); 130 var omem = appCtxt.getOutsideMouseEventMgr(); 131 omem.stopListening("ZmActionController"); 132 this._active = false; 133 }; 134 135 /** 136 * Substitute "pause" for "hold" and make sure there's only one of them 137 * @param {Array} transitions Array of ZmToast transitions to copy from. Values are copied to the returned array 138 */ 139 ZmActionController._substituteTransitions = function(transitions) { 140 var newTransitions = []; 141 var seenOne = false; 142 for (var i=0; i<transitions.length; i++) { 143 var transition = transitions[i]; 144 if (transition) { 145 if (transition.type == ZmToast.PAUSE.type) { 146 if (!seenOne) { 147 seenOne = true; 148 newTransitions.push(ZmToast.HOLD); 149 } 150 } else { 151 newTransitions.push(transition); 152 } 153 } 154 } 155 return newTransitions; 156 }; 157 158 159 160 ZmActionController._registeredCallbacks = []; 161 162 ZmActionController._registerCallback = function(callback) { 163 var id = ZmActionController._registeredCallbacks.length; 164 ZmActionController._registeredCallbacks[id] = callback; 165 return id; 166 }; 167 168 ZmActionController.callRegisteredCallback = function(id) { 169 var callback = ZmActionController._registeredCallbacks[id]; 170 if (callback) { 171 if (callback instanceof AjxCallback) { 172 callback.run(); 173 } 174 else if (AjxUtil.isFunction(callback)) { 175 callback(); 176 } 177 } 178 }; 179 180 ZmActionController.prototype._outsideMouseDownListener = 181 function(ev) { 182 this.dismiss(); 183 }; 184