knockout validator with dynamic message - validation

I want my knockout validator to have a message that depends on the validation of the input. It seems like a very common use case but I can't find any way of doing it... here's a simplistic example of what I'd like to do
ko.validation.rules.dumb = {
validator: function( value )
{
if (value.startsWith( "s")) return {isValid:true}
return {isValid:false, message: value + " needs to start with an s"}
}
}
some_field.extend({dumb: {}});
This sort of works:
ko.validation.rules.sort_of_works = {
validator: function( value)
{
if (value.startWith("s")) return true;
ko.validation.rules.message = value + needs to start with an s";
return false;
}
}
but it really doesn't - because it only works if you only have one field using that validator :(
I tried accessing "this" in the function, but the this is the this of the validator function - which isn't useful, as it doesn't have a message on it. Also - I've seen people make message a function, so it depends on the input itself - but my validation is expensive (think something like parsing, where you want to say exactly where the error is in the string) - and I don't want to do it once for the validation, and then again for the message.
What I want works perfectly with the async validation callback function - in fact that's sort of what I'm mimicking, the validation actually happens on the server - but unfortunately the rest of the app (not written by me) is not really setup to support IsValidating - so I can't use async.

I think the library is designed to chain multiple validations using extend.
So, instead of having one validator function that checks a ton of cases, you create a set of validators that all do one simple check that corresponds to a single error message:
ko.validation.rules.startsWith = {
validator: (val, param) => val?.startsWith(param),
message: 'The field must start with {0}'
};
ko.validation.rules.endsWith = {
validator: (val, param) => val?.endsWith(param),
message: 'The field must end with {0}'
};
ko.validation.registerExtenders();
const myValue = ko.observable()
.extend({ startsWith: "s" })
.extend({ endsWith: "p" });
ko.applyBindings({ myValue });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.4/knockout.validation.js"></script>
<label>
Input a word that starts with an <code>s</code> and ends with a <code>p</code><br>
<input data-bind="value: myValue">
</label>

Related

Accessing reactive properties with vee-validate 4

