Source: forms/controls/BaseFormControl.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 should be extended by all form controls in order to provide a consistent look and feel. It wraps
 * a standard widget (which can be provided by JavaScript toolkits other than Dojo) or multiple widgets
 * and creates the name, description and units labels are appropriate. It also provides the capability for
 * form controls to communicate with each other and dynamically update their appearance and behaviour
 * through configured rules (e.g. to allow progressive disclosure, etc through configuration).</p>
 *
 * @example <caption>Example configuration for a required TextBox control:</caption>
 * {
 *     name: "alfresco/forms/controls/TextBox",
 *     config: {
 *        fieldId: "DISTANCE",
 *        label: "Distance",
 *        description: "Enter the distance travelled",
 *        unitsLabel: "miles",
 *        name: "distance",
 *        value: "0",
 *        requirementConfig: {
 *           initialValue: true
 *        }
 *     }
 * }
 *
 * @example <caption>Example configuration for Select control:</caption>
 * {
 *     name: "alfresco/forms/controls/Select",
 *     config: {
 *        fieldId: "COLOUR",
 *        label: "Colour",
 *        description: "Select a colour from the list",
 *        name: "colour",
 *        value: "RED",
 *        optionsConfig: {
 *           fixed: [
 *              { label: "Red", value: "RED" },
 *              { label: "Green", value: "GREEN" },
 *              { label: "Blue", value: "BLUE" }
 *           ]
 *        }
 *     }
 * }
 *
 * @example <caption>Example configuration for a hidden TextBox that can be dynamically revealed:</caption>
 * {
 *     name: "alfresco/forms/controls/TextBox",
 *     config: {
 *        fieldId: "AGE",
 *        label: "How old are you?",
 *        description: "Enter the distance travelled",
 *        placeHolder: "Age...",
 *        unitsLabel: "years",
 *        name: "age",
 *        visibilityConfig: {
 *           initialValue: false,
 *           rules: [
 *              {
 *                 targetId": "SHOW_AGE",
 *                 is: ["true"]
 *              }
 *           ]
 *        }
 *     }
 * }
 *
 * @module alfresco/forms/controls/BaseFormControl
 * @extends external:dijit/_WidgetBase
 * @mixes external:dojo/_TemplatedMixin
 * @mixes external:dojo/_FocusMixin
 * @mixes module:alfresco/forms/controls/FormControlValidationMixin
 * @mixes module:alfresco/core/Core
 * @mixes module:alfresco/forms/controls/utilities/RulesEngineMixin
 * @mixes module:alfresco/lists/KeyboardNavigationSuppressionMixin
 * @extendSafe
 * @author Dave Draper
 * @author Richard Smith
 */
