Vue3: How to receive #click event on parent DOM? - javascript

I have following structure in Vue3 template:
template:
/*html*/
`<div class='element1' #click='onClick'>
<img :src='image_url' />
<p>{{desc_text}}</p>
</div>`,
data() {
return {
image_url: "path/to/image",
desc_text: "Some text goes here"
}
},
methods: {
onClick(e) {
console.log(e);
}
}
Depending on where I click, console outputs e.target to be either <img>, <p> or <div class='element1'>, and it will have e.path set to array of elements, from the element that was topmost under the pointer to the bottom-most, while currentTarget is set to null.
I am looking for a solution where event handler would be called with e.target that points to the element that has #click listener added, in this case, <div class='element1'>. Not to point to any of it's children.
I've been looking at Vue3 documentation on the event bubbles, but so far no modifiers fit my need.
For example #click.self will only fire if e.target is the element that has the event listener, however, if pointer was above any of it's children, it will not fire.
Right now, I use pointer-events: none; styling on all children elements I don't want to have their own pointer event handling, which appears to have sufficient coverage. However, I'm surprised if Vue does not have any option on it's own to handle this case, maybe I am missing it?
I've read through Vue3 Event Modifiers document, but no option listed there does this.
In some cases, I have dozen of sub-elements in an element (eg. a card showing image, title, paragraph etc.), and I'd like entire element to be one big button to click on, with just one #click event handler set.

You can achieve same result (maybe better) without using event target but by passing arguments to the onClick handler.
What I usually do when rendering clickable list is that I pass the entire item
<div v-for="element in elements" :key="'element' + item.id" class="'element' + element.id" #click="onClick(element)">
<img :src='element.image_url' />
<p>{{element.desc_text}}</p>
</div>
It then become easy to handle the onclick as you already have the element you want at your disposal
onClick(element) {
console.log(element);
}

Related

When using tippyjs-react singleton, can I figure out the target tooltip inside the callbacks that attached to source tooltip?

So I have several Target tooltips and one Source, I want to attach onTrigger or onShow callback to the Source tooltip. Is there an easy way to figure out inside callback which Target tooltip is shown?
I don't want to check against smth like outerHtml etc, but rather to somehow attach ID to the tooltip, or at least some data-*** attribute
Example: https://codesandbox.io/s/optimistic-paper-8or8vc?file=/src/App.js
<Tippy content="OneOneOneOneOneOneOneOneOne" singleton={target}>
<button>One</button>
</Tippy>
<Tippy content="TwoTwoTwoTwoTwoTwoTwoTwoTwo" singleton={target}>
<button>Two</button>
</Tippy>
<Tippy
singleton={source}
onTrigger={function (instance, event) {
// is there a way for me to figuereout which target was the trigger?
console.log(instance, event);
}}
/>

Paper Toolbar javascript not reacting

