I'm starting to make a multiselect without any package ...
When I create a select multiple, it is necessary key ctrl for select multiple elements.
I hate this ...
Can I make it with a function?
<template>
<select multiple v-model="selected">
<option v-for="item in items" v-bind:key="item.id">{{item.name}}</option>
</select>
</template>
<script>
export default {
name: 'SelectMultiple',
props: {
items: Array,
},
data(){
return{
selected: ['option1']
}
},
}
</script>
You can try with :value instead v-model and use method on click event for selecting:
Vue.component('myselect', {
template: `
<div>
<select multiple :value="selected" #click="selection">
<option v-for="item in items" :key="item.id" :selected="isSelected(item.name)">{{item.name}}</option>
</select>
{{selected}}
</div>
`,
props: {
items: Array,
},
data(){
return{
selected: ['option1']
}
},
methods: {
isSelected(item) {
return this.selected.includes(item)
},
selection(e) {
const el = e.target.value;
if(this.selected.includes(el)){
this.selected = this.selected.filter(s => s !== el)
} else {
this.selected = [...this.selected, el]
}
}
}
})
new Vue({
el: "#demo",
data(){
return{
items: [{id:1, name:'option1'}, {id:2, name:'option2'}, {id:3, name:'option3'}, {id:4, name:'option4'}, {id:5, name:'option5'}]
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<myselect :items="items"></myselect>
</div>
Related
The question has three parts revolving around two files App.vue and X Array.vue :-
1.When value of input is changed, how it could be written back to the array?
2.If the value entered is empty how to remove the element from array?
3.How to show one extra input element always so that it is possible to add new values(linked with 2)?
XArray should basically be an array editor.
App.vue
<template>
<div>
<XArray v-model="myArray" />
<pre>{{ myArray }}</pre>
</div>
</template>
<script>
import XArray from './components/XArray.vue';
export default {
components: {
XArray,
},
data: () => {
return {
myArray: ['one', 'two'],
};
},
};
</script>
XArray.vue
<template>
<input
v-for="(option, index) in modelValue"
:key="index"
#input="$emit('update:modelValue', [...modelValue, `${$event.target.value}`])"
/>
</template>
<script>
export default {
props: {
modelValue: {
type: Array,
required: true,
},
};
</script>
Please take a look at following snippet:
const app = Vue.createApp({
data() {
return {
myArray: ['one', 'two'],
}
},
methods: {
addEl() {
this.myArray.push('new')
}
}
})
app.component('child', {
template: `
<div>
<input
v-for="(option, index) in modelValue"
:key="index"
#input="$emit('update:modelValue', upd(index, $event.target.value))"
:value="option"
/>
</div>
`,
props: {
modelValue: {
type: Array,
required: true,
}
},
methods: {
upd(idx, val) {
return val ? [
...this.modelValue.map((item, i) =>
i !== idx
? item
: val
),
] : this.modelValue.length > 1 ?
[ ...this.modelValue.filter((item, i) => {
if(i !== idx) return item
})] :
[ "last" ]
}
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<child v-model="myArray"></child>
<pre>{{ myArray }}</pre>
<button #click="addEl">add</button>
</div>
I require a lookup dropdown where I should be able to watch for multiple data changes. Currently I have this in my html:
<input list="allUsernames" type="text" v-model="selectedUser">
<datalist id="allUsernames">
<option v-for="(user, index) in allUsers"
:id="user.USERID"
:value="user.USERNAME"
:email="user.EMAIL">
</option>
</datalist>
My script data tag looks like this:
data(){
return{
allUsers: [],
selectedUserID: '',
selectedUserEmail: '',
selectedUser: '',
}
}
allUsers gets filled by an SQL call containing USERID, USERNAME and EMAIL.
I want a watch to be able to get the :id and the :email part of the option tag, but right now I can only seem to retrieve the value by default:
watch: {
selectedUser(val, oldval)
{
console.log('this only returns the :value binding:' + val);
//how do I get :id and :email values?
}
},
I want to set selectedUserID and selectedUserEmail based on the dropdown option selection made, using the vbindings :id and :email (so that I get the user.USERID and user.EMAIL values), how do I do this?
You can do this more cleanly with only the v-model data, and no watch is necessary:
data(){
return{
allUsers: [],
selectedUser: ''
}
}
Only the value binding is necessary on the options:
<option v-for="(user, index) in allUsers" :value="user.USERNAME"></option>
Use a computed to track the full object of the selected user:
computed: {
selected() {
return this.allUsers.find(user => user.USERNAME === this.selectedUser);
}
}
The selected computed refers to the whole object of the selected user, so you can use selected.USERID and selected.EMAIL in both the instance and the template.
Here's a demo:
new Vue({
el: "#app",
data(){
return{
allUsers: [
{ USERID: 1, USERNAME: 'name1', EMAIL: 'email1' },
{ USERID: 2, USERNAME: 'name2', EMAIL: 'email2' },
{ USERID: 3, USERNAME: 'name3', EMAIL: 'email3' },
],
selectedUser: ''
}
},
computed: {
selected() {
return this.allUsers.find(user => user.USERNAME === this.selectedUser);
}
}
});
.display {
margin-top: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input list="allUsers" type="text" v-model="selectedUser">
<datalist id="allUsers">
<option v-for="(user, index) in allUsers" :value="user.USERNAME"></option>
</datalist>
<div v-if="selected" class="display">
<div>ID: {{ selected.USERID }}</div>
<div>EMAIL: {{ selected.EMAIL }}</div>
</div>
</div>
A good key to remember is that a watch is only necessary if you want to perform an async or time-based action when data changes. Otherwise, in most cases, use a computed.
I have a very simple vue component
<template>
<div class="field has-addons">
<div class="control is-expanded">
<div class="select is-fullwidth">
<select v-model="selected" #change="onChange($event)">
<option disabled value="">Select a list for Sync</option>
<option v-for="option in selectOptions" :value="option.id">{{option.name}}</option>
</select>
</div>
</div>
<div class="control">
<a :href="syncUrl">
<div class="button is-success">Sync</div>
</a>
</div>
</div>
</template>
<style scoped>
</style>
<script>
export default {
props: {
lists: String,
training_id: String
},
mounted: function() {
console.log('mounted');
},
computed: {
syncUrl: function () {
return "/admin/trainings/" + this.training_id + "/sync_list_to_training/" + this.selected
}
},
data: function(){
return {
selectedList: '',
selectOptions: '',
selected: ''
}
},
beforeMount() {
this.selectOptions = JSON.parse(this.lists)
this.inputName = this.name
},
methods: {
onChange(event) {
console.log(event.target.value)
}
}
}
</script>
It works, I can display the select with my options and value.
By the way the component doesn't appear in Vue Inspector, no props or data inspection.
And the select doesn't work.
I have "mounted" in my log, the select is populated with the right data from the props. But when I seelct an option the change event is not fired.
Why? The component is working and not working, i cannot find a solution.
Edit: even if I change the component to the "vue Example"
<template>
<div class="field has-addons">
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
</template>
<style scoped>
</style>
<script>
export default {
data: function(){
return {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
}
}
</script>
Is doesn't work and I cannot see it in inspector...
Here is the invocation
import Vue from 'vue/dist/vue.esm'
import TurbolinksAdapter from 'vue-turbolinks';
Vue.use(TurbolinksAdapter)
import ActiveCampaign from '../../../application/components/active_campaign/ActiveCampaign.vue'
document.addEventListener('turbolinks:load', () => {
const vueApp = new Vue({
el: '#app-container',
components: {
'active_campaign': ActiveCampaign,
},
})
})
No error in console: vue-devtools Detected Vue v2.6.10
UPD:
Can you try this?
Another hack is to wait for the next tick:
<input type="checkbox" v-model="myModel" #change="myFunction()">
...
myFunction: function() {
var context = this;
Vue.nextTick(function () {
alert(context.myModel);
}
}
This is from here https://github.com/vuejs/vue/issues/293#issuecomment-265716984
I followed the solution to create dynamic form elements here:
Dynamically adding different components in Vue
Works great, my next conundrum is now I want to remove form elements if the user adds too many.
The way it works is the user creates a "set" which is defined as a group of inputs. So each set could be for a different person, or place, etc.
Here is my JSfiddle https://jsfiddle.net/61x784uv/
Html
<div id="component-pii-input" v-for="field in fields" v-bind:is="field.type" :key="field.id">
</div>
<button id='button-add-pii-component' v-on:click="addFormElement('pii-entry-field')">Add Set</button>
</div>
Javascript
Vue.component('pii-entry-field', {
data: function () {
return {
fields: [],
count: 0,
}
},
methods: {
addFormElement: function(type) {
this.fields.push({
'type': type,
id: this.count++
});
},
},
template: ` <div class='pii-field'><div>
<component v-for="field in fields" v-bind:is="field.type":key="field.id"></component>
</div>
<button id='button-add-pii-input' v-on:click="addFormElement('pii-input-field')">Add Input</button>
<hr>
</div>`,
})
Vue.component('pii-input-field', {
data: function () {
return {
}
},
template: ` <div>
<select>
<option disabled>Select Classification</option>
<option>Name</option>
<option>Address</option>
<option>Email</option>
<option>Phone</option>
<option>Medical</option>
<option>Financial</option>
</select>
<div>
<input type="text" placeholder="Input">
</div>
<button v-on:click="removeFormElement">Remove Input</button></span>
</div>`,
})
var app = new Vue({
el: '#app',
data: {
fields: [],
count: 0,
},
methods: {
addFormElement: function(type) {
this.fields.push({
'type': type,
id: this.count++
});
},
}
});
Here is a working fiddle: https://jsfiddle.net/e12hbLcr/
Walkthrough:
You need to give the component some kind of id to know what component should be removed. You can do this with props.
<component v-for="field in fields" v-bind:is="field.type" :id="field.id" :key="field.id">
Now create the function in the child-component and edit the button so it sends the id.
<button v-on:click="removeFormElement(id)">Remove Input</button>
Remember in Vue that props go down (parent -> child) and events up (child-parent). So now we need to tell the parent that this button was clicked and an id was sent.
removeFormElement(id) {
console.log('sending message up to remove id', id)
this.$emit('remove', id)
}
Tell the parent component to listen to that event and attach a function to that event.
<component v-for="field in fields" v-bind:is="field.type" :id="field.id" #remove="removeFormElement" :key="field.id">
Note that the # is same as v-on:
Finally remove that item from the fields array.
removeFormElement(id) {
console.log('removing form element', id)
const index = this.fields.findIndex(f => f.id === id)
this.fields.splice(index,1)
}
You should probably move these remove buttons into a <slot> of the component so you could pass in the scoped id.
But if you can't, you could $emit removal event on the $parent of the individual components, passing the id of the item to remove.
Vue.component('pii-entry-field', {
data() {
return {
fields: [],
count: 0,
}
},
mounted() {
this.$on('removeFormElement', this.removeFormElement);
},
methods: {
addFormElement(type) {
this.fields.push({
'type': type,
id: this.count++
});
},
removeFormElement(id) {
const index = this.fields.findIndex(f => f.id === id);
this.fields.splice(index, 1);
}
},
template: `
<div class='pii-field'>
<component v-for="field in fields" v-bind:is="field.type" :key="field.id"></component>
<button id='button-add-pii-input' v-on:click="addFormElement('pii-input-field')">Add Input</button>
<hr>
</div>`,
})
Vue.component('pii-input-field', {
data() {
return {
}
},
methods: {
removeFormElement() {
const id = this.$vnode.key;
this.$parent.$emit('removeFormElement', id);
}
},
template: `
<div>
<select>
<option disabled>Select Classification</option>
<option>Name</option>
<option>Address</option>
<option>Email</option>
<option>Phone</option>
<option>Medical</option>
<option>Financial</option>
</select>
<div>
<input type="text" placeholder="Input" />
</div>
<button v-on:click="removeFormElement">Remove Input</button>
</div>`,
})
var app = new Vue({
el: '#app',
data() {
return {
fields: [],
count: 0,
}
},
methods: {
addFormElement(type) {
this.fields.push({
'type': type,
id: this.count++
});
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="component-pii-input" v-for="field in fields" v-bind:is="field.type" :key="field.id">
</div>
<button id="button-add-pii-component" v-on:click="addFormElement('pii-entry-field')" class="uk-button uk-button-primary uk-width-1-1 uk-margin-small">Add Set</button>
</div>
I'm trying to implement custom select component with Vuejs 2. As stated in the documentation that i should not modify value props directly and suggested to use event to pass the selected data to parent component. I'm having issue when the option value is an object and got [Object object] instead.
here's my select component template:
<div :class="inputLength">
<select :id="id"
:value="value"
#change="setValue($event.target.value)"
:multiple="multiple"
class="selectpicker">
<option value="">Nothing selected.</option>
<option :selected="option == value" v-for="option in options"
:value="option">
{{ option[label] }}
</option>
</select>
<span v-if="error.any()" class="help-block" v-text="error.all()"></span>
</div>
and here's the script part:
export default {
props: {
value: {
default() {
return ''
}
},
options: {
type: Array,
require: true
},
...
},
methods: {
setValue(val) {
this.error.clear();
this.$emit('input', val);
}
}
}
and here's the parent component
<input-select-horizontal
v-model="form.category"
:label-class="{'col-md-4': true}"
input-length="col-md-8"
:options="categories.all()"
label="name"
:error="form.errors.get('category_id')">
<span slot="label">Category <span class="required" aria-required="true">*</span></span>
the options:
[
{
id: 1,
name: 'Category 1',
description: 'desc 1'
},
{
id: 2,
name: 'Category 2',
description: 'desc 2'
},
...
]
I'm expecting the
form.category = {
id: 1,
name: "Category 1",
description: "desc 1"
}
but got [Object object]
did i miss something?
Your problem lies here:
<option v-for="option in options" :value="option">
{{ option[label] }}
</option>
You're taking a whole object and assigning it to the value attribute of the option element. This won't work, because the value attribute has to be a string. So the object is converted to [Object object].
You should try using :value="option.id", the ID value should get through to the parent component normally and you can use it to find the right category.
As mzgajner mentioned, you can't bind an object because it will convert it to a string.
What you can do however, is to convert your object to a base64 string in the options component, and then decode it again in the select component.
For example:
Component CustomOption
<template>
<option v-bind="{ ...$attrs, value: innerValue }" v-on="$listeners">
<slot>
{{ label || $attrs.value }}
</slot>
</option>
</template>
<script>
export default {
props: {
label: [String, Number, Boolean],
},
computed: {
innerValue() {
return btoa(JSON.stringify(this.$attrs.value));
},
},
};
</script>
Component CustomSelect
<template>
<select
:value="innerValue"
v-bind="$attrs"
v-on="{
...$listeners,
input: onInput,
}"
>
<slot></slot>
</select>
</template>
<script>
export default {
props: {
value: null
},
computed: {
innerValue() {
return btoa(JSON.stringify(this.value));
},
},
methods: {
onInput(e) {
let value = JSON.parse(atob(e.target.value));
this.$emit('input', value);
},
},
};
</script>
https://www.npmjs.com/package/stf-vue-select
<stf-select v-model="value" style="width: 300px; margin: 0 auto">
<div slot="label">Input address</div>
<div slot="value">
<div v-if="value">
<span>{{value.address}} (<small>{{value.text}}</small>)</span>
</div>
</div>
<section class="options delivery_order__options">
<stf-select-option
v-for="item of list" :key="item.id"
:value="item"
:class="{'stf-select-option_selected': item.id === (value && value.id)}"
>
<span>{{item.text}} (<small>{{item.address}}</small>)</span>
</stf-select-option>
</section>
</stf-select>