12

I'm trying to construct a page that allows users to edit records, and it should be in SLDS, so I'm trying to use lightning:input to minimize the amount of code I need to write. Ultimately, a method will be called to update the records on the server. Ideally, I'd like to be able to identify which row was updated without saving/comparing the state of all the input elements.

Using normal HTML inputs, this is trivial. Under Locker Service, it appears that I can't traverse back up into my code to figure out which element to use, nor can I set custom data elements to identify which row is being updated. Here's a simple example that demonstrates what I'm trying to achieve:

<aura:application extends="force:slds">
    <aura:attribute name="message1" type="String" />
    <aura:attribute name="message2" type="String" />
    <aura:attribute name="values1" type="List" default="['','']" />
    <aura:attribute name="values2" type="List" default="['','']" />

    <aura:iteration items="{!v.values1}" var="value" indexVar="index">
        <div data-index="{!index}" onchange="{!c.update1}">
            <label class="slds-form-element__label" for="{!'item'+index}">HTML Input</label>
            <input class="slds-input" id="{!'item'+index}" value="{!value}" />
        </div>
    </aura:iteration>
    <hr />
    {!v.message1}
    <hr />
    <aura:iteration items="{!v.values2}" var="value" indexVar="index">
        <div data-index="{!index}" onchange="{!c.update2}">
            <lightning:input type="text" value="{!value}" label="Lightning Input" />
        </div>
    </aura:iteration>
    <hr />
    {!v.message2}
    <hr />
</aura:application>

({
    update1: function(component, event, helper) {
        component.set(
            "v.message1", 
            `HTML line ${1+parseInt(event.target.parentNode.dataset.index)} was updated to ${event.target.value}`
        );
    },
    update2: function(component, event, helper) {
        component.set(
            "v.message2", 
            `Lightning line ${1+parseInt(event.target.parentNode.dataset.index)} was updated to ${event.target.value}`
        );
    }
})

In update1, we can easily identify the value that was changed and the row which was updated. Is there a way comparable way to get the index of the row in update2 that's compatible with Locker Service, or otherwise identify which item in the list is being updated?


Edit: Ideally, if possible, we should be able to read an attribute, or otherwise be able to determine, specifically which row was updated without using an attribute that has a different purpose. For example, using name will cause type="radio" to work incorrectly.

5

I do not think class attribute should be used to store index. Instead we can use name in Lightning:input to do so.

<aura:iteration items="{!v.values2}" var="value" indexVar="index">
        <lightning:input type="text" name="{!index}" value="{!value}" label="Lightning Input" onchange="{!c.update3}"/>
    </aura:iteration>

In js

update3 : function(component,event){
        var index = event.getSource().get('v.name');
        var value = event.getSource().get('v.value');
    }
| improve this answer | |
  • I like this answer, but I realized that it won't work for lightning:input type="radio". I may end up using this solution for this specific case, but I'd like to see a solution that is agnostic of the data type being used. – sfdcfox Mar 26 '18 at 17:47
  • @sfdxfox. just wondering, why it's won't work for type="radio". Thanks – amidstCloud Mar 26 '18 at 21:29
  • 1
    @amidstCloud because for a group of radio buttons to work as radio we have to give same name. – Manjot Singh Mar 27 '18 at 4:01
4

This is similar to a previous post. Look at the answer by our team member JF Paradis which throws some light about why event.target is not returning what you expect it to return. This will be fixed in the Summer'18 release. Please bare with the workaround till such time.

In your case,

<aura:iteration items="{!v.values2}" var="value" indexVar="index">
    <div data-index="{!index}" onchange="{!c.update2}">
        <lightning:input type="text" value="{!value}" label="Lightning Input" />
    </div>
</aura:iteration>

after the fix, the following controller function will give you the desired result