I have a Polymer paper-toolbar element that takes some custom attributes like background-color and titles. It contains a.o. a search button and is called in various other elements.
The problem is that only the element where it is first called will display the search box when toggled while in other elements it does not.
This is the code for the toolbar:
<template>
<paper-toolbar class$="{{toolbarSize}}" style$="background-color:{{toolbarColor}};">
<span class="title"><p>{{topTitle}}</p></span>
<div id="searchbox">
<input-search placeholder="Search …"></input-search>
</div>
<paper-icon-button id="searchicon" icon="search" on-tap="openSearch"></paper-icon-button>
<span class="middle title"><h1>{{middleTitle}}</h1></span>
<span class="bottom title"><p class="subtitle">{{bottomTitle}}</p></span>
</paper-toolbar>
</template>
<script>
Polymer({
is: 'tool-bar',
properties: {
topTitle: String,
middleTitle: String,
bottomTitle: String,
toolbarSize: String,
toolbarColor: String
},
openSearch: function() {
var sb = document.getElementById("searchbox");
console.log(sb);
if (sb.hasAttribute("hidden")) {
sb.removeAttribute("hidden");
} else {
sb.setAttribute("hidden", true);
}
}
});
</script>
This is the code that calls in in various other elements:
<paper-scroll-header-panel>
<div class="paper-header staticpage">
<tool-bar
toolbar-color="var(--sc-gold-500)"
toolbar-size="tall"
middle-title="Titletext"
bottom-title="Subtitle text">
</tool-bar>
</div>
<page-view></page-view>
</paper-scroll-header-panel>
When I open the site and click on the search icon, it indeed toggles the searchbox just fine. But when I go to any other page (a different element that calls the same toolbar with different attributes), it does not toggle the toolbar any more.
This looks to me like a bug but if anybody has a solution or explanation for this behavior, I'd be very grateful. I have tried it with various other input-elements and it has the same result.
Output of the Console.log:
The console.log seems to indicate that everything is fine.
On the first page (where the element hides/unhides correctly):
First click: <div id="searchbox" class="style-scope tool-bar" hidden="true">
Second click: <div id="searchbox" class="style-scope tool-bar">
Then I move to another page/element and it gives exactly the same results, except that the element does not hide, even when the attribute hidden="true". When I look at the inspect element however, it does not show the attribute hidden="true".
However, when I click it so the console.log says that hidden="true", and then I move back to the first page/element, the searchbox is indeed hidden on that first page.
Because Polymer is based on Shadow-DOM, standard DOM selectors (such as document.getElementById('someId')) are ill-advised will lead to unexpected results. This is because a custom element will insert duplicate IDs into the DOM.
To overcome this, you must use Polymer's element selector method instead: Polymer.dom(this.root).querySelector('#someId'). This can be conveniently shortened to this.$$.someId. (where this is the custom element)
The Fix
For your code as above, change the openSearch function to the following:
openSearch: function() {
this.toggleAttribute('hidden', true, this.$.searchbox);
}
Thanks #Kuba for pointing out my initial error.
Fix Explanation
Element Selection
this.$ is an object of element IDs for the current custom element (this) when it was stamped onto the page. Therefore, this.$.searchbox gets the element's handle for this custom element's 'searchbox' element. This is in comparison to document.getElementById(...), which will only get the first element with id="searchbox" it finds on the page, not necessarily the one belonging to the current custom element.
Attribute Toggling
Polymer adds some special methods to it's element handles (that come from PolymerBase) for custom elements. One of these is the PolymerBase.toggleAttribute(String name [, Boolean value, Element node]) method (link to docs). To use this method with a polymer element, you call it on the element's reference from this.$ or this.$$.
For elements of a custom element, use:
this.toggleAttribute('hidden', true, this.$.someElementId)
If the target element is a custom element loaded by polymer, you can also use:
this.$.someElementId.toggleAttribute('hidden')
this.$.someElementId.toggleAttribute('hidden', true)
As a side note: Please rename your toolbar to vims-toolbar or similar to follow the custom elements naming scheme of <namespace>-<element-name>.
Further reading: PolymerBase docs

Always select a parent element when clicking on a child

