Source: services/FormsRuntimeService.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/>.
 */

/**
 * <b>IMPORTANT NOTE: This service is not fully complete yet and does not guarantee full support for the
 * rendering all configured forms defined in XML for the forms runtime. It requires the 
 * "aikau-forms-runtime-support" JAR file to be available in the "share/WEB-INF/lib" folder and it will
 * only work on Share. This has been made available early to for the purposes of collaborative development
 * with the Alfresco Community.</b>
 * 
 * @module alfresco/services/FormsRuntimeService
 * @extends module:alfresco/services/BaseService
 * @mixes module:alfresco/core/CoreXhr
 * @author Dave Draper
 * @since 1.0.76
 */
define(["dojo/_base/declare",
        "alfresco/services/BaseService",
        "alfresco/core/CoreXhr",
        "alfresco/core/NodeUtils",
        "alfresco/core/topics",
        "service/constants/Default",
        "webscripts/defaults",
        "dojo/_base/array",
        "dojo/_base/lang",
        "jquery",
        // No call backs from here...
        "alfresco/forms/Form",
        "alfresco/forms/ControlRow",
        "alfresco/renderers/Boolean",
        "alfresco/forms/controls/CheckBox",
        "alfresco/forms/controls/DateRange",
        "alfresco/forms/controls/DateTextBox",
        "alfresco/forms/controls/FilePicker",
        "alfresco/forms/controls/FilteringSelect",
        "alfresco/forms/controls/MultiSelectInput",
        "alfresco/forms/controls/NumberSpinner",
        "alfresco/forms/controls/Select",
        "alfresco/forms/controls/TextArea",
        "alfresco/forms/controls/TextBox",
        "alfresco/forms/controls/Transitions",
        "alfresco/node/MetadataGroups",
        "alfresco/renderers/Date",
        "alfresco/renderers/Property",
        "alfresco/renderers/Size"],
        function(declare, BaseService, CoreXhr, NodeUtils, topics, AlfConstants, webScriptDefaults, array, lang, $) {
   
   return declare([BaseService, CoreXhr], {
      
      /**
       * An array of the i18n files to use with this widget.
       *
       * @instance
       * @type {object[]}
       * @default [{i18nFile: "./i18n/FormsRuntimeService.properties"}]
       */
      i18nRequirements: [{i18nFile: "./i18n/FormsRuntimeService.properties"}],

      /**
       * 
       * @instance
       * @type {object}
       */
      controlMappings: {

         // To save duplication of data there will always be a fallback check on default
         // control mappings, this allows there to be flexibility in configuration and mapping
         // on "kind" (type) specific mapping...
         "default": {
            "/org/alfresco/components/form/controls/association.ftl": {
               name: "alfresco/forms/controls/FilePicker",
               config: {
                  valueDelimiter: ",",
                  addedAndRemovedValues: true
               }
            },

            "/org/alfresco/components/form/controls/authority.ftl": {
               name: "alfresco/forms/controls/MultiSelectInput",
               config: {
                  width: "400px",
                  valueDelimiter: ",",
                  addedAndRemovedValues: true,
                  optionsConfig: {
                     labelAttribute: "name",
                     queryAttribute: "name",
                     valueAttribute: "nodeRef",
                     publishTopic: topics.GET_AUTHORITIES,
                     publishPayload: {
                        resultsProperty: "response.data.items"
                     }
                  }
               }
            },

            "/org/alfresco/components/form/controls/category.ftl": {
               name: "alfresco/forms/controls/MultiSelectInput",
               config: {
                  name: "tags",
                  width: "400px",
                  valueDelimiter: ",",
                  optionsConfig: {
                     queryAttribute: "name",
                     valueAttribute: "nodeRef",
                     labelAttribute: "name",
                     publishTopic: "ALF_RETRIEVE_CURRENT_TAGS",
                     publishPayload: {
                        resultsProperty: "response.data.items"
                     }
                  }
               }
            },
            "/org/alfresco/components/form/controls/checkbox.ftl": {
               name: "alfresco/forms/controls/CheckBox"
            },
            "/org/alfresco/components/form/controls/date.ftl": {
               name: "alfresco/forms/controls/DateTextBox",
               config: {
                  unsetReturnValue: ""
               }
            },
            "/org/alfresco/components/form/controls/daterange.ftl": {
               name: "alfresco/forms/controls/DateRange",
               config: {
                  valueFormatSelector: "datetime"
               }
            },
            "/org/alfresco/components/form/controls/workflow/email-notification.ftl": {
               name: "alfresco/forms/controls/CheckBox"
            },
            "/org/alfresco/components/form/controls/info.ftl": {
               name: "alfresco/renderers/Property"
            },
            "/org/alfresco/components/form/controls/mimetype.ftl": {
               name: "alfresco/forms/controls/FilteringSelect",
               config: {
                  optionsConfig: {
                     queryAttribute: "label",
                     publishTopic: topics.GET_FORMS_FORMS_RUNTIME_MIMETYPES,
                     publishPayload: {
                        resultsProperty: "options"
                     }
                  }
               }
            },
            "/org/alfresco/components/form/controls/number.ftl": {
               name: "alfresco/forms/controls/NumberSpinner",
               config: {
                  permittedDecimalPlaces: 10
               }
            },

            "/org/alfresco/components/form/controls/workflow/taskowner.ftl": {
               name: "alfresco/renderers/User",
               config: {

               }
            },

            "/org/alfresco/components/form/controls/workflow/packageitems.ftl": {
               name: "alfresco/forms/controls/FilePicker",
               config: {
                  valueDelimiter: ",",
                  addedAndRemovedValues: true
               }
            },
            "/org/alfresco/components/form/controls/percentage-approve.ftl": {
               name: "alfresco/forms/controls/NumberSpinner",
               config: {
                  min: 0,
                  max: 100
               }
            },
            "/org/alfresco/components/form/controls/workflow/priority.ftl": {
               name: "alfresco/forms/controls/Select",
               config: {
                  optionsConfig: {
                     fixed: [
                        {
                           label: "priority.high", value: "1"
                        },
                        {
                           label: "priority.medium", value: "2"
                        },
                        {
                           label: "priority.low", value: "3"
                        }
                     ]
                  }
               }
            },
            "/org/alfresco/components/form/controls/readonly.ftl": {
               name: "alfresco/forms/controls/TextBox",
               config: {
                  disablementConfig: {
                     initialValue: true
                  }
               }
            },
            "/org/alfresco/components/form/controls/selectone.ftl": {
               name: "alfresco/forms/controls/Select"
            },
            "/org/alfresco/components/form/controls/textarea.ftl": {
               name: "alfresco/forms/controls/TextArea"
            },
            "/org/alfresco/components/form/controls/textfield.ftl": {
               name: "alfresco/forms/controls/TextBox"
            }
         },

         // The following mappings are "kind" (type) specific and within each
         // "kind" are view mode specific mappings...
         node: {
            edit: {

            },

            // The "view" mode of the "node" kind is different in that it displays the
            // data as renderers rather than form controls...
            view: {
               "/org/alfresco/components/form/controls/checkbox.ftl": {
                  name: "alfresco/renderers/Boolean"
               },
               "/org/alfresco/components/form/controls/date.ftl": {
                  name: "alfresco/renderers/Date",
                  config: {
                     simple: true
                     // modifiedDateProperty: "prop_cm_modified",
                     // modifiedByProperty: "prop_cm_modifier"
                  }
               },
               "/org/alfresco/components/form/controls/mimetype.ftl": {
                  name: "alfresco/renderers/Property"
               },
               "/org/alfresco/components/form/controls/number.ftl": {
                  name: "alfresco/renderers/Property"
               },
               "/org/alfresco/components/form/controls/readonly.ftl": {
                  name: "alfresco/renderers/Property"
               },
               "/org/alfresco/components/form/controls/size.ftl": {
                  name: "alfresco/renderers/Size"
               },
               "/org/alfresco/components/form/controls/textfield.ftl": {
                  name: "alfresco/renderers/Property"
               },
               "/org/alfresco/components/form/controls/textarea.ftl": {
                  name: "alfresco/renderers/Property"
               }
            }
         },

         task: {
            edit: {

               "/org/alfresco/components/form/controls/workflow/transitions.ftl": {
                  name: "alfresco/forms/controls/Transitions"
               }
            },

            view: {
               prop_bpm_dueDate: {
                  name: "alfresco/renderers/Date",
                  config: {
                     simple: true
                  }
               },

               "/org/alfresco/components/form/controls/info.ftl": {
                  name: "alfresco/renderers/Property"
               },
               "/org/alfresco/components/form/controls/workflow/priority.ftl": {
                  name: "alfresco/renderers/Property"
               },
               "/org/alfresco/components/form/controls/selectone.ftl": {
                  name: "alfresco/renderers/Property" 
               }
            }
         },

         workflow: {
            create: {

            }
         }
      },

      /**
       * This has been added to support the slightly unsual scenarios where the configured
       * parameter name in the form is not the parameter name that should be substitued. This
       * was originally added to support the case of "Advanced Search" forms where the
       * "prop_cm_modified" parameter name should be modified to be "prop_cm_modified-date-range"
       * 
       * @instance
       * @type {object}
       * @since 1.0.91
       */
      propertyNameMapping: {
         type: {
            edit: {
               prop_cm_modified: "prop_cm_modified-date-range"
            }
         }
      },

      /**
       * Maps the XML constraint configuration specifying YUI2 based Share functions to Regular expression
       * configuration for form validation.
       * 
       * @instance
       * @type {object[]}
       * @default
       * @since 1.0.77
       */
      constraintMappings: {
         "Alfresco.forms.validation.fileName": {
            pattern: "([\"\*\\\>\<\?\/\:\|]+)|([\.]?[\.]+$)|(^[ \t]+|[ \t]+$)",
            match: false
         },
         "Alfresco.forms.validation.wikiTitle": {
            pattern: "([#\\\?\/\|]+)|([\.]?[\.]+$)",
            match: false
         },
         "Alfresco.forms.validation.nodeRef": {
            pattern: "^[^\:^ ]+\:\/\/[^\:^ ]+\/[^ ]+$",
            match: true
         },
         "Alfresco.forms.validation.phone": {
            pattern: "^[0-9\(\)\[\]\-\+\*#\\:\/,; ]+$",
            match: true
         },
         "Alfresco.forms.validation.time": {
            pattern: "^([0-1]\d|2[0-3]):[0-5]\d(:[0-5]\d)?$",
            match: true
         },
         "Alfresco.forms.validation.url": {
            pattern: "(ftp|http|https):\/\/[\w\-_]+(\.[\w\-_]+)*([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?",
            match: true
         }
         // TODO: "Alfresco.forms.validation.email" not handled yet due to complexity

      },

      /**
       * Custom control mappings to check before the default control mappings.
       * 
       * @instance
       * @type {string}
       * @default
       */
      customControlMappings: null,

      /**
       * This will be populated with MIME type options the first time the 
       * "/org/alfresco/components/form/controls/mimetype.ftl" is mapped to a
       * formcontrol and used. It prevents multiple XHR for data that is unlikely to
       * change unnecessary.
       * 
       * @instance
       * @type {object[]}
       * @default
       * @since 1.0.77
       */
      _loadedMimeTypes: null,

      /**
       * 
       * 
       * @instance
       * @listens module:alfresco/core/topics#REQUEST_FORM
       * @listens module:alfresco/core/topics#GET_FORMS_FORMS_RUNTIME_MIMETYPES
       */
      registerSubscriptions: function alfresco_services_FormsRuntimeService__registerSubscriptions() {
         this.alfSubscribe(topics.REQUEST_FORM, lang.hitch(this, this.onFormRequest));
         this.alfSubscribe(topics.GET_FORMS_FORMS_RUNTIME_MIMETYPES, lang.hitch(this, this.onMimeTypesRequest));
      },

      /**
       * Handles requests to load the available MIME types to display as 
       * [form control]{@link module:alfresco/forms/controls/BaseFormControl} options. If the
       * MIME types have already been loaded the previously loaded values will be returned
       * by called [publishMimeTypeOptions]{@link module:alfresco/services/FormsRuntimeService#publishMimeTypeOptions}.
       * 
       * @instance
       * @param {object} payload The details for the options request.
       * @since 1.0.77
       */
      onMimeTypesRequest: function alfresco_services_FormsRuntimeService__onMimeTypesRequest(payload) {
         if (!this._loadedMimeTypes)
         {
            this.serviceXhr({
               url : AlfConstants.URL_SERVICECONTEXT + "utils/mimetypemap",
               data: payload,
               method: "GET",
               successCallback: this.onMimeTypesLoaded,
               failureCallback: function() { /* No action required */ },
               progressCallback: function() { /* No action required */ },
               callbackScope: this
            });
         }
         else
         {
            this.publishMimeTypeOptions(payload);
         }
      },

      /**
       * Handles successful requests for available MIME types, converts the response into a
       * structure that is appropriate for [form controls]{@link module:alfresco/forms/controls/BaseFormControls},
       * stores the data in [_loadedMimeTypes]{@link module:alfresco/services/FormsRuntimeService#_loadedMimeTypes}
       * and calls [publishMimeTypeOptions]{@link module:alfresco/services/FormsRuntimeService#publishMimeTypeOptions}
       * to publish the available options.
       * 
       * @instance
       * @param {object} response
       * @param {object} originalRequestConfig
       * @since 1.0.77
       */
      onMimeTypesLoaded: function alfresco_services_FormsRuntimeService__onMimeTypesLoaded(response, originalRequestConfig) {
         if (response && response.mimetypes)
         {
            var mimetypes = response.mimetypes;
            this._loadedMimeTypes = [];
            array.forEach(Object.keys(mimetypes), function(value) {
               this._loadedMimeTypes.push({
                  label: mimetypes[value],
                  value: value
               });
            }, this);

            // Sort alphabetically by label...
            this._loadedMimeTypes.sort(function(a,b) {
               var nameA = a.label.toUpperCase();
               var nameB = b.label.toUpperCase();
               if (nameA < nameB) {
                  return -1;
               }
               if (nameA > nameB) {
                  return 1;
               }
               return 0;
            });

            // Add "Unknown" type in first...
            this._loadedMimeTypes.unshift({
               label: this.message("formsruntimeservice.unknown.mimetype"),
               value: ""
            });

            this.publishMimeTypeOptions(originalRequestConfig.data);
         }
      },

      /**
       * Publishes the [available MIME type options]{@link module:alfresco/services/FormsRuntimeService#_loadedMimeTypes}.
       * 
       * @instance
       * @param {object} payload The details for the options request.
       * @since 1.0.77
       */
      publishMimeTypeOptions: function(data) {
         var topic;
         if (data.alfResponseTopic)
         {
            topic = (data.alfResponseScope || "") + data.alfResponseTopic;
         }
         else
         {
            topic = data.responseTopic;
         }
         this.alfPublish(topic, {
            options: this._loadedMimeTypes
         });
      },
      
      /**
       * 
       * 
       * @instance
       */
      onFormRequest: function alfresco_services_FormsRuntimeService__onFormRequest(payload) {
         if (payload.itemKind &&
             payload.itemId &&
             payload.mode)
         {
            var itemId = payload.itemId;
            var url = AlfConstants.URL_SERVICECONTEXT + "aikau/" + webScriptDefaults.WEBSCRIPT_VERSION + "/form" +
                      "?itemKind=" + payload.itemKind + 
                      "&itemId=" + itemId + 
                      "&formId=" + (payload.formId || "null") + 
                      "&mode=" + payload.mode;

            var successTopic = payload.alfSuccessTopic || 
                               payload.alfResponseTopic ||
                               (payload.alfTopic + "_SUCCESS");

            this.serviceXhr({url : url,
                             method: "GET",
                             formConfig: payload.formConfig,
                             alfDestination: payload.alfDestination,
                             alfSuccessTopic: successTopic,
                             successCallback: this.onFormLoaded,
                             failureCallback: this.onFormLoadFailure,
                             callbackScope: this});
         }
         else
         {
            this.alfLog("error", "A request was made to retrieve a form that was missing one of 'itemKind', 'itemId', 'formId' or 'mode' attributes", payload, this);
         }
      },

      /**
       * Returns the number of columns that should be used for the 
       * [ControlRow]{@link module:alfresco/forms/ControlRow} for the supplied structure. The
       * "message" attribute in the structure contains the name of a template that can
       * be mapped to a number of controls.
       * 
       * @instance
       * @return {number|null} The number of columns or null if they can't be determined
       * @since 1.0.86
       */
      getColumnsForControlRow: function alfresco_services_FormsRuntimeService__getColumnsForControlRow(structureElement) {
         var columns = null;
         if (structureElement.message)
         {
            switch (structureElement.message) {
               case "/org/alfresco/components/form/2-column-set.ftl":
                  columns = 2;
                  break;

               case "/org/alfresco/components/form/2-column-wide-left-set.ftl":
                  columns = 2;
                  break;

               case "/org/alfresco/components/form/3-column-set.ftl":
                  columns = 3;
                  break;

               default:
                  this.alfLog("warn", "Could not find a layout structure for ", structureElement.message, this);
            }
         }
         return columns;
      },

      /**
       * The form data is returned as a JSON object that needs to be parsed and the data mapped to Aikau
       * configuration.
       * 
       * @instance
       * @param {object} response The response will be the form configuration to render
       * @param {object} originalReqeuestConfig The object used when making the XHR request that resulted in this callback
       */
      onFormLoaded: function alfresco_services_FormsRuntimeService__onFormLoaded(response, originalRequestConfig) {
         if (response.structure &&
             response.fields &&
             response.constraints)
         {
            var widgets = [];
            if (response["arguments"].itemKind === "node" && response.mode === "view")
            {
               var properties = [];
               var metadataGroup = {
                  name: "alfresco/node/MetadataGroups",
                  config: {
                     currentItem: response.data,
                     groups: [
                        {
                           title: this.message("formsruntimeservice.properties"), // TODO: Need to figure out correct value here...
                           widgets: properties
                        }
                     ]
                  }
               };

               array.forEach(response.structure, function(structureElement) {
                  if (structureElement.children)
                  {
                     array.forEach(structureElement.children, lang.hitch(this, this.addField, properties, response));
                  }
               }, this);

               widgets.push(metadataGroup);
            }
            else
            {
               // Check for an optional ID for the form...
               var formId = lang.getObject("formConfig.formId", false, originalRequestConfig);

               // Mixin in any additional data to include in the payload...
               var formSubmissionPayloadMixin = lang.getObject("formConfig.formSubmissionPayloadMixin", false, originalRequestConfig);
               var okButtonPublishPayload = {
                  url: response.submissionUrl,
                  urlType: "FULL",
                  alf_destination: originalRequestConfig.alfDestination
               };
               formSubmissionPayloadMixin && lang.mixin(okButtonPublishPayload, formSubmissionPayloadMixin);

               var okButtonPublishTopic = lang.getObject("formConfig.okButtonPublishTopic", false, originalRequestConfig);
               var widgetsBefore = lang.getObject("formConfig.widgetsBefore", false, originalRequestConfig) || [];

               var formControls = lang.clone(widgetsBefore);
               var formConfig = {
                  id: formId,
                  name: "alfresco/forms/Form",
                  config: {
                     showOkButton: response.showSubmitButton,
                     showCancelButton: response.showCancelButton,
                     okButtonPublishTopic: okButtonPublishTopic || (response.method === "post" ? "ALF_CRUD_CREATE" : "ALF_CRUD_UPDATE"),
                     okButtonPublishPayload: okButtonPublishPayload,
                     okButtonPublishGlobal: true,
                     value: response.data,
                     widgets: formControls,
                     formSubmissionTriggerTopic: topics.TRIGGER_FORM_SUBMISSION
                  }
               };

               var okButtonLabel = lang.getObject("formConfig.okButtonLabel", false, originalRequestConfig);
               if (okButtonLabel)
               {
                  formConfig.config.okButtonLabel = okButtonLabel;
               }

               // Iterate over the structure array and add the the form controls for all of the fields
               // that it contains...
               // TODO: Need to handle the different structures that are available...
               array.forEach(response.structure, function(structureElement) {
                  var rowControls = [];
                  var structureWidget = {
                     name: "alfresco/forms/ControlRow",
                     config: {
                        title: structureElement.params === "title" ? structureElement.event : null,
                        widgets: rowControls
                     }
                  };
                  formControls.push(structureWidget);
                  
                  var columns = this.getColumnsForControlRow(structureElement);
                  if (columns)
                  {
                     while (structureElement.children.length)
                     {
                        var childrenToAdd = structureElement.children.splice(0, columns);
                        if (childrenToAdd)
                        {
                           array.forEach(childrenToAdd, lang.hitch(this, this.addField, rowControls, response));
                        }
                        if (structureElement.children.length)
                        {
                           rowControls = [];
                           structureWidget = {
                              name: "alfresco/forms/ControlRow",
                              config: {
                                 widgets: rowControls
                              }
                           };
                           formControls.push(structureWidget);
                        }
                     }
                  }
                  else
                  {
                     // Select the appropriate target for the form controls, at the moment this assumes
                     // that if a "message" attribute is provided then all form controls go into the same
                     // row - however, that needs to be validated, it might be necessary to iterate over the
                     // controls to ensure that the appropriate number of controls are added per row.
                     var targetForControls = structureElement.message ? rowControls : formControls;
                     if (structureElement.children)
                     {
                        array.forEach(structureElement.children, lang.hitch(this, this.addField, targetForControls, response));
                     }
                  }

               }, this);

               widgets.push(formConfig);
            }
            
            var useDialog = lang.getObject("formConfig.useDialog", false, originalRequestConfig);
            if (useDialog)
            {
               var dialogTitle = lang.getObject("formConfig.dialogTitle", false, originalRequestConfig);
               var dialogId = lang.getObject("formConfig.formId", false, originalRequestConfig);
               this.alfServicePublish(topics.CREATE_FORM_DIALOG, {
                  dialogId: dialogId,
                  dialogTitle: dialogTitle,
                  formSubmissionTopic: formConfig.config.okButtonPublishTopic,
                  formSubmissionGlobal: true,
                  formSubmissionPayloadMixin: formConfig.config.okButtonPublishPayload,
                  formValue: formConfig.config.value,
                  widgets: formConfig.config.widgets,
                  formSubmissionTriggerTopic: topics.TRIGGER_FORM_SUBMISSION
               });
            }
            else
            {
               this.alfPublish(originalRequestConfig.alfSuccessTopic, {
                  widgets: widgets
               });
            }
         }
      },

      /**
       * Called from [onFormLoaded]{@link module:alfresco/services/FormsRuntimeService#onFormLoaded} for
       * each structural element in the form. Each structural element will typically represent a field
       * to be rendered.
       * 
       * @instance
       * @param {object[]} widgets The array of widgets to add the widget for the field to
       * @param {object} formConfig The full configuration for the form being rendered
       * @param {object} structureElement The current structural element defining a field to be rendered
       */
      addField: function alfresco_services_FormsRuntimeService__addField(widgets, formConfig, structureElement) {
         // if (structureElement && 
         //     structureElement.id && 
         //     typeof formConfig.data[structureElement.id] !== "undefined")
         // NOTE: It's not clear how we should treat fields that have no associated data, in some cases this
         //       prevents the form from being posted (on edit - last accessed), but in workflow not all fields
         //       having matching data.
         if (structureElement && 
             structureElement.id)
         {
            // TODO: Do we need to consider "kind" attributes on the structureElement other than "field" ???
            
            // Look up the target field referenced in the structure element...
            var targetField = formConfig.fields[structureElement.id];
            if (targetField )
            {
               // We need to get the control template from the field configuration. This will be a Surf/YUI2 
               // reference which is fairly meaningless in the context of Aikau, however we can use this to 
               // map to an Aikau form control...
               var controlTemplate = lang.getObject("control.template", false, targetField);
               if (controlTemplate)
               {
                  var widget;
                  if (formConfig["arguments"].itemKind === "node" && formConfig.mode === "view")
                  {
                     // The "view" mode for the "node" kind is treated differently from all other
                     // form renderings...
                     widget = this.getViewProperty(targetField, controlTemplate, formConfig);
                  }
                  else
                  {
                     widget = this.getEditFormControl(targetField, controlTemplate, formConfig);
                  }
                  widget && widgets.push(widget);
               }
            }
         }
      },

      /**
       * Called from [addField]{@link module:alfresco/services/FormsRuntimeService#addField} 
       * and [getViewProperty]{@link module:alfresco/services/FormsRuntimeService#getViewProperty} to
       * find the Aikau control widget that has been mapped to the supplied controlTemplate.
       * 
       * @instance
       * @param {object} formConfig The full configuration for the form being rendered
       * @param {object} targetField The configuration for the field to render.
       * @param {string} controlTemplate The name of the template to be mapped to a widget
       * @param {object} mappings The mappings configuration object to explore
       * @return {object} The mapped control.
       * @since 1.0.77
       */
      getMappedControl: function alfresco_services_FormsRuntimeService__getMappedControl(formConfig, targetField, controlTemplate, mappings) {
         var kind = formConfig["arguments"].itemKind;
         var mode = formConfig.mode;
         var control;
         if (mappings)
         {
            var kindMapping = mappings[kind];
            if (kindMapping)
            {
               var modeMapping = kindMapping[mode];
               if (modeMapping)
               {
                  control = modeMapping[targetField.dataKeyName];
                  if (!control)
                  {
                     control = modeMapping[controlTemplate];
                  }
               }
               if (!control)
               {
                  control = kindMapping[controlTemplate];
               }
            }
            if (!control)
            {
               control = mappings["default"][controlTemplate];
            }
         }
         return control;
      },

      /**
       * Called from [addField]{@link module:alfresco/services/FormsRuntimeService#addField} to create
       * a "view" mode widget for the field supplied.
       * 
       * @instance
       * @param  {object} targetField The configuration for the field to render.
       * @param  {string} controlTemplate The name of the template to be mapped to a widget
       * @param {object} formConfig The full configuration for the form being rendered
       * @return {object} A model for the widget to be used to render the property.
       */
      getViewProperty: function alfresco_services_FormsRuntimeService__getViewProperty(targetField, controlTemplate, formConfig) {
         var widget;
         var formControl = this.getMappedControl(formConfig, targetField, controlTemplate, this.customControlMappings) ||
                           this.getMappedControl(formConfig, targetField, controlTemplate, this.controlMappings);
         if (formControl)
         {
            widget = lang.clone(formControl);
            var data = {
               id: targetField.name.toUpperCase(),
               label: targetField.label,
               config: {
                  propertyToRender: targetField.dataKeyName
               }
            };
            $.extend(true, widget, data);
         }
         else
         {
            this.alfLog("warn", "Could not find a mapping for ", controlTemplate, this);
         }
         return widget;
      },

      /**
       * Called from [addField]{@link module:alfresco/services/FormsRuntimeService#addField} to create
       * a "edit" mode widget for the field supplied.
       * 
       * @instance
       * @param {object} targetField The configuration for the field to render.
       * @param {string} controlTemplate The name of the template to be mapped to a widget
       * @param {object} formConfig The full configuration for the form being rendered
       * @return {object} A model for the widget to be used to render the property.
       */
      getEditFormControl: function alfresco_services_FormsRuntimeService__addFormControl(targetField, controlTemplate, formConfig) {
         var widget;
         
         var formControl = this.getMappedControl(formConfig, targetField, controlTemplate, this.customControlMappings) ||
                           this.getMappedControl(formConfig, targetField, controlTemplate, this.controlMappings);
         if (formControl)
         {
            var kind = formConfig["arguments"].itemKind;
            var mode = formConfig.mode;
            var name = lang.getObject(kind + "." + mode + "." + targetField.name, false, this.propertyNameMapping) || targetField.name;

            widget = lang.clone(formControl);
            var data = {
               id: targetField.name.toUpperCase(),
               config: {
                  currentItem: formConfig.data,
                  fieldId: targetField.name.toUpperCase(),
                  propertyToRender: name, // NOTE: Hedging bets for renderers
                  name: name,
                  label: targetField.label,
                  description: targetField.description
               }
            };
            $.extend(true, widget, data);

            if (formConfig.constraints)
            {
               array.forEach(formConfig.constraints, function(constraint) {
                  if (constraint.fieldId === targetField.name)
                  {
                     this.addConstraint(widget, constraint);
                  }
               }, this);
            }

            // Add any option configuration that may be present...
            this.addOptions(widget, targetField);
            
         }
         else
         {
            this.alfLog("warn", "Could not find a mapping for ", controlTemplate, this);
         }
         return widget;
      },

      /**
       * 
       * @instance
       * @param {object} widget The widget model to add the constraint to
       * @param {object} constraint The constraint configuration
       */
      addConstraint: function alfresco_services_FormsRuntimeService__addConstraint(widget, constraint) {
         if (constraint.id === "MANDATORY")
         {
            widget.config.requirementConfig = {
               initialValue: true
            };
         }
         else if (constraint.id === "REGEX")
         {
            var regex = this.constraintMappings[constraint.validationHandler];
            if (regex)
            {
               if (!widget.config.validationConfig)
               {
                  widget.config.validationConfig = [];
               }
               widget.config.validationConfig.push({
                  validation: "regex",
                  regex: regex.pattern,
                  invertRule: !regex.match,
                  errorMessage: constraint.message
               });
            }
         }
         // TODO: Need to address these constraints !!!
         else if (constraint.id === "NUMBER") {}
         else if (constraint.id === "MINMAX") {}
         else if (constraint.id === "LIST") {}
         else if (constraint.id === "LENGTH") {}
      },

      /**
       * Adds options to the supplied widget. The options are derived from the "options" and
       * "optionSeparator" attributes of the "params" configuration for the control of the
       * supplied targetField.
       * 
       * @instance
       * @param {object} widget The widget to add option to
       * @param {object} targetField The data for the field to check for options data
       * @since 1.0.77
       */
      addOptions: function alfresco_services_FormsRuntimeService__addOptions(widget, targetField) {
         if (!widget.config.optionsConfig)
         {
            var options = lang.getObject("control.params.options", false, targetField);
            var optionSeparator = lang.getObject("control.params.optionSeparator", false, targetField);
            if (options && optionSeparator)
            {
               var optionsConfig = {
                  fixed: []
               };
               var optionsArray = options.split(optionSeparator);
               array.forEach(optionsArray, function(option) {
                  var optionComponents = option.split("|");
                  optionsConfig.fixed.push({
                     value: optionComponents[0],
                     label: optionComponents[optionComponents.length - 1]
                  });
               }, this);
               widget.config.optionsConfig = optionsConfig;
            }
         }
      },

      /**
       * 
       * 
       * @instance
       */
      onFormLoadFailure: function alfresco_services_FormsRuntimeService__onFormLoadFailure(/*jshint unused: false*/response, originalRequestConfig) {
         // TODO: Need to handle failures appropriately...
      }
   });
});