Source: renderers/_ActionsMixin.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 module can be mixed into other modules to generate [menu items]{@link module:alfresco/menus/AlfMenuItem}
 * representing Alfresco document or folder actions generated for a specific node or an entirely custom action
 * list. It exists as mixin to support multipe ways of rendering actions (e.g. either in a menu bar or in a
 * context menu)</p>.
 *
 * @module alfresco/renderers/_ActionsMixin
 * @mixes module:alfresco/core/Core
 * @mixes module:alfresco/documentlibrary/_AlfDocumentListTopicMixin
 * @mixes module:alfresco/renderers/_PublishPayloadMixin
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "alfresco/core/Core",
        "alfresco/core/CoreWidgetProcessing",
        "alfresco/documentlibrary/_AlfDocumentListTopicMixin",
        "alfresco/renderers/_PublishPayloadMixin",
        "alfresco/core/ObjectProcessingMixin",
        "alfresco/menus/_AlfPopupCloseMixin",
        "alfresco/menus/AlfMenuItem",
        "dojo/_base/array",
        "dojo/_base/lang",
        "service/constants/Default",
        "alfresco/core/ArrayUtils",
        "alfresco/core/JsNode",
        "alfresco/core/ObjectTypeUtils"],
        function(declare, AlfCore, CoreWidgetProcessing, _AlfDocumentListTopicMixin, _PublishPayloadMixin, ObjectProcessingMixin, 
                 _AlfPopupCloseMixin, AlfMenuItem, array, lang, AlfConstants, AlfArray, JsNode, ObjectTypeUtils) {

   return declare([AlfCore, CoreWidgetProcessing, _AlfDocumentListTopicMixin, _PublishPayloadMixin, ObjectProcessingMixin, _AlfPopupCloseMixin], {

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

      /**
       * An array of the i18n files to use with this widget.
       *
       * @instance
       * @type {object[]}
       * @default [{i18nFile: "./i18n/_ActionsMixin.properties"}]
       */
      i18nRequirements: [{i18nFile: "./i18n/_ActionsMixin.properties"}],

      /**
       *  Array containing a list of allowed actions
       *  This is used to filter out actions that the actions API returns, but haven't yet been implemented.
       *  TODO: Remove this once all actions have been implemented by the actions service.
       *  Currently - all actions of type link and pagelink should work.
       *
       * @instance
       * @type {array}
       * @default
       */
      allowedActions: null,

      /**
       * A stringified array containing a list of allowed actions. This has been added to support token replacement
       * within JSON models. It will override any [allowedActions]{@link module:alfresco/renderers/_ActionsMixin#allowedActions}
       * configuration.
       *
       * @instance
       * @type {array}
       * @default
       */
      allowedActionsString: null,

      /**
       * An array of action configuration objects to render in the action menu. This array will take precedence
       * over any actions defined on the "currentItem" unless the
       * [mergeActions]{@link module:alfresco/renderers/_ActionsMixin#mergeActions}) attribute is
       * configured to be true (in which case both the actions defined on the "currentItem" will be rendered along
       * with the actions defined in this array).
       * 
       * @instance
       * @type {object[]}
       * @default
       */
      customActions: null,

      /**
       * Indicates whether or not actions should be filtered according to the
       * [allowedActions array]{@link module:alfresco/renderers/_ActionsMixin#allowedActions}.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      filterActions: false,

      /**
       * This indicates that both actions defined on the "currentItem" as well as actions defined by the
       * [customActions]{@link module:alfresco/renderers/_ActionsMixin#customActions} and
       * [widgetsForActions]{@link module:alfresco/renderers/_ActionsMixin#widgetsForActions} will be rendered.
       * 
       * @instance
       * @type {boolean}
       * @default
       */
      mergeActions: false,

      /**
       * Handles parsing of [allowedActionsString]{@link module:alfresco/renderers/_ActionsMixin#allowedActionsString} if configured
       * to override [allowedActions]{@link module:alfresco/renderers/_ActionsMixin#allowedActions}.
       * 
       * @instance
       */
      postCreate: function alfresco_renderers__ActionsMixin__postCreate() {
         this.inherited(arguments);
         if (this.allowedActionsString)
         {
            try
            {
               this.allowedActions = JSON.parse(this.allowedActionsString);
            }
            catch(e)
            {
               // Ignore bad configuration
               this.alfLog("warn", "A non-parsable 'allowedActionsString' was configured", this, this.allowedActionsString);
            }
         }
      },

      /**
       * This is array of actions to render if no customActions are defined or no actions are found in the
       * currentItem. The list of actions does not currently reflect all the actions that will eventually be available,
       * additional actions will be added in future releases as support is provided for them.
       *
       * @instance
       * @type {object[]}
       */
      widgetsForActions: [
         {
            name: "alfresco/renderers/actions/Download"
         },
         {
            name: "alfresco/renderers/actions/DownloadAsZip"
         },
         {
            name: "alfresco/renderers/actions/UploadNewVersion"
         },
         {
            name: "alfresco/renderers/actions/ChangeType"
         },
         {
            name: "alfresco/renderers/actions/CopyTo"
         },
         {
            name: "alfresco/renderers/actions/Delete"
         },
         {
            name: "alfresco/renderers/actions/ManageAspects"
         },
         {
            name: "alfresco/renderers/actions/MoveTo"
         },
         {
            name: "alfresco/renderers/actions/StartWorkflow"
         }
      ],

      /**
       * Add the actions provided by the current item.
       *
       * @instance
       */
      addActions: function alfresco_renderers__ActionsMixin__addActions() {
         // Iterate over the actions to create a menu item for each of them...
         if (this.mergeActions === true)
         {
            // Add the actions on the currentItem and then add additional custom actions...
            ObjectTypeUtils.isArray(this.currentItem.actions) && array.forEach(this.currentItem.actions, lang.hitch(this, this.addAction));
            ObjectTypeUtils.isArray(this.customActions) && array.forEach(this.customActions, lang.hitch(this, this.addAction));
            this.processWidgetsForActions();
         }
         else if (ObjectTypeUtils.isArray(this.customActions))
         {
            array.forEach(this.customActions, lang.hitch(this, this.addAction));
         }
         else if (ObjectTypeUtils.isArray(this.currentItem.actions))
         {
            array.forEach(this.currentItem.actions, lang.hitch(this, this.addAction));
         }
         else if (ObjectTypeUtils.isArray(this.widgetsForActions))
         {
            // Provide default actions based on sensible defaults evaluated based on the 
            // current item to be actioned...
            this.processWidgetsForActions();
         }
      },

      /**
       * This function handles the creation of actions based on the 
       * [widgetsForActions]{@link module:alfresco/renderers/_ActionsMixin#widgetsForActions} array. This
       * aims to provide a sensible set of default actions based on the metadata of the current node.
       * 
       * @instance
       */
      processWidgetsForActions: function alfresco_renderers__ActionsMixin__processWidgetsForActions() {
         // TODO: We probably want to avoid rendering all the actions until the menu is opened...
         var idPrefix = this.id + "_";
         var actions = [];
         array.forEach(this.widgetsForActions, function(action) {
            if (action && action.name)
            {
               // It's expected that an Actions rendererer will be used inside a row of Document List view. The
               // row itself may or may note have its own scope and where some actions will require that the
               // list is refreshed (e.g. when items are deleted, moved or copied) in which case the responseScope
               // should be addressed at the list. We therefore need to set the response scope accordingly...
               var responseScope = this.publishToParent ? this.parentPubSubScope : this.pubSubScope;
               require([action.name], function(config) {
                  if (config)
                  {
                     var clonedConfig = lang.clone(config);
                     clonedConfig.id = idPrefix + clonedConfig.id;
                     lang.setObject("publishPayload.responseScope", responseScope, clonedConfig);

                     // TODO: A potential future improvement here would be to allow replacement
                     //       or augmentation of the default renderFilters.
                     actions.push({
                        name: "alfresco/menus/AlfMenuItem",
                        config: clonedConfig
                     });
                  }
               });
            }
         }, this);
         this.processWidgets(actions);
      },

      /**
       * This overrides the [inherited extension point]{@link module:alfresco/core/CoreWidgetProcessing#allWidgetsProcessed}
       * to add each created action [menu item]{@link module:alfresco/menus/AlfMenuItem} to the menu.
       * 
       * @instance
       * @param {object[]} widgets The widgets created (this is expected to be a single item)
       */
      allWidgetsProcessed: function alfresco_renderers__ActionsMixin__allWidgetsProcessed(widgets) {
         array.forEach(widgets, function(widget) {
            this._menu.addChild(widget);
         }, this);
      },

      /**
       *
       * @instance
       * @param {object} action The configuration for the action to add
       * @param (integer} index The index of the action
       */
      addAction: function alfresco_renderers__ActionsMixin__addAction(action, /*jshint unused:false*/index) {
         if (this.filterActions === false || AlfArray.arrayContains(this.allowedActions, action.id))
         {
            this.alfLog("log", "Adding action", action);

            // If there is a node object then create the corresponding jsNode object - this is required for 
            // label processing below
            if (!this.currentItem.jsNode && this.currentItem.node)
            {
               this.currentItem.jsNode = new JsNode(this.currentItem.node);
            }

            // Some Share actions have variable labels, most notably the simple workflow actions, and these should
            // be processed to ensure that they are rendered correctly.
            if (action.label)
            {
               action.label = this.processTokens(action.label, this.currentItem);
            }

            var id = action.id ? (this.id + "_" + action.id) : null;
            var payload = (action.publishPayload) ? action.publishPayload : {document: this.currentItem, action: action};
            payload = this.generatePayload(payload, this.currentItem, null, action.publishPayloadType, action.publishPayloadItemMixin, action.publishPayloadModifiers);

            // It's expected that an Actions rendererer will be used inside a row of Document List view. The
            // row itself may or may note have its own scope and where some actions will require that the
            // list is refreshed (e.g. when items are deleted, moved or copied) in which case the responseScope
            // should be addressed at the list. We therefore need to set the response scope accordingly...
            payload.responseScope = this.publishToParent ? this.parentPubSubScope : this.pubSubScope;

            var menuItem = new AlfMenuItem({
               id: id,
               label: action.label,
               iconImage: AlfConstants.URL_RESCONTEXT + "components/documentlibrary/actions/" + action.icon + "-16.png",
               type: action.type,
               pubSubScope: this.pubSubScope,
               parentPubSubScope: this.parentPubSubScope,
               publishTopic: action.publishTopic || this.singleDocumentActionTopic,
               publishPayload: payload,
               publishGlobal: true,
               publishToParent: false
            });
            this._menu.addChild(menuItem);
         }
         else
         {
            this.alfLog("log", "Skipping action as it's missing from whitelist: " + action);
         }
      },

      /**
       * 
       * @instance
       * @since 1.0.62
       */
      createEmptyMenu: function alfresco_renderers__ActionsMixin__createEmptyMenu() {
         this.dropDown = this.createWidget({
            id: this.id + "_DROPDOWN",
            name: "alfresco/menus/AlfMenuGroups",
            config: {
               widgets: [
                  {
                     name: "alfresco/menus/AlfMenuGroup",
                     assignToScope: this,
                     assignTo: "_menu"
                  }
               ]
            }
         });

         // NOTE: Need to assign this.dropDown to this.popup in order for _AlfPopupCloseMixin capabilities
         //       to work correctly...
         this.popup = this.dropDown;
         this.registerPopupCloseEvent();
      },

      /**
       * 
       * @instance
       * @param  {Function} callback The function to call to display the drop-down
       * @since 1.0.62
       */
      createDropDownMenu: function alfresco_renderers__ActionsMixin__createDropDownMenu(callback) {
         this.createEmptyMenu();
         this.addActions();
         callback();
      },

      /**
       * 
       * @instance
       * @since 1.0.62
       */
      isLoaded: function alfresco_renderers__ActionsMixin__isLoaded() {
         return (!!this.dropDown && this.dropDown.isLoaded);
      },

      /**
       * 
       * @instance
       * @param  {Function} callback The function to call to display the drop-down
       * @since 1.0.62
       */
      loadDropDown: function alfresco_renderers__ActionsMixin__loadDropDown(callback) {
         var dropDown = this.dropDown;
         if(!dropDown) { 
            this.createDropDownMenu(callback);
         }
         else {
            callback();
         }
      }
   });
});