link2877 link2878 link2879 link2880 link2881 link2882 link2883 link2884 link2885 link2886 link2887 link2888 link2889 link2890 link2891 link2892 link2893 link2894 link2895 link2896 link2897 link2898 link2899 link2900 link2901 link2902 link2903 link2904 link2905 link2906 link2907 link2908 link2909 link2910 link2911 link2912 link2913 link2914 link2915 link2916 link2917 link2918 link2919 link2920 link2921 link2922 link2923 link2924 link2925 link2926 link2927 link2928 link2929 link2930 link2931 link2932 link2933 link2934 link2935 link2936 link2937 link2938 link2939 link2940 link2941 link2942 link2943 link2944 link2945 link2946 link2947 link2948 link2949 link2950 link2951 link2952 link2953 link2954 link2955 link2956 link2957 link2958 link2959 link2960 link2961 link2962 link2963 link2964 link2965 link2966 link2967 link2968 link2969 link2970 link2971 link2972 link2973 link2974 link2975 link2976 link2977 link2978 link2979 link2980 link2981 link2982 link2983 link2984 link2985 link2986 link2987 link2988 link2989 link2990 link2991 link2992 link2993 link2994 link2995 link2996 link2997 link2998 link2999 link3000 link3001 link3002 link3003 link3004 link3005 link3006 link3007 link3008 link3009 link3010 link3011 link3012 link3013

[Vue.js] bind click event to child component using v-bind Subscribe to RSS

I created a simple Minesweeper game and when it comes to the decision, which cell to render there are three possibilities:

Unrevealed cell
Revealed mine cell
Revealed neutral cell

I created a row component that renders all the cells contained by the row.

<template>
<div>

<component
v-for=”(cell, columnIndex) in row”
:key=”columnIndex”
v-bind=”getCellProps(cell, columnIndex)”
:is=”getComponentCell(cell)”
/>

</div>
</template>

<script>
// imports here

export default {
components: {
UnrevealedCell,
RevealedNeutralCell,
RevealedMineCell
},
props: {
row: Array,
rowIndex: Number
},
methods: {
getCellProps: function(cell, columnIndex) {
if(cell.revealed) {
if (cell.isMine) {
return {};
} else {
return {
mineNeighbours: cell.mineNeighbours
};
}
} else {
return {
unrevealedCell: cell,
x: columnIndex,
y: this.rowIndex,
cellClicked: this.onCellClicked
};
}
},
getComponentCell: function(cell) {
if(cell.revealed) {
if (cell.isMine) {
return RevealedMineCell;
} else {
return RevealedNeutralCell;
}
} else {
return UnrevealedCell;
}
},
onCellClicked: function(x, y) {
debugger;
}
}
}
</script>

Unfortunately my cellClicked event is not working. The child component is able to emit the event correctly but my onCellClicked doesn’t get executed. I think this is because I can’t write

cellClicked: this.onCellClicked

as it would normally be

@cellClicked

Without the @ the attribute might get added as a component property. How can I fix this to listen to the emitted cellClicked event?

Solution :

A few thoughts occur.

Firstly, the reason this isn’t working is because v-bind is used to set component props and element attributes. The @ prefix is a shorthand for v-on, so it isn’t a prop or attribute in this sense, it’s a directive in its own right. v-on does support an object version, just like v-bind, so you can do something like v-on=”getCellEvents(cell, columnIndex)” and return a suitable object for each cell type. This is probably the cleanest direct answer to the original question. Less clean and less direct answers are also available…

You could implement this by making cellClicked a prop of the child cell and then calling it as a callback function rather than emitting an event. Not saying you should, but you could. That would work with the code you posted above completely unchanged.

Another alternative is just to add the event listener for all cells. Include @cellClicked=”onCellCicked” in the template without worrying about the cell type. If the other cell types don’t emit that event then nothing will happen. vue.js doesn’t know what events a component can fire, you can listen for anything.

Further thoughts…

the cell template is a bit anaemic. I know people generally advise keeping logic out of the template but in the case I’d say you’ve probably taken it too far and it just makes things harder to understand. There are two ways you could address this:

Rewrite the component to use a render function instead. Templates exist because humans find them easier to read than render functions but in the case you’ve got all the logic in JavaScript anyway. The template isn’t really adding anything and going all-in with a render function would probably be easier to understand than what you have currently.
Move the logic into the template. I don’t see any obvious reason not to do it that way from the code you’ve posted. I’ll post an example at the end.

Either of these two approaches would remove the problem you had adding an event listener.

A final thought on the click events is that you could use event propagation to handle them instead. Add a single click listener on a suitable element of the surrounding component and don’t listen for events on the cells/rows at all. The single listener could then establish which cell was clicked (potentially fiddly) and whether anything needs to be done about it. While this would increase the coupling between the components I would imagine that it wouldn’t really matter as these components aren’t really reusable elsewhere anyway. I’m not recommending this as an approach at this stage but it is worth keeping in mind whenever you find yourself creating large numbers of repetitive components that all need the same events. In the scenario it would probably only make sense if you start to run into performance problems, and even then there will likely be better ways to fix such problems.

So, I promised an example of the template approach:

<template>
<div>
<template v-for=”(cell, columnIndex) in row”>
<unrevealed-cell
v-if=”!cell.revealed”
:key=”columnIndex”
:unrevealed-cell=”cell”
:x=”columnIndex”
:y=”rowIndex”
@cellClicked=”onCellClicked”
/>
<revealed-mine-cell
v-else-if=”cell.mine”
/>
<revealed-neutral-cell
v-else
:mineNeighbours=”cell.mineNeighbours”
/>
</template>
</div>
</template>

I’m not sure why the UnrevealedCell needs the x and y but if it’s just so that it can emit them as part of the event then you might want to consider registering the listener as @cellClicked=”onCellClicked(columnIndex, rowIndex)” and then there’s no need to emit the co-ordinates from the cell. I also wonder whether you need 3 separate components for these cells. My gut reaction is that one component would be more appropriate with the row component not needing to have any understanding of the individual cells at all.