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>
Related
I have some component which is rendered 6 times in v-for loop. I'm trying to do onclick function which will call method inside of specified chart component. But now i'm getting errors like this.$refs.chart1.pauseChart() is not an function. This is how i'm trying to achieve it:
<BaseChart ref="`chart$[index]`" #click="pauseChart(index)"/>
pauseChart(index) {
this.$refs.[`chart${index}`].pauseChart()
}
refs inside v-for are arrays rather than singulars. Therefore, if you have
<template v-for="(something, index) in list">
<BaseChart ref="chart" :key="index" #click="pauseChart(index)" />
</template>
you should use
methods:
{
pauseChart(idx)
{
this.$refs.chart[idx].pauseChart();
}
}
For more information - refer to Vue documentation
ref will be having an array. hence, you have to access that with 0 index.
Live Demo :
Vue.component('child', {
methods: {
pauseChart() {
console.log('child method call');
}
},
props: ['childmsg'],
template: '<p v-on="$listeners">{{ childmsg }}</p>',
});
var app = new Vue({
el: '#app',
methods: {
pauseChart(chartIndex) {
this.$refs[chartIndex][0].pauseChart();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<div v-for="index in 6" :key="index">
<child childmsg="Hello Vue!" :ref="`chart${index}`" #click="pauseChart(`chart${index}`)">
</child>
</div>
</div>
at my example js code is:
new Vue({
el: '#app',
data: {
list: [1,2,3]
},
methods: {
change: function(number) {
return number === 2 ? 'a' : 'b';
}
},
components: {
'html-list': {
props: ['item'],
template: '<li :title='change(item)'> text </li>'
}
}
});
at my example html code is:
<ul id='app'>
<html-list
v-for:'num in list'
:item='num'
></html-list>
</ul>
I want the following result:
<ul id='app'>
<li title='b'> text </li>
<li title='a'> text </li>
<li title='b'> text </li>
</ul>
But it's not working.
I tried several different ways but couldn't find a way to dynamically bind data by calling a function on the component.
I don't want to use global components because there are so many components to create.
What am I doing wrong?
You trying to use function change, which is not declared in component.
There are several solutions for this:
declare function in component
components: {
'my-component': {
props: [...],
template: '...',
methods: {
change: function() {...}
}
}
}
if you want use function from parent component template: '<li :title='$parent.change(item)'> text </li>'
Use Vuex
I have a list of checkboxes:
<ul>
<li v-for="system in payment_systems">
<input type="checkbox" :id="'ps-' + system.id" v-bind:value="system" v-model="checked_payment_systems">
<label :for="'ps-' + system.id">{{ system.translated.name }}</label>
</li>
</ul>
And I need to store checked items to Vuex so I use computed properties like this:
computed: {
checked_payment_systems: {
get() {
return this.$store.state.program.payment_systems;
},
set(payment_systems) {
console.log(payment_systems)
}
}
},
The problem is that in setter I get only true/false instead of object or array of objects.
the computed property you defined v-models with an input value. the set property will be called on with the input value.
in your example, you are binding the same get-set to all of your checkboxes. it should be done differently.
if i where you, i would remove the v-model and manually declare a function to happen onchange and a value, and pass them the a key, yo get the specific value in my object.
i made for you an example: https://jsfiddle.net/efrat19/p87ag0w3/1/
const store = new Vuex.Store({
state: {
program:{
payment_systems:{'paypal':false,'tranzila':false},
}
},
mutations:{
setPayment(state,{system,value}){
state.program.payment_systems[system]=value;
}
},
actions:{
setPayment({commit},{system,value}){
commit('setPayment',{system,value})
}
}
})
const app = new Vue({
store,
el: '#app',
data() {
},
computed: {
checked_payment_systems(){
return system=>
this.$store.state.program.payment_systems[system]
}
},
methods:{
setValue(system,value){
this.$store.dispatch('setPayment',{system,value})
}
}});
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="
https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.0/vuex.js"></script>
<div id="app">
<li v-for="(value,system) in $store.state.program.payment_systems">
<input type="checkbox" :id="'ps-' + system.id" :checked="checked_payment_systems(system)" #change="setValue(system,$event.target.checked)">
<label :for="'ps-' + system.id" >{{system}}</label>
</li>
<br>
values in the store:
<br>
<br>
{{$store.state.program.payment_systems}}
</div>
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.
I'm very new to Vuejs and JS frameworks in general, so bear with me. I'm trying to call a method that resides in my root component from a child component (2 levels deep) by passing it as a prop, but I get the error:
Uncaught TypeError: this.onChange is not a function
at VueComponent._onChange (category.js:8)
at boundFn (vendor.js?okqp5g:361)
at HTMLInputElement.invoker (vendor.js?okqp5g:2179)
I'm not sure if I'm on the right track by assigning the prop to a method inside the child component, but see what you think:
index.js
var app = new Vue({
el: '#app',
data: function () {
return {
categories: [],
articles: []
}
},
methods: {
onChange: function () {
console.log('first one');
return function () {
console.log('second one');
}
}
},
});
The html:
<div id="app">
<sidebar :onChange=onChange :categories=categories></sidebar>
<varticles :articles=articles></varticles>
</div>
sidebar.js:
Vue.component('sidebar', {
props: ['onChange', 'categories'],
methods: {
_onChange: function () {
this.onChange();
}
},
template: `
<div class="sidebar">
<category v-for="item in categories" :onChange="_onChange" v-bind:category="item"></category>
</div>
`
});
category.js:
Vue.component('category', {
props: ['category', 'onChange'],
methods: {
_onChange: function () {
this.onChange();
}
},
template: `
<div class="category">
<h2>{{ category.name }}</h2>
<ul>
<li v-for="option in category.options">
<input v-on:change="_onChange" v-bind:id="option.tid" type="checkbox" v-model="option.checked">
<label v-bind:for="option.tid">{{ option.name }}</label>
</li>
</ul>
</div>
`
});
There's got to be simpler way to do this!
I'd suggest taking a look at this https://v2.vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case. A simplified version of your code is in this fiddle https://jsfiddle.net/z11fe07p/641/
When writing props in your templates declare them without Capital letters.
A prop declared as onChange in your props is equivalent to on-change in your html.
<sidebar :on-change=onChange :categories=categories></sidebar>
Also I would suggest looking at events and non parent-child communication if you want a link between components that are more than 1 level deep. https://v2.vuejs.org/v2/guide/components.html?#Non-Parent-Child-Communication