Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Navigating Salesforce Excellence
Navigating Salesforce Excellence
Passing a record Id from an Aura component into its JavaScript controller looks like it should be trivial, and then you hit the wall: inside an iteration, every rendered element fires the same handler, and the handler has no idea which row it came from.
The fix is HTML data-* attributes. You hang the values you need on the element itself, then read them back off the event. Here is the version that works, including the one mistake that catches nearly everyone.
Inside your iteration, add a data- attribute for each value you want to carry through. The names are yours, they just have to start with data-:
<aura:iteration items="{!v.records}" var="iterator">
<a data-category="{!iterator.value1}"
data-id="{!iterator.value2}"
onclick="{!c.action}">
{!iterator.label}
</a>
</aura:iteration>
Both values now travel with the element. No attributes on the component, no events, no helper round trip.
({
action: function (component, event, helper) {
var el = event.currentTarget;
var cat = el.dataset.category;
var id = el.dataset.id;
console.log('category: ' + cat + ', id: ' + id);
}
})
dataset converts each data-* attribute into a camelCase property, so data-category becomes dataset.category. If you prefer being explicit, el.getAttribute('data-category') does the same job.
Use event.currentTarget, not event.target.
They look interchangeable and behave identically right up until they do not:
data- attributes come back undefined.This is why the technique works fine in a bare test component and then breaks the moment someone puts an icon inside the link. If your values are mysteriously undefined only sometimes, this is why.
There is no limit. Add as many attributes as you need:
<a data-id="{!iterator.recordId}"
data-stage="{!iterator.stage}"
data-index="{!iterator.index}"
onclick="{!c.handleRowClick}"> ... </a>
One caveat worth knowing: every value arrives as a string. A number comes back as "42", and a boolean comes back as "false", which is truthy. Convert before you compare:
var index = parseInt(el.dataset.index, 10);
var isOpen = el.dataset.isOpen === 'true';
If you are migrating this pattern to LWC, it carries over almost unchanged:
<!-- template -->
<template for:each={records} for:item="rec">
<a key={rec.Id}
data-id={rec.Id}
data-category={rec.Category__c}
onclick={handleClick}>{rec.Name}</a>
</template>
// JavaScript
handleClick(event) {
const id = event.currentTarget.dataset.id;
const cat = event.currentTarget.dataset.category;
}
Same attributes, same currentTarget rule, same string conversion caveat.
event.target. Switch to currentTarget.dataset is camelCase. data-record-id becomes dataset.recordId, not dataset.record-id.{!c.action} and that a controller method of that exact name exists.That is the whole pattern. It is a small amount of markup, it avoids component events entirely for this kind of row-level interaction, and once you know the currentTarget rule it is reliable.