Source: renderers/EditableComment.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/>.
 */

/**
 * This renderer should be used to show an individual comment that can be switched into an edit mode that
 * shows a [TinyMCE editor]{@link module:alfresco/forms/controls/TinyMCE} for updating the comment. Note that
 * the widget does not take responsibility for which users have the ability to toggle between read and edit 
 * modes. It does not provide the controls to switch modes so the other controls for toggling mode should be
 * rendered based on the current user permissions. Examples of this approach can be found in the 
 * [CommentsList renderer]{@link module:alfresco/renderers/CommentsList}.
 *
 * @module alfresco/renderers/EditableComment
 * @extends module:aikau/core/BaseWidget
 * @mixes module:alfresco/core/CoreWidgetProcessing
 * @mixes module:alfresco/renderers/_PublishPayloadMixin
 * @author Dave Draper
 */
define(["dojo/_base/declare",
        "aikau/core/BaseWidget",
        "alfresco/core/CoreWidgetProcessing",
        "alfresco/renderers/_PublishPayloadMixin",
        "dojo/_base/array",
        "dojo/_base/lang",
        "dojo/dom-construct",
        "dojo/dom-class"], 
        function(declare, BaseWidget, CoreWidgetProcessing, _PublishPayloadMixin, array, lang, domConstruct, domClass) {

   return declare([BaseWidget, CoreWidgetProcessing, _PublishPayloadMixin], {
      
      /**
       * An array of the CSS files to use with this widget.
       * 
       * @instance
       * @type {object[]}
       * @default [{cssFile:"./css/EditableComment.css"}]
       */
      cssRequirements: [{cssFile:"./css/EditableComment.css"}],
      
      /**
       * An array of the i18n files to use with this widget.
       *
       * @instance
       * @type {object[]}
       * @default [{i18nFile: "./i18n/EditableComment.properties"}]
       */
      i18nRequirements: [{i18nFile: "./i18n/EditableComment.properties"}],
      
      /**
       * This will be instantiated as a map of the controls used by the widget. It has been added in order
       * to be passed as a reference for the "assignToScope" attribute of the 
       * [TinyMCE form control]{@link module:alfresco/forms/controls/TinyMCE} that is referenced
       * in the [widgetsForForm]{@link module:alfresco/renderers/EditableComment#widgetsForForm} model. This
       * attribute should not be configured or set.
       * 
       * @instance
       * @type {object}
       * @default
       * @since 1.0.68
       */
      controls: null,

      /**
       * @instance
       * @type {string}
       * @default
       */
      okButtonLabel: "comment.save",

      /**
       * The parameter to post that contains the content.
       *
       * @instance
       * @type {string}
       * @default
       */
      postParam: "content",

      /**
       * This should be set to the name of the property to render (e.g. "cm:name"). The property is expected
       * to be in the properties map for the item being rendered. 
       * 
       * @instance
       * @type {string}
       * @default
       */
      propertyToRender: null,
      
      /**
       * The topic to publish whenever the internal form is posted. By default this is expected to 
       * work with the [CrudService]{@link module:alfresco/services/CrudService} and uses its update
       * topic but this can be easily overridden.
       * 
       * @instance
       * @type {string}
       * @default
       */
      publishTopic: "ALF_CRUD_UPDATE",

      /**
       * The payload to publish whenever a comment edit is saved.
       *
       * @instance
       * @type {object}
       */
      publishPayload: {
         url: "api/comment/node/{nodeRef}",
         pubSubScope: "{parentPubSubScope}"
      },

      /**
       * Defines the publish payload type. By default this is set to "PROCESS" as it is expected that we will
       * need to process currentItem data to set a nodeRef in the "url" attribute of the default publishPayload.
       *
       * @instance
       * @type {string}
       * @default
       */
      publishPayloadType: "PROCESS",

      /**
       * The default modifiers to use when processing the configured publishPayload.
       *
       * @instance
       * @type {array}
       * @default ["processCurrentItemTokens", "convertNodeRefToUrl","processInstanceTokens"]
       */
      publishPayloadModifiers: ["processCurrentItemTokens", "convertNodeRefToUrl","processInstanceTokens"],

      /**
       * Indicates whether or not publications should be made globally. Default is true.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      publishGlobal: true,

      /**
       * Indicates whether or not the widget is in read mode or not. Default is true.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      readMode: true,

      /**
       * The topic that will be subscribed to that will toggle between edit and read modes.
       *
       * @instance
       * @type {string}
       * @default
       */
      subscriptionTopic: "ALF_EDIT_COMMENT",

      /**
       * 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_EditableComment__createWidgetDom() {
         this.domNode = document.createElement("div");
         this.domNode.classList.add("alfresco-renderers-EditableComment");
         this.domNode.classList.add(this._mode);

         this.readNode = document.createElement("div");
         this.readNode.classList.add("read");

         this.editNode = document.createElement("div");
         this.editNode.classList.add("edit");

         this.formNode = document.createElement("div");

         this.editNode.appendChild(this.formNode);
         this.domNode.appendChild(this.readNode);
         this.domNode.appendChild(this.editNode);
      },

      /**
       * Set up the attributes to be used when rendering the template.
       * 
       * @instance
       */
      postMixInProperties: function alfresco_renderers_EditableComment__postMixInProperties() {
         // Initialise the mode...
         if (this.readMode === true)
         {
            this._mode = "read";
         }
         else
         {
            this._mode = "edit";
         }

         // Get the comment value...
         this.renderedValue = lang.getObject(this.propertyToRender, false, this.currentItem);
         if (this.renderedValue === null || typeof this.renderedValue === "undefined")
         {
            this.renderedValue = "";
         }
      },
      
      /**
       * 
       * @instance
       */
      postCreate: function alfresco_renderers_EditableComment__postCreate() {
         this._formCreated = false;

         // Clone the configured payload to avoid collisions with other instances...
         this.publishPayload = lang.clone(this.publishPayload);

         // Setup subscriptions as necessary... (TODO: Overridable function?)
         this.alfSubscribe(this.subscriptionTopic, lang.hitch(this, this.onEditRequest));
         this.alfSubscribe("ALF_CANCEL_EDIT_COMMENT", lang.hitch(this, this.onCancelEdit));
         this.alfSubscribe("ALF_EDIT_COMMENT_SAVE", lang.hitch(this, this.onEditSave));

         // Safely add the rendered value to the document
         var safeDocFrag = this._makeSafe(this.renderedValue);
         this.readNode.appendChild(safeDocFrag);

         this.controls = {};
      },

      /**
       * This function switches the widget into edit mode. It will create the required editor as necessary
       * the first time the function is called.
       *
       * @instance
       * @param {object} payload
       */
      onEditRequest: function alfresco_renderers_EditableComment__onEditRequest(/*jshint unused:false*/ payload) {
         if (this._formCreated === false)
         {
            var widgetsConfig = lang.clone(this.widgetsForForm);
            this.processObject(["processInstanceTokens","processCurrentItemTokens","convertNodeRefToUrl"], widgetsConfig);
            this.processWidgets(widgetsConfig, this.formNode);
            this._formCreated = true;
         }

         // Toggle the CSS classes to reveal the edit form...
         domClass.remove(this.domNode, "read");
         domClass.add(this.domNode, "edit");
         this._mode = "edit";
      },

      /**
       * Handles requests to cancel editing
       *
       * @instance
       * @param {object} payload The payload published on the cancellation topic
       */
      onCancelEdit: function alfresco_renderers_EditableComment__onCancelEdit(/*jshint unused:false*/ payload) {
         domClass.remove(this.domNode, "edit");
         domClass.add(this.domNode, "read");
         this._mode = "read";

         // On cancel, make sure to reset the value in the TinyMCE editor...
         this._form._editControl.setValue(this.renderedValue);
      },

      /**
       * Handles requests to save the updated comment.
       *
       * @instance
       * @param {object} payload The details of the comment update
       */
      onEditSave: function alfresco_renderers_EditableComment__onEditSave(payload) {
         this.onCancelEdit();
         var dataPayload = lang.clone(payload);

         // Build a new payload to publish based on the supplied configuration...
         var generatedPayload = this.getGeneratedPayload(true);

         // Mix the data into the configured payload and publish it!
         lang.mixin(generatedPayload, dataPayload);

         // See AKU-957 - make sure that the parent pubSubScope is used. There might be a future case
         // where we need to make this configurable, but this should work in all scopes - it was previously
         // working at global scope, but the parentPubScope makes sense in all circumstances.
         generatedPayload.responseScope = this.parentPubSubScope;
         this.alfPublish(this.publishTopic, generatedPayload, this.publishGlobal);
      },

      /**
       * Convert the supplied, potentially-unsafe HTML into a safe document fragment
       *
       * @instance
       * @param    {string} unsafeHtml The "unsafe" HTML
       * @returns  {object} The now-safe HTML as a document fragment
       */
      _makeSafe: function alfresco_renderers_EditableComment___makeSafe(unsafeHtml) {
         var safeDocFrag = document.createDocumentFragment(),
            containerDiv = safeDocFrag.appendChild(document.createElement("DIV"));
         containerDiv.innerHTML = unsafeHtml;
         while(containerDiv.hasChildNodes()) {
            safeDocFrag.appendChild(containerDiv.firstChild);
         }
         safeDocFrag.removeChild(containerDiv);
         // TODO Make this safe!
         return safeDocFrag;
      },

      /**
       *
       * @instance
       */
      widgetsForForm: [
         {
            name: "alfresco/forms/Form",
            assignTo: "_form",
            config: {
               okButtonPublishTopic: "ALF_EDIT_COMMENT_SAVE",
               okButtonLabel: "{okButtonLabel}",
               okButtonPublishPayload: {
                  url: "api/comment/node/{nodeRef}"
               },
               cancelButtonPublishTopic: "ALF_CANCEL_EDIT_COMMENT",
               widgets: [
                  {
                     name: "alfresco/forms/controls/TinyMCE",
                     assignTo: "_editControl",
                     config: {
                        name: "{postParam}",
                        value: "{renderedValue}",
                        validationConfig: [
                           {
                              validation: "textContentRequired"
                           }
                        ]
                     }
                  }
               ]
            }
         }
      ]
   });
});