5

I've asked this question to change the objectApiName attribute of lightning:recordEditForm. Thanks to the great answer of @PranayJaiswal I found a workaround.

Now I soon will face the need of having to create recordEditForm components dynamically. The use case are very long lists of SObjects where I have to make rows editable.

I want to create the recordEditForm and the nested inputFields dynamically by $A.createComponents following a suggestion of @sfdcfox in the context of the original question but now intentionally like this:

makeEdit : function(cmp, evt, hlp) { 
    console.log('ParentId :: ', cmp.get("v.ParentId") ); 
    console.log('ParentType :: ', cmp.get("v.ParentType") ); 
    $A.createComponents([
            ["lightning:recordEditForm",{
                "recordId"          : cmp.get("v.ParentId"),
                "objectApiName"     : cmp.get("v.ParentType"),
            }],
            ["lightning:inputField",{
                "fieldName" : "Name"
            }]
        ]
        ,function(components, status, errorMessage){
            if (status === "SUCCESS") {
                var wrapper         = components[0];
                var inner           = components[1];
                wrapper.set("v.body", [inner]);
                cmp.set("v.body", [wrapper]);
            }
            else if (status === "INCOMPLETE") {
                console.log("No response from server or client is offline.")
                // Show offline error
            }
            else if (status === "ERROR") {
                console.log("Error: " + errorMessage);
                // Show error message
            }
        }
    );
},

Now I can invoke the controller code - but nothing - I repeat: n o t h i n g - gets rendered at all. What is my fault here? Note that the console.log gets executed and the output confirms that the attribute are holding good values.

Why this is not working?

See more code here.

6
  • You also need something like cmp.set("v.body", [wrapper])" for the success case so the lightning:recordEditForm component that contains the lightning:inputField is added.
    – Keith C
    Commented May 2, 2018 at 17:28
  • @KeithC now as you say so, I've added it - but still: nothing rendered. Updated the code above with your line.
    – Uwe Heim
    Commented May 2, 2018 at 17:31
  • Using wrapper.set("v.body", [inner]); might help as AFIK the body is always an array.
    – Keith C
    Commented May 2, 2018 at 17:32
  • @KeithC unfortunately not: updated, but still nothing rendered. See code above.
    – Uwe Heim
    Commented May 2, 2018 at 17:34
  • 1
    if I had premier support I would gladly report this bug to Salesforce and help them get it fixed. But I have not, so I can not... like always ;-)
    – Uwe Heim
    Commented May 2, 2018 at 17:44

3 Answers 3

5

I came up with a way to do this; you have to create the children first, and then the parent, similar to how the system would do it in mockup. Here's a demonstration:

<aura:application extends="force:slds">
    <aura:attribute name="recordEdit" type="Aura.Component[]" />
    <aura:handler name="init" value="{!this}" action="{!c.init}" />
    {!v.recordEdit}
</aura:application>

({
    init: function(component, event, helper) {
        // First, create children
        $A.createComponents([
            ["lightning:inputField", { fieldName: "Name"}],
            ["lightning:inputField", { fieldName: "Industry" }]
        ], (components, status, errors) => {
            // Then parent, specifying body of lightning:inputFields
            $A.createComponents(
            [["lightning:recordEditForm", { recordTypeId: "01250000000QNyF", objectApiName: "Account", body: components }]], 
                            (components, status, errors) => {
                                component.set("v.recordEdit", components);
                            });
        });
    }
})
1
  • very cool, will try that code soon!
    – Uwe Heim
    Commented May 2, 2018 at 17:46
2

There is another way to do this. Define a new custom component, c:recordEditWithInputField and just create it dynamically.

ComponentName: c:recordEditWithInputField

    <aura:component>
<aura:attribute name="recordId" type="String" default="" />
<aura:attribute name="Parent" type="SObject" default="{}" />
<aura:attribute name="ParentType" type="String" default="" />

<lightning:recordEditForm recordId="{!v.recordId}"  objectApiName="{!v.ParentType}">

 <lightning:inputField fieldName="Name" />
 </lightning:recordEditForm>

</aura:component>

And then create it dynamically as

