Vue and Vuex: Updating state based on changes to the view - vue.js

I'm trying to build
An application that renders a form, where the default input values are equal to the data from the store.
When the save button is clicked, the state will be updated according to the new data added to the view by the user.
Currently the inputs are bound to the store data, and so I have no reference to the "live" value of the inputs. When the user clicks save, how do I grab the "live" values?
Component Template
<input type="text" class="form-control" :value="item.name">
<input type="text" class="form-control" :value="item.price">
<button class="btn btn-primary" v-on:click="updateItem(item)">Save</button>
Component
data: function() {
return {}
},
methods: {
updateItem(item) {
this.$store.commit('updateItem', item);
},
},
computed: {
items() {
return this.$store.getters.getItem;
}
}
Potential Solutions
I thought I could perhaps create a "clone" of the store, and bind the inputs to the cloned item data. Then this object will be updated as the view changes, and so I can grab those "live" values, and commit the data from the view to the store. Is this a good solution?

If you wanted to update without the user having to click the button, then I would suggest one of the methods explained in the docs.
But since you want to do it wen they click the button, try something like this:
<template>
<form>
<input type="text" class="form-control" v-model="item.name">
<input type="text" class="form-control" v-model="item.price">
<button class="btn btn-primary" #click.prevent="updateItem">Save</button>
</form>
</template>
<script>
export default {
data() {
return {
item: {
name: null,
price: null,
}
}
},
mounted() {
// Set the default value of this.item based on what's in the store
this.item = this.$store.getters.getItem
},
methods: {
updateItem() {
this.$store.commit('updateItem', this.item);
}
}
}
</script>

Related

How to access data inside a modal using Vue js?

I'm editing a candidate_profile with a relationship with the Users table. I was able to access the User Object within the candidate_profile and display it, but I just want to display the name property.
Here is a screenshot of the User object in the textarea field. How can I do this?
Here is my data:
data() {
return {
candidateProfiles: {
user: ''
},
editCandidateProfileData: {},
}
};
},
Here is my button which calls the editProfile function:
<button type="button" class="btn btn-lg btn-success"
v-on:click="editProfile(candidateProfile)">Request an Interview</button>
editProfile(candidateProfile) {
this.editCandidateProfileData = {...candidateProfile};
this.showInterviewRequestModal();
},
Finally, inside my modal I can get the User object like this:
<b-form-group label="Name" label-for="name">
<b-form-textarea
id="experience"
v-model="editCandidateProfileData.user"
placeholder="Tell us a little about your experience..."
:rows="10"
:max-rows="12"
>
</b-form-textarea>
<div class="invalid-feedback" v-if="errors.user">{{ errors.user[0] }}</div>
</b-form-group>

Problem with checking ID in filter function inside a method

