'use strict';

/**
 * smartfloformsapi
 */

/**
 * Represents remote SmartFlo™ Forms Service APIs.
 * It is expected that this class will be injected into an
 * Angular module as a service.
 * <br><br>
 * This AngularJS service is built upon the SmartFlo™ RESTful Service APIs for
 * handling tasks in the SmartFlo™ system. It is provided to remove 
 * some of the complexities entailed by using RESTful APIs.
 * @description <strong>Warning!</strong> The SmartFlo™ APIs assume 2 global 
 * variables are set in 
 * $rootScope prior to using the APIs:
 * <ol>
 * <li><code>$rootScope.credential</code></li> set implicitely by the AuthAPI 
 * class upon success of the authenticate method
 * <li><code>$rootScope.site</code></li> set by the calling application to the  
 * domain (e.g., www.smartflo.biz) or IP address of the target SmartFlo™ system, 
 * possibly when initializing the module that declares an API service
 * </ol>
 * @copyright © Chalex Corp. 2002 - 2017. All rights reserved.
 * @example 
 * //
 * // Warning! The global '$rootScope.site' field value must be set 
 * // to the target SmartFlo™ domain prior to using theSmartFlo™ APIs. In this 
 * // example it is set by the configuration block of the 'sftasklist' module.'
 * //
 * angular.module('sftasklist', [])
 *      // inject API as a service
 *      .service('SFformsApi', ['$rootScope', '$http', FormsAPI]) 
 *      .controller('SFtasklistCtrl', TaskListCtrl);
 *      .run(function ($rootScope, $location) { 
 *      // store the site for use by APIs
 *          $rootScope.site = $location.host();
 *          if ($location.port !== null & $location.port !== 80) {
 *          $rootScope.site += ':' + $location.port().toString();
 *     }
 * });
 * @constructor 
 * @param {object} $rootScope - The topmost parent scope in AngularJS.
 * @param {service} $http - The core AngularJS service that facilitates 
 * communication with the remote HTTP servers via the browser's XMLHttpRequest 
 * object or via JSONP.
 * @returns {class} A constructor that will be instantiated.
 */
