Source: documentlibrary/AlfBreadcrumbTrail.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/>.
 */

/**
 * <p>This widget can be used to render breadcrumb trails. It can be used either to render a fixed set of breadcrumbs (e.g.
 * where you might want to render some information about a fixed location of a page) or it can be used to render path
 * based data. When rendering path based data it is possible to get the path from the
 * [browser URL hash]{@link module:alfresco/documentlibrary/AlfBreadcrumbTrail#useHash} or from explicitly configured
 * [path publishing topics]{@link module:alfresco/documentlibrary/AlfBreadcrumbTrail#pathChangeTopic}. When rendering
 * a path based breadcrumb trail it is expected that the last item in the trail  represents the
 * current location and clicking it can either navigate the user to another page (in which case you should set the
 * [lastBreadcrumbPublishTopic]{@link module:alfresco/documentlibrary/AlfBreadcrumbTrail#lastBreadcrumbPublishTopic} and other associated
 * attributes). Clicking on any other breadcrumb in the trail is expected to simply publish information about the selected
 * path.<p>
 * 
 * <p>Each element in the trail is rendered by the [AlfBreadcrumb]{@link module:alfresco/documentlibrary/AlfBreadcrumb}
 * widget. This module should be extended and the 
 * [renderBreadcrumb]{@link module:alfresco/documentlibrary/AlfBreadcrumbTrail#lastBreadcrumbPublishTopic#renderBreadcrumb} function
 * overridden if an alternative widget needs to be used.</p>
 * 
 * @module alfresco/documentlibrary/AlfBreadcrumbTrail
 * @extends external:dijit/_WidgetBase
 * @mixes external:dojo/_TemplatedMixin
 * @mixes module:alfresco/core/Core
 * @mixes module:alfresco/core/CoreWidgetProcessing
 * @mixes module:alfresco/documentlibrary/_AlfDocumentListTopicMixin
 * @mixes module:alfresco/renderers/_PublishPayloadMixin
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "dijit/_WidgetBase", 
        "dijit/_TemplatedMixin",
        "dojo/text!./templates/AlfBreadcrumbTrail.html",
        "alfresco/core/Core",
        "alfresco/core/CoreWidgetProcessing",
        "alfresco/documentlibrary/_AlfDocumentListTopicMixin",
        "alfresco/renderers/_PublishPayloadMixin",
        "alfresco/documentlibrary/AlfBreadcrumb",
        "alfresco/util/hashUtils",
        "dojo/_base/lang",
        "dojo/_base/array",
        "dojo/dom-construct",
        "dojo/dom-class"], 
        function(declare, _WidgetBase, _TemplatedMixin, template,  AlfCore, CoreWidgetProcessing, _AlfDocumentListTopicMixin, 
                 _PublishPayloadMixin, AlfBreadcrumb, hashUtils, lang, array, domConstruct, domClass) {

   return declare([_WidgetBase, _TemplatedMixin, AlfCore, CoreWidgetProcessing, _PublishPayloadMixin, _AlfDocumentListTopicMixin], {
      
      /**
       * An array of the i18n files to use with this widget.
       * 
       * @instance
       * @type {object[]}
       * @default [{i18nFile: "./i18n/AlfBreadcrumbTrail.properties"}]
       */
      i18nRequirements: [{i18nFile: "./i18n/AlfBreadcrumbTrail.properties"}],
      
      /**
       * An array of the CSS files to use with this widget.
       * 
       * @instance cssRequirements {Array}
       * @type {object[]}
       * @default [{cssFile:"./css/AlfBreadcrumbTrail.css"}]
       */
      cssRequirements: [{cssFile:"./css/AlfBreadcrumbTrail.css"}],
      
      /**
       * The HTML template to use for the widget.
       * @instance
       * @type {String}
       */
      templateString: template,
      
      /**
       * An array of fixed breadcrumbs to render. Each breadcrumb needs to have a "label" attribute and can
       * also optionally include "publishTopic" and "publishPayload" attributes if clicking on the breadcrumb
       * should have an action.
       * 
       * @instance
       * @type {object[]}
       * @default
       */
      breadcrumbs: null,

      /**
       * A path to use as a breadcrumb trail. This is a simple alternative to defining invidual breadcrumbs
       * but gives less flexibility over what clicking on the breadcrumbs do.
       *
       * @instance
       * @type {string}
       * @default
       */
      currentPath: "/",

      /**
       * Indicates whether or not to hide the breadcrumb trail when first instantiated.
       * @instance 
       * @type{boolean}
       * @default
       */
      hide: false,
      
      /**
       * This indicates that the final breadcrumb in the trail  is 
       * 
       * @instance
       * @type {boolean}
       * @default
       */
      lastBreadcrumbIsCurrentNode: false,

      /**
       * This is the topic that is published when the final breadcrumb in the trail is clicked.
       *
       * @instance
       * @type {string}
       * @default
       */
      lastBreadcrumbPublishTopic: null,

      /**
       * This is the payload that is published when the final breadcrumb in the trail is clicked.
       *
       * @instance
       * @type {object}
       * @default
       */
      lastBreadcrumbPublishPayload: null,

      /**
       * Indicates whether or not the clicking on the last breadcrumb publishes on the gloabl scope. This 
       * defaults to true because it is expected that the last breadcrumb will be a page navigation and
       * therefore need to publish to a globally subscribing 
       * [NavigationService]{@link module:alfresco/services/NavigationServce}.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      lastBreadcrumbPublishGlobal: true,

      /**
       * Indicates whether or not the clicking on the last breadcrumb publishes on the parent scope
       *
       * @instance
       * @type {boolean}
       * @default
       */
      lastBreadcrumbPublishToParent: false,

      /**
       * This is the type of payload that is published when the final breadcrumb in the trail is clicked. 
       * By default it will use the 
       * [lastBreadcrumbPublishPayload]{@link module:alfresco/documentlibrary/AlfBreadCrumbTrail#lastBreadcrumbPublishPayload} 
       * exactly as it is configured.
       *
       * @instance
       * @type {string}
       * @default
       */
      lastBreadcrumbPublishPayloadType: "CONFIGURED",

      /**
       * Indicate whether or not the "currentItem" object will be mixed into the 
       * [lastBreadcrumbPublishPayload]{@link module:alfresco/documentlibrary/AlfBreadCrumbTrail#lastBreadcrumbPublishPayload}.
       * By default this is set to false because it is not expected that there will typically be a "currentItem" object set.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      lastBreadcrumbPublishPayloadItemMixin: false,

      /**
       * An array of the modifying functions that will be applied to the 
       * [lastBreadcrumbPublishPayload]{@link module:alfresco/documentlibrary/AlfBreadCrumbTrail#lastBreadcrumbPublishPayload}
       * if the 
       * [lastBreadcrumbPublishPayloadType]{@link module:alfresco/documentlibrary/AlfBreadCrumbTrail#lastBreadcrumbPublishPayloadType}
       * is configured to be "PROCESS".
       *
       * @instance
       * @type {string[]}
       * @default
       */
      lastBreadcrumbPublishPayloadModifiers: null,

      /**
       * A topic to subscribe to for path change events.
       * 
       * @instance
       * @type {string}
       * @default
       * @since 1.0.17
       */
      pathChangeTopic: null,

      /**
       * The property to use when retrieving path data from payloads published on the 
       * [pathChangeTopic]{@link module:alfresco/documentlibrary/AlfBreadCrumbTrail#pathChangeTopic}.
       * 
       * @instance
       * @type {string}
       * @default
       * @since 1.0.96
       */
      pathChangeTopicPathProperty: "path",

      /**
       * The label for the root of the breadcrumb trail.
       * 
       * @instance
       * @type {string}
       * @default
       */
      rootLabel: "root.label",

      /**
       * Indicates whether or not to display a root label for the breadcrumb trail.
       * This label will effectively represent "/"
       * 
       * @instance
       * @type {boolean} 
       * @default
       */
      showRootLabel: true,
         
      /**
       * Indicates whether the browser URL hash will be used to provide the breadcrumb trail. If this is
       * set to true then the breadcrumb trail will be re-rendered as the hash changes.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      useHash: false,

      /**
       * This boolean flag is used to indicate whether or not a filter is being displayed rather than
       * a breadcrumb trail. This should not be configured or changed as it is used to maintain the
       * internal state of the widget. It is necessary to keep track of whether or not a filter is 
       * displayed to determine what actions to take when the 
       * [current node changes]{@link module:alfresco/documentlibrary/AlfBreadcrumbTrail#onCurrentNodeChange}.
       * 
       * @instance
       * @type {boolean}
       * @default
       */
      _filterDisplayed: false,

      /**
       * Implements the Dojo widget lifecycle function to subscribe to the relevant topics that
       * provide information on path and filter changes. 
       * 
       * @instance
       */
      postCreate: function alfresco_documentlibrary_AlfBreadcrumbTrail__postCreate() {
         this.alfSubscribe(this.showPathTopic, lang.hitch(this, this.onShowBreadcrumb));
         this.alfSubscribe(this.filterSelectionTopic, lang.hitch(this, this.onFilterSelection));
         this.alfSubscribe("ALF_DOCUMENTLIST_CATEGORY_CHANGED", lang.hitch(this, this.onFilterSelection));
         this.alfSubscribe(this.docListTagChangedTopic, lang.hitch(this, this.onFilterSelection));

         if (this.breadcrumbs)
         {
            // Render a fixed breadcrumb trail...
            array.forEach(this.breadcrumbs, lang.hitch(this, this.renderBreadcrumb));
         }
         else
         {
            // When the "lastBreadcrumb" represents the current node (e.g. when the breadcrumb trail represents
            // a location in a Document Library for example) then it is necessary to subscribe to changes
            // of the current node...
            if (this.lastBreadcrumbIsCurrentNode === true)
            {
               this.alfSubscribe(this.metadataChangeTopic, lang.hitch(this, this.onCurrentNodeChange));
            }

            if (this.currentPath)
            {
               this.renderPathBreadcrumbTrail();
            }
            if (this.useHash === true)
            {
               // Subscribe using the widgets own scope (don't assume global) because the topic published
               // will in practice be published at a share scope (i.e. when used in the context of a 
               // scoped Document Library the hash change publication will be scoped, not global)...
               this.alfSubscribe(this.hashChangeTopic, lang.hitch(this, this.onHashChanged));
               this.onHashChanged(hashUtils.getHash());
            }
            else if (this.pathChangeTopic)
            {
               this.alfSubscribe(this.pathChangeTopic, lang.hitch(this, this.onPathChanged));
            }
         }
      },

      /**
       * The current Node that content will be worked relative to.
       * 
       * @instance
       * @type {object}
       * @default
       */
      currentNode: null,
      
      /**
       * This handles publications on the [metadataChangeTopic]{@link module:alfresco/documentlibrary/_AlfDocumentListTopicMixin#metadataChangeTopic}
       * topic to set the location of the current folder (e.g the last crumb in the trail). This information is required
       * because the last crumb navigates to the details page for the node rather than just the list view of the crumb. 
       * 
       * @instance
       * @param {object} payload 
       */
      onCurrentNodeChange: function alfresco_documentlibrary_AlfBreadcrumbTrail__onCurrentNodeChange(payload) {
         if (payload && payload.node)
         {
            this.currentNode = payload.node;
            if (this._filterDisplayed === false)
            {
               // It's necessary to re-render the breadcrumb trail after the current node changes because on initial
               // page load the current node update will occur after the breadcrumb has been rendered for the first time...
               // Currently this can result in the breadcrumb rendering twice, but at least it will be accurate (only 
               // remove this line when verifying initial page loading results)...
               this.renderPathBreadcrumbTrail();
            }
         }
         else
         {
            this.alfLog("error", "A request was made to update the current NodeRef, but no 'node' property was provided in the payload: ", payload);
         }
      },

      /**
       * Handle hash-change events
       *
       * @instance
       * @param {object} newHash The new hash value
       * @since 1.0.60
       */
      onHashChanged: function alfresco_documentlibrary_AlfBreadcrumbTrail__onHashChanged(newHash) {
         if (newHash.path) 
         {
            this.onPathChanged(newHash);
         } 
         else if (newHash.filter && newHash.description) 
         {
            this.onFilterSelection(newHash);
         }
      },
      
      /**
       * This handles publications on the [hashChangeTopic]{@link module:alfresco/documentlibrary/_AlfDocumentListTopicMixin#hashChangeTopic}
       * topic and updates the [currentPath]{@link module:alfresco/documentlibrary/AlfBreadcrumbTrail#currentPath} attribute and calls the
       * [renderBreadcrumbTrail]{@link module:alfresco/documentlibrary/AlfBreadcrumbTrail#renderBreadcrumbTrail} function to re-render the
       * breadcrumb trail.
       *  
       * @instance
       * @param {object} payload The details of the path change. The object must have both 'filterId' and 'filterData' attributes
       */
      onPathChanged: function alfresco_documentlibrary_AlfBreadcrumbTrail__onPathChanged(payload) {
         this.alfLog("log", "Detected path change", payload);
         var path = payload && lang.getObject(this.pathChangeTopicPathProperty, false, payload);
         if (path)
         {
            this._filterDisplayed = false;
            this.currentPath = path;
            this.renderPathBreadcrumbTrail();
            if(this.useHash === true) {
               hashUtils.updateHash({
                  description: null
               }, true);
            }

         }
      },
     
      /**
       * Renders a breadcrumb.
       * 
       * @param {object} breadcrumb The configuration for the breadcrumb
       * @param {number} index The index of the breadcrumb
       */
      renderBreadcrumb: function alfresco_documentlibrary_AlfBreadcrumbTrail__renderBreadcrumb(breadcrumb, /*jshint unused:false*/ index) {
         if (breadcrumb.label)
         {
            var bc = this.createWidget({
               name: "alfresco/documentlibrary/AlfBreadcrumb",
               config: breadcrumb
            });
            bc.placeAt(this.containerNode);
         }
      },

      /**
       * This function can be used to copy the publication configuration from the breadcrumb trail
       * to an individual [AlfBreadcrumb]{@link module:alfresco/documentlibrary/AlfBreadcrumb}.
       * 
       * @instance
       * @param {object} config The configuration object to copy the publication configuration to
       * @since 1.0.71
       */
      copyPublicationConfig: function alfresco_documentlibrary_AlfBreadcrumbTrail__copyPublicationConfig(config) {
         config.publishTopic = this.publishTopic;
         config.publishPayload = this.publishPayload;
         config.publishPayloadItemMixin = this.publishPayloadItemMixin;
         config.publishPayloadModifiers = this.publishPayloadModifiers;
         config.publishPayloadType = this.publishPayloadType;
         config.publishGlobal = this.publishGlobal;
         config.publishToParent = this.publishToParent;
      },


      /**
       * Renders an individual [AlfBreadcrumb]{@link module:alfresco/documentlibrary/AlfBreadcrumb} in the breadcrumb trail.
       * 
       * @instance
       * @param {string} folderName The name of the folder to render
       * @param {integer} index The index of the folder in array of breadcrumbs
       */
      renderPathBreadcrumb: function alfresco_documentlibrary_AlfBreadcrumbTrail__renderPathBreadcrumb(paths, folderName, index) {
         var path = paths.slice(0, index+1).join("/");
         var config = {
            label: folderName,
            path: path
         };
         if (index < paths.length -1)
         {
            if (this.useHash === true)
            {
               config.publishTopic = "ALF_NAVIGATE_TO_PAGE";
               config.publishPayload = {
                  url: "path=" + (path || "/") + "¤tPage=1",
                  type: "HASH",
                  target: "CURRENT",
                  modifyCurrent: true
               };
               config.publishGlobal = true;
            }
            else if (this.publishTopic)
            {
               // If a specific publishTopic has been provided then use this for all the breadcrumbs 
               // (with the exception of the last breadcrumb as this can be individually
               // configured)...
               this.copyPublicationConfig(config);
            }
            else
            {
               config.publishTopic = this.pathChangeTopic;
               config.publishPayload = {
                  path: (path || "/")
               };
            }
         }
         else if (this.lastBreadcrumbPublishTopic)
         {
            config.publishTopic = this.lastBreadcrumbPublishTopic;
            config.publishPayload = this.generatePayload(this.lastBreadcrumbPublishPayload, this.currentItem, null, this.lastBreadcrumbPublishPayloadType, this.lastBreadcrumbPublishPayloadItemMixin, this.lastBreadcrumbPublishPayloadModifiers);
            config.publishGlobal = this.lastBreadcrumbPublishGlobal;
            config.publishToParent = this.lastBreadcrumbPublishToParent;
         }
         else if (this.publishTopic)
         {
            // Still allows for explicit last breadcrumb configuration to trump global configuration...
            this.copyPublicationConfig(config);
         }
         this.renderBreadcrumb(config);
      },
      
      /**
       * Renders the breadcrumb trail by calling the [renderBreadcrumb]{@link module:alfresco/documentlibrary/AlfBreadcrumbTrail#renderBreadcrumb}
       * function for each element in the current path.
       * 
       * @instance
       */
      renderPathBreadcrumbTrail: function alfresco_documentlibrary_AlfBreadcrumbTrail__renderPathBreadcrumbTrail() {
         if (this.currentPath)
         {
            domConstruct.empty(this.containerNode);

            if (this.currentPath[0] !== "/")
            {
               this.currentPath = "/" + this.currentPath;
            }
            var paths = this.currentPath.split("/");
            if (this.currentPath === "/")
            {
               paths = ["/"];
            }
            if (paths[paths.length-1] === "")
            {
               paths.pop();
            }

            var displayPaths = paths.concat();
            if (this.rootLabel)
            {
               displayPaths[0] = this.message(this.rootLabel);
            }

            if (!this.showRootLabel)
            {
               displayPaths.shift();
               paths.shift();
            }
            
            array.forEach(displayPaths, lang.hitch(this, this.renderPathBreadcrumb, paths));
            
            if (this.hide) 
            {
               domClass.add(this.domNode, "hidden");
            }
            else
            {
               domClass.remove(this.domNode, "hidden");
            }
         }
      },
      
      /**
       * This handles publications on the [onShowBreadcrumb]{@link module:alfresco/documentlibrary/_AlfDocumentListTopicMixin#onShowBreadcrumb}
       * topic to change the breadcrumb trail visibility.
       * 
       * @instance
       * @param {object} payload Must contain a "selected" attribute which should map to whether or not the trail is shown or not. 
       */
      onShowBreadcrumb: function alfresco_documentlibrary_AlfBreadcrumbTrail__onShowBreadcrumb(payload) {
         if (payload && (payload.selected || payload.selected === false))
         {
            this.hide = !payload.selected;
            if (this.hide) 
            {
               domClass.add(this.domNode, "hidden");
            }
            else
            {
               domClass.remove(this.domNode, "hidden");
            }
         }
      },
      
      /**
       * This handles publications on the [filterSelectionTopic]{@link module:alfresco/documentlibrary/_AlfDocumentListTopicMixin#filterSelectionTopic} topic
       * to show when a filter is selected. The rendered path is replaced with the details of the filter.
       * 
       * @instance
       */
      onFilterSelection: function alfresco_documentlibrary_AlfBreadcrumbTrail__onFilterSelection(payload) {
         // Empty the current breadcrumb trail...
         if (payload && payload.description)
         {
            domConstruct.empty(this.containerNode);
            this._filterDisplayed = true;
            this.renderBreadcrumb({
               label: payload.description
            });
            
            if(this.useHash === true) 
            {
               if(hashUtils.getHash().description !== payload.description) 
               {
                  hashUtils.updateHash({
                     description: payload.description
                  }, true);
               }
            }
         }
         else
         {
            this.alfLog("warn", "A request was made to display filter information but no description was provided: ", payload);
         }
      }
   });
});