Best practice for form validation - validation

What are the best form validation practice for Vue? I've been using simple if tree in a method for validation disabled attribute of the send button but even with just two fields it may already be many cases to validate and given I have different forms I have to copypaste a lot
Here is method I use for one of modal windows:
isCryptoWithdrawalPossible() {
if (this.sendWalletModal.amount || this.sendWalletModal.address) {
if (this.sendWalletModal.amount && !this.sendWalletModal.address) {
this.sendWalletModal.error = this.$t('wallets.wallets.needAddress');
return false;
} else if (!this.sendWalletModal.amount && this.sendWalletModal.address) {
this.sendWalletModal.error = this.$t('wallets.wallets.needAmount')
return false;
}
if (this.sendWalletModal.amount < this.sendWalletModal.method.minAmount) {
this.sendWalletModal.error = this.$t('wallets.wallets.overLimitMinAmount', [this.sendWalletModal.method.minAmount]);
return false;
}
else if (this.sendWalletModal.amount > this.sendWalletModal.method.maxAmount) {
this.sendWalletModal.error = this.$t('wallets.wallets.overLimitMaxAmount', [this.sendWalletModal.method.maxAmount]);
return false;
}
else if (this.sendWalletModal.amount > this.filteredWallets.find( el => el.currencyTitle == this.sendWalletModal.currency).avaliableFunds - this.calculateSendFee(this.sendWalletModal)) {
this.sendWalletModal.error = this.$t('wallets.wallets.insufficientBalance')
return false;
}
else {
this.sendWalletModal.error = '';
return true;
}
} else {
this.sendWalletModal.error = '';
return false;
}
},

Check Vuelidate if you're looking for a third party lib to handle validations simply