I have a div (a tab) with 3 span inside, like this:
<div class="chat-tabs">
<div class="chat-tabs-cont">
<div id="chat-tab-1" class="chat-tab chat-tab-sel">
<span class="chat-tab-n">1</span>
<span class="chat-tab-t">Tab text 1</span>
<span class="chat-tab-c">11:00</span>
</div>
<div id="chat-tab-2" class="chat-tab">
<span class="chat-tab-n">2</span>
<span class="chat-tab-t">Tab text 2</span>
<span class="chat-tab-c">11:30</span>
</div>
</div>
</div>
These are tabs, so when I click on one tab, I have a click event in Meteor to give the new tab a class of chat-tab-sel and remove this class from old tab (standard tab behaviour).
My problem is that depending where the user clicks, my event.target is not allways the parent div chat-tab, but one of child span. And I need to add/remove classes to the parent div.
I think if the parent has display: block it may work, but in this case I need it to be display: flex because it makes sense to have flexible width on childs.
So: Is it possible to ensure that the parent div is targeted when user clicks on a child?
If you use a normal Meteor event handler in combination with #Brian Shamblen's tip it should just work.
Template.myTemplate.events({
'click .chatTab': function(ev){
$(".chat-tab").removeClass("chat-tab-sel"); // remove from all
$(ev.target).closest(".chat-tab").addClass("chat-tab-sel"); // set the one you're on
}
});
Events bubble. This means that you can listen for events on the parent. In an event listener this is bound to the element that you bind the event listener to. So we can listen for any clicks inside that element, then set the current active tab to inactive, and the clicked tab to active.
Not sure about Meteor specifically, but this is how I would accomplish this using vanilla JavaScript.
var tabs = document.querySelectorAll('.chat-tab');
for(var i in Object.keys(tabs)) tabs[i].onclick = function() {
document.querySelector('.chat-tab.chat-tab-sel').className = 'chat-tab';
this.className += ' chat-tab-sel'
}
.chat-tab-sel {
border: 1px solid #012450;
}
<div class="chat-tabs">
<div class="chat-tabs-cont">
<div id="chat-tab-1" class="chat-tab chat-tab-sel">
<span class="chat-tab-n">1</span>
<span class="chat-tab-t">Tab text 1</span>
<span class="chat-tab-c">11:00</span>
</div>
<div id="chat-tab-2" class="chat-tab">
<span class="chat-tab-n">2</span>
<span class="chat-tab-t">Tab text 2</span>
<span class="chat-tab-c">11:30</span>
</div>
</div>
</div>
Building on Tiny Giant's answer on events bubbling, here is to do this in meteor:
Template.theTemplate.events({
'click .chat-tab': function(ev) {
$('.chat-tab').removeClass('chat-tab-sel');
$(ev.currentTarget).addClass('chat-tab-sel');
}
});
Here is a interesting info.meteor.com blogpost that goes into some detail on this:
Browser events: bubbling, capturing, and delegation
Suppose you have this HTML structure:
<body>
<p>
<a><span>Hello</span></a>
</p>
</body>
Event delegation is not a browser feature, but a popular technique built into libraries like jQuery. Many blogs get confused talking about it or equate it with bubbling, but I hope the following description is clear.
Suppose you want to respond to a click on any A tag on the page, even if the set of A tags changes over time. In particular, you don't want to visit every A tag and add an event listener. So, taking advantage of bubbling, you bind a single event handler to the BODY, and from this handler you look at event.target to see if an A was clicked, and if so, which one. Be careful, though, because event.target may be the SPAN! You need to not just check if the event's target is an A tag, but also walk up the DOM tree in a simple simulation of bubbling.
This is event delegation. The BODY element is the delegate that handles events on behalf of the A tags. Conceptually, we'd like to think of the event handler as being on the A tags, so we create that illusion as much as we can. To that end, the final step in event delegation (at least in jQuery and Meteor) is to set event.currentTarget to the A tag. Further code that handles the event then sees an A tag as currentTarget and a SPAN tag as target. The BODY element is not really important, so it is nowhere to be found.

When a Div class is clicked, alert it's inner content's Div Class

