Source: src/main/javascript/oracle/oj/ojmasonrylayout/ojmasonrylayout.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/**
 * Copyright (c) 2015, Oracle and/or its affiliates.
 * All rights reserved.
 */

/**
 * @ojcomponent oj.ojMasonryLayout
 * @augments oj.baseComponent
 * @since 1.1.0
 * 
 * @classdesc
 * <h3 id="masonryLayoutOverview-section">
 *   JET MasonryLayout Component
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#masonryLayoutOverview-section"></a>
 * </h3>
 * 
 * <p>Description: A grid of tiles.
 * 
 * <p>A JET MasonryLayout can be created from any 
 * <code class="prettyprint"><div></code> element that contains multiple 
 * direct child <code class="prettyprint"><div></code> elements that can be 
 * sized and positioned.  
 * <p>Each direct child element must be styled using one of the predefined 
 * <code class="prettyprint">oj-masonrylayout-tile-CxR</code> style classes to specify
 * the size of that tile.  A tile can span multiple columns and/or rows.  The
 * predefined size style classes are:
 * <table class="keyboard-table">
 *   <thead>
 *     <tr>
 *       <th>Class</th>
 *       <th>Description</th>
 *     </tr>
 *   </thead>
 *   <tbody>
 *     <tr>
 *       <td>oj-masonrylayout-tile-1x1</td>
 *       <td>A tile that spans 1 column and 1 row.</td>
 *     </tr>
 *     <tr>
 *       <td>oj-masonrylayout-tile-1x2</td>
 *       <td>A tile that spans 1 column and 2 rows.</td>
 *     </tr>
 *     <tr>
 *       <td>oj-masonrylayout-tile-1x3</td>
 *       <td>A tile that spans 1 column and 3 rows.</td>
 *     </tr>
 *     <tr>
 *       <td>oj-masonrylayout-tile-2x1</td>
 *       <td>A tile that spans 2 columns and 1 row.</td>
 *     </tr>
 *     <tr>
 *       <td>oj-masonrylayout-tile-2x2</td>
 *       <td>A tile that spans 2 columns and 2 rows.</td>
 *     </tr>
 *     <tr>
 *       <td>oj-masonrylayout-tile-2x3</td>
 *       <td>A tile that spans 2 columns and 3 rows.</td>
 *     </tr>
 *     <tr>
 *       <td>oj-masonrylayout-tile-3x1</td>
 *       <td>A tile that spans 3 columns and 1 row.</td>
 *     </tr>
 *     <tr>
 *       <td>oj-masonrylayout-tile-3x2</td>
 *       <td>A tile that spans 3 columns and 2 rows.</td>
 *     </tr>
 *   </tbody>
 * </table>
 * <p>The number of columns in the layout is determined by the width of the 
 * component and the width of a 1x1 tile.  The number of rows is determined by
 * the number of columns and the number and sizes of tiles to be laid out.  When
 * the component is resized, relayout will occur and the number of columns and
 * rows may change.  
 * <p>When performing layout, tiles are processed in the order in which they
 * originally appeared in the DOM.  Cells in the grid are processed in 
 * left-to-right, top-to-bottom order (or right-to-left, top-to-bottom order 
 * when the reading direction is right-to-left).  A tile will be positioned in 
 * the first empty cell in which it fits.  This can result in empty cells in the
 * layout.  Subsequent tiles may fill those earlier gaps if they fit.  
 * 
 * <pre class="prettyprint">
 * <code>
 * <div id="masonryLayout" data-bind="ojComponent: {component: 'ojMasonryLayout'}">
 *   <div id="tile1" class="oj-masonrylayout-tile-1x1">Alpha</div>
 *   <div id="tile2" class="oj-masonrylayout-tile-1x1">Beta</div>
 *   <div id="tile3" class="oj-masonrylayout-tile-1x1">Gamma</div>
 *   <div id="tile4" class="oj-masonrylayout-tile-1x1">Delta</div>
 *   <div id="tile5" class="oj-masonrylayout-tile-1x1">Epsilon</div>
 *   <div id="tile6" class="oj-masonrylayout-tile-1x1">Zeta</div>
 * </div>
 * </code></pre>
 *
 *
 * <h3 id="touch-section">
 *   Touch End User Information
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#touch-section"></a>
 * </h3>
 *
 * {@ojinclude "name":"touchDoc"}
 * 
 * 
 * <h3 id="keyboard-section">
 *   Keyboard End User Information
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#keyboard-section"></a>
 * </h3>
 * 
 * {@ojinclude "name":"keyboardDoc"}
 * 
 * 
 * <h3 id="keyboard-appdev-section">
 *   Keyboard Application Developer Information
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#keyboard-appdev-section"></a>
 * </h3>
 * 
 * <p>Providing keyboard support for the tiles in the masonry layout is the 
 * responsibility of the application developer, if the tiles do not already
 * support keyboard interaction.  This could be done, for example, by specifying 
 * <code class="prettyprint">tabindex</code> on each tile to enable tab
 * navigation.  Alternatively, this could be done by adding a keyboard listener
 * and responding to key events, like pressing the arrow keys.
 * 
 * 
 * <h3 id="accessibility-section">
 *   Accessibility
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#accessibility-section"></a>
 * </h3>
 *
 * <p>MasonryLayout is for layout only.  It is the responsibility of the 
 * application developer to make the tiles in the layout accessible.  Sighted 
 * keyboard-only users need to be able to access the tiles in the layout just by
 * using the keyboard.  It is up to the child tiles of the MasonryLayout to 
 * support keyboard navigation.  The MasonryLayout reorders the tile DOM
 * elements to match the visual layout order so that tab order and screen reader
 * reading order will match the visual layout order.
 * 
 * 
 * <h3 id="rtl-section">
 *   Reading direction
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#rtl-section"></a>
 * </h3>
 * 
 * <p>As with any JET component, in the unusual case that the directionality 
 * (LTR or RTL) changes post-init, the MasonryLayout must be 
 * <code class="prettyprint">refresh()</code>ed.
 * 
 * 
 * <h3 id="pseudos-section">
 *   Pseudo-selectors
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#pseudos-section"></a>
 * </h3>
 * 
 * <p>The <code class="prettyprint">:oj-masonrylayout</code> pseudo-selector can 
 * be used in jQuery expressions to select JET MasonryLayout.  For example:
 * 
 * <pre class="prettyprint">
 * <code>$( ":oj-masonrylayout" ) // selects all JET MasonryLayouts on the page
 * $myEventTarget.closest( ":oj-masonrylayout" ) // selects the closest ancestor that is a JET MasonryLayout
 * </code></pre>
 * 
 * 
 * <h3 id="jqui2jet-section">
 *   JET for jQuery UI developers
 *   <a class="bookmarkable-link" title="Bookmarkable Link" href="#jqui2jet-section"></a>
 * </h3>
 * 
 * <p>Event names for all JET components are prefixed with "oj", instead of 
 * component-specific prefixes like "masonryLayout".  
 * 
 * <!-- - - - - Above this point, the tags are for the class.
 *              Below this point, the tags are for the constructor (initializer). - - - - - - -->
 * 
 * @desc Creates a JET MasonryLayout. 
 * @example <caption>Initialize the MasonryLayout with no options specified:</caption>
 * $( ".selector" ).ojMasonryLayout();
 * 
 * @example <caption>Initialize the MasonryLayout with some options specified:</caption>
 * $( ".selector" ).ojMasonryLayout( { "reorderHandle": ".appReorderHandle" } );
 * 
 * @example <caption>Initialize the MasonryLayout via the JET 
 * <code class="prettyprint">ojComponent</code> binding:</caption>
 * <div id="masonryLayout" data-bind="ojComponent: { component: 'ojMasonryLayout', reorderHandle: '.appReorderHandle'}">
 */
