Source: renderers/Progress.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 renderer is specifically designed report progress to the user. It was originally written to support
 * the download folder action in Alfresco Share so it's default configuration (e.g. labels, topics and payload
 * properties) are intended for that purpose. To use this widget for reporting the progress on alternative 
 * activities then the default configuration should be overridden.</p>
 * <p>The [requestProgressTopic]{@link module:alfresco/renderers/Progress#requestProgressTopic} should be configured
 * as the topic to publish to begin requesting progress information to be generated.</p>
 *
 * @module alfresco/renderers/ProgressActions
 * @extends module:aikau/core/BaseWidget
 * @author David Webster
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "aikau/core/BaseWidget",
        "dojo/_base/lang",
        "dojo/dom-style"],
        function(declare, BaseWidget, lang, domStyle) {

   return declare([BaseWidget], {

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

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

      /**
       * renderProgressUI
       *
       * @instance
       * @type {String}
       * @default
       */
      renderProgressUITopic: "ALF_PROGRESS_RENDER",

      /**
       * The message to display when progress is being initialized. By default this includes a message
       * that was originally created to support the original use-case for reporting progress on generating
       * an archive to download. This should be overridden for alternative use cases.
       *
       * @instance
       * @type {string}
       * @default
       */
      creatingMessage: "renderer.progress.creating",

      /**
       * Whether the task(s) have been cancelled
       *
       * @instance
       * @readonly
       * @type {boolean}
       * @default
       * @since 1.0.44
       */
      isCancelled: false,

      /**
       * The message to display that reports the current status. This message will be provided two tokens
       * that indicate the number of items processed and the total number of items to process.
       *
       * @instance
       * @type {string}
       * @default
       */
      statusMessage: "renderer.progress.status",

      /**
       * The message to display when an error occurs reporting progress.
       *
       * @instance
       * @type {string}
       * @default
       */
      errorMessage: "renderer.progress.error",

      /**
       * The message to display when progress is complete.
       *
       * @instance
       * @type {string}
       * @default
       */
      completedMessage: "renderer.progress.complete",

      /**
       * Overrides [the inherited function]{@link module:aikau/core/BaseWidget#createWidgetDom}
       * to construct the DOM for the widget using native browser capabilities.
       *
       * @instance
       * @since 1.0.101
       */
      createWidgetDom: function alfresco_renderers_Progress__createWidgetDom() {
         this.containerNode = this.domNode = document.createElement("div");
         this.domNode.classList.add("alfresco-renderers-Progress");

         this.labelNode = document.createElement("p");
         this.labelNode.classList.add("alfProgressBarLabel");

         var progressContainer = document.createElement("div");
         progressContainer.classList.add("alfProgressBarContainer");

         this.progressNode = document.createElement("div");
         this.progressNode.classList.add("alfProgressBarProgress");

         progressContainer.appendChild(this.progressNode);
         this.domNode.appendChild(this.labelNode);
         this.domNode.appendChild(progressContainer);
      },

      /**
       * Sets up the form specific configuration for the dialog.
       *
       * @instance
       * @returns {object} The dialog configuration.
       */
      postCreate: function alfresco_renderers_Progress__postCreate() {
         this.labelNode.innerHTML = this.message(this.creatingMessage);

         // Subscribe to Update Progress Action.
         this.alfSubscribe("ALF_CLOSE_DIALOG", lang.hitch(this, this.cleanUp), true);
         this.alfSubscribe(this.renderProgressUITopic, lang.hitch(this, this.onRenderUI));

         // Kick off the initial progress request
         this.onRequestProgress();
      },

      /**
       * This is the topic that will be published to request the start of progress updates. It will publish
       * a payload on this topic containing scoped topics that should be used to provide 
       * [update]{@link module:alfresco/renderers/Progress#progressUpdateTopic}, 
       * [completion]{@link module:alfresco/renderers/Progress#progressCompleteTopic},
       * [cancellation]{@link module:alfresco/renderers/Progress#progressCancelledTopic} and
       * [error]{@link module:alfresco/renderers/Progress#progressErrorTopic} events.
       *
       * @instance
       * @type {string}
       * @default
       */
      requestProgressTopic: null,

      /**
       * The topic to publish on to provide progress updates.
       *
       * @instance
       * @type {string}
       * @default
       */
      progressUpdateTopic: "ALF_PROGRESS_UPDATED",

      /**
       * The topic to publish on to indicate the task has completed.
       *
       * @instance
       * @type {string}
       * @default
       */
      progressCompleteTopic: "ALF_PROGRESS_COMPLETED",

      /**
       * The topic to publish on to indicate that the task has been cancelled
       *
       * @instance
       * @type {string}
       * @default
       */
      progressCancelledTopic: "ALF_PROGRESS_CANCELLED",

      /**
       * The topic to publish on to indicate that there has been an error processing the task
       *
       * @instance
       * @type {string}
       * @default
       */
      progressErrorTopic: "ALF_PROGRESS_ERROR",

      /**
       * Called when progress returns & updates progress dialog.
       *
       * @instance
       */
      onRequestProgress: function alfresco_renderers_Progress__onRequestProgress() {
         this.alfLog("log", "Requesting progress");

         if (!this.requestProgressTopic) 
         {
            this.alfLog("error", "I don't know where to request progress from: requestProgressTopic not set!" + this);
         }
         else
         {
            var subscriptionListeners = [
               this.alfSubscribe(this.progressUpdateTopic, lang.hitch(this, this.onProgressUpdate)),
               this.alfSubscribe(this.progressCompleteTopic, lang.hitch(this, this.onProgressComplete)),
               this.alfSubscribe(this.progressCancelledTopic, lang.hitch(this, this.onProgressCancelled)),
               this.alfSubscribe(this.progressErrorTopic, lang.hitch(this, this.onProgressError))
            ];
            var payload = {
               progressUpdateTopic: this.pubSubScope + this.progressUpdateTopic,
               progressCompleteTopic: this.pubSubScope + this.progressCompleteTopic,
               progressCancelledTopic: this.pubSubScope + this.progressCancelledTopic,
               progressErrorTopic: this.pubSubScope + this.progressErrorTopic,
               subscriptionListeners: subscriptionListeners,
               nodes: this.nodes
            };
            this.alfPublish(this.requestProgressTopic, payload, true);
         }
      },

      /**
       * Called when progress returns & updates progress dialog.
       *
       * @instance
       * @param {object} payload
       */
      onProgressUpdate: function alfresco_renderers_Progress__onProgressUpdate(payload) {
         this.alfLog("log", "Progress Dialog Update received: " + payload);
         this.alfPublish(this.renderProgressUITopic, payload);
      },

      /**
       * Called when progress is finished.
       *
       * @instance
       * @param {object} payload
       */
      onProgressComplete: function alfresco_renderers_Progress__onProgressComplete(payload) {
         if(!this.isCancelled) {
            this.alfLog("log", "Progress Dialog Complete: " + payload);

            // Update the UI:
            this.displayUIMessage(this.message(this.completedMessage));
            this.updateProgressBar(0);

            // Trigger the progressFinishedTopic
            if (this.progressFinishedTopic) {
               this.alfPublish(this.progressFinishedTopic, payload);
            }

            this.alfPublish("ALF_CLOSE_DIALOG", payload, true);
         }
      },

      /**
       * Called when action has been cancelled.
       *
       * @instance
       * @param {object} payload
       */
      onProgressCancelled: function alfresco_renderers_Progress__onProgressCancelled(payload) {
         this.isCancelled = true;
         this.alfLog("log", "Progress Dialog Cancelled: " + payload);
         this.alfPublish("ALF_CLOSE_DIALOG", payload, true);
      },

      /**
       * Called when there has been a fatal error. Progress has stopped and will not resume.
       * Error status contained in payload.errorMessage.
       *
       * @instance
       * @param {object} payload
       */
      onProgressError: function alfresco_renderers_Progress__onProgressError(payload) {
         this.alfLog("log", "Progress Dialog Error: " + payload);
         this.displayUIMessage(this.message(this.errorMessage));
      },

      /**
       * The dot-notation address of the property in the progress publication that contains the
       * amount "done". This is expected to be an integer.
       *
       * @instance
       * @type {string}
       * @default
       */
      doneProperty: "response.done",

      /**
       * The dot-notation address of the property in the progress publication that contains the
       * total amount "to do". This is expected to be an integer. The percentage complete will be
       * measured by the value of the [doneProperty]{@link module:alfresco/renderers/Progress} divided 
       * by the value represented by this property.
       *
       * @instance
       * @type {string}
       * @default
       */
      totalProperty: "response.total",

      /**
       * The dot-notation address of the property in the progress publication that contains the
       * number of items completed towards the target.
       *
       * @instance
       * @type {string}
       * @default
       */
      itemsAddedProperty: "response.filesAdded",

      /**
       * The dot-notation address of the property in the progress publication that contains the
       * total number of items to process.
       *
       * @instance
       * @type {string}
       * @default
       */
      totalItemsProperty: "response.totalFiles",

      /**
       * Called to update the UI with the data.
       * 
       * @instance
       * @param {object} payload
       */
      onRenderUI: function alfresco_renderers_Progress_onRenderUI(payload) {
         // Get the raw data from the payload...
         var done = lang.getObject(this.doneProperty, false, payload);
         var total = lang.getObject(this.totalProperty, false, payload);
         var filesAdded = lang.getObject(this.itemsAddedProperty, false, payload);
         var totalFiles = lang.getObject(this.totalItemsProperty, false, payload);

         // Get integer values from the data...
         done = parseInt(done, 10);
         total = parseInt(total, 10);
         filesAdded = parseInt(filesAdded, 10);
         totalFiles = parseInt(totalFiles, 10);

         // 0 is a legal numeric value (e.g. initial "start" progress)
         // can't use simple truthy vs non-truthy check
         if (isNaN(total) || total < 0 || isNaN(done) || done < 0 ||
                 isNaN(filesAdded) || filesAdded < 0 || isNaN(totalFiles) || totalFiles < 0)
         {
            this.alfLog("error", "Missing required data for progress: done=" + done + 
               ", total=" + total + 
               ", itemsAdded: " + filesAdded +
               ", totalItems: " + totalFiles);
         }
         else
         {
            var progressPercentage = (total > 0) ? Math.round(done / total * 100) : 0,
               percentageRemaining = 100 - progressPercentage;

            this.alfLog("log", "progress: " + progressPercentage);

            // Write to Dom:
            var messageVars = {
               0: filesAdded,
               1: totalFiles
            };
            this.displayUIMessage(this.message(this.statusMessage, messageVars));

            // Set style on progress bar:
            this.updateProgressBar(percentageRemaining);
         }
      },

      /**
       * Display the specified message in the dialog
       *
       * @instance
       * @param {string} message
       */
      displayUIMessage: function alfresco_renderers_Progress__displayUIMessage(message) {
         this.alfLog("log", "Progress message: " + message);
         if (this.labelNode) {
            this.labelNode.innerHTML = message;
         }
      },

      /**
       *
       * @instance
       * @param {number} percentageRemaining
       */
      updateProgressBar: function alfresco_renderers_Progress__updateProgressBar(percentageRemaining) {
         if (this.progressNode)
         {
            domStyle.set(this.progressNode, "left", "-" + percentageRemaining + "%");
         }
      },

      /**
       *
       * @instance
       * @param {object} payload
       */
      cleanUp: function alfresco_renderers_Progress__cleanUp(payload) {
         this.cleanProgressListeners(payload);
      },

      /**
       *
       * @instance
       * @param {object} payload
       */
      cleanProgressListeners: function alfresco_renderers_Progress__cleanProgressListeners(payload) {
         if (!payload.subscriptionListeners)
         {
            this.alfLog("error", "No subscription listeners to unsubscribe from");
            return;
         }
         this.alfUnsubscribe(payload.subscriptionListeners);
      }
   });
});