I work on CRUD based on Vuejs. I have three components Goods.vue, UpdateGood.vue, App.vue
My Problem is in the "updateGood" method inside App.vue . I have to click the "update" button twice to update my current good item in table.
First click is for filter function to get the current item.
Second click is for splice function to replace updated item with old one. I want to implement the filter function in my editGood() method for getting the id of current item, but don't know how to do it.
I tried to set the filter function in editGood() method where I take current object by clicking the button
App.vue file:
template:
<template>
<div id="app">
<!-- Component for adding a good -->
<AddGood v-on:add-good="addGood"/>
<!-- Component for managing goods in table -->
<Goods v-bind:goods="goods" v-on:del-good="deleteGood" v-on:edit-good="editGood"/>
<UpdateGood v-bind:good="goodToUpdate" v-on:update-good="updateGood" />
</div>
</template>
script:
import Goods from './components/Goods.vue';
import AddGood from './components/AddGood.vue';
import UpdateGood from './components/UpdateGood.vue';
methods : {
addGood(newGood){
this.goods = [...this.goods,newGood];
},
deleteGood(id){
this.goods = this.goods.filter(good => good.id !== id);
},
editGood(good){
this.goodToUpdate = Object.assign({}, good);
},
updateGood(updatedGood){
// Creating new array with only one object inside
e
let goodOriginalIndex = this.goods.indexOf(goodOriginalObject[0]);
this.goods.splice(goodOriginalIndex, 1, updatedGood);
alert("Js"+JSON.stringify(updatedGood));
}
}
Goods.vue:
<tr v-bind:key="good.id" v-for="good in goods"
v-on:del-good="$emit('del-good',good)">
<td>{{good.id}}</td>
<td>{{good.date}}</td>
<td>{{good.name}}</td>
<td>{{good.price}}</td>
<td>{{good.amount}}</td>
<td>{{good.sum}}</td>
<td><input type="submit" value="Удалить"
#click="$emit('del-good',good.id)"/></td>
<td><input type="submit" value="Изменить"
#click="$emit('edit-good',good)"/></td>
</tr>
UpdateGood.vue
template:
<div class="form-style-6">
<h1>Текущий Товар</h1>
<form #submit="updateGood">
<input type="text" v-model="good.id" placeholder="Артикул" />
<input type="text" v-model="good.date" placeholder="Дата Поступления" />
<input type="text" v-model="good.name" placeholder="Название" />
<input type="text" v-model="good.price" placeholder="Цена" />
<input type="text" v-model="good.amount" placeholder="Количество" />
<input type="text" v-model="good.sum" placeholder="Сумма" />
<input type="submit" value="Обновить" class="btn" />
</form>
</div>
script:
<script>
export default {
name : 'UpdateGood',
props: ["good"],
methods : {
updateGood: function(e){
e.preventDefault();
// Send up to parent
this.$emit('update-good', this.good);
}
}
}
</script>
I have to get the id of object in the editGood() method which pass this id to updateGood() function where splice is impelemented.
You can use watch function to subscribe when goodToUpdate object was updated and call updateGood function after that.
// your main component
{
methods: {
updateGood(updatedGood){
// Creating new array with only one object inside
let goodOriginalIndex = this.goods.indexOf(goodOriginalObject[0]);
this.goods.splice(goodOriginalIndex, 1, updatedGood);
alert("Js"+JSON.stringify(updatedGood));
this.goodToUpdate = null // reset after update
// if you just want to replace the old good by the new good, you can use map
this.goods = this.goods.map(good => good.id === updatedGood.id ? updatedGood : good )
},
...
},
watch: {
// whenever question changes, this function will run
goodToUpdate: function (newGood, oldGood) {
if(newGood){
this.updateGood(newGood)
}
}
},
}

How can I update value in input type text on vue.js 2?

My view blade laravel like this :
<form slot="search" class="navbar-search" action="{{url('search')}}">
<search-header-view></search-header-view>
</form>
The view blade laravel call vue component (search-header-view component)
My vue component(search-header-view component) like this :
<template>
<div class="form-group">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search" name="q" autofocus v-model="keyword" :value="keyword">
<span class="input-group-btn">
<button class="btn btn-default" type="submit" ref="submitButton"><span class="fa fa-search"></span></button>
</span>
<ul v-if="!selected && keyword">
<li v-for="state in filteredStates" #click="select(state.name)">{{ state.name }}</li>
</ul>
</div>
</div>
</template>
<script>
export default {
name: 'SearchHeaderView',
components: { DropdownCategory },
data() {
return {
baseUrl: window.Laravel.baseUrl,
keyword: null,
selected: null,
filteredStates: []
}
},
watch: {
keyword(value) {
this.$store.dispatch('getProducts', { q: value })
.then(res => {
this.filteredStates = res.data;
})
}
},
methods: {
select: function(state) {
this.keyword = state
this.selected = state
this.$refs.submitButton.click();
},
input: function() {
this.selected = null
}
}
}
</script>
If I input keyword "product" in input text, it will show autocomplete : "product chelsea", "product liverpool", "product arsenal"
If I click "product chelsea", the url like this : http://myshop.dev/search?q=product
Should the url like this : http://myshop.dev/search?q=product+chelsea
I had add :value="keyword" in input type text to udpate value of input type text, but it does not work
How can I solve this problem?
Update
I had find the solution like this :
methods: {
select: function(state) {
this.keyword = state
this.selected = state
const self = this
setTimeout(function () {
self.$refs.submitButton.click()
}, 1500)
},
...
}
It works. But is this solution the best solution? or there is another better solution?
Instead of timeout you can use vue's nextTick function.
I didn't checked your code by executing but seems its problem regarding timings as when submit is pressed your value isn't updated.
so setTimeout is helping js to buy some time to update value, but its 1500 so its 1.5 second and its little longer and yes we can not identify how much time it will take each time so we tempted to put max possible time, still its not perfect solution
you can do something like this. replace your setTimeout with this one
const self = this
Vue.nextTick(function () {
// DOM updated
self.$refs.submitButton.click()
})
nextTick will let DOM updated and set values then it will execute your code.
It should work, let me know if it works or not.