(function()
{
oj.__registerWidget("oj.ojMasonryLayout", $['oj']['baseComponent'],
{
  defaultElement: "<div>",
  widgetEventPrefix: "oj",

  options:
  {
    /** 
     * Specify the string jQuery selector of the descendant DOM element in a 
     * <code class="prettyprint">masonryLayout</code> child that can be used to 
     * reorder the child by drag-and-drop.  
     * 
     * <p>This option is <code class="prettyprint">null</code> by default, 
     * meaning that <code class="prettyprint">masonryLayout</code> children 
     * cannot be reordered.  If each child that can be reordered has an element 
     * with style class <code class="prettyprint">'my-reorder-handle'</code>, 
     * then <code class="prettyprint">reorderHandle</code> would be specified as 
     * <code class="prettyprint">'.my-reorder-handle'</code>.
     * 
     * <p>When specifying a <code class="prettyprint">reorderHandle</code>, 
     * the application should also specify a context menu with actions to cut
     * and paste tiles as an accessible alternative to drag-and-drop reordering.
     *
     * @expose 
     * @memberof oj.ojMasonryLayout
     * @instance
     * @type {?string}
     * @default <code class="prettyprint">null</code>
     * @see #contextMenu
     *
     * @example <caption>Initialize the masonryLayout with the 
     * <code class="prettyprint">reorderHandle</code> option specified:</caption>
     * // HTML
     * <div>                              // masonryLayout DOM element
     *   <div id="child1">                  // child DOM element
     *     <div class="my-reorder-handle"/>   // reorder handle
     *   </div>
     *   <div id="child2">                  // child DOM element
     *     <div class="my-reorder-handle"/>   // reorder handle
     *   </div>
     *   <div id="child3">                  // child DOM element
     *     <div class="my-reorder-handle"/>   // reorder handle
     *   </div>
     *   <div id="child4">                  // child DOM element
     *     <div class="my-reorder-handle"/>   // reorder handle
     *   </div>
     *   <div id="child5">                  // child DOM element
     *     <div class="my-reorder-handle"/>   // reorder handle
     *   </div>
     * </div>
     * 
     * // JS
     * $( ".selector" ).ojMasonryLayout( { "reorderHandle": ".my-reorder-handle" } );
     * 
     * @example <caption>Get or set the <code class="prettyprint">reorderHandle</code> 
     * option after initialization:</caption>
     * // getter
     * var contentParent = $( ".selector" ).ojMasonryLayout( "option", "reorderHandle" );
     * 
     * // setter
     * $( ".selector" ).ojMasonryLayout( "option", "reorderHandle", ".my-reorder-handle" );
     */
    reorderHandle: null,
    
    /**
     * MasonryLayout inherits the <code class="prettyprint">disabled</code> 
     * option from its superclass, but does not support it in order to avoid 
     * tight coupling between a MasonryLayout and its contents.  Setting this
     * option on MasonryLayout has no effect.
     * 
     * <p><b>WARNING:</b> Applications should not depend on this behavior 
     * because we reserve the right to change it in the future in order to 
     * support <code class="prettyprint">disabled</code> and propagate it to 
     * child components of MasonryLayout.  
     * 
     * @member
     * @name disabled
     * @memberof oj.ojMasonryLayout
     * @instance
     * @type {boolean}
     * @default <code class="prettyprint">false</code>
     */
    // disabled option declared in superclass, but we still want the above API doc

    /**
     * Identifies the JET Menu that the component should launch as a context 
     * menu on right-click or <kbd>Shift-F10</kbd>. If specified, the browser's 
     * native context menu will be replaced by the specified JET Menu.
     * 
     * <p>To specify a JET context menu on a DOM element that is not a JET 
     * component, see the <code class="prettyprint">ojContextMenu</code> binding.  
     * 
     * <p>To make the page semantically accurate from the outset, applications 
     * are encouraged to specify the context menu via the standard HTML5 syntax 
     * shown in the below example.  When the component is initialized, the 
     * context menu thus specified will be set on the component.
     *
     * <p>When defining a contextMenu, ojMasonryLayout will provide built-in 
     * behavior for "cut" and "paste" if the following format for menu <li> 
     * items is used (no <a> elements are required):
     * <ul><li> <li data-oj-command="oj-masonrylayout-cut" /></li>
     *     <li> <li data-oj-command="oj-masonrylayout-paste-before" /></li>
     *     <li> <li data-oj-command="oj-masonrylayout-paste-after" /></li>
     * </ul>
     * The available translated text will be applied to menu items defined this way.
     *
     * <p>The JET Menu should be initialized before any component using it as a 
     * context menu.
     * 
     * @member
     * @name contextMenu
     * @memberof! oj.ojMasonryLayout
     * @instance
     * @type {string | null}
     * @default <code class="prettyprint">null</code>
     * 
     * @example <caption>Initialize a JET MasonryLayout with a context menu:</caption>
     * // via recommended HTML5 syntax:
     * <div id="myMasonryLayout" contextmenu="myContextMenu" data-bind="ojComponent: { ... }>
     * 
     * // via JET initializer (less preferred) :
     * $( ".selector" ).ojMasonryLayout({ "contextMenu": "#myContextMenu"  ... } });
     * 
     * @example <caption>Get or set the <code class="prettyprint">contextMenu</code> 
     * option for an ojMasonryLayout after initialization:</caption>
     * // getter
     * var menu = $( ".selector" ).ojMasonryLayout( "option", "contextMenu" );
     * 
     * // setter
     * $( ".selector" ).ojMasonryLayout( "option", "contextMenu", "#myContextMenu" );
     */
    // contextMenu option declared in superclass, but we still want the above API doc
    
    //event callbacks
    
    /**
     * Triggered immediately before a tile is inserted.
     * The beforeInsert can be cancelled by calling 
     * <code class="prettyprint">event.preventDefault()</code>.
     *
     * @expose 
     * @event 
     * @memberof oj.ojMasonryLayout
     * @instance
     * @property {Event} event <code class="prettyprint">jQuery</code> event object
     * @property {Object} ui Parameters
     * @property {jQuery} ui.tile The tile that is about to be inserted.
     * @property {number} ui.index The 0-based index into the set of rendered
     *           <code class="prettyprint">masonryLayout</code> children where 
     *           the tile will be inserted.
     * 
     * @example <caption>Initialize the masonryLayout with the 
     * <code class="prettyprint">beforeInsert</code> callback specified:</caption>
     * $( ".selector" ).ojMasonryLayout({
     *     "beforeInsert": function( event, ui ) {}
     * });
     *
     * @example <caption>Bind an event listener to the 
     * <code class="prettyprint">ojbeforeinsert</code> event:</caption>
     * $( ".selector" ).on( "ojbeforeinsert", function( event, ui ) {} );
     */
    beforeInsert: null,
    
    /**
     * Triggered immediately after a tile is inserted.
     *
     * @expose 
     * @event 
     * @memberof oj.ojMasonryLayout
     * @instance
     * @property {Event} event <code class="prettyprint">jQuery</code> event object
     * @property {Object} ui Parameters
     * @property {jQuery} ui.tile The tile that was inserted.
     * @property {number} ui.index The 0-based index into the set of rendered
     *           <code class="prettyprint">masonryLayout</code> children where 
     *           the tile was inserted.
     * 
     * @example <caption>Initialize the masonryLayout with the 
     * <code class="prettyprint">insert</code> callback specified:</caption>
     * $( ".selector" ).ojMasonryLayout({
     *     "insert": function( event, ui ) {}
     * });
     *
     * @example <caption>Bind an event listener to the 
     * <code class="prettyprint">ojinsert</code> event:</caption>
     * $( ".selector" ).on( "ojinsert", function( event, ui ) {} );
     */
    insert: null,
    
    /**
     * Triggered immediately before a tile is removed.
     * The beforeRemove can be cancelled by calling 
     * <code class="prettyprint">event.preventDefault()</code>.
     *
     * @expose 
     * @event 
     * @memberof oj.ojMasonryLayout
     * @instance
     * @property {Event} event <code class="prettyprint">jQuery</code> event object
     * @property {Object} ui Parameters
     * @property {jQuery} ui.tile The tile that will be removed.
     * 
     * @example <caption>Initialize the masonryLayout with the 
     * <code class="prettyprint">beforeRemove</code> callback specified:</caption>
     * $( ".selector" ).ojMasonryLayout({
     *     "beforeRemove": function( event, ui ) {}
     * });
     *
     * @example <caption>Bind an event listener to the 
     * <code class="prettyprint">ojbeforeremove</code> event:</caption>
     * $( ".selector" ).on( "ojbeforeremove", function( event, ui ) {} );
     */
    beforeRemove: null,
    
    /**
     * Triggered immediately after a tile is removed.
     *
     * @expose 
     * @event 
     * @memberof oj.ojMasonryLayout
     * @instance
     * @property {Event} event <code class="prettyprint">jQuery</code> event object
     * @property {Object} ui Parameters
     * @property {jQuery} ui.tile The tile that was removed.
     * 
     * @example <caption>Initialize the masonryLayout with the 
     * <code class="prettyprint">remove</code> callback specified:</caption>
     * $( ".selector" ).ojMasonryLayout({
     *     "remove": function( event, ui ) {}
     * });
     *
     * @example <caption>Bind an event listener to the 
     * <code class="prettyprint">ojremove</code> event:</caption>
     * $( ".selector" ).on( "ojremove", function( event, ui ) {} );
     */
    remove: null,
    
    /**
     * Triggered immediately before a tile is resized.
     * The beforeResize can be cancelled by calling 
     * <code class="prettyprint">event.preventDefault()</code>.
     *
     * @expose 
     * @event 
     * @memberof oj.ojMasonryLayout
     * @instance
     * @property {Event} event <code class="prettyprint">jQuery</code> event object
     * @property {Object} ui Parameters
     * @property {jQuery} ui.tile The tile that will be resized.
     * @property {string} ui.previousSizeStyleClass The previous size style
     *           class applied to the tile.
     * @property {string} ui.sizeStyleClass The new size style class that will
     *           be applied to the tile.
     * 
     * @example <caption>Initialize the masonryLayout with the 
     * <code class="prettyprint">beforeResize</code> callback specified:</caption>
     * $( ".selector" ).ojMasonryLayout({
     *     "beforeResize": function( event, ui ) {}
     * });
     *
     * @example <caption>Bind an event listener to the 
     * <code class="prettyprint">ojbeforeresize</code> event:</caption>
     * $( ".selector" ).on( "ojbeforeresize", function( event, ui ) {} );
     */
    beforeResize: null,
    
    /**
     * Triggered immediately after a tile is resized.
     *
     * @expose 
     * @event 
     * @memberof oj.ojMasonryLayout
     * @instance
     * @property {Event} event <code class="prettyprint">jQuery</code> event object
     * @property {Object} ui Parameters
     * @property {jQuery} ui.tile The tile that was resized.
     * @property {string} ui.previousSizeStyleClass The previous size style
     *           class applied to the tile.
     * @property {string} ui.sizeStyleClass The new size style class applied to 
     *           the tile.
     * 
     * @example <caption>Initialize the masonryLayout with the 
     * <code class="prettyprint">resize</code> callback specified:</caption>
     * $( ".selector" ).ojMasonryLayout({
     *     "resize": function( event, ui ) {}
     * });
     *
     * @example <caption>Bind an event listener to the 
     * <code class="prettyprint">ojresize</code> event:</caption>
     * $( ".selector" ).on( "ojresize", function( event, ui ) {} );
     */
    resize: null,
    
    /**
     * Triggered immediately before a tile is reordered.
     * The beforeReorder can be cancelled by calling 
     * <code class="prettyprint">event.preventDefault()</code>.
     *
     * @expose 
     * @event 
     * @memberof oj.ojMasonryLayout
     * @instance
     * @property {Event} event <code class="prettyprint">jQuery</code> event object
     * @property {Object} ui Parameters
     * @property {jQuery} ui.tile The tile that will be reordered.
     * @property {number} ui.fromIndex The 0-based index into the set of rendered
     *           <code class="prettyprint">masonryLayout</code> children from 
     *           which the tile will be reordered.  
     * 
     * @example <caption>Initialize the masonryLayout with the 
     * <code class="prettyprint">beforeReorder</code> callback specified:</caption>
     * $( ".selector" ).ojMasonryLayout({
     *     "beforeReorder": function( event, ui ) {}
     * });
     *
     * @example <caption>Bind an event listener to the 
     * <code class="prettyprint">ojbeforereorder</code> event:</caption>
     * $( ".selector" ).on( "ojbeforereorder", function( event, ui ) {} );
     */
    beforeReorder: null,
    
    /**
     * Triggered immediately after a tile is reordered.
     *
     * @expose 
     * @event 
     * @memberof oj.ojMasonryLayout
     * @instance
     * @property {Event} event <code class="prettyprint">jQuery</code> event object
     * @property {Object} ui Parameters
     * @property {jQuery} ui.tile The tile that was reordered.
     * @property {number} ui.fromIndex The 0-based index into the set of rendered
     *           <code class="prettyprint">masonryLayout</code> children from 
     *           which the tile was reordered.  
     * @property {number} ui.toIndex The 0-based index into the set of rendered
     *           <code class="prettyprint">masonryLayout</code> children to 
     *           which the tile was reordered.  
     * 
     * @example <caption>Initialize the masonryLayout with the 
     * <code class="prettyprint">reorder</code> callback specified:</caption>
     * $( ".selector" ).ojMasonryLayout({
     *     "reorder": function( event, ui ) {}
     * });
     *
     * @example <caption>Bind an event listener to the 
     * <code class="prettyprint">ojreorder</code> event:</caption>
     * $( ".selector" ).on( "ojreorder", function( event, ui ) {} );
     */
    reorder: null

    /**
     * Triggered when the masonryLayout is created.
     *
     * @event
     * @name create
     * @memberof oj.ojMasonryLayout
     * @instance
     * @property {Event} event <code class="prettyprint">jQuery</code> event object
     * @property {Object} ui Currently empty
     *
     * @example <caption>Initialize the masonryLayout with the <code class="prettyprint">create</code> callback specified:</caption>
     * $( ".selector" ).ojMasonryLayout({
     *     "create": function( event, ui ) {}
     * });
     *
     * @example <caption>Bind an event listener to the <code class="prettyprint">ojcreate</code> event:</caption>
     * $( ".selector" ).on( "ojcreate", function( event, ui ) {} );
     */
    // create event declared in superclass, but we still want the above API doc
  },

  /**
   * After _ComponentCreate and _AfterCreate, 
   * the widget should be 100% set up. this._super should be called first.
   * @override
   * @protected
   * @instance
   * @memberof oj.ojMasonryLayout
   */
  _ComponentCreate: function() // Override of protected base class method.  
  {
    //call superclass first
    this._super();
    
    var elem = this.element;  
    elem.addClass("oj-masonrylayout oj-component");
    
    //log warning message when "disabled" option set
    var options = this.options;
    if (options.disabled)
    {
      oj.Logger.warn(_WARNING_DISABLED_OPTION);
    }
    
    this.reorderHandleEventNamespace = this.eventNamespace + "ReorderHandle";
    
    this._menu = {};
    this._menu.usermenu = false;
    this._menu.$container = false;
    this._menu.$elemCut = false;
    this._menu.$elemPasteBefore = false;
    this._menu.$elemPasteAfter = false;

    this._initMenu();
    this._applyMenu();
    
    this._setup(true);
  },

  // This method currently runs at create, init, and refresh time (since refresh() is called by _init()).
  /**
   * Refreshes the visual state of the masonryLayout. JET components require a 
   * <code class="prettyprint">refresh()</code> or re-init after the DOM is 
   * programmatically changed underneath the component.
   * 
   * <p>This method does not accept any arguments.
   * 
   * @expose 
   * @memberof oj.ojMasonryLayout
   * @instance
   * 
   * @example <caption>Invoke the <code class="prettyprint">refresh</code> method:</caption>
   * $( ".selector" ).ojMasonryLayout( "refresh" );
   */
  refresh: function() // Override of public base class method.  
  {
    this._super();
    
    //if RTL has changed, just destroy and recreate the MasonryLayoutCommon
    var bRTL = (this._GetReadingDirection() === "rtl");
    var bRecreate = (bRTL !== this._bRTL);
    if (bRecreate)
      this._destroyMLCommon();
    this._setup(bRecreate);
  },
  
  /**
   * Notifies the component that its subtree has been made visible 
   * programmatically after the component has been created.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @protected 
   * @override
   */
  _NotifyShown: function()
  {
    this._super();
    //perform a deferred layout
    if (this._needsSetup)
      this._setup(this._needsSetup[0]);
  },
  
  /**
   * Notifies the component that its subtree has been connected to the document
   * programmatically after the component has been created.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @protected 
   * @override
   */
  _NotifyAttached: function()
  {
    this._super();
    //perform a deferred layout
    if (this._needsSetup)
      this._setup(this._needsSetup[0]);
  },

  /**
   * Notifies the component that its context menu is opening.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @protected 
   * @override
   */
  _NotifyContextMenuGesture: function(menu, event, eventType)
  {
    //prepare the context menu here instead of in a context menu "beforeOpen" event
    this._prepareContextMenuBeforeOpen(event);
    
    this._OpenContextMenu(event, eventType, {"launcher": $(event.target).closest(":tabbable")});
  },

  // isInit is true for init (create and re-init), false for refresh
  /** 
   * Setup the masonryLayout.
   * @param {boolean} isInit true if _setup is called from _init(), false
   *        if called from refresh()
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _setup: function(isInit) // Private, not an override (not in base class).  
  {
    //if masonryLayout is detached or hidden, we can't layout correctly, so 
    //defer layout until masonryLayout is attached or shown
    if (!this._canCalculateSizes())
    {
      //want a true value of isInit to take precedence over a false value
      var oldIsInit = false;
      if (this._needsSetup)
      {
        oldIsInit = this._needsSetup[0];
      }
      this._needsSetup = [isInit || oldIsInit];
      return;
    }
    this._needsSetup = null;
    
    this._bRTL = (this._GetReadingDirection() === "rtl");
    this._bTouchSupported = oj.DomUtils.isTouchSupported();
    var elem = this.element;  
    var options = this.options;
    if (isInit)
    {
      var self = this;
      this._showTileOnEndFunc = function(elem) {self._showTileOnEnd(elem);};
      this._hideTileOnEndFunc = function(elem) {self._hideTileOnEnd(elem);};
      this._layoutOnEndFunc = function() {self._layoutOnEnd();};
      this._layoutCycleOnStartFunc = function() {self._layoutCycleOnStart();};
      this._layoutCycleOnEndFunc = function() {self._layoutCycleOnEnd();};
      if (!this._mlCommon)
      {
        var selectors = {};
        selectors.tiles = _TILE_SELECTOR;
        var styles = {};
        styles.transitionComponentResizeToStyleClass = "oj-masonrylayout-transition-resize-to";
        styles.transitionComponentResizeToFastStyleClass = "oj-masonrylayout-transition-resize-to-fast";
        styles.transitionMoveToStyleClass = "oj-masonrylayout-tile-transition-move-to";
        styles.transitionMoveToFastStyleClass = "oj-masonrylayout-tile-transition-move-to-fast";
        styles.transitionHideFromStyleClass = "oj-masonrylayout-tile-transition-hide-from";
        styles.transitionHideToStyleClass = "oj-masonrylayout-tile-transition-hide-to";
        styles.transitionShowFromStyleClass = _OJ_MASONRYLAYOUT_TILE_TRANSITION_SHOW_FROM_CLASS;
        styles.transitionShowToStyleClass = "oj-masonrylayout-tile-transition-show-to";
        styles.transitionResizeToStyleClass = "oj-masonrylayout-tile-transition-resize-to";
        var callbackInfo = {};
        callbackInfo.addStyleClassName = _addStyleClassName;
        callbackInfo.removeStyleClassName = _removeStyleClassName;
        callbackInfo.getSizeStyleClassName = _getSizeStyleClassName;
        callbackInfo.getTileSpan = _getTileSpan;
        callbackInfo.showTileOnEndFunc = this._showTileOnEndFunc;
        callbackInfo.hideTileOnEndFunc = this._hideTileOnEndFunc;
        callbackInfo.layoutOnEndFunc = this._layoutOnEndFunc;
        callbackInfo.layoutCycleOnStartFunc = this._layoutCycleOnStartFunc;
        callbackInfo.layoutCycleOnEndFunc = this._layoutCycleOnEndFunc;
        callbackInfo.sortTilesOriginalOrderFunc = _sortTilesOriginalOrder;
        callbackInfo.subtreeAttached = oj.Components.subtreeAttached;
        callbackInfo.subtreeDetached = oj.Components.subtreeDetached;
        
        //save the original order of the tiles, because the DOM order may 
        //change due to layout and we want to always use the original order
        //for running subsequent layouts
        this._saveTilesOriginalOrder();
        
        this._mlCommon = new MasonryLayoutCommon(
            elem[0], 
            this._bRTL, 
            oj.Config.getAutomationMode() === "enabled",
            selectors, 
            styles, 
            callbackInfo);
      }
      
      this._handleDragStartFunc = function(event) {self._handleDragStart(event);};
      this._handleDragEnterFunc = function(event) {self._handleDragEnter(event);};
      this._handleDragOverFunc = function(event) {self._handleDragOver(event);};
      this._handleDragLeaveFunc = function(event) {self._handleDragLeave(event);};
      this._handleDragEndFunc = function(event) {self._handleDragEnd(event);};
      this._handleDropFunc = function(event) {self._handleDrop(event);};
      if (options.reorderHandle)
        this._setupReorderHandles();
    }
    
    var mlCommon = this._mlCommon;
    mlCommon.setup(isInit);
    
    if (isInit)
    {
      this._handleResizeFunc = function(width, height) {self._handleResize();};
      oj.DomUtils.addResizeListener(elem[0], this._handleResizeFunc);
    }
  },

  /** 
   * Destroy the masonryLayout.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @override
   * @protected
   */
  _destroy: function() // Override of protected base class method.  
  {
    this._clearMenu();
    
    var elem = this.element;
    oj.DomUtils.removeResizeListener(elem[0], this._handleResizeFunc);
    this._handleResizeFunc = null;
    
    //restore tiles to their original order and delete stored indices
    this._restoreTilesOriginalOrder();
    var children = this._getTileElements();
    var numChildren = children.length;
    for (var i = 0; i < numChildren; i++)
    {
      var child = children[i];
      delete child._jetDataMasonryOriginalOrder;
    }
    
    this._destroyMLCommon();
    
    elem.removeClass("oj-masonrylayout oj-component");
    var options = this.options;
    if (options.reorderHandle)
    {
      this._tearDownReorderHandles();
    }
    this._handleDragStartFunc = null;
    this._handleDragEnterFunc = null;
    this._handleDragOverFunc = null;
    this._handleDragLeaveFunc = null;
    this._handleDragEndFunc = null;
    this._handleDropFunc = null;
    
    this._showTileOnEndFunc = null;
    this._hideTileOnEndFunc = null;
    this._layoutOnEndFunc = null;
    this._layoutCycleOnStartFunc = null;
    this._layoutCycleOnEndFunc = null;
    
    this._arTilesToInsert = null;
    
    this._super();
  },

  /** 
   * Set an option on the masonryLayout.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @override
   * @protected
   */
  _setOption: function(key, value, flags) // Override of protected base class method.
                                   // Method name needn't be quoted since is in externs.js.
  {
    var elem = this.element;
    var options = this.options;
    var bReorderHandle = false;
    switch (key) 
    {
      case "reorderHandle":
        this._tearDownReorderHandles();
        bReorderHandle = true;
        break;
      case "disabled":
        //log warning message when "disabled" option set
        oj.Logger.warn(_WARNING_DISABLED_OPTION);
        break;
      case "contextMenu":
        if (!oj.DomUtils.isTouchSupported())
        {
          this._clearMenu();
          if (value)
            this._initMenu(value);
        }
        break;
    }
    this._super(key, value, flags);
    if (bReorderHandle && value)
    {
      this._setupReorderHandles();
    }
  },
  
  /**
   * Resize a tile in the masonryLayout.
   * @param {string} selector Selector identifying the tile to resize.
   * @param {string} sizeStyleClass New size style class to apply to the tile.
   * 
   * @expose 
   * @memberof oj.ojMasonryLayout
   * @instance
   * 
   * @example <caption>Invoke the <code class="prettyprint">resizeTile</code> method:</caption>
   * $( ".selector" ).ojMasonryLayout( "resizeTile", "#tileSelector", "oj-masonrylayout-tile-2x1" );
   */
  resizeTile: function(selector, sizeStyleClass)
  {
    var mlCommon = this._mlCommon;
    //FIX BUG 19996416: if we're already in a layout cycle, finish it immediately
    //before processing the resize
    if (mlCommon.isInLayoutCycle())
      mlCommon.finishLayoutCycle();

    var jqElem = $(selector);
    var elem = jqElem[0];
    var prevSizeStyleClass = _getSizeStyleClassName(elem);
    
    //FIX BUG 20008576: if the style class isn't changing, don't do anything;
    //this prevents the layout from preparing for a resize and then waiting for
    //the end of a resize animation that won't happen
    //FIX BUG 20867456: throw error if style class isn't changing
    if (sizeStyleClass == prevSizeStyleClass)
    {
      throw new Error("JET MasonryLayout: Unable to resize child " + selector +
                      " to style class " + sizeStyleClass + " because " + 
                      sizeStyleClass + " is already applied.");
    }
    
    //fire beforeResize event
    var eventData = {
      /** @expose */
      tile: jqElem,
      /** @expose */
      previousSizeStyleClass: prevSizeStyleClass,
      /** @expose */
      sizeStyleClass: sizeStyleClass
    };
    var bSuccess = this._trigger("beforeResize", null, eventData);
    
    if (bSuccess !== false)
    {
      if (!this._arResizingTiles)
        this._arResizingTiles = [];
      var arResizingTiles = this._arResizingTiles;
      arResizingTiles.push(elem, prevSizeStyleClass, sizeStyleClass);

      mlCommon.resizeTile(selector, sizeStyleClass);
    }
  },
  
  /**
   * Insert a tile into the masonryLayout.
   * @param {string} selector Selector identifying the tile to insert.  The tile
   *        does not need to be a child of the 
   *        <code class="prettyprint">masonryLayout</code> when this method is
   *        called.  This method will reparent the tile to the 
   *        <code class="prettyprint">masonryLayout</code>.
   * @param {number} index The 0-based index into the set of rendered
   *           <code class="prettyprint">masonryLayout</code> children where 
   *           the tile will be inserted.
   * 
   * @expose 
   * @memberof oj.ojMasonryLayout
   * @instance
   * 
   * @example <caption>Invoke the <code class="prettyprint">insertTile</code> method:</caption>
   * $( ".selector" ).ojMasonryLayout( "insertTile", "#tileSelector", 2 );
   */
  insertTile: function(selector, index)
  {
    var mlCommon = this._mlCommon;
    //FIX BUG 19996416: if we're already in a layout cycle, finish it immediately
    //before processing the insert
    if (mlCommon.isInLayoutCycle())
      mlCommon.finishLayoutCycle();

    if (isNaN(index))
      index = -1;
    
    var jqElem = $(selector);
    var elem = jqElem[0];
    
    //fire beforeInsert event
    var eventData = {
      /** @expose */
      tile: jqElem,
      /** @expose */
      index: index
    };
    var bSuccess = this._trigger("beforeInsert", null, eventData);
    
    if (bSuccess !== false)
    {
      elem._jetDataTileInsertIndex = index;
      
      //only need to initially hide inserts if they will be animated later,
      //otherwise we can just leave them shown here
      if (mlCommon.isAnimationEnabled())
      {
        //immediately hide insert in order to show it later with a transition
        jqElem.addClass(_OJ_MASONRYLAYOUT_TILE_TRANSITION_SHOW_FROM_CLASS);
      }

      var style = elem.style;
      style.top = "-1px";
      //FIX BUG 19826013: in RTL, position tiles using "right" instead of "left"
      if (this._bRTL)
        style.right = "-1px";
      else
        style.left = "-1px";
      
      this._insertTileOriginalOrder(elem, index);
      mlCommon.insertTileDomElem(elem, index);
      //notify the element that it's been attached to the DOM
      oj.Components.subtreeAttached(elem);

      if (!this._arTilesToInsert)
        this._arTilesToInsert = [];
      var arTilesToInsert = this._arTilesToInsert;
      arTilesToInsert.push(selector);
    }
  },
  
  /**
   * Remove a tile from the masonryLayout.
   * @param {string} selector Selector identifying the tile to remove.
   * 
   * @expose 
   * @memberof oj.ojMasonryLayout
   * @instance
   * 
   * @example <caption>Invoke the <code class="prettyprint">removeTile</code> method:</caption>
   * $( ".selector" ).ojMasonryLayout( "removeTile", "#tileSelector" );
   */
  removeTile: function(selector)
  {
    var mlCommon = this._mlCommon;
    //FIX BUG 19996416: if we're already in a layout cycle, finish it immediately
    //before processing the delete
    if (mlCommon.isInLayoutCycle())
      mlCommon.finishLayoutCycle();

    //if the infolet being deleted contains the focus, remember that so we can 
    //try to restore focus to the next infolet after layout
    var jqInfolet = $(selector);
    var infolet = jqInfolet[0];
    if (oj.FocusUtils.containsFocus(infolet))
    {
      var children = this._getTileElements(true);
      var index = children.indexOf(infolet);
      //save the previous infolet, because this one is being deleted
      if (index > 0)
        this._deletingTileWithFocusPrev = children[index - 1];
    }
    
    //fire beforeRemove event
    var eventData = {
      /** @expose */
      tile: jqInfolet
    };
    var bSuccess = this._trigger("beforeRemove", null, eventData);
    
    if (bSuccess !== false)
      mlCommon.hideTile(selector);
  },
  
  /** 
   * Handle a component resize.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _handleResize: function()
  {
    if (!this._bDragging && !this._inLayoutCycle)
    {
      var mlCommon = this._mlCommon;
      mlCommon.resizeNotify();
    }
  },
  
  /** 
   * Callback to run after a tile is shown.
   * @param {Object} elem Tile that was shown.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _showTileOnEnd: function(elem)
  {
    var index = elem._jetDataTileInsertIndex;
    delete elem._jetDataTileInsertIndex;
    
    //fire insert event
    var eventData = {
      /** @expose */
      tile: $(elem),
      /** @expose */
      index: index
    };
    this._trigger("insert", null, eventData);
  },
  
  /** 
   * Callback to run after a tile is hidden.
   * @param {!Element} elem Tile that was hidden.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _hideTileOnEnd: function(elem)
  {
    //notify the element that it's been detached from the DOM BEFORE actually
    //detaching it so that components can save state
    oj.Components.subtreeDetached(elem);
    
    var parentNode = elem.parentNode;
    parentNode.removeChild(elem);
    //remove the tile from the original order
    this._removeTileOriginalOrder(elem);
    
    //fire remove event
    var eventData = {
      /** @expose */
      tile: $(elem)
    };
    this._trigger("remove", null, eventData);
  },
  
  /** 
   * Callback to run after layout is done.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _layoutOnEnd: function()
  {
    if (this._arTilesToInsert)
    {
      var mlCommon = this._mlCommon;
      var arTilesToInsert = this._arTilesToInsert;
      for (var i = 0; i < arTilesToInsert.length; i++)
      {
        var selector = arTilesToInsert[i];
        mlCommon.showTile(selector);
      }
      this._arTilesToInsert = null;
    }
    
    if (this._arResizingTiles)
    {
      var arResizingTiles = this._arResizingTiles;
      for (var i = 0; i < arResizingTiles.length; i += 3)
      {
        var elem = arResizingTiles[i];
        var prevSizeStyleClass = arResizingTiles[i + 1];
        var sizeStyleClass = arResizingTiles[i + 2];
        
        //fire resize event
        var eventData = {
          /** @expose */
          tile: $(elem),
          /** @expose */
          previousSizeStyleClass: prevSizeStyleClass,
          /** @expose */
          sizeStyleClass: sizeStyleClass
        };
        this._trigger("resize", null, eventData);
      }
      this._arResizingTiles = null;
    }
    
    if (this._bDragging)
    {
      if (this._bDragMoveTransition)
      {
        this._handleDragMoveTransitionEnd();
      }
      else if (this._bDragEndTransition)
      {
        this._handleDragEndTransitionEnd();
      }
    }
  },
  
  /**
   * Callback to run before whole layout cycle starts.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _layoutCycleOnStart: function()
  {
    this._inLayoutCycle = true;
    this._layoutStartActiveDomElem = null;

    var activeDomElem = document.activeElement;
    //if the currently focused element is a child of the  masonryLayout, 
    //remember that so we can try to restore focus after layout, in case the 
    //child DOM order changes
    if (activeDomElem)
    {
      if (oj.DomUtils.isAncestor(this.element[0], activeDomElem))
        this._layoutStartActiveDomElem = activeDomElem;
    }
  },

  /**
   * Callback to run after whole layout cycle is done.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _layoutCycleOnEnd: function()
  {
    this._inLayoutCycle = false;
    
    var elem = this.element[0];
    //if we saved the focus element before layout, try to restore focus now
    if (this._layoutStartActiveDomElem)
    {
      var layoutStartActiveDomElem = this._layoutStartActiveDomElem;
      this._layoutStartActiveDomElem = null;
      //after deleting a tile, focus the tile that took its position in the layout
      if (this._deletingTileWithFocusPrev)
      {
        var tile = this._deletingTileWithFocusPrev;
        this._deletingTileWithFocusPrev = null;
        if (tile && oj.DomUtils.isAncestor(elem, tile))
        {
          var children = this._getTileElements(elem, true);
          var index = children.indexOf(tile);
          //because we saved the id of the prev tile, try to restore focus
          //to the tile after it, which would have been where the deleted
          //tile was positioned
          if (index >= 0 && index < children.length - 1)
            oj.FocusUtils.focusFirstTabStop(children[index + 1]);
          else
            oj.FocusUtils.focusFirstTabStop(tile);
        }
      }
      else
      {
        //restore focus to the element that had focus before layout, or to
        //the first tabstop in the masonryLayout
        if (oj.DomUtils.isAncestor(elem, layoutStartActiveDomElem))
          oj.FocusUtils.focusElement(layoutStartActiveDomElem);
        else
          oj.FocusUtils.focusFirstTabStop(elem);
      }
    }
  },

  /** 
   * Destroy the MasonryLayoutCommon.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _destroyMLCommon: function()
  {
    var mlCommon = this._mlCommon;
    if (mlCommon)
      mlCommon.destroy();
    this._mlCommon = null;
  },

  /** 
   * Determine whether the masonryLayout can calculate sizes (when it is 
   * attached to the page DOM and not hidden).
   * @returns {boolean} true if sizes can be calculated, false if not
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _canCalculateSizes: function() 
  {
    var div = document.createElement("div");
    var style = div.style;
    style.width = "10px";
    style.height = "10px";
    var elem = this.element[0];
    elem.appendChild(div);
    var bCanCalcSizes = false;
    try
    {
      bCanCalcSizes = div.offsetWidth > 0 && div.offsetHeight > 0;
    }
    catch (e)
    {
      //do nothing
    }
    elem.removeChild(div);
    return bCanCalcSizes;
  },
  
  /**
   * Get the child tile elements.
   * @param {boolean} excludeDropSite True to exclude the dropsite, false to 
   *        include it.
   * @returns {Array} Array of child tile elements.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _getTileElements: function(excludeDropSite)
  {
    var elem = this.element;
    var children = elem.children(_TILE_SELECTOR);
    var numChildren = children.length;
    var arChildren = [];
    for (var i = 0; i < numChildren; i++)
    {
      var child = children[i];
      if (!excludeDropSite || (excludeDropSite && child !== this._dropSite))
      {
        var style = child.style;
        if (style.visibility !== _HIDDEN && style.display !== _NONE)
          arChildren.push(child);
      }
    }
    return arChildren;
  },
  
  /**
   * Save the original DOM order of the tiles.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _saveTilesOriginalOrder: function()
  {
    var arTiles = this._getTileElements();
    if (arTiles)
    {
      for (var i = 0; i < arTiles.length; i++)
      {
        var tile = arTiles[i];
        //only assign original index to tile if it hasn't already been assigned,
        //so that repeated calls to _setup(true) won't overwrite the original
        //values with current layout order values
        if (!tile._jetDataMasonryOriginalOrder)
        {
          //make original order index 1-based so that we can easily check for
          //the existence of the attribute on an element
          //(e.g. "if (tile._jetDataMasonryOriginalOrder)")
          tile._jetDataMasonryOriginalOrder = i + 1;
        }
      }
    }
  },
  
  /**
   * Restore the original DOM order of the tiles.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _restoreTilesOriginalOrder: function()
  {
    var children = this._getTileElements();
    var sortedChildren = this._getTileElements();
    _sortTilesOriginalOrder(sortedChildren);
    for (var i = 0; i < children.length; i++)
    {
      var child = children[i];
      var sortedChild = sortedChildren[i];
      //if the tiles are different, need to rearrange the DOM
      if (child != sortedChild)
      {
        //notify the element that it's been detached from the DOM BEFORE actually
        //detaching it so that components can save state
        oj.Components.subtreeDetached(sortedChild);
        //insert the tile from the layout order into the DOM at this index
        var parentNode = child.parentNode;
        parentNode.insertBefore(sortedChild, child); // @HTMLUpdateOK
        //notify the element that it's been attached to the DOM
        oj.Components.subtreeAttached(sortedChild);

        //rearrange the array of children to match the DOM reorder above
        var sortedChildIndex = children.indexOf(sortedChild);
        if (sortedChildIndex > i)
        {
          children.splice(sortedChildIndex, 1);
          children.splice(i, 0, sortedChild);
        }
      }
    }
  },
  
  /**
   * Insert a tile into the original DOM order at the given index.
   * @param {Object} insertedTile Tile to insert.
   * @param {number} index Index at which to insert.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _insertTileOriginalOrder: function(insertedTile, index)
  {
    var arTiles = this._getTileElements();
    if (index < 0)
      index = arTiles.length;
    if (arTiles)
    {
      for (var i = 0; i < arTiles.length; i++)
      {
        var tile = arTiles[i];
        //increment the index of each shifted tile
        if (tile._jetDataMasonryOriginalOrder && tile._jetDataMasonryOriginalOrder >= index + 1)
          tile._jetDataMasonryOriginalOrder++;
      }
    }
    //save the index on the inserted tile AFTER the above loop so that its index 
    //didn't get incremented
    insertedTile._jetDataMasonryOriginalOrder = index + 1;
  },
  
  /**
   * Remove a tile from the original DOM order.
   * @param {Object} removedTile Tile to remove.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _removeTileOriginalOrder: function(removedTile)
  {
    if (removedTile._jetDataMasonryOriginalOrder)
    {
      var arTiles = this._getTileElements();
      if (arTiles)
      {
        for (var i = 0; i < arTiles.length; i++)
        {
          var tile = arTiles[i];
          //decrement the index of each shifted tile
          if (tile._jetDataMasonryOriginalOrder && 
              tile._jetDataMasonryOriginalOrder > removedTile._jetDataMasonryOriginalOrder)
            tile._jetDataMasonryOriginalOrder--;
        }
      }
      //delete the stored index from the tile
      delete removedTile._jetDataMasonryOriginalOrder;
    }
  },
  
  // start functions for context menu reordering ///////////////////////////////
  
  /**
   * Initialize the context menu.  This is called on startup, or on option
   * "contextMenu" change.
   * @param {Object=} newVal True if called because of an option change.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _initMenu: function(newVal)
  {
    var menu = null;
    var t = null;

    // check for contextmenu attribute on the root element
    if ((!newVal) && (!this.options["contextMenu"]))
    {
      menu = this.element.attr("contextmenu");
      if (menu)
        this.options["contextMenu"] = "#" + menu;
    }

    if ((!newVal) && (!this.options["contextMenu"]))
      return;

    menu = newVal || this.options["contextMenu"];
    t = $.type(menu);
    if (t == "function") {
      try
      {
        // call user's method to get the context menu
        menu = menu();
      }
      catch (e)
      {
        menu = null;
      }
      t = $.type(menu);
    }

    if (t !== "string")
      return;

    // get the user's <ul> list  
    var $m = $(menu); 
    if ($m)
    {
      // ensure it's not visible
      $m.css("display", _NONE);
      var dm = this._menu;
      if (!dm)
        return;

      dm.$container = $m;
      // have a context menu
      dm.usermenu   = true;
    }

    // if we have a context menu
    if (this._menu.usermenu)
    {
      // and it is being changed
      if (newVal)
      {
        // complete menu creation/attachment
        this._applyMenu();
      }
    }

    //  If not a new val from options, Menu will be noted at the end of initialization in _start()
  },

  /**
   * Replace built in shortcut reorder context menu items with real items and
   * add listeners to the context menu.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _applyMenu: function()
  {
    if (!this._menu || !this._menu.usermenu || !this.options.reorderHandle)
      return;

    // Add our listeners so that we can handle build-in cut/paste, etc
    var $menuContainer = this._menu.$container;
    var self = this;

    $menuContainer.on("ojselect",     $.proxy(this._handleContextMenuSelect,     this));

    // If there are any ojMasonryLayout built in menu item ids, construct the menu items
    var listItems = $menuContainer.find("[data-oj-command]");
    var bChanged  = false;

    listItems.each(function()
      {
        if ($(this).children('a').length === 0)
        {
          var command = $(this).attr('data-oj-command').slice("oj-masonrylayout-".length);
          $(this).replaceWith(self._buildContextMenuItem(command)); // @HTMLUpdateOK
          $(this).addClass("oj-menu-item")

          bChanged = true;
        }
      });

    if (bChanged)
      $menuContainer.ojMenu('refresh');

    this._menu.$elemCut = $menuContainer.find("#" + _OJMASONRYLAYOUTCUT);
    this._menu.$elemPasteBefore = $menuContainer.find("#" + _OJMASONRYLAYOUTPASTEBEFORE);
    this._menu.$elemPasteAfter = $menuContainer.find("#" + _OJMASONRYLAYOUTPASTEAFTER);
  },

  /**
   * Clear out any contextMenu data.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _clearMenu: function()
  {
    var menu = this._menu;
    if (menu && menu.usermenu)
    {
      menu.usermenu = false;
      menu.$container.off("ojselect");
      menu.$container = null;
    }
  },
  
  /**
   * Prepare the context menu before it's opened.
   * @param {Object} e jQuery event object.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _prepareContextMenuBeforeOpen: function(e)
  {
    var elem = this.element;
    var tile = _findContainingTile(e.originalEvent.target, elem[0]);
    this._menu.tile = tile;

    //set menu items disabled state
    if (this._menu.usermenu)
    {
      var cutTile = this._menu.cutTile;
      var bRefreshMenu = false;
      
      var elemCut = this._menu.$elemCut;
      if (elemCut)
      {
        var cutDisabled = elemCut.hasClass(_OJ_DISABLED);
        var bDisable = false;
        //disable "cut" if this tile has already been cut
        if (cutTile && tile === cutTile)
          bDisable = true;
        
        if (bDisable && !cutDisabled)
        {
          elemCut.addClass(_OJ_DISABLED);
          bRefreshMenu = true;
        }
        else if (!bDisable && cutDisabled)
        {
          elemCut.removeClass(_OJ_DISABLED);
          bRefreshMenu = true;
        }
      }
      
      var elemPasteBefore = this._menu.$elemPasteBefore;
      if (elemPasteBefore)
      {
        var pasteBeforeDisabled = elemPasteBefore.hasClass(_OJ_DISABLED);
        var bDisable = false;
        //disable "pasteBefore" if this is the tile that was cut or if the cut 
        //tile is the one before this tile
        if (!cutTile ||
            (tile === cutTile || tile === _getNextElement(cutTile)))
        {
          bDisable = true;
        }
        
        if (bDisable && !pasteBeforeDisabled)
        {
          elemPasteBefore.addClass(_OJ_DISABLED);
          bRefreshMenu = true;
        }
        else if (!bDisable && pasteBeforeDisabled)
        {
          elemPasteBefore.removeClass(_OJ_DISABLED);
          bRefreshMenu = true;
        }
      }
      
      var elemPasteAfter = this._menu.$elemPasteAfter;
      if (elemPasteAfter)
      {
        var pasteAfterDisabled = elemPasteAfter.hasClass(_OJ_DISABLED);
        var bDisable = false;
        //disable "pasteAfter" if this is the tile that was cut or if the cut 
        //tile is the one after this tile
        if (!cutTile ||
            (cutTile === tile || cutTile === _getNextElement(tile)))
        {
          bDisable = true;
        }
        
        if (bDisable && !pasteAfterDisabled)
        {
          elemPasteAfter.addClass(_OJ_DISABLED);
          bRefreshMenu = true;
        }
        else if (!bDisable && pasteAfterDisabled)
        {
          elemPasteAfter.removeClass(_OJ_DISABLED);
          bRefreshMenu = true;
        }
      }

      if (bRefreshMenu)
        this._menu.$container.ojMenu("refresh");
    }
  },

  /**
   * Build a context menu item for a cut/paste command.
   * @param {string} cmd Command to execute for item.
   * @returns {jQuery} jQuery list item object.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _buildContextMenuItem: function(cmd)
  {
    var id = _MENU_CMD_MAP[cmd];
    var transKey = _MENU_TRANSLATION_MAP[cmd];
    var label = $('<a href="#"></a>');
    label.text(this.getTranslatedString(transKey)); // @HTMLUpdateOK
    label.wrap('<li id=' + id + '></li>'); // @HTMLUpdateOK
    return label.parent();
  },

  /**
   * Execute a "cut" command from the context menu.
   * @param {Object} obj Tile element to cut.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _menuCut: function(obj)
  {
    if (obj)
      this._menu.cutTile = obj;
  },

  /**
   * Execute a "paste" command from the context menu.
   * @param {Object} obj Tile element at paste location.
   * @param {boolean} pasteBefore True to paste before the given tile, false to
   *        paste after the given tile.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _menuPaste: function(obj, pasteBefore)
  { 
    if (obj && this._menu.cutTile)
    {
      var cutTile = this._menu.cutTile;
      this._menu.cutTile = false;
      this._doPaste(cutTile, obj, pasteBefore);
    }
  },
  
  /**
   * Paste the cut tile before or after the given paste tile.
   * @param {Object} cutTile Tile element to paste.
   * @param {Object} pasteTile Tile element at paste location.
   * @param {boolean} pasteBefore True to paste before the given tile, false to
   *        paste after the given tile.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _doPaste: function (cutTile, pasteTile, pasteBefore)
  {
    var fromIndex = cutTile._jetDataMasonryOriginalOrder - 1;
    var jqCutTile = $(cutTile);
    
    //fire beforeReorder event
    var beforeEventData = {
      /** @expose */
      tile: jqCutTile,
      /** @expose */
      fromIndex: fromIndex
    };
    var bSuccess = this._trigger("beforeReorder", null, beforeEventData);
    
    if (bSuccess !== false)
    {
      //remove the cutTile from the original ordering before getting the toIndex
      this._removeTileOriginalOrder(cutTile);
      var toIndex = pasteTile._jetDataMasonryOriginalOrder - 1;
      if (!pasteBefore)
        toIndex++;

      var elem = this.element[0];
      if (pasteBefore)
      {
        this._insertTileOriginalOrder(cutTile, toIndex);
        elem.insertBefore(cutTile, pasteTile); // @HTMLUpdateOK
      }
      else
      {
        var nextElem = _getNextElement(pasteTile);
        this._insertTileOriginalOrder(cutTile, toIndex);
        elem.insertBefore(cutTile, nextElem); // @HTMLUpdateOK
      }

      var mlCommon = this._mlCommon;
      mlCommon.setup(true);

      //fire reorder event
      var eventData = {
        /** @expose */
        tile: jqCutTile,
        /** @expose */
        fromIndex: fromIndex,
        /** @expose */
        toIndex: toIndex
      };
      this._trigger("reorder", null, eventData);
    }
  },

  /**
   * Handle a context menu select event.
   * @param {Event} ev jQuery event object.
   * @param {Object} ui Parameters.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _handleContextMenuSelect: function(ev, ui)
  {
    var id = ui ? ui.item.attr("id") : undefined;

    if (id === _OJMASONRYLAYOUTCUT)
      this._menuCut(this._menu.tile);
    else if (id === _OJMASONRYLAYOUTPASTEBEFORE)
      this._menuPaste(this._menu.tile, true);
    else if (id === _OJMASONRYLAYOUTPASTEAFTER)
      this._menuPaste(this._menu.tile, false);
  },
  
  // end functions for context menu reordering /////////////////////////////////
  
  // start functions for drag-and-drop reordering //////////////////////////////
  
  /**
   * Get the index of the given child tile.
   * @param {Object} tile Tile for which to get the index.
   * @returns {number} Index of the tile.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _getTileIndex: function(tile)
  {
    //index should be with respect to "real" children, not including the dropsite,
    //so exclude the dropsite from the children array
    var children = this._getTileElements(true);
    //need to sort the children because the index is with respect to the original order
    _sortTilesOriginalOrder(children);
    var numChildren = children.length;
    for (var i = 0; i < numChildren; i++)
    {
      if (children[i] === tile)
        return i;
    }
    return -1;
  },
  
  /**
   * Setup drag and drop on the reorder handles.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _setupReorderHandles: function()
  {
    var elem = this.element;
    var options = this.options;
    var reorderHandles = elem.find(options.reorderHandle);
    reorderHandles
      .attr("draggable", "true")
      .addClass("oj-draggable");
    elem
      .on("dragstart" + this.reorderHandleEventNamespace, this._handleDragStartFunc)
      .on("dragenter" + this.reorderHandleEventNamespace, this._handleDragEnterFunc)
      .on("dragover" + this.reorderHandleEventNamespace, this._handleDragOverFunc)
      .on("dragleave" + this.reorderHandleEventNamespace, this._handleDragLeaveFunc)
      .on("dragend" + this.reorderHandleEventNamespace, this._handleDragEndFunc)
      .on("drop" + this.reorderHandleEventNamespace, this._handleDropFunc);
  },
  
  /**
   * Tear down drag and drop on the reorder handles.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _tearDownReorderHandles: function()
  {
    var elem = this.element;
    var options = this.options;
    var reorderHandles = elem.find(options.reorderHandle);
    reorderHandles
      .removeAttr("draggable")
      .removeClass("oj-draggable");
    elem.off(this.reorderHandleEventNamespace);
  },
  
  /**
   * Handle a dragstart event.
   * @param {Object} event jQuery Event
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _handleDragStart: function(event)
  {
    var options = this.options;
    //don't try to start another drag while there's already one happening
    //(in case the previous transition is still running)
    if (options.reorderHandle && !this._bDragging)
    {
      var target = event.target;
      var elem = this.element;
      var tile = _findContainingTile(target, elem[0]);
      if (tile)
      {
        var index = this._getTileIndex(tile);
        tile._jetDataMasonryDragSourceIndex = index;
        
        //fire beforeReorder event
        var eventData = {
          /** @expose */
          tile: $(tile),
          /** @expose */
          fromIndex: index
        };
        var bSuccess = this._trigger("beforeReorder", null, eventData);
        
        if (bSuccess !== false)
        {
          var originalEvent = event.originalEvent;
          this._dragStart(tile, originalEvent.pageX, originalEvent.pageY, originalEvent.dataTransfer);
        }
      }
    }
  },
  
  /**
   * Handle a dragenter event.
   * @param {Object} event jQuery Event
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _handleDragEnter: function(event)
  {
    var originalEvent = event.originalEvent;
    var relatedTarget = originalEvent.relatedTarget;
    var elem = this.element[0];
    var enteringMasonryLayout = false;
    if (relatedTarget)
    {
      enteringMasonryLayout = (elem != relatedTarget) && !oj.DomUtils.isAncestor(elem, relatedTarget);
    }
    //chrome 40.0.2214.111 doesn't set a relatedTarget, so need to use other means
    //to figure out if we're re-entering the masonryLayout
    else if (this._dragLeftMasonryLayout)
    {
      var elemUnderPoint = document.elementFromPoint(originalEvent.clientX, originalEvent.clientY);
      enteringMasonryLayout = elemUnderPoint && 
                              (elemUnderPoint == elem ||
                               oj.DomUtils.isAncestor(elem, elemUnderPoint));
    }
    if (enteringMasonryLayout)
    {
      this._dragLeftMasonryLayout = false;
      if (!this._draggedTile)
      {
        var dataTransfer = originalEvent.dataTransfer;
        dataTransfer.dropEffect = 'none';
      }
      else if (this._dropSite)
      {
        $(this._dropSite).css("display", "");
        var mlCommon = this._mlCommon;
        mlCommon.setup(false, true);
      }
    }
  },
  
  /**
   * Handle a dragover event.
   * @param {Object} event jQuery Event
   * @returns {boolean} False
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _handleDragOver: function(event)
  {
    var originalEvent = event.originalEvent;
    var dataTransfer = originalEvent.dataTransfer;
    dataTransfer.dropEffect = 'move';
    
    this._dragMove(originalEvent.pageX, originalEvent.clientX, originalEvent.clientY);

    //necessary to allow a drop
    event.preventDefault();
    return false;
  },
  
  /**
   * Handle a dragleave event.
   * @param {Object} event jQuery Event
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _handleDragLeave: function(event)
  {
    var originalEvent = event.originalEvent;
    var relatedTarget = originalEvent.relatedTarget;
    var elem = this.element[0];
    var leavingMasonryLayout = false;
    if (relatedTarget)
    {
      leavingMasonryLayout = (elem != relatedTarget) && !oj.DomUtils.isAncestor(elem, relatedTarget);
    }
    //chrome 40.0.2214.111 doesn't set a relatedTarget, so need to use other means
    //to figure out if we're exiting the masonryLayout
    else
    {
      var elemUnderPoint = document.elementFromPoint(originalEvent.clientX, originalEvent.clientY);
      leavingMasonryLayout = elemUnderPoint && 
                             elemUnderPoint != elem &&
                             !oj.DomUtils.isAncestor(elem, elemUnderPoint);
    }
    if (leavingMasonryLayout)
    {
      this._dragLeftMasonryLayout = true;
      if (this._dropSite)
      {
        $(this._dropSite).css("display", _NONE);
        var mlCommon = this._mlCommon;
        mlCommon.setup(false, true);
      }
    }
  },
  
  /**
   * Handle a dragend event.
   * @param {Object} event jQuery Event
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _handleDragEnd: function(event)
  {
    //only process the dragend if we're not currently processing a drop (for 
    //example if the mouse button was released outside of the masonryLayout)
    if (!this._bDropping)
    {
      if (this._draggedTile && this._dropSite)
      {
        var draggedTile = this._draggedTile;
        var dropSite = this._dropSite;
        var elem = this.element[0];
        //tile was not moved outside
        if (oj.DomUtils.isAncestor(elem, draggedTile))
        {
          $(dropSite).css("display", "");
          this._removeTileOriginalOrder(dropSite);
          var parent = dropSite.parentNode;
          parent.removeChild(dropSite);
          $(draggedTile).css("display", "");
          this._insertTileOriginalOrder(draggedTile, draggedTile._jetDataMasonryOriginalOrder - 1);
          var mlCommon = this._mlCommon;
          mlCommon.setup(false, true);
        }

        delete draggedTile._jetDataMasonryDragSourceIndex;
      }
      this._draggedTile = null;
      this._dropSite = null;
      this._bDragMoveTransition = false;
      this._bMouseMoved = false;
      this._dragOffset = null;
      this._bDragEndTransition = false;
      this._bDragging = false;
    }
  },
  
  /**
   * Handle a drop event.
   * @param {Object} event jQuery Event
   * @returns {boolean} False
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _handleDrop: function(event)
  {
    //FIX BUG 20867069: if the drop happens before the drag move layout 
    //animation is done, finish that layout cycle immdiately before processing
    //the drop
    var mlCommon = this._mlCommon;
    if (mlCommon.isInLayoutCycle())
      mlCommon.finishLayoutCycle();
    
    var originalEvent = event.originalEvent;
    this._drop(this._draggedTile, originalEvent.pageX, originalEvent.pageY);
    
    //stop the browser from redirecting
    event.stopPropagation();
    return false;
  },

  /** 
   * Start a drag.
   * @param {!Element} tile Tile being dragged.
   * @param {number} pageX Page coordinate at which the drag starts.
   * @param {number} pageY Page coordinate at which the drag starts.
   * @param {Object} dataTransfer Drag and drop DataTransfer object.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _dragStart: function(tile, pageX, pageY, dataTransfer)
  {
    this._bDragging = true;
    this._bDropping = false;
    this._bMouseMoved = false;
    this._draggedTile = tile;
    var elem = this.element[0];

    var sizeClass = _getSizeStyleClassName(tile);
    this._dropSite = document.createElement("div");
    var dropSite = this._dropSite;
    dropSite._jetDataMasonryOriginalOrder = tile._jetDataMasonryOriginalOrder;
    dropSite.className = sizeClass + " oj-drop";
    var style = dropSite.style;
    var tileStyle = tile.style;
    style.top = tileStyle.top;
    //FIX BUG 19826013: in RTL, position tiles using "right" instead of "left"
    if (this._bRTL)
      style.right = tileStyle.right;
    else
      style.left = tileStyle.left;
    //need to get the relative position of the tile BEFORE replacing it with
    //the dropsite and adding it to the elemParent below
    var offset = _getRelativePosition(tile);
    elem.insertBefore(dropSite, tile); // @HTMLUpdateOK

    var dragOffset = {left: pageX - offset.left, top: pageY - offset.top};
    this._dragOffset = dragOffset;
    
    //add the oj-drag class BEFORE creating the drag feedback image
    $(tile).addClass("oj-drag");
    
    dataTransfer.effectAllowed = 'move';
    dataTransfer.setData('text/html', tile.outerHTML);
    dataTransfer.setDragImage(tile, dragOffset.left, dragOffset.top);
    
    //hide the original tile in a timeout AFTER creating the drag feedback image
    var self = this;
    this._dragStartHideTileTimeout = setTimeout(function() {
        tileStyle.display = _NONE;
        $(tile).removeClass("oj-drag");
        self._dragStartHideTileTimeout = null;
        //notify the element that it's been hidden
        oj.Components.subtreeHidden(tile);
      }, 0);
  },

  /** 
   * Drag a tile.
   * @param {number} pageX Page coordinate of the drag move.
   * @param {number} clientX Client coordinate of the drag move.
   * @param {number} clientY Client coordinate of the drag move.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _dragMove: function(pageX, clientX, clientY)
  {
    this._bMouseMoved = true;

    if (!this._bDragMoveTransition)
    {
      var elem = this.element[0];
      var elemUnderPoint = document.elementFromPoint(clientX, clientY);
      var tileUnderPoint = _findContainingTile(elemUnderPoint, elem);
      if (tileUnderPoint && 
          tileUnderPoint !== this._dropSite && 
          tileUnderPoint !== this._draggedTile)
      {
        var offset = _getRelativePosition(elem);
        var relX = pageX - offset.left;
        var oldNextSibling = _getNextElement(this._dropSite);
        var bRightSide = relX >= tileUnderPoint.offsetLeft + tileUnderPoint.offsetWidth * .5;
        this._removeTileOriginalOrder(this._dropSite);
        if ((bRightSide && !this._bRTL) || (!bRightSide && this._bRTL))
        {
          var nextElem = _getNextElement(tileUnderPoint);
          if (nextElem)
          {
            //insert before the nextElem
            this._insertTileOriginalOrder(this._dropSite, nextElem._jetDataMasonryOriginalOrder - 1);
            elem.insertBefore(this._dropSite, nextElem); // @HTMLUpdateOK
          }
          else
          {
            //append to the end
            this._insertTileOriginalOrder(this._dropSite, tileUnderPoint._jetDataMasonryOriginalOrder);
            elem.appendChild(this._dropSite);
          }
        }
        else
        {
          //insert before the tileUnderPoint
          this._insertTileOriginalOrder(this._dropSite, tileUnderPoint._jetDataMasonryOriginalOrder - 1);
          elem.insertBefore(this._dropSite, tileUnderPoint); // @HTMLUpdateOK
        }
        var newNextSibling = _getNextElement(this._dropSite);
        //only need to layout if the dropSite has actually changed order
        if (oldNextSibling !== newNextSibling)
        {
          //only record the start of a drag move transition if the layout 
          //actually changed
          var mlCommon = this._mlCommon;
          this._bDragMoveTransition = mlCommon.setup(false, true);
        }
      }
    }
  },

  /** 
   * Handle the end of a layout transition while dragging a tile.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _handleDragMoveTransitionEnd: function()
  {
    this._bDragMoveTransition = false;
  },

  /** 
   * Drop a tile.
   * @param {!Element} tile Tile being dragged.
   * @param {number} pageX Page coordinate of the drop.
   * @param {number} pageY Page coordinate of the drop.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _drop: function(tile, pageX, pageY)
  {
    this._bDropping = true;
    var elem = this.element[0];
    var dropSite = this._dropSite;
    this._dropSite = null;
    //notify the element that it's been detached from the DOM BEFORE actually
    //detaching it so that components can save state
    oj.Components.subtreeDetached(tile);
    elem.replaceChild(tile, dropSite);
    //notify the element that it's been attached to the DOM
    oj.Components.subtreeAttached(tile);
    tile._jetDataMasonryOriginalOrder = dropSite._jetDataMasonryOriginalOrder;
    var style = tile.style;
    style.display = "";
    //notify the element that it's been shown
    oj.Components.subtreeShown(tile);

    //immediately transform the page absolute coords to ones relative to the 
    //ojmasonrylayout
    var offset = _getRelativePosition(elem);
    var dragOffset = this._dragOffset;
    style.top = (pageY - dragOffset.top - offset.top) + _PX;
    //FIX BUG 19826013: in RTL, position tiles using "right" instead of "left"
    var newLeft = pageX - dragOffset.left - offset.left;
    if (this._bRTL)
    {
      style.right = (elem.offsetWidth - (newLeft + $(tile).outerWidth(true))) + _PX;
      style.left = "";
    }
    else
      style.left = newLeft + _PX;

    this._dragOffset = null;

    if (this._bMouseMoved)
    {
      //transition the tile to its permanent place in the layout
      var mlCommon = this._mlCommon;
      this._bDragEndTransition = mlCommon.setup(false, true);
    }
    else
    {
      this._handleDragEndTransitionEnd();
    }
  },

  /** 
   * Handle the end of a layout transition when a tile is dropped.
   * @memberof oj.ojMasonryLayout
   * @instance
   * @private
   */
  _handleDragEndTransitionEnd: function()
  {
    this._bDragEndTransition = false;
    this._bDragging = false;
    this._bDropping = false;
    this._bMouseMoved = false;
    
    var tile = this._draggedTile;
    this._draggedTile = null;
    var fromIndex = tile._jetDataMasonryDragSourceIndex;
    delete tile._jetDataMasonryDragSourceIndex;
    var toIndex = this._getTileIndex(tile);
    
    //fire reorder event
    var eventData = {
      /** @expose */
      tile: $(tile),
      /** @expose */
      fromIndex: fromIndex,
      /** @expose */
      toIndex: toIndex
    };
    this._trigger("reorder", null, eventData);
  },
  
  // end functions for drag-and-drop reordering ////////////////////////////////
  
  /**
   * Return the subcomponent node represented by the documented locator 
   * attribute values.
   * This component does not expose any subIds.
   * @expose
   * @memberof! oj.ojMasonryLayout
   * @instance
   * @override
   * @param {Object} locator An Object containing at minimum a subId property 
   *        whose value is a string, documented by the component, that allows 
   *        the component to look up the subcomponent associated with that 
   *        string.  It contains:<p>
   *        component: optional - in the future there may be more than one 
   *        component contained within a page element<p>
   *        subId: the string, documented by the component, that the component 
   *        expects in getNodeBySubId to locate a particular subcomponent
   * @returns {Element|null} the subcomponent located by the subId string passed
   *          in locator, if found.<p>
   */
  getNodeBySubId: function(locator)
  {
    return this._super(locator);
  }
  
  // start jsdoc fragments /////////////////////////////////////////////////////

  /**
   * <p>MasonryLayout is for layout only and does not directly support touch 
   * interaction. It is up to the application to provide touch support. 
   *
   * @ojfragment touchDoc - Used in touch gesture section of classdesc, and standalone gesture doc
   * @memberof oj.ojMasonryLayout
   */

  /**
   * <p>MasonryLayout is for layout only and does not directly support keyboard 
   * interaction. It is up to the application to provide keyboard support. 
   *
   * @ojfragment keyboardDoc - Used in keyboard section of classdesc, and standalone gesture doc
   * @memberof oj.ojMasonryLayout
   */
  
  // end jsdoc fragments ///////////////////////////////////////////////////////
  
}); // end of oj.__registerWidget

