/* xeslint-disable */ /////////////////////////////// // gzform // // provides form services and utilities // validation services // dirty and change tracking // and also general error display in forms //probably should be broken up more // All translation keys for validation *MUST* be fetched prior to this being used as it assumes all keys are fetched first // Add any new keys used to the block in translation.js=>commonKeysEditForm import Vue from "vue"; let triggeringChange = false; function isEmpty(o) { if (typeof o == "number" && o == 0) { return false; } return !o; } //////////////////////////////////// // isInt value?? // //FROM HERE: https://stackoverflow.com/a/14794066/8939 //fast test if is an integer: function isInt(value) { let x; if (isNaN(value)) { return false; } x = parseFloat(value); return (x | 0) === x; } //////////////////////////////////// // isNumber // //FROM HERE: https://stackoverflow.com/a/1830632/8939 function isNumber(n) { return !isNaN(parseFloat(n)) && isFinite(n); } //////////////////////////////////// // Get control from ref // function getControl(vm, ref) { let ctrl = vm.$refs[ref]; //I don't think this is reauired anymore // if (ctrl === undefined) { // //it's either a sub field in custom fields component or it's a coding error // let customFields = vm.$refs["customFields"]; // if (customFields !== undefined) { // ctrl = customFields.$refs[ref]; // } // } return ctrl; } //////////////////////////////////// // Get value from control // function getControlValue(ctrl) { let value = ctrl.value; return value; } //////////////////////////////////// // Get field name from control // function getControlLabel(ctrl) { if (ctrl.label == undefined) { return "UNKNOWN CONTROL"; } else { return ctrl.label; } } ///////////////////////////////////////// // Get errors for a particular field // from server error collection // function getErrorsForField(vm, ref) { let ret = []; if (ref == "errorbox") { ret = window.$gz._.filter(vm.formState.serverError.details, function(o) { return !o.target; }); } else { ret = window.$gz._.filter(vm.formState.serverError.details, function(o) { if (!o.target) { return false; } //server error fields are capitalized //client field names are generally lower case except for custom fields //so we need to normalize them all to lower case to match //they will always differ by more than case so this is fine return o.target.toLowerCase() == ref.toLowerCase(); }); } return ret; } /////////////////////////////// // ERROR BOX ERRORS // gathers any messages for error box on form which is the generic catch all for non field specific errors from server // and application itself locally function getErrorBoxErrors(vm, errs) { let hasErrors = false; let ret = ""; if (errs.length > 0) { hasErrors = true; //loop array and append each error to a return string for (let i = 0; i < errs.length; i++) { ret += errs[i] + "\r\n"; } } //any application errors? if (vm.formState.appError) { hasErrors = true; ret += vm.formState.appError + "\r\n----------\r\n" + ret; } if (!hasErrors) { return null; } else { return ret; } } export default { /////////////////////////////// // REQUIRED // required(vm, ref) { if (vm.formState.loading) { return true; } let ctrl = getControl(vm, ref); if (typeof ctrl == "undefined") { return true; } let value = getControlValue(ctrl); if (!isEmpty(value)) { return true; } // "ErrorRequiredFieldEmpty": "{0} is a required field. Please enter a value for {0}", let err = vm.$ay.t("ErrorRequiredFieldEmpty"); let fieldName = getControlLabel(ctrl); err = window.$gz._.replace(err, "{0}", fieldName); //lodash replace only replaces first instance so need to do it twice err = window.$gz._.replace(err, "{0}", fieldName); //Update the form status this.setFormState({ vm: vm, valid: false }); return err; }, /////////////////////////////// // MAXLENGTH // maxLength(vm, ref, max) { if (vm.formState.loading) { return true; } let ctrl = getControl(vm, ref); if (typeof ctrl == "undefined") { return true; } let value = getControlValue(ctrl); if (isEmpty(value)) { return true; } if (value.length > max) { //get the translated rule text // "ErrorFieldLengthExceeded": "{0} can not exceed {1} characters.", let err = vm.$ay.t("ErrorFieldLengthExceeded"); let fieldName = getControlLabel(ctrl); err = window.$gz._.replace(err, "{0}", fieldName); err = window.$gz._.replace(err, "{1}", max); //Update the form status this.setFormState({ vm: vm, valid: false }); return err; } else { return true; } }, /////////////////////////////// // MAX 255 // max255(vm, ref) { if (vm.formState.loading) { return true; } return this.maxLength(vm, ref, 255); }, /////////////////////////////// // DatePrecedence // (start date must precede end date, however if both are empty then that's ok) // datePrecedence(vm, refStart, refEnd) { if (vm.formState.loading) { return true; } let ctrlStart = getControl(vm, refStart); if (typeof ctrlStart == "undefined") { return true; } let ctrlEnd = getControl(vm, refEnd); if (typeof ctrlEnd == "undefined") { return true; } let valueStart = getControlValue(ctrlStart); if (isEmpty(valueStart)) { return true; } let valueEnd = getControlValue(ctrlEnd); if (isEmpty(valueEnd)) { return true; } valueStart = window.$gz.DateTime.fromISO(valueStart); valueEnd = window.$gz.DateTime.fromISO(valueEnd); // if either is not valid. //moment.github.io/luxon/docs/manual/validity.html https: if (!valueStart.isValid || !valueEnd.isValid) { return true; } if (valueStart > valueEnd) { // "ErrorStartDateAfterEndDate": "Start date must be earlier than stop / end date", let err = vm.$ay.t("ErrorStartDateAfterEndDate"); //Update the form status this.setFormState({ vm: vm, valid: false }); return err; } else { return true; } }, /////////////////////////////// // Confirm password // (two fields must match) // confirmMatch(vm, refFirst, refSecond) { if (vm.formState.loading) { return true; } let ctrlFirst = getControl(vm, refFirst); if (typeof ctrlFirst == "undefined") { return true; } let ctrlSecond = getControl(vm, refSecond); if (typeof ctrlSecond == "undefined") { return true; } let valueFirst = getControlValue(ctrlFirst); let valueSecond = getControlValue(ctrlSecond); if (valueFirst != valueSecond) { let err = vm.$ay.t("ErrorNoMatch"); //Update the form status this.setFormState({ vm: vm, valid: false }); return err; } else { return true; } }, /////////////////////////////// // INTEGER IS VALID // integerValid(vm, ref) { if (vm.formState.loading) { return true; } let ctrl = getControl(vm, ref); if (typeof ctrl == "undefined") { return true; } //DEBUG //logControl("integerValid", ctrl, ref); let value = getControlValue(ctrl); if (isEmpty(value)) { return true; } if (isInt(value)) { return true; } // "ErrorFieldValueNotInteger": "Value must be an integer" let err = vm.$ay.t("ErrorFieldValueNotInteger"); //Update the form status this.setFormState({ vm: vm, valid: false }); return err; }, /////////////////////////////// // DECIMAL // Basically anything that can be a number is valid // decimalValid(vm, ref) { if (vm.formState.loading) { return true; } //TODO: Handle commas and spaces in numbers //as per window.$gz.translation rules for numbers let ctrl = getControl(vm, ref); if (typeof ctrl == "undefined") { return true; } //DEBUG //logControl("decimalValid", ctrl, ref); let value = getControlValue(ctrl); if (isEmpty(value)) { return true; } if (isNumber(value)) { return true; } // "ErrorFieldValueNotDecimal": "Value must be a number" let err = vm.$ay.t("ErrorFieldValueNotDecimal"); //Update the form status this.setFormState({ vm: vm, valid: false }); return err; }, /////////////////////////////// // USER REQUIRED FIELDS // (Fields defined by AyaNova users as required on form that are not stock required already) // (was using this in testing on widget form notes field but not sure where else it's applicable) userRequiredFields(vm, ref, formCustomTemplateFieldName) { if (vm.formState.loading) { return true; } let template = window.$gz.store.state.formCustomTemplate[vm.formCustomTemplateKey]; if (template === undefined) { return true; } //See if control formCustomTemplateFieldName is in server required fields collection //this is a collection of both custom field definitions and standard form fields that are required //since all names are unique can just filter out the one we need by name which will inherently ignore custom fields by default //_https://lodash.com/docs#find let templateItem = window.$gz._.find(template, [ "fld", formCustomTemplateFieldName ]); //templateItem.required if (templateItem === undefined || templateItem.required !== true) { return true; } let ctrl = getControl(vm, ref); if (typeof ctrl == "undefined") { return true; } let value = getControlValue(ctrl); if (!isEmpty(value)) { return true; } // "ErrorRequiredFieldEmpty": "{0} is a required field. Please enter a value for {0}", let err = vm.$ay.t("ErrorRequiredFieldEmpty"); let fieldName = getControlLabel(ctrl); err = window.$gz._.replace(err, "{0}", fieldName); //lodash replace only replaces first instance so need to do it twice err = window.$gz._.replace(err, "{0}", fieldName); //Update the form status this.setFormState({ vm: vm, valid: false }); return err; }, /////////////////////////////// // CUSTOMFIELDS // For now the only rule is that they can be required or not // customFieldsCheck(vm, templateItem, subvm, fieldName) { //templateItem sample // dataKey: "c2" // fld: "WidgetCustom2" // hide: "false" // required: true // type: "text" if (vm.formState.loading) { return true; } if (templateItem.required !== true) { return true; } let value = subvm.GetValueForField(templateItem.dataKey); if (!isEmpty(value)) { return true; } //It's empty and it's required so return error // "ErrorRequiredFieldEmpty": "{0} is a required field. Please enter a value for {0}", let err = vm.$ay.t("ErrorRequiredFieldEmpty"); //let fieldName = getControlLabel(ctrl); err = window.$gz._.replace(err, "{0}", fieldName); //lodash replace only replaces first instance so need to do it twice err = window.$gz._.replace(err, "{0}", fieldName); //Update the form status this.setFormState({ vm: vm, valid: false }); return err; }, /////////////////////////////// // SERVER ERRORS // Process and return server errors if any for form and field specified // serverErrors(vm, ref) { //CHECK PREREQUISITES IN DEV MODE TO ENSURE FORM ISN"T MISSING NEEDED DATA ATTRIBUTES ETC if (vm.$ay.dev) { //make sure formState.serverErrors is defined on data if (!window.$gz._.has(vm, "formState.serverError")) { throw "DEV ERROR gzform::formState.serverErrors -> formState.serverError seems to be missing from form's vue data object"; } //make sure formState.appError is defined on data if (!window.$gz._.has(vm, "formState.appError")) { throw "DEV ERROR gzform::formState.serverErrors -> formState.appError seems to be missing from form's vue data object"; } //make sure formState.errorBoxMessage is defined on data if (!window.$gz._.has(vm, "formState.errorBoxMessage")) { throw "DEV ERROR gzform::formState.serverErrors -> formState.errorBoxMessage seems to be missing from form's vue data object"; } //ensure the error returned is in an expected format to catch coding errors at the server end if (!window.$gz._.isEmpty(vm.formState.serverError)) { //Make sure there is an error code if there is an error collection if (!vm.formState.serverError.code) { throw "DEV ERROR gzform::formState.serverErrors -> server returned error without code"; } } } let ret = []; //check for errors if we have any errors if (!window.$gz._.isEmpty(vm.formState.serverError)) { //First let's get the top level error code let apiErrorCode = parseInt(vm.formState.serverError.code); //Not all server errors mean the form is invalid, exceptions here let formValid = false; /* These errors are not the user's fault and no changes to the form are required so they may be temporary and user should be able to retry save API_CLOSED = 2000, API_OPS_ONLY = 2001, API_SERVER_ERROR = 2002, */ switch (apiErrorCode) { case 2000: case 2001: case 2002: formValid = true; //we came here because the user saved because the form was valid so it's safe to set that the same again break; default: formValid = false; } //GENERAL ERROR if (ref == "errorbox") { //Add any general errors to ret let err = vm.$ay.t("ErrorAPI" + apiErrorCode.toString()); if (vm.formState.serverError.message) { err = err + "\r\n" + vm.formState.serverError.message; } //Update the form status this.setFormState({ vm: vm, valid: formValid }); ret.push(err); } //DETAIL ERRORS //{"error":{"code":"2200","details":[{"message":"Exception: Error converting value \"\" to type 'AyaNova.Biz.AUTHORIZATION_ROLES'. Path 'roles', line 1, position 141.","target":"roles","error":"2203"}],"message":"Object did not pass validation"}} //Specific field validation errors are in an array in "details" key if (!window.$gz._.isEmpty(vm.formState.serverError.details)) { //See if this key is in the details array let errorsForField = getErrorsForField(vm, ref); if (errorsForField.length > 0) { //iterate the errorsForField object and add each to return array of errors window.$gz._.each(errorsForField, function(ve) { let fldErr = ""; let fldErrorCode = parseInt(ve.error); fldErr = vm.$ay.t("ErrorAPI" + fldErrorCode.toString()) + " [" + ve.error + "]"; if (ve.message) { fldErr += ' - "' + ve.message + '"'; } ret.push(fldErr); }); //Update the form status this.setFormState({ vm: vm, valid: false }); return ret; } } } //default if no error message to display return ret; }, /////////////////////////////// // ShowMe // (returns false if the field has been set to hidden by the user in the formcustomtemplate) // NOTE: that in a form this should only be used with non stock-required fields, if they are already required they cannot be hidden // showMe(vm, formCustomTemplateFieldName) { let template = window.$gz.store.state.formCustomTemplate[vm.formCustomTemplateKey]; if (template === undefined) { return true; } //See if control templateFieldName is in server required fields collection //this is a collection of both custom field definitions and standard form fields that are required //since all names are unique can just filter out the one we need by name which will inherently ignore custom fields by default //_https://lodash.com/docs#find let templateItem = window.$gz._.find(template, [ "fld", formCustomTemplateFieldName ]); if (templateItem === undefined || templateItem.hide !== true) { return true; } //Only here if we have a record in the custom template for this particular field and it's set to hide:true return false; }, /////////////////////////////// // ClearformState.serverErrors // Clear all server errors and app errors and ensure error box doesn't show // deleteAllErrorBoxErrors(vm) { //clear all keys from server error window.$gz.util.removeAllPropertiesFromObject(vm.formState.serverError); //clear app errors vm.formState.appError = null; //clear out actual message box display vm.formState.errorBoxMessage = null; //Update the form status this.setFormState({ vm: vm, valid: true }); }, /////////////////////////////// // setErrorBoxErrors // Gather server errors and set the appropriate keys // setErrorBoxErrors(vm) { let errs = this.serverErrors(vm, "errorbox"); let ret = getErrorBoxErrors(vm, errs); vm.formState.errorBoxMessage = ret; }, /////////////////////////////// // On fieldValueChanged handler // This is required so that server errors can be cleared when input is changed // fieldValueChanged(vm, ref) { if (triggeringChange || vm.formState.loading) { return; } //If ref appears in the formState.serverErrors details collection, remove each one let m = window.$gz._.remove(vm.formState.serverError.details, function(o) { if (!o.target) { return false; } return o.target.toLowerCase() == ref; }); //If there are no more errors in details then remove the whole thing as it's no longer required if ( vm.formState.serverError.details && vm.formState.serverError.details.length < 1 ) { if (vm.formState.serverError.code == "2200") { //clear all keys from server error window.$gz.util.removeAllPropertiesFromObject(vm.formState.serverError); } } //Clear out old validation display in form by forcing the control's data to change //I tried calling form validate and reset and all that bullshit but it did nothing //probably because it has safeguards to prevent excess validation, this works though so far //I added the triggering change guard but it actually doesn't seem to be required here, more investigation is required if (m.length > 0) { triggeringChange = true; let val = vm.obj[ref]; vm.obj[ref] = null; vm.obj[ref] = val; triggeringChange = false; } //Update the form status this.setFormState({ vm: vm, dirty: true, valid: vm.$refs.form.validate() }); }, //////////////////////////////////// // set calling form Valid state // // {vm:vm,dirty:bool | undefined, // valid:bool | undefined, // loading:bool | undefined} // setFormState(newState) { //this returns a promise so any function that needs to wait for this can utilize that return Vue.nextTick(function() { if (newState.valid != undefined) { newState.vm.formState.valid = newState.valid; } if (newState.dirty != undefined) { newState.vm.formState.dirty = newState.dirty; } if (newState.loading != undefined) { newState.vm.formState.loading = newState.loading; } if (newState.readOnly != undefined) { newState.vm.formState.readOnly = newState.readOnly; } }); }, //////////////////////////////////// // Get form settings // for form specified or empty object if there is none // EAch form is responsible for what it stores and how it initializes, this just provides that // the form does the actual work of what settings it requires // Form settings are temp and saved, saved ones go into vuex and localstorage and persist a refresh // and temporary ones are stored in session storage and don't persist a refresh // getFormSettings(formKey) { let formSettings = { temp: JSON.parse(sessionStorage.getItem(formKey)), saved: window.$gz.store.state.formSettings[formKey] }; return formSettings; }, //////////////////////////////////// // Set form settings // for form key specified // requires object with one or both keys {temp:{...tempformsettings...},saved:{...persistedformsettings...}} // setFormSettings(formKey, formSettings) { if (window.$gz.dev) { if (!formSettings.saved && !formSettings.temp) { throw "gzform:setFormSettings - saved AND temp keys are both missing from form data!"; } } if (formSettings.saved) { window.$gz.store.commit("setFormSettings", { formKey: formKey, formSettings: formSettings.saved }); } if (formSettings.temp) { sessionStorage.setItem(formKey, JSON.stringify(formSettings.temp)); } }, //////////////////////////////////// // Get last report used from form settings // getLastReport(formKey) { let fs = window.$gz.store.state.formSettings[formKey]; if (fs == null || fs.lastReport == null) { return null; } return fs.lastReport; }, //////////////////////////////////// // Set last report used in form settings // setLastReport(formKey, reportSelected) { let fs = window.$gz.store.state.formSettings[formKey]; if (fs == null || fs.lastReport == null) { fs = {}; } fs.lastReport = reportSelected; window.$gz.store.commit("setFormSettings", { formKey: formKey, formSettings: fs }); }, //////////////////////////////////// // Add no selection item // Used by forms that need the option of an unselected // item in a pick list // addNoSelectionItem(listArray) { if (listArray == undefined || listArray == null) { listArray = []; } listArray.unshift({ name: "-", id: 0 }); } };