Update Jan 2020: here is a nice overview of vuelidate - which is kinda compared to vee-validate (i would like to have had someone very familar with vee-validate show some more of its features but as an intro to vuelidate I liked the presentation). https://www.vuemastery.com/conferences/connect-tech-2019/vue-form-validation
Vuelidate on github: (https://github.com/vuelidate/vuelidate). Until now vee-validate has been better supported, but perhaps vuelidate have themselves better organised having been under transition for a while.
Also you can take a look at this link: https://laracasts.com/discuss/channels/vue/vue-validator-vs-vee-validate-vs
We use vee-validate where I work, vee-validate's home page is here: https://baianat.github.io/vee-validate/ .
It has a nice example sandbox here: https://baianat.github.io/vee-validate/examples/ .
I would recommend taking a look at the flags example in the codesandbox to see how vee-validate tracks changes on fields that it is validating.
It uses date-fns as it's default date library (which may be a concern for you if you are using moment, but I use both date-fns and moment dates with vee-validate (I use the "custom rules" feature for the moment dates)).
Once vee-validate is applied to your various form fields you can check to see if any of the fields have changed by calling a function as such:
hasChanged: function() {
return Object.keys(this.fields).some(key => this.fields[key].changed);
}
I have had issues with vee-validate, especially around dates, but it appears to be one of the better validation libraries out there for vue -and it appears to me that it is currently easier to get answers if you run into problems(this opinion will be biased because I have used vee-validate as against other libraries). One of the issues with vee-validate appears to be that if you change a field and then change it back to its original value then the field is still marked as changed -so you have to track initial values and current values yourself. But in many form scenarios you have to manually track hasChanged yourself anyway, for example if you decide that viewing a form changes the status of the data (from not viewed to viewed for example) then you will need to manually record the change of a "myFormViewed" variable which is unrelated-to/outside-of the form fields.

Related

How to restrict SlickGrid to make a API call, while clicking or changing compound filters?

I have a SlickGrid Table, in which there are compound filters, currently when i try to change the compound filter (lets say from Equal To to Less Than), then it makes an API call.
I don't want to make an API call, how do i achieve this?
I searched in slickgrid docs, but couldn't find any property(if it is available).
Image
Please note that I'm the author of Angular-Slickgrid
So I looked at the problem you're having and it seems like a valid problem to look into, I agree that for some filters like the Compound Date Filter Operator we shouldn't query right away, that is after changing a the operator dropdown without providing a date. So, for that reason I am adding a new grid option skipCompoundOperatorFilterWithNullInput which will avoid triggering a filter change (it will also avoid querying the backend when implemented) when we first change the operator dropdown without providing a date being entered.
Note that this new option will only be available with Angular-Slickgrid v5.1.0+ (via this PR, now supports this and it will only be enabled by default on the Compound Date Filter (any other filters will have to explicitly enable this new flag either via grid option or via a column definition).
What if I cannot upgrade to 5.1.0? Are there any other ways of dealing with this?
Yes, it's just a bit more involving dealing with this though, it however requires a lot more work from your side. The information you need to know is that nearly every piece of code from Angular-Slickgrid and Slickgrid-Universal are protected TypeScript classes and functions which mean that you can simply use TypeScript to extends any of them. Let's take for example the CompoundDateFilter class, we could extend it this way to skip the callback triggering without a date provided (this._currentDate)
import { CompoundDateFilter, OperatorString } from '#slickgrid-universal/common';
export class CustomCompoundDateFilter extends CompoundDateFilter {
protected onTriggerEvent(e: Event | undefined) {
if (this._clearFilterTriggered) {
this.callback(e, { columnDef: this.columnDef, clearFilterTriggered: this._clearFilterTriggered, shouldTriggerQuery: this._shouldTriggerQuery });
this._filterElm.classList.remove('filled');
} else {
const selectedOperator = this._selectOperatorElm.value as OperatorString;
(this._currentValue) ? this._filterElm.classList.add('filled') : this._filterElm.classList.remove('filled');
// -- NEW CODE BELOW -- (to skip triggering callback on undefined date)
// when changing compound operator, we don't want to trigger the filter callback unless the date input is also provided
if (this._currentDate !== undefined) {
this.callback(e, { columnDef: this.columnDef, searchTerms: (this._currentValue ? [this._currentValue] : null), operator: selectedOperator || '', shouldTriggerQuery: this._shouldTriggerQuery });
}
}
this._clearFilterTriggered = false;
this._shouldTriggerQuery = true;
}
}
then use this new custom filter class in your column definitions
import { CustomCompoundDateFilter } from './custom-compoundDateFilter';
initGrid() {
this.columnDefinitions = [{
id: 'start', name: 'Start', field: 'start',
filterable: true, filter: { model: CustomCompoundDateFilter },
}];
}
and there you have it, below is a proof that it is working since I changed the operator and as you can see below this action is no longer resulting in 0 row returned. However if I had done the inverse, which is to input the date but without an operator, it would have execute the filtering because "no operator" is defaulting to the "equal to" operator.

how the nativescript radlist view load on demand works

This might not be the question but it was the list of doubts which comes when learning native script from scratch.
I had a 1000 or more list of data stored in data table. know i want to display it on a list view but i don't want to read all the data at once. because i have images stored in other directory and want to read that also. So, for 20 to 30 data's the performance is quite good. but for 1000 data it is taking more than 15 minutes to read the data as well as images associated with it. since i'm storing some high quality images.
Therefore i decided to read only 20 data's with their respective images. and display it on list. know when user reaches the 15th data of the list. i decided to read 10 more data from the server.
know when i search this i came across "RadListView Load on Demand".
then i just looked at the code below.
public addMoreItemsFromSource(chunkSize: number) {
let newItems = this._sourceDataItems.splice(0, chunkSize);
this.dataItems.push(newItems);
}
public onLoadMoreItemsRequested(args: LoadOnDemandListViewEventData) {
const that = new WeakRef(this);
const listView: RadListView = args.object;
if (this._sourceDataItems.length > 0) {
setTimeout(function () {
that.get().addMoreItemsFromSource(2);
listView.notifyLoadOnDemandFinished();
}, 1500);
args.returnValue = true;
} else {
args.returnValue = false;
listView.notifyLoadOnDemandFinished(true);
}
}
In nativescript if i want to access binding element xml element. i must use observables in viewmodel or exports.com_name on associated js file.
but in this example it is started with public..! how to use this in javascript.
what is new WeakRef(this) ?
why it is needed ?
how to identify user has scrolled to 15 data, as i want to load more data when he came at 15th data.
after getting data how to update array of list and show it in listview ?
Finally i just want to know how to use load on demand
i tried to create a playground sample of what i have tried but it is giving error. it cannot found module of radlistview.
Remember i'm a fresher So, kindly keep this in mind when answering. thank you,
please modify the question if you feel it is not upto standards.
you can check the updated answer here
https://play.nativescript.org/?template=play-js&id=1Xireo
TypeScript to JavaScript
You may use any TypeScript compiler to convert the source code to JavaScript. There are even online compilers like the official TypeScript Playground for instance.
In my opinion, it's hard to expect ES5 examples any more. ES6-9 introduced a lot of new features that makes JavaScript development much more easier and TypeScript takes JavaScript to next level, interpreter to compiler.
To answer your question, you will use the prototype chain to define methods on your class in ES5.
YourClass.prototype.addMoreItemsFromSource = function (chunkSize) {
var newItems = this._sourceDataItems.splice(0, chunkSize);
this.dataItems.push(newItems);
};
YourClass.prototype.onLoadMoreItemsRequested = (args) {
var that = new WeakRef(this);
var listView = args.object;
if (this._sourceDataItems.length > 0) {
setTimeout(function () {
that.get().addMoreItemsFromSource(2);
listView.notifyLoadOnDemandFinished();
}, 1500);
args.returnValue = true;
} else {
args.returnValue = false;
listView.notifyLoadOnDemandFinished(true);
}
}
If you are using fromObject syntax for your Observable, then these functions can be passed inside
addMoreItemsFromSource: function (chunkSize) {
....
};
WeakRef: It helps managing your memory effiencetly by keeping a loose reference to the target, read more on docs.
How to load more:
If you set loadOnDemandMode to Auto then loadMoreDataRequested event will be triggered whenever user reaches the end of scrolling.
loadOnDemandBufferSize decides how many items before the end of scroll the event should be triggered.
Read more on docs.
How to update the array:
That's exactly what showcased in addMoreItemsFromSource function. Use .push(item) on the ObservableArray that is linked to your list view.

Ember-validation how to implement lazy validation

I am using ember-cli:2.5.0 and ember-validations:v2.0.0-alpha.5
In my ember-component i have a validation which is running automatically for each change in a attribute but i want to run this validation only if i call "validate()" method in technical term call validation lazily.
Please find the below code samples,
import Ember from 'ember';
import EmberValidations, { validator } from 'ember-validations';
export default Ember.Component.extend(EmberValidations, {
didReceiveAttrs() {
this.set('newBook', this._bookModel().create());
},
_bookModel(data = {}) {
return Ember.Object.extend(EmberValidations, {
bookVersion: null,
isEditable: false,
validationActive: false,
validations: {
bookVersion: {
inline: validator(function() {
if(this.validationActive){ //Here this.validationActive always return undefined
var version = this.model.get('bookVersion') || "",
message = [];
if (Ember.isEmpty(bookVersion)) {
message.push("Book Version is mandatory!!!");
}
if (message.length > 0) {
return message.join(',');
}
}
})
}
}
}, data);
}
});
actions: {
this.get('newBook').set("validationActive",true);
this.get('newBook').validate().then(() => {
//Do the action
}
}
I want the above validation to run only calling "this.get('newBook').validate()". I am entirely new to ember so down-voter please put your comments before down-voting for others kindly let me know for any more code samples.
Your help should be appreciable.
The addon you are using for validations ("ember-validations") is a very popular one and its documentation is pretty well when compared to others. If you look at the documentation there is a part named "Conditional Validators" in documentation. You can use a boolean property to control when the validation is to be performed.
You can see an illustration of what I mean in the following twiddle. I have created a simple validation within the application controller for user's name. The name field must have a length of at least 5 and the validation is to be performed only if the condition validationActive is true. Initially; the condition is false; which means validator did not work and isValid property of Controller (which is inherited from EmberValidations Mixin) is true. If you toggle the property with the button provided; the validation will run (since the condition is now set to true; hence validation is triggered) and isValid will return to false. If you change the value; the validation result will change appropriately with respect to the value of user's name. If you toggle the condition once again to set it to false; then isValid will become true no matter what the valie of user's name is.
I hope this gives you an insight about how to control when your validations should work.
Here is what you should do after your edit: The field is undefined because you are trying to reach component's own validationActive field within inline validator. Please get validationActive as follows this.model.get('validationActive') and give a try. It should work.

SlickGrid CollapseAllGroups

I am trying to get dataView.collapseAllGroups() to work with SlickGrid.
The post In SlickGrid, how do I collapse grouping via javascript says to just use collapseAllGroups() but it doesn't seem to work.
Even when going to the current demo page http://mleibman.github.io/SlickGrid/examples/example5-collapsing.html and typing dataView.collapseAllGroups() into the console, it doesn't seem to do anything. Is there something else that I need to do to refresh the grid?
Edit
I was trying to get the Grid to display a tree where the groups are collapsed by default. Although I cannot get CollapseAllGroups() to work, I was able to do a hack by adding "if (item._collapsed == null) item._collapsed = true;" to myFilter function that is in the example above.
This is a rough worksound but it does the job for now until I find the real solution:
function myFilter(item) {
// Added this line:
if (item._collapsed == null) item._collapsed = true;
if (item.parent != null) {
var parent = gridData[item.parent];
while (parent) {
if (parent._collapsed) {
return false;
}
parent = gridData[parent.parent];
}
}
return true;
}
That particular example demonstrates how to implement hierarchies using custom formatters and a filter. It does NOT use the DataView's grouping feature, so the collapseAllGroups() call has no effect.

ASP.NET MVC 3 Unobtrusive Jquery Validate not showing custom error messages more than once

So, I took some code from this Microsoft provided Example which allows me to use the jquery validate unobtrusive library to parse validation error message returned from my server and display them in the UI. They have a video demonstrating this. So, here is the piece of Javascript code I'm using:
$.validator.addMethod("failure", function () { return false; });
$.validator.unobtrusive.adapters.addBool("failure");
$.validator.unobtrusive.revalidate = function (form, validationResult) {
$.removeData(form[0], 'validator');
var serverValidationErrors = [];
for (var property in validationResult) {
//var elementId = property.toLowerCase();
var item = form.find('#' + property);
if (item.length < 1) { item = form.find('#' + property.replace('.', '_')); }
serverValidationErrors.push(item);
item.attr('data-val-failure', validationResult[property].join(', '));
jQuery.validator.unobtrusive.parseElement(item[0]);
}
form.valid();
$.removeData(form[0], 'validator');
$.each(serverValidationErrors, function () {
this.removeAttr('data-val-failure');
jQuery.validator.unobtrusive.parseElement(this[0]);
});
};
So then after a AJAX form post in the handle error function I would do something like this:
$.validator.unobtrusive.revalidate(form, { 'PhysicalAddress.CityName': ['You must select a valid city'] });
Where PhysicalAddress.CityName is the name of my viewmodel property and html input field. So, it knows to put the validation message next to the correct html element.
This works 1 time. Then when they hit submit again and my code calls the unobtrusive.revalidate method again.. it doesnt work. It only shows the validation message one time then after that the validation message disappears for good.
Does anyone have any idea as to why this might be happening?.. I stepped through the revalidate method and no errors were thrown and everything seems like it should work.. but the unobtrusive library for some reason is not re-binding the validation error message.
Thanks
Probably this behavior depends on a known problem of the jQuery validation plugin: dynamically adding new validation rules for elements works just once! Further attempts are rejected because the plugin think they are a duplicated attempt to define the already defined rules.
This is the reason why the $.validator.unobtrusive.parse doesn't work when you add newly created content (when for instance you add a new row to a collection of items). There is a patch for the $.validator.unobtrusive.parse that you might try to apply also to the revalidate function....but it is better to rewrite it from scratch in a different way. The revalidate function usse the validation plugin just to place at the right place all validation errors, then it tries to reset the state of the validation plugin. However, deleting the validator object from the form is not enough to cancel all job done since there is another object contained in the form.data('unobtrusiveValidation'), where form is a variable containing the form being validated...This data are not reset by the revalidate function...and CANNOT be reset since resetting them would cause the cancellation of ALL client side validation rules.
Maybe this problem has been solved in the last version of the validation plugin, so try to update to the last version with nuget.
If this doesn't solve your issue I can pass you an analogous function implemented in a completely different way(it mimics what the server does on the server side to show server side errors). It will be contained in the upcoming version of the Mvc Controls toolkit. However, if you give me a couple of days (I will be very busy for 2 days) I can extract it from there with its dependencies so you can use it. Let me know if you are interested.
Below the code I promised. It expects an array whose elements are:
{
id:id of the element in error
errors:array of strings errors associated to the element
}
It accepts several errors for each element but just display di first one for each element
id is different from the name because . [ ] an other special char are replaced by _
You can transform name into id on the sever with
htmlName.Replace('$', '_').Replace('.', '_').Replace('[', '_').Replace(']', '_');
or on the client in javascript with:
name.replace(/[\$\[\]\.]/g, '_');
function remoteErrors(jForm, errors) {
//////////
function inner_ServerErrors(elements) {
var ToApply = function () {
for (var i = 0; i < elements.length; i++) {
var currElement = elements[i];
var currDom = $('#' + currElement.id);
if (currDom.length == 0) continue;
var currForm = currDom.parents('form').first();
if (currForm.length == 0) continue;
if (!currDom.hasClass('input-validation-error'))
currDom.addClass('input-validation-error');
var currDisplay = $(currForm).find("[data-valmsg-for='" + currElement.name + "']");
if (currDisplay.length > 0) {
currDisplay.removeClass("field-validation-valid").addClass("field-validation-error");
replace = $.parseJSON(currDisplay.attr("data-valmsg-replace")) !== false;
if (replace) {
currDisplay.empty();
$(currElement.errors[0]).appendTo(currDisplay);
}
}
}
};
setTimeout(ToApply, 0);
}
/////////
jForm.find('.input-validation-error').removeClass('input-validation-error');
jForm.find('.field-validation-error').removeClass('field-validation-error').addClass('field-validation-valid');
var container = jForm.find("[data-valmsg-summary=true]");
list = container.find("ul");
list.empty();
if (errors.length > 0) {
$.each(errors, function (i, ival) {
$.each(ival.errors, function (j, jval) {
$("<li />").html(jval).appendTo(list);
});
});
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
inner_ServerErrors(errors);
setTimeout(function () { jForm.find('span.input-validation-error[data-element-type]').removeClass('input-validation-error') }, 0);
}
else {
container.addClass("validation-summary-valid").removeClass("validation-summary-errors");
}
}
function clearErrors(jForm) {
remoteErrors(jForm, []);
}

Resources