Getting asynchronous values from ant-design vue form using onValuesChange - forms

I have a form that submits and send data to the backend using ant-design-vue. However, what I would like to achieve is give the user some form of feedback so while they type in the field they get to see the value {fullname placeholder} updated immediately, and clicking on the submit button sends it to the backend altogether.
{{ fullname || 'Your Name' }}
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }">
<a-form-item label="Full Name">
<a-input
type="text"
placeholder="Your Name"
:disabled="toggleEdit === 'edit'"
v-decorator="[
'fullname',
{
initialValue: this.fullname || '',
rules: [{ required: true, message: 'Name is required!' }],
},
]"
autocomplete="name"
/> </a-form-item
></a-col>
So the {{ fullname }} at the top updates immediately the user types Similar to v-model. But I would like to know how I can achieve this in ant-design-vue form with the onValuesChange method.

You need to use v-model to bind your value on inputfirstly.
meanwhile, use #click on button with submit method
<a-input
type="text"
v-model="inputValue" <---here
placeholder="Your Name"
:disabled="toggleEdit === 'edit'"
v-decorator="['fullname',
{
initialValue: this.fullname || '',
rules: [{ required: true, message: 'Name is required!' }],
},
]"
autocomplete="name"/>
<button #click="submit"></button>
...
data(){
return{
inputValue: ""
}
}
methods:{
submit(){
// I send keyword with a object which include this inputValue by POST method
// you can change it to yours, and keyword as well
fetch("api-url").post({ keyword: this.inputValue })
.then(response.json())
.then(res => { //dosomthing when you get res from back-end })
}
}
Does above code is your requirement?

Related

Bootstrap-vue: Auto-select first hardcoded <option> in <b-form-select>

I'm using b-form-select with server-side generated option tags:
<b-form-select :state="errors.has('type') ? false : null"
v-model="type"
v-validate="'required'"
name="type"
plain>
<option value="note" >Note</option>
<option value="reminder" >Reminder</option>
</b-form-select>
When no data is set for this field I want to auto-select the first option in the list.
Is this possible? I have not found how to access the component's options from within my Vue instance.
your v-model should have the value of the first option.
example
<template>
<div>
<b-form-select v-model="selected" :options="options" />
<div class="mt-3">Selected: <strong>{{ selected }}</strong></div>
</div>
</template>
<script>
export default {
data() {
return {
selected: 'a',
options: [
{ value: null, text: 'Please select an option' },
{ value: 'a', text: 'This is First option' },
{ value: 'b', text: 'Selected Option' },
{ value: { C: '3PO' }, text: 'This is an option with object value' },
{ value: 'd', text: 'This one is disabled', disabled: true }
]
}
}
}
</script>
You can trigger this.selected=${firstOptionValue} when no data is set.
what if we don't know what the first option is. The list is generated?
if you have dynamic data, something like this will work.
<template>
<div>
<b-form-select v-model="selected" :options="options" />
<div class="mt-3">Selected: <strong>{{ selected }}</strong></div>
</div>
</template>
<script>
export default {
data() {
return {
selected: [],
options: [],
};
},
mounted: function() {
this.getOptions();
},
methods: {
getOptions() {
//Your logic goes here for data fetch from API
const options = res.data;
this.options = res.data;
this.selected = options[0].fieldName; // Assigns first index of Options to model
return options;
},
},
};
</script>
If your options are stored in a property which is loaded dynamically:
computed property
async computed (using AsyncComputed plugin)
through props, which may change
Then you can #Watch the property to set the first option.
That way the behavior of selecting the first item is separated from data-loading and your code is more understandable.
Example using Typescript and #AsyncComputed
export default class PersonComponent extends Vue {
selectedPersonId: string = undefined;
// ...
// Example method that loads persons data from API
#AsyncComputed()
async persons(): Promise<Person[]> {
return await apiClient.persons.getAll();
}
// Computed property that transforms api data to option list
get personSelectOptions() {
const persons = this.persons as Person[];
return persons.map((person) => ({
text: person.name,
value: person.id
}));
}
// Select the first person in the options list whenever the options change
#Watch('personSelectOptions')
automaticallySelectFirstPerson(persons: {value: string}[]) {
this.selectedPersonId = persons[0].value;
}
}

How To Properly Initialize Form Data In Vue

so I have a component that is rendering a form and it also is pre-filling the fields with data received from ajax request.
My issue is that I want to not only be able to edit fields but also add new fields to submit at the same time, so because of this I am trying to initialize my pre-filled data and new data into the same Object to be submitted with my ajax request. With my current set up the form-data is not consistently filling in the fields before the form is rendered.
This is the form template
<form #submit.prevent="editThisWorkflow" class="d-flex-column justify-content-center" >
<div>
<input type="text" v-model="workflowData.workflow">
</div>
<div >
<div v-for="object in workflowData.statuses" :key="object.id">
<input type="text" v-model="object.status">
</div>
<div v-for="(status, index) in workflowData.newStatuses" :key="index">
<input type="text" placeholder="Add Status" v-model="status.value">
<button type="button" #click="deleteField(index)">X</button>
</div>
<button type="button" #click="addField">
New Status Field
</button>
</div>
<div>
<div>
<button type="submit">Save</button>
<router-link :to="{ path: '/administrator/workflows'}" >Cancel</router-link>
</div>
</div>
</form>
This is the script
data() {
return {
workflowData: {
id: this.$store.state.workflow.id,
workflow: this.$store.state.workflow.workflow,
statuses: this.$store.state.workflow.statuses,
newStatuses: []
},
workflowLoaded: false
}
},
computed: {
...mapGetters(['workflow']),
},
methods: {
...mapActions(['editWorkflow']),
editThisWorkflow() {
this.editWorkflow({
id: this.workflowData.id,
workflow: this.workflowData.workflow,
statuses: this.workflowData.statuses,
newStatuses: this.workflowData.newStatuses
})
},
addField() {
this.workflowData.newStatuses.push({ value: ''});
},
deleteField(index) {
this.workflowData.newStatuses.splice(index, 1);
}
And this is the store method to submit the data
editWorkflow(context, workflowData) {
axios.patch('/workflowstatuses/' + workflowData.id, {
workflow: workflowData.workflow,
statuses: workflowData.statuses,
newStatuses: workflowData.newStatuses
})
.then(response => {
context.commit('editWorkflow', response.data)
})
.catch(error => {
console.log(error.response.data)
})
},
My problem comes in here
data() {
return {
workflowData: {
id: this.$store.state.workflow.id,
workflow: this.$store.state.workflow.workflow,
statuses: this.$store.state.workflow.statuses,
newStatuses: []
},
workflowLoaded: false
}
},
Is there a better way to set this part??
workflowData: {
id: this.$store.state.workflow.id,
workflow: this.$store.state.workflow.workflow,
statuses: this.$store.state.workflow.statuses,
newStatuses: []
},
If you only need to assign store values to your form once then you can use mounted function.
mounted: function() {
this.id = this.$store.state.workflow.id
this.workflow = this.$store.state.workflow.workflow
this.statuses = this.$store.state.workflow.statuses
},
data() {
return {
workflowData: {
id: '',
workflow: '',
statuses: '',
newStatuses: []
},
workflowLoaded: false
}
},
the data property does not accept this, I usually use arrow function in this question because it prohibits me from using this, and prohibits my team from also using this within the data.
Declare all necessary items within the datato maintain reactivity, and assign the value within the mounted of the page.
mounted() {
this.workflowData.id = this.$store.state.workflow.id
this.workflowData.workflow = this.$store.state.workflow.workflow
this.workflowData.statuses = this.$store.state.workflow.statuses
},
data: () => ({
workflowData: {
id: '',
workflow: '',
statuses: '',
newStatuses: []
},
workflowLoaded: false
}
},
})
The way how I resolved this problem turned out to be simpler than most of the solutions presented here. I found it hard to reach data from this.$store.state due to Vuejs life cycle. And assigning values to v-mode tourned out to be impossible because "v-model will ignore the initial value, checked or selected attributes found on any form elements. It will always treat the Vue instance data as the source of truth."
Solution
To pre-fill the field with data received from ajax request e.g. input field of type email I did as follow.
1st. I saved the output of my ajax request in application's storage (Cookies) -it can be Local Storage or Session, depended what is appropriate to you.
2nd. I populated my Vuex's store (single source of truth) with the data from my application storage. I do it every time when I reload a page.
3rd. Instead of binding a data to v-model in Vuejs life cycle, or using value attribute of html input (<input type="email" value="email#example.com">). I Pre-filled input by populating placeholder attribute of html with data coming from Vuex store like this:
<input v-model="form.input.email" type="email" name="email" v-bind:placeholder="store.state.user.data.email">

How to add required rule to antd input with initial value as undefined?

I have created a form as a component which is used in create and update components. so when create uses this form component I don't pass any props as initial values for the form fields.
But inside update I pass initials after they have been fetched from server.
<FormItem {...formItemLayout} >
{getFieldDecorator('username', { initialValue: user.username || null }, {
rules: [{ required: true, message: "نام کاربری را وارد کنید." }]
})(
<Input
type="text"
placeholder="نام کاربری"/>
)}
</FormItem>
I expect the username field to be validated like before I didn't add initialValue.
But I doesn't get any error after submit as before adding initialValue if the username is empty.
I think that you didn't put the initialValue on the right place , try this (works for me ) :
<FormItem {...formItemLayout} >
{getFieldDecorator('username', {
initialValue: user.username || null,
rules: [{ required: true, message: "نام کاربری را وارد کنید." }]
})(
<Input
type="text"
placeholder="نام کاربری"/>
)}
</FormItem>
Instead of initialValue, you can use setFieldsValue method and set the initial values for your labels like this:
setInitialValues = () => {
const { form } = this.props;
form.setFieldsValue({
username: user.username
});
};
Your form should remain the same, just get rid of initialValue:
<FormItem {...formItemLayout} >
{getFieldDecorator('username', {
rules: [{ required: true, message: "نام کاربری را وارد کنید." }]
})(<Input
type="text"
placeholder="نام کاربری"/>
)}
</FormItem>
And don't forget to call the function setInitialValues in componentDidMount:
componentDidMount() {
this.setInitialValues();
}
What about adding required attribute on your input tag?
Like this:
<Input
type="text"
placeholder="نام کاربری"
required/>
And eventually, to set an error message, you would need to use validationErrors attribute
The end result would look like this:
<Input
type="text"
placeholder="نام کاربری"
required
validationErrors={{
isDefaultRequiredValue: 'Field is required'
}}/>

How to check that a user has already voted on a poll?

SITUATION:
Currently, I store an array of objects inside my User which contains all the votes he has cast.
Here is the model:
var schema = new Schema({
firstName: {type: String, required: true},
lastName: {type: String, required: true},
password: {type: String, required: true},
email: {type: String, required: true, unique: true},
polls: [{type: Schema.Types.ObjectId, ref: 'Poll'}]
votes: [{
poll: {type: Schema.Types.ObjectId, ref: 'Poll'},
choice: {type: number}
}]
});
When a user selects an option, this method is triggered inside my service:
voteOn(poll: Poll, userID: string, choice: number) {
UserModel.findById(userID, function (err, user) {
user.votes.push({poll, choice });
const body = JSON.stringify(user);
const headers = new Headers({'Content-Type': 'application/json'});
const token = localStorage.getItem('token')
? '?token=' + localStorage.getItem('token')
: '';
return this.http.patch('https://voting-app-10.herokuapp.com/user'+token, body, {headers: headers})
.map((response: Response) => response.json())
.catch((error: Response) => {
this.errorService.handleError(error);
return Observable.throw(error);
})
.subscribe();
});
}
This method saves the poll the user has voted and the choice he made inside the votes array of the user.
My problem is the following:
PROBLEM:
When the polls load, I would like to show the polls on which the user has already voted on as having the choice he made pre-selected as well as preventing him from voting more than once.
How can I achieve that ?
Here is the method inside my service which gets the polls:
getPolls() {
return this.http.get('https://voting-app-10.herokuapp.com/poll')
.map((response: Response) => {
const polls = response.json().obj;
let transformedPolls: Poll[] = [];
polls.reverse();
for (let poll of polls) {
transformedPolls.push(new Poll(
poll.title,
poll.choice1,
poll.choice2,
poll.counter1,
poll.counter2,
poll.user.firstName,
poll._id,
poll.user._id,
)
);
}
this.polls = transformedPolls;
return transformedPolls;
})
.catch((error: Response) => {
this.errorService.handleError(error);
return Observable.throw(error);
});
}
And here is the component.html for a poll:
<article class="panel panel-default">
<div class="panel-body">
{{ poll.title }}
<br>
<br>
<form #form="ngForm">
{{ poll.counter1 }} votes <input type="radio" id="{{ poll.choice1 }}" name="my_radio" value="{{ poll.choice1 }}" (click)="onChoice1(form)"> {{ poll.choice1 }}
<br>
{{ poll.counter2 }} votes <input type="radio" id="{{ poll.choice2 }}" name="my_radio" value="{{ poll.choice2 }}" (click)="onChoice2(form)"> {{ poll.choice2 }}
</form>
</div>
<footer class="panel-footer">
<div class="author">
{{ poll.username }}
</div>
<div class="config" *ngIf="belongsToUser()">
<a (click)="onEdit()">Edit</a>
<a (click)="onDelete()">Delete</a>
</div>
</footer>
</article>
There are a lot of ways you could do this.
Without changing your current data model what you need to do is compare the poll.id to the user.votes[pollIndex].id and if they match disable input on the poll. Coming from a ReactJS background I could advise how to do this but IDK with angular.
If I were to take over this project, I would probably make a new Mongo schema called Vote or UserPoll like:
{
user: {type: Schema.Types.ObjectId, ref: 'User'),
poll: {type: Schema.Types.ObjectId, ref: 'Poll'},
choice: {type: number}
}
Then if a user want to take a poll, create a new UserPoll object with the current user and poll. Then you can find all the UserPolls that the current User is in and then use an array filter based on if there is a choice or not.
Hopefully that makes sense.

How to Using Json(Mongo DB) data in AngularJS 2 expression

I'm develop in ng2 rc4 and my User data stored Mongo DB. User's can edit their data in my page, but my editing page hav got a problem. My source looks like this:
import { User } from './user.service.ts';
#component(
selector: 'edit-user',
template: `
Email : <input type="text" [(ngModel)]="userInfo.email"><br />
Name : <input type="text" [(ngModel)]="userInfo.name"><br />
Address : <input type="text" [(ngModel)]="userInfo.address"><br />
Tel :
<input type="text" [(ngModel)]="userInfo.tel.tel1">-
<input type="text" [(ngModel)]="userInfo.tel.tel2">-
<input type="text" [(ngModel)]="userInfo.tel.tel3"><br />
<button>Submit</button>
`,
providers: [ User ]
)
export class EditUser {
private userInfo: any = {
'email': '',
'name': '',
'address': '',
'tel': {
'tel1': '',
'tel2': '',
'tel3': ''
}
};
constructor(private user: User) {
}
ngOnInit() {
this.getUser();
}
getUser() {
this.user.getUser( ... )
.then(res => {
...
// case 1
// res = {'email': 'a#a.a', 'name': 'NameA', 'address': 'aaa', 'tel': {'tel1': '1', 'tel2': '2', 'tel3': '3'}};
// case 2
// res = {'email': 'b#b.b', 'name': 'NameB'};
this.userInfo = res;
...
})
.catch( ... )
}
}
Everything is okay when in case 1 but in case 2 there is no tel object and input tag throws error because of the missing tel object in res. The user was not entering tel information in the case 2. So it is a 2 way binding error: undefined tel property of userInfo. don't expression, don't enter the tel.tel1 property.
I can't change mongoDB and json hierarchy. How can I resolve this?
Assign empty object to tel if empty
res.tel = res.tel || {};
this.userInfo = res;