Conditional Validation doesn't update when property changes - aurelia

How do I use conditional validation based on a property that can change?
The following validation rules will only validate if the toValidate flag is set to true.
ValidationRules.ensure('Email').required().when(v => v.toValidate);
get toValidate() {
if (this.checkBox.Checked)
return true;
}
else {
return false;
}
}
So when toValidate is set to true the required() rule will be ran.
If the value of toValidate changes due to the user making a change to say a checkbox on the page the validation rule is not affected and will stay either on or off depending on what it was on first loaded.
Is there a way to allow the conditional rule to be applied on the fly?

A validation rule will only be re-run when the property in the ensure() function changes. If you want to re-run a validation rule (or rules) on some other change, then you can do so manually.
Run all rules:
this.controller.validate();
Run one rule:
this.controller.validate({ object: this.yourObject, propertyName: "Email" });
You can setup observers in various different ways to initiate this - that's another topic!

Related

FluentValidation: Only do .Must when field is not empty

I would like to only check for a .Must condition when a field is not empty/null.
The following code is not evaluation .Must properly (.CheckUrl not being called) when bad urls are passed.
RuleFor(c => c.Website)
.Must((item, list, context) => ValidationHelper.CheckUrl(item.Website, context))
.When(x => !string.IsNullOrEmpty(x.Website));
I want to fail the validation only if something is passed for url, empty should not fail it.
I could change the .CheckUrl method to return true if url empty and remove the .When but i prefer not to do that if there is a way around the validation rules itself

Skip watcher in vueJS

I have a form for updating document entity.
The document entity consists of list of employees (which is an array of objects) and each employee has a post which is just a string.
I have a dropdown (kind of wrapper for vue-multiselect) which accepts the array of employees and syncs selected employee to a selectedEmployee variable in data().
And also I have a watcher for selectedEmployee which sets the post input automatically when an employee is selected in the dropdown.
So, when creating a document in the form everything's fine, however, when I update the document, then I fetch existing document from server, set selectedEmployee and set employee's post. But, the document also keeps employee's post, so the first time when I open document's form in order to update it, I don't want to automatically update document's post. I want it to be updated only when user actually selects employee himself.
But the watcher gets called the first time too.
So, imagine we have John Doe and his a manager. When I create the document, I change his post to designer. Then, I open up the document form in order to update it, and I should see that for this specific document John Doe's post is "designer", but the watcher gets called and returns the post to manager.
I tried to make a fake variable in data(), like doneFetching, but it works only if I update this var directly in watcher, which looks quite dangerous, plus, in other entities I have many different kinds of selected employees, so making tons of fake flags is not an option.
Here is real code sample (employee = representative in my case):
selectedApproveRepresentative(representative) {
if (!representative) {
this.memoData.approve_representative_id = null
return
}
this.memoData.approve_representative_id = representative.id
// Here is temporary solution, but I have many watchers for many different kinds of employees. If I move the doneFetching flag after I initialized the form, it'll be set to true, and only after that the watcher will be called
if (this.mode === 'update' && !this.doneFetching) {
this.doneFetching = true
return
}
// In normal case a representative might have or have not post, so depending on this case we set it to be empty or filled. But this should not be called the first time I open the form
this.memoData.approve_representative_post_dative_case =
representative.post_dative_case ?
representative.post_dative_case : ''
},
Here is where I initialize data:
created() {
if (this.memo) {
this.memoData = _.cloneDeep(this.memo)
this.selectedApproveRepresentative = _.cloneDeep(this.memo.approve_representative)
}
},
as I understood, your problem is the watcher executed when you init the component. Have you tried setting the immediate property of the watcher to false?
Not everybody knows that the watchers can be defined in different ways.
The simple one that everybody know
watchers: {
propertyToWatch() { //code... }
}
Passing the name of a function as 'string'
watchers: {
propertyToWatch: 'nameOfAfunctionDefinedInMethodsSection'
}
The object declaration
This one is the most descriptive way of declaring a watcher. You write it as an object with a handler property (it can be the name of a function passed as string as above), and other properties like deep to watch nested properties of an object, or in your case immediate which tells to the watcher if the should run immediately when the component is mounted.
watchers: {
propertyToWatch: {
immediate: false,
handler: function() { //code.. }
}
}

Aurelia - Custom validation to check if a form has changes

I see THIS question regarding whether the form I am validating has changed.
However it has nothing to do with Aurelia validation and I would like to validate whether the form has any changes upon clicking the save button. I dont want to do it on the server.
What I have done is to save the values I fetched initially so I can do a comparison.
fetch("/api/Client/editClient/" + parms.id, {
method: "GET",
headers
})
.then(response => response.json())
.then(data => {
this.client.deserialize(data);
this.originalClient === this.client;
})
original client is the unmodified object.
I have created a custom validation function however its not working as intended.
I thought I could use the current value and then compare it to the original value.
ValidationRules.customRule(
'changesExist',
(value, obj, fetchedEntity) =>
fetchedEntity != value,
'No changes detected'
);
When I try and use it I find it erroring:
// Validation Rules.
ValidationRules
.ensure(a: ClientDetails) => a).satisfiesRule('changesExist', this.originalClient);
I am unsure how to make this work. What I want is a validation that compares the orginalClient object with the the one that is to be sent back to the server. This way I can check if there is reason for sending it back can saving it to the database...
It's rather brute force, but this will likely do what you want:
JSON.stringify(fetchedEntity) !== JSON.stringify(value)
The app-contacts sample app does it using an areEqual function. Have a look at the repo and you can see how it's used and how you could adapt it for your purposes.
function areEqual(obj1, obj2) {
return Object.keys(obj1).every((key) => obj2.hasOwnProperty(key) && (obj1[key] === obj2[key]));
};

Dynamically changing jQuery unobtrusive validation attributes

I have a page built in ASP.NET MVC 4 that uses the jquery.validate.unobtrusive library for client side validation. There is an input that needs to be within a range of numbers. However, this range can change dynamically based on user interactions with other parts of the form.
The defaults validate just fine, however after updating the data-rule-range attribute, the validation and message are still triggered on the original values.
Here is the input on initial page load:
<input id="amount" data-rule-range="[1,350]" data-msg-range="Please enter an amount between ${0} and ${1}">
This validates correctly with the message Please enter an amount between $1 and $350 if a number greater than 350 is entered.
After an event fires elsewhere, the data-rule-range is updated and the element looks as such:
<input id="amount" data-rule-range="[1,600]" data-msg-range="Please enter an amount between ${0} and ${1}">
At this point if 500 is entered into the input it will fail validation with the same previous message stating it must be between $1 and $350. I have also tried removing the validator and unobtrusiveValidation from the form and parsing it again with no luck.
$('form').removeData('validator');
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
Is there a clean way to change the validation behavior based on the input attributes dynamically?
As Sparky pointed out changing default attributes dynamically will not be picked up after the validation plugin has been initialized. To best work around this without rewiring how we register validated fields and rules, I found it easiest to register a custom adapter in the unobtrusive library:
jQuery.validator.unobtrusive.adapters.add("amount", {}, function (options) {
options.rules["amount"] = true;
options.messages["amount"] = function () { return $(options.element).attr('data-val-amount'); };
});
jQuery.validator.addMethod("amount", function (val, el, params) {
try {
var max = $(el).attr('data-amount-max');
var min = $(el).attr('data-amount-min');
return val <= max && val >= min;
} catch (e) {
console.log("Attribute data-amount-max or data-amount-min missing from input");
return false;
}
});
Because the message is a function, it will be evaluated every time and always pick up the latest attribute value for data-val-amount. The downside to this solution is that everytime there is a change we need to change all three attributes on the input: data-amount-min, data-amount-max, and data-val-amount.
Finally here is the input markup on initial load. The only attribute that needs to be present on load is data-val-amount.
<input id="amount" data-val-amount="Please enter an amount between ${0} and ${1}" data-val="true">
You cannot change rules dynamically by changing the data attributes. That's because the jQuery Validate plugin is initialized with the existing attributes on page load... there is no way for the plugin to auto re-initialize after dynamic changes are made to the attributes.
You must use the .rules('add') and .rules('remove') methods provided by the developer.
See: http://jqueryvalidation.org/rules/
you can try this one:
// reset the form's validator
$("form").removeData("validator");
// change the range
$("#amount").attr("data-rule-range", "[1,600]");
// reapply the form's validator
$.validator.unobtrusive.parse(document);
charle's solution works! you cannot have model attributes to use it though, I build my inputs like:
#Html.TextBoxFor(m => Model.EnterValue, new
{
#class = "form-control",
id="xxxx"
data_val = "true",
data_val_range = String.Format(Messages.ValueTooBig, Model.ParamName),
data_val_range_max = 6,
data_val_range_min = 2,
data_val_regex_pattern = "^\\d+(,\\d{1,2})?$"
})
and then in javascript:
$("form").removeData("validator");
$("#xxxx").attr('data-val-range-max', 3)
$("#xxxx").attr('data-val-range-min', 0)
$.validator.unobtrusive.parse(document);

