0

I am dynamically creating multiple Lightning components in my Community. They are not displaying in the correct order until the page is refreshed. Thoughts? Here is what I want my page to look like - which it does after I refresh the screen... enter image description here

Here is what it looks like before I refresh. Notice the difference in order of the fields. Fields of the same type seem to be grouped together until I hit refresh and then they appear in the order I put them in on the field set. I will share the code. In general, I am looping through a field set and displaying the fields as components on a form.
enter image description here

Here is the code:

if(fieldType == 'PICKLIST'){

            var getPLValue = component.get("c.getPickListValues");
            getPLValue.setParams({"fieldAPI" : fieldAPIName});
            getPLValue.setCallback(this, function(response){
                component.set("v.ResponsePL",response.getReturnValue());
                $A.createComponent(
                    "c:NIIMBL_Dynamic_Picklist", 
                    { 
                        "options": response.getReturnValue(), 
                        "default" : "Yes",
                        "name": fieldLabel,
                        "label": fieldLabel,
                        "required" : false,
                        "value" : ""
                    },
                    function(newPicklist) {
                        var body = component.get("v.formBody");
                        body.push(newPicklist);
                        component.set("v.formBody",body);
                    });
            });
            $A.enqueueAction(getPLValue);
    }

        else if(fieldType == 'EMAIL'){
                $A.createComponent(
                    "lightning:input",
                    {
                        "value" : component.getReference("v.EventRSVP."+ fieldAPIName),
                        "aura:id": "findableAuraId",
                        "label": fieldLabel,
                        "required" : true,
                        "type" : 'Email'
                    },
                    function(newInput, status, errorMessage){
                        if (status === "SUCCESS") {
                            var body = component.get("v.formBody");
                            body.push(newInput);
                            component.set("v.formBody",body);
                        }
                        else if (status === "INCOMPLETE") {
                            console.log("No response from server or client is offline.")
                        }
                            else if (status === "ERROR") {
                                console.log("Error: " + errorMessage);
                            }
            });
        }            


            else if(fieldType=="STRING")
            {

                    $A.createComponent(
                        "lightning:input",
                        {
                            "value" : component.getReference("v.EventRSVP."+ fieldAPIName),
                            "aura:id": "findableAuraId",
                            "label": fieldLabel,
                            "required" : true
                        },
                        function(newInput, status, errorMessage){
                            if (status === "SUCCESS") {
                                var body = component.get("v.formBody");
                                body.push(newInput);
                                component.set("v.formBody",body);
                            }
                            else if (status === "INCOMPLETE") {
                                console.log("No response from server or client is offline.")
                            }
                                else if (status === "ERROR") {
                                    console.log("Error: " + errorMessage);
                                }
                        });
  • 1
    can you include the relevant code please? =) – glls Mar 19 '19 at 16:21
0

You've basically got a synchronization problem. For each field (I'm assuming the code that you shared is the body of a loop), you do something like this:

getPLValue.setCallback(this, function(response){
    component.set("v.ResponsePL",response.getReturnValue());
    $A.createComponent(
        "c:NIIMBL_Dynamic_Picklist", 
        { 
            // snipped
        },
        function(newPicklist) {
            var body = component.get("v.formBody");
            body.push(newPicklist);
            component.set("v.formBody",body); // This is the critical line
        });
});

That's a callback inside of a callback - first you call to the server to get picklist options, and then you call to create the component. Only inside the "deepest" callback do you add the created component to the body facet of your form. For types other than picklsits, you only have one callback layer, but the fundamental structure that gets created is the same: for N fields, you have at least N callbacks enqueued.

When you enqueue callbacks, they're not going on a FIFO queue per se. The callbacks may take different durations to execute (particularly those which require a server call); you don't have a guarantee of any kind that they'll execute in the order that you enqueue them.

I think what's ultimately happening (although I couldn't swear to this) is that when you reload the page you're getting cached data, obviating the synchronization problem.

One way to approach this is to use Promises. I'm not going to write it out in full here (we don't have the full breadth of your code, and it'd take quite a bit of testing in any case), but I think one way to approach this would be to wrap each server call in a Promise and use the Promise.all() method to run a single callback after they all complete, accepting the results of the collected asynchronous methods in order and adding them to the form only once they all complete successfully.

A rough sketch could look like this, using the Email field type as an example. I'm eliding the double-layered callback of the Picklist type for simplicity of illustration: you could solve that issue by iterating over your fields twice and first making all the server calls needed for your picklists, then making all of your component-creation calls.

let promises = [];
for (...) {
    if (fieldType === 'STRING') { 
         // etc...
    } else if (fieldType == 'EMAIL'){
        promises.push(new Promise(function (resolve, reject) {
            $A.createComponent(
                "lightning:input",
                { /* parameters */ },
                function(newInput, status, errorMessage){
                    if (status === "SUCCESS") {
                        resolve(newInput);

                    } else {
                        reject(status); // FIXME: you'll want to be more sophisticated here.
                    }
                }
            );
        });
    } else if (fieldType == 'PICKLIST') {
       // etc...
    }
}

// Now, we kick off all of the Promises, and run a single callback when they return.
Promise.all(promises).then(function(componentList) {
    let body = component.get("v.formBody");                
    componentList.forEach(function(ea) {
        body.push(ea);
    });
    component.set("v.formBody",body);
}).catch(function(errorList) {
    // handle errors here...
});

I've used this approach successfully when needing to synchronize the final action taken that depends on a number of separate server calls or other asynchrony.

  • Thank you...I am going to look into Promises. – Michele Kleinhomer Mar 20 '19 at 0:40

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.