Source: lists/views/layouts/Grid.js

/**
 * Copyright (C) 2005-2016 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Use this widget to render a grid. Every widget rendered within it will be added so that if a row
 * contains the number of widgets defined by [columns]{@link module:alfresco/lists/views/layouts/Grid#columns]
 * a new row will be started for the next processed widget.
 *
 * @module alfresco/lists/views/layouts/Grid
 * @extends external:dijit/_WidgetBase
 * @mixes external:dojo/_TemplatedMixin
 * @mixes module:alfresco/lists/views/layouts/_MultiItemRendererMixin
 * @mixes module:alfresco/lists/KeyboardNavigationSuppressionMixin
 * @mixes module:alfresco/core/Core
 * @mixes module:alfresco/lists/views/layouts/_LayoutMixin
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "dijit/_WidgetBase",
        "dijit/_TemplatedMixin",
        "alfresco/core/ResizeMixin",
        "dijit/_KeyNavContainer",
        "alfresco/lists/KeyboardNavigationSuppressionMixin",
        "dojo/text!./templates/Grid.html",
        "alfresco/lists/views/layouts/_MultiItemRendererMixin",
        "alfresco/core/Core",
        "alfresco/lists/views/layouts/_LayoutMixin",
        "alfresco/core/WidgetsCreator",
        "dojo/keys",
        "dojo/on",
        "dojo/_base/lang",
        "dojo/_base/array",
        "dojo/dom-attr",
        "dojo/dom-class",
        "dojo/dom-construct",
        "dojo/dom-geometry",
        "dojo/dom-style",
        "dijit/registry",
        "dijit/focus",
        "jquery"],
        function(declare, _WidgetBase, _TemplatedMixin, ResizeMixin, _KeyNavContainer, KeyboardNavigationSuppressionMixin, template, _MultiItemRendererMixin,
                 AlfCore, _LayoutMixin, WidgetsCreator, keys, on, lang, array, domAttr, domClass, domConstruct, domGeom, domStyle,
                 registry, focusUtil, $) {

   return declare([_WidgetBase, _TemplatedMixin, ResizeMixin, _KeyNavContainer, _MultiItemRendererMixin, KeyboardNavigationSuppressionMixin, AlfCore, _LayoutMixin], {

      /**
       * An array of the CSS files to use with this widget.
       *
       * @instance
       * @type {object[]}
       * @default [{cssFile:"./css/Grid.css"}]
       */
      cssRequirements: [{cssFile:"./css/Grid.css"}],

      /**
       * The HTML template to use for the widget.
       *
       * @instance
       * @type {String}
       */
      templateString: template,

      /**
       * This is the number of columns in the grid.
       *
       * @instance
       * @type {number}
       * @default
       */
      columns: 4,

      /**
       * This is used to keep track of any empty cells that are created as a result of calling 
       * [completeRow]{@link module:alfresco/lists/views/layouts/Grid#completeRow} so that they
       * can be destroyed when more results are added (when used within an infinite scrolling
       * list).
       * 
       * @instance
       * @type {element[]}
       * @default
       * @since 1.0.40
       */
      emptyCells: null,

      /**
       * Indicates whether or not highlighting should be enabled. If this is configured to be
       * true then highlighting of focus and expansion will be handled.
       *
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.44
       */
      enableHighlighting: false,

      /**
       * This is set to the [itemKeyProperty]{@link module:alfresco/lists/views/layouts/Grid#itemKeyProperty}
       * of the item in the grid that has been expanded by the publication of the 
       * [expandTopic]{@link module:alfresco/lists/views/layouts/Grid#expandTopic}
       *
       * @instance
       * @type {string}
       * @default
       * @since 1.0.44
       */
      expandedItemKey: null,

      /**
       * This is set to a reference to a panel expanded within the grid showing more details of one of the
       * rendered items. It should not be configured in models, but can be referenced in extending modules.
       *
       * @instance
       * @type {string}
       * @default
       * @since 1.0.44
       */
      expandedPanel: null,

      /**
       * This is an array of optional topics that can be subscribed to to create a panel within the grid for
       * showing additional data about a particular cell in the grid. The payload should contain
       * a "widgets" attribute that represents the model to render within the panel.
       * 
       * @instance
       * @type {string[]}
       * @default
       * @since 1.0.44
       * 
       * @event
       * @property {objects[]} widgets The widgets to show in the expanded panel.
       */
      expandTopics: null,

      /**
       * Indicates whether the number of columns is fixed for resize events. This means that
       * the thumbnail size can change. 
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.40
       */
      fixedColumns: true,

      /**
       * Used to keep track of which cell is mapped to each itemKeyProperty]
       *
       * @instance
       * @type {object}
       * @default
       * @since 1.0.44
       */
      gridCellMapping: null,

      /**
       * This is the property that is used to uniquely identify each 
       * [item]{@link module:alfresco/core/CoreWidgetProcessing#currentItem} rendered in the grid. It is used
       * as the key in the [gridCellMapping]{@link module:alfresco/lists/views/layouts/Grid#gridCellMapping}
       * to map each item to the cell that it is rendered in. This is required in order to know where to 
       * exand the grid when the 
       * [expandTopics]{@link module:alfresco/lists/views/layouts/Grid#expandTopics} is
       * published.
       * 
       * @instance
       * @type {string}
       * @default
       * @since 1.0.44
       */
      itemKeyProperty: null,

      /**
       * The label to use for the next link. This defaults to null, so MUST be set for the next link to be displayed.
       *
       * @instance
       * @type {string}
       * @default
       */
      nextLinkLabel: null,

      /**
       * The topic to publish when the next link is clicked.
       *
       * @instance
       * @type {string}
       * @default
       */
      nextLinkPublishTopic: null,

      /**
       * When set to true this will show a link for requesting more data (if available). This should be used when
       * the grid is rendering data in an infinite scroll view. It is required because when the grid cells are small
       * the data may not be sufficient to allow the scrolling events to occur that will request more data.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      showNextLink: false,

      /**
       * The size of each thumbnail. This is only used when
       * [columns are not fixed]{@link module:alfresco/lists/views/layouts/Grid#fixedColumns}.
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.40
       */
      thumbnailSize: null,

      /**
       * Keeps tracks of the last set [expandedItemKey]{@link module:alfresco/lists/views/Grid#expandedItemKey}
       * in order to allow the [expandedPanel]{@link module:alfresco/lists/views/Grid#expandedPanel} to be 
       * re-expanded following a resize events that re-renders the data.
       * 
       * @instance
       * @type {string}
       * @default
       * @since 1.0.83
       */
      _lastExpandedItemKey: null,

      /**
       * Logs the last requested widgets model provided in a call to 
       * [expandedPanel]{@link module:alfresco/lists/views/Grid#expandedPanel} in order to be able to recreate
       * the last [expandedPanel]{@link module:alfresco/lists/views/Grid#expandedPanel} following resize
       * events.
       * 
       * @instance
       * @type {object}
       * @default
       * @since 1.0.83
       */
      _lastExpandedWidgets: null,

      /**
       * Calls [processWidgets]{@link module:alfresco/core/Core#processWidgets}
       *
       * @instance postCreate
       * @listens module:alfresco/lists/views/layouts/Grid#expandTopics
       */
      postCreate: function alfresco_lists_views_layouts_Grid__postCreate() {
         this.inherited(arguments);
         if (this.currentItem)
         {
            if (this.widgets)
            {
               this.processWidgets(this.widgets, this.containerNode);
            }
         }

         this.setupKeyboardNavigation();
         on(this.domNode, "onSuppressKeyNavigation", lang.hitch(this, this.onSuppressKeyNavigation));
         on(this.domNode, "onItemFocused", lang.hitch(this, this.onItemFocused));

         // Update the grid as the window changes...
         this.alfSetupResizeSubscriptions(this.resizeCells, this);

         // Subscribe to any topics that will trigger the expansion of a panel to display more
         // information about the related cell...
         if (this.expandTopics && this.itemKeyProperty)
         {
            array.forEach(this.expandTopics, function(topic) {
               this.alfSubscribe(topic, lang.hitch(this, this.expandPanel));
            }, this);
         }

         if (this.enableHighlighting)
         {
            domClass.add(this.domNode, "alfresco-lists-views-layouts-Grid--enableHighlighting");
         }

         on(this.domNode, "keydown", lang.hitch(this, function(evt) {
            if (evt && evt.keyCode === keys.ESCAPE) {
               this.collapsePanel();
            }
         }));
      },


      /**
       * Destroys the [expandedPanel]{@link module:alfresco/lists/views/layouts/Grid#expandedPanel} and
       * restores the focus to the cell that was selected to be expanded.
       * 
       * @instance
       * @since 1.0.44
       */
      collapsePanel: function alfresco_lists_views_layouts_Grid__collapsePanel() {
         if (this.expandedPanel)
         {
            if (this.expandedItemKey)
            {
               var expandedCell = this.gridCellMapping[this.expandedItemKey];
               var expandedCellWidgets = registry.findWidgets(expandedCell);
               if (expandedCellWidgets && expandedCellWidgets.length)
               {
                  this.focusOnCell(expandedCellWidgets[0]);
               }
               domClass.remove(expandedCell, "alfresco-lists-views-layouts-Grid__cell--expanded");
               this.expandedItemKey = null;
            }

            // NOTE: This needs to be done after resetting focus to prevent exceptions trying to
            //       blur a destroyed widget...
            var widgets = registry.findWidgets(this.expandedPanel);
            array.forEach(widgets, function(widget) {
               widget.destroy();
            });
            domConstruct.destroy(this.expandedPanel);
         }
      },

      /**
       * Creates a new [expandedPanel]{@link module:alfresco/lists/views/layouts/Grid#expandedPanel}
       * for an item rendered in the grid or 
       * [collapses]{@link module:alfresco/lists/views/layouts/Grid#collapsePanel} the currently
       * expanded panel if it represents the requested item.
       * 
       * @instance
       * @param {object} payload The payload containing the details of the item to expand and what to
       * place in the expanded panel.
       * @since 1.0.44
       */
      expandPanel: function alfresco_lists_views_layouts_Grid__expandPanel(payload) {
         var itemKey = lang.getObject(this.itemKeyProperty, false, payload);
         if (itemKey && this.gridCellMapping[itemKey])
         {
            var cell = this.gridCellMapping[itemKey];
            if (itemKey === this.expandedItemKey)
            {
               // The item is already expanded so collapse it...
               this.collapsePanel();
               domClass.remove(cell, "alfresco-lists-views-layouts-Grid__cell--expanded");
            }
            else
            {
               // Collapse the previously displayed panel (will only have an effect if a 
               // panel has been expanded)...
               this.collapsePanel();
               
               // Set the current itemKey as the expanded panel...
               this.expandedItemKey = itemKey;

               // A new item has been requested to be expanded...
               var row = cell.parentNode;
               domClass.add(cell, "alfresco-lists-views-layouts-Grid__cell--expanded");

               // Create a new row...
               this.expandedPanel = domConstruct.create("tr", {
                  className: "alfresco-lists-views-layouts-Grid__expandedPanel"
               }, row, "after");

               // Add a single cell that spans all the columns in the row...
               var spanningCell = domConstruct.create("td", {
                  colspan: this.columns
               }, this.expandedPanel);

               var forWidgets = domConstruct.create("div", {
               }, spanningCell);

               if (payload.widgets)
               {
                  this._lastExpandedWidgets = payload.widgets;

                  var wc = new WidgetsCreator({ 
                     widgets: payload.widgets,

                     // Add a callback to focus on the first created widget...
                     callback: lang.hitch(this, function(widgets) {
                        if (widgets && widgets.length)
                        {
                           this.focusOnCell(widgets[0]);
                        }
                     })
                  });
                  wc.buildWidgets(forWidgets, this);
               }
            }
         }
      },

      /**
       * Overrides the [superclass implementation]{@link module:alfresco/lists/views/AlfListView#setupKeyboardNavigation}
       * to move to the next/previous item using the left and right cursor keys and the up/down keys to access the cell directly
       * above or below.
       *
       * @instance
       */
      setupKeyboardNavigation: function alfresco_lists_views_layouts_Grid__setupKeyboardNavigation() {
         this._keyNavCodes[keys.UP_ARROW] = lang.hitch(this, this.focusOnCellAbove);
         this._keyNavCodes[keys.RIGHT_ARROW] = lang.hitch(this, this.focusOnCellRight);
         this._keyNavCodes[keys.DOWN_ARROW] = lang.hitch(this, this.focusOnCellBelow);
         this._keyNavCodes[keys.LEFT_ARROW] = lang.hitch(this, this.focusOnCellLeft);
      },

      /**
       * This is called whenever focus leaves a child widget. It will call the blur function
       * of the currently focused widget if it has one.
       *
       * @instance
       */
      _onChildBlur: function alfresco_lists_views_layouts_Grid___onChildBlur(focusedChild) {
         if (typeof focusedChild.blur === "function")
         {
            focusedChild.blur();
         }
         (focusedChild.domNode && focusedChild.domNode.parentNode) && domClass.remove(focusedChild.domNode.parentNode, "alfresco-lists-views-layouts-Grid__cell--focused");
      },

      /**
       * This function ensures that the widget requested to be focused has a focus function
       * and if so calls the "focusChild" function provided by the _KeyNavContainer. Otherwise
       * it manually takes care of setting the focus.
       *
       * @instance
       * @param {object} widget The widget to focus
       * @since 1.0.43
       */
      focusOnCell: function alfresco_lists_views_layouts_Grid__focusOnCell(widget) {
         if (typeof widget.focus === "function")
         {
            this.focusChild(widget);
         }
         else
         {
            if(this.focusedChild && widget !== this.focusedChild){
               this._onChildBlur(this.focusedChild);  // used to be used by _MenuBase
            }
            if (widget.domNode)
            {
               domAttr.set(widget.domNode, "tabIndex", this.tabIndex); // for IE focus outline to appear, must set tabIndex before focus
               focusUtil.focus(widget.domNode);
            }
         }
         domClass.add(widget.domNode.parentNode, "alfresco-lists-views-layouts-Grid__cell--focused");
      },

      /**
       *
       *
       * @instance
       */
      focusOnCellLeft: function alfresco_lists_views_layouts_Grid__focusOnCellLeft() {
         var target = null,
             focusIndex = this.getIndexOfChild(this.focusedChild),
             allChildren = this.getChildren(),
             childCount = this.getChildren().length;
         if (focusIndex > 0)
         {
            target = allChildren[focusIndex-1];
         }
         else
         {
            target = allChildren[childCount-1];
         }
         this.focusOnCell(target);
      },

      /**
       *
       *
       * @instance
       */
      focusOnCellRight: function alfresco_lists_views_layouts_Grid__focusOnCellLeft() {
         var target = null,
             focusIndex = this.getIndexOfChild(this.focusedChild),
             allChildren = this.getChildren(),
             childCount = this.getChildren().length;
         if (focusIndex < childCount-1)
         {
            target = allChildren[focusIndex+1];
         }
         else
         {
            target = allChildren[0];
         }
         this.focusOnCell(target);
      },

      /**
       * Gives focus to the cell immediately above the currently focused cell. If the focused cell is on the
       * first row then it will select the cell in the same column on the last column (and if there isn't a cell
       * in the same column on the last row then the last item is selected).
       *
       * @instance
       */
      focusOnCellAbove: function alfresco_lists_views_layouts_Grid__focusOnCellAbove() {
         var target = null,
             focusIndex = this.getIndexOfChild(this.focusedChild),
             focusColumn = (focusIndex % this.columns) + 1,
             allChildren = this.getChildren(),
             childCount = this.getChildren().length;
         if (focusIndex - this.columns < 0)
         {
            // Go to last row
            var rem = childCount % this.columns;
            if (rem === 0 || rem >= focusColumn)
            {
               // Get the matching column on the last row...
               target = allChildren[childCount - (this.columns - focusColumn) + 1];
            }
            else
            {
               // Focus the last child...
               target = allChildren[childCount-1];
            }
         }
         else
         {
            target = allChildren[focusIndex - this.columns];
         }
         this.focusOnCell(target);
      },

      /**
       * Gives focus to the cell immediately below the currently focused cell. If the currently focused
       * cell is on the last row then the cell in the same column on the first row is selected.
       *
       * @instance
       */
      focusOnCellBelow: function alfresco_lists_views_layouts_Grid__focusOnCellBelow() {
         var target = null,
             focusIndex = this.getIndexOfChild(this.focusedChild),
             focusColumn = (focusIndex % this.columns),
             allChildren = this.getChildren(),
             childCount = this.getChildren().length;
         if ((focusIndex + this.columns) >= childCount)
         {
            target = allChildren[focusColumn];
         }
         else
         {
            target = allChildren[focusIndex + this.columns];
         }
         this.focusOnCell(target);
      },

      /**
       * Gets the content box of the containing DOM node of the grid and then iterates over all the cells in the grid calling
       * the [resizeCell]{@link module:alfresco/lists/views/layouts/Grid#resizeCell] function for each with the desired width.
       * The width to set is the available width divided by the number of columns to display.
       *
       * @instance resizeCells
       */
      resizeCells: function alfresco_lists_views_layouts_Grid__resizeCells() {
         this.alfLog("info", "Resizing");
         var node = lang.getObject("containerNode.parentNode", false, this);
         if (node)
         {
            var marginBox = domGeom.getContentBox(node); // NOTE: Get the parent node for the size because the table will grow outside of its allotted area
            if (this.fixedColumns === true)
            {
               var widthToSet = (Math.floor(marginBox.w / this.columns) - 10) + "px";
               $(node).find("tr > td").each(lang.hitch(this, this.resizeCell, marginBox, widthToSet));
            }
            else
            {
               // When not resizing based on fixed columns it is necessary to work out the containable
               // number of columns for the configured thumbnail size and then update the grid width
               // as necessary to ensure neat spacing of thumbnails...
               var remainingSpace = marginBox.w % this.thumbnailSize;
               var gridWidth = marginBox.w - remainingSpace;
               if (gridWidth)
               {
                  var columns = gridWidth / this.thumbnailSize;
                  if (columns !== this.columns)
                  {
                     // If the number of columns containable has changed then it is necessary to completely
                     // re-render the layout, so the existing widgets need to be destroyed and then recreated
                     this.columns = columns;

                     // Find and destroy all the existing widgetrs...
                     var widgets = registry.findWidgets(this.containerNode);
                     array.forEach(widgets, function(widget) {
                        widget.destroy();
                     });
                     domConstruct.empty(this.containerNode);
                     
                     this._lastExpandedItemKey = this.expandedItemKey;
                     this.collapsePanel();

                     // Re-render the data for the new columns...
                     this.renderData();
                  }

                  // Resize the cells and widgets...
                  domStyle.set(this.domNode, "width", gridWidth + "px");
                  $(node).find("tr > td").each(lang.hitch(this, this.resizeCell, marginBox, this.thumbnailSize + "px"));
               }
            }
         }
      },

      /**
       * Sets the width of an individual cell.
       *
       * @instance resizeCell
       * @param {Object} containerNodeMarginBox The margin box for the container nodes parent
       * @param {number} widthToSet The widget for the cell (in pixels)
       * @param {element} node The node to set width on
       * @param {number} index The current index of the element in the array
       */
      resizeCell: function alfresco_lists_views_layouts_Grid__resizeCell(containerNodeMarginBox, widthToSet, index, node /*jshint unused:false*/) {
         if (!domClass.contains(node.parentNode, "alfresco-lists-views-layouts-Grid__expandedPanel"))
         {
            domStyle.set(node, {"width": widthToSet});
            var dimensions = {
               w: widthToSet,
               h: null
            };

            // See AKU-704 - if you review the change history here you'll see that this has now gone back
            //               to the original implementation of only resizing direct children
            array.forEach(node.children, lang.hitch(this, this.resizeWidget, dimensions));
         }
      },

      /**
       * This function will check to see if there is a widget associated with the DOM node provided as an argument and if that
       * widget has a resize function it will call it with the supplied dimensions.
       *
       * @instance
       * @param {object} dimensions The object containing the width and height for the widget.
       * @param {object} widgetNode The DOM node that possibly has a widget associated. Use registry to check
       * @param {number} index The index of the node
       */
      resizeWidget: function alfresco_lists_views_layouts_Grid__resizeWidget(dimensions, widgetNode, /*jshint unused:false*/ index) {
         var widget = registry.byNode(widgetNode);
         if (widget && typeof widget.resize === "function")
         {
            widget.resize({
               w: dimensions.w,
               h: null
            });
         }
         else
         {
            // See AKU-689 - resize the widgets DOM node and publish an event to indicate that it has been resized...
            domStyle.set(widget.domNode, "width", dimensions.w);
            this.alfPublishResizeEvent(widget.domNode, true);
         }
      },

      /**
       * Overridden to add an additional TD elements for each cell in the grid. It will also create a new TR element if
       * the end of the current row has been reached.
       *
       * @instance
       * @param {object} widget The widget definition to create the DOM node for
       * @param {element} rootNode The DOM node to create the new DOM node as a child of
       * @param {string} rootClassName A string containing one or more space separated CSS classes to set on the DOM node
       */
      createWidgetDomNode: function alfresco_lists_views_layouts_Grid__createWidgetDomNode(widget, rootNode, /*jshint unused:false*/ rootClassName) {
         var nodeToAdd = rootNode;
         if (this.currentIndex % this.columns === 0)
         {
            // Create a new row if the maximum number of columns has been exceeded...
            var newRow = domConstruct.create("TR", {}, rootNode);
            nodeToAdd = domConstruct.create("TD", {
               className: "alfresco-lists-views-layouts-Grid__cell"
            }, newRow);
         }
         else
         {
            var lastNode = rootNode.children[rootNode.children.length-1];
            nodeToAdd = domConstruct.create("TD", {
               className: "alfresco-lists-views-layouts-Grid__cell"
            }, lastNode);
         }

         // TODO: Add warnings
         // TODO: Only do this if subscribing to expansion topics
         if (this.itemKeyProperty)
         {
            var itemKey = lang.getObject(this.itemKeyProperty, false, this.currentItem);
            if (itemKey)
            {
               this.gridCellMapping[itemKey] = nodeToAdd;
            }
         }

         // Add a new cell...
         return domConstruct.create("DIV", {}, nodeToAdd);
      },

      /**
       * Extends the [mixed in function]{@link module:alfresco/lists/views/layouts/_MultiItemRendererMixin#renderData}
       * to reset the [gridCellMapping]{@link module:alfresco/lists/views/layouts/Grid#gridCellMapping} in preparation
       * for rendering a new data set.
       * 
       * @instance
       * @since 1.0.44
       */
      renderData: function alfresco_lists_views_layouts_Grid__renderData() {
         this.gridCellMapping = {};
         this.inherited(arguments);
      }, 

      /**
       * Extends the [inherited function]{@link module:alfresco/lists/views/layouts/_MultiItemRendererMixin#renderNextItem}
       * to ensure that any DOM elements added for allowing the user to retrieve more items is destroyed. These will
       * have been created by the [allItemsRendered function]{@link module:alfresco/lists/views/layouts/Grid#allItemsRendered}
       * when more data is available.
       *
       * @instance
       */
      renderNextItem: function alfresco_lists_views_layouts_Grid__renderNextItem() {
         if (this.nextLinkDisplay)
         {
            this.nextLinkDisplay.destroy();
            this.nextLinkDisplay = null;
         }
         if (this.emptyCells)
         {
            array.forEach(this.emptyCells, function(emptyCell) {
               domConstruct.destroy(emptyCell);
            });
            this.emptyCells = [];
         }
         this.inherited(arguments);
      },

      /**
       * To ensure that the grid items are spaced correctly when there are less items
       * in the final row than there are columns, it is necessary to create empty cells
       * to fill the final columns in the row.
       * 
       * @instance
       * @since 1.0.40
       */
      completeRow: function alfresco_lists_views_layouts_Grid__completeRow(lastColumn) {
         if (!this.emptyCells)
         {
            this.emptyCells = [];
         }
         for (var i=lastColumn; i<this.columns; i++)
         {
            var cell = domConstruct.create("TD", {
               className: "alfresco-lists-views-layouts-Grid__emptyCell"
            }, this.domNode.lastChild);
            this.emptyCells.push(cell);
         }
      },

      /**
       * Overrides the [inherited function]{@link module:alfresco/lists/views/layouts/_MultiItemRendererMixin#allItemsRendered}
       * to create a link for retrieving more data when
       *
       * @instance
       */
      allItemsRendered: function alfresco_lists_views_layouts_Grid__allItemsRendered() {
         var lastColumn = this.currentIndex % this.columns;
         if (lastColumn !== 0)
         {
            this.completeRow(lastColumn);
         }

         // If a panel was previously expanded before the data was re-rendered then we want to ensure that
         // it is expanded again (see AKU-1054)
         if (this._lastExpandedItemKey)
         {
            var payload = {
               widgets: this._lastExpandedWidgets
            };
            payload[this.itemKeyProperty] = this._lastExpandedItemKey;
            this.expandPanel(payload);
         }
         
         if(this.showNextLink &&
            ((this.totalRecords > (this.startIndex + this.currentPageSize)) ||
             this.currentData.totalRecords < this.currentData.numberFound ||
             this.currentData.totalRecords > this.currentData.items.length))
         {
            if (lastColumn === 0)
            {
               // We need to create a new row for the "Show Next" link because the previous row is complete...
               domConstruct.create("TR", {}, this.domNode);
               this.completeRow(lastColumn);
            }

            this.nextLinkDisplay = this.createWidget({
               name: "alfresco/layout/VerticalWidgets",
               assignTo: "nextLinkDisplay",
               config: {
                  widgets: [
                     {
                        name: "alfresco/renderers/PropertyLink",
                        config: {
                           currentItem: {
                              label: this.nextLinkLabel
                           },
                           propertyToRender: "label",
                           renderSize: "small",
                           useCurrentItemAsPayload: false,
                           publishTopic: this.nextLinkPublishTopic,
                           publishPayloadType: "CONFIGURED",
                           publishPayload: {}
                        }
                     }
                  ]
               }
            });
            this.nextLinkDisplay.placeAt(this.emptyCells[0]);
         }
      }
   });
});