Vue2 JS - v-for component in a form - vuejs2

i'm facing this problem.
I have a form where i get a component with a v-for cycle.
In this component I have a multiple select.
I cannot understand how to gather data from the components when submitting the form
I modified the question thanks to #Reinier68 tip on Components, bu still not getting
This is my component
<template>
<div>
<select
v-bind:value="selectedValue"
v-on:select="$emit('select', $event.target.selectedValue)"
:options="options"></select>
</div>
</template>
<script>
export default {
name: 'categorySelect',
data: function(){
return {
selected: []
}
},
props: {
options: Array,
selectedValue: null
},
}
</script>
Then, in the parent, i use this component in a v-for
<div v-for="cu in s.cus" :key="cu.cuId">
<category-select :options="options" v-model="selected[cu.cuId]" :selectedValue="value"></category-select>
</div>
The problem is that when submitting the for i still get nothing, my selected[] is null

props are for input, $emit is for output.
You pass in the value into the component with the prop, then on change, you use $emit to send the value out, then listen for the value on the component.
To assign back the selected value it's better to use a method but can be achived inline #select="v => $set(values, index, v)" and because its an array you need to use $set else it wont be reactive like #select="v => values[index] = v"
Below is an example, not using an object as you have not provided it, but; it would be the same prop in, emit out.
const categorySelect = {
template: '#category-select',
props: ['selected', 'options'],
data() {
return {
value: this.selected
}
},
methods: {
onChange(event) {
this.$emit('select', event.target.value)
}
}
}
//
new Vue({
el: '#app',
components: {
categorySelect
},
data() {
return {
options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
values: [1, 0, 5, 0]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.min.js"></script>
<div id="app">
<!-- -->
<div v-for="(value, index) in values" :key="index">
<category-select
:options="options"
:selected="value"
#select="v => $set(values, index, parseInt(v, 10))"
/>
</div>
<!-- -->
<pre>{{ values }}</pre>
</div>
<template id="category-select">
<select
v-model="value"
#change="onChange">
<option v-for="option in options">{{option}}</option>
</select>
</template>

Related

Only show slot if it has content, when slot has no name?

As answered here, we can check if a slot has content or not. But I am using a slot which has no name:
<template>
<div id="map" v-if="!isValueNull">
<div id="map-key">{{ name }}</div>
<div id="map-value">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
name: {type: String, default: null}
},
computed: {
isValueNull() {
console.log(this.$slots)
return false;
}
}
}
</script>
I am using like this:
<my-map name="someName">{{someValue}}</my-map>
How can I not show the component when it has no value?
All slots have a name. If you don't give it a name explicitly then it'll be called default.
So you can check for $slots.default.
A word of caution though. $slots is not reactive, so when it changes it won't invalidate any computed properties that use it. However, it will trigger a re-rendering of the component, so if you use it directly in the template or via a method it should work fine.
Here's an example to illustrate that the caching of computed properties is not invalidated when the slot's contents change.
const child = {
template: `
<div>
<div>computedHasSlotContent: {{ computedHasSlotContent }}</div>
<div>methodHasSlotContent: {{ methodHasSlotContent() }}</div>
<slot></slot>
</div>
`,
computed: {
computedHasSlotContent () {
return !!this.$slots.default
}
},
methods: {
methodHasSlotContent () {
return !!this.$slots.default
}
}
}
new Vue({
components: {
child
},
el: '#app',
data () {
return {
show: true
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button #click="show = !show">Toggle</button>
<child>
<p v-if="show">Child text</p>
</child>
</div>
Why you dont pass that value as prop to map component.
<my-map :someValue="someValue" name="someName">{{someValue}}</my-map>
and in my-map add prop:
props: {
someValue:{default: null},
},
So now you just check if someValue is null:
<div id="map" v-if="!someValue">
...
</div

How do i get the ViewModel from Vue's :is

i have these components:
<template id="test-button-component">
<div class="test-button__container">
This is test button
<button #click="clickButton">{{buttonTitle}}</button>
</div>
</template>
<template id="test-button-component2">
<div class="test-button__container">
<button></button>
</div>
</template>
I try to use the Vue's :is binding to do a component binding by name as follow:
<div :is='myComponentName' ></div>
every time the myComponentName changed to other component, the new component will replace the old component. The thing i need is, is there any way i can get the instance of the component so i can get the view model instance of the currently bound component?
You can add a ref attribute (for example ref="custom") to the <div> tag for the dynamic component. And then reference the component instance via this.$refs.custom.
Here's a simple example where the data of the component gets logged whenever the value being bound to the is prop is changed:
new Vue({
el: '#app',
data() {
return {
value: 'foo',
children: {
foo: {
name: 'foo',
template: '<div>foo</div>',
data() {
return { value: 1 };
}
},
bar: {
name: 'bar',
template: '<div>bar</div>',
data() {
return { value: 2 };
}
}
}
}
},
computed: {
custom() {
return this.children[this.value];
}
},
watch: {
custom() {
this.$nextTick(() => {
console.log(this.$refs.custom.$data)
});
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<select v-model="value">
<option>foo</option>
<option>bar</option>
</select>
<div :is="custom" ref="custom"></div>
</div>
Note that the $data for the component reference by $refs.custom is getting logged inside of a $nextTick handler. This is because the bound component won't update until the parent view has re-rendered.

Deleting vue component from list always delete the last element in list

I have read the documentation for rendering the custom components in list using v-for here.
But for some reason, I am not able to get this working.It always delete the last component instead of the one I send in the index. Any idea why it is not working ?
My VUE JS version is : 2.5.16.
Using PHPStorm IDE and running on docker (linux container)
And Laravel mix (I have "laravel-mix": "0.*" entry in package.json) to use webpack and compile the JS modules.
Here is the piece of some of my code
// Parent Component JS
<template>
<ul>
<li
is="child-component"
v-for="(child, index) in componentList"
:key="index"
:myVal="Something...."
#remove="dropField(index)"
#add-custom-field="addField"
></li>
</ul>
</template>
<script>
import childComponent from './ChildComponent';
export default {
name: 'CustomList',
components: {'child-component' :childComponent},
data() {
return {
componentList: []
}
},
methods: {
addField() {
console.log('Handling add-custom-field field...');
this.componentList.push(childComponent);
},
dropField(index) {
console.log(`I am deleting the component with index = ${index} from listview in parent...`);
this.componentList.splice(index, 1);
}
}
}
// Child Component JS
<template>
<div>
<input type="text" v-model="currentValue" /><button #click.prevent="$emit('remove')" > Remove </button>
</div
</template>
<script>
export default {
props: { myVal : '' },
data() { return { currentValue: ''} },
created() {this.currentValue = this.myVal;}
}
</script>
The issue is caused by in-place patch” strategy for v-for. That means Vue will not rebuild all childs when removed one element from componentList.
Check Vue Guide on an “in-place patch” strategy for v-for:
When Vue is updating a list of elements rendered with v-for, by
default it uses an “in-place patch” strategy. If the order of the data
items has changed, instead of moving the DOM elements to match the
order of the items, Vue will patch each element in-place and make sure
it reflects what should be rendered at that particular index.
Actually you already deleted the last item, but the problem is the data property=currentValue of first&second child have been 'a', 'b', when first mounted. Later when Vue re-render (delete the last child), data property=currentValue keeps same value though prop=myVal already changed.
Look at below demo, I added one input and bind myVal, you will see the differences.
Vue.config.productionTip = false
let childComponent = Vue.component('child', {
template: `<div class="item">
<p>Index:{{parentIndex}} => <button #click.prevent="removed()" > Remove </button>
Data:<input type="text" v-model="currentValue" />Props:<input type="text" v-bind:value="myVal" />
</p>
</div>`,
props: { 'myVal':{
type: String,
default: ''
} ,
'parentIndex': {
type: Number,
default: 0
}
},
data() {
return {
currentValue: ''
}
},
mounted() {
this.currentValue = this.myVal
},
methods: {
removed: function () {
this.$emit('remove')
}
}
})
app = new Vue({
el: "#app",
data() {
return {
componentList: ['a', 'b', 'c'],
componentType:childComponent
}
},
methods: {
addField() {
console.log('Handling add-custom-field field...');
this.componentList.push(childComponent);
},
dropField(index) {
console.log(`I am deleting the component with index = ${index} from listview in parent...`);
this.componentList.splice(index, 1);
}
}
})
li:nth-child(odd) {
background-color:#d0d5dd;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="(child, index) in componentList"><div
:is="componentType"
:key="index"
:my-val="child"
:parent-index="index"
#remove="dropField(index)"
#add-custom-field="addField"
>{{child}}</div></li>
</ul>
</div>
I discover that if you have another updated :key property (not index) it will work as you want
here's my example
<template>
<div id="app">
<ul>
<li
v-for="(teacher, index) in teachers_list"
v-bind="teacher"
:key="teacher.id"
>
<p>Teacher id {{teacher.id}}</p>
<button #click="deleteTeacher(index)"></button>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
teachers_list: [
{name: 'teacher a', id: 100},
{name: 'teacher b', id: 200},
{name: 'teacher c', id: 300},
]
}
},
methods: {
deleteTeacher(index) {
console.log(index);
this.teachers_list.splice(index, 1)
}
}
}
</script>

How can I add operator ternary in input type text on vue.js 2?

My vue component like this :
<template>
<div>
...
<li v-for="category in categories">
...
<input type="radio" class="category-radio" :value="category.id" (category.id == categoryId) ? 'checked data-waschecked=true' : ''>
...
</li>
...
</div>
</template>
<script>
export default {
props: ['categories', 'categoryId'],
}
</script>
I want to add condition in input type text. I use operator ternary like above code
If the code executed, it does not work
There is no error. So i'm confused to solve it
Maybe my code is still not correct
How can I solve it?
The issue is you're trying to use JavaScript expression inside plain HTML. This won't work.
You can either bind each attribute manually like this:
:checked="(expression) ? true : false"
or bind to a computed property which depends on your expression and returns your calculated property. Alternatively, you can bind an object with one to many properties, and bind the whole object at once (this is possible also):
new Vue({
el: '#app',
data: {
categories: [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
{ id: 3, name: 'three' }
],
selectedId: 2 // for simplicity
},
computed: {
attrs: function() {
return function(id) { // computed can also return a function, so you can use args
return (id === this.selectedId) ? { checked: true, 'data-waschecked': true } : {}
}
}
},
mounted() { // log your element
console.log(document.querySelector('input[data-waschecked=true]'))
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="category in categories">
<input type="checkbox" v-bind="attrs(category.id)">
</li>
</ul>
</div>

Vue custom filtering input component

I'am trying to create a component that have 'just' an text input. String typed in this input will be used to filter a list. My problem is that I cannot handle how to share this filter string between my component and the main app that contains the list to filter.
I tried several things and most of the time I get the error :
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value
So I looked Vuex but I thinks it cannot help in this case because I can have several filter component used in he same page for different list, and I don't want them to be synchronized ^^
Here is what I have:
The filter component
<script type="x/template" id="filterTpl">
<div>
<span class="filter-wrapper">
<input type="search" class="input input-filter" v-model.trim="filter" />
</span>
</div>
</script>
<script>
Vue.component('list-filter', {
props: {
filter: String
}
template: '#filterTpl'
});
</script>
And my main app:
<div id="contacts">
<list-filter :filter="filter"></list-filter>
<ul class="contacts-list managed-list flex">
<li class="contact" v-for="contactGroup in filteredData">
[...]
</li>
</ul>
</div>
<script>
var contactsV = new Vue({
el: '#contacts',
data: {
filter: "",
studyContactsGroups: []
},
computed: {
filteredData: function(){
// Using this.filter to filter the studyContactsGroups data
[...]
return filteredContacts;
}
}
});
</script>
Thanks for any help or tips :)
You can synchronize child value and parent prop either via explicit prop-event connection or more concise v-bind with sync modifier:
new Vue({
el: '#app',
data: {
rawData: ['John', 'Jane', 'Jim', 'Eddy', 'Maggy', 'Trump', 'Che'],
filter: ''
},
components: {
'my-input' : {
// bind prop 'query' to value and
// #input update parent prop 'filter' via event used with '.sync'
template: `<input :value="query" #input="updateFilter">`,
props: ['query'],
methods: {
updateFilter: function(e) {
this.$emit('update:query', e.target.value) // this is described in documentation
}
}
}
},
computed: {
filteredData: function() {
// simple filter function
return this.rawData.filter(el => el.toLowerCase()
.match(this.filter.toLowerCase()))
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<my-input :query.sync="filter"></my-input>
<hr>
<ul>
<li v-for="line in filteredData">{{ line }}</li>
</ul>
</div>