I am getting my head around Vee-Validate next (v4) and how I might incorporate it in a Vue 3 project without loosing Vue's reactivity (i.e. not relying on the values simply being passed to the Form submit event).
By way of example, if I were making a hypothetical component which has autocomplete functionality, and sent a get request to the server once 3 letters had been typed, but for the input itself to be valid it required 8 letters, how would I get the value associated with the input?
using plain Vue, with pseudo-code something like:
defineComponent({
setup () {
const myVal = ref('')
const options = ref([])
watchEffect(() => if (myVal.value.length > 3) {
axios.get(...).then(serverVals => options.value = serverVals))
})
return { myVal, options }
how would I achieve this with vee-validate 4.x?
defineComponent({
setup () {
const schema = yup.object({ myVal: yup.string().required().min(8) })
// ???? what now to watch myVal
please note this is not about autocomplete - a range slider where I wanted a server call when the value was greater than 10 but a validation message if greater than 90 would also suffice as an example.
You could employ useField here to get a reactive value that's automatically watched.
const { value: myVal, errorMessage } = useField('myVal', undefined, {
initialValue: ''
});
const options = ref([])
watchEffect(() => if (myVal.value.length > 3) {
axios.get(...).then(serverVals => options.value = serverVals))
})
return { myVal, options }
Documentation has an example of using useField:
https://vee-validate.logaretm.com/v4/guide/composition-api#usefield()
Note that you don't have to use useForm, if you are using <Form> component and passing schema to it then that should work just fine.

Trying to access redux store from within validate function

Using Redux-form, I'm running into issues with validation.
I would like to access the store for referencing a piece of state in validation.
I have standard redux form export:
function mapStateToProps({address_object}) {
return{
address_object: address_object}
}
export default reduxForm({
form: 'wizard',
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
validate,
})(
connect(mapStateToProps,mapDispatchToProps)(WizardFormFirstPage)
);
and my validate function:
const validate = (values, props) => {
console.log("DEBUG :",props)
... if / else etc
which lets me gets at the props of the form
however I would like to run validation against something that is stored in state. something like:
if (<something in state> === values.<thing>) {
errors.field = "broken"}
console logging the props that validation receives, I can't get it to see anything in state. I can see the address_object in the WizardFormFirstPage component obviously (so actions and reducers are working fine)
Do I need to declare the validate function inside the component (that's connected with mapStateToProps) in order to access this.props.whatever ?? If thats the case, does it work if I call it from the export reduxForm()??
redux noob - apologies if dumb question
Your almost there.
When you connect your form add the state you need for validation in mapStateToProps.
The state will then be available in the validate props:
reduxForm({
form: 'wizard',
validate => (values, { stateForValidation }) => {
if(values.myField === stateForValidation)) {
errors.myField = 'invalid'
}
}
})(connect(
({ stateForValidation }) => ({ stateForValidation })
)(form))
If anyone cares I ended up handling validation at the Field level with
<Field
validate={this.validateAddress} ...>
then the component method:
validateAddress = () => {
errors = {}
// validation stuff
errors._error = "string to display" //use this instead of errors.fieldname = ""
return errors
}
which gave me access to props. However #pariesz answer above answers this question much better.

Knockoutjs Validation

I am facing a problem with my validation
Here is my field with extend property
self.searchText = ko.observable("")
.extend({ pattern: { params: /^[a-zA-Z0-9\åäöÅÄÖ]+$/g, message: "Invalid symbols."} });
Regex is well to not allow special symbols But, in runtime I can enter any symbol
What's wrong in my code?
Assuming the problem you're actually trying to solve is "use knockout to prevent special characters", not "use knockout to tell them that they've used special characters", then you'll want something like this:
var allowedChars = /[a-zA-Z0-9\åäöÅÄÖ]/;
var _noSpecialCharacters = ko.observable("");
self.searchText = ko.computed({
read: function () {
//read underlying observable
return _noSpecialCharacters();
},
write: function (newVal) {
//filter disallowed characters
var filtered = newVal.split("").filter(function (char) {
return allowedChars.test(char)
}).join("")
//write to underlying observable
_noSpecialCharacters(filtered);
}
});
You could also wrap this functionality into a custom knockout extension pretty easily, which would let you use syntax like what you're trying to use in the question.
jsfiddle

AngularJS + custom validations

I have some doubts about AngularJS + Custom Validations, from what I have read and checked by myself:
AngularJS provides great helpers for simple field validation (ng-require, ...).
The best way to implement a single field custom validation is via directive (not polluting the controller).
My doubts come when we have custom business validations that impact on more than one filed. Let's check the following simple scenario:
We are editing a flight arrival status, fields: Status (landed / scheduled / delayed), comments (additional info abot the flight status).
The business validations that I want to apply is: comments fields is required only if status fields value is "Delayed".
The way I have implemented it:
Define a directive to take care of Status + Comments field changes (status via $watch).
This directive delegates the business validation into a service
The benefits I think this approach is giving to me are:
My business service validation gets isolated.
I could easily add unit testing to that business validation.
I could reuse it and it doesn't depend on UI elements.
I have compiled this sample in a JSFiddle (JSFiddle validation sample).
JS:
function MyCtrl($scope) {
$scope.arrival = {
"id": 1,
"originAirport": "Malaga (AGP)",
"flightNumber": "Iberia 132",
"dueAt": "2013-05-26T12:10:10",
"status": 2,
"info": "test"
}
}
myApp.directive('validateinfofield', ['formArrivalValidation', function (formArrivalValidation) {
return {
require: "ngModel",
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue){
// Empty? Let's check status
//if (!viewValue.length && scope.arrival.status == 3) {
if(formArrivalValidation.validateInfoField(scope.arrival.status, viewValue)) {
ctrl.$setValidity('validInfo', true);
} else {
ctrl.$setValidity('validInfo', false);
}
});
// Let's add a watch to arrival.status if the values change we need to
// reevaluate, if comments is empty and status is delayes display error
scope.$watch('arrival.status', function (newValue, oldValue) {
if (formArrivalValidation.validateInfoField(newValue, scope.editArrivalForm.txInfo.$viewValue)) {
ctrl.$setValidity('validInfo', true);
} else {
ctrl.$setValidity('validInfo', false);
}
});
}
};
}]);
// Validation Service, limited to our "business language"
myApp.factory('formArrivalValidation',
function () {
return {
validateInfoField: function (status, infoField) {
var isOk = true;
if (status == 3) {
if (infoField == undefined) {
isOk = false;
} else {
if (!infoField.length)
isOk = false;
}
}
return isOk;
},
};
});
Is this a good approach to follow? Is there better and simpler way to achieve this?
Regarding this part - "The business validations that I want to apply is: comments fields is required only if status fields value is "Delayed".
for comment field set ng-required="flight.status == 'DELAYED'"
Coming back to this question... one valid approach could be to write a directive that accepts as parameter a second value (e.g. comments is empty)

Knockout validation issues

I have the following issues with my knockout model validations and not sure how to resolve them. Following is my model first of all, with the validation rules:
var Data = function (data) {
this.Val = data;
}
function ViewModel(item) {
var parse = JSON.parse(item.d);
var self = this;
this.Name = ko.observable(parse.Name);
this.UserType = ko.observable(parse.UserType);
this.ID = ko.observable(parse.ID).extend({ required: { params: true, message: "ID is required" }, decimal: { params: 2, message: "Should be decimal"} });
this.Username = ko.observable(parsed.Username).extend({ required: {
onlyIf: function () {
return self.UserType() > 1;
}
}
});
this.WeeklyData = ko.observableArray([]);
var records = $.map(parse.WeeklyData, function (data) { return new Data(data) });
this.WeeklyData(records);
this.WeeklyData2 = ko.observableArray([]);
var records = $.map(parse.WeeklyData2, function (data) { return new Data(data) });
this.WeeklyData2(records);
}
ko.extenders.numeric = function (target, precision) {
var result = ko.dependentObservable({
read: function () {
return target().toFixed(precision);
},
write: target
});
result.raw = target;
return result;
};
Here are my problems:
1) with the ID() observable, I want to restrict it to two decimal points, so I've created the validation extender 'numeric' but it's not working. Is there anything wrong with how I'm using it and how to correct it?
2) Also, if I want to restrict an observable to whole numbers, how can I do that?
3) when I define a rule with a condition, (i.e. Username()), how do I define a custom message for that? I was able to do it for default rules, but with the conditional rules, it's not working
4) I have two observable arrays WeeklyData1 and WeeklyData2 both of which contains Data() objects. I want to have separate min/max rules for these two, for example, min/max - 1,7 for WeeklyData1 and min/max - 1,150 for WeeklyData2. How can I get it done?
4) Right now my error messages appear right next to the data field, but I want all those to appear in a single validation summary, while displaying '*' against the field. I've been told to use Validation-bindings, but I'm not sure how to use it, can someone please give an example?
It's a lot of questions, I know, but I appreciate if someone could help.
Thanks in advance
Instead of diving in your code i have created a small-small demonstrations for your questions. Ok so here we go,
1) with the ID() observable, I want to restrict it to two decimal points.... and 2) Also, if I want to restrict an observable to whole numbers....
Your 1 and 2 question are pretty similar so i covered both of this in a single fiddle. Check this fiddle.
3) when I define a rule with a condition, (i.e. Username()), how do I define a custom message ....
You can use message property to set custom messages, Check this fiddle.
4) I have two observable arrays WeeklyData1 and WeeklyData2 both of which contains Data() objects
I am not clear which this question, what type of data both of these array contains and for what you want to set min/max rule ( array length or other ). So please clear this, than i will try to help on this.
5) Right now my error messages appear right next to the data field.....
This questions answer i already given in your how to? with knockout js validations question (Check update).
Let me know if it helps!

Resources