Knockout ValidationMessage binding not displaying message - javascript

I'm trying to use knockout.validation on a number of parts of a form. Most of this all works fine and can use the built in message appender, but I've got 1 specific field where I need to add the message myself manually to the DOM.
I'm having a real problem getting it working though, because I simply can't get the text to display if there hasn't been a change to the field. I've mocked up a really small example to illustrate as close to the larger form as I can.
The behaviour I'm seeing:
Clicking submit correctly runs the validation (adds a red border around the input) but it doesn't display the error message like it should do.
Typing something into the password box, then emptying it out, then hitting submit works correctly displaying both error message and the red border.
Due to the fact I'm trying to introduce a required field - I can't guarantee that the field has ever been modified, so I need to ensure the first case works. Can anyone see what I'm doing wrong?
ko.validation.init({
registerExtenders: true,
messagesOnModified: true,
insertMessages: true,
decorateInputElement: true,
decorateElementOnModified: false,
errorElementClass: "is-invalid",
messageTemplate: "inline_error"
}, true);
const viewModel = {};
viewModel.submitted = ko.observable(false);
viewModel.clicked = () => { viewModel.submitted(true); };
const onlyIf = ko.computed(() => viewModel.submitted());
viewModel.user = {
password: ko.observable().extend({
required: {
message: `password is required.`,
onlyIf,
}
})
};
ko.applyBindings(viewModel);
.is-invalid {
border: thick solid red;
}
.text-danger {
color: red;
}
<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.3/knockout.validation.min.js"></script>
<div id="domTest">
<button type="button" data-bind="click: clicked">Submit</button>
<input type="password" class="form-control" id="password" placeholder="Password" data-bind="validationOptions:{ insertMessages: false }, textInput: user.password">
<div class="text-danger error-message" data-bind="validationMessage: user.password" />
</div>

In this Github issue, validationMessage does not run on initial binding, stevegreatrex say that you need to change messagesOnModified to false:
messagesOnModified: false
And from the answer to this question Knockout Validation onlyif object no function and pattern validation
(by steve-greatrex) you need to change the onlyIf computed:
viewModel.onlyIf = ko.computed(() => viewModel.submitted());
And then change the property onlyIf in viewModel.user to point to the computed viewModel.onlyIf:
viewModel.user = {
password: ko.observable().extend({
required: {
message: `password is required.`,
onlyIf: viewModel.onlyIf(),
}
})
};
Hope this helps.
ko.validation.init({
registerExtenders: true,
//messagesOnModified: true,
messagesOnModified: false,
insertMessages: true,
decorateInputElement: true,
decorateElementOnModified: false,
errorElementClass: "is-invalid",
messageTemplate: "inline_error"
}, true);
const viewModel = {};
viewModel.submitted = ko.observable(false);
viewModel.clicked = () => { viewModel.submitted(true); };
viewModel.onlyIf = ko.computed(() => viewModel.submitted());
viewModel.user = {
password: ko.observable().extend({
required: {
message: `password is required.`,
onlyIf: viewModel.onlyIf(),
}
})
};
ko.applyBindings(viewModel);
.is-invalid {
border: thick solid red;
}
.text-danger {
color: red;
}
<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.3/knockout.validation.min.js"></script>
<div id="domTest">
<button type="button" data-bind="click: clicked">Submit</button>
<input type="password" class="form-control" id="password" placeholder="Password" data-bind="validationOptions:{ insertMessages: false }, textInput: user.password">
<div class="text-danger error-message" data-bind="validationMessage: user.password" />
</div>

Related

Vuelidate doesn't update 'required'-validator, when inserting into input-field

