I'm trying to implement this (https://codepen.io/iprodev/pen/azpWBr) in Vue. How can I do that?
I have tried to make implement this like this
<template>
<div>
<canvas height="100" id="confetti" width="100"></canvas>
</div>
</template>
<script>
export default {
created () {
// JS code from codepen
}
}
</script>
Certainly, it is doable. There are a few things you need to do. All the code in the Codepen snippet is wrapped inside DOMContentLoaded event handler since you need access to actual DOM tree. With Vue, you cannot use this event as Vue applications are SPA and the loaded event will be fired much before your actual view is getting rendered.
In this case, you should use mounted event instead of created lifecycle event of the component. mounted ensures that component's DOM is actually attached to the main document.
Further, you will need access to the actual HTMLCanvasElement. Instead of locating it by ID, your Vue.js $refs construct like this:
<canvas ref="confetti" height="100" width="100"></canvas>
Inside your component, you can then access the DOM element as:
mounted() {
this.$refs.confetti // Reference to HTMLCanvasElement
}
This is how you avoid using global id attributes with Vue.js. Now rest of the code is just how you want to organize it inside your component. You can abstract canvas rendering logic into a separate module and pass Canvas element or the methods can become part of Vue instance.
Third and final. There are a couple of event handlers assigned on window object like resize. You have to ensure that you are cleaning them up when the component is getting destroyed. If you don't do this, then even if the component is destroyed, these events will continue to trigger handlers. For this use beforeDestroy lifecycle event provided by Vue. Clean up all your globally registered event handlers.
Related
Im currently struggling with something where i want to add a data attribute to a component and then based on when NUXT is loaded have a click event bind to all nodes that have this data attribute. Im not using v-on because i want to have this separated from current Vue logic. So example:
component a.vue is tagged with data-element
<a href="/somelink" data-element>link</a>
component b.vue also has HTM elements tagged with data-element
<button data-element>link</button>
When the app loads i need to then loop through all data-element and bind an eventlistner to them.
I tried the above method and that works to some degree but fails when reactivity sets in and the DOM is updated. I checked mixins (not recommended using VueJS3), used composition API, used a mutation observer that checked the DOM status for changes and based on new elements loaded it ads click events, looked at hooks etc but now im getting confused at what is the best way to proceed. Some solutions work to some extend but feels hacky. Or is there a completely different approach i am missing.
To be Sure that the DOM has been initialized you need to set your event listeners on "mounted" lifecycle
mounted() {
const elements = [...document.querySelectorAll('[data-element]')];
elements.forEach(element => {
element.addEventListener('click', () => { /* your code here. */ })
})
}
We are using single file vue components and in a mousemove event handler we'd like to be able to detect if the target element is clickable.
In our Vue templates we are using v-on directives: v-on:click="someCallback".
Unfortunately there doesn't seem to be an easy way to tell for a given element if an event listener was registered for it (i.e. via v-on directive).
For this we'd like to add a custom attribute to those elements with a v-on:click directve - i.e. "clickable". But this should happen automatically.
So we'd have to either wrap Vue's own "on"-directive into a custom one or somehow hook into Vue's rendering cycle - but this seems not very straight-forward: Couldn't find the directive on the Vue instance or the Vue component object.
What we have tried:
retrieving the information about registered listeners from the target element provided by the event object passed to the event handler. But apparently browsers don't provide this information.
searching the Vue component object for some object that stores information about which event listener has been registered for which element with which handler. We were not able to find this information - but it should be somewhere, right?
Hope anyone has a nice idea on how to accomplish adding a custom attribute to elements with v-on:click directive automatically.
Thanks!
EDIT:
So we have i.e.
<div id="x" #click="someMethod" />
in our template.
But we want to add a custom attribute automatically (we dont want to add it manually for all the trillion cases):
<div id="x" clickable #click="someMethod" />
Then in the event handler for addEventListener('mousemove', handler) we would check for this attribute: if (e.target.hasAttribute('clickable'))
But any other way of accomplishing this (so being able to tell inside the handler for mousemove if the element is clickable) would be fine too.
You could create a container component and import it into all your other vue components, ensuring it's the first component in your template, like:
<template>
<v-container>
// your template here
</v-container>
</template>
<script>
// Obviously replace the path and point to your location of the component
import ComponentContainer from './common/ComponentContainer.vue'
export default {
name: 'MyClientComponent',
components: {
'v-container': ComponentContainer
}
}
</script>
And this is the container component that looks for click events and adds the clickable class:
<template>
<div class="component-container">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'ComponentContainer',
mounted() {
this.$slots.default.forEach(vn => {
this.addClickableClassNames(vn);
});
},
methods: {
addClickableClassNames(vnode) {
if (vnode) {
let data = vnode.data;
if (data && data.on) {
// Check for click events and add a
// clickable class if one exists
if (data.on.click && vnode.elm && vnode.elm.classList) {
vnode.elm.classList.add('clickable');
}
}
// Now recursively check children
if (vnode.children) {
vnode.children.forEach(vn => {
this.addClickableClassNames(vn);
});
}
}
}
}
}
</script>
This works, but I wouldn't like to comment on the performance of a large dom. And you, and other devs, need to remember to import into all components, which isn't ideal. But, it's a solution that might give you other ideas on improving and making more scalable.
I can't think of a way to "automatically" add this clickable attribute. I think unfortunately you will still need to "tag" your clickable elements one by one.
I would have a directive which you can add to any element in your templates.
Directive
Vue.directive('myDirective', {
inserted(el, bindings) {
el.addEventListener('mouseover', () => {
alert(bindings.value);
})
}
});
Usage
<span v-my-directive="true">Element 1</span>
<span v-my-directive="false">Element 2</span>
You will notice that in the template when using the directive a value is being passed to it. This is then read via bindings.value. Of course based on this value you can do whatever functionality you need.
JSFiddle: https://jsfiddle.net/tc45xf82/2/
Vue Docs: https://v2.vuejs.org/v2/guide/custom-directive.html
I have a nested component, what is the Polymer way to travel up the DOM or component tree in this case.
<parent-component>
<some-component>
<component-i-am-starting-from></<component-i-am-starting-from>
</some-component>
<some-other-component>
</some-other-component>
</parent-component>
I'd like to be at a deep nested component and reference any of the parent components and their models or trigger an event inside one of them. Bonus if I can access sibling components, etc.
Traveling down was easy enough with
this.$.idOfChildComponent.event()
I have tried dispatchEvent, domHost, shadowRoot, can't seem to get any further up the component tree then the direct parent or get an error back that something is undefined, is not a function, etc.
Is there a way like React to pass a reference down as a property. The docs do not seem to be helpful nor scouring the internet.
Thanks!
update
So I am not sure if this is the correct way but it works ok calling a parent function from a child function.
<parent-component id="parentComponent">
<some-component>
<component-i-am-starting-from></<component-i-am-starting-from>
</some-component>
<some-other-component>
</some-other-component>
</parent-component>
componentIAmStartingFromFunc(){
document.getElementById('parentElement').parentElementFunc()
}
However does not seem to work for siblings?
** Update **
I essentially did the same the to call the sibling event by calling the parent from one of its children, and then sent out a trigger to the sibling which is also a child component.
Calling a parent function from child ;
child-component.html (polymer 2.x)
this.dispatchEvent(new CustomEvent('upway-func', {detail: {op:"Optionally I can send some data"}}));
child-component.html (polymer 1.x)
this.fire('upway-func', {detail: {op:"Optionally I can send some data"}});
parent-component.html
...
<child-component on-upway-func='_someFunction'></child-component>
...
_someFunction(d){
console.log(d.detail.op); // "Optionally I can send some data"
}
Here this link for more detail
Have you tried using this.parentElement?
Ask yourself: if I were a span in a div, how would I get to the div?
If you want to find a particular element in the ancestor chain by selector, you can use .closest() in good browsers or https://github.com/jonathantneal/closest
The message system mentioned in another answer can work but doesn't help you with positional relationship traversing which I assume your question was requiring. Passing ids and/or objects down can be used with messages in some cases.
How to target method inside other component?
I'm working on a project where I want to target a method inside another component. I press a button inside the dashboard component and I want to change the data inside my line-chart component (which uses vue-chartjs). How can I target this method?
Well you could target it over $refs however, this is pretty dirty, as it prodvides a pretty strict binding of your componentes.
A better solution would be to trigger an event (eventbus) or over an prop.
You can call children's method by referencing them through refs
In your example, your dashboard's template should looks like this :
<template>
<div>
<button #click="$refs.chart.yourMethod()">Call child method</button>
<line-chart ref="chart"></line-chart>
</div>
</template>
I have a parent component which has multiple child components
<grid>
<cell></cell>
<cell></cell>
<cell></cell>
</grid>
My cell components emit an event with payload saying it is being edited
this.$emit('editing',cellId)
I know I can capture the event like
<cell #editing="do something"></cell> or capture using EventBus.$on('editing'), I do not want to use root listener as well this.$root.$on('editing')
But because its the parent component, how can i listen to event of 'editing' when the parent component is mounted
mounted: function(){
this.$on('editing',dosomething)
}
why am I not able to add listen when the parent component is mounted?
The main difference that you are missing is described in the Custom Events section:
In addition, a parent component can listen to the events emitted from a child component using v-on directly in the template where the child component is used.
You cannot use $on to listen to events emitted by children. You must use v-on directly in the template, as in the example below.
What this means is that child-parent communication is done through a directive, using the v-on (or #edit) way.
Your example here
mounted: function(){
this.$on('editing',dosomething)
}
Won't actually work. In the emit documentation it's said that:
Trigger an event on the current instance.
Which means that inside the same component, you can actually use this.$on and it will work. But if you want to use it in parent, then you should use the inline directive to have it bind, otherwise it won't work.
Also keep in mind that emits works only for a single step, meaning that the the parent can catch it. If you need to emit subchild -> child -> parent, then you should catch the event (using inline binding) from subchild in child, and re-emit it again so it goes to parent.
If it is outside of children-parent scope, then you should use what is called a global bus. Basically it narrows down everything to a single instance, which emits and listens for all events. So then they are no longer child-parent or whatever kind of connection, they are all on the very same instance, and therefore you can always use them in every component of yours.
Bottom line is - your approach to listen on mounted won't work. Hope that helps :)
The difference between listening to each child and listening to the EventBus is this:
when you emit an event like this.$emit('event') the event will be fired only for the parent component.
when you do a EventBus like EventBus.$emit('event') then your event will be sent to all the components
There are pros and cons for each one; EventBus can send events to components which will never use that event (can become an event polution), and the parent event emitter can be not that elegant to use as EventBus.
So you decide which approach is good for you.
TL;DR
Stumbled across this issue my self. Like the other said, when you want to bind an event listener from a parent, you need to use v-on because, well ... , you are binding something tot he child. So the actual callback is ran by the child, when that child detects that the event ocured.
In the child use this.$parent.$emit('event-name');
In the parent use
mounted: function() {
this.$on('event-name', function() {console.log('test')});
}
The above will let you trigger the event in the parent so you can use $on in the parent to lsiten to that event that was triggered by the child.