Vuejs 2.1.10 method passed as prop not a function - vuejs2

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

Related

Calling function from outside of component but rendered in v-for loop

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>

Vue updating components at the same time after push

I am building a form in Vue
I have a component that looks as follow:
<template>
<transition name="preview-pane">
<label>{{ option.group }}</label>
<input type="text" class="form-control"
:name="`group_name[${index}]`"
v-on:input="option.group = $event.target.value"
:value="option.group">
<a ref="#" class="btn btn-primary float-right" #click="$emit('copy')" role="button">{{ __('Copy') }} </a>
</transition>
</template>
<script>
export default {
props: {
option: {
group: ''
},
index: {}
}
}
</script>
My Vue instance is as follow:
var products = new Vue({
el: '#products',
data: {
options: []
},
methods: {
add() {
this.options.push({
group: ''
})
},
copy(index) {
this.options.push(this.options[index])
}
}
})
And last my html looks as follow
<product-option
v-for="(option, index) in options"
:key="index"
:option="option"
:index="index"
#copy="copy(index)">
</product-option>
I have one button that basically takes one of the options and push it once again (copy method on the vue instance). When I run everything seems fine but then when I change the input it update the props of all the components that have been copied.
What can I do to make vue understand that each component should work separately?
Well in case someone have the same issue, I sort it out like this:
copy(index) {
var object = this.options[index]
var newObject = {}
for (const property in object) {
newObject[property] = object[property]
}
this.options.push(newObject)
}

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 show raw html with slot in component vuejs

<template slot="content">
<form-label class="align-left">パスワードの再設定を行います。</form-label>
</template>
How can i show <form-label class="align-left">パスワードの再設定を行います。</form-label> as text for slot in component in Vuejs?
Vue.component("raw-text", {
template: `<div>
<span ref="source"><slot></slot></span>
</div>`,
mounted: function() {
this.$el.innerText = this.$refs.source.innerHTML;
}});
I've tried this, but $refs.source.innerHTML is compiled source which i'm not expect that.
i expect the output as:
<form-label class="align-left">パスワードの再設定を行います。</form-label>
but the actual is <div class="align-left nts-label"><label><!----> パスワードの再設定を行います。</label></div>
Use v-html directives:
Vue.component("raw-text", {
data: function() {
return {
slotHtml: 'パスワードの再設定を行います'
}
},
template: `<div>
<span ref="source"><slot v-html="slotHtml"></slot></span>
</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>