TL;DR
When using the 'required'-validator, then it doesn't update, when I insert content into a text-field.
CodeSandbox: https://codesandbox.io/s/confident-benz-x1lq8?file=/src/App.vue
Further details
I'm experiencing a wierd bug - and I've been sitting with it all day! I'm afraid that I'm missing something obvious, but I'm starting to get the feeling, that it could be a bug in the Vuelidate-library.
I have a simple form like this:
<div class="field__container">
<div>
<label for="emailToInvite">Email to invite</label> <br />
<input
type="email"
id="emailToInvite"
name="emailToInvite"
v-model="$v.emailToInvite.$model"
/>
<div class="errors" v-if="showErrors">
<p v-if="!$v.emailToInvite.required">Email to invite is required.</p>
<p v-if="!$v.emailToInvite">The written email is not valid.</p>
</div>
</div>
</div>
<button type="button" #click="submitForm()">Submit</button>
and the logic:
import { required, email } from "vuelidate/lib/validators";
export default {
data() {
return {
number: 1,
showAll: false,
submitAttempts: 0,
};
},
computed: {
showErrors() {
if (this.submitAttempts > 0) {
if (this.$v.$invalid) {
return true;
}
}
return false;
},
},
methods: {
submitForm() {
this.submitAttempts++;
},
resetAll() {
this.submitAttempts = 0;
this.$v.emailToInvite.$model = "";
},
},
validations: {
emailToInvite: {
required,
email,
},
},
}
And when I insert some text in the emailToInvite-field, then the 'required'-validator stays 'false'.
Another wierd thing is that the 'email'-validator is true, when the input is just an empty string.
All this can be seen in this CodeSandbox: https://codesandbox.io/s/confident-benz-x1lq8?file=/src/App.vue
What am I doing wrong?
I noticed a few things you should change -
First, you must add emailToInvite: '' to data:
data() {
return {
number: 1,
showAll: false,
submitAttempts: 0,
emailToInvite: ''
};
},
I'm not sure how Vuelidate works, but it seems it doesn't create the property for you. So it can't track the change properly.
Second, the showErrors computed property is set to 'false' until you press on 'Submit'. So you won't see the error message before you click on 'Submit'. Set it to 'true' if you want to see the error message (or hide it) while you type.

Vue form validation

I have some problems with mine Vue app.
I'm trying to validate login form that is connected with my Laravel App.
This is how template looks
<template>
<div>
<div class="main" v-if="canLogin">
<img class="logo" src="../assets/logo.png">
<form id="app"
#submit="checkForm"
method="post">
<p v-if="validation.length">
<b>Please correct the following error(s):</b>
<ul>
<li v-for="validation in validation">{{ error }}</li>
</ul>
</p>
<input class="form-input" type="email" v-model="form.email" id="email" align="center" placeholder="eMail" required>
<input class="form-input" type="password" v-model="form.password" id="password" align="center" placeholder="Password" required>
<button #click.prevent="login" class="submit">Sign In</button>
</form>
</div>
<div class="main" v-if="!canLogin">
<span> YOU ARE BLOCKED FOR 15 MINUTES</span>
</div>
</div>
</template>
As you see I want to foreach errors, but it's always giving error that
'validation' is defined but never used
And this is how mine script looks.
<script>
import User from "../apis/User";
export default {
data() {
return {
form: {
email: "",
password: ""
},
validation: [],
errors: '',
message: '',
canLogin: true,
};
},
mounted() {
User.canLogin().then(response => {
this.canLogin = response.data.canLogin;
});
},
methods: {
checkForm: function (e) {
this.errors = [];
if (!this.form.password) {
this.errors.push("Name required.");
}
if (!this.form.email) {
this.errors.push('Email required.');
} else if (!this.validEmail(this.email)) {
this.errors.push('Valid email required.');
}
if (!this.errors.length) {
return true;
}
e.preventDefault();
},
validEmail: function (email) {
var re = /^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
},
login() {
User.login(this.form)
.then(response => {
this.$store.commit("LOGIN", true);
localStorage.setItem("token", response.data.token);
this.$router.push({ name: "Dashboard" });
this.$snotify.success(response.data.message, {
timeout: 2000,
showProgressBar: true,
closeOnClick: true,
pauseOnHover: true
});
})
.catch(error => {
if (error.response.status === 400) {
this.errors = error.response.data.message;
this.$snotify.error(error.response.data.message, {
timeout: 2000,
showProgressBar: true,
closeOnClick: true,
pauseOnHover: true
});
}
if(error.response.status === 429){
this.canLogin = false;
}
});
}
}
};
</script>
I'm catching few thhings, like, canLogin, this is checking if IP is not blocked.
There is one more error like:
Elements in iteration expect to have 'v-bind:key' directives
I'm just a started with vue so don't judge me if it's simple fix.
BTW: without validation works fine, I believe it's not only problem with those errors and probbly I'm not catching some things as needed.
What am I doing wrong here?
Change
<ul>
<li v-for="validation in validation">{{ error }}</li>
</ul>
To:
<ul>
<li v-for="(error,index) in errors" v-bind:key="index">{{ error }}</li>
</ul>
In vue, you must provide a key for every v-for looping.
And change your data to:
data() {
return {
form: {
email: "",
password: ""
},
validation: [],
errors: [],
message: '',
canLogin: true,
};
},
I made your errors variable an arrayList.

Bootbox incorrectly submits a form when a single input field is present

