link1644 link1645 link1646 link1647 link1648 link1649 link1650 link1651 link1652 link1653 link1654 link1655 link1656 link1657 link1658 link1659 link1660 link1661 link1662 link1663 link1664 link1665 link1666 link1667 link1668 link1669 link1670 link1671 link1672 link1673 link1674 link1675 link1676 link1677 link1678 link1679 link1680 link1681 link1682 link1683 link1684 link1685 link1686 link1687 link1688 link1689 link1690 link1691 link1692 link1693 link1694 link1695 link1696 link1697 link1698 link1699 link1700 link1701 link1702 link1703 link1704 link1705 link1706 link1707 link1708 link1709 link1710 link1711 link1712 link1713 link1714 link1715 link1716 link1717 link1718 link1719 link1720 link1721 link1722 link1723 link1724 link1725 link1726 link1727 link1728 link1729 link1730 link1731 link1732 link1733 link1734 link1735 link1736 link1737 link1738 link1739 link1740 link1741 link1742 link1743 link1744 link1745 link1746 link1747 link1748 link1749 link1750 link1751 link1752 link1753 link1754 link1755 link1756 link1757 link1758 link1759 link1760 link1761 link1762 link1763 link1764 link1765 link1766 link1767 link1768 link1769 link1770 link1771 link1772 link1773 link1774 link1775 link1776 link1777 link1778 link1779 link1780

[Vue.js] Cannot get DOM events on component in host component

there is a vue.js component that contains a list of objects named lines. I build a table from those lines using different components based on the line type. This works perfectly. Here’s a stripped down version of the component:

<template>
<table>
<tr v-for=”line in lines”
:key=”line.key”
:is=”componentForType[line.eventType] || ‘LogLine’”
v-bind=”line”
/>
</table>
</template>

<script>
export default {
name: ‘DebugLog’,
components: {
LogLine,
FormattedLogLine,
UserDebug,
Limits
},
data () {
return {
lines: [],
selectedKey: null,
componentForType: {
‘USER_DEBUG’ : ‘UserDebug’,
‘LIMIT_USAGE_FOR_NS’ : ‘Limits’,
‘EXCEPTION_THROWN’ : ‘FormattedLogLine’,
‘FATAL_ERROR’ : ‘FormattedLogLine’
}

}
},
mounted() {
// code that loads this.lines
}
}
</script>

Now to be able to click any row of the table, and have the row become “selected”, meaning that store line.key in this.selectedKey and use CSS to render that line differently. But I can’t get the events working. Here’s the updated <template>; nothing else is changed:

<template>
<table>
<tr v-for=”line in lines”
:key=”line.key”
:is=”componentForType[line.eventType] || ‘LogLine’”
v-bind=”line”
:class=”{selected: line.key == selectedKey}”
@click.capture=”selectedKey = line.key”
/>
</table>
</template>

I’ve added the last 2 properties on the tr element - a dynamic class binding and a click event handler to set this.selectedKey to the active line’s key. But it isn’t working. I replaced the @click handler code with console.log(line.key) and nothing is logged, which tells me that my @click handler is never firing. I originally wrote it with out the .capture modifier, but tried adding the modifier when the original didn’t work.

Is vue.js stopping propagation from the child component to the parent? Can I not bind the click event on the tr since it :is another vue.js component? Or is there something else going on? The examples I’ve found in the docs are much simpler and I’m not sure they correspond to my situation. The various child components are not binding any click events. I’d prefer to handle the event entirely in the parent as shown, since I will have a number of types of child component, and I don’t want to have to implement click handlers in each.

Update: Looking at my child components, I note that each contains a tr tag that must effectively replace the tr in the parent template. For example, my most basic component is LogLine, shown here:

<template>
<tr>
<td>{timeStamp}</td>
<td>{eventType}</td>
<td>{lineNumber}</td>
<td>{lineData}</td>
</tr>
</template>

<script>
export default {
name: ‘LogLine’,
props: [‘timeStamp’, ‘eventType’, ‘lineData’, ‘lineNumber’],
data: function () {
return {}
}
}
</script>

So I’m guessing that the binding in the parent isn’t actually binding on the tr in the DOM; it’s just binding on the vue.js component, listening for a click event to be sent from the child with $emit; and that each child component will need to bind @click on its tr and emit it to the parent. Assuming I’m right, is there any shortcut I can use from the parent template to have vue.js forward the DOM events? Any other option I’m missing besides binding click in every child component?

Solution :

Piggy-backing off of Jacob’s answer here. Since you’re essentially attaching an event listener to a dynamic component it expects a custom click event. So you have two options here:

1) Listen for the native DOM click event within that component (by attaching a click event listener to a normal DOM element within the component) and emit a custom click event to the parent.

2) Use the .native modifier to listen for the native DOM click event instead of a custom one directly in the parent.

Solution 2:

Since you are using an :is prop, it’s considered a dynamic vue.js component, not a DOM element.

Events listener on a vue.js component won’t be passed down to its DOM element by default. You have to do it manually by going into the component template and add v-on=”$listeners”.

demo: https://jsfiddle.net/jacobgoh101/am59ojwx/7/

e.g. <div v-on=”$listeners”> … </div>

Solution 3:

@Jacob Goh’s use of v-on=”$listeners” is simple and allows forwarding of all DOM events in one action, but I wanted to document an approach I tried on my own for completeness. I will be switching to Jacob’s solution in my component. when now using Husam’s .native modifier in the parent as it is more suitable to my particular use case.

I was able to make my component work by editing each child component, capturing the click event and re-emitting it. For example:

<template>
<tr @click=”$emit(‘click’)”>
<td>{timeStamp}</td>
<td>{eventType}</td>
<td>{lineNumber}</td>
<td>{lineData}</td>
</tr>
</template>

<script>
export default {
name: ‘LogLine’,
props: [‘timeStamp’, ‘eventType’, ‘lineData’, ‘lineNumber’],
data: function () {
return {}
}
}
</script>