How To Properly Initialize Form Data In Vue - forms

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">

Related

Vue.js: How to fill a form prepopulated with data from a get request?

I want to load data with a GET request and fill the data to the input data attributes at vue.js 3 like
<input id="name" type="text" v-bind:placeholder="$t('message.NamePlaceholder')" value="{{ name }}" required>
and this is my script part
<script>
export default {
data () {
return {
userInformation: {},
name: "",
}
},
mounted () {
this.getUserInformation();
},
methods () {
getUserInformation() {
this.$axios({
method: 'get',
url: 'http://127.0.0.1:8000/api/get_user_information',
}).then(response => {this.userInformation = response.data});
this.name = this.userInformation.Name;
}
},
}
But the input field contains only {{ name }}. I tried also v-bind:value, but this didn't solve the problem.
Whenever you need to bind values to attributes {{}} are unnecessary. You can just write v-bind:value="name" or :value="name"
E.g.:
<input id="name" type="text" :placeholder="message.NamePlaceholder" :value="name" required></input>
The mistake was that I have to set the variable this.name at the axios command:
this.$axios({
method: 'get',
url: 'http://127.0.0.1:8000/api/get_user_information',
}).then(response => {
this.userInformation = response.data;
this.name = this.userInformation.Name;
});

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;
}
}

React|Rest API: Storing form data into an object on the REST API

I've set up a react web application that's currently listing all "Employees" from a mongodb.
I'm now trying to "add" employees to the database through a react frontend form.
I've managed to pass the data from the form to the application but I'm unsure of the process I need to go through to actually get that data solidified into an object and stored in the api.
Please excuse my code, it's disgusting as this is my first week learning react(honestly with little js knowledge, that's another story) and I've just patched together like 20 tutorials....
Here's my Form class:
class Form extends React.Component {
state = {
fullname: '',
}
change = e => {
this.setState({
[e.target.name]: e.target.value
});
}
onSubmit = e => {
e.preventDefault();
this.props.onSubmit(this.state)
this.setState({
fullname: ''
})
}
render() {
return <div>
<form>
<input name="fullname" placeholder="Full Name" value={this.state.fullname} onChange={e => this.change(e)} />
<button onClick={e => this.onSubmit(e)}>Submit</button>
</form>
</div>
}
}
and my Listing(?) class:
class EmployeeList extends React.Component {
constructor(props) {
super(props);
this.state = {employee: []};
this.EmployeeList = this.EmployeeList.bind(this)
this.componentDidMount = this.componentDidMount.bind(this)
}
componentDidMount() {
this.EmployeeList();
}
EmployeeList() {
fetch('/api/employees').then(function(data){
return data.json();
}).then( json => {
this.setState({
employee: json
});
console.log(json);
});
}
onSubmit = fields => {
console.log('app component got: ', fields)
}
render() {
//return a mapped array of employees
const employees = this.state.employee.map((item, i) => {
return <div className="row">
<span className="col-sm-6">{item.fullname}</span>
<span className="col-sm-2" id={item.action1}></span>
<span className="col-sm-2" id={item.action2}></span>
<span className="col-sm-2" id={item.action3}></span>
</div>
});
return <div>
<Form onSubmit={fields => this.onSubmit(fields)}/>
<div className="container">
<div className="row">
<div className="col-sm-6 bg-warning"><h3>Full Name</h3></div>
<div className="col-sm-2 bg-success"><h3>Action 1</h3></div>
<div className="col-sm-2 bg-success"><h3>Action 2</h3></div>
<div className="col-sm-2 bg-success"><h3>Action 3</h3></div>
</div>
</div>
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{ employees }</div>
</div>
</div>
}
}
I've managed to pass the data to the listing app evident by
onSubmit = fields => {
console.log('app component got: ', fields)
}
But how can I go about making a post request to store this data I send into an object on the db? And then also reload the page so that the new list of all employee's is shown?
Thanks so much for your time!
You can use fetch API to make POST request as well. Second parameter is the config object wherein you can pass the required request configurations.
fetch('url', {
method: 'post',
body: JSON.stringify({
name: fields.fullname
})
})
.then(response) {
response.json();
}
.then( json => {
this.setState({
employee: json
});
});
Additional Request Configs which can be used :
method - GET, POST, PUT, DELETE, HEAD
url - URL of the request
headers - associated Headers object
referrer - referrer of the request
mode - cors, no-cors, same-origin
credentials - should cookies go with the request? omit, same-origin
redirect - follow, error, manual
integrity - subresource integrity value
cache - cache mode (default, reload, no-cache)

