VueJS with Large Forms - forms

I have a huge form with 20+ fields. i feel so much redundancy on the code i write now. What is the best way ?
<script>
new Vue({
data : {
user : {
first_name : "",
last_name : "",
username : "",
and 20+.........
}
}
});
</script>
<form>
<input name="first_name" v-model="first_name">
<input name="last_name" v-model="last_name">
<input name="username" v-model="username">
and 20+......... input fields
</form>
i feel something like this would be nice. the user object will be created dynamically.. is this possible ?
<script>
new Vue({
data : {
user : Object
}
});
</script>
<form v-model="user">
<input name="first_name">
<input name="last_name">
<input name="username">
and 20+......... input fields
</form>
Thank you in advance

Completely Redone in Vue 2
Your approach is the reverse of the usual Vue approach, in that you want to lay out your data structure in the view and have Vue pick it up, rather than laying it out in the data and having Vue render it. I can see how that would be desirable if you have a customized layout you want to achieve.
Unconventional needs require unconventional solutions, so this approach is unconventional. In particular, it is not generally recommended that a child component modify data in a parent.
That said, the approach is to create a component that will accept the form inputs as a slot and the parent object as a prop. In mounted, it gets all the input fields with name attributes and
Creates the member in the parent object, using $set
Sets a watch on the newly-created member
Adds an input event listener to complete the two-way binding
You would probably want to add more props to the component, to make the form itself more versatile, but this gives you the functionality you're looking for.
Vue.component('autoBindingForm', {
template: '<form><slot></slot></form>',
props: ['createIn'],
mounted() {
const inputs = this.$el.querySelectorAll('input[name]');
for (const i of inputs) {
this.$set(this.createIn, i.name, i.value);
this.$watch(`createIn.${i.name}`, (newVal, oldVal) => {
i.value = newVal;
});
i.addEventListener('input', (e) => {
this.createIn[i.name] = i.value;
});
}
}
});
const vm = new Vue({
el: '#app',
data: {
user: {}
}
});
// Testing that binding works both ways
setTimeout(() => {
vm.user.last_name = 'Set it';
}, 800);
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<auto-binding-form :create-in="user">
<input name="first_name" value="hi">
<input name="last_name">
<input name="username">
<div>{{Object.keys(user)}}</div>
<div>{{Object.values(user)}}</div>
</auto-binding-form>
</div>

How about
data: {
fields: { name: {v:''}, surname: {v:''}, ... }
}
and
<input v-for="(val, prop) in fields" :name="prop" v-model="val.v" />
?
https://jsfiddle.net/gurghet/dhdxqwjv/1/

Related

Formdata post with vuex