update2: function(component, event, helper) {
    // event.target => <div>, the first accessible element in the event bubble path      
    component.set(
        "v.message2", 
        `Lightning line ${1+parseInt(event.target.dataset.index)} was updated to ${event.target.value}`
    );
}
| improve this answer | |
  • Yes, if event.target was correct (e.g. on my own namespace), then this would be ideal, since it was my first choice anyways. Is there someone that can look at this over on the Lightning team to make sure it will work like this? I'm going to put my answer in as a temporary workaround, but I want to be able to swap it out fairly easily. – sfdcfox Mar 28 '18 at 0:14
  • Ravi is on one of the lightning teams, he's pretty knowledgable on these issues and I have confidence he knows what release the fix will be in. – Kris Gray Mar 28 '18 at 2:30
  • @sfdcfox Confirmed. This has been fixed in Summer'18 release. That release is still not available for customers, so in the meantime you can use your workaround. – Ravi Jayaramappa Mar 28 '18 at 18:31
  • @RaviJayaramappa If the target is changing, does that mean that it's going to break everything we've written? – sfdcfox Mar 28 '18 at 18:41
  • @sfdcfox So lets consider the only usecase that will see the effect of the fix. That is, event.target when the event originates from a child component of another namespace(i.e. a dom element invisible to the current component). This use case does not work today, nobody should be using this and hence there should be no breaking change. It should only enable getting the target directly and not through workarounds. All other usecases will continue to work the same way. Also, the workaround you have, which uses event.getSource(), will continue to work. – Ravi Jayaramappa Mar 28 '18 at 23:24
2

I'm not quite sure if this is a perfect solution but it works. As we can't give dynamic Id but we can give dynamic class name. So I pass dynamic value in class attribute.

And then using the onchange event call the controller and get the index.

<aura:iteration items="{!v.values2}" var="value" indexVar="index">
    <div data-index="{!index}" >
        <lightning:input type="text" value="{!value}" class="{!index}" label="Lightning Input" onchange="{!c.update3}"/>
    </div>
</aura:iteration>

In class attribute I have passed the index variable and in onchange I'm calling a new method

update3: function(component, event, helper) {
        component.set(
            "v.message2", 
            'Lightning line '+event.getSource().get("v.class")+' was updated to '+event.getSource().get("v.value")
        );
    }

Using the event.getSource().get("v.class") and event.getSource().get("v.value") I can easily get the index and updated value. And this approach work in Locker service as well. A

| improve this answer | |
  • No meaning on doing this ` onchange="{!c.update2}"` on div element; – itzmukeshy7 Mar 26 '18 at 17:37
  • @itzmukeshy7 yeah we don't need that. Just commented the code. – Tushar Sharma Mar 26 '18 at 17:39
2

We can use the event source to identify which element was updated. This requires iterating over the elements to determine which element was updated.

Here's the revised version:

<aura:application extends="force:slds">
    <aura:attribute name="message2" type="String" />
    <aura:attribute name="values2" type="List" default="['','']" />
    <aura:iteration items="{!v.values2}" var="value" indexVar="index">
        <div>
            <lightning:input type="text"
                             aura:id="boxes"
                             class="{!index}" 
                             onchange="{!c.update2}"
                             value="{!value}" 
                             label="Lightning Input">
            </lightning:input>
        </div>
    </aura:iteration>
    <hr />
    {!v.message2}
    <hr />
</aura:application>

({
    update2: function(component, event, helper) {
        var inputs = component.find("boxes"),
            source = event.getSource(),
            index;
        if(!inputs) {
            inputs = [];
        }
        if(!inputs.length) {
            inputs = [inputs];
        }
        inputs.forEach((v,i)=>{if(v===source){index=i}});
        component.set(
            "v.message2", 
            `Lightning line ${1+index} was updated to ${event.getSource().get("v.value")}`
        );
    }
})

I'm not keen on this idea because it involves a loop just to find the appropriate item, but considering the alternatives, I feel like this might be the most appropriate method.

This question has also prompted me to post this idea to allow us to attach arbitrary data via a data attribute that could be added globally to aura:component. This would eliminate the need for hacky workarounds like the answers posted on this question as of the time of posting this answer.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.