Source: lists/views/ListRenderer.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/>.
 */

/**
 * Makes a request to bring a specific item into view, i.e. so that a previously actioned item is displayed
 * when the list data updated. The payload contains a single attribute "item" that should be a value that
 * will map to the [itemKey]{@link module:alfresco/lists/views/layouts/_MultiItemRendererMixin#itemKey}
 * of the renderer (which should be configured on the [view]{@link module:alfresco/lists/views/AlfListView#itemKey}).
 *
 * @event module:alfresco/lists/views/ListRenderer~ALF_BRING_ITEM_INTO_VIEW
 * @property {string} item - This is an attribute value of the item that uniquely identifies it.
 */

/**
 * <p>This module has been created to ensure that keyboard navigation (via the dijit/_KeyNavContainer) can be used
 * without impacting the rest of the view. An instance of this module should be created within each 
 * [list view]{@link module:alfresco/lists/views/AlfListView} (unless a view requires 
 * a specific renderer implementation).</p>
 * <p>This module can be extended if required to provide custom rendering of lists. In particular it may be necessary
 * to override the [bringItemIntoView]{@link module:alfresco/lists/views/ListRenderer#bringItemIntoView} function if
 * an extending module lays out item in a non-vertical style (as the default behaviour is simply to scroll down until
 * the requested item comes into view)</p>
 * 
 * @module alfresco/lists/views/ListRenderer
 * @extends external:dijit/_WidgetBase
 * @mixes external:dojo/_TemplatedMixin
 * @mixes external:dojo/_KeyNavContainer
 * @mixes module:alfresco/lists/views/layouts/_MultiItemRendererMixin
 * @mixes module:alfresco/lists/KeyboardNavigationSuppressionMixin
 * @mixes module:alfresco/core/Core
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "dijit/_WidgetBase", 
        "dijit/_TemplatedMixin",
        "dijit/_KeyNavContainer",
        "alfresco/lists/KeyboardNavigationSuppressionMixin",
        "dojo/text!./templates/ListRenderer.html",
        "alfresco/lists/views/layouts/_MultiItemRendererMixin",
        "alfresco/core/Core",
        "alfresco/core/JsNode",
        "dojo/_base/lang",
        "dojo/_base/array",
        "dojo/on",
        "dojo/keys",
        "jquery",
        "jqueryui"], 
        function(declare, _WidgetBase, _TemplatedMixin, _KeyNavContainer, KeyboardNavigationSuppressionMixin, template, 
                 _MultiItemRendererMixin, AlfCore, JsNode, lang, array, on, keys, $) {
   
   return declare([_WidgetBase, _TemplatedMixin, _KeyNavContainer, _MultiItemRendererMixin, KeyboardNavigationSuppressionMixin, AlfCore], {
      
      /**
       * The HTML template to use for the widget.
       * @instance
       * @type {String}
       */
      templateString: template,
      
      /**
       * The widgets to be processed to generate each item in the rendered view.
       * 
       * @instance 
       * @type {object[]} 
       * @default
       */
      widgets: null,
      
      /**
       * Implements the widget life-cycle method to add drag-and-drop upload capabilities to the root DOM node.
       * This allows files to be dragged and dropped from the operating system directly into the browser
       * and uploaded to the location represented by the document list. 
       * 
       * @instance
       * @listens module:alfresco/lists/views/ListRenderer~event:ALF_BRING_ITEM_INTO_VIEW
       */
      postCreate: function alfresco_lists_views_ListRenderer__postCreate() {
         this.inherited(arguments);
         this.setupKeyboardNavigation();
         on(this.domNode, "onSuppressKeyNavigation", lang.hitch(this, this.onSuppressKeyNavigation));
         on(this.domNode, "onItemFocused", lang.hitch(this, this.onItemFocused));

         if (this.itemKey)
         {
            this.alfSubscribe("ALF_BRING_ITEM_INTO_VIEW", lang.hitch(this, this.onBringItemIntoView));
         }
      },

      /**
       * Overrides the _KevNavContainer function to call the "blur" function of the widget that has lost
       * focus (assuming it has one).
       * 
       * @instance
       */
      _onChildBlur: function alfresco_lists_views_ListRenderer___onChildBlur(widget) {
         if (typeof widget.blur === "function")
         {
            widget.blur();
         }
      },

      /**
       * Handles requests to focus a specific child item that has been clicked on. This is a custom
       * event issued from a module mixing in the 
       * [_MultiItemRendererMixin]{@link module:alfresco/lists/views/layouts/_MultiItemRendererMixin}.
       *
       * @instance
       * @param {object} evt The click event
       */
      onItemFocused: function alfresco_lists_views_ListRenderer__onItemFocused(evt) {
         this.focusChild(evt.item);
      },

      /**
       * This sets up the default keyboard handling for a view. The standard controls are navigation to the
       * next item by pressing the down key and navigation to the previous item by pressing the up key.
       * 
       * @instance
       */
      setupKeyboardNavigation: function alfresco_lists_views_ListRenderer__setupKeyboardNavigation() {
         this.connectKeyNavHandlers([keys.UP_ARROW], [keys.DOWN_ARROW]);
      },

      /**
       * This function is called whenever a parent [list]{@link module:alfresco/lists/AlfList} publishes a request
       * to bring a specfic item into view, e.g. to ensure that a requested item that is displayed off the page is
       * brought into view by automatically setting the scroll position. This function only checks to see whether or not
       * the current widget represents that item - it does not perform the actual action to bring the item into view.
       *
       * @instance
       * @param  {object} payload The details of the item to find
       */
      onBringItemIntoView: function alfresco_lists_views_ListRenderer__onBringItemIntoView(payload) {
         if (payload && (payload.item || payload.item === 0))
         {
            array.some(this._renderedItemWidgets, function(widgets) {
               return array.some(widgets, function(widget) {

                  var found = false;
                  if (widget && 
                      widget.currentItem && 
                      (widget.currentItem[this.itemKey] || widget.currentItem[this.itemKey] === 0) &&
                      widget.currentItem[this.itemKey].toString() === payload.item)
                  {
                     this.bringItemIntoView(widget);
                     found = true;
                  }
                  return found;
               }, this);
            }, this);
         }
      },

      /**
       * This function is called to bring a specific item into the users view. By default this is done by scrolling
       * the item into view.
       *
       * @instance
       * @param {object} widget The widget to bring into view.
       */
      bringItemIntoView: function alfresco_lists_views_ListRenderer__bringItemIntoView(widget) {
         if (widget.domNode)
         {
            // Find the scroll parent and check to see if it is the document, we need to special case scrolling within
            // the document as we need to animate the scrollTop of both the html and body elements.
            var scrollParent = this.findScrollParent(widget.domNode);
            if (scrollParent.is("html"))
            {
               var offset = $(widget.domNode).offset();
               $("html, body").animate({
                  scrollTop: offset.top
               });
            }
            else
            {
               // When dealing with a scrollable element within the main document we just need to calculate the 
               // appropriate position to scroll to based on the position within the item and the current
               // scrollTop value...
               var position = $(widget.domNode).position();
               var currentScrollTop = scrollParent.scrollTop();
               var scrollTo = currentScrollTop + position.top;
               scrollParent.animate({
                  scrollTop: scrollTo
               });
            }
         }
      },

      /**
       * This function recursively searches out through the DOM to find the first parent of the supplied element that
       * is capable of scrolling vertically and has scrollbars displayed.
       * 
       * @instance
       * @returns {element} The DOM element that is the scroll parent with scroll bars displayed
       * @since 1.0.53
       */
      findScrollParent: function alfresco_lists_views_ListRenderer__findScrollParent(domNode) {
         var scrollParent = $(domNode).scrollParent();
         if (!scrollParent.is("html") &&
             scrollParent[0].clientHeight === scrollParent[0].scrollHeight)
         {
            scrollParent = this.findScrollParent(scrollParent[0]);
         }
         return scrollParent;
      }
   });
});