From df2e17b1ab66b65a3df81d3cd8da5b60cc507bb2 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Wed, 27 Mar 2019 23:15:34 +0000 Subject: [PATCH] --- ayanova/devdocs/todo.txt | 32 +++---- ayanova/src/api/apiutil.js | 57 +++++++++++ ayanova/src/api/gzvalidate.js | 40 ++++++++ ayanova/src/views/inventory-widget-edit.vue | 101 +++++++++++++++++--- 4 files changed, 203 insertions(+), 27 deletions(-) diff --git a/ayanova/devdocs/todo.txt b/ayanova/devdocs/todo.txt index f305c6d7..386a4568 100644 --- a/ayanova/devdocs/todo.txt +++ b/ayanova/devdocs/todo.txt @@ -14,27 +14,16 @@ TODO CLIENT STUFF TODO NEXT +End to end action + - The faster I get to a fully working basic level like being able to do data entry and submit to server new or updated records, the faster I can flesh out all the rest + - Code the submit data to server route for update + - Code for new record to the server -GZ VALIDATE - - Add rules for each field type validation - - Is there anything pulled into the validate code directly that is already available off the "v" variable? - - localization etc +Simulate server broken rules so can integrate into ui and rule system -Going to need to handle localized numeric entry formats - - -DATETIME - Validation - - after much fuckery it's becoming clear that between the localization requirements and the complexity of server broken rules coming back from the api that I should just use my own validation code - - See here: https://vuetifyjs.com/en/components/forms#form - - And here: https://vuetifyjs.com/en/components/text-fields#custom-validation - - What is needed is a class that returns an array of functions that can be passed to vuetify "rules" property of components - On change each rule is called in turn and if one returns false or a string then it's in an error state - I also need to add that it scans and or keeps a collection of broken rules that come back from the server and each component checks a rule that checks in turn for server broken rules in last api call - - And my rules object needs to be localized so I guess it should be aware of the locale object and work with it as required - - Should it lazy load error translations? (In as batchy a way as possible?) - - Or, perhaps it's more convenient to cache all possible regular form entry errors since there isn't really that many, perhaps less than a dozen when it comes down to it - My rules object needs to be coded so I can easily specify a collection of rules appropriate to a form field - So, for example let's say I have a date input, if I commonly need one to be required and be after or before another field then I should have a single combined rule of RequiredAndAfterTarget and this method is aggregates both the required and the After rules into a single collection returned @@ -46,9 +35,16 @@ DON'T code the user options with the currency symbol etc until after it's all be Locale should fetch those settings the first time it sees they are not present so that they are refreshed upon use and are not stored in localstorage (or should they be? anyway, can work that out later) + + Other fields that are locale dependent DateTime field next + + + +FIXES REQUIRED + - API get code is incorrectly dealing with expired bearer cert, a 401 is returned and it tries to parse the result as if it succeeded when it really should trigger a login process - Time zone offset mismatch warning needs expansion, it should only prompt a few times (maybe or find a way to deal with this) and it should offer to change it at the server automatically - Localize time zone mismatch warning @@ -160,6 +156,10 @@ Make all fields work according to specs below - What to do if they edit? Refresh the list but keep the same page location? +TESTING TODO +Localized input and parsing and validation + - Going to need to handle localized numeric entry formats + - Deal with this once form is in good shape and worth testing with different locales ----------------- TODO AFTER CLIENT block ABOVE: diff --git a/ayanova/src/api/apiutil.js b/ayanova/src/api/apiutil.js index 7b2ff381..899e4aa2 100644 --- a/ayanova/src/api/apiutil.js +++ b/ayanova/src/api/apiutil.js @@ -93,6 +93,14 @@ export default { body: JSON.stringify(data) }; }, + fetchPutOptions(data) { + return { + method: "put", + mode: "cors", + headers: this.postAuthorizedHeaders(), + body: JSON.stringify(data) + }; + }, fetchGetOptions() { /* GET WITH AUTH */ return { @@ -134,6 +142,9 @@ export default { } return store.state.apiUrl + apiPath; }, + ///////////////////////////// + // ENCODE QUERY STRING + // buildQuery(obj, sep, eq, name) { sep = sep || "&"; eq = eq || "="; @@ -166,6 +177,9 @@ export default { encodeURIComponent(stringifyPrimitive(obj)) ); }, + /////////////////////////////////// + // GET DATA FROM API SERVER + // get(route) { var that = this; return new Promise(function(resolve, reject) { @@ -195,6 +209,49 @@ export default { } }); }); + }, + /////////////////////////////////// + // POST / PUT DATA TO API SERVER + // + upsert(route, data) { + var that = this; + return new Promise(function(resolve, reject) { + //determine if this is a new or existing record + var fetchOptions = undefined; + if (data.concurrencyToken) { + fetchOptions = that.fetchPutOptions(data); + } else { + fetchOptions = that.fetchPostOptions(data); + } + fetch(that.APIUrl(route), fetchOptions) + .then(that.status) + .then(that.json) + .then(response => { + //Note: response.error indicates there is an error, however this is not an unusual condition + //it could be validation errors or other general error so we need to treat it here like it's normal + //and let the caller deal with it appropriately + resolve(response); + }) + .catch(function(error) { + //fundamental error, can't proceed with this call + + var errorMessage = + "API error: UPSERT route =" + route + ", message =" + error.message; + store.commit("logItem", errorMessage); + + if (error.message && error.message.includes("401")) { + store.commit( + "logItem", + "User is not authorized, redirecting to login" + ); + auth.logout(); + router.push("/login"); + } else { + //alert("Error: " + errorMessage); + reject(error); + } + }); + }); } //new functions above here diff --git a/ayanova/src/api/gzvalidate.js b/ayanova/src/api/gzvalidate.js index d6888671..34b753cc 100644 --- a/ayanova/src/api/gzvalidate.js +++ b/ayanova/src/api/gzvalidate.js @@ -75,6 +75,46 @@ function getControlLabel(ctrl) { } export default { + /////////////////////////////// + // SERVER ERRORS + // + Server(v, ref) { + + + //check if any errors and short circuit if none + + //check if this control is mentioned at all and short circuit return false if not + + var ctrl = getControl(v, ref); + if(typeof ctrl == 'undefined'){ + return false; + } + + //It IS in the list of error controls: + + //check if this control has been changed at all, I guess being here might mean it has? + //If this control is dirty / was changed from the value that was in it when the server returned the error then remove the error for this control from the server error list and return false + //ELSE if this control is unchanged / not dirty + //return the error(s) properly localized + + + //IF user modifies field in any way then this rule must be cleared + + //Some rules are for the entire object so...??? + //maybe need a form control that is just about rules or something + + return false; + + + + // // "ErrorRequiredFieldEmpty": "{0} is a required field. Please enter a value for {0}", + // var err = locale.get("ErrorRequiredFieldEmpty"); + // var fieldName = getControlLabel(ctrl); + // err = _.replace(err, "{0}", fieldName); + // //lodash replace only replaces first instance so need to do it twice + // err = _.replace(err, "{0}", fieldName); + // return err; + }, /////////////////////////////// // REQUIRED // diff --git a/ayanova/src/views/inventory-widget-edit.vue b/ayanova/src/views/inventory-widget-edit.vue index 5bf85c6b..76f4f1b3 100644 --- a/ayanova/src/views/inventory-widget-edit.vue +++ b/ayanova/src/views/inventory-widget-edit.vue @@ -1,7 +1,7 @@ @@ -133,10 +143,27 @@ export default { data() { return { obj: {}, + serverErrors: {}, formReady: false }; }, methods: { + getErrorCount() { + return 3; + }, + canHasServerError(fieldName) { + if (fieldName == "roles") { + return ["This is an error"]; + } + }, + validate() { + // this.$refs.form.resetValidation(); + // this.$refs.form.validate(); + //test to manually insert an error into the field like a server validation error would need to do + //UPDATE: this cannot work because you need to bind to error-messages, not set it directly like here + // this.$refs.roles["error-messages"] = ["This is an error!"]; + // this.$refs.roles.error = true; + }, getDataFromApi() { var url = "Widget/" + this.$route.params.id; this.$gzapi.get(url).then(res => { @@ -145,9 +172,61 @@ export default { }, submit() { // debugger; - //this.$validator.validateAll(); - this.$refs.form.validate(); - } + //Submit the form data to the api + + //Check for broken rules, do not submit with broken rules + //No broken rules then: + //Gather up the form data into a submitable object (can I just submit the existing object??) + //Post it + //Update the local object with the returned result + //Check the return object for broken rules + var that = this; + var url = "Widget/" + this.$route.params.id; + this.$gzapi + .upsert(url, this.obj) + .then(res => { + if (res.error) { + debugger; + that.serverErrors = res.error; + that.$refs.form.resetValidation(); + that.$refs.form.validate(); + //example error when submit when there are no roles set at all (blank) + //{"error":{"code":"2200","details":[{"code":"2200","message":"","target":"roles","error":"VALIDATION_FAILED"}],"message":"Object did not pass validation"}} + //todo: integrate validation error into form so user can see it + //User can only submit if no existing errors so should be starting with a clean error free form and then will see validation errors under appropriate fields + //if a user makes a change to a control then that control being changed should remove the rule from the server until the next submit happens + } else { + //Logic for detecing if a post or put: if id then it was a post, if no id then it was a put + if (res.id) { + //Handle "post" of new record + that.obj = res.data; + } else { + //Handle "put" of an existing record + that.obj.concurrencyToken = res.data.concurrencyToken; + } + } + }) + .catch(function(error) { + //we should be here if it was a gross error of some kind not a mild one like validation but more like if the server doesn't exist or something I guess + + console.log(error); + alert("Houston, we have a problem"); + }); + + //example from login form + // if (this.input.username != "" && this.input.password != "") { + // auth + // .authenticate(this.input.username, this.input.password) + // .then(() => { + // this.$router.replace({ name: "home" }); + // }) + // .catch(function(error) { + // /* xeslint-disable-next-line */ + // //console.log(error); + // alert("login failed: " + error); + // }); + // } + } //end of submit() } };