How to search collection in meteor with more parameters

I need help with searching the meteor collection with more parameters.
I am using search query and filters to see certain objects from a collection. The problem is that I want client to load whole collection and then reactively change what the user sees, but only changing the subscribe, not calling server again. Thill now search query + one filter is working okay, but only if I call server every time something changes. Now in my code below you can see that I am doing it with if else elements, but that is not a good way. Any suggestion will help. Thank you.
Template.jobs.onCreated( function showOnCreate() {
Meteor.subscribe('Jobs');
this.searchQuery = new ReactiveVar('');
this.remoteQuery = new ReactiveVar(false);
this.typeQuery = new ReactiveVar(false);
});
Template.jobs.helpers({
job: () => {
query = Template.instance().searchQuery.get();
remoteQuery = Template.instance().remoteQuery.get();
typeQuery = Template.instance().typeQuery.get();
let regex = new RegExp( query, 'i' );
// **************************
// the problem starts here
// **************************
if (Router.current().params.slug) {
const companyJobs = Company.findOne({slug: Router.current().params.slug}).jobs;
if ( companyJobs !== undefined) {
return Meteor.subscribe('Jobs', {'_id': { '$in': companyJobs }});
}
return false
} else if (Router.current().params.slug === undefined && remoteQuery === true ) {
return Job.find({ $or: [ { Name: regex }, { Description: regex }, ] , Remote: true, positionType: [],});
} else if (typeQuery = '') {
return Job.find({ $or: [ { Name: regex }, { Description: regex }, ] , positionType: typeQuery, });
},
// -------*****************************
employer: () => {
if (Router.current().params.slug === undefined) {
Meteor.subscribe('Companies');
return 'Poslodavac: ' + Company.findOne({_id: Template.currentData().CompanyId}).Name;
}
return false
},
jobUrl: () => {
Meteor.subscribe('Companies');
companySlug = Company.findOne({_id: Template.currentData().CompanyId}).slug;
return ('/company/' + companySlug + '/job/' );
}
});
Template.jobs.events({
'click .positionType': (event, templateInstance) => {
if (Template.instance().remoteQuery.get().lenght > 1){
Template.instance().typeQuery.set(Template.instance().remoteQuery.get().push(event.target.value));
console.log(Template.instance().remoteQuery.get())
} else {
console.log(Template.instance().remoteQuery.get())
console.log('ggggggg')
Template.instance().typeQuery.set(event.target.value);
}
},
'click #remoteFriendly': (event, templateInstance) => {
Template.instance().remoteQuery.set(!Template.instance().remoteQuery.get());
},
});
Html tempalte with filters:
<template name="jobs" >
<div>
<p>Filteri:</p>
<span>
<input type="checkbox" id="remoteFriendly" name="remote"> <span for="remoteFriendly"> Remote friendly? </span>
</span>
<span>
<p>Tip pozicije:</p>
<input type="checkbox" class="positionType" id="1" value="Programiranje" > <span for="1"> Programiranje </span>
<input type="checkbox" class="positionType" id="2" value="Dizajn" > <span for="2"> Dizajn </span>
<input type="checkbox" class="positionType" id="3" value="Marketing" > <span for="3"> Marketing </span>
<input type="checkbox" class="positionType" id="4" value="Ostalo" > <span for="4"> Ostalo </span>
</span>
</div>
{{#each job}}
<div style="border: 0.1rem solid black; margin: 1cm; padding: 5px; max-width: 420px;" > <!-- OVO JE PRIVREMENI STIL, OBRISATI-->
<p> Posao: {{Name}} <br> Opis: {{Description}}</p>
<p> {{employer}} </p>
<p>Remote friendly?: {{Remote}}</p>
<p>Tip pozicije: {{positionType}}</p>
<p> Saznajte vise OVDE</p>
</div>
{{/each}}
<p id="nesto"></p>
</template>
Welcome to SO!
You seem to be confused between Pub/Sub and Collection.find.
You should first realize that the 2 are different mechanisms, which provide different functionalities.
Pub/Sub indeed sends data from your Server into your Client's Minimongo database. But this data is not displayed yet.
Collection.find is used on your Server against your actual MongoDB, and on your Client against your local Minimongo DB.
Therefore on your client, once you have correctly subscribed to your server publication (typically at app level or template level / in onCreated hook), you can directly call Jobs.find in your helpers (or anywhere else) to get your documents, without having to change the subscription (unless the latter needs new parameters).
There should be nothing wrong with your commented code:
return Job.find({'_id': { '$in': companyJobs }});
In general, avoid any expensive computation in helpers (like Meteor.subscribe), as helpers may be executed many times without you noticing it. Your Meteor.subscribe('Companies') should also go to template level (i.e. in onCreated hook).
Therefore, instead of doing your if / else conditions in your helper, simply do it once at your template level. To account for your need to use a value from another document in another collection, why not just passing directly the company's slug as an argument to your Jobs subscription, and performing the computation Server-side? Or even just subscribing to everything, as your current initial subscription seems to do.
Then your helper will just use Jobs.find, which queries against your Client's local minimongo DB, leaving your Server unbothered.

knockoutjs mapping select data-bind options

I am having problems binding the selected value of a selectbox to a property within the view model. For some reason it keeps coming back unchanged when posted back to the server.
My Html is:
<form action="/Task/Create" data-bind="submit: save">
<table border="1">
<tr>
<td>ref type</td>
<td><select data-bind="options: ReferenceTypes, optionsText: 'Name', optionsCaption: 'Select...', value:Task.ReferenceTypeId"></select></td>
<td>Reference</td>
<td><input data-bind="value:Task.Reference" /></td>
</tr>
</table>
<button type="submit">Save Listings</button>
</form>
The Javascript is:
<script type="text/javascript">
var viewModel = {};
$.getJSON('/Task/CreateJson', function (result) {
viewModel = ko.mapping.fromJS(result.Data);
viewModel.save = function () {
var data = ko.toJSON(this);
$.ajax({
url: '/Task/Create',
contentType: 'application/json',
type: "POST",
data: data,
dataType: 'json',
success: function (result) {
ko.mapping.updateFromJS(viewModel, result);
}
});
}
ko.applyBindings(viewModel);
});
</script>
JSON from Fiddler that gets loaded into the page as below.
{
"ContentEncoding":null,
"ContentType":null,
"Data":{
"Task":{
"ReferenceTypeId":0,
"Reference":"Default Value"
},
"ReferenceTypes":[
{
"Id":2,
"Name":"A Ref Type"
},
{
"Id":3,
"Name":"B Ref Type"
},
{
"Id":1,
"Name":"C Ref Type"
}
]
},
"JsonRequestBehavior":1
}
This comes back into the server (ASP.NET MVC3) correctly, with the updated Reference string value, but ReferenceTypeId is not bound to the correctly selected drop down value. Do I need to perform any additional functions to bind correctly etc? Or tell the data-bind what the select value column is (Id) etc? I have checked in Fiddler on the values getting posted back from the browser, and it has the same original value (0). So it is definately not the server.
I hope someone can help, if you need any further information please ask.
Kind Regards
Phil
The issue is that your options binding will try to assign the object that it is bound to, to the value observable specified.
For example if you select "A Ref Type" the options binding will push the json object
{ "Id":2, "Name":"A Ref Type" }
Into your Task.ReferenceTypeId observable which will then be serialized back to your server. In this case you need to add an optionsValue config options to tell the binding just to save the id.
<select data-bind="options: ReferenceTypes, optionsText: 'Name',
optionsCaption: 'Select...', optionsValue: 'Id', value:Task.ReferenceTypeId">
</select>
Here's an example.
http://jsfiddle.net/madcapnmckay/Ba5gx/
Hope this helps.