Solved

How to create snapshot capturing the data attribute of an element that was clicked?

  • 13 June 2022
  • 7 replies
  • 624 views

Userlevel 5
Badge +4

Imagine I have this element

 

<a href="https://site.com" data-name="foobar">Click this</a>

 

Assume I have an event that captures clicks on this.  Can someone give me example of snapshot javascript code to capture the value of `data-name` (so “foobar”)?  I often encounter this issue of wanting to write more advanced snapshots, but the heap documentation seems to be pretty poor on this and I feel like they could use way more practical examples for stuff like this.

icon

Best answer by dlad 16 June 2022, 00:54

View original

7 replies

Userlevel 5
Badge +4

I thought I solved it myself with this snapshot code:

 

(function(){return event.target.getAttribute('data-sub-module')})()

However, on the click, it wasn’t being picked up.


here’s a more specific example of code in our page:

<button type="button" data-module="collections" data-sub-module="top-10-daily-distractions" data-action="bookmark">
    <i class="fas fa-bookmark"></i>
</button>

My event is defined with this:

button[data-module="collections"]

Which correct makes when I clicked on the image. However, the value for sub-module isn’t coming through in the snapshot. I believe this has to do with the fact the data attribute is defined on the parent and not the image tag. 

 

anyone have any suggestions how to correctly get the value of `data-sub-module` in this case?

 

I’m really hoping I don’t have to do something crazy like this:

 

(function(){
var element = event.target;
while(element && element.parentElement && element.tagName != "TABLE") {
if (element.hasAttribute("data-sub-module") )
{
return element.getAttribute("data-sub-module")
}
element = element.parentElement;
}
})()
Userlevel 4
Badge +2

Hi Trevin,

Great question, thanks for posting. We’ll look at the snapshot documentation and evaluate adding more examples to cover more scenarios, but in the meantime, I can help out here.

One of the most important things to know about JS snapshots in Heap is the context of the click vs. the data you want to return. For clicks on this specific element, the following JS snapshot should return the value of the data-name attribute:

event.target.getAttribute('data-name')

 

There are a few non-obvious aspects of this that will help inform future snapshots:

event.target

In a JS Snapshot, ‘event.target’ always represents the specific element that was clicked. If the user clicked the specific element you highlighted above, then JS’s getAttribute() function returns the desired value.

DOM Traversal

A common scenario is where we’d like to capture that same attribute value, but the user did not click precisely on that element. For example, maybe the user clicked a checkbox that is a child element of an ancestor element that is common to both the checkbox and the attribute-containing element. In this case, event.target is the checkbox element, not the element that contains the desired attribute value. So the snapshot event.target.getAttribute(‘desired-attribute’) would not work, because event.target is the checkbox, not the element that contains desired-attribute.

This is a common scenario. We see it a lot on pages like product tile grids where one part of the tile has the desired context, but the user can click anywhere in the product tile, not always on the specific part of the product tile with the data we want.

The workaround is to “traverse the DOM” from the specifically-clicked-on-element, up to the common ancestor element that is a container of both the clicked element and the data-containing element, and back down to return the value from the data containing element, like this:

event.target.closest('<common_ancestor_element>').querySelector('<data_containing_element>').getAttribute('desired-attribute')

.closest() moves up the DOM to the common ancestor/container, and querySelector() moves down the DOM to the element that contains the desired data. The same concept is shown below in diagram format. The snapshots will return the specified values no matter where in the tile the user clicked:

DOM Traversal for JS Snapshots

This was probably a little more information than you bargained for but I hope it’s helpful! Please thread any questions.

Userlevel 5
Badge +4

 

DOM Traversal

A common scenario is where we’d like to capture that same attribute value, but the user did not click precisely on that element. For example, maybe the user clicked a checkbox that is a child element of an ancestor element that is common to both the checkbox and the attribute-containing element. In this case, event.target is the checkbox element, not the element that contains the desired attribute value. So the snapshot event.target.getAttribute(‘desired-attribute’) would not work, because event.target is the checkbox, not the element that contains desired-attribute.

This is a common scenario. We see it a lot on pages like product tile grids where one part of the tile has the desired context, but the user can click anywhere in the product tile, not always on the specific part of the product tile with the data we want.

The workaround is to “traverse the DOM” from the specifically-clicked-on-element, up to the common ancestor element that is a container of both the clicked element and the data-containing element, and back down to return the value from the data containing element, like this:

event.target.closest('<common_ancestor_element>').querySelector('<data_containing_element>').getAttribute('desired-attribute')

.closest() moves up the DOM to the common ancestor/container, and querySelector() moves down the DOM to the element that contains the desired data. The same concept is shown below in diagram format. The snapshots will return the specified values no matter where in the tile the user clicked:

DOM Traversal for JS Snapshots

This was probably a little more information than you bargained for but I hope it’s helpful! Please thread any questions.

 

Thanks! this is helpful.  However, I need some more info.

 

Imagine the Heap event is defined to catch clicks on this:

 

a[data-module="foo"], button[data-module="foo"]

 

Using your example, how would i use the closest() and querySelector() approach when the element could either by a <button> or <a> in this case?

I agree your approach works if I know the specific element.

In this case, is it best to just iteratively work up the DOM like i posted earlier?

(function(){
var element = event.target;
while(element && element.parentElement) {
if (element.hasAttribute("data-sub-module") )
{
return element.getAttribute("data-sub-module")
}
element = element.parentElement;
}
})()

My main reason for asking this is because I think my proposed approach is perhaps the most generic and powerful, that doesn’t require you to specify the exact ancestor characteristics, other than a data attribute you’re looking for.

Userlevel 4
Badge +2

Hi Trevin,

Thanks for your detailed followup. I think we’re sharing the same idea, just with different code. Your code will certainly walk up the ancestor chain until any element with the attribute data-sub-module is found. On this specific example, event.target.closest(‘[data-sub-module]’) should do the same thing. That code, alone, walks up the ancestor chain until the selector (in this case, any element with attribute [data-sub-module] is found. 

The code I shared originally that followed the patterns event.target.closest().querySelector()… simply walks up the ancestor chain then back down the descendant chain to a target element.

Userlevel 5
Badge +4

Hi Trevin,

Thanks for your detailed followup. I think we’re sharing the same idea, just with different code. Your code will certainly walk up the ancestor chain until any element with the attribute data-sub-module is found. On this specific example, event.target.closest(‘[data-sub-module]’) should do the same thing. That code, alone, walks up the ancestor chain until the selector (in this case, any element with attribute [data-sub-module] is found. 

The code I shared originally that followed the patterns event.target.closest().querySelector()… simply walks up the ancestor chain then back down the descendant chain to a target element.

Ah we are indeed talking about same thing. I didn’t realize that the .closest() took data attributes. 

Userlevel 4
Badge +2

Yep, it takes any valid CSS selector, it’s a great tool.

Userlevel 5
Badge +4

To clarify to anyone else reading this, this was the final snapshot code assuming you are looking for the attribute value data attribute named `data-module`:

 

(function(){

const attributeName = "data-module";

var element = event.target;
return element.closest('[' + attributeName + ']').getAttribute(attributeName);
})()

Note: The reason I put the `data-module` in a variable name is because I have 3 instances of this same snapshot code tracking 3 different data attributes and only wanted to change one line to reduce errors.

Reply