I have code below in vue js and I wanted to update data the model by using AddCategory and it works fine, on the other hand, I wanted to post this to API Url using the post data below, but when I set v-model="posts.AddCategory" it didn't work, is there a way to do it?
<b-form-input
id="input"
type="text"
name="AddCategory"
v-model="AddCategory" //v-model="posts.AddCategory" didn't work
placeholder="categories"
required
/>
<div
class="categories"
v-for="(category, index) in categories"
:key="index"
#click="Add(index, AddCategory)"
>
<mark> {{ category.category_name }} </mark>
<script>
export default {
name: "postComponent",
components: {
Editor: PrismEditor,
},
data() {
return {
categories: [],
selectedIndex: null,
isediting: false,
AddCategory: "",
posts: {
title: null,
question: null,
code: require("../example.js").default /* eslint-enable */,
},
};
},
methods: {
Add(AddCategory, index) {
this.AddCategory = AddCategory;
this.selectedIndex = index;
this.isediting = true;
},
Your v-model=posts.AddCategory won't work as you are hitting one of the object caveats as defined in vue https://v2.vuejs.org/v2/guide/reactivity.html#For-Objects.
What you can do is first in your Add method you could to so:
Add(category,index){
this.$set(this.post,'AddCategory',category);
....
}
Related
i have code below in Vue js that shows Categories from API and whenever i click on any of them it add it to the array AddCategory and post it to API, also i've implemented button Removeall that whenever i click on it, it remove all the selected categories in array (it will empty the array) and it works fine. my problem is when i want to click again on the selected single category (in function remove below) it's not poping it up from array however it pushed it twice, any help?
<template>
<b-row class="categories-row">
<div
class="categories"
v-for="(category, index) in categories"
:key="index"
#click="Add(category._id, index)"
:class="[selectedIndex.includes(index) ? 'green' : 'gray']"
required
>
{{ category.category_name }}
</div>
</b-row>
</template>
export default {
data() {
return {
categories: [],
selectedIndex: [],
AddCategory: [],
posts: {
description: null,
questionTitle: null,
categories: null,
},
};
},
methods: {
Add(AddCategory, index) {
if (this.selectedIndex.includes(index))
this.Remove(index);
else
this.selectedIndex.push(index);
this.AddCategory.push(AddCategory);
},
Remove(index) { //not working
this.selectedIndex.splice(this.selectedIndex.indexOf(index),1);
},
RemoveAll() {
this.AddCategory = [];
this.selectedIndex.splice(this.AddCategory);
},}}
try this:
Remove(index) {
this.selectedIndex = this.selectedIndex.filter((item)=> item !== index) ;
}
Edited:
#sara97 maybe you need remove it from this.AddCategory too.
Edited:
#sara97 and because it runs "this.AddCategory.push(AddCategory);" everytime. use {} in if and else.
Add(AddCategory, index) {
if (this.selectedIndex.includes(index)) {
this.Remove(AddCategory,index);
}else{
this.selectedIndex.push(index);
this.AddCategory.push(AddCategory);}
},
Remove(AddCategory,index) {
this.selectedIndex = this.selectedIndex.filter((item)=> item !== index);
this.AddCategory = this.AddCategory.filter((item)=> item !== AddCategory)
},
I've created a simple component named DefaultButton.
It bases on properties, that are being set up whenever this component is being created.
The point is that after mounting it, It does not react on changes connected with "defaultbutton", that is an object located in properties
<template>
<button :class="buttonClass" v-if="isActive" #click="$emit('buttonAction', defaultbutton.id)" >
{{ this.defaultbutton.text }}
</button>
<button :class="buttonClass" v-else disabled="disabled">
{{ this.defaultbutton.text }}
</button>
</template>
<script>
export default {
name: "defaultbutton",
props: {
defaultbutton: Object
},
computed: {
buttonClass() {
return `b41ngt ${this.defaultbutton.state}`;
},
isActive() {
return (this.defaultbutton.state === "BUTTON_ACTIVE" || this.defaultbutton.state === "BUTTON_ACTIVE_NOT_CHOSEN");
}
}
};
</script>
Having following component as a parent one:
<template>
<div v-if="state_items.length == 2" class="ui placeholder segment">
{{ this.state_items[0].state }}
{{ this.state_items[1].state }}
{{ this.current_active_state }}
<div class="ui two column very relaxed stackable grid">
<div class="column">
<default-button :defaultbutton="state_items[0]" #buttonAction="changecurrentstate(0)"/>
</div>
<div class="middle aligned column">
<default-button :defaultbutton="state_items[1]" #buttonAction="changecurrentstate(1)"/>
</div>
</div>
<div class="ui vertical divider">
Or
</div>
</div>
</template>
<script type="text/javascript">
import DefaultButton from '../Button/DefaultButton'
export default {
name: 'changestatebox',
data() {
return {
current_active_state: 1
}
},
props: {
state_items: []
},
components: {
DefaultButton
},
methods: {
changecurrentstate: function(index) {
if(this.current_active_state != index) {
this.state_items[this.current_active_state].state = 'BUTTON_ACTIVE_NOT_CHOSEN';
this.state_items[index].state = 'BUTTON_ACTIVE';
this.current_active_state = index;
}
},
},
mounted: function () {
this.state_items[0].state = 'BUTTON_ACTIVE';
this.state_items[1].state = 'BUTTON_ACTIVE_NOT_CHOSEN';
}
}
</script>
It clearly shows, using:
{{ this.state_items[0].state }}
{{ this.state_items[1].state }}
{{ this.current_active_state }}
that the state of these items are being changed, but I am unable to see any results on the generated "DefaultButtons". Classes of objects included in these components are not being changed.
#edit
I've completely changed the way of delivering the data.
Due to this change, I've abandoned the usage of an array; instead I've used two completely not related object.
The result is the same - class of the child component's object is not being
DefaulButton.vue:
<template>
<button :class="buttonClass" v-if="isActive" #click="$emit('buttonAction', defaultbutton.id)" >
{{ this.defaultbutton.text }}
</button>
<button :class="buttonClass" v-else disabled="disabled">
{{ this.defaultbutton.text }}
</button>
</template>
<style lang="scss">
import './DefaultButton.css';
</style>
<script>
export default {
name: "defaultbutton",
props: {
defaultbutton: {
type: Object,
default: () => ({
id: '',
text: '',
state: '',
})
}
},
computed: {
buttonClass() {
return `b41ngt ${this.defaultbutton.state}`;
},
isActive() {
return (this.defaultbutton.state === "BUTTON_ACTIVE" ||
this.defaultbutton.state === "BUTTON_ACTIVE_NOT_CHOSEN");
}
}
};
</script>
ChangeStateBox.vue:
<template>
<div class="ui placeholder segment">
{{ this.state_first.state }}
{{ this.state_second.state }}
{{ this.current_active_state }}
<div class="ui two column very relaxed stackable grid">
<div class="column">
<default-button :defaultbutton="state_first" #buttonAction="changecurrentstate(0)"/>
</div>
<div class="middle aligned column">
<default-button :defaultbutton="state_second" #buttonAction="changecurrentstate(1)"/>
</div>
</div>
<div class="ui vertical divider">
Or
</div>
</div>
</template>
<script type="text/javascript">
import DefaultButton from '../Button/DefaultButton'
export default {
name: 'changestatebox',
data() {
return {
current_active_state: 1
}
},
props: {
state_first: {
type: Object,
default: () => ({
id: '',
text: ''
})
},
state_second: {
type: Object,
default: () => ({
id: '',
text: ''
})
},
},
components: {
DefaultButton
},
methods: {
changecurrentstate: function(index) {
if(this.current_active_state != index) {
if(this.current_active_state == 1){
this.$set(this.state_first, 'state', "BUTTON_ACTIVE_NOT_CHOSEN");
this.$set(this.state_second, 'state', "BUTTON_ACTIVE");
} else {
this.$set(this.state_first, 'state', "BUTTON_ACTIVE");
this.$set(this.state_second, 'state', "BUTTON_ACTIVE_NOT_CHOSEN");
}
this.current_active_state = index;
}
},
},
created: function () {
this.state_first.state = 'BUTTON_ACTIVE';
this.state_second.state = 'BUTTON_ACTIVE_NOT_CHOSEN';
}
}
</script>
You're declaring props wrong. It is either an array of prop names or it is an object with one entry for each prop declaring its type, or it is an object with one entry for each prop declaring multiple properties.
You have
props: {
state_items: []
},
but to supply a default it should be
props: {
state_items: {
type: Array,
default: []
}
},
But your problem is most likely that you're mutating state_items in such a way that Vue can't react to the change
Your main problem is the way you are changing the button state, according with Array change detection vue can't detect mutations by indexing.
Due to limitations in JavaScript, Vue cannot detect the following
changes to an array:
When you directly set an item with the index, e.g.
vm.items[indexOfItem] = newValue When you modify the length of the
array, e.g. vm.items.length = newLength
In case someone will be having the same issue:
#Roy J as well as #DobleL were right.
The reason behind this issue was related with the wrong initialization of state objects.
According to the documentation:
Vue cannot detect property addition or deletion.
Since Vue performs the getter/setter conversion process during instance
initialization, a property must be present in the
data object in order for Vue to convert it and make it reactive.
Before reading this sentence, I used to start with following objects as an initial data:
var local_state_first = {
id: '1',
text: 'Realized',
};
var local_state_second = {
id: '2',
text: 'Active'
};
and the correct version of it looks like this:
var local_state_first = {
id: '1',
text: 'Realized',
state: 'BUTTON_ACTIVE'
};
var local_state_second = {
id: '2',
text: 'Active',
state: 'BUTTON_ACTIVE'
};
whereas declaring the main component as:
<change-state-box :state_first="local_state_first" :state_second="local_state_second" #buttonAction="onbuttonAction"/>
Rest of the code remains the same ( take a look at #edit mark in my main post )
My vue component like this :
<template>
...
<b-col v-for="item in items"
v-bind:key="item.id"
cols="2">
...
<strong slot="header" class="text-dark" :title="item.tittle" >{{item.tittle}}</strong><br/>
...
<strong class="bg-warning text-dark"><i class="fa fa-money"></i> <b>{{item.price}}</b></strong><br/>
...
</b-col>
...
<b-pagination size="sm" :total-rows="itemsPagination.total" :per-page="itemsPagination.per_page" v-model="itemsPagination.current_page" prev-text="Prev" next-text="Next" hide-goto-end-buttons/>
...
</template>
<script>
export default {
...
data () {
return{
items: '',
itemsPagination: ''
}
},
mounted: function () {
this.getItems()
},
methods: {
getItems() {
let params = []
...
let request = new Request(ApiUrls.items + '?' + params.join('&'), {
method: 'GET',
headers: new Headers({
'Authorization': 'bearer ' + this.$session.get(SessionKeys.ApiTokens),
'Content-Type': 'text/plain'
})
})
fetch(request).then(r=> r.json())
.then(r=> {
this.items = r.data
this.itemsPagination = r
})
.catch(e=>console.log(e))
}
}
}
</script>
If I console.log(this.itemsPagination), the result in console like this :
My view of pagination in my application like this :
If the script executed, it will display content of item in page 1. But if I click page 2 etc, the content of item is not change. I'm still confused to make it work well
How can I solve this problem?
Update
I using coreui
https://coreui.io/vue/demo/#/base/paginations
https://github.com/coreui/coreui-free-vue-admin-template/blob/master/src/views/base/Paginations.vue
https://bootstrap-vue.js.org/docs/components/pagination
You are trying to v-model="itemsPagination.current_page", but you initialize itemsPagination as an empty string:
data () {
return{
items: '',
itemsPagination: ''
}
}
Vue cannot detect property addition or deletion, so nothing reacts. You need to initialize itemsPagination as an object that contains (at least) current_page:
data () {
return{
items: '',
itemsPagination: {
current_page: 1
}
}
}
Update:
You can actually edit the example here. Double-click the upper right corner to edit it, and paste in this code:
<template>
<div>
<h6>Default</h6>
<b-pagination size="md" :total-rows="100" v-model="itemsPagination.current_page" :per-page="10">
</b-pagination>
<br>
<h6>Small</h6>
<b-pagination size="sm" :total-rows="100" v-model="itemsPagination.current_page" :per-page="10">
</b-pagination>
<br>
<h6>Large</h6>
<b-pagination size="lg" :total-rows="100" v-model="itemsPagination.current_page" :per-page="10">
</b-pagination>
<br>
<div>currentPage: {{itemsPagination.current_page}}</div>
</div>
</template>
<script>
export default {
data () {
return {
itemsPagination: {
current_page: 1
}
}
}
}
</script>
<!-- pagination-1.vue -->
Maybe this is not the best way to answer you (redirecting to a video), but i think this video will be clearer than me about what is happening, and how to solve it .
https://youtu.be/7lpemgMhi0k?t=17m42s
I believe, you don't update items reactivly.
Can you try follow:
data () {
return{
container:{
// for Vue.set I didnt found how to do it without nested fields for data object, so did like that on my own project
items: '',
itemsPagination: ''
}
}
},
And then in fetch:
Vue.set(this.container, 'items', r.data);
// or this.$set, if you dont use arrow function here, and not Vue accessable
see https://v2.vuejs.org/v2/guide/reactivity.html
If that not helps, check for console.log(this) inside of your fetch if it your component
I am using Vue-multiselect with Laravel.
I am using the multiselect component in my form to let the user select multiple countries. The component works fine but when I submit the form and I dd() it, it shows [object Object].
I can't get the value of the multiselect component. I have found similar questions but none of them worked for me.
Here is my code:
The ExampleComponent.vue file:
<template slot-scope="{ option }">
<div>
<label class="typo__label">Restricted country</label>
<multiselect
v-model="internalValue"
tag-placeholder="Add restricted country"
placeholder="Search or add a country"
label="name"
name="selectedcountries[]"
:options="options"
:multiple="true"
track-by="name"
:taggable="true"
#tag="addTag"
>
</multiselect>
<pre class="language-json"><code>{{ internalValue }}</code></pre>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
// register globally
Vue.component('multiselect', Multiselect)
export default {
components: {
Multiselect
},
props: ['value'],
data () {
return {
internalValue: this.value,
options: [
{ name: 'Hungary' },
{ name: 'USA' },
{ name: 'China' }
]
}
},
watch: {
internalValue(v){
this.$emit('input', v);
}
},
methods: {
addTag (newTag) {
const tag = {
name: newTag,
code: newTag.substring(0, 2) + Math.floor((Math.random() * 10000000))
}
this.options.push(tag)
this.value.push(tag)
}
},
}
</script>
Here is my register form:
<div id="select">
<example-component v-model="selectedValue"></example-component>
<input type="hidden" name="countriespost" :value="selectedValue">
</div>
<script>
const select = new Vue({
el: '#select',
data: {
selectedValue: null
},
});
</script>
When I submit the form, the countriespost shows me me this: [object Object] instead of the actual value.
It's because you are providing an array of objects as options property:
options: [
{ name: 'Hungary' },
{ name: 'USA' },
{ name: 'China' }
]
so the value emited on input is an object.
Try to change the options to following:
options: [ 'Hungary', 'USA', 'China' ]
If you pass an array of objects to the :options prop of the multiselect component, you should submit the form with javascript so you can extract the object ids or whatever you need on the backend and then send them through.
Add a method like this:
submit: function() {
let data = {
objectIds: _.map(this.selectedOptions, option => option.id), //lodash library used here
// whatever other data you need
}
axios.post('/form-submit-url', data).then(r => {
console.log(r);
});
}
Then trigger it with a #click.stop event on your submit button.
returning this.user (a global computed property) works as expected. Of course, I'm making a copy because I do not want to overwrite the actual user data. So, I'm using Object.assign. However, once I include return Object.assign({}, this.user) (opposed to this.user), the watch method no longer functions.
Here is my template (I am using bootstrap-vue):
<template>
<form role="form">
<b-form-group
label="First Name"
label-for="basicName"
:label-cols="3"
:horizontal="true">
<b-form-input id="user-name-first" type="text" v-model="userFormData.fname"></b-form-input>
</b-form-group>
<b-form-group
label="Last Name"
label-for="basicName"
:label-cols="3"
:horizontal="true">
<b-form-input id="user-name-lirst" type="text" v-model="userFormData.lname"></b-form-input>
</b-form-group>
<b-form-group
label="Email"
label-for="user-email"
:label-cols="3"
:horizontal="true">
<b-form-input id="user-email" type="text" v-model="userFormData.email"></b-form-input>
</b-form-group>
<b-form-group
:label-cols="3"
:horizontal="true">
<b-button type="submit" variant="primary">Save changes</b-button>
<b-button type="button" variant="secondary" #click="userFormCancel">Cancel</b-button>
</b-form-group>
</form>
</template>
So, this works and sets editsPending to true whenever changes are applied to userProfile (via v-model on an input)
<script>
export default {
name: 'userProfile',
data () {
return {
editsPending: false
}
},
computed: {
userFormData: function () {
return this.user
}
},
watch: {
userFormData: {
deep: true,
handler (val) {
this.editsPending = true
}
}
},
methods: {
userFormCancel () {
this.editsPending = false
}
}
}
</script>
...but this does not; userFormData becomes a clone of user but editsPending is not affected by updates to userFormData.
<script>
export default {
name: 'userProfile',
data () {
return {
editsPending: false
}
},
computed: {
userFormData: function () {
return Object.assign({}, this.user)
}
},
watch: {
userFormData: {
deep: true,
handler (val) {
this.editsPending = true
}
}
},
methods: {
userFormCancel () {
this.editsPending = false
}
}
}
</script>
Can anyone explain why this may be happening and suggest a viable solution?
A computed property will only re-evaluate when some of its
dependencies have changed. (source)
That's why it works with return this.user and not with Object.assign because it's not a reactive dependency.
If you want reactive data you should initialize userFormData as an empty object data and assign your user when your Vue instance is created:
data () {
return {
editsPending: false,
userFormData: {}
}
},
created() {
this.userFormData = Object.assign({}, this.user)
},
Tested different things to reproduce the behaviour you see.
I suspect that in your template your are binding your inputs to UserFormdata (incorrect)
<input v-model="userFormData.name">
Instead of (correct)
<input v-model="user.name">
If you could share your template that would help ;)
Edit: After template addition.
new Vue({
el: '#app',
data () {
return {
editsPending: false,
user: { name: 'John Doe' },
userCachedData: {},
}
},
created() {
this.userCachedData = Object.assign({}, this.user);
},
watch: {
user: {
deep: true,
handler (val) {
this.editsPending = true
}
}
},
methods: {
userFormCancel () {
this.editsPending = false
}
}
})
<div id="app">
{{ user }}
{{ userCachedData }}
<br>
<input v-model="user.name" />
{{ this.editsPending }}
</div>
Codepen: https://codepen.io/aurelien-bottazini/pen/BVNJaG?editors=1010
You can use the $emit to assign value to object:
mounted() {
this.$emit("userFormData", this.user);
}