Conditional validation in VueJS - javascript

I am using VueJS with vue-validator and I have been struggling for hours to do simple conditional validation. The example provided in the documentation does not seem to work, at least not in my case.
What I am trying to accomplish is requiring two input groups (observer_firstName and observer_lastName) if a condition (showObserverEntry) is true and requiring another (role) if it is false.
So, if showObserverEntry is false, role should be required/visible. If showObserverEntry is true, role SHOULD NOT be required or visible, observer_firstName and observer_lastName should be required and visible.
Everything works when the page is loaded and showObserverEntry is set to false, it continues to work when switched to true, but when it goes back to false again validation stops working for role. Peeking at the data output, the validation data changes to validation { } where it initially has data.
Vue instance with other methods removed:
var vm = new Vue({
el: "#scheduleContainer",
validator: {
validates: {
requiredIf: function (val, condition){
return val && condition
}
}
},
data: {
loading: true,
stationId: stationId,
date: initialDate,
dateFormatted: initialDateFormatted,
nextDate: null,
prevDate: null,
entries: [],
requestEntries: [],
roles: [],
roleStaff: [],
showObserverEntry: false,
startPickerDatetime: null,
endPickerDatetime: null,
shiftEntry: {
start: null,
end: null,
role: null,
member: "",
observer: {
firstName: "",
lastName: ""
}
}
},
computed: {
validField: function () {
return this.validation.shiftEntry.observer.firstName.valid &&
this.validation.shiftEntry.observer.lastName.valid
}
},
methods: {
getRoleStaff: function () {
if (this.shiftEntry.role != '' && this.shiftEntry.role != 'observer') {
this.$http.post('/members/schedule/manage/json/roles/staff', {id: this.shiftEntry.role})
.success(function (data) {
this.$set('roleStaff', data.members);
vm.shiftEntry.member = "";
vm.showObserverEntry = false;
vm.shiftEntry.observer.firstName = "";
vm.shiftEntry.observer.lastName = "";
});
} else if (this.shiftEntry.role == 'observer') {
this.showObserverEntry = true;
this.resetFields()
}
else {
this.showObserverEntry = false;
this.roleStaff = [];
}
},
resetFields: function () {
this.roleStaff = [];
this.shiftEntry.role = "";
this.shiftEntry.member = "";
this.shiftEntry.observer.firstName = "";
this.shiftEntry.observer.lastName = "";
},
conditionalField: function (response, type) {
return response === type
}
}
});
Form fields:
<div class="form-group"
v-if="conditionalField(showObserverEntry, false)"
v-class="has-error: validation.shiftEntry.member.invalid">
<label for="member">Member:</label>
<select name="member"
id="member"
v-model="shiftEntry.member"
options="roleStaff"
v-attr="disabled: !roleStaff.length"
class="form-control"
v-validate="requiredIf: conditionalField(showObserverEntry, false)">
<option value="">Select Member</option>
</select>
</div>
<div class="form-group"
v-if="conditionalField(showObserverEntry, true)"
v-class="has-error: validation.shiftEntry.observer.firstName.invalid">
<label for="observer_firstName">First Name:</label>
<input type="text"
id="observer_firstName"
class="form-control"
v-model="shiftEntry.observer.firstName"
v-validate="requiredIf: conditionalField(showObserverEntry, true)">
</div>
<div class="form-group"
v-if="conditionalField(showObserverEntry, true)"
v-class="has-error: validation.shiftEntry.observer.lastName.invalid">
<label for="observer_lastName">Last Name:</label>
<input type="text"
id="observer_lastName"
class="form-control"
v-model="shiftEntry.observer.lastName"
v-validate="requiredIf: conditionalField(showObserverEntry, true)">
</div>

It is because a bug in Vue.js. Reason: If we remove one or more v-model based on certain condition(v-if), then it will make all other validation to deactivate.
Refer the issue :https://github.com/vuejs/vue-validator/issues/69

Related

How to bind focusOut event to knockoutjs

