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 * This file contains a Dwt composite control. 27 * 28 */ 29 30 /** 31 * @class 32 * A composite may contain other controls. All controls that need to contain child controls 33 * (such as menus, trees) should inherit from this class. 34 * 35 * @param {hash} params a hash of parameters 36 * @param {DwtComposite} params.parent the parent widget 37 * @param {string} params.className the CSS class 38 * @param {constant} params.posStyle the positioning style 39 * @param {boolean} params.deferred if <code>true</code>, postpone initialization until needed 40 * @param {string} params.id an explicit ID to use for the control's HTML element 41 * @param {number} params.index the index at which to add this control among parent's children 42 * 43 * @extends DwtControl 44 */ 45 DwtComposite = function(params) { 46 if (arguments.length == 0) { return; } 47 params = Dwt.getParams(arguments, DwtComposite.PARAMS); 48 49 params.className = params.className || "DwtComposite"; 50 DwtControl.call(this, params); 51 52 var desc = this.toString(); 53 if (desc == 'DwtComposite') { 54 desc = this.getHTMLElId(); 55 } 56 57 this._compositeTabGroup = new DwtTabGroup(desc + ' (DwtComposite)'); 58 59 /** 60 * Vector of child elements 61 * @type AjxVector 62 */ 63 this._children = new AjxVector(); 64 } 65 66 DwtComposite.PARAMS = DwtControl.PARAMS.concat(); 67 68 DwtComposite.prototype = new DwtControl; 69 DwtComposite.prototype.constructor = DwtComposite; 70 71 DwtComposite.prototype.isDwtComposite = true; 72 DwtComposite.prototype.toString = function() { return "DwtComposite"; } 73 74 75 76 /** 77 * Pending elements hash (i.e. elements that have not yet been realized). 78 * @private 79 */ 80 DwtComposite._pendingElements = new Object(); 81 82 83 /** 84 * Disposes of the control. This method will remove the control from under the 85 * control of it's parent and release any resources associate with the component. 86 * The method will also notify any event listeners on registered {@link DwtEvent.DISPOSE} event type. 87 * 88 * <p> 89 * In the case of {@link DwtComposite} this method will also dispose of all of the composite's 90 * children. 91 * 92 * <p> 93 * Subclasses may override this method to perform their own dispose functionality but 94 * should generally call the parent <code>dispose()</code> method. 95 * 96 * @see DwtControl#isDisposed 97 * @see DwtControl#addDisposeListener 98 * @see DwtControl#removeDisposeListener 99 */ 100 DwtComposite.prototype.dispose = 101 function() { 102 if (this._disposed) return; 103 104 var children = this._children.getArray(); 105 while (children.length > 0) { 106 children.pop().dispose(); 107 } 108 109 if (this._compositeTabGroup) { 110 this._compositeTabGroup.removeAllMembers(); 111 } 112 this._compositeTabGroup = null; 113 114 DwtControl.prototype.dispose.call(this); 115 } 116 117 /** 118 * Get a list of children of this composite. 119 * 120 * @return {array} an array of {@link DwtControl} objects 121 */ 122 DwtComposite.prototype.getChildren = 123 function() { 124 return this._children.getArray().slice(0); 125 } 126 127 /** 128 * Get the Nth child of this composite. 129 * 130 * @param {number} index the index of the child. 131 * 132 * @return {DwtControl} the child. 133 */ 134 DwtComposite.prototype.getChild = 135 function(idx) { 136 return this._children.get(idx); 137 }; 138 139 /** 140 * collapses consecutive separators into one. Gets rid of head or tail separators as well . 141 * Note that is does not remove the separators, just hides them so they can re-displayed as needed, next time this is called and other elements 142 * become visible 143 * 144 * this would be used on such subclasses as DwtMenu and DwtToolbar . 145 * However, currently it does not work with the toolbars, since separators there are not added as children to the toolbar composite. 146 * I tried to make it consistent with the DwtMenu approach, but it seemed a bit complicated right now. 147 * so for now I try to make it so no complete groups (items between separators) are hidden at one time. It might also be possible 148 * to do it for the toolbar using the _items HTML elements array, but probably less elegant than this approach. 149 */ 150 DwtComposite.prototype.cleanupSeparators = 151 function() { 152 var items = this.getChildren(); 153 var previousVisibleIsSeparator = true; // I lie so that upfront separator would be cleaned up 154 var lastSeparator; 155 for (var i = 0; i < items.length; i++) { 156 var item = items[i]; 157 var isSeparator = item.isStyle && item.isStyle(DwtMenuItem.SEPARATOR_STYLE); 158 159 if (isSeparator) { 160 item.setVisible(!previousVisibleIsSeparator); 161 if (!previousVisibleIsSeparator || !lastSeparator) { //the !lastSeparator is the edge case of first item is separator. (see comment about lie above) 162 //keep track of last visible separator (if it's also last item visible overall) 163 previousVisibleIsSeparator = true; 164 lastSeparator = item; 165 } 166 continue; 167 } 168 169 //not a separator 170 if (item.getVisible()) { 171 previousVisibleIsSeparator = false; 172 } 173 } 174 //cleanup tail separator 175 if (previousVisibleIsSeparator && lastSeparator) { 176 lastSeparator.setVisible(false); 177 } 178 }; 179 180 181 182 183 /** 184 * Gets the number of children of this composite. 185 * 186 * @return {number} the number of composite children 187 */ 188 DwtComposite.prototype.getNumChildren = 189 function() { 190 return this._children.size(); 191 } 192 193 /** 194 * Removes all of the composite children. 195 * 196 */ 197 DwtComposite.prototype.removeChildren = 198 function() { 199 var a = this._children.getArray(); 200 while (a.length > 0) { 201 a[0].dispose(); 202 } 203 if (this._compositeTabGroup) { 204 this._compositeTabGroup.removeAllMembers(); 205 } 206 } 207 208 /** 209 * Clears the composite HTML element of content and removes 210 * all composite children by calling <code>removeChildren</code>. 211 * 212 * @see #removeChildren 213 */ 214 DwtComposite.prototype.clear = 215 function() { 216 this.removeChildren(); 217 this.getHtmlElement().innerHTML = ""; 218 } 219 220 /** 221 * Adds the given child control to this composite at the index (if specified). 222 * 223 * @param {DwtControl} child the child control to add 224 * @param {number} index the index at which to add the child (may be <code>null</code>) 225 */ 226 DwtComposite.prototype.addChild = 227 function(child, index) { 228 this._children.add(child, index); 229 this._compositeTabGroup.addMember(child, index); 230 231 // check for a previously removed element 232 var childHtmlEl = child.getHtmlElement(); 233 childHtmlEl.setAttribute("parentId", this._htmlElId); 234 if (this instanceof DwtShell && this.isVirtual()) { 235 // If we are operating in "virtual shell" mode, then children of the shell's html elements 236 // are actually parented to the body 237 document.body.appendChild(childHtmlEl); 238 } else { 239 child.reparentHtmlElement(child.__parentElement || this.getHtmlElement(), index); 240 child.__parentElement = null; // don't keep the reference to element, if any 241 } 242 }; 243 244 /** 245 * Removes the specified child control from this control. A removed child is no longer retrievable via 246 * <code>getHtmlElement()</code>, so there is an option to save a reference to the removed child. 247 * That way it can be added later using <code>addChild()</code>. 248 * 249 * @param {DwtConrol} child the child control to remove 250 * @see #addChild 251 */ 252 DwtComposite.prototype.removeChild = 253 function(child) { 254 DBG.println(AjxDebug.DBG3, "DwtComposite.prototype.removeChild: " + child._htmlElId + " - " + child.toString()); 255 // Make sure that the child is initialized. Certain children (such as DwtTreeItems) 256 // can be created in a deferred manner (i.e. they will only be initialized if they 257 // are becoming visible. 258 if (child.isInitialized()) { 259 this._children.remove(child); 260 this._compositeTabGroup.removeMember(child); 261 // Sometimes children are nested in arbitrary HTML so we elect to remove them 262 // in this fashion rather than use this.getHtmlElement().removeChild(child.getHtmlElement() 263 var childHtmlEl = child.getHtmlElement(); 264 if (childHtmlEl) { 265 childHtmlEl.removeAttribute("parentId"); 266 if (childHtmlEl.parentNode) { 267 var el = childHtmlEl.parentNode.removeChild(childHtmlEl); 268 } 269 } 270 } 271 } 272 273 /** 274 * Return this.tabGroupMember if present (it always overrides any other contender), otherwise if this composite has 275 * children return the composite tab group, otherwise just return this control (instead of a group with one member). 276 * 277 * @returns {DwtComposite|DwtTabGroup} 278 */ 279 DwtComposite.prototype.getTabGroupMember = function() { 280 281 return this.tabGroupMember || (this.getNumChildren() > 0 ? this._compositeTabGroup : this); 282 }; 283 284 /** 285 * Allows the user to use the mouse to select text on the control. 286 * 287 * @private 288 */ 289 DwtComposite.prototype._setAllowSelection = 290 function() { 291 if (!this._allowSelection) { 292 this._allowSelection = true; 293 this.addListener(DwtEvent.ONMOUSEDOWN, new AjxListener(this, this._mouseDownListener)); 294 this.addListener(DwtEvent.ONCONTEXTMENU, new AjxListener(this, this._contextMenuListener)); 295 } 296 }; 297 298 /** 299 * Sets whether to prevent the browser from allowing text selection. 300 * 301 * @see DwtControl#preventSelection 302 * @private 303 */ 304 DwtComposite.prototype.preventSelection = 305 function(targetEl) { 306 return this._allowSelection ? false : DwtControl.prototype.preventSelection.call(this, targetEl); 307 }; 308 309 /** 310 * Determines whether to prevent the browser from displaying its context menu. 311 * 312 * @see DwtControl#preventContextMenu 313 * @private 314 */ 315 DwtComposite.prototype.preventContextMenu = 316 function(target) { 317 if (!this._allowSelection) { 318 return DwtControl.prototype.preventContextMenu.apply(this, arguments); 319 } 320 321 var bObjFound = target ? (target.id.indexOf("OBJ_") == 0) : false; 322 var bSelection = false; 323 324 // determine if anything has been selected (IE and mozilla do it differently) 325 if (document.selection) { // IE 326 bSelection = document.selection.type == "Text"; 327 } else if (getSelection()) { // mozilla 328 bSelection = getSelection().toString().length > 0; 329 } 330 331 // if something has been selected and target is not a custom object, 332 return (bSelection && !bObjFound) ? false : true; 333 }; 334 335 /** 336 * Handles focus control when the mouse button is released 337 * 338 * @see DwtControl#_focusByMouseUpEvent 339 * @private 340 */ 341 DwtComposite.prototype._focusByMouseUpEvent = 342 function() { 343 if (!this._allowSelection) { 344 DwtControl.prototype._focusByMouseUpEvent.apply(this, arguments); 345 } 346 // ...Else do nothing.... 347 // When text is being selected, we don't want the superclass 348 // to give focus to the keyboard input control. 349 }; 350 351 /** 352 * Event listener that is only registered when this control allows selection 353 * 354 * @see _allowSelection 355 * @private 356 */ 357 DwtComposite.prototype._mouseDownListener = 358 function(ev) { 359 if (ev.button == DwtMouseEvent.LEFT) { 360 // reset mouse event to propagate event to browser (allows text selection) 361 //todo - look into changing this, it's currently very confusing and inconsistent. 362 //bug 23462 change it to stop propagation, so supposedly it should NOT allow selection. 363 // (so the above comment is wrong). But it's more complicated than this, since ev._dontCallPreventDefault is more 364 // important, and is not set here, so a listener could set it to TRUE thus making it allow selection, despite 365 // _stopPropagation being set to true here. (not sure what the meaning is). 366 // Note for example the inconsistency with the DwtComposite.prototype._contextMenuListener method below 367 // As one cool way to allow selection look at ZmConvView2Header constructor, the line: 368 // this.setEventPropagation(true, [DwtEvent.ONMOUSEDOWN, DwtEvent.ONSELECTSTART, DwtEvent.ONMOUSEUP, DwtEvent.ONMOUSEMOVE]); 369 // which causes _dontCallPreventDefault to be set to true, not being overriden here, thus selection works. 370 ev._stopPropagation = true; 371 ev._returnValue = true; 372 } 373 }; 374 375 /** 376 * Event listener that is only registered when this control allows selection 377 * 378 * @see _allowSelection 379 * @private 380 */ 381 DwtComposite.prototype._contextMenuListener = 382 function(ev) { 383 // reset mouse event to propagate event to browser (allows context menu) 384 ev._stopPropagation = false; 385 ev._returnValue = true; 386 }; 387