I am using Vue CLI and Bootstrap and am having trouble with the form validation currently when the page loads all the input fields load as invalid. I can see why this is happening because the input fields are getting a class of is-invalid. I have fixed this by passing the state prop a value of null when it is false. It does not seem like the default behavior should be to run the validation when the page loads but maybe it is. I believe I have everything set up correctly as far as structure and proper classes I followed the bootstrap-vue docs.
My Code
<b-form
#submit.prevent="addReview"
name="review-form"
class="needs-validation"
novalidate
>
<div class="name">
<label class="sr-only" for="form-input-name">Name</label>
<b-input
id="form-input-name"
class="form-inputs mb-2 mr-sm-2 mb-sm-0"
v-model="name"
placeholder="Name"
required
:state="isEmpty(this.name) ? true : null" <---- My problem is here
></b-input>
...
</b-form>
My problem is I need 3 results from this ternary which obviously isn't possible. I need null on load to remove the error messages then false to display error on validation and true to display valid input. I have been struggling with this for days so any help with any aspect of this setup would be greatly appreciated if you want more code let me know. The submit button adds a class of was-validated which does display any error messages that are associated with empty inputs but doesn't validate the inputs.
Question
How do I validate inputs while still keeping form error messages hidden on load.
You aren't bound to just using a ternary statement in the :state prop - you can hook :state up to a computed property directly, which will achieve three things (this is similar to what is shown in the documentation):
We can have more than two conditions, breaking out of the limitations of the ternary statement.
The computed property will analyze user input on the fly, ensuring real-time validation of the form input.
Our template code will be cleaner and more readable (important).
I'm working loosely off of your example, but something like the following should solve your issue:
<template>
<div id="app">
<img width="25%" src="./assets/logo.png" style="margin-bottom: 15px;">
<b-form>
<div class="name">
<label class="sr-only" for="form-input- name">Name</label>
<b-input v-model="name" id="form-input-name" :state="isNameStateValid"></b-input>
</div>
</b-form>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
name: ""
};
},
methods: {
isValid() {
return this.name.length > 3 ? true : false; //your validation criteria goes here
}
},
computed: {
isNameStateValid() {
if (this.name) {
return this.isValid(this.name);
}
return null;
}
}
};
</script>
In the above, you would have a method that would check for your specific validation criteria (isValid, isEmpty, etc.).
Our new computed property, isNameStateEmpty, will use that method's return value, returning false for a validation failure (triggering BootstrapVue's failed validation state), true for a validation pass, or null in the event that this.name does not have a current value (examples being a fresh page load, or a user clearing the input field, making it blank).
See a working Codesandbox of this behavior here.
Because the input's v-model (v-bind:value and #change) is set to our "name" data property, every character change within the input field will reactively update our data property (this.name).
Because our isNameStateValid computed property has a dependency of this.name, it will reevaluate on every change of the this.name data property - ensuring real-time validation using BootstrapVue's validation state.
Hopefully that helps.
Related
I'm using Vee Validte 3.x in my Nuxt JS/Vue project. I need to be able to get the classes from a particular Validation Provider element by it's name or some unique identifier and output the classes object somewhere else on my page.
I'm struggling to figure out how, and thought a ref would work but didn't.
Here's my code:
<!-- Data Source (Table) (Validation Workaround) -->
<validation-provider
tag="div"
name="data source"
:rules="{ required: { allowFalse: false } }"
v-slot="{ errors, classes }"
>
<CustomInput
class="hidden-spans"
:options="editor.sources"
v-model="mockedCheckboxes.source.isChecked" />
<span class="block text-xs text-red-500 mt-1">{{ errors[0] }}</span>mockedCheckboxes.source.classes = classes" />
</validation-provider>
I'd like to be able to do something like:
this.$validator('data source').classes
Somewhere in my Vue file so that I can use the classes from one input elsewhere, this isn't working though.
this.$validator was actually removed in favor of ValidationObserver: https://vee-validate.logaretm.com/v3/migration.html#migrating-from-2-x-to-3-0
So far, if you try to validate something that is not directly in the component you're working on, you should watch the flags of the validation (errors) and update the status on a Vuex store. Then access Vuex back to decide if the input is valid and proceed further.
There is no global validation to my knowledge.
I have a form with some inputs, im submitting the form and displaying the data in a modal. It works but im not sure im doing it the right way and the code looks messy.
This is how the HTML looks (without the modal for the sake of simplicity):
<div id="app">
<form #submit.prevent="getValues">
<label>Last Name: </label><input type="text" name="lastName">
<br>
<button>Submit</button>
</form>
</div>
This is the Vue instance and getValues function:
let app = new Vue({
el: '#app',
data:{
lastName: ''
},
methods:{
getValues(submitEvent){
this.lastName = submitEvent.target.elements.lastName.value
}
}
})
In order to shorten the code a bit, i implemented v-model:
HTML:
<div id="app">
<form #submit.prevent="getValues">
<label>First Name: </label><input type="text" v-model="firstName">
<button>Submit</button>
</form>
</div>
Vue:
let app = new Vue({
el: '#app',
data:{
firstName: ''
},
methods:{
getValues(submitEvent){
firstName = this.firstName
}
}
})
It works too but firstName = this.firstName is confusing to me. I tried changing the names to understand the references but it wont work. Is the variable firstName inside getValues being used at all or is the value being rendered due to v-modal two way binding (and i cant tell because the modal isnt rendered until the form is submitted)?
Heres a JSFiddle including both approaches.
Which one is correct? Are they interchangeable? Whats the difference between them?
One method is using events, the other is using 2-way binding.
When you submit the form, the associated event fires a method (#submit.prevent="getValues"). Inside this method you read the value of name and set the lastName property to equal it.
When you do v-model="firstName" Vue sets up 2-way binding between the input field value and the firstName property.
You see the difference in that when you type into the input box, it immediately updates the property. Behind the scenes Vue is handling the change event, and updating the property. Equally, if you modified the data property some other way, the value in the input box would also update.
There's also a 3rd option of 'simple' binding. This is achieved by using the v-bind tag. This will do the updating of the data property in the same way, but won't modify the input box value 'in reverse' if changed elsewhere.
I am working for the first time with reactive forms in Angular(v7)
I want to make my submit button enabled if any of the fields has changed. So I am working with the dirty value of the form.
For a simple scenarios its working. I change a input type text and the button became enable.
But now I got a problem with an element (<span id="locationUpdated">) that the value inside of it is being changed by the result of some other javascript functions.
So I decided to listening the change of an hidden input that get the value the same way as the span element
<form [formGroup]="model.form" >
....
<input id="nameInput" type="text"
[(ngModel)]="model.user.name"
formControlName="name" />
...
<span [textContent]="model.user.location.label" id="locationUpdated"></span>
<input type="hidden" [value]="model.user.location.label" formControlName="location" />
....
<button [ngClass]="{'disabled': !model.form.dirty}></button>
</form>
--- Component ---
private buildFormUpdated() {
this.model.form = this.formBuilder.group({
name: [this.model.user.name, [Validators.required]],
location: [this.model.user.location.label]
});
}
I replace the hidden to text so I can see the value change and it is working fine.
However the property dirty continues false. If I change manually I get the dirty:true
What am I missing?
Thanks
What am I missing?
The dirty flag is not set to true when the data is changed programatically.
It is set to true only if the user blurs the control component, or changes the value (through the UI)
Please ref official docs - https://angular.io/guide/form-validation#why-check-dirty-and-touched
you can explicit marks the control as dirty by doing this after your logic
this.model.form.markAsDirty();
I have an Angular4 app that captures data from a form and stores in DynamoDB. It uses ngModel to support two-way data binding and on the display all looks good. Issue comes into play because of an input field typed as 'text' bound to Typescript 'number' field. Seems to be changing the type of the object value to 'string'. I would simply change the HTML Input type to 'number' except for the unnecessary and undesirable increment/decrement decorators on the form field (and hiding them seems to have limited support). So I was curious if there is another way to keep the data structure typed as desired... if this is a bug in ngModel... or if the input type just simply needs to be 'number'.
The structure in my sample.component.ts file looks like this:
export class Course {
Id: number;
Name: string;
}
...
courseInstance: Course;
saveCourse() {
JSON.stringify(this.courseInstance);
}
My sample.component.html file looks like this:
<div>
<label for="courseid">Course ID: </label>
<input type="text" class="form-control"[(ngModel)]="courseInstance.Id" name="courseid">
</div>
<div>
<label for="courseName">Course Name: </label>
<input type="text" class="form-control"[(ngModel)]="courseInstance.Name" name="courseName">
</div>
<div>
<button type="button" class="btn btn-info btn-lg" (click)="saveCourse()">Save</button>
</div>
Output of JSON.stringify(this.courseInstance) results in something like
{"Id":"100","Name":"Test course"}
Notice the value 100 represented as a string.
If I don't use the form, but simply create an instance such as
courseInstance: Course = {
Id: 100,
Name: 'Test course'
};
Then when outputing result of JSON.stringify(courseInstance); I get
{"Id":100,"Name":"Test course"}
If I try to use PutItem to store object in DynamoDB, the Id value fails type check when data comes from HTML form.
I would have thought that the typing in Typescript would have held precedence over the HTML 'text' input type.
Change the type text to the number to make the type number and don't let to write letters.
<input type="number" class="form-control"[(ngModel)]="courseInstance.Id" name="courseid">
The [(ngModel)] syntax can only set a data-bound property. If you need to do something more or something different, you can write the expanded form.
<div>
<label for="courseid">Course ID: </label>
<input type="text" #ref class="form-control" [ngModel]="courseInstance.Id" (ngModelChange)="onDataChange(ref.value)"name="courseid">
</div>
The ngModel data property sets the element's value property and the ngModelChange event property listens for changes to the element's value.use parseInt to get the desired result.
ngModelChange will be fired on every keystroke you need to debounce value else event will be emitted for every keystroke and To debounce values you can use a Subject with debounceTime() operator.A subject is both an observable and an observer. This means you can treat it as an observable and pass values to it as well. Apart from this use Template reference variable
In your component
import { Subject } from 'rxjs/Subject';
import {debounceTime } from 'rxjs/operators
debouncer= new Subject();
constructor() {
this.debouncer
.debounceTime(1000)
.subscribe((val) =>{
console.log(val);
this.courseInstance.id=ParseInt(val,10);
});
}
onDataChange(value) {
this.debouncer.next(value);
}
Angular currently doesn't support type checking in the property binding and event binding. As you can see here, there is an open issue in their github:
https://github.com/angular/angular/issues/16952
What I suggest you to do is to change your input to type number and add this css in your component:
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
EDIT: adding an application in the Stackblitz:
https://stackblitz.com/edit/angular4-ngmodel-changes-type-of-data-from-number-to-string
Noob at polymer here, so please bear with me.
I'm trying to learn how to create a form, one that requires the user to input text into a textbox, before hitting "Submit". Should the user hit "Submit" without anything in the textbox, the textbox is highlighted red, and displays an error message, etc.
Here's my code (no validation yet) so far:
<dom-module id="accountability-ticket">
<template>
<paper-dialog with-backdrop entry-animation="scale-up-animation" exit-animation="fade-out-animation" id="diagTicket">
<h2>I Own It Ticket</h2>
<div>
<paper-input-container id="gcashDeco" required error="GCash Ref. Required">
<input id="gcashText" is="iron-input">
</paper-input-container>
<div class="ctrlButtons flex">
<paper-button dialog-dismiss>Cancel</paper-button>
<paper-button on-click="confirmClick">Submit</paper-button>
</div>
</div>
</paper-dialog>
</template>
</dom-module>
<script>
Polymer({
is: "accountability-ticket",
confirmClick: function(event){
console.log(event);
var gCashDeco = document.getElementById('gcashDeco');
var gCashText = document.getElementById('gcashText');
}
});
</script>
I've been reading the Polymer documentation, and so far came up with two things:
<paper-input> doesn't validate, per se, according to v0.5 - It must be wrapped in <paper-input-decorator> first.
Version 1.0 is even less clear than that, with <paper-input-container> instead of <paper-input-decorator>, and mixed tags in the demo pages.
Given that I want to stick with the latest version (v1.0), what do I need to add to my code to get it to check if the textbox is empty, and display an error message if it is?
Thanks.
Yep, the Polymer docu is somewhat confusing, but as a general rule of thumb: always have a look at the behaviours the element is equiped with.
So, paper-input (in 1.0) comes with PaperInputBehavior and this implies that you can simply write the following:
<paper-input label="Input label" required error-message="Field required!"></paper-input>
<paper-input label="Input label" minlength="4" maxlength="10" auto-validate></paper-input>
<paper-input label="Input label" pattern="MY_REGEX" auto-validate></paper-input>
<paper-input label="Input label" validator="myvalidator"></paper-input>
auto-validate makes the input – of course – validate as it is being typed into. myvalidator must be an element implementing the IronValidatorBehavior and inserted somewhere on the page. If you don't want the fields to be auto-validating or wanna do it yourself, call validate() on that field or set the invalid-flag and the error message will be shown. You can even adjust the message programmatically.
While the validator seems to be useful, I've found it to be simple enough to test the inputs directly. This will do what you need:
...
<div>
<paper-input-container id="gcashDeco">
<paper-input-error>Field is empty</paper-input-error>
<input id="gcashText" is="iron-input" value="{{gcashInput::input}}">
</paper-input-container>
<div class="ctrlButtons flex">
<paper-button dialog-dismiss>Cancel</paper-button>
<paper-button on-tap="confirmClick">Submit</paper-button>
</div>
</div>
...
...
Polymer({
is: "accountability-ticket",
confirmClick: function() {
if (this.gcashInput == null)
{
//show error
this.$.gcashDeco.invalid = true;
}
}
This paper-input-error element is referenced by the id of whatever paper-input-container it is inside of. Setting it's invalid property to true shows the error, and false hides it.
<paper-input-error>Field is empty</paper-input-error>
This next snippet binds the field's input value to a variable this.gcashInput which you can access inside confirmClick or any other method.
{{gcashInput::input}}
As a final note, getting id's of elements inside of your Polymer element is done like this:
this.$.gcashDeco
Not the way you would with vanilla Javascript:
document.getElementById('gcashDeco');
The latter, vanilla JS way, would search the main DOM, not the Shadow DOM where your element resides. So, use document.getElementById() if you need to search the DOM, and use this.$.elemendId if you need to search your element for an id.