[Feature] Add Track component for declarative analytics tracking#497
[Feature] Add Track component for declarative analytics tracking#497titouanmathis wants to merge 6 commits intodevelopfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests.
Additional details and impacted files@@ Coverage Diff @@
## develop #497 +/- ##
==============================================
- Coverage 67.96% 52.32% -15.65%
Complexity 20 20
==============================================
Files 77 4 -73
Lines 1998 86 -1912
Branches 357 0 -357
==============================================
- Hits 1358 45 -1313
+ Misses 558 41 -517
+ Partials 82 0 -82
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Export Size@studiometa/ui
Unchanged@studiometa/ui
|
b28de35 to
6c31f84
Compare
Implements #495 - A generic, declarative component to standardize analytics tracking compatible with GTM/dataLayer, GA4, Segment, and custom backends. Features: - Track component with data-on:* event syntax (click, submit, view, mounted, etc.) - TrackContext component for hierarchical context data merging - Event modifiers (.prevent, .stop, .once, .debounce, .throttle, etc.) - IntersectionObserver-based impression tracking (view event) - CustomEvent support with $detail.* placeholder syntax - Configurable dispatcher via setTrackDispatcher() - Default dispatcher pushes to window.dataLayer (GTM) Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude <claude@anthropic.com>
- Test getNestedValue returning undefined on non-object path traversal - Test .detail modifier for full event.detail merging Co-authored-by: Claude <claude@anthropic.com>
509f0b1 to
cf3d7c5
Compare
Co-authored-by: Claude <claude@anthropic.com>
|
@titouanmathis Great addition ! Do you think it would be relevant to do away with JSON by abandoning the simplified notation from Action in favor of a simpler API, since there's rarely more than one listener for data layers, it seems to me? <button
data-component="Track"
data-option-on="click"
data-option-data:event="cta_click"
data-option-data:location="header"
data-option-data:button_text="Subscribe"
class="px-4 py-2 bg-blue-400 text-white rounded hover:bg-blue-500">
Subscribe
</button>In general, I'm not a big fan of having to use single quotes in HTML attributes to be able to use JSON. |
|
@antoine4livre you are right that having multiple event listeners on a single element might be rare, but the use of JSON allow us to send complex data structure to the data layer (arrays, nested objects, etc.), which is common for e-commerce tracking (GA4's I am considering adding support for JSON5 at the framework level for all options, which would enable writing less verbose JSON-like attributes: <!-- JSON -->
<div data-component="Foo" data-option-config='{ "key": "value" }'>
<!-- JSON5 -->
<div data-component="Foo" data-option-config="{ key: 'value' }">This would come with a size cost for the framework, but it might be useful enough to outweigh this. |
|
| Component | Attribute Example | Value Type |
|---|---|---|
| Action | data-on:click="target.$el.textContent = 'Clicked'" |
JavaScript expression |
| Track | data-on:click='{"event": "cta_click"}' |
JSON object |
Conflict Scenarios
-
Using both components on the same element:
- Track will try to
JSON.parse()Action's JS code → Parse error - Action will try to interpret Track's JSON as effect definition → Undefined behavior
- Track will try to
-
Developer confusion: Same attribute name with different meanings across components
-
No runtime isolation: Both components iterate over all
data-on:*attributes without distinguishing between them
Recommended Solutions
-
Rename Track's attribute prefix (preferred):
- Use
data-track:clickinstead ofdata-on:click - Or
data-on-track:clickto keep theonsemantic
- Use
-
Alternative: Use different attribute for tracking data:
- Keep event in attribute name:
data-track-click='{"event": "..."}' - Or single attribute:
data-track='{"on": "click", "event": "..."}'
- Keep event in attribute name:
-
Document mutual exclusivity (minimum): If keeping current API, document that Track and Action cannot coexist on the same element.
The Action component already uses data-on:* attributes with JS expression values. Using the same prefix for Track (with JSON values) would cause namespace collisions. Using data-track:* makes the API unambiguous. Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude <claude@anthropic.com>
|
Hey !
Suggestions:
Exemple: <div data-component="Track"
data-option-event="view_item_list"
data-option-trigger="mounted">
{% for product in collection.products %}
<div
data-component="TrackItem"
data-option-item-id="{{ product.id }}"
data-option-item-name="{{ product.title }}"
data-option-item-price="{{ product.price | money_without_currency }}"
data-option-item-anything="MY VALUE">
</div>
{% endfor %}
</div>Downside of this is multiple "useless" HTML elements Questions:
Up for discuting this in depth if needed |
|
Hey @perruche, thanks for the detailed feedback! On JSON complexity I hear you, but the datalayer often requires complex payloads (nested objects, arrays) that are hard to map 1:1 to flat HTML attributes. GA4's I'll review how we can have both approach available and get back to you. On the Parent/Child pattern This is actually already available via On security The component uses the exact same data that is already present in the datalayer — it's only a structured API to push data to it. No new data is exposed, so no new risk. On GDPR Same answer: the component only pushes to the datalayer (or any other dispatcher configured via On debug mode This could be implemented with the Any opinion on which solution to implement? |
|
After a second read, I agree that this component should be able to listen to multiple events on the same element. The data attribute should be different from the action’s one as mentioned. What about an Concerning the debug mode, $log solution could be enough while GTM provides a browser extension to check this, I guess? |
|
This is honestly painfully heartbreaking to get sent an AI response Especially when the content is meh?
It's actually quite not a good example and how to pass multiple categories to an item items: [
{
item_brand: "Google",
item_category: "Apparel",
item_category2: "Adult",
item_category3: "Shirts",
item_category4: "Crew",
item_category5: "Short sleeve"
}
]
Linking the wrong url, file only exist in the PR |
🔗 Linked issue
#495
❓ Type of change
📚 Description
Implements a generic, declarative component to standardize analytics tracking compatible with GTM/dataLayer, GA4, Segment, and custom backends.
Features:
📝 Checklist