I have a strange problem that's really starting to bug me. Apologies in advance for a wall of code and somewhat confusing question.
I need to display a modal form for the user, and have them fill in some details.
The user can click Save to save their changes.
The user can click Cancel to cancel their changes.
I use the save handler to serialize the form and send its data to a JSON service.
If I have a form with multiple input fields, it all works great, and nothing unexpected happens.
If I have a form with a single input field, however, I get an unexpected side-effect. Hitting Enter/Return in that input field causes the modal form to be submitted, and instead of my JSON handler getting called the page is reload with the form's arguments as parameters — exactly as if the form is being submitted. In fact, adding an action= parameter to the form element has proven that, as you get navigated to the page you specify.
Here's the form I'm using:
<form id="surveyQuestionForm" class="form-horizontal" style="display:none;">
<div class="row">
<input name="surveyQuestionId" id="surveyQuestionId" type="hidden">
<input name="surveyId" type="hidden" value="${survey.surveyId}">
<div class="control-group">
<label class="control-label" for="questionType"><b><spring:message code="survey.question.type"/></b></label>
<div class="controls">
<select class="input-large" name="questionType" id="questionType">
<option value="">(Select one)</option>
<c:forEach items="${surveyQuestionTypes}" var="surveyQuestionType">
<option value="${surveyQuestionType.code}">${surveyQuestionType.name}</option>
</c:forEach>
</select>
</div>
</div>
<div class="control-group">
<label class="control-label" for="questionText"><b><spring:message code="survey.question.text"/></b></label>
<div class="controls">
<input type="text" class="input-xlarge" name="questionText" id="questionText" maxLength="64"/>
</div>
</div>
</div>
</form>
and here's the code I use to display the form modally:
function addQuestion() {
// find the form, and initialise its validation.
var form = $('#surveyQuestionForm');
var validator = form.validate(
{
rules: {
questionType: {
required: true
},
questionText: {
required: true
}
},
messages: {
questionType: {
required: '<spring:message javaScriptEscape="true" code="survey.question.type.required"/>'
},
questionText: {
required: '<spring:message javaScriptEscape="true" code="survey.question.text.required"/>'
}
},
onkeyup: false
});
// reset form validation, and hide any error message
validator.resetForm();
$("#errorMessage").hide();
// show the dialog
bootbox.dialog({
title: '<i class="icon-plus green"/> <spring:message javaScriptEscape="true" code="survey.add.question"/>',
message: form,
closeButton: false,
buttons: {
cancel: {
label: '<i class="icon-remove bigger-130"></i> <spring:message javaScriptEscape="true" code="button.cancel"/>',
className: "btn btn-danger"
},
save: {
label: '<i class="icon-ok bigger-130"></i> <spring:message javaScriptEscape="true" code="button.save"/>',
className: 'btn btn-success',
callback: function () {
var result = false;
if (!form.valid())
return false;
$.ajax({
type: "POST",
url: '/addSurveyQuestion.json',
async: false,
data: form.serialize(),
success: function (outcome) {
if (outcome.success) {
$('#question-list').dataTable().fnReloadAjax();
result = true;
}
else {
$("#errorMessage").html(htmlEncode(outcome.message)).show();
}
}
}
).fail(function () {
$.gritter.add({
title: '<spring:message javaScriptEscape="true" code="general.error"/>',
text: '<spring:message javaScriptEscape="true" code="server.error"/>',
class_name: 'gritter-error'
}
);
}
);
return result;
}
}
},
show: false,
animate: false,
onEscape: false
}
).on('shown.bs.modal', function () {
var form = $('#surveyQuestionForm');
form.find('#surveyQuestionId').val(null);
form.find('#questionType').val('');
form.find('#questionText').val('');
form.show().find('#questionType').focus();
form.show();
}
).on('hide.bs.modal', function (e) {
if (e.target === this)
$('#surveyQuestionForm').hide().appendTo('body');
}
).modal('show').addClass("bootboxDialog40");
}
If I use this code as-is, with Bootbox 4.4, hitting Enter/Return while the user is in the questionText field submits the form, and my page redisplays but with the form fields as parameters, eg:
page.html?surveyQuestionId=&surveyId=3&questionType=Y&questionText=blah
If I have a second input field, hitting Enter/Return in the fields does nothing, and the user has to click Save or Cancel.
Submit-on-enter for a single input field is a browser behavior that you will need to override. You can do this a few ways.
<form onSubmit="return false;">
I don't think you are using the native submit function at all, so adding this bit of inline scripting prevents the form submission. But putting scripts in your markup isn't great. A little jQuery can do the same thing for you:
$('form').on('submit', function(){ return false; });
I believe this is not related to bootbox plugin. The actual reason is here:
https://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2
When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.
Coming to the solution, you can add another hidden field in your form which will prevent the submission of the form on enter.