Durandal - Correct way to disable .canDeactivate for 'Success' operations?

I have an edit page (in a DurandalJS single page app), where I use the .canDeactivate lifecycle method to check if there are any changes to the record, and optionally prompt them for confirmation before leaving the page.
I also have a 'Save' and 'View History' button. Is the correct thing to do to override the .canDeactivate method before calling router.navigate, to stop the modal popup invoking?
E.g.: As here:
self.onSave = function() {
self.repository.updateItem(self.model).done(function() {
self.canDeactivate = null; // Is this the correct way to do this?
router.navigate("#/home");
}
}
As this .canDeactivate will otherwise get called:
self.canDeactivate = function() {
if (!self.model.hasChanges()) {
return true;
}
return app.ShowMessage("Unsaved data will be lost", "Are you sure you wish to exit?", ["Yes", "No"]).done(function(result) {
return result !== "No";
}
};
Why dont you just set
self.model.hasChanges(false)
in your updateItem callback?
Then when your canDeactivate is called, it will return true.
Also you seem to have an error in your ShowMessage callback. I think you mean to do:
return result != "No";
I don't think the way Durandal decides whether to attempt to call a canDeactivate function is fully defined, other than the fact that if it's not in the view model, it won't try. Hence, even if it works as is, a future version of the framework could change its check to something like if (canDeactivate in viewModel) viewModel.canDeactivate(...); without further tests, and your code would break.
This is unlikely, but if you want to worry about it, you should thus delete self.canDeactivate instead of assigning it the null value.
Quote from the documentation:
To participate in the lifecycle, implement any (or none) of the
functions below on the object that you set the activator to (...)
Current implementation (activator.js, L126, 1eecbc2d3f84dc42eb7304bde761d88f300d8951):
if (item && item.canDeactivate) {
So it only checks if it's truthy (which would indicate using null works fine currently, too).
If you want to discuss the pattern, I don't see anything wrong with it, as long as it makes sense to you and everyone who should read the code.
You're not supposed to be activating and deactivating views programmatically in any critical path, so performance should be irrelevant either way (flag on view model or deletion of canDeactivate).