I'm trying to bind focusout event to my knockout js. here is the example:
<div class="form">
<label>
Country:
</label>
<input type="text" id="countryName" name="countryId._autocomplete" data-bind="value: countryName,event: { blur: onBlurCountryEvent }" />
</div>
<div class="form" data-bind="visible: onBlurCountryEvent">
<label>
Time zone:
</label>
<input type="text" id="timeZoneName" name="timeZoneId._autocomplete" data-bind="value: timeZoneName" />
</div>
and this is my knockoutjs:
define(['viewmodels/shell', 'durandal/system', 'durandal/services/logger', 'plugins/router', 'knockout', 'common', 'jqueryform', 'toastr', 'kovalidationconfig'],
function (shell, system, logger, router, ko, common, jqueryform, toastr, kvc) {
var vm = {
activate: activate,
logger: logger,
shell: shell,
countryId: ko.observable(),
countryName: ko.observable(),
timeZoneName: ko.observable(),
timeZoneId: ko.observable(),
timeZoneVisibility: timeZoneVisibility,
bindingComplete: function (view) {
bindFindCountryEvent(view);
bindFindTimeZoneEvent(view);
}
};
vm.onBlurCountryEvent = function () {
var countryVal = $('#countryName').val();
if (countryVal != undefined && countryVal != null && countryVal != '') {
console.log("trueee");
return true;
}
else {
console.log("falseee");
return false;
}
}
function bindFindCountryEvent(view) {
jQuery("#countryName", view).typeahead(
...
}
function bindFindTimeZoneEvent(view) {
jQuery("#timeZoneName", view).typeahead(
...
}
function activate(id) {
shell.isLoading(true);
...
shell.isLoading(false);
});
return true;
}
vm.save = function () {
...
};
});
So, as you can see, I want to have some event and binded function, when I do onBlur from my field country, to check, and to preview timezone field if there any selected country from dropdown search.
Also, if user skips the country, timezone filed should remain visible:false
the event works, and I can see in my console true/false values.
However, my field of timeZone is intact. No matter if this country field is empty or non-empty, the fields is visible always.
If I put visible:false (hardcoded value), it works.
Should I need to bind that function vm.onBlurCountryEvent?
the problem is that the function onBlurCountryEvent is not an observable, so knockout is not checking for changes. I would suggest adding a isTimezoneVisible : ko.observable(false) to your view model then set the isTimeZoneVisible in the onBlurCountryEvent.
In your view set the visible binding to isTimeZoneVisible. Something like the following
var vm = {
countryId: ko.observable(),
countryName: ko.observable(),
timeZoneName: ko.observable(),
timeZoneId: ko.observable(),
isTimeZoneVisible: ko.observable(false), //new property
bindingComplete: function(view) {
bindFindCountryEvent(view);
bindFindTimeZoneEvent(view);
}
};
vm.onBlurCountryEvent = function() {
var countryVal = $('#countryName').val();
if (countryVal != undefined && countryVal != null && countryVal != '') {
console.log("trueee");
vm.isTimeZoneVisible(true); //set property
} else {
console.log("falseee");
vm.isTimeZoneVisible(false); //set property
}
}
function bindFindCountryEvent(view) {
}
function bindFindTimeZoneEvent(view) {
}
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="form">
<label>
Country:
</label>
<input type="text" id="countryName" name="countryId._autocomplete" data-bind="value: countryName,event: { blur: onBlurCountryEvent }" />
</div>
<div class="form" data-bind="visible: isTimeZoneVisible">
<label>
Time zone:
</label>
<input type="text" id="timeZoneName" name="timeZoneId._autocomplete" data-bind="value: timeZoneName" />
</div>

How can I check if fields are empty on Send Message button?