Using Vee-validate to disable button until form is filled out correctly

I want to disable my submit button until my form is filled out correctly, this is what I have so far:
<form>
<input type="text" class="form-control" v-validate="'required|email'" name="email" placeholder="Email" v-model="userCreate.userPrincipalName" />
<span v-show="errors.has('email')">{{ errors.first('email') }}</span>
<button v-if="errors.any()" disabled="disabled" class="btn btn-primary" v-on:click="sendInvite();" data-dismiss="modal" type="submit">Send Invite</button>
<button v-else="errors.any()" class="btn btn-primary" v-on:click="sendInvite();" data-dismiss="modal" type="submit">Send Invite</button>
</form>
The above only prints an error message and disables my submit button after I've started inputting a value. I need it to be disabled from the start, before I start interacting with the input, so that I cannot send an empty string.
Another question is if there is a better way than using v-ifto do this?
EDIT:
userCreate: {
customerId: null,
userPrincipalName: '',
name: 'unknown',
isAdmin: false,
isGlobalAdmin: false,
parkIds: []
}
Probably simpliest way is to use ValidationObserver slot for a form. Like this:
<ValidationObserver v-slot="{ invalid }">
<form #submit.prevent="submit">
<InputWithValidation rules="required" v-model="first" :error-messages="errors" />
<InputWithValidation rules="required" v-model="second" :error-messages="errors" />
<v-btn :disabled="invalid">Submit</v-btn>
</form>
</ValidationObserver>
More info - Validation Observer
Setting up the button to be :disabled:"errors.any()" disables the button after validation. However, when the component first loads it will still be enabled.
Running this.$validator.validate() in the mounted() method, as #im_tsm suggests, causes the form to validate on startup and immediately show the error messages. That solution will cause the form to look pretty ugly. Also, the Object.keys(this.fields).some(key => this.fields[key].invalid); syntax is super ugly.
Instead, run the validator when the button is clicked, get the validity in the promise, and then use it in a conditional. With this solution, the form looks clean on startup but if they click the button it will show the errors and disable the button.
<button :disabled="errors.any()" v-on:click="sendInvite();">
Send Invite
</button>
sendInvite() {
this.$validator.validate().then(valid=> {
if (valid) {
...
}
})
}
Validator API
One way to disable a button until all the values you need are filled, is to use a computed property that will return bool if all values are assigned or not
Example:
Create a computed property like this:
computed: {
isComplete () {
return this.username && this.password && this.email;
}
}
And bind it to the html disabled attribute as:
<button :disabled='!isComplete'>Send Invite</button
This means, disable the button if !isComplete is true
Also, in your case you don't need two if/else-bound buttons. You can use just one to hide/show it based on if the form is completed or has any errors:
<button :disabled="errors.any() || !isCompleted" class="btn btn-primary" v-on:click="sendInvite();" data-dismiss="modal" type="submit">Send Invite</button>
This button will be disabled until all fields are filled and no errors are found
Another way is to make use of v-validate.initial
<input type="text" class="form-control" v-validate.initial="'required|email'" name="email" placeholder="Email" v-model="userCreate.userPrincipalName" />
This will execute the validation of the email input element after the page is loaded. And makes that your button is disabled before interacting with the input.
To check whether a form is invalid or not we can add a computed property like this:
computed: {
isFormInValid() {
return Object.keys(this.fields).some(key => this.fields[key].invalid);
},
},
Now if you want to start checking immediately before user interaction with any of the fields, you can validate manually inside mounted lifecycle hooks:
mounted() {
this.$validator.validate();
}
or using computed
computed: {
formValidated() {
return Object.keys(this.fields).some(key => this.fields[key].validated) && Object.keys(this.fields).some(key => this.fields[key].valid);
}
}
and use
button :disabled="!formValidated" class="btn btn-primary" v-on:click="sendInvite();" data-dismiss="modal" type="submit">
For the current version 3 (As at the time of writing).
Step 1
Ensure form fields can be watched.
Step 2
Get a reference to the validator instance:
<ValidationObserver ref="validator">.
Step 3
Trigger validation silently whenever the form fields change.
Here's an example:
export default {
data() {
return {
form: {
isValid: false,
fields: {
name: '',
phone: '',
}
}
}
},
watch: {
'form.fields': {
deep: true,
handler: function() {
this.updateFormValidity();
}
}
},
methods: {
async updateFormValidity() {
this.form.isValid = await this.$refs.validator.validate({
silent: true // Validate silently and don't cause observer errors to be updated. We only need true/false. No side effects.
});
},
}
}
<button :disabled="form.isValid">
Submit
</button>
You can add computed properties
...
computed: {
isFormValid () {
return Object.values(this.fields).every(({valid}) => valid)
}
}
...
and it bind to the button:
<button :disabled="!isFormValid" class="btn btn-primary" type="submit">Send Invite</button>
i try this on vee-validate version ^2.0.3

