Source: forms/controls/CodeMirrorEditor.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 is a form control that wraps a new instance of a 
 * <a href="http://codemirror.net/">Code Mirror editor</a> (version 5.20.2)
 * which allows user input to be syntax highlighted according to the configured
 * [editMode]{@link module:alfresco/forms/controls/CodeMirrorEditor#editMode}.
 * The default editing mode is for XML but other modes are available (see
 * the Code Mirror documentation for details). In addition to supporting all the
 * standard configuration attributes for all 
 * [form controls]{@link module:alfresco/forms/controls/BaseFormControl} it is 
 * also possible to configure the 
 * [height][editMode]{@link module:alfresco/forms/controls/CodeMirrorEditor#height}
 * and [width][editMode]{@link module:alfresco/forms/controls/CodeMirrorEditor#width}
 * of the editor.
 *
 * @example <caption>Sample configuration changing the default mode:</caption>
 * {
 *   name: "alfresco/forms/controls/CodeMirrorEditor",
 *   config: {
 *     label: "Enter script",
 *     description: "Add your JavaScript code here",
 *     name: "js",
 *     editMode: "javascript"
 *   }
 * }
 * 
 * @module alfresco/forms/controls/CodeMirrorEditor
 * @extends module:alfresco/forms/controls/BaseFormControl
 * @author Dave Draper
 */
define(["alfresco/forms/controls/BaseFormControl",
        "dojo/_base/declare",
        "alfresco/core/ResizeMixin",
        "dojo/Deferred",
        "dojo/dom-construct",
        "dojo/dom-class",
        "dojo/_base/lang",
        "dojo/_base/array",
        "cm/lib/codemirror",
        "cm/mode/htmlmixed/htmlmixed"], 
        function(BaseFormControl, declare, ResizeMixin, Deferred, domConstruct, domClass, lang, array, cm) {
   
   return declare([BaseFormControl, ResizeMixin], {
      
      /**
       * An array of the CSS files to use with this widget.
       * 
       * @instance
       * @type {Array}
       */
      cssRequirements: [{cssFile: "/js/lib/code-mirror/lib/codemirror.css"},
                        {cssFile: "./css/CodeMirrorEditor.css"}],

      /**
       * This is the editor mode for the embedded CodeMirror editor. By default it is "xml". This
       * will control the syntax highlighting.
       * 
       * @instance
       * @type {string}
       * @default
       */
      editMode: "xml",

      /**
       * The height of the editor  (in pixels).
       * 
       * @instance
       * @type {number}
       * @default
       */
      height: 300,

      /**
       * Indicates that the call to [createFormControl]{@link module:alfresco/forms/controls/CodeMirrorEditor#createFormControl}
       * is going to return a "promise" rather than an actual widget because it needs to require the additional AMD resources
       * first.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      isPromisedWidget: true,

      /**
       * The width of the editor (in pixels).
       * 
       * @instance
       * @type {number}
       * @default
       */
      width: 600,

      /**
       * Instantiates a new JSON editor, places it in the template DOM and setups the change events.
       * 
       * The ACE module sneakily overwrites the "require" variable which will prevent subsequent
       * calls to the Dojo require from working, therefore it's necessary to keep a reference to
       * the original function and then undo the damage once ACE has been loaded...
       * Although we could have included these in the modules "define" statement and have them
       * preloaded into the Dojo cache would break the page as soon as the ACE module is processed :(
       * 
       * @instance
       */
      createFormControl: function alfresco_forms_controls_CodeMirrorEditor__createFormControl(config) {
         /*jshint unused:false*/
         this.alfSetupResizeSubscriptions(this.onResizeEvent, this);

         // Update the DOM node to include a specific class that we can anchor styles off...
         domClass.add(this.domNode, "alfresco-forms-controls-CodeMirrorEditor");

         // Create a new element to attach the editor to...
         this.editorElement = domConstruct.create("textarea", { 
            style: "height:" + this.height + "px;width:" + this.width + "px;"
         }, this._controlNode);
         
         this.wrappedWidget = new Deferred();
         require(["cm/mode/" + this.editMode + "/" + this.editMode], lang.hitch(this, this.setupEditor));
            
         // TODO: Disable the "Tab" key binding for accessibility reasons...
         // this.wrappedWidget.commands.bindKey("Tab", null);
         return this.wrappedWidget;
      },

      /**
       * @instance
       */
      getWidgetConfig: function alfresco_forms_controls_CodeMirrorEditor__getWidgetConfig() {
         // Return the configuration for the widget
         return {
            id : this.generateUuid(),
            name: this.name
         };
      },
      
      /**
       * This is the callback function for resize events relating to the DOM element of this widget. It's
       * primary use is to call the CodeMirror refresh function and has been added to handle the scenario
       * where an editor is included in an [AlfDialog]{@link module:alfresco/dialogs/AlfDialog}. The AlfDialog
       * publishes a resize event when it is first opened and this can be used to ensure that the editor 
       * is rendered correctly (because otherwise it doesn't!)
       * 
       * @instance
       */
      onResizeEvent: function alfresco_forms_controls_CodeMirrorEditor__onResizeEvent() {
         if (this.wrappedWidget && typeof this.wrappedWidget.refresh === "function")
         {
            this.wrappedWidget.refresh();
         }
      },

      /**
       * Creates and configures the CodeMirror editor.
       *
       * @instance
       */
      setupEditor: function alfresco_forms_controls_CodeMirrorEditor__setupEditor() {
         var editor = cm.fromTextArea(this.editorElement, {
            lineNumbers: true,
            mode: this.editMode
         });
         editor.setSize(this.width, this.height);
         
         // Set the initial content...
         var initialValue = (this.value !== null && typeof this.value !== "undefined" && this.value) || "";
         this.lastValue = initialValue;
         editor.setValue(initialValue);
         
         // Handle changes to the content...
         editor.on("change", lang.hitch(this, this.onEditorChange));

         this.wrappedWidget.resolve(editor);

         // TODO: Possibly need to call refresh when the widget is made visible
         //       Need to check what happens when added directly to page but there are issues when used in a dialog
      },

      /**
       * Overrides the [inherited function]{@link module:alfresco/forms/controls/BaseFormControl#placeWidget}
       * primarily to prevent an unnecessary warning being logged. But additionally this calls the refresh function
       * on the Code Mirror editor to ensure it is rendered correctly.
       *
       * @instance
       */
      placeWidget: function alfresco_forms_controls_CodeMirrorEditor__placeWrappedWidget() {
         this.wrappedWidget.refresh();
      },
      
      /**
       * A callback handler that is hitched to change events in the instantiation of the JSON editor.
       * 
       * @instance
       */
      onEditorChange: function alfresco_forms_controls_CodeMirrorEditor__onEditorChange(editor, changeObject) {
         /*jshint unused:false*/
         var value = editor.getValue();
         this.onValueChangeEvent(this.name, this.lastValue, value);
         this.lastValue = value;
      },

      /**
       * Overrides to prevent any action from occurring.
       * 
       * @instance
       */
      setupChangeEvents: function alfresco_forms_controls_TinyMCE__setupChangeEvents() {
         // No action.
      },
      
      /**
       * Extends the [default function]{@link module:alfresco/forms/controls/BaseFormControl#processValidationRules} to 
       * see if the editor any errors. Currently the error checking is not actually implemented.
       * 
       * @instance
       * @returns {boolean} True if the editor content is valid and false otherwise.
       */
      processValidationRules: function alfresco_forms_controls_CodeMirrorEditor__processValidationRules() {
         var valid = this.inherited(arguments);
         // TODO: It would be nice if we could hook into some validation commands from CodeMirror
         return valid;
      },
      
      /**
       * Overrides the [inherited function]{@link module:alfresco/forms/controls/BaseFormControl#getValue} to
       * call the "getValue" function on the editor. This returns the JSON text rather than a JavaScript object.
       * 
       * @instance
       * @returns {string} The JSON value entered into the editor
       */
      getValue: function alfresco_forms_controls_CodeMirrorEditor__getValue() {
         if (this.wrappedWidget && this.wrappedWidget.getValue)
         {
            return this.wrappedWidget.getValue();
         }
         else
         {
            return (this.value !== null && typeof this.value !== "undefined" && this.value) || "";
         }
      }
   });
});