I am very new to vuex and vue-js. I am trying to post my forms data using vuex. So far I managed to pass the Title successfully but I don’t know how to pass more than one piece of data?
data to pass:
// Title
<input type="text" name="subject_title">`
// selected data
<datepicker id="set_date" name="set_date"></datepicker>
// array of options
<multiselect v-model="value" :max-height="0" :options="options" :searchable="false" :multiple="true" group-label="language" track-by="name" label="name" placeholder=""><span class="arrow" slot="caret"></span></multiselect>
I have put some of my code on github: https://github.com/samB67/VuexPost
You can create a object to store your whole form in your file upload.vue;
data() {
//...
homeworkForm: {
title: '',
date: null,
somethingElse: ''
}
}
Now in your form, you simply put the v-model to bind to each corresponding keys from your homeworkForm object;
<input v-model="homeworkForm.title" type="text" class="form-control" id="formGroupExampleInput" placeholder="Enter text">
<!-- ... -->
<datepicker v-model="homeworkForm.date" id="set_date" name="set_date" />
Then when you want to submit the form, you send the whole homeworkForm object to vuex.
postHandler() {
this.$store.dispatch('createHomework', this.homeworkForm);
},

How to change the state of a vue when you click the check button

I am creating a Todo Application using Vue.js, Express.js, and MongoDB.
I want to change the state of the fields that appear as v-for through the checkbox.
The code I want is to change the state of the text when the checkbox is checked and to show another element with v-if.
Here is the code I wrote: But it does not work.
If I have the wrong code, please help me.
List Component
<template>
<div class="todos">
<div v-for="todo in todos" v-bind:key="todo._id" class="todo">
<div>
<input type="checkbox" v-model="todo.done" v-on:click="completeTodo(todo)">
<del v-if="todo.done">{{todo.title}}</del>
<strong v-else>{{todo.title}}</strong>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
todos: {}
}
},
created () {
this.$http.get('/api/todos')
.then((response) => {
this.todos= response.data
})
},
methods: {
completeTodo (todo) {
todo.done = !todo.done
}
}
}
</script>
You don't need v-on:click="completeTodo(todo)". v-model is already doing the trick for you. Also, the todos in your code should be defined and instantiated as array not an object.
v-model is used for two way data binding. That means, whatever data you pass from your code will be bound to the checkbox value in this case and whenever a change is made from UI and value of checkbox is altered by user, that will be captured in v-model variable. v-model is a combo of :value prop and #change event in case of checkbox, hence, it is able to update data in both the ways.
Please refer this snippet.
var app = new Vue({
el: '#app',
data: {
todos: [
{
id: 1,
title: 'Study',
done: false
},
{
id: 2,
title: 'Work',
done: false
},
{
id: 3,
title: 'Play',
done: false
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="todos">
<div v-for="todo in todos" :key="todo.id">
<div>
<input type="checkbox" v-model="todo.done"/>
<del v-if="todo.done">{{todo.title}}</del>
<strong v-else>{{todo.title}}</strong>
</div>
</div>
</div>
</div>

Vue reactive forms components communication

I have multiple form components, each form as a component. Now, I want to use same component for adding data and editing data. So what I am thinking to do is something like when the Post component receives a prop containing data that means it is in a "editing mode" and populate the fields with its data, if not it is in "create mode".
So how should I use v-model in my form fields?
Should I v-model each form field to a computed property (which has a getter and a setter) and the computed property would check if the data prop is empty and if not use its data to populate fields ? And in the computed property set method to update the prop ?
parent component
<post :data.sync="dataObject"></post>
child (Post) component
<template>
<div>
<form>
<input type="text" label="title" v-model="computedTitle" />
<input type="text" label="message" v-model="computedMessage" />
</form>
</div>
<input type="button" #click="submitted"
<template>
<script>
export default {
data(){
return{
post:{
title:null,
message:null
}
}
},
props:["data"],
computed:{
computedTitle:{
get(){
return data ? data.title : ''
},
set(computedTitle){
computedTitle = computedTitle // trying to update computed property value with the field value...
}
},
computedMessage:{...}
}
}
</script>
You can use watch to check data prop, if it's set then set to local post variable.
If created, then data is null, post.title and post.message are set to null
If updated, then data is not null, post.title is set to data.title and post.message to set to data.message
<template>
<div>
<form>
<input type="text" label="title" v-model="post.title" />
<input type="text" label="message" v-model="post.message" />
</form>
</div>
<input type="button" #click="submitted"
<template>
<script>
export default {
data() {
return{
post: {
title: null,
message: null
}
}
},
props:["data"],
watch: {
data: {
handler(newData) {
if (newData) {
this.post = {
title: newData.title,
message: newData.message
}
}
},
immediate: true // this makes watch is called when component created
}
}
}
</script>
Note that you should use immediate: true to make the watch's function called when component is created

How to force Angular 2 to re-check validator?

I have a simple login form written in Angular 2 reactive (data-driven) template. It's working perfectly but when I refresh the page and browser fills e-mail+password (autocomplete), my form's valid property seems false.
But when I press any key or click anywhere in my page while form is invalid, angular updates some states (I guess) and my form become valid.
How can I trigger that state? How can I say angular "Hey, check my form again."? I can't trigger my own validation script because some validation messages are alerts. If I do this, when user open this page, he/she will see these alerts.
I remember, I use trigger('input') trick to update ng-model.
updateValueAndValidity() is what you are looking for. The document is here: AbstractControl. It can force recalculate the value and validation status of the control.
Here's a demo:
form.component.ts
export class FormComponent implements OnInit {
myform: FormGroup;
// explicit validation of each field
emailValid: AbstractControl;
passwordValid: AbstractControl;
constructor(private fb: FormBuilder) {
this.myform = fb.group({
'email': ['', Validators.required],
'password': ['', Validators.required],
});
this.emailValid = this.myform.controls['email'];
this.passwordValid = this.myform.controls['password'];
}
ngOnInit() {
this.myform.get('email').valueChanges.subscribe(()=>forceValidAgain());
this.myform.get('password').valueChanges.subscribe(()=>forceValidAgain());
}
forceValidAgain(){
this.emailValid.updateValueAndValidity();
this.passwordValid.updateValueAndValidity();
}
}
form.component.html
<form [formGroup]="myform" (ngSubmit)="onSubmit(myform.value)">
<div class="form-group">
<label for="email">Email</label>
<input type="email"
class="form-control"
id="email"
name="email"
[formControl]="myform.controls['email']"
[ngClass]="{'is-invalid': !emailValid.valid && emailValid.touched }">
<div
class="invalid-feedback"
*ngIf="emailValid.hasError('required')">
This field is required
</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password"
class="form-control"
id="password"
name="password"
[formControl]="myform.controls['password']"
[ngClass]="{'is-invalid': !passwordValid.valid && passwordValid.touched}">
<div
class="invalid-feedback"
*ngIf="passwordValid.hasError('required')">
This field is required
</div>
</div>
</form>
I would suggest creating a method like onValueChanged (that is mentioned in Angular2 docs), bind it to your form, and execute it while the component is initialized. So the binding to the form changes should be done in this way:
this.form.valueChanges.subscribe((data) => {
this.onValueChanged(data)
});
And execution for example just the line after like this:
this.onValueChanged();
This execution should solve your problem via validation during component initialization.
I think that the method implementation from the docs (below) is pretty clear.
onValueChanged(data?: any) {
if (!this.heroForm) { return; }
const form = this.heroForm;
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
Docs I am referring to: https://angular.io/docs/ts/latest/cookbook/form-validation.html

In Redux, how to get user input

I have a form, how to get the use input in the handleSubmit() method?
handleSubmit(e) {
e.preventDefault()
//how to get the user input?
}
render() {
return (
<div className="col-sm-4">
<form onSubmit={this.handleSubmit}>
<input type="text" placeholder="user"/>
<input type="text" placeholder="comments"/>
<input type="submit" hidden/>
</form>
</div>
)
}
so far, I know three solutions:
The first one, use refs, but I can see there are lots of people saying that we should avoid using it
The second one, add onChange() to each <input>, e.g.
class Example extends React.Component {
state = {
inputValue: ""
};
handleInputChanged(e) {
this.setState({
inputValue: e.target.value
});
}
render() {
return (
<div>
<input onChange={this.handleInputChanged.bind(this)} value={this.state.inputValue}>
</div>
);
}
}
this one is fine with a few inputs. But if the form has 20 input fields, then there are 20 different onChange methods?
third, use some npm module, like redux-form.
any other suggestion? Thanks
You can actually just do an onChange on the parent form like so:
onChange(e) {
switch(e.target.type) {
case 'checkbox':
this.setState({ [e.target.name]: e.target.checked });
break;
default:
this.setState({ [e.target.name]: e.target.value });
break;
}
}
// in render
<form onChange={this.onChange.bind(this)}>
<input name="foo1" />
<input name="foo2" />
<input name="foo3" />
<input name="foo4" />
<input name="foo5" />
<input name="foo6" />
<input name="foo7" />
<input name="foo8" />
</form>
There are certain libraries like https://github.com/christianalfoni/formsy-react, https://github.com/prometheusresearch/react-forms. These forms have additional functions pre written for form submitting, validations. I think using refs is a tedious and unwanted task if the form is big with the reason being that if it is controlled form you need to access the state value for controlled components which brings unnecessary complications. You can do it but it is better to use prewritten libraries.