define(["dojo/_base/declare",
        "dijit/_WidgetBase",
        "dijit/_TemplatedMixin",
        "dijit/_FocusMixin",
        "alfresco/core/Core",
        "alfresco/forms/controls/FormControlValidationMixin",
        "alfresco/forms/controls/utilities/RulesEngineMixin",
        "alfresco/lists/KeyboardNavigationSuppressionMixin",
        "dojo/text!./templates/BaseFormControl.html",
        "alfresco/core/topics",
        "alfresco/core/ObjectTypeUtils",
        "alfresco/core/ArrayUtils",
        "dojo/_base/lang",
        "dojo/_base/array",
        "dojo/dom-style",
        "dojo/dom-class",
        "dijit/Tooltip",
        "dojo/dom-attr",
        "dojo/query",
        "dojo/dom-construct",
        "dojo/Deferred",
        "dojo/on",
        "jquery"],
        function(declare, _Widget, _Templated, _FocusMixin, AlfCore, FormControlValidationMixin, RulesEngineMixin, 
                 KeyboardNavigationSuppressionMixin, template, topics, ObjectTypeUtils, arrayUtils, lang, array, domStyle, 
                 domClass, Tooltip, domAttr, query, domConstruct, Deferred, on, $) {

   return declare([_Widget, _Templated, _FocusMixin, AlfCore, FormControlValidationMixin, RulesEngineMixin, KeyboardNavigationSuppressionMixin], {

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

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

      /**
       * The HTML template to use for the widget.
       * @instance
       * @type {String}
       */
      templateString: template,

      /**
       * This will be set to the form control that the user will actually interact with (e.g. a text box, check box, etc).
       *
       * @instance
       * @type {object}
       * @default
       */
      wrappedWidget: null,

      /**
       * A scope for prefixing all publication and subscription topics. This is so that form controls can be used
       * and not interfere incorrectly with each other.
       *
       * @instance
       * @type {string}
       * @default
       */
      pubSubScope: "",

      /**
       * The widget to instantiate.
       *
       * @instance
       * @type {string}
       * @default
       */
      type: "",

      /**
       * This used to uniquely identify the form field. If not explicitly defined in the instantiation
       * configuration then a new UUID will be generated for it. It is important to have an id attribute
       * that is NOT used as the widgetId (to allow duplications - which the dijit/registry would otherwise
       * reject) and to have a value that can be used as a reference that will be unaffected by changes
       * (e.g. when configuring a form field dynamically with the application).
       *
       * @instance
       * @type {string}
       * @default
       */
      fieldId: "",

      /**
       * When set to true, and this is a multi-value control, then the initial value will - if nothing
       * is specifically set - be set to the first value available.
       *
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.65
       */
      firstValueIsDefault: true,

      /**
       * The label identifying the data to provide. The value supplied will be checked against the available
       * scoped NLS resources to attempt to translate message keys into localized values.
       *
       * @instance
       * @type {string}
       * @default
       */
      label: "",

      /**
       * A label for showing units measurements (e.g. "milliseconds", "MB", etc). The value supplied will be
       * checked against the available scoped NLS resources to attempt to translate message keys into localized values.
       *
       * @instance
       * @type {string}
       * @default
       */
      unitsLabel: "",

      /**
       * A description of the field. This will appear in a tooltip by default. The value supplied will be
       * checked against the available scoped NLS resources to attempt to translate message keys into localized values.
       *
       * @instance
       * @type {string}
       * @default
       */
      description: "",

      /**
       * Inline help for the field. If this is not empty it will cause a help icon to show which, when clicked, will
       * launch a dialog containing the additional help content.
       *
       * @instance
       * @type {string}
       * @default
       */
      inlineHelp: "",

      /**
       * The value to submit as the name for the data captured by this field when the form is submitted.
       *
       * @instance
       * @type {string}
       */
      name: "",

      /**
       * The list of static options
       *
       * @instance
       * @type {array}
       * @default
       */
      options: null,

      /**
       * Indicates whether or not values should be trimmed of whitespace (this only applies to 
       * values that are strings).
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.94
       */
      trimValue: false,

      /**
       * The value to submit as the value of the field when the form is submitted.
       *
       * @instance
       * @type {string}
       * @default
       */
      value: "",

      /**
       * By default if a field is hidden or disabled then it's value should not be posted. This allows multiple controls
       * representing the same data to be used together with visibility/disablement rules so that only one control's value]
       * is submitted. This variable has no direct effect on this widget but can be used by other widgets such as the
       * [form]{@link module:alfresco/forms/Form}. Intentionally hidden fields should override the default value so
       * that they are always submitted.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      postWhenHiddenOrDisabled: true,

      /**
       * By default all fields will have their value when a parent [form]{@link module:alfresco/forms/Form} has its value set
       * but in certain cases it may be desirable for fields to not have a data update when they are hidden or disabled. This
       * will be the case when multiple fields are being used to represent the same data but through progressive disclosure
       * only one field is displayed at a time. By overriding this variable a field can request not to have it's value updated
       * when it is hidden or disabled.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      noValueUpdateWhenHiddenOrDisabled: false,

      /**
       * Sometimes multiple form controls might be used to represent a single post parameter. For example a set of radio
       * buttons might present a set of options for a parameter where the last radio button progressively reveals a
       * text box for entering a custom value. In that case the radio buttons form control is superceded by the revealed
       * text box. By setting this attribute to an array containing the values that prevent the form control value being
       * included in the form post it is possible to define that behaviour
       *
       * @instance
       * @type {array}
       * @default
       */
      noPostWhenValueIs: null,

      /**
       * Whether - when a publishTopic is available in the supplied optionsConfig - to immediately
       * publish to that topic in order to retrieve any default pubSubOptions for this control. If
       * set to false, then it will not do that initial publish.
       *
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.33
       */
      getPubSubOptionsImmediately: true,

      /**
       * Indicates that this form control can have a value which is a subset of the available
       * options that can be chosen from. 
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.65
       */
      supportsMultiValue: false,

      /**
       * Indicates whether values should be set as separate properties for the files added and removed 
       * (from the initial state) rather than just a single value representing the files selected.
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.82
       */
      addedAndRemovedValues: false,

      /**
       * The suffix added to the form control [name]{@link module:alfresco/forms/controls/BaseFormControl#name}
       * when [addedAndRemovedValues]{@link module:alfresco/forms/controls/FilePicker#addedAndRemovedValues}
       * is configured to be true. The concatenated value is then used as the property for the files
       * that have been added to the form controls initial value.
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.82
       */
      addedNameSuffix: "_added",

      /**
       * The suffix added to the form control [name]{@link module:alfresco/forms/controls/BaseFormControl#name}
       * when [addedAndRemovedValues]{@link module:alfresco/forms/controls/FilePicker#addedAndRemovedValues}
       * is configured to be true. The concatenated value is then used as the property for the files
       * that have been removed from the form controls initial value.
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.82
       */
      removedNameSuffix: "_removed",

      /**
       * An optional token that can be provided for splitting the supplied value. This should be configured
       * when the value is provided as a string that needs to be converted into an array.
       * 
       * @instance
       * @type {string}
       * @default
       * @since 1.0.82
       */
      valueDelimiter: null,

      /**
       * This indicates whether or not the validation elements are hidden (for example the invalid
       * field indicator and any validation messages). This would typically be used for form dialogs
       * where the fields have no label.
       *
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.84
       */
      hideValidation: false,

      /**
       * This attribute has been added to retain backwards compatibility with regards to validation
       * of invisible fields. Fields that are invisible are not validated by default but it may be
       * necessary to validate hidden fields that are progressively disclosed. The use case for this
       * addition was the implementation of the Cloud Sync capabilities that required a form to be
       * disabled until a path was selected (but the path field was hidden until a network and
       * site were selected).
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.94
       */
      validateWhenHidden: false,

      /**
       * The default visibility status is always true (this can be overridden by extending controls).
       *
       * @instance
       * @type {boolean}
       * @default
       */
      _visible: true,

      /**
       * Indicates whether or not the form control has been added into the main browser document. This
       * should not be configured or used by any extending widgets.
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.49
       */
      ___addedToDocument: false,

      /**
       * Used to toggle visibility of the field.
       *
       * @instance
       * @param {boolean} status The boolean value to change the visibility state to.
       */
      alfVisible: function alfresco_forms_controls_BaseFormControl__alfVisible(status) {
         this.alfLog("log", "Change visibility status for '" + this.fieldId + "' to: " + status);
         this._visible = status;
         if (this.containerNode)
         {
            var display = status ? "" : "none";
            domStyle.set(this.containerNode, {
               display: display
            });
         }
      },

      /**
       * The default requirement status is always false (this can be overridden by extending controls).
       *
       * @instance
       * @type {boolean}
       * @default
       */
      _required: false,

      /**
       * Used to toggle the requirement state of the field.
       *
       * @instance
       * @param {boolean} status The boolean value to change the requirement state to
       */
      alfRequired: function alfresco_forms_controls_BaseFormControl__alfRequired(status) {
         this.alfLog("log", "Change requirement status for '" + this.fieldId + "' to: " + status, {});
         this._required = status;
         if (this._requirementIndicator)
         {
            if (this._required === true)
            {
               domClass.add(this._requirementIndicator, "required");
            }
            else
            {
               domClass.remove(this._requirementIndicator, "required");
            }

            // When requirement state is changed we need to re-validate the widget
            this.validate();
         }
      },

      /**
       * The default disabled status is always false  (this can be overridden by extending controls).
       *
       * @instance
       * @type {boolean}
       */
      _disabled: false,

      /**
       * Controls the disability status of the field.
       *
       * @instance
       * @param {boolean} status The boolean status to set the disablity state of the field to.
       */
      alfDisabled: function alfresco_forms_controls_BaseFormControl__alfDisabled(status) {
         this.alfLog("log", "Change disablement status for '" + this.fieldId + "' to: " + status);
         this._disabled = status;
         if (this.wrappedWidget && typeof this.wrappedWidget.set === "function")
         {
            this.wrappedWidget.set("disabled", status);
         }
         if (this.domNode)
         {
            if (status)
            {
               domClass.add(this.domNode, "alfresco-forms-controls-BaseFormControl--disabled");
            }
            else
            {
               domClass.remove(this.domNode, "alfresco-forms-controls-BaseFormControl--disabled");
            }
         }
         
         this.validate();
      },

      /**
       * <p><strong>PLEATE NOTE: </strong>This configuration will not be applicable to all form controls</p>
       * 
       * <p>The configuration for options that can be selected from. These can either be "fixed" or requested 
       * over the publication/subscription model. It is also possible to configure triggers that cause options
       * to be refreshed (this can be useful when changing the value of one field will result in different
       * options being available for another).</p>
       *
       * @example <caption>Basic fixed options</caption>
       * optionsConfig: {
       *   fixed: [
       *     { label: "Option One", value: "1" },
       *     { label: "Option Two", value: "2" },
       *     { label: "Option Three", value: "3" }
       *   ]
       * }
       *
       * @example <caption>Basic publication for options</caption>
       * optionsConfig: {
       *   publishTopic: "ALF_GET_FORM_CONTROL_OPTIONS",
       *   publishPayload: {
       *     url: url.context + "/proxy/alfresco/api/people",
       *     itemsAttribute: "people",
       *     labelAttribute: "userName",
       *     valueAttribute: "userName"
       *   },
       *   publishGlobal: true
       * }
       *
       * @example <caption>Update options when "FIELD1" or "FIELD2" change value</caption>
       * optionsConfig: {
       *   changesTo: [
       *     { targetId:"FIELD1" },
       *     { targetId:"FIELD2" }
       *   ],
       *   publishTopic: "GET_OPTIONS_TOPIC"
       * }
       *
       * @example <caption>Update options when "TOPIC1" is pulished globally or "TOPIC2" is published on the current scope</caption>
       * optionsConfig: {
       *   updateTopics: [
       *     { topic:"TOPIC1", global: true },
       *     { topic:"TOPIC2", global: false }
       *   ],
       *   publishTopic: "GET_OPTIONS_TOPIC"
       * }
       *
       * @example <caption>Example requesting options with a payload containing all the current form values</caption>
       * optionsConfig: {
       *    publishTopic: "ALF_GET_FORM_VALUE_DEPENDENT_OPTIONS",
       *    publishPayload: {
       *      publishTopic: "GET_OPTIONS_TOPIC"
       *    },
       *    publishGlobal: false // NOTE: It is important to set publishGlobal to false for this to work
       * }
       * 
       * @instance
       * @type {object}
       * @default
       */
      optionsConfig: null,

      /**
       * Defines the visibility behaviour of the widget. It is possible for the widget to dynamically be hidden
       * or displayed based on the value of one or more other widgets. See [processConfig]{@link module:alfresco/forms/controls/utilities/RulesEngineMixin#processConfig}
       * for the structure to use when configuring the rules</p>
       *
       * @instance
       * @type {object}
       * @default
       */
      visibilityConfig: null,

      /**
       * Defines the requirement behaviour of the widget. It is possible for the widget to dynamically be required
       * to have a value provided based on the value of one or more other widgets. See [processConfig]{@link module:alfresco/forms/controls/utilities/RulesEngineMixin#processConfig}
       * for the structure to use when configuring the rules</p>
       *
       * @instance
       * @type {object}
       * @default
       */
      requirementConfig: null,

      /**
       * Defines the disablement behaviour of the widget. It is possible for the widget to dynamically be disabled
       * or enabled based on the value of one or more other widgets. See [processConfig]{@link module:alfresco/forms/controls/utilities/RulesEngineMixin#processConfig}
       * for the structure to use when configuring the rules</p>
       *
       * @instance
       * @type {object}
       * @default
       */
      disablementConfig: null,

      /**
       * Defines an array of rules for auto-setting the value of the widget. Each element in the array is configured
       * in the same way as the [visibilityConfig]{@link module:alfresco/forms/controls/BaseFormControl#visibilityConfig},
       * [requirementConfig]{@link module:alfresco/forms/controls/BaseFormControl#requirementConfig} or
       * [disablementConfig]{@link module:alfresco/forms/controls/BaseFormControl#disablementConfig} in that it should
       * declare a "targetId" attribute (that maps to the "fieldId" of another widget in the same form) and
       * either a "is" or "isNot" array of values of that field to evaluate against. In addition it also needs to
       * set "rulePassValue" and "ruleFailValue" attributes that are the values that will be set on successful or
       * unsuccessful evaluation of the rule.
       *
       * @example <caption>Automatically sets the value of the form control when "SOURCE_FIELD" is 3</caption>
       * autoSetConfig: [
       *   {
       *     rulePassValue: "Updated",
       *     ruleFailValue: "",
       *     rules: [
       *        {
       *           targetId: "SOURCE_FIELD",
       *           is: ["3"]
       *        }
       *     ]
       *   }
       * ]
       *
       * @example <caption>Automatically set the value of the form control to that of "SOURCE_FIELD"</caption>
       * autoSetConfig: [
       *   {
       *     copyRule: {
       *       targetId: "SOURCE_FIELD"
       *     }
       *   }
       * ]
       *
       * @instance
       * @type {array}
       * @default
       */
      autoSetConfig: null,

      /**
       * @instance
       */
      constructor: function alfresco_forms_controls_BaseFormControl__constructor(args) {
         declare.safeMixin(this, args);

         if (!this.fieldId)
         {
            this.fieldId = this.generateUuid();
         }

         // We want to defer value assignment until the widget has been placed into the document, so
         // we set this flag to indicate that value assignment will be deferred and then create an array
         // to capture all the deferred values...
         this.deferValueAssigment = true;
         this.deferredValueAssignments = [];

         // Setup the rules for controlling visibility, requirement and disablement...
         // NOTE: The reason for the "alf_" prefix is that we need to make sure that the functions cannot corrupt attribute
         //       accessors functionality. This is particularly relevant to the "alfDisabled". Originally a function called
         //       "disabled" was created and this caused focus events to occur inadvertently.
         this.processConfig("alfVisible", this.visibilityConfig);
         this.processConfig("alfRequired", this.requirementConfig);
         this.processConfig("alfDisabled", this.disablementConfig);

         // Iterate over the autoSetConfig rules...
         array.forEach(this.autoSetConfig, lang.hitch(this, this.processAutoSetConfiguration));

         // Setup the options handling...
         this.processOptionsConfig(this.optionsConfig);

         if (this.validationConfig && typeof this.validationConfig.regex === "string")
         {
            this.validationConfig.regExObj = new RegExp(this.validationConfig.regex);
         }
      },

      /**
       * Called for each entry in the [autoSetConfig]{@link module:alfresco/forms/controls/BaseFormControl#autoSetConfig}
       * array. The [autoSetValue]{@link module:alfresco/forms/controls/BaseFormControl#autoSetValue}
       * function will be passed each time the rules are evaluated with the evaluation result and the
       * pass and fail evaluation values to set.
       *
       * @instance
       * @param {object} config The configuration for the current autoset value
       * @param {number} index The index of the configuration in the original array
       */
      processAutoSetConfiguration: function alfresco_forms_controls_BaseFormControl__processAutoSetConfiguration(config, index) {
         if (config.rulePassValue || config.ruleFailValue)
         {
            var instanceVar = "alfAutoSet___" + index;
            this[instanceVar] = lang.hitch(this, this.autoSetValue, config.rulePassValue, config.ruleFailValue);

            // Process the configuration...
            this.processConfig(instanceVar, config);
         }
         else if (config.copyRule && config.copyRule.targetId)
         {
            var topic = "_valueChangeOf_" + config.copyRule.targetId;
            this.alfSubscribe(topic, lang.hitch(this, function(payload) {
               this.setValue(payload.value);
            }));
         }
         else
         {
            this.alfLog("warn", "An autoset configuration element was provided without both 'rulePassValue' and 'ruleFailValue' attribute", config, this);
         }
      },

      /**
       * This function is called whenever an autoset configuration rules array is evaluated. It
       * sets the widget value depending upon whether or not the rule evaluated successfully or not.
       *
       * @instance
       * @param {object} passValue The value to set if hasPassedRule is true
       * @param {object} failValue The value to set if hasPassedRule is false
       * @param {boolean} hasPassedRule Indicated whether or not evaluation of the rules were successful
       */
      autoSetValue: function alfresco_forms_controls_BaseFormControl__autoSetValue(passValue, failValue, hasPassedRule) {
         // passValue/failValue may not be set to any value in autoSetConfig to achieve positive-/negative-only rule behaviour
         if (hasPassedRule === true)
         {
            if (typeof passValue !== "undefined")
            {
                this.setValue(passValue);
            }
         }
         else
         {
            if (typeof failValue !== "undefined")
            {
                this.setValue(failValue);
            }
         }
      },

      /**
       * <p>Processes the configuration for defining options and their update behaviour. This configuration is different to
       * the visibility/requirement/disablement rules so needs to be handled separately. The configuration can be defined
       * in the following structure:<p>
       * <p><pre>"optionsConfig": {
       *    "changesTo": [{
       *       "targetId": "someId",
       *    }],
       *    "updateTopics": [
       *       {
       *          "topic": "someTopic",
       *          "global": true
       *       }
       *    ],
       *    "itemsAttribute": "options",
       *    "publishTopic": "ALF_GET_FORM_CONTROL_OPTIONS",
       *    "publishPayload": {
       *       "url": AlfConstants.PROXY_URI + "api/groups",
       *       "itemsAttribute": "data",
       *       "labelAttribute": "displayName",
       *       "valueAttribute": "fullName"
       *    },
       *    "callback": "functionName",
       *    "fixed": [
       *       { "label": "Option1", "value": "Value1"},
       *       { "label": "Option2", "value": "Value2"}
       *    ]
       *  }</pre></p>
       *  <p>The precendence for setting options is as follows:<ul>
       *  <li>requestTopic</li>
       *  <li>callback</li>
       *  <li>fixed</li>
       *  </ul>e.g. if a "requestTopic" is provided then any "fixed" options will be ignored.</p>
       *
       * @instance
       * @param {object} config
       */
      processOptionsConfig: function alfresco_forms_controls_BaseFormControl__processOptionsConfig(config) {
         if (config)
         {
            // Create update subscriptions based on the requested changes to other fields in the form...
            // We're going to do some helpful checking of the configuration to aid development rather
            // than just ignoring invalid configuration...
            var changesTo = lang.getObject("changesTo", false, config);
            if (changesTo)
            {
               if (ObjectTypeUtils.isArray(changesTo))
               {
                  // We're using an additional topic prefix here which is the pattern used for publications
                  // within the form...
                  // TODO: Make the string an instance variable?
                  array.forEach(config.changesTo, lang.hitch(this, this.createOptionsChangesTo, config));
               }
               else
               {
                  this.alfLog("warn", "The supplied 'changesTo' attribute for '" + this.fieldId + "' was not an Array");
               }
            }
            // Create update subcriptions based on topics published that are external to the form. This allows
            // the options to respond to "global" events rather than just changes within the form itself.
            var updateTopics = lang.getObject("updateTopics", false, config);
            if (updateTopics)
            {
               if (ObjectTypeUtils.isArray(updateTopics))
               {
                  array.forEach(config.updateTopics, lang.hitch(this, this.createOptionsSubscriptions, config));
               }
               else
               {
                  this.alfLog("warn", "The supplied 'updateTopics' attribute for '" + this.fieldId + "' was not an Array");
               }
            }

            this.getInitialOptions(config);
         }
      },

      /**
       * Generate the initial set of options in the following precedence: 
       * 
       * <ol><li>PubSub Config</li>
       * <li>Callback function</li>
       * <li>Fixed options</li></ol>
       *  
       * @instance
       * @param {object} config The options configuration
       * @since 1.0.96
       */
      getInitialOptions: function alfresco_forms_controls_BaseFormControl__getInitialOptions(config) {
         var pubSub = lang.getObject("publishTopic", false, config),
             callback = lang.getObject("callback", false, config),
             fixed = lang.getObject("fixed", false, config);
         if (pubSub && this.getPubSubOptionsImmediately)
         {
            this.getPubSubOptions(config);
         }
         else if (callback)
         {
            this.processCallbackOptions(callback, config);
         }
         else if (fixed)
         {
            this.processFixedOptions(fixed);
         }
      },

      /**
       * Handle configuration requests that options be generated through a function call...
       * Note that we're not explicitly handling scope here, it's expected that a hitch call will be
       * used if a scope is required. It is also possible to set the callback as a string which will
       * be matched against a function of the current scope.
       *
       * @param {Function} callback The function to call to get the options for the control
       * @param {object} config The full options configuration
       */
      processCallbackOptions: function alfresco_forms_controls_BaseFormControl__processCallbackOptions(callback, config) {
         if (typeof callback === "function")
         {
            this.pendingOptions = callback(config);
         }
         else if (ObjectTypeUtils.isString(callback) && typeof this[callback] === "function")
         {
            this.pendingOptions = this[callback](config);
         }
         else
         {
            this.alfLog("warn", "The supplied 'callback' attribute for '" + this.fieldId + "' was not a Function");
         }
      },

      /**
       * Processes fixed options for the form control.
       *
       * @param  {array} fixed The fixe options to apply to the form control.
       */
      processFixedOptions: function alfresco_forms_controls_BaseFormControl__processFixedOptions(fixed) {
         // Handle configuration that specifies a fixed configuration...
         if (ObjectTypeUtils.isArray(fixed))
         {
            this.pendingOptions = fixed;
         }
         else
         {
            this.alfLog("log", "The supplied fixed options attribute for '" + this.fieldId + "' was not an Array");
         }
      },

      /**
       * This is a simple function that is used to convert label message keys into the appropriate
       * translated message.
       *
       * @instance
       * @param {object} option The option configuration
       * @param {number} index The index of the option
       */
      processOptionLabel: function alfresco_forms_controls_BaseFormControl__processOptionLabel(option, /*jshint unused:false*/ index) {
         // Get the option label and value attributes...
         // These are the values to look up in each item of the options data array...
         // They default to "label" and "value" if not specified
         var labelAttribute = this.optionsConfig.labelAttribute ? this.optionsConfig.labelAttribute : "label";
         var valueAttribute = this.optionsConfig.valueAttribute ? this.optionsConfig.valueAttribute : "value";
         if (option[labelAttribute])
         {
            option.label = this.message(option[labelAttribute]);
         }
         else if (option[valueAttribute])
         {
            option.label = option[valueAttribute];
         }
         else
         {
            this.alfLog("warn", "An option was provided with neither label nor value", option, this);
         }

         if (option.description)
         {
            option.description = this.message(option.description);
         }

         // See AKU-844 - also update the value attribute whilst we're here, not the correct location despite
         // the function name !
         if (option[valueAttribute])
         {
            option.value = option[valueAttribute];
         }
      },

      /**
       * Creates the subscription to the supplied topic information. All topics are handled by the
       * updateOptions function.
       *
       * @instance
       * @param {object} optionsConfig The overriding options config object
       * @param {object} subscription The details of the subscription to create
       * @param {number} index The index of the topic
       */
      createOptionsChangesTo: function alfresco_forms_controls_BaseFormControl__createOptionsChangesTo(optionsConfig, subscription, /*jshint unused:false*/ index) {
         if (subscription.targetId)
         {
            // Create the subscription...
            var topic = "_valueChangeOf_" + subscription.targetId,
                global = subscription.global || false;
            this.alfSubscribe(topic, lang.hitch(this, this.updateOptions, optionsConfig), global);
         }
         else
         {
            this.alfLog("warn", "No 'targetId' defined in subscription config", subscription, optionsConfig, this);
         }
      },

      /**
       * Creates the subscription to the supplied topic information. All topics are handled by the
       * updateOptions function.
       *
       * @instance
       * @param {object} optionsConfig The overriding options config object
       * @param {object} subscription The details of the subscription to create
       * @param {number} index The index of the topic
       */
      createOptionsSubscriptions: function alfresco_forms_controls_BaseFormControl__createOptionsSubscriptions(optionsConfig, subscription, /*jshint unused:false*/ index) {
         if (subscription.topic)
         {
            // Create the subscription...
            var global = subscription.global || false;
            this.alfSubscribe(subscription.topic, lang.hitch(this, this.updateOptions, optionsConfig), global);
         }
         else
         {
            this.alfLog("warn", "No 'topic' defined in subscription config", subscription, optionsConfig, this);
         }
      },

      /**
       * This is a built-in options callback that attempts to retrieve options from a publication event
       * where it is assumed that the publication payload. An example of using this function can be found in
       * the [getFormWidgets]{@link module:alfresco/forms/creation/FormRulesConfigCreatorElement#getFormWidgets}
       * function of the [FormRulesConfigCreatorElement module]{@link module:alfresco/forms/creation/FormRulesConfigCreatorElement}
       *
       * @instance
       * @param {object} optionsConfig The configuration for options handling defined for the current control
       * @param {object} payload The publication payload
       */
      getOptionsFromPublication: function alfresco_forms_controls_BaseFormControl__getOptionsFromPublication(optionsConfig, payload) {
         var options = lang.getObject("options", false, payload);
         if (options && ObjectTypeUtils.isArray(options))
         {
            return options;
         }
         else
         {
            return [];
         }
      },

      /**
       * This function is called when an rule triggering options reload occurs (e.g. the value of another relevant field in the
       * form has been changed).
       *
       * @instance
       * @param {object} optionsConfig The overriding options config object
       * @param {object} payload The publication payload
       */
      updateOptions: function alfresco_forms_controls_BaseFormControl__onUpdateOptions(optionsConfig, payload) {
         this.alfLog("log", "OPTIONS CONFIG: Field '" + this.fieldId + "' is handling value change of field'" + payload.name);
         if (optionsConfig.publishTopic)
         {
            this.getPubSubOptions(optionsConfig);
         }
         else if (optionsConfig.callback)
         {
            // Make the callback for setting the options. The callback can either be a function or
            // a String. If it is a String then it is assumed to be the name of a function in the
            // widget so will be checked.
            if (typeof optionsConfig.callback === "function")
            {
               this.setOptions(optionsConfig.callback(optionsConfig, payload, this));
            }
            else if (typeof optionsConfig.callback === "string" && typeof this[optionsConfig.callback] === "function")
            {
               this.setOptions(this[optionsConfig.callback](optionsConfig, payload, this));
            }
            else
            {
               this.alfLog("log", "The supplied 'callback' attribute for '" + this.fieldId + "' was neither a String nor a function");
            }
         }
      },

      /**
       * This gets set to the temporary subscription handle that is created whenever options are dynamically requested
       * by publishing on a configured topic. This information needs to be maintained as a widget instance variable
       * in order for the temporary subscription to be removed and prevent potential memory leaks.
       *
       * @instance
       * @type {object}
       * @default
       */
      _pubSubOptionsHandle: null,

      /**
       * Delegates focus calls to the wrapped widget.
       *
       * @instance
       */
      focus: function alfresco_forms_controls_BaseFormControl__focus() {
         if (this.wrappedWidget && typeof this.wrappedWidget.focus === "function")
         {
            this.wrappedWidget.focus();
         }
      },

      /**
       * Sets up a new subscription for options changes and then publishes a request to get those options.
       *
       * @instance
       * @param {string} config The configuration to use for retrieving options via Pub/Sub
       */
      getPubSubOptions: function alfresco_forms_controls_BaseFormControl__getPubSubOptions(config) {
         // Publish a topic to get the currently available options based on the current value...
         if (config.publishTopic)
         {
            var responseTopic = this.generateUuid();
            var payload = config.publishPayload;
            if (!payload)
            {
               payload = {};
            }
            payload.responseTopic = responseTopic;
            this._pubSubOptionsHandle = this.alfSubscribe(responseTopic, lang.hitch(this, this.onPubSubOptions), true);
            this.alfPublish(config.publishTopic, payload, (config.publishGlobal !== false));
         }
         else
         {
            this.alfLog("warn", "A request was made to obtain form control options via PubSub, but no 'publishTopic' attribute was provided", config, this);
         }
      },

      /**
       * This is hitched to the subscription set up in the [getPubSubOptions]{@link module:alfresco/forms/controls/BaseFormControl#getPubSubOptions}
       * function and simply unsubscribes the from the topic generated when requesting the options.
       *
       * @instance
       * @param {object} payload The published information
       */
      onPubSubOptions: function alfresco_forms_controls_BaseFormControl__onPubSubOptions(payload) {
         this.alfUnsubscribeSaveHandles([this._pubSubOptionsHandle]);

         var optionsAttribute = lang.getObject("itemsAttribute", false, this.optionsConfig) || "options";
         var options = lang.getObject(optionsAttribute, false, payload);
         if (options)
         {
            this.setOptions(options);
         }
         else
         {
            this.alfLog("warn", "No 'options' attribute published in payload for pubSub options", payload, this);
         }
      },

      /**
       * This is called to set the latest options.
       *
       * @instance
       * @type {object}
       */
      setOptions: function alfresco_forms_controls_BaseFormControl__setOptions(options) {
         this.alfLog("log", "Setting options for field '" + this.fieldId + "'", options);
         if (this.deferValueAssigment)
         {
            this.pendingOptions = options;
         }
         else
         {
            var value = this.value;
            this.options = options;
            if (this.wrappedWidget)
            {
               var currentOptions = this.wrappedWidget.get("options");
               if (currentOptions && typeof this.wrappedWidget.removeOption === "function")
               {
                  // Remove all the current options...
                  array.forEach(currentOptions, lang.hitch(this, this.removeOption));
               }
               if (typeof this.wrappedWidget.addOption === "function")
               {
                  // Add all the new options...
                  array.forEach(options, lang.hitch(this, this.addOption));
               }
            }
            this.setOptionsValue(value, options);
         }
      },

      /**
       * Sets the value if there are options to select from
       *
       * @instance
       * @param {object} value The value to attempt to set from the available options
       * @param {array} options The options to choose from
       */
      setOptionsValue: function alfresco_forms_controls_BaseFormControl__setOptionsValue(value, options) {
         var optionsContainsValue = false;
         if (options && options.length > 0)
         {
            if (!this.supportsMultiValue)
            {
               optionsContainsValue = array.some(options, function(option) {
                  return option.value === value;
               });
            }
            else
            {
               value = array.filter(value, function(currValue) {
                  return  array.some(options, function(option) {
                     return currValue === option.value;
                  });
               });
               optionsContainsValue = value.length > 0;
            }
         }

         if (optionsContainsValue)
         {
            // Reset the option...
            this.setValue(value);
            this.value = value;
         }
         else if (options && options.length > 0 && this.firstValueIsDefault)
         {
            this.setValue(options[0].value);
            this.value = options[0].value;
         }
      },

      /**
       * Removes an option from the wrapped widet.
       *
       * @instance
       * @param {object} option The option to remove
       * @param {number} index The index of the option to remove
       */
      removeOption: function alfresco_forms_controls_BaseFormControl__removeOption(option, index) {
         // jshint unused:false
         this.wrappedWidget.removeOption(option);
      },

      /**
       * Adds a new option to the wrapped widget.
       *
       * @instance
       * @param {object} option The option to add
       * @param {number} index The index of the option to add
       */
      addOption: function alfresco_forms_controls_BaseFormControl__addOption(option, index) {
         this.processOptionLabel(option, index);
         this.wrappedWidget.addOption(option, index);
      },

      /**
       * The local image to use for a validation in progress indicator.
       *
       * @instance
       * @type {string}
       * @default
       */
      validationInProgressImg: "ajax_anim.gif",

      /**
       * The alt-text label to use for the validation in progress indicator.
       *
       * @instance
       * @type {string}
       * @default
       */
      validationInProgressAltText: "validation.inprogress.alttext",

      /**
       * The local image to use for a validation-error indicator.
       *
       * @instance
       * @type {string}
       * @default
       * @since 1.0.47
       */
      validationErrorImg: "error-16.png",

      /**
       * The alt-text label to use for the validation-error indicator.
       *
       * @instance
       * @type {string}
       * @default
       * @since 1.0.47
       */
      validationErrorAltText: "validation.error.alttext",

      /**
       * The local image to use for a validation-warning indicator.
       *
       * @instance
       * @type {string}
       * @default
       * @since 1.0.91
       */
      validationWarningImg: "warning-16.png",

      /**
       * The alt-text label to use for the validation-warning indicator.
       *
       * @instance
       * @type {string}
       * @default
       * @since 1.0.91
       */
      validationWarningAltText: "validation.warning.alttext",

      /**
       * The local image to use for the inline help indicator.
       *
       * @instance
       * @type {string}
       * @default
       */
      inlineHelpImg: "help.png",

      /**
       * The alt-text label to use for the inline help indicator.
       *
       * @instance
       * @type {string}
       * @default
       */
      inlineHelpAltText: "inlinehelp.alttext",

      /**
       * The width of the inline help dialog.
       *
       * @instance
       * @type {number}
       * @default
       */
      inlineHelpWidth: 400,

      /**
       * Processes the image source for indicating validation is in progress and its alt-text label.
       *
       * @instance
       */
      postMixInProperties: function alfresco_forms_controls_BaseFormControl__postMixInProperties() {
         // Store the initial value to ensure that when the page is added to the document is configured
         // correctly, the value can be set with the intended value (as opposed to what the wrapped widget
         // thinks it should be)...
         this.initialValue = this.value;

         if (!this.validationInProgressImgSrc)
         {
            this.validationInProgressImgSrc = require.toUrl("alfresco/forms/controls/css/images/" + this.validationInProgressImg);
         }

         // Make sure that the label is set, in case it's explicitly set as null and overrides the default...
         this.label = this.label || "";

         this.validationInProgressAltText = this.message(this.validationInProgressAltText, {
            0: this.message(this.label)
         });

         if (!this.validationWarningImgSrc)
         {
            this.validationWarningImgSrc = require.toUrl("alfresco/forms/controls/css/images/" + this.validationWarningImg);
         }
         this.validationWarningAltText = this.message(this.validationWarningAltText, {
            0: this.message(this.label)
         });
         if (!this.validationErrorImgSrc)
         {
            this.validationErrorImgSrc = require.toUrl("alfresco/forms/controls/css/images/" + this.validationErrorImg);
         }
         this.validationErrorAltText = this.message(this.validationErrorAltText, {
            0: this.message(this.label)
         });

         if (!this.inlineHelpImgSrc)
         {
            this.inlineHelpImgSrc = require.toUrl("alfresco/forms/controls/css/images/" + this.inlineHelpImg);
         }
         this.inlineHelpAltText = this.message(this.inlineHelpAltText, {
            0: this.message(this.label)
         });
      },

      /**
       * The topic whose publication should trigger the setting of the control value from payload.value
       *
       * @instance
       * @type {string}
       * @default
       */
      valueSubscriptionTopic: null,

      /**
       *
       * @instance
       */
      postCreate: function alfresco_forms_controls_BaseFormControl__postCreate() {
         this.initialConfig = this.getWidgetConfig();

         // Use the _disabled property if not already set...
         if (typeof this.initialConfig.disabled === "undefined")
         {
            this.initialConfig.disabled = this._disabled;
         }

         if (this.additionalCssClasses)
         {
            domClass.add(this.domNode, this.additionalCssClasses);
         }

         if (this.hideValidation)
         {
            domClass.add(this.domNode, "alfresco-forms-controls-BaseFormControl--validation-hidden");
         }

         if (this.inlineHelp)
         {
            domClass.remove(this._inlineHelpIndicator, "hidden");
         }

         this.wrappedWidget = this.createFormControl(this.initialConfig);

         // Check to see if the widget is "promised" or is expected to be returned immediately. If promised then
         // the "then" function needs to be hitched to the "onPromisedWidget" so that setup can be completed once it
         // has been delivered...
         if (this.isPromisedWidget && typeof this.wrappedWidget.then === "function")
         {
            this.wrappedWidget.then(lang.hitch(this, this.onPromisedWidget));
         }
         else
         {
            this.completeWidgetSetup();
         }

         if (this.valueSubscriptionTopic)
         {
            this.alfSubscribe(this.valueSubscriptionTopic, lang.hitch(this, this.valueSubscribe));
         }
      },

      /**
       * This should be overridden when the [createFormControl function]{@link module:alfresco/forms/controls/BaseFormControl#createFormControl}
       * will return a Promise (that is a widget that will be created asynchronously rather than returned immediately. This allows
       * processing of the control to be deferred until it has been loaded. This will typically be needed when it is not possible
       * to pre-load the required JavaScript files into the cache.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      isPromisedWidget: false,

      /**
       * This function is called when [isPromisedWidget]{@link module:alfresco/forms/controls/BaseFormControl#isPromisedWidget} is set to true.
       * It is the function that is hitched to the "then" function of the returned promise and is called when the promise is resolved (e.g.
       * when the widget has been created).
       *
       * @instance
       * @param {object} promisedWidget The widget that was promised.
       */
      onPromisedWidget: function alfresco_forms_controls_BaseFormControl__onPromisedWidget(promisedWidget) {
         this.wrappedWidget = promisedWidget;
         this.completeWidgetSetup();
      },

      /**
       * Handles adding the wrapped widget into the DOM model provided by the template. By default this assumes that
       * the widget is a Dojo widget and calls it's  "placeWidget" function.
       *
       * @instance
       */
      placeWidget: function alfresco_forms_controls_BaseFormControl__placeWrappedWidget() {
         if (this.wrappedWidget && typeof this.wrappedWidget.placeAt === "function")
         {
            this.wrappedWidget.placeAt(this._controlNode);
         }
         else
         {
            this.alfLog("warn", "The wrapped widget has no 'placeAt' function - perhaps the 'placeWidget' function should be overridden?", this);
         }
      },

      /**
       * This function is set as a callback when the widget is not immediately added to the main document.
       * It is used as a safety check to prevent scripts from being injected into the page (setting a value
       * on a node before it is added into the page will cause it to execute when rendered, but setting once
       * it has been included in the initial rendering will not cause any problems.
       *
       * @instance
       * @param {object} payload This is expected to be an empty object or null.
       */
      onWidgetAddedToDocument: function alfresco_forms_controls_BaseFormControl__onWidgetAddedToDocument(/*jshint unused:false*/ payload) {
         // jshint maxstatements:false,maxcomplexity:false
         if (!this.___addedToDocument && $.contains(document.body, this.domNode))
         {
            this.___addedToDocument = true;

            // Update the flag to indicate that we're no longer deferring value assignment, this needs to be
            // done before setting the initial value or processing all the deferred values as otherwise they'll
            // just get added to the end of the deferred value array!
            this.deferValueAssigment = false;

            if (this.pendingOptions)
            {
               this.options = this.pendingOptions;
               this.setOptions(this.options);
            }

            this.setValue(this.initialValue);

            // Iterate over all the deferred value assignments, arguably we could just process the last deferred
            // value, however it may be necessary to ensure that the intended flow of processing is maintained
            // (e.g. there may be rules configured to set or update other fields based on one of the values)
            array.forEach(this.deferredValueAssignments, function(deferred) {
               deferred.resolve();
            });
            delete this.deferredValueAssignments;

            // Set up the events that indicate a change in value...
            this.setupChangeEvents();

            // Set the initial visibility, requirement and disablement...
            this.alfVisible(this._visible);
            this.alfRequired(this._required);
            this.alfDisabled(this._disabled);

            // Set the label...
            var widgetId = lang.getObject("id", false, this.wrappedWidget);
            if (this.label && lang.trim(this.label) !== "")
            {
               this._labelNode.innerHTML = this.encodeHTML(this.message(this.label));
               if (widgetId)
               {
                  domAttr.set(this._labelNode, "for", this.wrappedWidget.id);
               }
            }
            else
            {
               domClass.add(this.domNode, "alfresco-forms-controls-BaseFormControl--no-label");
            }

            // Set the description...
            if (this.description && lang.trim(this.description) !== "")
            {
               this._descriptionNode.innerHTML = this.encodeHTML(this.message(this.description));
            }
            else
            {
               domStyle.set(this._descriptionRowNode, {display: "none"});
            }

            // Set the units label...
            if (this.unitsLabel && this.unitsLabel !== "")
            {
               this._unitsNode.innerHTML = this.encodeHTML(this.message(this.unitsLabel));
            }
            else
            {
               // Hide the units node if there are no units to display...
               domStyle.set(this._unitsNode, { display: "none"});
            }

            // Set the error message for validation...
            if (this.validationConfig && typeof this.validationConfig.errorMessage === "string")
            {
               // TODO: This message might not make much sense if it is just missing data for a required field...
               this._validationMessage.innerHTML = this.encodeHTML(this.message(this.validationConfig.errorMessage));
            }

            // Fix for missing label on validation input
            var validationInput = query("input.dijitValidationIcon.dijitValidationInner", this._controlNode);
            if(validationInput && validationInput.length > 0 && widgetId)
            {
               var validationInputId = this.wrappedWidget.id + "_validationInput";

               // Add 'id' to the input
               domAttr.set(validationInput[0], "id", validationInputId);

               // Create a label for the input
               domConstruct.create("label", {
                  innerHTML: this.message("validation.control.invalid"),
                  "for": validationInputId,
                  "class": "hiddenAccessible"
               }, validationInput[0], "before");
            }

            // If we've promised a value publication then resolve it now...
            // This has been added so that enclosing forms can publish the initialised value of the form control
            if (this.deferredValuePublication)
            {
               this.deferredValuePublication.resolve();
            }

            on(this.domNode, "click", lang.hitch(this, function(evt) {
               if (evt)
               {
                  evt.preventFocusTheft = true;
               }
            }));

            // See AKU-1049 - emit a custom event to be captured by the form to let it know to re-publish
            // all the field values. This will allow "late" created fields to process rules...
            on.emit(this.domNode, "ALF_FIELD_ADDED_TO_FORM", {
               bubbles: true,
               cancelable: true
            });
         }
         else
         {
            // No action yet. Don't unsubscribe.
         }
      },

      /**
       * Adds the widget into the current DOM fragment and then sets up subscriptions on widget processing complete
       * publications as we want to wait the most recently requested widget processing to complete (which in all likelihood
       * should be the request that caused the creation of this widget. The callback function will set the
       * value of the form control making sure that the widget has been added to the document as we only set the
       * once it's part of the document to ensure that no unsafe value (e.g. an XSS attack) can be
       * executed as part of the initial page rendering.
       *
       * @extendable
       * @instance
       */
      completeWidgetSetup: function alfresco_forms_controls_BaseFormControl__completeWidgetSetup() {
         this.placeWidget();
         this.widgetProcessingCompleteSubscription = this.alfSubscribe(topics.WIDGET_PROCESSING_COMPLETE, lang.hitch(this, this.onWidgetAddedToDocument), true);
      },

      /**
       * Whenever a widgets value changes we need to publish the details out to the other form controls (that exist in the
       * same scope) so that they can modify their appearance/behaviour as necessary). This function sets up the default events
       * that indicate that a wigets value has changed. This function can be overridden to handle non-Dojo widgets or when
       * multiple widgets represent a single control. If this function is overridden then the overriding function
       * must ensure that the [onValueChangeEvent]{@link module:alfresco/forms/controls/BaseFormControl#onValueChangeEvent}
       * is function is called when the value of the form control created by the
       * [createFormControl]{@link module:alfresco/forms/controls/BaseFormControl#createFormControl} function
       * changes.
       *
       * @instance
       * @overrideable
       */
      setupChangeEvents: function alfresco_forms_controls_BaseFormControl__setupChangeEvents() {
         if (this.wrappedWidget && typeof this.wrappedWidget.watch === "function")
         {
            this.own(this.wrappedWidget.watch("value", lang.hitch(this, this.onValueChangeEvent)));
         }
         else
         {
            this.alfLog("warn", "No watch method found on wrapped widget", this);
         }
      },

      /**
       * This is used to log whether or not this form control has had user focus or not. The reason being that in
       * certain circumstances we might not want to show validation errors until the user has at least made an attempt
       * to enter a value before they are shown validation error messages.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      _hadFocus: false,

      /**
       * This is used to log whether or not the form control has been updated by the user, typically this would
       * be set when the user performs an action (such as typing). Unlike 
       * [_hadFocus]{@link module:alfresco/forms/controls/BaseFormControl#_hadFocus} it is not set by all form
       * controls.
       * 
       * @instance
       * @type {boolean}
       * @default
       * @since 1.0.89
       */
      _hadUserUpdate: false,

      /**
       * This function is called whenever the form control loses focus. When this happens the
       * [_hadFocus]{@link module:alfresco/forms/controls/BaseFormControl#_hadFocus} attribute is set to
       * true and if the
       * [_pendingValidationFailureDisplay]{@link module:alfresco/forms/controls/BaseFormControl#_pendingValidationFailureDisplay}
       * has been set to true (by the [showValidationFailure]{@link module:alfresco/forms/controls/BaseFormControl#showValidationFailure}
       * function) then the [showValidationFailure]{@link module:alfresco/forms/controls/BaseFormControl#showValidationFailure}
       * will be called again to actually display the error message.
       *
       * @instance
       */
      _onBlur: function alfresco_forms_controls_BaseFormControl___onBlur() {
         this._hadFocus = true;
         if (this._pendingValidationFailureDisplay)
         {
            this._pendingValidationFailureDisplay = false;
            this.showValidationFailure();
         }
         this.suppressContainerKeyboardNavigation(false);
         this.inherited(arguments);
      },

      /**
       * This function is called whenever the form control gains focus. This results in the 
       * [suppressContainerKeyboardNavigation]{@link module:alfresco/lists/KeyboardNavigationSuppressionMixin#suppressContainerKeyboardNavigation}
       * function being called to ensure that when the form is placed inside a 
       * [list]{@link module:alfresco/lists/AlfList} the [view]{@link module:alfresco/lists/views/AlfListView}
       * does not use the key presses (typically the cursor keys) to navigate the user around items in the
       * list.
       *
       * @instance
       * @since 1.0.63
       */
      _onFocus: function alfresco_forms_controls_BaseFormControl___onFocus() {
         this.suppressContainerKeyboardNavigation(true);
         this.inherited(arguments);
      },

      /**
       * Handles the change in value for the current form control by publishing the details of the change and calling the
       * validate function to check that the new value is acceptable.
       *
       * @instance
       * @param {string} name
       * @param {string} oldValue
       * @param {string} value
       */
      onValueChangeEvent: function alfresco_forms_controls_BaseFormControl__onValueChangeEvent(name, oldValue, value) {
         this.formControlValueChange(name, oldValue, value);
         this.validate();
      },

      /**
       * This gets the value currently assigned to the wrapped widget. It assumes the widget has a single "value"
       * attribute that can be retrieved (i.e. it assumes a Dojo widget). Any extending classes that do not use
       * Dojo widgets (or use multiple widgets) should override this implementation to return the correct value.
       *
       * @instance
       * @extendable
       * @returns {object} The current value of the field.
       */
      getValue: function alfresco_forms_controls_BaseFormControl__getValue() {
         var value = null;
         if (this.wrappedWidget)
         {
            try
            {
               value = this.wrappedWidget.getValue();
            }
            catch(e)
            {
               this.alfLog("log", "An exception was thrown retrieving the value for field: '" + this.fieldId + "'");
            }
         }
         if (this.trimValue && value && typeof value.trim === "function")
         {
            value = value.trim();
         }
         value = this.convertStringValuesToBoolean(value);
         return value;
      },

      /**
       * Indicates whether or not string values should be converted to a boolean value if possible. This is checked
       * during [getValue]{@link module:alfresco/forms/controls/BaseFormControl#getValue} and
       * [setValue]{@link module:alfresco/forms/controls/BaseFormControl#setValue} to handle value conversion in and out
       * of the widget. This attribute should be honoured if overriding those functions.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      _convertStringValuesToBooleans: false,

      /**
       * If the supplied argument is a string then this will convert that value into a boolean. Conversion will only
       * take place if the string is either "true" or "false" otherwise the supplied value will be returned. This conversion
       * will only take place if [_convertStringValuesToBooleans]{@link module:alfresco/forms/controls/BaseFormControl#convertStringValuesToBoolean}
       * is set to true (which is not the default value).
       *
       * @instance
       * @param {object} value
       * @returns {object}
       */
      convertStringValuesToBoolean: function alfresco_forms_controls_BaseFormControl__convertStringValuesToBoolean(value) {
         if (this._convertStringValuesToBooleans === true && ObjectTypeUtils.isString(value))
         {
            if (value.toLowerCase() === "true")
            {
               value = true;
            }
            else if (value.toLowerCase() === "false")
            {
               value = false;
            }
         }
         return value;
      },

      /**
       * Sets the value of the form control created by the
       * [createFormControl]{@link module:alfresco/forms/controls/BaseFormContro#createFormControl}
       * function.
       *
       * @instance
       * @extendable
       * @param {object} value The value to set.
       */
      setValue: function alfresco_forms_controls_BaseFormControl__setValue(value) {
         if (this.deferValueAssigment)
         {
            // If we're not quite ready to start setting values, then we should defer
            // updating the value until we are. We determine that we're ready to set
            // a value by the existence of the deferredValueAssignments attribute..
            var deferred = new Deferred();
            deferred.then(lang.hitch(this, this.setValue, value));
            this.deferredValueAssignments.push(deferred);
         }
         else
         {
            this.alfLog("log", "Setting field: '" + this.fieldId + "' with value: ", value);
            if (this.wrappedWidget && typeof this.wrappedWidget.setValue === "function")
            {
               if (this._convertStringValuesToBooleans === true && typeof value === "boolean")
               {
                  value = value.toString();
               }
               this.value = value; // This is stored for ensuring dynamic options retain the correct value

               // If there are no options, then the current value will be retained, but if their are options
               // and the new value is not among them then the first available option will be selected instead
               this.setOptionsValue(value, this.optionsValue);
               this.wrappedWidget.setValue(value);
            }
         }
      },

      /**
       * This function is hitched when a 'valueSubscriptionTopic' attribute is set on the control. It takes an
       * inbound payload and if it contains a value property it is applied to the control through the setValue
       * function.
       *
       * @instance
       * @param {object} payload
       */
      valueSubscribe: function alfresco_forms_controls_BaseFormControl__valueSubscribe(payload) {
         var value = lang.getObject("value", false, payload);
         if (value !== null && typeof value !== "undefined")
         {
            if (typeof payload.name === "undefined" || payload.name === this.name)
            {
               this.setValue(value);
            }
         }
         else
         {
            this.alfLog("warn", "Attempt made to set form control value by subscription with an inapproriate payload", this);
         }
      },

      /**
       * This function publishes the current value of the widget. It is provided so that enclosing forms can publish
       * all of its controls values to process all rules.
       *
       * @instance
       * @param {Deferred} [deferred] A deferred object can optionally be passed. This will only be resolved as widget value
       * initialization completes.
       */
      publishValue: function alfresco_forms_controls_BaseFormControl__publishValue(deferred) {
         this.alfLog("log", "Publishing value for field: '" + this.fieldId + "'");
         if (!deferred)
         {
            // Make sure that the published value is correct. We can't trust that the wrapped widget
            // won't be spuriously returning the wrong value (this will happen with select boxes that
            // set their value as options are added) before initialization is completed. If the
            // "initialValue" variable still exists then this indicates that the initialization is not
            // yet complete. It will be removed when initialization completes and from that moment on
            // we can rely on the value returned by the "getValue" function.
            var value;
            if (this.___addedToDocument)
            {
               value = this.getValue();
            }
            else
            {
               value = this.initialValue;
            }
            if (this.wrappedWidget)
            {
               this.alfPublish("_valueChangeOf_" + this.fieldId, {
                  fieldId: this.fieldId,
                  name: this.name,
                  oldValue: value,
                  value: value
               });
            }
         }
         else if (!this.___addedToDocument)
         {
            // If passed a deferred object then save it for resolving once the widget is added to
            // the document...
            this.deferredValuePublication = deferred;
         }
         else
         {
            deferred.resolve();
         }
      },

      /**
       * This function is called whenever the value of the wrapped form widget changes. It publishes the details of the change
       * so that other form widgets can update their status based on the value.
       *
       * @instance
       * @param {string} attributeName
       * @param {object} oldValue
       * @param {object} value
       */
      formControlValueChange: function alfresco_forms_controls_BaseFormControl__formControlValueChange(attributeName, oldValue, value) {
         this.alfPublish("_valueChangeOf_" + this.fieldId, {
            fieldId: this.fieldId,
            name: this.name,
            oldValue: oldValue,
            value: value
         });
      },

      /**
       * This function must be overriden by extending widgets to create and return an actual form control instance.
       *
       * @instance
       * @overrideable
       * @param {object} config The configuration to use when instantiating the form control
       */
      createFormControl: function alfresco_forms_controls_BaseFormControl__createFormControl(/*jshint unused:false*/ config) {
         // Extension point
      },

      /**
       * This is a method that is expected to be overridden. We won't even assume that the widget configuration
       * will be standard Dojo configuration because we might be instantiated a custom or 3rd party library widget.
       *
       * @instance
       * @overrideable
       * @returns {object} The configuration for the form control.
       */
      getWidgetConfig: function alfresco_forms_controls_BaseFormControl__getWidgetConfig() {
         return {};
      },

      /**
       * This is a life-cycle is provided as an extension point to be overridden to perform additional
       * actions once the form control has been created.
       *
       * @instance
       * @extensionPoint
       */
      startup: function alfresco_forms_controls_BaseFormControl__startup() {
         // No action by default
      },

      /**
       * This will hold all of the configuration for validation. It is initialised in the constructor.
       *
       * @instance
       * @type {object}
       * @default
       */
      validationConfig: null,

      /**
       * This function validates the current widget value.
       *
       * @instance
       * @returns {boolean} A value indicating whether or not validation passed successfully or not.
       */
      validate: function alfresco_forms_controls_BaseFormControl__validate() {
         if (this.deferValueAssigment)
         {
            // Do nothing until final value has been assigned
         }
         else if (this.validationConfig && ObjectTypeUtils.isArray(this.validationConfig))
         {
            this.startValidation();
         }
         else
         {
            var isValid = this.processValidationRules();
            if (isValid)
            {
               this.alfPublish("ALF_VALID_CONTROL", {
                  name: this.name,
                  fieldId: this.fieldId
               });
               this.hideValidationFailure();
            }
            else
            {
               this.alfPublish("ALF_INVALID_CONTROL", {
                  name: this.name,
                  fieldId: this.fieldId
               });

               this.showValidationFailure();
            }
         }
      },

      /**
       * This function defines the default validation processing. It should be overridden by extending form controls
       * that do not use the default rules. This function rather than the "validate" function should be overridden
       * because this function simply indicates whether or not the control is valid but the "validate" function
       * controls the rendering of error messages and publication of related events.
       *
       * The rules are only processed if the field is visible and enabled because only those fields have their values
       * included in the overall form value (e.g. invisible fields won't be submitted so it doesn't matter if their
       * contents is invalid)
       *
       * @instance
       * @returns {boolean} Indicates whether or not the validation rules were passed successfully
       */
      processValidationRules: function alfresco_forms_controls_BaseFormControl__processValidationRules() {
         var valid = true;
         if ((this._visible || this.validateWhenHidden) && !this._disabled)
         {
            // Things to validate against are...
            // 1) Does the widget have a value if it is required
            var value = this.getValue();

            this.alfLog("log", "Validating: '" + this.fieldId + "' with value:", value);

            var passedRequiredTest = true,
                passedRegExpTest = true; // Assume valid starting point.

            // Check that a value has been specified if this is a required field...
            var valueIsEmptyArray = ObjectTypeUtils.isArray(value) && value.length === 0;
            passedRequiredTest = !(this._required && ((!value && value !== 0 && value !== false) || valueIsEmptyArray));

            // Check if any specified regular expression is passed...
            if (this.validationConfig)
            {
               if (typeof this.validationConfig.regExObj !== "undefined" && this.validationConfig.regExObj instanceof RegExp)
               {
                  passedRegExpTest = this.validationConfig.regExObj.test(value);
               }
            }

            // 3) Does the widget value satisfy a callback function
            // 4) Does the widget value satisfy a remote validation request

            // TODO: Need to output an appropriate error message.
            valid = passedRequiredTest && passedRegExpTest;
         }
         else
         {
            // No action required if field is invisible or disabled
         }
         return valid;
      },

      /**
       * Indicates whether or not any validation errors will be shown as soon as the form control is displayed
       *
       * @instance
       * @type {boolean}
       * @default
       */
      showValidationErrorsImmediately: true,

      /**
       * This is used to log that a validation error message should be displayed once the form control has gained
       * and then lost focus (as indicated by the [_hadFocus]{@link module:alfresco/forms/controls/BaseFormControl#_hadFocus})
       * attribute. It will be set to true if the form control has not yet gained focus but validation has occurred and
       * revealed that there are error conditions in the current form value.
       *
       * @instance
       * @type {boolean}
       * @default
       */
      _pendingValidationFailureDisplay: false,

      /**
       * By default this simply adds the "validation-error" and "display" classes to the _validationIndicator
       * and _validationMessage DOM nodes respectively. However, the code has been broken out into a separate function
       * to support extending classes that may provide alternative HTML templates or wish to render errors
       * differently.
       *
       * @instance
       */
      showValidationFailure: function alfresco_forms_controls_BaseFormControl__showValidationFailure() {
         if (this.showValidationErrorsImmediately || (this._hadFocus || this._hadUserUpdate))
         {
            domClass.add(this.domNode, "alfresco-forms-controls-BaseFormControl--invalid");
         }
         else
         {
            this._pendingValidationFailureDisplay = true;
         }
      },

      /**
       * By default this simply adds the "validation-warning" and "display" classes to the _validationIndicator
       * and _validationMessage DOM nodes respectively. However, the code has been broken out into a separate function
       * to support extending classes that may provide alternative HTML templates or wish to render errors
       * differently.
       *
       * @instance
       * @since 1.0.91
       */
      showValidationWarning: function alfresco_forms_controls_BaseFormControl__showValidationWarning() {
         if (this.showValidationErrorsImmediately || (this._hadFocus || this._hadUserUpdate))
         {
            domClass.add(this.domNode, "alfresco-forms-controls-BaseFormControl--warning");
         }
         else
         {
            this._pendingValidationFailureDisplay = true;
         }
      },

      /**
       * By default this simply removes the "validation-error" and "display" classes to the _validationIndicator
       * and _validationMessage DOM nodes respectively. However, the code has been broken out into a separate function
       * to support extending classes that may provide alternative HTML templates or wish to render errors
       * differently.
       *
       * @instance
       */
      hideValidationFailure: function alfresco_forms_controls_BaseFormControl__hideValidationFailure() {
         domClass.remove(this.domNode, "alfresco-forms-controls-BaseFormControl--invalid");
         domClass.remove(this.domNode, "alfresco-forms-controls-BaseFormControl--warning");
         this._pendingValidationFailureDisplay = false;
      },

      /**
       * This function is called from a [form]{@link module:alfresco/forms/Form} when it needs to get the
       * values from all the controls that it contains. The current control will only add its value to the
       * supplied object if appropriate.
       *
       * @instance
       * @param {object} values The object to update with the current value of the control
       */
      addFormControlValue: function alfresco_forms_controls_BaseFormControl__addFormControlValue(values) {
         // Only include field values if the control is NOT hidden or disabled
         // unless specifically requested by the configuration. This allows
         // multiple controls to represent a single field but also allows intentionally
         // hidden fields to still have data submitted
         var hidden = this._visible !== undefined && this._visible === false;
         var disabled = this._disabled !== undefined && this._disabled === true;
         var noPostWhenHiddenOrDisabled = this.postWhenHiddenOrDisabled !== undefined && this.postWhenHiddenOrDisabled === false;
         if ((hidden || disabled) && noPostWhenHiddenOrDisabled)
         {
            // Don't set the value (line below is just to allow debug point to be set)
         }
         else if (this.noPostWhenValueIs && arrayUtils.arrayContains(this.noPostWhenValueIs, this.getValue()))
         {
            // Don't set the value if the noPostIfValueIs array contains the current value.
         }
         else
         {
            this.setFormControlValue(values);
         }
      },

      /**
       * This is called from [addFormControlValue]{@link module:alfresco/forms/controls/BaseFormControl#addFormControlValue}
       * when evaluation of the field configuration and state confirms the value can be set.
       * 
       * @instance
       * @param {object} values The object to update with the current value of the control
       * @since 1.0.82
       * @overridable
       */
      setFormControlValue: function alfresco_forms_controls_BaseFormControl__setFormControlValue(values) {
         if (this.addedAndRemovedValues)
         {
            var addedName = this.name + this.addedNameSuffix;
            var removedName = this.name + this.removedNameSuffix;

            if (this.initialValue)
            {
               var initialPick = this.initialValue;
               var currentPick = this.getValue();
               if (this.valueDelimiter)
               {
                  initialPick = this.initialValue.split(this.valueDelimiter);
                  currentPick = currentPick.split(this.valueDelimiter);
               }
               
               var added = [];
               var removed = [];

               array.forEach(currentPick, function(currItem) {
                  // If the current picked item was in the inital selection it has not been added...
                  // ... BUT if the current pick was NOT in the inital selection it HAS been added
                  if (array.some(initialPick, function(intialItem) {
                     return currItem === intialItem;
                  })) {
                     // No action required, the current pick was also in the initial pick...
                  }
                  else
                  {
                     // The current pick was not an initial pick so has been added...
                     added.push(currItem);
                  }
               }, this);

               array.forEach(initialPick, function(intialItem) {
                  // If the initially picked item is in the current selection it has not been removed...
                  // ... BUT if the initially picked item is NOT in the current selection it HAS been removed
                  if (array.some(currentPick, function(currItem) {
                     return currItem === intialItem;
                  })) {
                     // No action required, the current pick was also in the initial pick...
                  }
                  else
                  {
                     // The initial pick is not a current pick so has been removed...
                     removed.push(intialItem);
                  }
               }, this);

               if (this.valueDelimiter)
               {
                  added = added.join(this.valueDelimiter);
                  removed = removed.join(this.valueDelimiter);
               }

               lang.setObject(addedName, added, values);
               lang.setObject(removedName, removed, values);
            }
            else
            {
               // Nothing removed, everything added...
               lang.setObject(addedName, this.getValue(), values);
               lang.setObject(removedName, (this.valueDelimiter ? "": []), values);
            }
         }
         else
         {
            lang.setObject(this.get("name"), this.getValue(), values);
         }
      },

      /**
       * This function is called from a [form]{@link module:alfresco/forms/Form} when it needs to set the
       * values from all the controls that it contains. The current control will be updated if appropriate.
       *
       * @instance
       * @param {object} values The object to set the each form control value from
       * @param {boolean} initialization Indicates whether this call is part of the initialization of the containing form
       */
      updateFormControlValue: function alfresco_forms_controls_BaseFormControl__updateFormControlValue(values, initialization) {
         var hidden = this._visible !== undefined && this._visible === false;
         var disabled = this._disabled !== undefined && this._disabled === true;
         var noUpdateWhenHiddenOrDisabled = this.noValueUpdateWhenHiddenOrDisabled !== undefined && this.noValueUpdateWhenHiddenOrDisabled === true;
         if ((hidden || disabled) && noUpdateWhenHiddenOrDisabled)
         {
            // Don't set the value as the field is hidden or disabled and has requested that it not be updated
            // in these circumstances. The typical reason for this is that multiple controls represent a single
            // field and it is not the displayed control so shouldn't be updated to preserve its default value.
         }
         else
         {
            var name = this.get("name");
            if (name)
            {
               var v = lang.getObject(this.get("name"), false, values);
               if (typeof v !== "undefined")
               {
                  if (initialization)
                  {
                     // If this is set during initalization, then set the initial value...
                     this.initialValue = v;
                  }
                  this.setValue(v);
               }
            }
         }
      },

      /**
       * This function is called from a [form]{@link module:alfresco/forms/Form} when it needs to validate the
       * all the controls that it contains.
       *
       * @instance
       */
      validateFormControlValue: function alfresco_forms_controls_BaseFormControl__validateFormControlValue() {
         if (this.publishValue && typeof this.publishValue === "function")
         {
            this.validate();
         }
      },

      /**
       * This function is attached to the click event of the inline help icon and will display a dialog containing
       * inline help when called.
       *
       * @instance
       */
      showInlineHelp: function alfresco_forms_controls_BaseFormControl__showInlineHelp() {
         if (this.inlineHelp)
         {
            this.alfPublish(topics.CREATE_DIALOG, {
               dialogId: "BASE_FORM_CONTROL_INLINE_HELP",
               dialogTitle: this.message(this.label),
               dialogWidth: this.inlineHelpWidth + "px",
               widgetsContent: [
                  {
                     name: "dijit/layout/ContentPane",
                     config: {
                        content: this.message(this.inlineHelp).replace(/\n/g, "<br />")
                     }
                  }
               ]
            }, true);
         }
      }
   });
});