<template>
<div>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" v-model="firstName" placeholder="Enter your name">
</div>
<div class="form-group">
<label for="lastName">Last name</label>
<input type="text" class="form-control" v-model="lastName" placeholder="Enter your last name">
</div>
<div class="form-group">
<label for="message">Type Your message</label>
<textarea class="form-control" v-model="message" rows="3"></textarea>
</div>
<div class="form-group form-check" v-for="number in numbers" :key="number">
<input type="checkbox" :value="number.Broj" v-model="checkedNumbers">
<label class="form-check-label" >{{number.Broj}}</label>
</div>
<button type="submit" class="btn btn-primary" v-on:click="alert" #click="sendMessage">Send message</button>
</div>
</template>
<script>
import http from "../http-common.js";
import userServices from "../services/userServices.js";
export default {
data()
{
return {
firstName: null,
lastName: null,
message: null,
numbers: "",
checkedNumbers: [],
success: 'You have submitted form successfully'
};
},
methods:
{
async sendMessage()
{
await http.post("/message", {firstName: this.firstName, lastName: this.lastName, message: this.message, numbers: this.checkedNumbers});
this.$data.firstName = "",
this.$data.lastName = "",
this.$data.checkedNumbers = [],
this.$data.message = "";
},
alert() {
alert(this.success)
if(event)
alert(event.target.tagName)
},
retrieveNumbers() {
userServices.getNumbers().then(response => {
this.numbers = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
},
created() {
this.retrieveNumbers();
}
}
</script>
So I want to add the option of checking input fields when user clicks "Send Message" button. I tried some options but I faield at that. So please I would appretiate if someone would help me. I'm still learning.
I know I have to use v-if and create the method for checking the fields.
So if you would be most kind and help me solve this problem I would be really grateful.
Thank you dev, community <3
Can I please get a concrete answer. Because I'll learn in that way, so please without condescending and "no-answers"
You can do it manually :
<script>
import http from "../http-common.js";
import userServices from "../services/userServices.js";
export default {
data()
{
return {
firstName: null,
lastName: null,
message: null,
numbers: "",
checkedNumbers: [],
success: 'You have submitted form successfully'
};
},
methods:
{
async sendMessage()
{
if(!(this.firstName && this.lastName && this.numbers)) return;
await http.post("/message", {firstName: this.firstName, lastName: this.lastName, message: this.message, numbers: this.checkedNumbers});
this.$data.firstName = "",
this.$data.lastName = "",
this.$data.checkedNumbers = [],
this.$data.message = "";
},
alert() {
alert(this.success)
if(event)
alert(event.target.tagName)
},
retrieveNumbers() {
userServices.getNumbers().then(response => {
this.numbers = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
},
created() {
this.retrieveNumbers();
}
}
</script>
Or you can this usefull library
https://vuelidate.js.org/#sub-basic-form
You can simply define a method to check the fields and call that before the HTTP request in the sendMessage method.
You can initialize your data as an empty string "" and have a method like this:
validateForm() {
return this.firstName != "" && this.lastName != "" && this.message != ""
}
Update your sendMessage method to something like this:
async sendMessage() {
const isFormValid = this.validateForm()
if (isFormValid) {
await http.post(....)
...
}
}

Reset component data on click (Without $vm.forceUpdate())

I have the following data in an application form component.
data() {
return {
manuallyEnterAddress: false,
currentAddress: "",
postcode: undefined,
postcode2: undefined,
address: {
county: "",
town: "",
addressLine1: "",
atAddressFrom: "",
atAddressTo: ""
},
}
}
Once the application for is completed the data will look similar to the code below.
data() {
return {
manuallyEnterAddress: true,
currentAddress: "Some House",
postcode: SK1MPS,
postcode2: SK5N0Q,
address: {
county: "Cheshire",
town: "Chester",
addressLine1: "Random street",
atAddressFrom: "01/01/91",
atAddressTo: "01/01/2010"
},
}
}
When the form has been completed the user needs a way to reset the application form, returning the the first stepper, with blank fields.
Manually writing each field to reset would be horrific as there's at least ten times the data.
I've tried forceUpdate as shown below with no success.
newApplication() {
$vm.forceUpdate()
}
Is there a way I could use the "newApplication" function to reset all of the data on the component?
In your case there is no need to re-render the vue Component, which is what forceUpdate() will be doing forcefully. I will suggest using an object for modeling your form, lets say, formModel. For Example:
Template:
<form id="app" #submit="checkForm" method="post" novalidate="true">
<label for="name">Name</label>
<input type="text" name="name" id="name" v-model="formModel.name">
<label for="email">Email</label>
<input type="email" name="email" id="email" v-model="formModel.email">
<input type="submit" value="Submit">
</form>
<<ul>
<li v-for="error in errors">{{ error }}</li>
</ul>
JS:
let app = new Vue({
el: "#app",
data: {
errors: [],
formModel: {}
},
methods: {
checkForm: function(e) {
let self = this
self.errors = []
if (!self.formModel.name) {
self.errors.push("Name required.")
}
if (!self.formModel.email) {
self.errors.push("Email required.")
} else if (!self.validEmail(self.formModel.email)) {
self.errors.push("Valid email required.")
}
if (!self.errors.length) {
self.initializeForm()
};
e.preventDefault()
},
validEmail: function(email) {
let re = /^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/
return re.test(email)
},
initializeForm(){
self.formModel = {}
}
}
});
In this manner no matter how many input elements you have in your component you will just need to set the main model object.

Vee Validate field validation not updating

I have created a settings page where users can update their email addresses. Everything worked fine but suddenly the validation is not updating anymore. Only the first change of the input field triggers validateState().
Any further changes will not trigger this function so the status of that field stays as it is.
I have compared the code with other components that use the same code and they still work fine.
I am using bootstrap-vue components for the form.
<template>
<div class="row">
<div class="col-md-12">
<b-form #submit="onSubmit">
<b-form-group :label="$t('general.email')"
label-for="settingsEmail"
:invalid-feedback="errors.first('email')">
<b-form-input id="settingsEmail"
type="text"
v-model="form.email"
:disabled="saving"
name="email"
:state="validateState('email')"
v-validate="{required: true, email: true}">
</b-form-input>
</b-form-group>
<b-button type="submit" variant="primary" :disabled="saving || !hasChanged() || errors.any()"><i class="fa fa-refresh fa-spin fa-fw" v-if="saving"></i> {{$t('general.save')}}</b-button>
</b-form>
</div>
</div>
</template>
<script>
import {UPDATE_USER} from '../config/actions'
export default {
name: 'settingsAccount',
data() {
return {
form: {},
saving: false
}
},
computed: {
user: function() {
return this.$store.getters.getUser;
}
},
created() {
this.init();
},
methods: {
init() {
this.form.email = this.user.email;
},
hasChanged() {
if(this.form.email !== this.user.email) {
return true;
}
return false;
},
onSubmit(event) {
event.preventDefault();
this.saving = true;
this.$validator.validateAll().then((result) => {
if (result) {
let data = {};
if(this.form.email !== this.user.email) {
data.email = this.form.email;
}
this.$store.dispatch(UPDATE_USER, data).then(() => {
this.saving = false;
this.$validator.reset();
}).catch(() => {
this.saving = false;
});
} else {
this.saving = false;
}
});
},
validateState(ref) {
if (this.veeFields[ref] && (this.veeFields[ref].dirty || this.veeFields[ref].validated)) {
return !this.errors.has(ref)
}
return null
},
}
}
</script>
The problem you're having is that the form data element is an empty object, so it will only trigger reactivity when the whole object changes. Either you need to change your data to be this:
data() {
return {
form: {email:''},
saving: false
}
},
Or in your init function, explicitly add the email property as reactive:
methods: {
init() {
this.$set(form,'email',this.user.email)
},
//...
If you're not clear on why, you can read the details here: https://v2.vuejs.org/v2/guide/reactivity.html
A working example (minus vuex) here: https://codesandbox.io/s/x4kp93w3o
PS, when writing questions about vue, it's very helpful to boil it down to a simpler example. Get rid of vuex, remove your translation stuff. Sometimes the answer will jump out at you once you have it as simple as possible.

Knockout foreach array checkboxes are not visually updating

I have a json array that has elements created in a foreach databind.
Then I'm retaining the selected object in that array so that I can have independent "Save Changes" buttons for each object in that array. All of that is working (primarycontactname for example) except the binding for the checkboxes.
<div class="container span8" data-bind="foreach: locationssubscribed">
<div class="well span3" data-bind="click: $parent.selectedLocationSubscribed">
<input type="text" class="span3" data-bind="value: primarycontactname" placeholder="Contact Name.." />
<br />
<div class="checkbox" data-bind="visible: (vendorbringinggifts() === 0 || vendorbringinggifts() === vendorid())">
<input id="chkGiftsAreBeingBrought" type="checkbox" data-bind="checked: giftsarebeingbrought" />
</div>
<button data-bind="click: $root.saveVendorToLocation, enable: needsave, text: needsave() ? 'Save Location Changes' : 'No Changes to Save', css: { 'btn-primary': needsave }" class="btn">Save Location Changes</button>
</div>
</div
The checkboxes load correctly based on the giftsarebeingbrought observable in each array object but when clicking the checkbox the visible check doesn't toggle. Using the debugger I can see that the observable giftsarebeingbrought in the original array and in the selectedLocationSubscribed are toggling on the first click but then do not toggle again on subsequent clicks and the visual checkbox never changes after the initial binding.
{
"locationssubscribed": [
{
"vendortolocationid": 10,
"primarycontactname": "Fake Name1",
"vendorbringinggifts": 0,
"giftsarebeingbrought": false,
"needsave": false
},
{
"vendortolocationid": 11,
"primarycontactname": "Fake Name2",
"vendorbringinggifts": 0,
"giftsarebeingbrought": false,
"needsave": false
},
{
"vendortolocationid": 12,
"primarycontactname": "Fake Name3",
"vendorbringinggifts": 0,
"giftsarebeingbrought": false,
"needsave": false
},
{
"vendortolocationid": 13,
"primarycontactname": "Fake Name4",
"vendorbringinggifts": 0,
"giftsarebeingbrought": false,
"needsave": false
}
],
"selectedLocationSubscribed": {
"vendortolocationid": 12,
"primarycontactname": "Fake Name1",
"vendorbringinggifts": 0,
"giftsarebeingbrought": true,
"needsave": true
}
}
function VendorToLocation(vtl) {
this.vendortolocationid = ko.observable(vtl.VendorToLocationID);
this.primarycontactname = ko.observable(vtl.PrimaryContactName);
this.vendorbringinggifts = ko.observable(vtl.VendorBringingGifts);
this.giftsarebeingbrought = ko.observable(vtl.GiftsAreBeingBrought);
this.needsave = ko.observable(false);
}
function VendorViewModel() {
var self = this;
self.locationssubscribed = ko.observableArray();
self.selectedLocationSubscribed = ko.observable();
self.selectedLocationSubscribed.subscribe(function (ftl) {
if (ftl !== null) {
ftl.needsave(true);
}
});
self.getLocationsAvailable = function (vendorID) {
self.locationsavailable.removeAll();
$.ajax($("#GetLocationsAvailableUrl").val(), {
data: '{ "vendorID":' + vendorID + '}',
async: false,
success: function (allData) {
self.locationsavailable($.map(allData, function (item) { return new LocationsAvailable(item) }));
}
});
}
self.getLocationSubscription = function (vendorID) {
self.locationssubscribed.removeAll();
$.ajax($("#GetLocationSubscriptionUrl").val(), {
data: '{ "vendorID":' + vendorID + '}',
success: function (allData) {
self.locationssubscribed($.map(allData, function (item) { return new VendorToLocation(item) }));
}
});
}
self.saveVendorToLocation = function () {
var url = $("#updateVendorToLocationUrl").val();
var vendorid = self.selectedVendor().vendorid();
var selectedLoc = self.selectedLocationSubscribed();
$.ajax(url, {
data: '{ "vtl" : ' + ko.toJSON(selectedLoc) + '}',
success: function (result) {
if (result === false) {
toastr.error("ERROR!: Either you or a competing vendor has chosen this location since you last loaded the webpage. Please refresh the page.");
} else {
toastr.success("Vendor to location details saved");
selectedLoc.vendortolocationid(result.VendorToLocationID);
self.updateVendorView(); // added 170307 1030 to get vendor contact details to update automatically
self.getActionLog(vendorid);
selectedLoc.needsave(false);
}
}
});
};
}
$(document).ready(function () {
var myViewModel = new VendorViewModel();
ko.applyBindings(myViewModel);
myViewModel.updateVendorView();
myViewModel.getLocationSubscription(curVendorID);
}
The goal is to get the checkbox working correctly. The rest of the textbox based bindings I removed to condense the post have worked correctly for years some I'm now stumped as to what I'm doing wrong with the textbox.
Let me reconfirm, so you're trying to enable the associated button as well as check the checkbox if a user click on an element inside <div class="well span3 ...>".
I put all the suggestions in the code directly.
function VendorToLocation(vtl) {
var self = this;
self.vendortolocationid = ko.observable(vtl.vendortolocationid);
self.primarycontactname = ko.observable(vtl.primarycontactname);
self.vendorbringinggifts = ko.observable(vtl.vendorbringinggifts);
self.giftsarebeingbrought = ko.observable(vtl.giftsarebeingbrought);
self.needsave = ko.observable(vtl.needsave);
// I prefer to put all the logic in here instead of being embedded to the HTML
self.isCheckboxVisible = ko.pureComputed(function(){
return self.vendorbringinggifts() === 0 || self.vendorbringinggifts() === self.vendortolocationid();
});
}
function VendorViewModel() {
var self = this;
self.locationssubscribed = ko.observableArray(
[
new VendorToLocation ({
"vendortolocationid": 10,
"primarycontactname": "Fake Name1",
"vendorbringinggifts": 0,
"giftsarebeingbrought": false,
"needsave": false
}),
new VendorToLocation ({
"vendortolocationid": 11,
"primarycontactname": "Fake Name2",
"vendorbringinggifts": 0,
"giftsarebeingbrought": false,
"needsave": false
}),
new VendorToLocation ({
"vendortolocationid": 12,
"primarycontactname": "Fake Name3",
"vendorbringinggifts": 0,
"giftsarebeingbrought": false,
"needsave": false
}),
new VendorToLocation ({
"vendortolocationid": 13,
"primarycontactname": "Fake Name4",
"vendorbringinggifts": 0,
"giftsarebeingbrought": false,
"needsave": false
})
]
);
// To store the selected location
self.selectedLocationSubscribed = ko.observable();
// To modify selected location, enable the button and modify the checkbox whenever user click on an element that uses this as its click event
self.selectLocationSubscribed = function(data, event) {
if(data !== null) {
self.selectedLocationSubscribed(data);
// If you want to change needsave of other properties to false (disable all other buttons) before that you can do it here
ko.utils.arrayForEach(self.locationssubscribed(), function(location) {
if(data.vendortolocationid() !== location.vendortolocationid()){
location.needsave(false);
location.giftsarebeingbrought(false);
}
});
// code ends here
// And then you modify the selected needsave the selected object to true to enable the button
data.needsave(true);
data.giftsarebeingbrought(true);
}
// To perform the default browser click action
return true;
}
}
$(document).ready(function () {
var myViewModel = new VendorViewModel();
ko.applyBindings(myViewModel);
});
<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/3.2.0/knockout-min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<div class="container span8" data-bind="foreach: locationssubscribed">
<div class="well span3" data-bind="click: $parent.selectLocationSubscribed">
<input type="text" class="span3" data-bind="value: primarycontactname" placeholder="Contact Name.." />
<br />
<div class="checkbox" data-bind="visible: (vendorbringinggifts() === 0 || vendorbringinggifts() === vendorid())">
<input id="chkGiftsAreBeingBrought" type="checkbox" data-bind="checked: giftsarebeingbrought" />
</div>
<button data-bind="click: $root.saveVendorToLocation, enable: needsave, text: needsave() ? 'Save Location Changes' : 'No Changes to Save', css: { 'btn-primary': needsave }" class="btn">Save Location Changes</button>
</div>
</div>
The click binding prevents the default action. To enable the default action, return true from the event handler. This means you can't directly pass an observable to the click binding.
click: $parent.handleClick
JS:
self.handleClick = function (item) {
// do something with item
return true; // don't prevent default action
}

Categories

Resources