Vuex - Computed property "name" was assigned to but it has no setter

I have a component with some form validation. It is a multi step checkout form. The code below is for the first step. I'd like to validate that the user entered some text, store their name in the global state and then send then to the next step. I am using vee-validate and vuex
<template>
<div>
<div class='field'>
<label class='label' for='name'>Name</label>
<div class="control has-icons-right">
<input name="name" v-model="name" v-validate="'required|alpha'" :class="{'input': true, 'is-danger': errors.has('name') }" type="text" placeholder="First and Last">
<span class="icon is-small is-right" v-if="errors.has('name')">
<i class="fa fa-warning"></i>
</span>
</div>
<p class="help is-danger" v-show="errors.has('name')">{{ errors.first('name') }}</p>
</div>
<div class="field pull-right">
<button class="button is-medium is-primary" type="submit" #click.prevent="nextStep">Next Step</button>
</div>
</div>
</template>
<script>
export default {
methods: {
nextStep(){
var self = this;
// from baianat/vee-validate
this.$validator.validateAll().then((result) => {
if (result) {
this.$store.dispatch('addContactInfoForOrder', self);
this.$store.dispatch('goToNextStep');
return;
}
});
}
},
computed: {
name: function(){
return this.$store.state.name;
}
}
}
</script>
I have a store for handling order state and recording the name. Ultimately I would like to send all of the info from multi step form to the server.
export default {
state: {
name: '',
},
mutations: {
UPDATE_ORDER_CONTACT(state, payload){
state.name = payload.name;
}
},
actions: {
addContactInfoForOrder({commit}, payload) {
commit('UPDATE_ORDER_CONTACT', payload);
}
}
}
When I run this code I get an error that Computed property "name" was assigned to but it has no setter.
How do I bind the value from the name field to the global state? I would like this to be persistent so that even if a user goes back a step (after clicking "Next Step") they will see the name they entered on this step
If you're going to v-model a computed, it needs a setter. Whatever you want it to do with the updated value (probably write it to the $store, considering that's what your getter pulls it from) you do in the setter.
If writing it back to the store happens via form submission, you don't want to v-model, you just want to set :value.
If you want to have an intermediate state, where it's saved somewhere but doesn't overwrite the source in the $store until form submission, you'll need to create such a data item.
It should be like this.
In your Component
computed: {
...mapGetters({
nameFromStore: 'name'
}),
name: {
get(){
return this.nameFromStore
},
set(newName){
return newName
}
}
}
In your store
export const store = new Vuex.Store({
state:{
name : "Stackoverflow"
},
getters: {
name: (state) => {
return state.name;
}
}
}
For me it was changing.
this.name = response.data;
To what computed returns so;
this.$store.state.name = response.data;
I've had such an error when getting value from the store, in computed, via ...mapState(['sampleVariable']), as you. Then I've used the this.sampleVariable in <script> and sampleVariable in <template>.
What fixed the issue was to return this in data(), assign it to a separated variable, and reuse across the component the newly created variable, like so:
data() {
return {
newVariable: this.$store.state.sampleVariable,
}
}
Then, I've changed references in the component from sampleVariable to newVariable, and the error was gone.
I was facing exact same error
Computed property "callRingtatus" was assigned to but it has no setter
here is a sample code according to my scenario
computed: {
callRingtatus(){
return this.$store.getters['chat/callState']===2
}
}
I change the above code into the following way
computed: {
callRingtatus(){
return this.$store.state.chat.callState===2
}
}
fetch values from vuex store state instead of getters inside the computed hook