How do i even put these, let me try. In the following sets of codes, i want to click 'parentclass' and have an alert value of 'child1' and when i click the class below it which is 'Parent 2' have an alert fire with a value of 'child2'
So this must alert the content of that class only and not the entire class.
Here's some Javascript in Jquery.
var childclass = $('.childclass').html();
$('.parentclass').click(function(e) {
alert (childclass)
});
$('.childclass').click(function(e) {
e.stopPropagation()
e.preventDefault()
});
And HTML
<a href="" onClick="return false">
<div class='parentclass'>
Parent 1
<div style="display:none" class="childclass">child1</div>
</div>
</a>
<a href="" onClick="return false">
<div class='parentclass'>
Parent 2
<div style="display:none" class="childclass">child2</div>
</div>
</a>
This line var childclass = $('.childclass').html(); doesnt make sense as it doesn't know which element in particular you mean. The result of that will just be child1child2 which is just a concatenation of the .html() of all the elements with class childclass. This is obviously not what you want.
Therefore you should dynamically find the child with a class of childclass upon receiving the click event.
$('.parentclass').click(function(e) {
alert($(this).find('.childclass').html())
});
Also, you should know that your child class event handler is useless as we don't care if the event gets propogated downwards. If you DID care, then your e.stopPropagation() and e.preventDefault() should be in the event handler of the parent class.
You need to fetch the html of the clicked parent element within the click handler
$('.parentclass').click(function (e) {
alert($(this).find('.childclass').html())
});
$('.childclass').click(function (e) {
e.stopPropagation()
e.preventDefault()
});
Demo: Fiddle
Several ways you can go about this.
First, if your HTML will not be dynamic (elements already exist when page loads), then you can select elements by the parent class name and assign click event as so:
$('.parentclass').click(function(e) {
// the first variable here is selecting the inner elements having class 'childclass'
// keep in mind, if more than one child having that class is present within this parent, it will select all of them
var child = $(this).find('.childclass');
// here we alert the text of the inner child found
// if it is more than one, you will have undesired results. you may want to specify `.first()`
alert(child.text())
})
For newer jQuery you can also use $('.parentclass').on('click', function(e) {.
If you expect any pieces of parentclass to be dynamic, then you'll want to delegate the event based on either a static parent to the parents or document. This can be like so:
$(document).on('click', '.parentclass', function(e) {
alert($(this).find('.childclass').text())
})
Or, if you have a static (already there when page loads) wrapping element, give it an ID like `parentClassWrapper' and assign the click event dynamically as:
$('#parentClassWrapper').on('click', '.parentclass', function(e) {
alert($(this).find('.childclass').text())
})
Some helpful links:
jQuery API
jQuery Selectors
.click()
.on()
Some info on Event Delegation
jquery on vs click methods
jQuery .on('click') vs. .click() and .delegate('click')
jquery .live('click') vs .click()
I made several adjustments to your html that are worth noting. There's no need for the <a> tag. Don't use inline js - onlick in your html. Note that I wrapped the text inside of the div in the <a> tag instead. This markup is more semantic. Also, move your styles to css rather than in the html.
<div class="parent">
<a>Parent 1</a>
<a class="child">child of parent 1 contents</a>
</div>
<div class="parent">
<a>Parent 2</a>
<a class="child">child of parent 2 contents</a>
</div>
css:
.parent > .child { /* good practice: only select an immediate child of the parent */
display: none;
}
The other answers here are using find() to select the child, but I recommend children() instead. For example, if you had additional nested .childs, find() will select them all, but children() will only select direct .childs of the parent, so it is better in this case. I also recommend using the console for debugging rather than alert.
Live demo here (click).
$('.parent').click(function() {
var $child = $(this).children('.child');
var cls = $child.attr('class');
console.log(cls);
$child.show(); //added so that you can click the child
});
$('.child').click(function() {
var html = $(this).html();
console.log(html);
//if you just want the text, use this instead:
var text = $(this).text();
console.log(text);
});

Event on one element to trigger event on another

I'll use a specific example but this could apply to any event on one element triggering any event on another. Suppose that we want to focus an <input> when we click a <span>. We can do this using a directive like:
<div ng-controller="Ctrl">
<span ng-focus-other>focus</span><input>
<div>
app.directive("ngFocusOther", function () {
return function (scope, element) {
element.bind("click", function () {
this.nextSibling.focus();
});
};
});
However this does not seem very Angular. This would also not work were there any changes to the DOM (e.g. if <input> were moved before <span>). It would also be nice if the same code could work on multiple groups of <span>-<input> combinations perhaps each with a different DOM layout.
What is the Angular way of using one event to trigger another event on another element?
Why not simply have clicking the span set a boolean called "inputFocused" to true and then let the input pay attention to that scope variable. I.e.
<span ng-click="inputFocused = true">FOCUS</span>
<input ng-focus="inputFocused" value="GIVE ME FOCUS">

Categories