makeEdit : function(cmp, evt, hlp) { 
    console.log('ParentId :: ', cmp.get("v.ParentId") ); 
    console.log('ParentType :: ', cmp.get("v.ParentType") ); 
    $A.createComponents([
            ["c:recordEditWithInputField",{
                "recordId"          : cmp.get("v.ParentId"),
                "objectApiName"     : cmp.get("v.ParentType"),
            }]
        ]
        ,function(components, status, errorMessage){
            if (status === "SUCCESS") {
                var newComponent  = components[0];
                cmp.set("v.body", [newComponent]);
            }
            else if (status === "INCOMPLETE") {
                console.log("No response from server or client is offline.")
                // Show offline error
            }
            else if (status === "ERROR") {
                console.log("Error: " + errorMessage);
                // Show error message
            }
        }
    );
},

If there is more than just inputfield, it makes sense to group them up as single component.

1
  • 1
    +1 for adding a new component often being a good way to go; seems rather like adding a new class in OO programming often being a good way to go.
    – Keith C
    Commented May 2, 2018 at 20:18
1

I was tasked with doing something similar to this, dynamically generating two siblings and a dynamic number of child elements. In my use case, I was dynamically creating generic HTML elements and injecting them into the a known div acting as a placeholder.

  • 1 iframe
  • 1 form
    • n input tags within the form

The html output rendered in a structure such as:

<div aura:id="iframewrapper">
    <iframe />
    <form>
        <input />
        <input />
        <input />
    </form>
</div>

The component controller js looks like this:

// each attribute of this data object will become an html input tag 
// in a dynamically created form tag
var data = {
    "record": "001xxxnnnxxxnnnxxx",
    "isActive": true,
    "action": "someAction"
};

var componentsToCreate = [
    ["aura:html", {
        "tag": "iframe",
        "HTMLAttributes": {
            "aura:id": "uc_iframe_id_" + data.record,
            "id": "uc_iframe_id_" + data.record,
            "name": "uc_iframe_name_" + data.record,
            "class": "universalContainers-frame",
            "data-created-by": data.record
        }
    }],
    ["aura:html", {
        "tag": "form",
        "HTMLAttributes": {
            "aura:id": "data_form_id_" + data.record,
            "id": "data_form_id_" + data.record,
            "name": "data_form_name_" + data.record,
            "target": "uc_iframe_name_" + data.record,
            "method": "post",
            "action": "https://api.example.com/endpoint/",
            "class": "slds-hide"
        }
    }]
];

for (name in data) {
    if (data.hasOwnProperty(name)) {
        if (!data[name]) { // if any attribute does not have a value, show the toast
            var toastEvent = $A.get("e.force:showToast");
            toastEvent.setParams({
                "type": "other",
                "key": "link",
                "mode": "sticky",
                "title": "Required Value Missing",
                "message": "A value is required for the attribute '" + name + "'."
            });
            toastEvent.fire();
            return;
        }

        var inputTag = ["aura:html", {
            "tag": "input",
            "HTMLAttributes": {
                "aura:id": "data_form_input_id_" + data.record + "_" + name,
                "id": "data_form_input_id_" + data.record + "_" + name,
                "name": name,
                "value": data[name].toString()
            }
        }];

        // add the component to the collection
        componentsToCreate.push(inputTag);
    }
}

$A.createComponents(componentsToCreate,
    function (components, status, errorMessage) {
        /*
        * Element  0 is an iframe
        * Element  1 is an html form tag with target = iframe.id
        * Elements 2-n are input tags to be placed within the form
        */

        // a div in the component with aura:id="iframewrapper"
        var container = component.find("iframewrapper"); 

        if (container.isValid()) {
            //  the body array of the div tag
            var body = container.get("v.body"); 

            for (var i = 0; i < components.length; i++) {

                // elements in the array > 1 are input tags
                // make them children of the form tag
                if (i > 1) {

                    // get the body of the html form tag
                    var formBody = components[1].get("v.body"); 

                    // add the input tag to the form's body array
                    formBody.push(components[i]);

                    // update the body of the form tag 
                    components[1].set("v.body", formBody);

                } else {

                    // put the iframe and form tags in the body of the div
                    body.push(components[i]);
                }
            }

            // push the new html tags into the div's body
            container.set("v.body", body); 
        }
    }
);

You must log in to answer this question.

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