0

I use document.getElementById with the id attribute to locate elements in my component. I cannot use component.find and aura:id because the elements are generated from an iteration.

This iteration is over a list of records that I get from the server.

PROBLEM

document.getElementById cannot find the elements because they are not rendered yet - they will be rendered only after the server returns the records.

CURRENT SOLUTION

I have put the document.getElementById inside the aura:handler name="change" event, and also then I use a waiting mechanism to check if the element exist:

MARKUP

<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

<aura:handler name="change" value="{!v.records}" action="{!c.recordsFinishedLoading}"/>
<aura:attribute name="maxSelectionsTry" type="Integer" default="5" />

<aura:attribute name="records" type="sObject[]" description="list of records" />

<nav class="slds-context-bar__secondary" role="navigation">
    <ul class="slds-grid">  
        <li class="slds-context-bar__item">
            <a href="javascript:void(0);" class="slds-context-bar__label-action" title="labelAccount}" onclick="{!c.openAccount}">
                <span class="slds-truncate" title="labelAccount">labelAccount</span>
            </a>
        </li>
        <li class="slds-context-bar__item slds-context-bar__dropdown-trigger slds-dropdown-trigger slds-dropdown-trigger_hover">
            <a href="javascript:void(0);" class="slds-context-bar__label-action" title="labelRecords}" onclick="{!c.openRecords}">
                <span class="slds-truncate" title="labelRecords">labelRecords</span>
            </a>
            <div class="slds-context-bar__icon-action slds-p-left_none">
                <lightning:icon iconName="utility:chevrondown" size="x-small" alternativeText="Open Submenu"/>
            </div>
            <div class="slds-dropdown">
                <ul class="slds-dropdown__list" role="menu">

                    <aura:if isTrue="{! and(v.records, v.records.length>0)}">
                        <!--iterate on all records-->
                        <aura:iteration items="{!v.records}" var="record">
                            <li class="slds-dropdown__item" role="presentation">
                                <a href="javascript:void(0);" role="menuitem" tabindex="-1" id="{!record.Id}" onclick="{!c.openRecordFromMenu}">
                                    <span class="slds-truncate" title="{!record.Name}">{!record.Name}</span>
                                </a>
                            </li>
                        </aura:iteration>
                    </aura:if>
                </ul>
            </div>
        </li>
    </ul>
</nav>

CONTROLLER

recordsFinishedLoading: function(component, event, helper) {
    var recordId = "someHardCodedID";
    helper.findId(component, helper, recordId)
}

HELPER

findId: function(component, helper, recordId) {
    var itemById = document.getElementById(recordId);
    if (itemById) {
        // do some css stuff on this item
    }
    else {
        // element does not exist yet - so sleep and retry (for maxSelectionsTry times)
        var maxSelectionsTry = component.get("v.maxSelectionsTry");
        if (maxSelectionsTry > 0){
            component.set("v.maxSelectionsTry", maxSelectionsTry-1);
            setTimeout(function() {
                helper.findId(component, helper, grantId);
            }, 1000 /*1second*/);
        }
    }
},

I have tried to use document.getElementById from aura:handler name="change" but it returns null.

I have tried to use document.getElementById from the custom afterRender but it returns null.

QUESTION

Is there any other way to wait for the page to load and then document.getElementById would work?

Is there a nicer way to overcome this problem?

5
  • Your premise is incorrect. You can find elements created by aura:iteration. You're just trying to get them too early in the life cycle. Also, your component code looks like it basically same straight from SLDS. You need to modify it appropriately or it won't work.
    – sfdcfox
    Commented Aug 7, 2018 at 16:31
  • @sfdcfox my component works. I have added here only a portion of it . How can I get the elements later on the life cycle? I have tried with afterRender and it did not work Commented Aug 7, 2018 at 16:36
  • There's no convenient way to determine when they'll be available. I still think you're approaching this from the wrong direction. It'd be a lot easier to just bind whatever it is you're doing into part of the data structure.
    – sfdcfox
    Commented Aug 7, 2018 at 16:52
  • @sfdcfox - as you have suggested - I have added the css selection to the returned server records (added a 'className' non-existing field in my JavaScript) and the whole process became much more simple - no need for document.getElementById anymore. Thanks. My question now is - what to do with this StackExchange question? Commented Aug 8, 2018 at 11:26
  • I'd recommend that you answer it, so people can learn from your experience.
    – sfdcfox
    Commented Aug 8, 2018 at 17:04

1 Answer 1

1

Quick answer: There is no way to decide when the elements are rendered already, and then to query and use document.getElementById - the way I posted in the question - to sleep in a loop, is an ugly, but working, way to do that.

I had a solution to my problem by the help of @sfdcfox, and what I did is - adding the css name to the records after they returned from server, while in the markup I am binding the className to it:

MARKUP CHANGES:

<aura:iteration items="{!v.records}" var="record">
    <li class="slds-dropdown__item" role="presentation">
        <a href="javascript:void(0);" role="menuitem" tabindex="-1" class="{!record.className}" id="{!record.Id}" onclick="{!c.openRecordFromMenu}">
            <span class="slds-truncate" title="{!record.Name}">{!record.Name}</span>
        </a>
    </li>
</aura:iteration>

CONTROLLER CHANGES IN THE METHOD

getRecords: function(component, event, helper) {
    helper.getRecords(component, helper);
},

HELPER CHANGES IN THE METHOD - mainly adding the addSelectedRecordClass method after data was received:

getRecords: function(component, helper){
    // get records from server - a list of sObjects
    var action = component.get("c.getRecords");
    action.setParams({
        sObjectName: 'Contact',
        commaDelimitedFieldNames: 'Id,Name'
    });
    action.setCallback(this, function(response){
        var state = response.getState();
        if (component.isValid() && state === "SUCCESS") {
            var records = response.getReturnValue();
            records = helper.addSelectedRecordClass(component, records);
            component.set("v.records", records);
        }
    });
    $A.enqueueAction(action);
},

// find the selected record in the given record list, and add a css class to highlight it
addSelectedRecordClass: function(component, records) {
    var selectedRecordId = component.get("v.selectedRecord");
    for(var i=0; i<records.length; i++) {
        if (records[i].Id == selectedRecordId)
            records[i].className = 'selectedMenuItem';
        else
            records[i].className = '';
    }
    return records;
},

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .