Laravel can't get the values from Vue-multiselect - javascript

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.

Related

Vue: How to use v-model in post ApI

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

Jest Testing Vue-Multiselect

I have a Vue application using Jest/Sinon for testing.
Currently having issues testing the vue-multiselect html element. I can't seem to be able to click on it and show the options. I want to watch some methods get executed as I click but since I can't seem to register a click on the darn thing I don't see any changes :(
Goal:
Click on multiselect drop down
Click on an option
Close dropdown which will submit selection
EditUser.vue
....
<div class="col2">
<form>
<label for="firstName">First Name: {{editUser.first_name}}</label>
<label for="lastname">Last Name: {{editUser.last_name}}</label>
<label for="email2">Email: {{editUser.id}}</label>
<label for="managerEmail">Manager Email: {{editUser.manager_email}}</label>
<label v-for="role in editUser.role" v-bind:key="role">Current Roles: {{role}}</label>
<label class="typo__label">
<strong>Select Roles OVERWRITES existing roles:</strong>
</label>
<div id="multiselectDiv" :class="{'invalid' : isInvalid}">
<multiselect
v-model="value"
:options="options"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
:allow-empty="false"
placeholder="Select All Applicable Roles"
:preselect-first="false"
#open="onTouch()"
#close="submitRoles(value)"
></multiselect>
<label class="typo__label form__label" v-show="isInvalid">Must have at least one value</label>
</div>
<button class="button" #click="removeRoles">Remove all roles</button>
</form>
</div>
....
<script>
import { mapState } from "vuex";
import Multiselect from "vue-multiselect";
const fb = require("../firebaseConfig.js");
import { usersCollection } from "../firebaseConfig";
export default {
computed: {
...mapState(["currentUser", "userProfile", "users", "editUser"]),
isInvalid() {
return this.isTouched && this.value.length === 0;
}
},
methods: {
onTouch() {
this.isTouched = true;
},
submitRoles(value) {
fb.usersCollection
.doc(this.editUser.id)
.update({
role: value
})
.catch(err => {
console.log(err);
});
this.$router.push("/dashboard");
},
removeRoles() {
fb.usersCollection
.doc(this.editUser.id)
.update({
role: []
})
.catch(err => {
console.log(err);
});
this.$router.push("/dashboard");
}
},
components: { Multiselect },
data() {
return {
value: [],
options: ["admin", "sales", "auditor"],
isTouched: false
};
}
};
</script>
editUserTest.spec.js
test('OnTouch method triggers on open of select menu', () => {
const wrapper = mount(EditUser, {
store,
localVue,
propsData: {
options: ["admin", "sales", "auditor"],
},
computed: {
editUser() {
return {
first_name: 'Bob',
last_name: 'Calamezo',
id: 'bob#email.com',
manager_email: 'someManager#email.com',
role: ['admin', 'sales'],
};
},
},
});
expect(wrapper.vm.$data.isTouched).toBe(false);
expect(wrapper.find('#multiselectDiv').exists()).toBe(true);
const selectIt = wrapper.find('#multiselectDiv').element;
selectIt.dispatchEvent(new Event('click'));
console.log(wrapper.find('.col2').html());
// expect(wrapper.vm.$data.isTouched).toBe(true);
});
Any help would be greatly appreciated!
Thanks!
What worked for me was this:
import { Multiselect } from 'vue-multiselect';
const multiselect = wrapper.findComponent(Multiselect);
const input = multiselect.find("input");
input.setValue("substring of a label");
input.trigger("keydown.enter");
Critically input.setValue is important if you want to find something in a list, rather than knowing its fixed offset in advance.
I got it to work with this:
import { Multiselect } from 'vue-multiselect';
const multiselect = wrapper.findComponent(Multiselect);
await multiselect.trigger('focus');
await multiselect.trigger('click');
Did you try it with:
let multiselect = wrapper.find('#multiselectDiv');
multiselect.vm.$emit('open');

Dynamics inputs, the v-model update all values in v-for

I try this following code with Vue.js 2:
<div id="app">
<div v-for="(item, index) in items">
<div>
<input type="text" v-model="items[index].message">
<input type="text" v-model="items[index].surface">
</div>
</div>
<button #click="addNewfield">Add</button>
</div>
var app = new Vue({
el: '#app',
data: {
item: {
message: 'test',
surface: 45
},
items: [],
},
mounted() {
this.items.push(this.item)
},
methods: {
addNewfield() {
this.items.push(this.item);
}
}
})
The goal is to create news input when user click on Add button. I tried different ways like :
<input type="text" v-model="item.message">
But it doesn't work. If you write in "message" input, all "message" inputs will be updated.
How can I only updated the concerned value ?
Thanks for help !
This is happening because objects in Javascript are stored by reference. This means that every time you push this.item onto the array, it's adding a reference to the exact same object as the last.
You can avoid this by creating a new object each time:
methods: {
addNewfield() {
const obj = {
message: 'test',
surface: 45
}
this.items.push(obj);
}
}
Another option would be to clone the original object each time like:
methods: {
addNewfield() {
const clone = Object.assign({}, this.item);
this.items.push(clone);
}
}

Why is the Vue.js input value not updating?

I have a Vue.js text-input component like the following:
<template>
<input
type="text"
:id="name"
:name="name"
v-model="inputValue"
>
</template>
<script>
export default {
props: ['name', 'value'],
data: function () {
return {
inputValue: this.value
};
},
watch: {
inputValue: function () {
eventBus.$emit('inputChanged', {
type: 'text',
name: this.name,
value: this.inputValue
});
}
}
};
</script>
And I am using that text-input in another component as follows:
<ul>
<li v-for="row in rows" :key="row.id">
<text-input :name="row.name" :value="row.value">
</text-input>
</li>
</ul>
Then, within the JS of the component using text-input, I have code like the following for removing li rows:
this.rows = this.rows.filter((row, i) => i !== idx);
The filter method is properly removing the row that has an index of idx from the rows array, and in the parent component, I can confirm that the row is indeed gone, however, if I have, for example, two rows, the first with a value of 1 and the second with a value of 2, and then I delete the first row, even though the remaining row has a value of 2, I am still seeing 1 in the text input.
Why? I don't understand why Vue.js is not updating the value of the text input, even though the value of value is clearly changing from 1 to 2, and I can confirm that in the parent component.
Maybe I'm just not understanding how Vue.js and v-model work, but it seems like the value of the text input should update. Any advice/explanation would be greatly appreciated. Thank you.
You cannot mutate values between components like that.
Here is a sample snippet on how to properly pass values back and forth. You will need to use computed setter/getter. Added a button to change the value and reflect it back to the instance. It works for both directions.
<template>
<div>
<input type="text" :id="name" v-model="inputValue" />
<button #click="inputValue='value2'">click</button>
</div>
</template>
<script>
export default {
props: ['name', 'value'],
computed: {
inputValue: {
get() {
return this.value;
},
set(val) {
this.$emit('updated', val);
}
}
}
}
</script>
Notice that the "#updated" event updates back the local variable with the updated value:
<text-input :name="row.name" :value="row.value" #updated="item=>row.value=item"></text-input>
From your code you are trying to listen to changes.. in v-model data..
// Your Vue components
<template>
<input
type="text"
:id="name"
:name="name"
v-model="inputValue"
>
</template>
<script>
export default {
props: ['name', 'value'],
data: function () {
return {
inputValue: ""
};
},
};
</script>
If You really want to listen for changes..
<ul>
<li v-for="row in rows" :key="row.id">
<text-input #keyup="_keyUp" :name="row.name" :value="row.value">
</text-input>
</li>
</ul>
in your component file
<template>...</template>
<script>
export default {
props: ['name', 'value'],
data: function () {
return {
inputValue: ""
};
},
methods : {
_keyUp : () => {// handle events here}
};
</script>
check here for events on input here
To bind value from props..
get the props value, then assign it to 'inputValue' variable
it will reflect in tthe input element

Input field unable to change model when component and model are dynamically created

I have a data structure with nested objects that I want to bind to sub-components, and I'd like these components to edit the data structure directly so that I can save it all from one place. The structure is something like
job = {
id: 1,
uuid: 'a-unique-value',
content_blocks: [
{
id: 5,
uuid: 'some-unique-value',
block_type: 'text',
body: { en: { content: 'Hello' }, fr: { content: 'Bonjour' } }
},
{
id: 9,
uuid: 'some-other-unique-value',
block_type: 'text',
body: { en: { content: 'How are you?' }, fr: { content: 'Comment ça va?' } }
},
]
}
So, I instantiate my sub-components like this
<div v-for="block in job.content_blocks" :key="block.uuid">
<component :data="block" :is="contentTypeToComponentName(block.block_type)" />
</div>
(contentTypeToComponentName goes from text to TextContentBlock, which is the name of the component)
The TextContentBlock goes like this
export default {
props: {
data: {
type: Object,
required: true
}
},
created: function() {
if (!this.data.body) {
this.data.body = {
it: { content: "" },
en: { content: "" }
}
}
}
}
The created() function takes care of adding missing, block-specific data that are unknown to the component adding new content_blocks, for when I want to dynamically add blocks via a special button, which goes like this
addBlock: function(block_type) {
this.job.content_blocks = [...this.job.content_blocks, {
block_type: block_type,
uuid: magic_uuidv4_generator(),
order: this.job.content_blocks.length === 0 ? 1 : _.last(this.job.content_blocks).order + 1
}]
}
The template for TextContentBlock is
<b-tab v-for="l in ['fr', 'en']" :key="`${data.uuid}-${l}`">
<template slot="title">
{{ l.toUpperCase() }} <span class="missing" v-show="!data.body[l] || data.body[l] == ''">(missing)</span>
</template>
<b-form-textarea v-model="data.body[l].content" rows="6" />
<div class="small mt-3">
<code>{{ { block_type: data.block_type, uuid: data.uuid, order: data.order } }}</code>
</div>
</b-tab>
Now, when I load data from the API, I can correctly edit and save the content of these blocks -- which is weird considering that props are supposed to be immutable.
However, when I add new blocks, the textarea above wouldn't let me edit anything. I type stuff into it, and it just deletes it (or, I think, it replaces it with the "previous", or "initial" value). This does not happen when pulling content from the API (say, on page load).
Anyway, this led me to the discovery of immutability, I then created a local copy of the data prop like this
data: function() {
return {
block_data: this.data
}
}
and adjusted every data to be block_data but I get the same behaviour as before.
What exactly am I missing?
As the OP's comments, the root cause should be how to sync textarea value between child and parent component.
The issue the OP met should be caused by parent component always pass same value to the textarea inside the child component, that causes even type in something in the textarea, it still bind the same value which passed from parent component)
As Vue Guide said:
v-model is essentially syntax sugar for updating data on user input
events, plus special care for some edge cases.
The syntax sugar will be like:
the directive=v-model will bind value, then listen input event to make change like v-bind:value="val" v-on:input="val = $event.target.value"
So adjust your codes to like below demo:
for input, textarea HTMLElement, uses v-bind instead of v-model
then uses $emit to popup input event to parent component
In parent component, uses v-model to sync the latest value.
Vue.config.productionTip = false
Vue.component('child', {
template: `<div class="child">
<label>{{value.name}}</label><button #click="changeLabel()">Label +1</button>
<textarea :value="value.body" #input="changeInput($event)"></textarea>
</div>`,
props: ['value'],
methods: {
changeInput: function (ev) {
let newData = Object.assign({}, this.value)
newData.body = ev.target.value
this.$emit('input', newData) //emit whole prop=value object, you can only emit value.body or else based on your design.
// you can comment out `newData.body = ev.target.value`, then you will see the result will be same as the issue you met.
},
changeLabel: function () {
let newData = Object.assign({}, this.value)
newData.name += ' 1'
this.$emit('input', newData)
}
}
});
var vm = new Vue({
el: '#app',
data: () => ({
options: [
{id: 0, name: 'Apple', body: 'Puss in Boots'},
{id: 1, name: 'Banana', body: ''}
]
}),
})
.child {
border: 1px solid green;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<span> Current: {{options}}</span>
<hr>
<div v-for="(item, index) in options" :key="index">
<child v-model="options[index]"></child>
</div>
</div>

Categories