// start static members and functions //////////////////////////////////////////

//_tmpVar is simply to trick the closure compiler so that it doesn't issue a
//warning about the below real vars declared using a single "var" statement:
//WARNING - declaration of multiple variables with shared type information
var _tmpVar;

var _PX     = "px",
    _HIDDEN = "hidden",
    _NONE   = "none",
    
    _OJ_DISABLED = "oj-disabled",

    _OJ_MASONRYLAYOUT_TILE_1X1 = "oj-masonrylayout-tile-1x1",
    _OJ_MASONRYLAYOUT_TILE_1X2 = "oj-masonrylayout-tile-1x2",
    _OJ_MASONRYLAYOUT_TILE_1X3 = "oj-masonrylayout-tile-1x3",
    _OJ_MASONRYLAYOUT_TILE_2X1 = "oj-masonrylayout-tile-2x1",
    _OJ_MASONRYLAYOUT_TILE_2X2 = "oj-masonrylayout-tile-2x2",
    _OJ_MASONRYLAYOUT_TILE_2X3 = "oj-masonrylayout-tile-2x3",
    _OJ_MASONRYLAYOUT_TILE_3X1 = "oj-masonrylayout-tile-3x1",
    _OJ_MASONRYLAYOUT_TILE_3X2 = "oj-masonrylayout-tile-3x2",

    _TILE_SELECTOR =   "." + _OJ_MASONRYLAYOUT_TILE_1X1 + 
                     ", ." + _OJ_MASONRYLAYOUT_TILE_1X2 + 
                     ", ." + _OJ_MASONRYLAYOUT_TILE_1X3 + 
                     ", ." + _OJ_MASONRYLAYOUT_TILE_2X1 + 
                     ", ." + _OJ_MASONRYLAYOUT_TILE_2X2 + 
                     ", ." + _OJ_MASONRYLAYOUT_TILE_2X3 + 
                     ", ." + _OJ_MASONRYLAYOUT_TILE_3X1 + 
                     ", ." + _OJ_MASONRYLAYOUT_TILE_3X2,
    
    _OJ_MASONRYLAYOUT_TILE_TRANSITION_SHOW_FROM_CLASS = "oj-masonrylayout-tile-transition-show-from",
    
    //log warning message when "disabled" option set
    _WARNING_DISABLED_OPTION = "JET MasonryLayout: 'disabled' option not supported",

    //Context Menu: menu item ids
    _OJMASONRYLAYOUTCUT = "ojmasonrylayoutcut",
    _OJMASONRYLAYOUTPASTEBEFORE = "ojmasonrylayoutpastebefore",
    _OJMASONRYLAYOUTPASTEAFTER = "ojmasonrylayoutpasteafter",
    _MENU_CMD_MAP = {"cut"          : _OJMASONRYLAYOUTCUT,
                     "paste-before" : _OJMASONRYLAYOUTPASTEBEFORE,
                     "paste-after"  : _OJMASONRYLAYOUTPASTEAFTER
                    },
    _MENU_TRANSLATION_MAP = {"cut"          : "labelCut",
                             "paste-before" : "labelPasteBefore",
                             "paste-after"  : "labelPasteAfter"
                            },

    /** 
     * Get the position of an element relative to the document.
     * @param {Object} elem Element for which to get position.
     * @returns {Object} Object containing properties: top, left.
     * @private
     */
    _getRelativePosition = function(elem)
    {
      return $(elem).offset();
    },

    /** 
     * Add a style class name to an element.
     * @param {Object} elem Element to which to add style class.
     * @param {string} styleClass Style class name to add.
     * @private
     */
    _addStyleClassName = function(elem, styleClass)
    {
      $(elem).addClass(styleClass);
    },

    /** 
     * Remove a style class name from an element.
     * @param {Object} elem Element from which to remove style class.
     * @param {string} styleClass Style class name to remove.
     * @private
     */
    _removeStyleClassName = function(elem, styleClass)
    {
      $(elem).removeClass(styleClass);
    },

    /** 
     * Get the masonry tile size style class applied to an element.
     * @param {Object} elem Element for which to get size style class name.
     * @returns {?string} Style class name.
     * @private
     */
    _getSizeStyleClassName = function(elem)
    {
      var str = null;
      var tile = $(elem);

      if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_1X1))
        str = _OJ_MASONRYLAYOUT_TILE_1X1;
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_2X1))
        str = _OJ_MASONRYLAYOUT_TILE_2X1;
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_3X1))
        str = _OJ_MASONRYLAYOUT_TILE_3X1;
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_1X2))
        str = _OJ_MASONRYLAYOUT_TILE_1X2;
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_1X3))
        str = _OJ_MASONRYLAYOUT_TILE_1X3;
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_2X2))
        str = _OJ_MASONRYLAYOUT_TILE_2X2;
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_2X3))
        str = _OJ_MASONRYLAYOUT_TILE_2X3;
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_3X2))
        str = _OJ_MASONRYLAYOUT_TILE_3X2;

      return str;
    },

    /** 
     * Get the masonry tile span of an element.
     * @param {Object} elem Element for which to get span.
     * @returns {Object} Object containing properties: colSpan, rowSpan.
     * @private
     */
    _getTileSpan = function(elem)
    {
      var span = null;
      var tile = $(elem);

      if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_1X1))
        span = {colSpan: 1, rowSpan: 1};
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_2X1))
        span = {colSpan: 2, rowSpan: 1};
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_3X1))
        span = {colSpan: 3, rowSpan: 1};
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_1X2))
        span = {colSpan: 1, rowSpan: 2};
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_1X3))
        span = {colSpan: 1, rowSpan: 3};
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_2X2))
        span = {colSpan: 2, rowSpan: 2};
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_2X3))
        span = {colSpan: 2, rowSpan: 3};
      else if (tile.hasClass(_OJ_MASONRYLAYOUT_TILE_3X2))
        span = {colSpan: 3, rowSpan: 2};

      return span;
    },
    
    /**
     * Sort tiles based on their original DOM order.
     * @param {Array} arTiles Array of tiles.
     * @returns {Array} The given array of tiles, sorted.
     * @private
     */
    _sortTilesOriginalOrder = function(arTiles)
    {
      if (arTiles)
        arTiles.sort(_compareTilesOriginalOrder);
      
      return arTiles;
    },
    
    /**
     * Compare tiles for sorting based on the original DOM order.
     * @param {Object} tile1 A tile.
     * @param {Object} tile2 Another tile.
     * @returns {number} 1 if tile2 should be sorted before tile1, -1 if tile1
     *          should be sorted before tile2, or 0 if the tile order doesn't
     *          need to change
     * @private
     */
    _compareTilesOriginalOrder = function(tile1, tile2)
    {
      if (tile2._jetDataMasonryOriginalOrder < tile1._jetDataMasonryOriginalOrder)
        return 1;
      if (tile1._jetDataMasonryOriginalOrder < tile2._jetDataMasonryOriginalOrder)
        return -1;
      return 0;
    },

    // start static functions for drag-and-drop reordering /////////////////////

    /** 
     * Find the masonry tile that contains the given element.
     * @param {Object} elem Descendant element of a masonry tile.
     * @param {Object} rootElem Root of the masonryLayout.
     * @returns {Object} Masonry tile element.
     * @private
     */
    _findContainingTile = function(elem, rootElem)
    {
      var currElem = elem;
      while (currElem)
      {
        var style = currElem.style;
        if (style && (style.visibility === _HIDDEN || style.display === _NONE))
          break;
        var parentElem = currElem.parentNode;
        if (parentElem === rootElem)
          return currElem;
        currElem = parentElem;
      }
      return null;
    },

    /** 
     * Get the next visible sibling element.
     * @param {Object} elem Element for which to get next visible sibling.
     * @returns {Object} Next visible sibling element.
     * @private
     */
    _getNextElement = function(elem)
    {
      var currElem = elem;
      while (currElem)
      {
        var nextElem = currElem.nextSibling;
        var bVisible = true;
        if (nextElem)
        {
          var style = nextElem.style;
          if (style && (style.visibility === _HIDDEN || style.display === _NONE))
            bVisible = false;
        }
        if (nextElem && nextElem.nodeType === 1 && bVisible)
          return nextElem;
        currElem = nextElem;
      }
      return null;
    };

    // end static functions for drag-and-drop reordering ///////////////////////
    
// end static members and functions ////////////////////////////////////////////

}()); // end of MasonryLayout wrapper function