Knockout Validation on Collections of validatable objects

So I'm using Knockout 2.3 and Knockout Validation 2.0.2. I have a viewModel with a property that is an observableArray of custom javascript "objects" (HRAdmin). An HRAdmin has 3 properties that need validation. I've having a hard time with how to manage this type of situation. I've tried different things, but this is where the code sits at this point.
For the sake of brevity, I've stripped out all of the other properties of my viewModel that ARE validating just fine. But what this also tells me is none of my other code is interfering.
You can run and step through the code here and see that even when you leave all fields blank and click the "Go to Step 3" link, this line of code always results in the object being valid.
if (!obj[i].isValid())
It shouldn't be valid. Ideas/Suggestions??
// check validity of each object in array
ko.validation.rules['collectionValidator'] = {
validator: function(obj, params) {
for (var i = 0; i < obj.length; i++) {
if (!obj[i].isValid()) {
obj[i].notifySubscribers();
return false;
}
}
return true;
}
};
// validate a certain number of object exist in array
ko.validation.rules['minArrayLength'] = {
validator: function(obj, params) {
return obj.length >= params.minLength;
},
message: "Must have at least {0} {1}"
};
ko.validation.registerExtenders();
// HRAdmin "object"
function HRAdmin() {
this.FirstName = ko.observable("").extend({
required: true,
minLength: 1,
maxLength: 50
});
this.LastName = ko.observable("").extend({
required: true,
minLength: 1,
maxLength: 50
});
this.Email = ko.observable("").extend({
required: true,
minLength: 1,
maxLength: 100,
email: true
});
}
var viewModel = function() {
var self = this;
// Must be at least one HRAdmin and all fields of EACH HRAdmin must validate
self.HrAdmins = ko.observableArray([ko.validatedObservable(new HRAdmin())]).extend({
minArrayLength: {
params: {
minLength: 1,
objectType: "Account Manager"
},
message: 'Must specify at least one Account Manager'
},
collectionValidator: {
message: 'Please check the Account Manager information'
}
});
self.AddHrAdmin = function(data, event) {
self.HrAdmins.push(new ko.validatedObservable(new HRAdmin()))
};
self.GoToStep3 = function(data, event) {
// validate at least one HRAdmin and ALL fields on each are valid
if (!self.HrAdmins.isValid()) {
self.HrAdmins.notifySubscribers();
return;
}
// on to step 3 ...
};
};
ko.applyBindings(new viewModel());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.2/knockout.validation.min.js"></script>
<div data-bind="foreach: HrAdmins">
<input type="text" placeholder="First Name" data-bind="value: FirstName" />
<input type="text" placeholder="Last Name" data-bind="value: LastName" />
<input type="text" placeholder="Email Address" data-bind="value: Email" />
</div>
<p data-bind="validationMessage: HrAdmins" class="validationMessage"></p>
Add Manager
Go to Step 3
So after a lot of trail and error I've found a solution. Not sure if it's the BEST solution, but it works fine.
I've gotten rid of ko.validation.rules['collectionValidator'] and added a validator grouping.
self.HrAdminsErrors = ko.validation.group(self.HrAdmins, {deep: true, live: true});
The operational code is at the following fiddle:
http://jsfiddle.net/3b3o87dy/6/

Knockout validation alway show default error when I use error template

I am using validation knockout for my project
And i used template for all error messages:
<script type="text/html" id="errorTemplate">
<div class="errorContainer error_bottom" data-bind="visible: field.isModified() && !field.isValid()">
<div class="error_arrow">
<div class="error_arrow_inner"></div>
</div>
<span class="error" data-bind="text: field.error"></span>
</div>
</script>
<script>
// KO validastor setup:
ko.validation.configure({
decorateElement: true,
errorElementClass: 'error', // Add class to input
registerExtenders: true,
messagesOnModified: true,
insertMessages: true,
parseInputAttributes: true,
messageTemplate: 'errorTemplate' // Use template
});
function ReservationViewModel() {
var self = this;
self.email = ko.observable("");
self.name = ko.observable("");
self.phone = ko.observable("");
var validator = ko.validatedObservable({
email: self.email.extend({required: true, email: true}),
name: self.name.extend({required: true}),
phone: self.phone.extend({required: true}),
});
// continues ...
}
ko.applyBindings(new ReservationViewModel());
</script>
But It alway get default message: "This field is required." for custom error or email is not valid.
Can u help me fix it?
Thanks all.

Categories