1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2011, 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) 2011, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @class 26 * This is a folder choosing widget designed to be displayed as a dropdown hanging off a menu. Other than 27 * that, it works mostly like ZmChooseFolderDialog. Instead of a New button, it has a "New Folder" menu item 28 * at the bottom. Items are moved when a menu item is clicked, instead of the OK button in the dialog. 29 * 30 * The implementation mostly relies on calling ZmChooseFolderDialog and ZmDialog methods, since those were 31 * already written. A cleaner implementation would probably include a base widget that this and the dialog 32 * would use. 33 * 34 * @author Eran Yarkon 35 * 36 * @param {hash} params a hash of parameters 37 * @param {DwtComposite} params.parent the parent widget 38 * @param {string} params.className the CSS class 39 * @param {constant} params.posStyle the positioning style (see {@link Dwt}) 40 * 41 * @extends DwtComposite 42 */ 43 ZmFolderChooser = function(params) { 44 if (arguments.length == 0) { return; } 45 params.className = params.className || "ZmFolderChooser"; 46 DwtComposite.call(this, params); 47 48 this._overview = {}; 49 this._opc = appCtxt.getOverviewController(); 50 this._treeView = {}; 51 this._folderTreeDivId = this._htmlElId + "_folderTreeDivId"; 52 53 this._uuid = Dwt.getNextId(); 54 55 AjxDispatcher.require("Extras"); // ZmChooseFolderDialog 56 this._changeListener = ZmChooseFolderDialog.prototype._folderTreeChangeListener.bind(this); 57 this._treeViewListener = this._treeViewSelectionListener.bind(this); 58 59 var moveMenu = params.parent; 60 moveMenu._addItem(this, params.index); //this is what DwtMenuItem does. Allows this item to be in the menu items table - better for layout purposes such as consistent widths 61 this._moveOnFolderCreate = true; 62 63 if (!params.hideNewButton) { 64 //add separator menu item on the move menu (the parent) 65 new DwtMenuItem({parent:moveMenu, style:DwtMenuItem.SEPARATOR_STYLE}); 66 67 // add New button 68 var newFolderItem = this._newButton = new DwtMenuItem({parent:moveMenu, id: moveMenu.getHTMLElId() + "|NEWFOLDER"}); 69 var appName = appCtxt.getCurrentAppName(); 70 var defaultApp = ZmApp.MAIL; 71 var newTextKey = ZmFolderChooser.NEW_ORG_KEY[appName] || ZmFolderChooser.NEW_ORG_KEY[defaultApp]; 72 var newImage = ZmFolderChooser.NEW_ORG_ICON[appName] || ZmFolderChooser.NEW_ORG_ICON[defaultApp]; 73 var newShortcut = ZmFolderChooser.NEW_ORG_SHORTCUT[appName]; 74 newFolderItem.setText(ZmMsg[newTextKey]); 75 newFolderItem.setImage(newImage); 76 if (newShortcut) { 77 newFolderItem.setShortcut(appCtxt.getShortcutHint(this._keyMap, newShortcut)); 78 } 79 newFolderItem.addSelectionListener(this._showNewDialog.bind(this)); 80 } 81 82 this._init(); 83 }; 84 85 ZmFolderChooser.prototype = new DwtComposite; 86 ZmFolderChooser.prototype.constructor = ZmFolderChooser; 87 88 ZmFolderChooser.prototype.isZmFolderChooser = true; 89 ZmFolderChooser.prototype.toString = function() { return "ZmFolderChooser"; }; 90 91 92 // Properties for New button at bottom 93 ZmFolderChooser.NEW_ORG_KEY = {}; 94 ZmFolderChooser.NEW_ORG_KEY[ZmApp.MAIL] = "newFolder"; 95 ZmFolderChooser.NEW_ORG_KEY[ZmApp.CONTACTS] = "newAddrBook"; 96 ZmFolderChooser.NEW_ORG_KEY[ZmApp.CALENDAR] = "newCalendar"; 97 ZmFolderChooser.NEW_ORG_KEY[ZmApp.TASKS] = "newTaskFolder"; 98 99 ZmFolderChooser.NEW_ORG_ICON = {}; 100 ZmFolderChooser.NEW_ORG_ICON[ZmApp.MAIL] = "NewFolder"; 101 ZmFolderChooser.NEW_ORG_ICON[ZmApp.CONTACTS] = "NewContactsFolder"; 102 ZmFolderChooser.NEW_ORG_ICON[ZmApp.CALENDAR] = "NewAppointment"; 103 ZmFolderChooser.NEW_ORG_ICON[ZmApp.TASKS] = "NewTaskList"; 104 105 ZmFolderChooser.NEW_ORG_SHORTCUT = {}; 106 ZmFolderChooser.NEW_ORG_SHORTCUT[ZmApp.MAIL] = ZmKeyMap.NEW_FOLDER; 107 ZmFolderChooser.NEW_ORG_SHORTCUT[ZmApp.CALENDAR] = ZmKeyMap.NEW_CALENDAR; 108 109 /** 110 * 111 * see ZmChooseFolderDialog.prototype.popup 112 */ 113 ZmFolderChooser.prototype.setupFolderChooser = 114 function(params, selectionCallback) { 115 116 this._selectionCallback = selectionCallback; 117 this._overviewId = params.overviewId; 118 119 ZmChooseFolderDialog.prototype.popup.call(this, params, true); 120 }; 121 122 ZmFolderChooser.prototype._getNewButton = 123 function () { 124 return this._newButton; 125 }; 126 127 ZmFolderChooser.prototype.updateData = 128 function(data) { 129 this._data = data; 130 }; 131 132 ZmFolderChooser.prototype._focus = 133 function() { 134 var overview = this._overview[this._overviewId]; 135 if (overview) { 136 overview.focus(); 137 } 138 }; 139 140 /** 141 * this is not really doing the popup, just setting more stuff up, but to reuse the caller (ZmChooseFolderDialog.prototype.popup) 142 * from ZmChooseFolderDialog, I had to keep the name. 143 * 144 * @param params 145 */ 146 ZmFolderChooser.prototype._doPopup = 147 function(params) { 148 ZmChooseFolderDialog.prototype._doPopup.call(this, params, true); 149 }; 150 151 152 /** 153 * this reuses ZmDialog stuff. With slight necessary changes. Might be fragile if this is changed in ZmDialog 154 * in which case we might be better off with copy-paste. but for now it works. 155 * 156 * @param params 157 * @param forceSingle 158 */ 159 ZmFolderChooser.prototype._setOverview = 160 function(params, forceSingle) { 161 params.overviewClass = "menuOverview"; 162 params.dynamicWidth = true; 163 164 var overview = ZmDialog.prototype._setOverview.call(this, params, forceSingle); //reuse from ZmDialog 165 166 if (!appCtxt.multiAccounts || forceSingle) { 167 //this is needed for some reason 168 this._overview[params.overviewId] = overview; 169 } 170 171 return overview; 172 }; 173 174 /** 175 * delegate to ZmDialog. called from ZmDialog.prototype._setOverview (which we delegate to from ZmFolderChooser.prototype._setOverview) 176 */ 177 ZmFolderChooser.prototype._renderOverview = 178 function() { 179 ZmDialog.prototype._renderOverview.apply(this, arguments); //reuse code from ZmDialog 180 }; 181 182 /** 183 * delegate to ZmDialog. 184 */ 185 ZmFolderChooser.prototype._setRootSelection = 186 function() { 187 ZmDialog.prototype._setRootSelection.apply(this, arguments); //reuse code from ZmDialog 188 }; 189 190 191 /** 192 * delegate to ZmDialog. called from ZmDialog.prototype._setOverview (which we delegate to from ZmFolderChooser.prototype._setOverview) 193 */ 194 ZmFolderChooser.prototype._makeOverviewVisible = 195 function() { 196 ZmDialog.prototype._makeOverviewVisible.apply(this, arguments); //reuse code from ZmDialog 197 }; 198 199 ZmFolderChooser.prototype._resetTree = 200 function(treeIds, overview) { 201 ZmChooseFolderDialog.prototype._resetTree.call(this, treeIds, overview); 202 }; 203 204 ZmFolderChooser.prototype._getOverview = 205 function() { 206 return ZmChooseFolderDialog.prototype._getOverview.call(this) 207 }; 208 209 ZmFolderChooser.prototype._treeViewSelectionListener = 210 function(ev) { 211 if (ev.detail != DwtTree.ITEM_SELECTED) { 212 return; 213 } 214 //set in DwtTree.prototype._itemClicked and DwtTree.prototype.setSelection (which is called by DwtTreeItem.prototype.handleKeyAction) 215 if (!ev.clicked && !ev.enter) { 216 return; 217 } 218 219 //I kept this logic from ZmChooseFolderDialog.prototype._treeViewSelectionListener. Not sure what it means exactly 220 if (this._getOverview() instanceof ZmAccountOverviewContainer) { 221 if (ev.item instanceof DwtHeaderTreeItem) { 222 return; 223 } 224 225 var oc = this._opc.getOverviewContainer(this._curOverviewId); 226 var overview = oc.getOverview(ev.item.getData(ZmTreeView.KEY_ID)); 227 oc.deselectAll(overview); 228 } 229 230 var organizer = ev.item && ev.item.getData(Dwt.KEY_OBJECT); 231 if (organizer.id == ZmFolder.ID_LOAD_FOLDERS) { 232 return; // user clicked on "Show More Folders", it's not a real selection, it just expanded more folders. 233 } 234 var value = organizer ? organizer.getName(null, null, true) : ev.item.getText(); 235 this._lastVal = value.toLowerCase(); 236 this._doSelection(); 237 }; 238 239 /** 240 * copied mostly from ZmChooseFolderDialog.prototype._okButtonListener 241 * @param tgtFolder 242 */ 243 ZmFolderChooser.prototype._doSelection = 244 function(tgtFolder) { 245 tgtFolder = tgtFolder || this._getOverview().getSelected(); 246 if (!tgtFolder) { 247 tgtFolder = appCtxt.getById(this._selected); 248 } 249 var folderList = (tgtFolder && (!(tgtFolder instanceof Array))) 250 ? [tgtFolder] : tgtFolder; 251 252 var msg = (!folderList || (folderList && folderList.length == 0)) 253 ? ZmMsg.noTargetFolder : null; 254 255 //todo - what is that? can you move stuff to multiple targets? annotation on ZmChooseFolderDialog show it might be for filters on multiple folders. obviously in that case we can't have a drop down. we might have to keep that folder dialog 256 257 // check for valid target 258 if (!msg && this._data) { 259 for (var i = 0; i < folderList.length; i++) { 260 var folder = folderList[i]; 261 if (folder.mayContain && !folder.mayContain(this._data, null, this._acceptFolderMatch)) { 262 if (this._data instanceof ZmFolder) { 263 msg = ZmMsg.badTargetFolder; 264 } 265 else { 266 var items = AjxUtil.toArray(this._data); 267 for (var i = 0; i < items.length; i++) { 268 var item = items[i]; 269 if (!item) { 270 continue; 271 } 272 if (item.isDraft && (folder.nId != ZmFolder.ID_TRASH && folder.nId != ZmFolder.ID_DRAFTS && folder.rid != ZmFolder.ID_DRAFTS)) { 273 // can move drafts into Trash or Drafts 274 msg = ZmMsg.badTargetFolderForDraftItem; 275 break; 276 } 277 else if ((folder.nId == ZmFolder.ID_DRAFTS || folder.rid == ZmFolder.ID_DRAFTS) && !item.isDraft) { 278 // only drafts can be moved into Drafts 279 msg = ZmMsg.badItemForDraftsFolder; 280 break; 281 } 282 } 283 if (!msg) { 284 msg = ZmMsg.badTargetFolderItems; 285 } 286 } 287 break; 288 } 289 } 290 } 291 292 if (msg) { 293 ZmDialog.prototype._showError.call(this, msg); 294 return; 295 } 296 if (this._selectionCallback) { 297 this._selectionCallback(tgtFolder); 298 } 299 }; 300 301 ZmFolderChooser.prototype._resetTreeView = 302 function(visible) { 303 ZmChooseFolderDialog.prototype._resetTreeView.call(this, visible); 304 }; 305 306 ZmFolderChooser.prototype.getOverviewId = 307 function(part) { 308 return appCtxt.getOverviewId([this.toString(), part], null); 309 }; 310 311 ZmFolderChooser.prototype._loadFolders = 312 function() { 313 ZmChooseFolderDialog.prototype._loadFolders.call(this); 314 }; 315 316 ZmFolderChooser.prototype._init = 317 function() { 318 319 var html = [], idx = 0; 320 321 html[idx++] = "<table cellspacing='0' cellpadding='0' style='border-collapse:collapse;'>"; 322 html[idx++] = "<tr><td><div id='" + this._folderTreeDivId + "'>"; 323 html[idx++] = "</div></td></tr>"; 324 html[idx++] = "</table>"; 325 326 this.getHtmlElement().innerHTML = html.join(""); 327 }; 328 329 ZmFolderChooser.prototype._showNewDialog = 330 function() { 331 var item = this._getOverview().getSelected(true); 332 var newType = (item && item.type) || this._treeIds[0]; 333 var ftc = this._opc.getTreeController(newType); 334 var dialog = ftc._getNewDialog(); 335 dialog.reset(); 336 dialog.registerCallback(DwtDialog.OK_BUTTON, ZmChooseFolderDialog.prototype._newCallback, this, [ftc, dialog]); 337 this.parent.popdown(); //pop it down so it doenst pop down when user clicks on the "new" dialog, confusing them. this is also consistent with the tag menu "new". 338 dialog.popup(); 339 }; 340