I have a simple vue component:
Vue.component('repo-button', {
props:["check_in_id", "repo_id"],
template: '#repo-button',
methods: {
fetchRepo: function() {
console.log("this is working")
}
},
data: function() {
var that = this;
return {}
}
});
var repositionings = new Vue({
el: "#repo-vue"
})
And my view looks like:
<script type="text/x-template" id="repo-button">
<div class='socialCircle-item success'>
<i class='fa fa-comment'
:data-check_in='check_in_id'
:data-repo='repo_id'>
</i>
</div>
</script>
<div id="repo-vue">
<div is="repo-button" repo_id="8" check_in_id="30" #click="fetchRepo></div>
</div>
and I see the error:
[Vue warn]: Property or method "fetchRepo" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
It seems like I have registered the method, but it's looking in the root component for the method when I think it should be an instance method.
How am I supposed to include the fetchRepo instance method?
You've defined fetchRepo as a method of your component, but you are trying to call it from outside the component.
Move the click handler inside your component.
<script type="text/x-template" id="repo-button">
<div class='socialCircle-item success' #click="fetchRepo">
<i class='fa fa-comment'
:data-check_in='check_in_id'
:data-repo='repo_id'>
</i>
</div>
</script>
<div id="repo-vue">
<div is="repo-button" repo_id="8" check_in_id="30"></div>
</div>
Related
I have an app that shares an external module with other applications by sharing a global javascript object.
One of these apps is developed with vue 2 and when the global object is updated in the external module, the option data property of vue 2 is updated perfectly while in vue 3 it is not. I also tried with the new reactive property but nothing to do, is it a bug?
Not being able to make any changes to the external module because it is shared with other apps, how can I make it work in vue 3?
Here are some test links:
Vue 2 share external object
<script src="https://unpkg.com/vue"></script>
<script>
var EXTERNAL_OBJECT={
name:"Bob",
list:[{name:"Ivan"}]
}
function change_object(){
EXTERNAL_OBJECT.name+="+++"
EXTERNAL_OBJECT.list.push({name:"Carl"})
}
</script>
<button onClick="change_object()">change external object</button>
<div id="app">
<div>
{{share.name}}
</div>
<div v-for="item in share.list">
{{item.name}}
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
share:EXTERNAL_OBJECT
}
})
</script>
Vue 3 share external object
<script src="https://unpkg.com/vue#3.2.4/dist/vue.global.js"></script>
<script>
var EXTERNAL_OBJECT={
name:"Bob",
list:[{name:"Ivan"}]
}
function change_object(){
EXTERNAL_OBJECT.name+="+++"
EXTERNAL_OBJECT.list.push({name:"Carl"})
}
</script>
<button onClick="change_object()">change external object</button>
<div id="app">
<div>
{{share.name}}
</div>
<div v-for="item in share.list">
{{item.name}}
</div>
</div>
<script>
const app = Vue.createApp({
data () {
return {
share:EXTERNAL_OBJECT
}
}
});
app.mount('#app')
</script>
Vue 3 share external object with reactive property
<script src="https://unpkg.com/vue#3.2.4/dist/vue.global.js"></script>
<script>
var EXTERNAL_OBJECT={
name:"Bob",
list:[{name:"Ivan"}]
}
function change_object(){
EXTERNAL_OBJECT.name+="+++"
EXTERNAL_OBJECT.list.push({name:"Carl"})
}
</script>
<button onClick="change_object()">change external object</button>
<div id="app">
<div>
{{share.name}}
</div>
<div v-for="item in share.list">
{{item.name}}
</div>
</div>
<script>
const { createApp, reactive } = Vue
const app = createApp({
setup(){
let share = reactive(EXTERNAL_OBJECT)
return {
share
}
},
data () {
return {
msg:"reactive test"
}
}
});
app.mount('#app')
</script>
thanks
It is a bit hard to read ... I just look at the Vue3 Example.
How many file are you showing?
Cant write your EXTERNAL_OBJECT directly in the reactive property? Like:
const EXTERNAL_OBJECT = reactive({ name:"Bob",
list:[{name:"Ivan"}] });
This is my child element
<template lang="html">
<div class="row">
<div class="col-lg-8 col-md-8 col-sm-12 col-xs-12">
<bar-chart :v-if="this.barChartReadyToBeRendered" :chart-data='null' :height="340"></bar-chart>
</div>
<div class="flex-col-docs col-lg-3">
<div class="column" style="height: 150px">
<div class="col">
<q-select dark stack-label="Show Targets" class="select-notification"
v-model="selectTargetNotification"
:options="this.getTargetChangeOptions"
/>
</div>
<div class="col">
<q-select dark stack-label="Agency" class="select-notification"
v-model="selectOrgNotification"
:options="this.getOrganisationOptions"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import BarChart from '../../components/BarChart'
export default {
components: {
BarChart
},
.
.
/* Other code */
mounted () {
console.log('OUTSIDE MOUNTED')
this.$nextTick(() => {
console.log(this.$el)
let ctx = document.getElementById('bar-chart')
console.log('WWWWWWWWWWWWWWWWWW')
console.log(ctx)
console.log(this.$el)
this.createChart('bar-chart')
})
}
</script>
The bar chart chartjs is
<script>
import { Bar, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Bar,
mixins: [reactiveProp],
props: ['options'],
mounted () {
this.renderChart(this.chartData, this.options)
}
}
</script>
<style>
</style>
In my parent element, the template is
<template>
<q-page padding class="row justify-center">
<div style="width: 80vw; max-width: 100vw;">
<div class="flex-row-docs">
<div class="doc-container">
<q-list no-border>
<div class="row justify-start">
<div class="col-6">
<target-changes-agency></target-changes-agency>
</div>
</div>
<div class="q-mb-md q-mt-md q-headline">Full coverage</div>
<span v-if="!isNewsByIdLoaded" class="row justify-center">
<q-spinner-mat :size="36" style="color: #027be3ff; text-align: justify; margin: 2rem;" />
</span>
<div class="row">
<article-cluster :isNewsByIdLoaded="isNewsByIdLoaded"></article-cluster>
</div>
</q-list>
</div>
</div>
</div>
</q-page>
</template>
I am expecting to console.log(ctx) and console.log(this.$el), however the output of those 2 is null and <!-- --> respectively.
I thought mounted and this.$nextTick() will allow me to have access to the DOM. What am i missing here? please help thank you
Why are you assuming that document.getElementById('bar-chart') would return any element? There is no element with that ID being created. What you're rather looking for is document.getElementsByTagName('bar-chart'), but that will also yield no result, because Vue does not internally create Web Components, but inserts the component's root element in place instead. So, what you can do is give your bar-chart component an id attribute, which will be passed to the root element automatically.
The next issue is that your bar-chart component is only visible when the condition in v-if is truthy. That's probably not the case when the component is first being loaded. In this working minimal example, I simply set v-if="false".
const { Bar, mixins } = VueChartJs
const { reactiveProp } = mixins
const BarChart = Vue.component('bar-chart', {
extends: Bar,
mixins: [reactiveProp],
props: ['options'],
mounted () {
//this.renderChart(this.chartData, this.options)
this.$nextTick(() => {
console.log('mounted bar-chart component:');
console.log(this.$el)
});
}
});
Vue.component('example-component', {
template: `<div><bar-chart v-if="false" id="barchart" chart-data="null" height="340"></bar-chart></div>`,
components: [BarChart],
mounted () {
this.$nextTick(() => {
console.log('mounted child component:');
let ctx = document.getElementById('barchart')
console.log(ctx)
console.log(this.$el)
})
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-chartjs#3.5.0/dist/vue-chartjs.min.js"></script>
<div id="app">
<example-component></example-component>
</div>
(The stack snippet console actually hides the <!-- -->, but you can see it in this codepen. Vue automatically inserts this empty HTML comment as a placeholder for a component that is not currently being displayed.)
The output is actually expected, as the bar-chart component is not being rendered, therefore this.$el (referring to the child component, not the bar-chart component) is empty.
Now here ist the same snippet with v-if="true" on the bar-chart component:
const { Bar, mixins } = VueChartJs
const { reactiveProp } = mixins
const BarChart = Vue.component('bar-chart', {
extends: Bar,
mixins: [reactiveProp],
props: ['options'],
mounted () {
//this.renderChart(this.chartData, this.options)
this.$nextTick(() => {
console.log('mounted bar-chart component:');
console.log(this.$el)
});
}
});
Vue.component('example-component', {
template: `<div><bar-chart v-if="true" id="barchart" chart-data="null" height="340"></bar-chart></div>`,
components: [BarChart],
mounted () {
this.$nextTick(() => {
console.log('mounted child component:');
let ctx = document.getElementById('barchart')
console.log(ctx)
console.log(this.$el)
})
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-chartjs#3.5.0/dist/vue-chartjs.min.js"></script>
<div id="app">
<example-component></example-component>
</div>
As you can see, the logs now return the correct elements, also in the mounted() hook of the bar-chart component.
Of course, you shouldn't use the id attribute in your component if you ever plan to have multiple instances of this component, because it would result in multiple elements having the same ID, which is invalid HTML and might lead to unexpected interferences. So, this was only for demonstration purposes in this minimal example. In your real code, you could use Vue's ref attribute instead, which you can then refer to via this.$refs inside the parent component.
There are two other issues in your code:
You don't need the colon in front of v-if, because it automatically binds to the expression given as its value.
You don't need this. in your expressions, you're in the components context automatically and can simply use the properties' names directly.
I am fairly new to Vue but doesn't this behavior completely contradict the design of props down, events up?
I have managed to stop it by using Object.assign({}, this.test_object ); when initializing the value in child-component but shouldn't that be the default behaviour?
Here is some background.
I am trying to have a dirty state in a much larger application (Eg a value has changed so a user must save the data back to the database before continuing on their way)
I had an event being emitted, and caught by the parent but the code I had to test the value and init the dirty state was not running as the value had already been changed in the parent component.
Vue.component( 'parent-component', {
template: '#parent-component',
data: function() {
return {
testObject: {
val: 'Test Value'
}
}
}
});
Vue.component( 'child-component', {
template: '#child-component',
props: {
test_object: Object
},
data: function() {
return {
child_object: this.test_object
}
}
});
Vue.config.productionTip = false;
new Vue({
el: '#app',
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script type="text/x-template" id="parent-component">
<div>
<child-component :test_object="testObject"></child-component>
<p>This is in the parent component</p>
<p><code>testObject.val = {{testObject.val}}</code></p>
</div>
</script>
<script type="text/x-template" id="child-component">
<div>
<label for="html_input">HTML Input</label>
<input style="border:1px solid #CCC; display:block;" type="text" name="html_input" v-model="child_object.val" />
</div>
</script>
<div id="app">
<parent-component></parent-component>
</div>
Use of v-model is a very deceptive thing. If you are not careful, you might end-up mutating data that doesn't belong to your component. In your case, you are accidentally passing read-only props directly to the v-model. It doesn't know if it is a prop or a local component state.
What you are doing is the right solution but considering one-way/unidirectional data flow in mind, we can rewrite this example in more explicit and elegant fashion:
Your component definition would be:
Vue.component( 'parent-component', {
template: '#parent-component',
data: function() {
return {
testObject: {
val: 'Test Value'
}
}
},
methods: {
// Added this method to listen for input event changes
onChange(newValue) {
this.testObject.val = newValue;
// Or if you favor immutability
// this.testObject = {
// ...this.testObject,
// val: newValue
// };
}
}
});
Your templates should be:
<script type="text/x-template" id="parent-component">
<div>
<child-component :test_object="testObject"
#inputChange="onChange"></child-component>
<p>This is in the parent component</p>
<p><code>testObject.val = {{testObject.val}}</code></p>
</div>
</script>
<!-- Instead of v-model, you can use :value and #input binding. -->
<script type="text/x-template" id="child-component">
<div>
<label for="html_input">HTML Input</label>
<input type="text" name="html_input"
:value="test_object.val"
#input="$emit('inputChange', $event.target.value)" />
</div>
</script>
Key things to note:
When using v-model, ensure that you are strictly working on a local value/data of the component. By no means, it should be referenced copy of external prop.
A custom form-like component can be readily converted into the one that can work with v-model provided you accept current value as :value prop and event as #input. v-model will just work out of the box.
Any modification to the value should happen in the same component.
I am faced with a situation to render a third party date picker and also get the value from the third party component. since it is a third party i can't define a prop on it. However, the third party component provides an ability to pass variable in v-model.
So the design of my components requires me to be able to pass named slot from parent to child to grandchild.
for example;
//============ enter-or-upload-task.js =============
Vue.config.productionTip = false;
const sourceoftruth = {orderdate:''};
Vue.component('upload-by-csv',{
template:
`<div><input type="file" /></div>
`
});
//here i want to use the date picker
Vue.component('enter-task-by-form',{
data:function(){
return {
sourceoftruth
};
},
methods:{
getOrderDate: function(){
console.log(sourceoftruth.orderdate);
}
},
template:
`<div>
<label>Order Date:</label>
<!--Hoping to get datepicker component displayed here -->
<slot name="picker"></slot>
<input type="submit" #click.prevent = "getOrderDate()" />
</div>
`
});
const EnterTaskOrUploadByCSV = {
template:
`<div>
<upload-by-csv></upload-by-csv>
<enter-task-by-form>
<!-- wish to pass this named slot to this component -->
<slot name="picker"></slot>
</enter-task-by-form>
</div>
`
}
new Vue({
el:"#app",
data:{
sourceoftruth
},
components:{
'datepicker':vuejsDatepicker,
'enter-form-or-csv':EnterTaskOrUploadByCSV
}
})
// ===========================================================
The index.html is
<script src="./js/lib/vue.js></script>
<script src="./js/lib/vuejs-datepicker.js"></script>
<div id="app">
<enter-form-or-csv>
<datepicker v-model="sourceoftruth.orderdate" slot="picker">
</datepicker>
</enter-form-or-csv>
</div>
<script src = "./js/components/enter-or-upload-task.js"></script>
I have tried passing the named slot to the appropriate position but couldn't get it working. Please I need help on how to solve this.
Glad it's working now. The changes I made that it to work
in EnterTaskOrUploadByCSV i added a template slot to hold the datepicker component going to be received from the parent. then the grandchild component would be expecting the template named slot.
const EnterTaskOrUploadByCSV = {
template:
`<div>
<upload-by-csv></upload-by-csv>
<enter-task-by-form>
<template slot="passpicker">
<slot name="picker"></slot>
</template>
</enter-task-by-form>
</div>
`
}
The grandchild component expecting the template named slot.
Vue.component('enter-task-by-form',{
data:function(){
return {
sourceoftruth
};
},
methods:{
getOrderDate: function(){
console.log(sourceoftruth.orderdate);
}
},
template:
`<div>
<label>Order Date:</label>
<!-- Datepicker will show now -->
<slot name="passpicker"></slot>
<input type="submit" #click.prevent = "getOrderDate()" />
</div>
`
});
I have a button inside of a component that is calling an $emit('close') and the container that is holding the component has a #close='myMethod' and myMethod does not get called when you click the button that's inside the component.
Html:
<button #click="myMethod">outer</button>
<div class="parent" #close="myMethod">
<my-component></my-component>
</div>
<div id="my-component" style="display:none;">
<button #click="$emit('close')">emit</button>
</div>
JS:
Vue.component('my-component', {
template: '#my-component'
});
var app = new Vue({
el: '#app',
methods: {
myMethod: function() {
console.log('myMethod invoked');
}
}
});
If you click the outer button, the method gets invoked, but not the button inside the template. What am I missing?
Jsbin: http://jsbin.com/cuhipekocu/edit?html,js,console,output
You're not listening to the event on the component. Try
<div class="parent">
<my-component #close="myMethod"></my-component>
</div>