Firing an event from parent to child element in Polymer 2.0 - polymer-2.x

Can somebody provide an example of sending an event from parent to child in Polymer 2.o custom element?
I tried with following:
<child-element>
this.addEventListener('dbinit', this._evdbInitStatus);
and
<parent-element>
this.dispatchEvent(new CustomEvent('dbinit', {detail: {kicked: true}}));
The call back does not get invoked.

This is not really related to Polymer itself, as you can see in the documentation on how events work, here, events capturing stops at the element that triggered the event and the event bubbling (as the name suggests, the events bubble up) starts from the element and goes up in the tree.
So, in other words, an event fired by a parent element won't be captured by a child element. You will need to use a data binding to pass data down.
To keep a consistent, predictable flow of data, in general, it's better anyway if data travels down via data binding and up via events.

Related

Use $emit without a click event

I'm new to Vue and there are some things I didn't understand yet.
In the first child there's a click event to $emit information to the next child. But in the other one, the second child, is there anyway to pass the information to the next child using $emit without a click event or any other event (like scroll, hover etc)? I just want to pass it ahead. Can I use mounted()?
Yes! What you're looking for is $emit(), in combination with the v-on directive, both of which it sounds like you're already using.
In the child component that you mention, the lowest one on the chain, you're already capturing a click event from some component, and then presumably using $emit('my-event', [...some data...]) to emit a new event out from the child component itself.
All you need to do now is to add event listeners and handlers in the components up the chain, using Vue's custom events mechanic, so that they can receive and emit their own events, and the data can make it up to the parent. Custom events allow your components to emit and listen for events of any name, which may carry any data they'd like (this means they're not limited to being click/ hover/ blur/ etc.).
Here's the step-by-step of that approach:
(Structure: Parent <--> Child #1 <--> Child #2 <--> Child #3)
Child #3 captures a click event, and emits an event in its handler
Child #2 is listening for this event on #3, captures it, and $emits its own event in its handler
Child #1 is listening for this event on #2, captures it, and emits own event in its handler
Parent captures event, and executes handler
In code, you'll have a listener and handler in each of your components, which can look something like this:
(I've used inline handlers here for brevity, but you can use defined methods as handlers too, just call this.$emit(...) from the method.)
Child #3:
<component ... #click="$emit('my-event-a', $event)" />
Child #2:
<ChildComponent3 ... #my-event-a="$emit('my-event-b', $event)" />
Child #1:
<ChildComponent2 ... #my-event-b="$emit('my-event-c', $event)" />
Parent:
<ChildComponent1 ... #my-event-c="myHandler" />
$event here is just special Vue syntax for referencing the event data in an inline handler, which in this case allows your new event to contain the same data.
The events in the various child components can be named whatever you'd like, just make sure that the respective listeners are listening for the correct event name (eg. #event-x="..." for $emit('event-x') and #pizza-eaten="..." for $emit('pizza-eaten')).
I also wanted to mention that passing the event object (or any data at all) with an emitted event is completely optional. To do so, just invoke $emit with the event name and no other arguments: $emit('my-event'). This is useful when you simply care that an event has occurred, and the handler requires no further info.
There are many ways to approach this problem, but this is the most straightforward to understand, and works great as long as you don't need tons of direct interaction between a component and its deeply nested children.
If you do find your component structure getting more complicated, then you may want to look into a fully realized state management solution, like Vuex, which allows your components to interact with a state manager rather than having to deal with tons of events through a complicated component structure.

How to prevent selection of a SortableJS item when clicking on one of its child elements?

The Issue
I'm using SortableJS to build a draggable tree component. Which means each of my sortable-items has a toggle-arrow as a child element that opens and closes a sub-tree (if there is one).
I'm attempting to use stopPropagation() to prevent the selection of the parent sortable-item if the toggle-arrow is clicked, but it's not working.
It looks like this when closed:
And looks like this when open:
The blue highlight you see in the open state (the second image) is the styling I've chosen for the selectedClass option when using the multiDrag plugin.
This is illustrating that when I click on the toggle-arrow it results in the parent sortable-item being selected.
I don't want this to happen.
The Code
The code for an item in my SortableJS tree component looks like so (using Vue.js, and Pug syntax):
div.sortable-item
div.content
div.toggle-arrow(#click.stop="toggleTree($event)")
div.icon
div.title
div.sub-tree
And then I've got a handler for the #click binding on my toggle-arrow element:
toggleTree = function($event) {
$event.stopPropagation()
/// Code for handling the sub-tree toggling goes here.
/// The sub-tree toggling itself works just fine.
}
You can see that I'm declaring #click.stop as the event binding, which should stop the click event from bubbling up from the toggle-arrow child element, but it's not working.
I'm even attempting to use $event.stopPropagation within the handler. But, the event seems to continue to bubble, and thus the parent sortable-item element ends up in a selected state.
I've also tried declaring #click.native.stop as the event binding, but it simply prevents my toggleTree method from firing at all. I'm assuming there's another event handler somewhere within SortableJS that's interfering with the #click.native.stop binding.
Questions
How do I stop propagation of an event when a child element of my sortable-item is clicked?
How is selection handled by the multiDrag plugin? I dug through the code and saw that the select event is fired within the handler of the drop event of the sortable-item, but I'm confused by that. Why is the drop event handler being used to toggle selection of a sortable-item?
Thanks in advance for any light you may be able to shed on this.
Wrong Event
Looking at the source of SortableJS it seems that the event you want to stop from bubbling is not the click event, but rather the mouseup event.
The "Stuck" Drag Item Problem
As indicated in the comments of this answer, stopping propagation on the mouseup event causes an issue where the drag is started unintentionally, and the sortable-item becomes "stuck" to the pointer.
It seems that the "drag initiation" is triggered by either pointerdown, mousedown, or touchstart events, depending on the device.
It can be safely assumed that the pointerdown event is the one that does the triggering according to caniuse.com.
The Solution
So the actual way to solve this is to use a #pointerdown.stop event binding to trigger your toggleTree method without triggering either selection of the sortable-item, or the unintentional drag initiation.
div.sortable-item
div.content
div.toggle-arrow(#pointerdown.stop="toggleTree($event)")
div.icon
div.title
div.sub-tree
Change
div.toggle-arrow(#click.stop="toggleTree($event)")
to
div.toggle-arrow(#click.native.stop="toggleTree($event)")
If all you did in toggleTree was stopPropagation, you could have changed it to:
div.toggle-arrow(#click.native.stop)
Docs.
In short, you're currently stopping propagation on any emitted clicks from the child component (a.k.a. custom Vue event, which doesn't actually need propagation stopped as it doesn't bubble by default). What you want to do is call event.stopPropagation() on the native click event.
An alternative would be to use:
div.toggle-arrow(#click.native="toggleTree($event)")
... and call .stopPropagation() inside toggleTree. Which is precisely what .stop modifier does.

Detect when child component is rerendered

I have a link element injected into translation string that I later consume via aurelia-i18n plugin. I need to listen for a click event on this link. As far as I researched it is not possible to add click.trigger="function() to an html element in translation string.
So I ended up just manually assigning a listener to the link DOM element in attached method of parent component. However when language updates, link updates as well and my listener is gone. Is there a way to know when component has been updated so I could reassign the listener there?
Or is a better solution to this issues?

Modifying child data from parent vue

I have a component that represents an option in a form, with data representing the currently selected option. There is a parent component which represents the full form, with a submit button and a reset button. I keep track of what options are currently selected in the form by emitting events from the child to the parent (this is important because the form updates dynamically)
.
I'm trying to design the reset button, which clears all fields in the form (sets the currently selected option to an empty string). I would need to modify the data of the child component. Should I do this using a Vue instance as a bus? That seems overkill. Is there a better way to design these components?
I think you want to use sync on the properties your passing into the child component. I use it to load my child component like:
<textbox :content.sync="new_comment" placeholder="Add a comment..."></textbox>
If you already emitting from your child component then changes to new_comment will automatically be passed through.
You can find a lot of ways to do this here.
For me, after a lot of playing around with props, i found that the best and safest way is to use this.$refs.
Even if you have more than one child component with the same ref name, you can go through each child with a forEach.
You can create a custom event to listen to the reset button on each form field. Check out the documentation for this here
Just put a method in the child, perhaps Clear, and call it from the parent. You use $refs in the parent to get to the children.

How to communicate between components within a certain component?

Suppose I have a tree of component like this:
<widget>
<widget-header>
<panel-toggle></panel-toggle>
</widget-header>
<widget-body>
<panel></panel>
</widget-body>
</widget>
Now supposed I want the panel-toggle component to be able to toggle the visibility of the panel component. I could have it affect a prop passed down from widget through to each component, but that didn't seem like the best solution. I tried sending an event with this.$emit(eventName) but the event is only picked up by the immediate parent of the element emitting the event. In this case, that would be panel-toggle emitting the event and only widget-header being able to pick it up. I tried sending the event across the root element with this.$root.$emit(eventName) and picking it up with this.$root.$on(eventName), but then it is picked up by all widget components and that is no good. What I ended up doing is sending the event with this.$parent.$parent.$emit(eventName) and then picking it up from panel with this.$parent.$parent.$on(eventName). While that worked, it doesn't seem like the right way to go about this.
What would be the correct way to achieve this communication between components within the component widget only with Vue? Is the answer somehow related to the ref feature?
Since you're concerned (and with good reason) about the globalness of a global event bus, the solution is a localized event bus. Create a data item in the parent:
panelBus: new Vue()
and pass it to each of the children as a prop. Now they have a private communcation channel for just the two of them.