class FormsAPI {
    constructor($rootScope, $http) {
        return {
            /** 
             * Retrieves a slice of the set of all tasks matching the query. 
             * @function 
             * @name getMatchingJobboardTasks
             * @description Retrieve a range (or page) of tasks. This may 
             * include tasks not assigned to the current user so their display 
             * should be guarded by membership in a SmartFlo™ group 
             * (e.g., Project Manager or Job Manager).
             * <br><br>
             * Tries to match specified query terms against various job data 
             * fields. Query terms are comma (<code>,</code>) separated. 
             * <br>
             * <or>
             * <li><code>job data</code></li>
             *    brand, category, client_company, distribution_channel, item_description, item_id, 
             *    product, project_id, task_id, task_name, project_manager, project_name, 
             *    workflow_id, workflow_definition, resource_group, status
             * <br><br>
             * <li><code>extended job metadata</code></li>
             *    account_name, art_due_date, creative_agency, customer_position, 
             *    file_destination, master_artwork_template, num_of_units, production_schedule, 
             *    program_owner, team, states_program, program_classification, 
             *    program_type, legal_owner, alliance_owner, prize_logic_owner, program_manager_owner
             * </ol>
             * @memberof JobsAPI.prototype
             * @param {object} params - An object providing the query terms, the
             * start position and page size for the set of tasks returned, and
             * a request that the query be expanded or not. In this example, 
             * ag-Grid (Copyright © 2017 ag-Grid Ltd, All rights reserved) is 
             * used to provide paging.
             * <pre><code>
             * var params = {
             *      tomatch: [comma separated list of query terms],
             *      startrow: gridparams.startRow,
             *      pagesize: gridparams.endRow - gridparams.startRow,
             *      expand: [true | false - whether to use terms as elements
             *      of an "OR" (expand === true) query or an
             *      "AND" (expand === false) query.
             * };
             * 
             * For non-expanding queries: all running or suspended job tasks in the pan-affiliate group for 
             * which one of the target columns matches ALL of the comma separated search terms.
             * 
             * For expanding queries: all running or suspended job tasks in the pan-affiliate group for 
             * which any of the target columns match ANY of the comma separated search terms.
             *
             * </pre></code>
             * @param {function} successHandler - A function that is 
             * called back upon success.
             * @param {function} [failureHandler] - An optional 
             * function that is called back upon failure. If none is specified,
             * the success callback is called with failure data. On failure 
             * however, the only valid fields returned are: 
             * <code>status, statusMessage, api</code> and <code>date</code>.
             * @throws (string) "missing.callback.exception"
             * @throws {} $http errors
             * @returns {object} A result object. 
             * <pre><code>
             * result {
             *      date: [time API call was completed],
             *      status: ['0' for success, otherwise failure],
             *      statusMessage: [message indicating reason for status value],
             *      api: [the API that was called],
             *      totalNumberOfTasks: [the total number of tasks assigned to the user],
             *      jobboard: [Array holding the current page of task data objects]
             * };
             * </pre></code>
             * @see {@link getTask} for a description of the task data.
             * @example 
             * $scope.datasourceCallback;
             * function createTasksDatasource() {
             *      var dataSource = {
             *          pageSize: $scope.pageSize,
             *          getRows: function (gridparams) {
             *              $scope.datasourceCallback = gridparams.successCallback;
             *              var params = {
             *                  startrow: gridparams.startRow,
             *                  pagesize: gridparams.endRow - gridparams.startRow
             *              };
             *              SFjobsApi.getMatchingJobboardTasks(param, getJobboardTasksHandler);
             *          }
             *      };
             *      $scope.gridOptions.api.setDatasource(dataSource);
             * }
             * 
             * function getJobboardTasksHandler(result) {
             *      if ('0' === result.status) {
             *          $scope.datasourceCallback(result.jobboard, result.totalNumberOfTasks);
             *          $scope.numTasks = result.totalNumberOfTasks;
             *          $scope.currentTasks = result.jobboard;
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            getMatchingJobboardTasks: function (params, successHandler, failureHandler) {
                // require a successHandler
                if (!successHandler) {
                    throw new Error("missing.callback.exception");
                }

                // if no failureHandler is defined, use successHandler as 
                // general callback
                if (!failureHandler) {
                    failureHandler = successHandler;
                }

                if (params.tomatch === undefined) {
                    params.tomatch = null;
                }
                var request = get('jobs/matches?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var taskdata = {};
                            taskdata.date = new Date();
                            /*if (jsonData.dam_service_response) { // from dam apis
                             taskdata.api = jsonData.dam_service_response.api;
                             taskdata.status = jsonData.dam_service_response.error_response.response_code;
                             taskdata.statusMessage = jsonData.dam_service_response.error_response.response_message;
                             } else */
                            if (jsonData.bpm_service_response) { // from bpm apis
                                taskdata.api = jsonData.bpm_service_response.api;
                                taskdata.status = jsonData.bpm_service_response.error_response.response_code;
                                taskdata.statusMessage = jsonData.bpm_service_response.error_response.response_message;
                                taskdata.totalNumberOfTasks = 0;
                            }

                            if ('0' === taskdata.status) {
                                taskdata.totalNumberOfTasks = getNumberOfTasks(jsonData);

                                var tasks = null;
                                if (jsonData.bpm_service_response.response.task_list && jsonData.bpm_service_response.response.task_list.task) {
                                    tasks = jsonData.bpm_service_response.response.task_list.task;
                                }

                                //X2JS sparsing creates single value as non-array so convert to Array
                                if (tasks && !Array.isArray(tasks)) {
                                    var tmp = [];
                                    tmp.push(tasks);
                                    tasks = tmp;
                                } else if (!tasks) {
                                    tasks = [];
                                }

                                //X2JS parsing needs fixing
                                fixTaskArrays(tasks);

                                // set performer to owner if task claimed
                                processPerformerList(tasks);

                                taskdata.jobboard = tasks;
                                successHandler(taskdata);
                            } else {
                                failureHandler(taskdata);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** 
             * Retrieves a given form definition created using the SmartFlo™
             * XMLFormBuilder tool. 
             * @function 
             * @name getXMlForm
             * @description Retrieves a given form definition created using 
             * the SmartFlo™ XMLFormBuilder tool and imbedded in a task for a 
             * running job. <br>
             * Note: Form definitions must be unique within a given job. 
             * Further, if the form has been previously saved, the most recent 
             * saved data initialize the current form field values.
             * @memberof FormsAPI.prototype
             * @param {object} params - An object providing jobid and task id 
             * of the currently selected task.
             * <pre><code>
             * var params = {
             *      jobid: [job ID of currently selected task],
             *      formid: formid [form ID]
             * };
             * </pre></code>
             * @param {function} successHandler - A function that is 
             * called back upon success.
             * @param {function} [failureHandler] - An optional 
             * function that is called back upon failure. If none is specified,
             * the success callback is called with failure data. On failure 
             * however, the only valid fields returned are: 
             * <code>status, statusMessage, api</code> and <code>date</code>.
             * @throws (string) "missing.callback.exception"
             * @throws {} $http errors
             * @returns {object} A result object. 
             *  <pre><code>
             *  result {
             *      date: [time API call was completed],
             *      status: ['0' for success, otherwise failure],
             *      statusMessage: [message indicating reason for status value],
             *      api: [the API that was called],
             *      jsonForm: {
             *          formfields: [], //array of form field definitions including Questionnaires
             *          formimages: [], //array of form images
             *          formlabels: []  // array of form labels
             *      }
             * };
             * 
             * </code></pre>
             * @example 
             * function getXMLForm(jobid, formid) {
             *      var params = {
             *          jobid: jobid, // job ID of currently selected task
             *          formid: formid: // form ID of currently selected task
             *      };
             *      SFformsApi.getXMlForm(params, handleGetXMlForm);
             * }
             *
             * function handleGetXMlForm(result) {
             *      if ('0' === result.status) {
             *          $scope.jsonForm = result.jsonForm;
             *          if ($scope.jsonForm.formlabels) {
             *              for (var i=0; i < $scope.jsonForm.formlabels.length; i++) {
             *                  $scope.jsonForm.formfields.push($scope.jsonForm.formlabels[i]);
             *              }
             *          }
             *          if ($scope.jsonForm.formimages) {
             *              for (var j=0; j < $scope.jsonForm.formimages.length; j++) {
             *                  $scope.jsonForm.formfields.push($scope.jsonForm.formimages[j]);
             *              }
             *          }
             *          SFStorage.saveCurrentXmlForm($scope.jsonForm);
             *          $location.path('/formstask');
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            getXMlForm: function (params, successHandler, failureHandler) {
                // require a successHandler
                if (!successHandler) {
                    throw new Error("missing.callback.exception");
                }

                // if no failureHandler is defined, use successHandler as 
                // general callback
                if (!failureHandler) {
                    failureHandler = successHandler;
                }
                var request = get('xmlforms?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var xmlformdata = {};
                            xmlformdata.date = new Date();
                            if (jsonData.bpm_service_response) { // from bpm apis
                                xmlformdata.api = jsonData.bpm_service_response.api;
                                xmlformdata.status = jsonData.bpm_service_response.error_response.response_code;
                                xmlformdata.statusMessage = jsonData.bpm_service_response.error_response.response_message;
                            }

                            if ('0' === xmlformdata.status) {
                                if (jsonData.bpm_service_response.response &&
                                        jsonData.bpm_service_response.response.jsonForm) {
                                    var jsonForm = JSON.parse(jsonData.bpm_service_response.response.jsonForm);
                                    xmlformdata.jsonForm = fixFormData(jsonForm.form);
                                } else {
                                    xmlformdata.jsonForm = null;
                                }
                                successHandler(xmlformdata);
                            } else {
                                failureHandler(xmlformdata);
                            }
                        },
                        function (errorInfo) {
                            throw Error(errorInfo.data);
                        });
            },
            /** 
             * Retrieves the most recent saved values for a given form in a 
             * given job.
             * @function 
             * @name getSavedXMlFormValues
             * @description Retrieves the most recent saved values for a given 
             * form in a given job.
             * @memberof FormsAPI.prototype
             * @param {object} params - An object providing jobid and task id 
             * of the currently selected task.
             * <pre><code>
             * var params = {
             *      jobid: [job ID of currently selected task],
             *      formid: formid [form ID]
             * };
             * </pre></code>
             * @param {function} successHandler - A function that is 
             * called back upon success.
             * @param {function} [failureHandler] - An optional 
             * function that is called back upon failure. If none is specified,
             * the success callback is called with failure data. On failure 
             * however, the only valid fields returned are: 
             * <code>status, statusMessage, api</code> and <code>date</code>.
             * @throws (string) "missing.callback.exception"
             * @throws {} $http errors
             * @returns {object} A result object. 
             *  <pre><code>
             *  result {
             *      date: [time API call was completed],
             *      status: ['0' for success, otherwise failure],
             *      statusMessage: [message indicating reason for status value],
             *      api: [the API that was called],
             *      savedFormData: {
             *          affiliate: [SmartFlo™ affiliate],
             *          jobid: [the job id],
             *          formid: [form ID],
             *          editedBy: [most recent editor of form values],
             *          editedOn: [date of most recent edit],
             *          fields: [array of field value objects]
             *      }
             *  };
             *  
             *  fieldValue {
             *     fieldid: [id of field],
             *     fieldname: [name of field],
             *     fieldvalue: [value of field],
             *     fieldlabel: [field prompt],
             *     fieldtype: [field type]
             *  };
             * 
             * </code></pre>
             * @example 
             * function getSavedXMlFormValues(jobid, formid) {
             *      var params = {
             *          jobid: jobid, // job ID of currently selected task
             *          formid: formid: // form ID of currently selected task
             *      };
             *      SFformsApi.getSavedXMlFormValues(params, handleGetSavedXMlFormValues);
             * }
             *
             * function handleGetSavedXMlFormValues(result) {
             *      if ('0' === result.status) {
             *          $scope.savedFormData = result.savedFormData;
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            getSavedXMlFormValues: function (params, successHandler, failureHandler) {
                // require a successHandler
                if (!successHandler) {
                    throw new Error("missing.callback.exception");
                }

                // if no failureHandler is defined, use successHandler as 
                // general callback
                if (!failureHandler) {
                    failureHandler = successHandler;
                }
                var request = get('xmlforms/savedvalues?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var xmlformdata = {};
                            xmlformdata.date = new Date();
                            if (jsonData.bpm_service_response) { // from bpm apis
                                xmlformdata.api = jsonData.bpm_service_response.api;
                                xmlformdata.status = jsonData.bpm_service_response.error_response.response_code;
                                xmlformdata.statusMessage = jsonData.bpm_service_response.error_response.response_message;
                            }

                            if ('0' === xmlformdata.status) {
                                if (jsonData.bpm_service_response.response &&
                                        jsonData.bpm_service_response.response.formdata)
                                {
                                    var savedData = JSON.parse(jsonData.bpm_service_response.response.formdata);
                                    if (savedData.form_input) {
                                        var savedFormData = {
                                            affiliate: savedData.form_input.affiliate,
                                            jobid: params.jobid,
                                            formid: savedData.form_input.formid,
                                            lastEditedOn: savedData.form_input.created,
                                            lastEditedBy: savedData.form_input.editedby ? savedData.form_input.editedby : 'editor.unknown',
                                            fields: savedData.form_input.field
                                        };
                                        xmlformdata.savedFormData = savedFormData;
                                    } else {
                                        xmlformdata.savedFormData = {};
                                    }
                                } else {
                                    xmlformdata.savedFormData = {};
                                }
                                successHandler(xmlformdata);
                            } else {
                                failureHandler(xmlformdata);
                            }
                        },
                        function (errorInfo) {
                            throw Error(errorInfo.data);
                        });
            },
            /** 
             * Retrieves a list of the form IDs of a job's completed forms. 
             * @function 
             * @name getXMlFormIDs
             * @description Retrieves a list of the form IDs of a given job's 
             * completed forms.
             * @memberof FormsAPI.prototype
             * @param {object} params - An object providing the jobid of the 
             * currently selected task.
             * <pre><code>
             * var params = {
             *      jobid: [job ID of currently selected task]
             * };
             * </pre></code>
             * @param {function} successHandler - A function that is 
             * called back upon success.
             * @param {function} [failureHandler] - An optional 
             * function that is called back upon failure. If none is specified,
             * the success callback is called with failure data. On failure 
             * however, the only valid fields returned are: 
             * <code>status, statusMessage, api</code> and <code>date</code>.
             * @throws (string) "missing.callback.exception"
             * @throws {} $http errors
             * @returns {object} A result object. 
             *  <pre><code>
             *  result {
             *      date: [time API call was completed],
             *      status: ['0' for success, otherwise failure],
             *      statusMessage: [message indicating reason for status value],
             *      api: [the API that was called],
             *      formids: [] //array of form ids
             * };
             * 
             * </code></pre>
             * @example 
             * function getXMLFormIDs(jobid) {
             *      var params = {
             *          jobid: jobid // job ID of currently selected task
             *      };
             *      SFformsApi.getXMlFormIDs(params, handleGetXMlFormIDs);
             * }
             *
             * function handleGetXMlFormIDs(result) {
             *      if ('0' === result.status) {
             *          $scope.formids = result.formids;
             *          $location.path('/formsdisplay');
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            getXMlFormIDs: function (params, successHandler, failureHandler) {
                // require a successHandler
                if (!successHandler) {
                    throw new Error("missing.callback.exception");
                }

                // if no failureHandler is defined, use successHandler as 
                // general callback
                if (!failureHandler) {
                    failureHandler = successHandler;
                }
                var request = get('xmlforms?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var xmlformdata = {};
                            xmlformdata.date = new Date();
                            if (jsonData.bpm_service_response) { // from bpm apis
                                xmlformdata.api = jsonData.bpm_service_response.api;
                                xmlformdata.status = jsonData.bpm_service_response.error_response.response_code;
                                xmlformdata.statusMessage = jsonData.bpm_service_response.error_response.response_message;
                            }

                            if ('0' === xmlformdata.status) {
                                if (jsonData.bpm_service_response.response &&
                                        jsonData.bpm_service_response.response.formid_list &&
                                        jsonData.bpm_service_response.response.formid_list.formid) {
                                    var formids = jsonData.bpm_service_response.response.formid_list.formid;
                                    if (!angular.isArray(formids)) {
                                        var arr = [];
                                        arr.push(formids);
                                        formids = arr;
                                    }
                                    formids.splice(0, 0, 'label.select.form');
                                    xmlformdata.formids = convertToNameValueList(formids);
                                } else {
                                    xmlformdata.formids = null;
                                }
                                successHandler(xmlformdata);
                            } else {
                                failureHandler(xmlformdata);
                            }
                        },
                        function (errorInfo) {
                            throw Error(errorInfo.data);
                        });
            },
            /** 
             * Posts a given task form with its current settings to the
             * SmartFlo™ system. 
             * @function 
             * @name postXMLForm
             * @description Posts a given task form with its current settings  
             * to the SmartFlo™ system. <br>
             * Note: Form definitions must be unique with a given job.
             * @memberof FormsAPI.prototype
             * @param {object} params - An object providing jobid and task id 
             * of the currently selected task.
             * <pre><code>
             * var params = {
             *      jobid: [job ID of currently selected task],
             *      taskid: [task ID of currently selected task],
             *      formid: formid [form ID],
             *      formdata: JSON.stringify($scope.formdata) [form model]
             * };
             * </pre></code>
             * @param {function} successHandler - A function that is 
             * called back upon success.
             * @param {function} [failureHandler] - An optional 
             * function that is called back upon failure. If none is specified,
             * the success callback is called with failure data. On failure 
             * however, the only valid fields returned are: 
             * <code>status, statusMessage, api</code> and <code>date</code>.
             * @throws (string) "missing.callback.exception"
             * @throws {} $http errors
             * @returns {object} A result object. 
             *  <pre><code>
             *  result {
             *      date: [time API call was completed],
             *      status: ['0' for success, otherwise failure],
             *      statusMessage: [message indicating reason for status value],
             *      api: [the API that was called]
             * };
             * 
             * </code></pre>
             * @example 
             * function getXMLForm(jobid, formid) {
             *      var params = {
             *          jobid: jobid, // job ID of currently selected task
             *          taskid: [task ID of currently selected task],
             *          formid: formid, // form ID of currently selected task
             *          formdata: JSON.stringify($scope.formdata) [form model]
             *      };
             *      SFformsApi.getXMlForm(params, handleGetXMlForm);
             * }
             *
             * function handleGetXMlForm(result) {
             *      if ('0' === result.status) {
             *          $scope.errorCode = 0;
             *          $scope.feedbackMessage = $translate.instant('label.feedback.form.saved');
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            postXMLForm: function (params, successHandler, failureHandler) {
                // require a successHandler
                if (!successHandler) {
                    throw new Error("missing.callback.exception");
                }

                // if no failureHandler is defined, use successHandler as 
                // general callback
                if (!failureHandler) {
                    failureHandler = successHandler;
                }
                var request = post('xmlforms?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var xmlformdata = {};
                            xmlformdata.date = new Date();
                            if (jsonData.bpm_service_response) { // from bpm apis
                                xmlformdata.api = jsonData.bpm_service_response.api;
                                xmlformdata.status = jsonData.bpm_service_response.error_response.response_code;
                                xmlformdata.statusMessage = jsonData.bpm_service_response.error_response.response_message;
                            }

                            if ('0' === xmlformdata.status) {
                                if (jsonData.bpm_service_response.response &&
                                        jsonData.bpm_service_response.response.form_input) {
                                    xmlformdata.formInput = jsonData.bpm_service_response.response.form_input;
                                } else {
                                    xmlformdata.formInput = "";
                                }
                                successHandler(xmlformdata);
                            } else {
                                failureHandler(xmlformdata);
                            }
                        },
                        function (errorInfo) {
                            throw Error(errorInfo.data);
                        });
            },
            /** 
             * Renders SmartFlo™ form field definitions 
             * (returned by <i>getXMLForm</i>) into Formly field definitions. 
             * @function 
             * @name renderFields
             * @description  Renders SmartFlo™ form field definitions  
             * (returned by <i>getXMLForm</i>) into Formly field definitions.
             * @memberof FormsAPI.prototype
             * @param {array} formfields - An array of SmartFlo™ field 
             * definitions.
             * @param {object} applicationStyles - An  object containing
             * optional .css styles that will be applied on a field type basis.
             * <pre><code>
             * As a conveniance, SmartFlo™ provides a JavaScript class that will
             * initialize all field types with a common CSS class selector
             * string:
             * <br>var applicationStyles = new SFApplicationStyles('col-xs-6');<br>
             * applicationStyles = {
             *      TextInput: applicationStyles,
             *      TextArea: applicationStyles,
             *      CheckBox: applicationStyles,
             *      DateField: applicationStyles,
             *      ComboBox: applicationStyles,
             *      VRadioButtonGroup: applicationStyles,
             *      HRadioButtonGroup: applicationStyles,
             *      NumericStepper: applicationStyles,
             *      Label: applicationStyles,
             *      Image: applicationStyles,
             *      RBQuestionnaire: applicationStyles,
             *      CBQuestionnaire: applicationStyles
             * };
             * </code></pre>
             * @param {function} [$translate] - Optionally, the $translate 
             * function from the angular-translate AngularJS module. If passed,
             * <i>renderFields</i> will call <i>$translate.instant()</i> on
             * all defined field prompts for display purposes.
             * @example 
             * JavaScript Fragment:
             * if ($scope.taskFields) {
             *      var applicationStyles = new SFApplicationStyles('col-xs-6');
             *      applicationStyles.Label = '';
             *      applicationStyles.Image = '';
             *      $scope.taskFields = SFformsApi.renderFields($scope.taskFields, applicationStyles, $translate);
             * }
             * 
             * Formly HTML:
             * <form novalidate name="vm.taskform>
             *      <formly-form model="formdata" fields="taskFields" form="vm.taskform">
             *      </formly-form>
             * </form>
             */
            renderFields: function (formfields, applicationStyles, $translate) {
                var fields = [];
                var rf = null;
                for (var i = 0; i < formfields.length; i++) {
                    var f = formfields[i];
                    switch (f.inputtype) {
                        case 'TextInput':
                            rf = renderTextField(f, applicationStyles.TextInput, $translate);
                            break;
                        case 'TextArea':
                            rf = renderTextAreaField(f, applicationStyles.TextArea, $translate);
                            break;
                        case 'CheckBox':
                            rf = renderCheckBoxField(f, applicationStyles.CheckBox, $translate);
                            break;
                        case 'DateField':
                            rf = renderDateField(f, applicationStyles.DateField, $translate);
                            break;
                        case 'ComboBox':
                            rf = renderComboBox(f, applicationStyles.ComboBox, $translate);
                            break;
                        case 'RadioButtonGroup':
                            rf = renderVRadioButtons(f, applicationStyles.VRadioButtonGroup, $translate);
                            break;
                        case 'HRadioButtonGroup':
                            rf = renderHRadioButtons(f, applicationStyles.HRadioButtonGroup, $translate);
                            break;
                        case 'NumericStepper':
                            rf = renderNumericStepper(f, applicationStyles.NumericStepper, $translate);
                            break;
                        case 'Questionnaire':
                            if (f.formitemattributes.answertype === 'radio') {
                                rf = renderRadioButtonQuestionnaire(f, applicationStyles.RBQuestionnaire, $translate);
                            } else {
                                rf = renderCheckBoxQuestionnaire(f, applicationStyles.RBQuestionnaire, $translate);
                            }
                            break;
                        case 'Label':
                            rf = renderLabel(f, applicationStyles.Label);
                            break;
                        case 'Image':
                            rf = renderImage(f, applicationStyles.Image);
                            break;
                        default:
                            break;
                    }
                    if (rf !== null) {
                        fields[i] = rf;
                        rf = null;
                    }
                }
                return fields.sort(fieldsSort);
            }
        };

        function renderTextField(f, styles, $translate) {
            var tf = {};
            tf.key = f.fieldid;
            tf.name = f.fieldname;
            tf.type = 'input';
            tf.defaultValue = f.defaultValue;
            var className = ' sffield formlyTextInput';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            tf.className = className;
            tf.templateOptions = {
                // input attributes
                placeholder: f.inputattributes.text,
                width: f.inputattributes.width,
                // form item attributes
                label: renderPrompt(f.formitemattributes.label, $translate),
                x: f.formitemattributes.x,
                y: f.formitemattributes.y,
                required: f.formitemattributes.required
            };
            tf.modelOptions = {
                debounce: {
                    default: 2000,
                    blur: 0
                },
                updateOn: 'default blur'
            };
            if (f.inputattributes.displayAsPassword &&
                    f.inputattributes.displayAsPassword === true)
            {
                tf.templateOptions.type = 'password';
            }
            tf.validation = {
                messages: {
                    required: 'to.label + " is required"'
                }
            };
            return tf;
        }

        function renderTextAreaField(f, styles, $translate) {
            var taf = {};
            taf.key = f.fieldid;
            taf.name = f.fieldname;
            taf.type = 'textarea';
            taf.defaultValue = f.defaultValue;
            var className = ' sffield formlyTextArea';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            taf.className = className;
            taf.templateOptions = {
                // input attributes
                rows: 10,
                placeholder: f.inputattributes.text,
                width: f.inputattributes.width,
                // form item attributes
                label: renderPrompt(f.formitemattributes.label, $translate),
                x: f.formitemattributes.x,
                y: f.formitemattributes.y,
                required: f.formitemattributes.required
            };
            taf.modelOptions = {
                debounce: {
                    default: 2000,
                    blur: 0
                },
                updateOn: 'default blur'
            };
            taf.validation = {
                messages: {
                    required: 'to.label + " is required"'
                }
            };
            return taf;
        }

        function renderCheckBoxField(f, styles, $translate) {
            var cf = {};
            cf.key = f.fieldid;
            cf.name = f.fieldname;
            cf.type = 'checkbox';
            cf.defaultValue = renderDefaultValue('CheckBox', f.defaultValue, f.inputattributes.selected);
            var className = ' sffield formlyCheckBox';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            cf.className = className;
            cf.templateOptions = {
                // input attributes
                width: f.inputattributes.width,
                // form item attributes
                label: renderPrompt(f.formitemattributes.label, $translate),
                x: f.formitemattributes.x,
                y: f.formitemattributes.y,
                required: f.formitemattributes.required
            };
            cf.validation = {
                messages: {
                    required: 'to.label + " is required"'
                }
            };
            return cf;
        }

        function renderDateField(f, styles, $translate) {
            var df = {};
            df.key = f.fieldid;
            df.name = f.fieldname;
            df.type = 'sfdatepicker';
            df.defaultValue = renderDefaultValue('DateField', f.defaultValue, new Date().toDateString());
            var className = ' sffield formlyDateField';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            df.className = className;
            df.templateOptions = {
                type: "text",
                datepickerPopup: renderDateFormat(f.inputattributes.formatString),
                datepickerOptions: {
                    format: renderDateFormat(f.inputattributes.formatString),
                    initDate: new Date()
                },
                // input attributes
                width: f.inputattributes.width,
                // form item attributes
                label: renderPrompt(f.formitemattributes.label, $translate),
                x: f.formitemattributes.x,
                y: f.formitemattributes.y,
                required: f.formitemattributes.required
            };
            df.validation = {
                messages: {
                    required: 'to.label + " is required"'
                }
            };
            return df;
        }

        function renderComboBox(f, styles, $translate) {
            var cbf = {};
            cbf.key = f.fieldid;
            cbf.name = f.fieldname;
            cbf.type = 'select';
            cbf.defaultValue = renderDefaultValue('ComboBox', f.defaultValue, f.options.option[0].value);
            var className = ' sffield formlyComboBox';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            cbf.className = className;
            cbf.templateOptions = {
                options: renderOptions(f.options.option),
                // input attributes
                width: f.inputattributes.width,
                // form item attributes
                label: renderPrompt(f.formitemattributes.label, $translate),
                x: f.formitemattributes.x,
                y: f.formitemattributes.y,
                required: f.formitemattributes.required
            };
            cbf.validation = {
                messages: {
                    required: 'to.label + " is required"'
                }
            };
            return cbf;
        }

        function renderVRadioButtons(f, styles, $translate) {
            var rbf = {};
            rbf.key = f.fieldid;
            rbf.name = f.fieldname;
            rbf.type = 'radio';
            rbf.defaultValue = renderDefaultValue('RadioButtonGroup', f.defaultValue, findSelectedOption(f.options.option));
            var className = ' sffield formlyVRadioButton';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            rbf.className = className;
            rbf.templateOptions = {
                keyProp: "name",
                valueProp: "value",
                options: renderOptions(f.options.option),
                width: f.inputattributes.width,
                // form item attributes
                label: renderPrompt(f.formitemattributes.label, $translate),
                x: f.formitemattributes.x,
                y: f.formitemattributes.y,
                required: f.formitemattributes.required
            };
            rbf.validation = {
                messages: {
                    required: 'to.label + " is required"'
                }
            };
            return rbf;
        }

        function renderHRadioButtons(f, styles, $translate) {
            var rbf = renderVRadioButtons(f, $translate);
            rbf.templateOptions.inline = true;
            rbf.defaultValue = renderDefaultValue('HRadioButtonGroup', f.defaultValue, findSelectedOption(f.options.option));
            var className = ' sffield formlyHRadioButton';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            rbf.className = className;
            return rbf;
        }

        function renderNumericStepper(f, styles, $translate) {
            var nsf = {};
            nsf.key = f.fieldid;
            nsf.name = f.fieldname;
            nsf.type = 'input';
            nsf.defaultValue = renderDefaultValue('NumericStepper', f.defaultValue, f.inputattributes.value);
            var className = ' sffield formlyNumericStepper';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            nsf.className = className;
            nsf.templateOptions = {
                type: 'number',
                // input attributes
                step: f.inputattributes.stepSize,
                min: f.inputattributes.minimum,
                max: f.inputattributes.maximum,
                width: f.inputattributes.width,
                // form item attributes
                label: renderPrompt(f.formitemattributes.label, $translate),
                x: f.formitemattributes.x,
                y: f.formitemattributes.y,
                required: f.formitemattributes.required
            };
            nsf.validation = {
                messages: {
                    required: 'to.label + " is required"'
                }
            };
            return nsf;
        }

        function renderRadioButtonQuestionnaire(f, styles, $translate) {
            var rbqf = {};
            rbqf.key = f.fieldid;
            rbqf.name = f.fieldname;
            rbqf.type = 'sfrbquestionnaire';
            var className = ' sffield formlyRBQuestionnaire';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            rbqf.className = className;

            if (f.defaultValues) {
                rbqf.defaultValue = renderQuestionnaireDefaults('radiobutton', f.defaultValues);
            }
            var formborder = true;
            if (f.formitemattributes.gridborder) {
                formborder = f.formitemattributes.gridborder;
            }
            var instruction = "";
            if (f.formitemattributes.instruction) {
                instruction = f.formitemattributes.instruction;
            }
            var fieldname = "";
            if (f.fieldname) {
                fieldname = $translate.instant(f.fieldname);
            }
            rbqf.templateOptions = {
                type: 'radiobutton',
                // input attributes
                // form item attributes
                colHeaders: f.formitemattributes.colHeaders,
                rowHeaders: f.formitemattributes.rowHeaders,
                instruction: instruction,
                formborder: formborder,
                label: fieldname,
                width: f.formitemattributes.width,
                x: f.formitemattributes.x,
                y: f.formitemattributes.y,
                required: f.formitemattributes.required
            };
            return rbqf;
        }

        function renderCheckBoxQuestionnaire(f, styles, $translate) {
            var cbqf = {};
            cbqf.key = f.fieldid;
            cbqf.name = f.fieldname;
            cbqf.type = 'sfcbquestionnaire';
            var className = ' sffield formlyCBQuestionnaire';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            cbqf.className = className;

            if (f.defaultValues) {
                cbqf.defaultValue = renderQuestionnaireDefaults('checkbox', f.defaultValues);
            }
            var formborder = true;
            if (f.formitemattributes.gridborder) {
                formborder = f.formitemattributes.gridborder;
            }
            var instruction = "";
            if (f.formitemattributes.instruction) {
                instruction = f.formitemattributes.instruction;
            }
            var fieldname = "";
            if (f.fieldname) {
                fieldname = $translate.instant(f.fieldname) + ":";
            }
            cbqf.templateOptions = {
                type: 'checkbox',
                // input attributes
                // form item attributes
                colHeaders: f.formitemattributes.colHeaders,
                rowHeaders: f.formitemattributes.rowHeaders,
                instruction: instruction,
                formborder: formborder,
                label: fieldname,
                width: f.formitemattributes.width,
                x: f.formitemattributes.x,
                y: f.formitemattributes.y,
                reqCount: 0,
                numFields: 0,
                inited: false,
                errorClass: "",
                required: f.formitemattributes.required
            };
            cbqf.validators = {
                required: function ($viewValue, $modelValue, scope) {
                    return $viewValue | $modelValue;
                }
            };
            cbqf.validation = {
                messages: {
                    required: 'to.label + " is required"'
                }
            };
            return cbqf;
        }

        function renderLabel(f, styles) {
            var lf = {};
            lf.key = f.fieldid;
            lf.type = 'sflabel';
            var className = ' sffield formlyLabel';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            lf.className = className;
            lf.templateOptions = {
                label: f.labelattributes.text,
                fontSize: f.labelattributes.fontSize,
                fontWeight: f.labelattributes.fontWeight,
                textDecoration: f.labelattributes.textDecoration,
                x: f.labelattributes.x,
                y: f.labelattributes.y
            };
            return lf;
        }

        function renderImage(f, styles) {
            var imgf = {};
            imgf.key = f.fieldid;
            imgf.type = 'sfimage';
            var className = ' sffield formlyImage';
            if (styles && styles.length > 0) {
                className = styles + className;
            }
            imgf.className = className;
            imgf.templateOptions = {
                src: f.formitemattributes.source,
                width: f.formitemattributes.width,
                height: f.formitemattributes.height,
                x: f.formitemattributes.x,
                y: f.formitemattributes.y
            };
            return imgf;
        }

        function findSelectedOption(options) {
            var selectedValue = '';
            for (var i = 0; i < options.length; i++) {
                var opt = options[i];
                if (opt.selected === true) {
                    selectedValue = opt.value;
                }
            }
            return selectedValue;
        }

        function renderQuestionnaireDefaults(type, defaultValues) {
            if (defaultValues) {
                var model = {};
                if (!angular.isArray(defaultValues)) {
                    var arr = [];
                    arr.push(defaultValues);
                    defaultValues = arr;
                }
                for (var i = 0; i < defaultValues.length; i++) {
                    var val = defaultValues[i].split('-*X*-');
                    var row = val[0];
                    var col = val[1];
                    switch (type) {
                        case 'radiobutton':
                            if (!model[row]) {
                                model[row] = {};
                            }
                            model[row] = col;
                            break;
                        case 'checkbox':
                            if (!model[row]) {
                                model[row] = {};
                            }
                            if (!model[row][col]) {
                                model[row][col] = {};
                            }
                            model[row][col] = true;
                            break;
                        default:
                            break;
                    }
                }
            }
            return model;
        }

        function renderDefaultValue(type, value, fallback) {
            if (!value || value === undefined) {
                value = fallback;
            }
            var defaultValue = value;
            if (value) {
                switch (type) {
                    case 'DateField':
                        defaultValue = new Date(value);
                        break;
                    default:
                        defaultValue = value;
                        break;
                }
            }
            return defaultValue;
        }

        function renderPrompt(label, $translate) {
            var prompt = label;
            if ($translate) {
                prompt = $translate.instant(prompt);
            }
            return prompt;
        }

        function renderDateFormat(format) {
            var fmt = 'yyyy.MM.dd';
            if (format && format.length > 0) {
                fmt = format;
            }
            return fmt;
        }

        function renderOptions(options) {
            var opts = [];
            if (options && !angular.isArray(options)) {
                var o2 = [];
                o2[0] = options;
                options = o2;
            }
            if (options) {
                for (var i = 0; i < options.length; i++) {
                    var opt = {
                        name: options[i].content,
                        value: options[i].value
                    };
                    opts.push(opt);
                }
            }
            return opts;
        }

        function fieldsSort(a, b) {
            var idA = a.key; // ignore upper and lowercase
            var idB = b.key; // ignore upper and lowercase
            var val = naturalSorter(idA, idB);
            return (val);
        }

        function naturalSorter(as, bs) {
            var a, b, a1, b1, i = 0, n, L,
                    rx = /(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.\D+)|(\.$)/g;
            if (as === bs)
                return 0;
            a = as.toLowerCase().match(rx);
            b = bs.toLowerCase().match(rx);
            L = a.length;
            while (i < L) {
                if (!b[i])
                    return 1;
                a1 = a[i],
                        b1 = b[i++];
                if (a1 !== b1) {
                    n = a1 - b1;
                    if (!isNaN(n))
                        return n;
                    return a1 > b1 ? 1 : -1;
                }
            }
            return b[i] ? -1 : 0;
        }

        function get(path, param) {
            return request("GET", path, param);
        }

        function post(path, param, data) {
            return request("POST", path, param, data);
        }

        function put(path, param, data) {
            return request("PUT", path, param, data);
        }

        function del(path, param) {
            return request("DELETE", path, param);
        }

        function request(verb, path, param, data) {
            var site = $rootScope.site;
            var token = null;
            if (validData($rootScope.credential) && validData($rootScope.credential.token)) {
                token = $rootScope.credential.token;
            }
            var req = {
                method: verb,
                url: url(site, token, path, param),
                headers: {
                    'Authorization': getAuthHeader()
                },
                data: validData(data) ? data : null
            };
            return $http(req);
        }

        function url(site, token, path, param) {
            var reqUrl = 'https://' + site + '/api/' + token + '/bpm/';
            if (validData(path)) {
                reqUrl += path;
            }

            if (validData(param)) {
                reqUrl += $.param(param);
            }
            return reqUrl;
        }

        function validData(data) {
            return data !== undefined && data !== null;
        }

        function getAuthHeader() {
            return "";
        }

        function convertToNameValueList(list) {
            if (list) {
                if (!Array.isArray(list)) {
                    var temp = list;
                    var list = [];
                    list.push(temp);
                }
                
                var lst = [];
                for (var i = 0; i < list.length; i++) {
                    var obj = {
                        name: list[i],
                        value: list[i]
                    };
                    lst.push(obj);
                }
            }
            return lst;
        }

        function fixQuestionnaires(questionnaires) {
            if (!angular.isArray(questionnaires)) {
                var arr1 = [];
                arr1.push(questionnaires);
                questionnaires = arr1;
            }

            for (var i = 0; i < questionnaires.length; i++) {
                questionnaires[i].formitemattributes.colHeaders = convertToNameValueList(questionnaires[i].formitemattributes.colheaders.colheader);
                questionnaires[i].formitemattributes.rowHeaders = convertToNameValueList(questionnaires[i].formitemattributes.rowheaders.rowheader);
                delete questionnaires[i].formitemattributes.colheaders;
                delete questionnaires[i].formitemattributes.rowheaders;
            }
            return questionnaires;
        }

        function fixFormData(formdata) {
            if (formdata.formfield) {
                if (!angular.isArray(formdata.formfield)) {
                    var arr0 = [];
                    arr0.push(formdata.formfield);
                    formdata.formfield = arr0;
                }
            } else {
                formdata.formfield = [];
            }
            formdata.formfields = formdata.formfield;
            delete formdata.formfield;

            if (formdata.questionnaire) {
                formdata.questionnaires = fixQuestionnaires(formdata.questionnaire);
                delete formdata.questionnaire;

                for (var i = 0; i < formdata.questionnaires.length; i++) {
                    formdata.formfields.push(formdata.questionnaires[i]);
                }
            }

            if (formdata.formimage) {
                if (!angular.isArray(formdata.formimage)) {
                    var arr2 = [];
                    arr2.push(formdata.formimage);
                    formdata.formimage = arr2;
                }

            }
            formdata.formimages = formdata.formimage;
            delete formdata.formimage;

            if (formdata.formlabel) {
                if (!angular.isArray(formdata.formlabel)) {
                    var arr3 = [];
                    arr3.push(formdata.formlabel);
                    formdata.formlabel = arr3;
                }
            }
            formdata.formlabels = formdata.formlabel;
            delete formdata.formlabel;
            return formdata;
        }
    }
}

class SFApplicationStyles {
    constructor(applicationStyles) {
        if (!applicationStyles) {
            applicationStyles = '';
        }
        return {
            TextInput: applicationStyles,
            TextArea: applicationStyles,
            CheckBox: applicationStyles,
            DateField: applicationStyles,
            ComboBox: applicationStyles,
            VRadioButtonGroup: applicationStyles,
            HRadioButtonGroup: applicationStyles,
            NumericStepper: applicationStyles,
            Label: applicationStyles,
            Image: applicationStyles,
            RBQuestionnaire: applicationStyles,
            CBQuestionnaire: applicationStyles
        };
    }
}

/**
 * A SmartFlo™ angular module providing custom form field types used by the 
 * SmartFlo™ forms service.
 * @module sfCustomFormFields
 * @description A SmartFlo™ angular module providing custom form field types 
 * used by the SmartFlo™ forms service. It uses Formly Forms to render
 * XML form definitions created by the SmartFlo™ XMLFormBuilder tool into HTML5 
 * form fields. The custom fields provided are:
 * <ol>
 * <li><code>sfdatepicker</code></li> a date picker field
 * <li><code>sfcbquestionnaire</code></li> a two-dimensional grid of checkboxes 
 * where each row poses a question and each column provides an answer
 * <li><code>sfrbquestionnaire</code></li> a two-dimensional grid of radio 
 * buttons where each row poses a question and each column provides an answer
 * <li><code>sflabel</code></li> a label field
 * <li><code>sfimage</code></li> an image field
 * </ol>
 * SmartFlo™ requires inclusion of this module to support these field types. 
 * The module itself requires HTML templates that it expects to be located in 
 * the directory path "views/templates", starting from the application root. So:        
 * <pre><code>                         
 *                          [root of app]                         
 *                               |
 *                             views
 *                               |
 *                           templates
 *                               |
 *          -------------------------------------------
 *          |                    |                    |
 * CheckBoxQuestionnaire.html    |      RadioButtonQuestionnaire.html
 *                               |
 *                        DatePicker.html
 * </code></pre>
 * 
 * <b>Example Questionnaires:</b>
 * <br>
 * <img src="https://www.smartflo.biz/3/?1Oh9a" alt="SmartFlo™ Questionnaire Field Types" >
 * <br>
 * @copyright © Chalex Corp. 2002 - 2017. All rights reserved.
 */
(function () {

    'use strict';
    angular.module('sfCustomFormFields', ['formly', 'formlyBootstrap', 'ui.bootstrap'])
            .run(function (formlyConfig) {
                var datepickerattributes = [
                    'date-disabled',
                    'custom-class',
                    'show-weeks',
                    'starting-day',
                    'init-date',
                    'min-mode',
                    'max-mode',
                    'format-day',
                    'format-month',
                    'format-year',
                    'format-day-header',
                    'format-day-title',
                    'format-month-title',
                    'year-range',
                    'shortcut-propagation',
                    'datepicker-popup',
                    'show-button-bar',
                    'current-text',
                    'clear-text',
                    'close-text',
                    'close-on-date-selection',
                    'datepicker-append-to-body'
                ];
                var datepickerbindings = [
                    'datepicker-mode',
                    'min-date',
                    'max-date'
                ];
                var ngModelDatePickerAttrs = {};
                angular.forEach(datepickerattributes, function (attr) {
                    ngModelDatePickerAttrs[camelize(attr)] = {attribute: attr};
                });
                angular.forEach(datepickerbindings, function (binding) {
                    ngModelDatePickerAttrs[camelize(binding)] = {bound: binding};
                });
                formlyConfig.setType({
                    name: 'sfdatepicker',
                    templateUrl: 'views/templates/DatePicker.html',
                    wrapper: ['bootstrapLabel', 'bootstrapHasError'],
                    defaultOptions: {
                        ngModelAttrs: ngModelDatePickerAttrs,
                        templateOptions: {
                            datepickerOptions: {
                                format: 'MM.dd.yyyy',
                                initDate: new Date()
                            }
                        }
                    },
                    controller: ['$scope', function ($scope) {
                            $scope.datepicker = {};
                            $scope.datepicker.opened = false;
                            $scope.datepicker.open = function ($event) {
                                $scope.datepicker.opened = !$scope.datepicker.opened;
                            };
                        }]
                });

                formlyConfig.setType({
                    name: 'sfcbquestionnaire',
                    templateUrl: 'views/templates/CheckBoxQuestionnaire.html',
                    wrapper: ['bootstrapLabel', 'bootstrapHasError'],
                    defaultOptions: {
                        templateOptions: {
                            type: 'checkbox',
                            instruction: 'Default Instruction',
                            colHeaders: [],
                            rowHeaders: []
                        }
                    }
                });
                formlyConfig.setType({
                    name: 'sfrbquestionnaire',
                    templateUrl: 'views/templates/RadioButtonQuestionnaire.html',
                    wrapper: ['bootstrapLabel', 'bootstrapHasError'],
                    defaultOptions: {
                        templateOptions: {
                            type: 'radiobutton',
                            instruction: 'Default Instruction',
                            colHeaders: [],
                            rowHeaders: []
                        }
                    }
                });
                formlyConfig.setType({
                    name: 'sflabel',
                    template: '<formly-transclude></formly-transclude><span style="white-space:nowrap;font-size:{{to.fontSize}};font-weight:{{to.fontWeight}};text-decoration:{{to.textDecoration}};">{{to.label}}</span>'
                });
                formlyConfig.setType({
                    name: 'sfimage',
                    template: '<formly-transclude></formly-transclude><img style="white-space:nowrap;" src="{{to.src}}" width="{{to.width}}" height="{{to.height}}">',
                    defaultOptions: {
                        templateOptions: {
                            source: 'https://'
                        }
                    }
                });
                function camelize(string) {
                    string = string.replace(/[\-_\s]+(.)?/g, function (match, chr) {
                        return chr ? chr.toUpperCase() : '';
                    });
                    // Ensure 1st char is always lowercase
                    return string.replace(/^([A-Z])/, function (match, chr) {
                        return chr ? chr.toLowerCase() : '';
                    });
                }
            });
})();