[SalesForce] Dynamic Lightning Input Component

I have a custom Lightning component which is a simple input form with the Contact fields(required). Obviously the controller class has an insert statement for the Contact sObject.

Is there a way I can create an input component that is not specific to any sObject, and have a Property setup(Design file in the Aura definition bundle) that says: <design:attribute name="objectAPIName" label="sObject"/> which I can assign any sObject I wanted, and the form should dynamically show the required fields specific to the provided sObject?

Controller to fetch all the fields(Dynamically):

@AUraEnabled
public static List<String> dynamicField(String ObjectName){

    String objName = ObjectName;
    Schema.SObjectType t = Schema.getGlobalDescribe().get(objName);
    SObject s = t.newSObject();

    DescribeSObjectResult describeResultPP = s.getSObjectType().getDescribe();
    List<String> fieldNamesPP = new List<String>( describeResultPP.fields.getMap().keySet() );

    System.debug('Field attributes: ' +fieldNamesPP);

    return fieldNamesPP;
}

How can I make use of these fields to populate on the input form? My concern is, I get all the fields of that specific sObject, how can I only display/show required fields to fill in? Also I am not even sure how I can display those on a Lightning.cmp file.

Best Answer

Let me share an overall architecture that I used to accomplish a very similar problem. The first thing I did was to create an apex class FieldDetail to model a field describe with @AuraEnabled get methods for all relevant information like label, data type, etc. Next, an inputField component was created to consume that information and conditionally render a specific component using $A.createComponent() depending on the data type. Note that we used both lightning namespaced components as well as some components from the strike project. Once we had an input that could render itself for a FieldDetail, it was simply a task of adding a top level controller that would retrieve a list of FieldDetail objects and iterating over them to render a c:inputField component for each field. In your case that controller may have a method signature similar to:

@auraEnabled
public static List<FieldDetail> getRequiredFieldDetails(String sObjectName)

You could then pass the sObjectName parameter from either a design attribute or using one of the built in interfaces like force:hasSObjectName and use object and field describes to build your list of FieldDetail objects.

== Code Sample Update ==

The following class represents the details for a specific field and can be returned from an @AuraEnabled method to be consumed in Lightning.

/**
 *  SObjectFieldDetail object provides information of an SObject Field for
 *  thge lightning context.
 */
public with sharing class SObjectFieldDetail {

  private SObject record { get; set; }

  private Schema.SObjectField field { get; set; }

  /**
   * Constructor
   */
  public SObjectFieldDetail(SObject record, Schema.SObjectField field) {
    this.record = record;
    this.field = field;
  }

  @auraEnabled
  /**
   * @returns    String value of the DisplayType of the field
   */
  public String getType() {
    Schema.DisplayType dataType = field.getDescribe().getType();
    return String.valueOf(dataType);
  }

  @auraEnabled
  /**
   * @returns    Label of the field
   */
  public String getLabel() {
    return field.getDescribe().getLabel();
  }

  @auraEnabled
  /**
   * @returns    Returns true if the field is html formatted. (Rich Text)
   *             URL Fields return false
   */
  public Boolean getIsHtmlFormatted() {
    return field.getDescribe().isHtmlFormatted();
  }

  @auraEnabled
  /**
   * @returns    True, if current user has read access.
   */
  public Boolean getHasReadAccess() {
    return field.getDescribe().isAccessible();
  }

  @auraEnabled
  /**
   * @returns    True, if current user has write access.
   */
  public Boolean getHasEditAccess() {
    return field.getDescribe().isCreateable();
  }

  @auraEnabled
  /**
   * @returns    Returns field name
   */
  public String getFieldName() {
    return field.getDescribe().getName();
  }

  @auraEnabled
  /**
   * @returns    Returns local field name
   */
  public String getFieldLocalName() {
    return field.getDescribe().getLocalName();
  }
  @auraEnabled
  /**
   * @returns    If the field type is picklist, returns List of the label of all picklist options.
   *             Else, returns empty list.
   */
  public List<String> getAllPicklistOptions() {
    List<String> picklistOptions = new List<String>();
    if(getType() == 'PICKLIST'){
      List<Schema.PicklistEntry> picklistValues = field.getDescribe().getPicklistValues();
      for(Schema.PicklistEntry picklistEntry : picklistValues){
        picklistOptions.add(picklistEntry.getLabel());
      }
    }
    return picklistOptions;
  }

  @auraEnabled
  /**
   * @returns    If the field type is picklist, returns List of the label of active picklist options.
   *             Else, returns empty list.
   */
  public List<String> getPicklistOptions() {
    List<String> picklistOptions = new List<String>();
    if(getType() == 'PICKLIST'){
      List<Schema.PicklistEntry> picklistValues = field.getDescribe().getPicklistValues();
      for(Schema.PicklistEntry picklistEntry : picklistValues){
        if(picklistEntry.isActive()){
          picklistOptions.add(picklistEntry.getLabel());
        }
      }
    }
    return picklistOptions;
  }

  @auraEnabled
  /**
   * @returns    If the field type is picklist, returns List of the label of active picklist options.
   *             Else, returns empty list.
   */
  public String getDefaultPicklistOption() {
    if(getType() == 'PICKLIST'){
      return String.valueOf(field.getDescribe().getDefaultValue());
    }
    return null;
  }

  @auraEnabled
  /**
   * @returns    Current value of the field
   */
  public Object getCurrentValue() {
    if(getHasReadAccess()){
      return record.get(field);
    }
    else {
      return null;
    }
  }

  @auraEnabled
  /**
   * @returns    String value of the DisplayType of the field
   */
  public String getSObjectName() {
    return record.getSObjectType().getDescribe().getName();
  }

  @auraEnabled
  /**
   * @returns    String value of the DisplayType of the field
   */
  public String getSObjectLocalName() {
    return record.getSObjectType().getDescribe().getLocalName();
  }

  @auraEnabled
  /**
   * @returns    Current max length of field
   */
  public Integer getFieldLength() {
    return field.getDescribe().getLength();
  }

  @auraEnabled
  /**
   * @returns    Current max length of field
   */
  public Integer getFieldPrecision() {
    return field.getDescribe().getPrecision();
  }

  @auraEnabled
  /**
   * @returns    Current max number of digits of field
   */
  public Integer getFieldDigits() {
    return field.getDescribe().getDigits();
  }

  @auraEnabled
  /**
   * @returns    Current max number of digits of field right of decimal
   */
  public Integer getFieldScale() {
    return field.getDescribe().getScale();
  }

  @auraEnabled
  /**
   * @returns    Current max number of digits of field right of decimal
   */
  public Decimal getStep() {
    Decimal step = 1;

    for(Integer i = 0; i < getFieldScale(); i++){
      step = step / 10;
    }
    return step;
  }

  @auraEnabled
  /**
   * @returns    Current max number of digits of field right of decimal
   */
  public Decimal getMax() {
    Integer count = getFieldPrecision() - getFieldScale();
    String str = '';

    for(Integer i = 0; i < count; i++){
      str = str + '9';
    }
    if(getFieldScale() > 0){
      str = str + '.';
      for(Integer i = 0; i < getFieldScale(); i++){
        str = str + '9';
      }
    }
    if(str.length() > 0){
      return Decimal.valueOf(str);
    }
    else {
      return 0;
    }
  }

You can then construct instances of this class by passing in a record (this could be a new record like new Contact() and a list of fields. If you wanted all required fields, you use code similar to

List<Schema.SObjectField> allContactFields = Schema.Contact.sObjectType.getDescribe().fields.getMap().values();
List<Schema.SObjectField> requiredContactFields = new List<Schema.SObjectField>();

for(Schema.SObjectField field : allContactFields) {
    if(field.getDescribe().isCreateable() && !field.getDescribe().isNillable() && !field.getDescribe().isDefaultedOnCreate()) {
        requiredContactFields.add(field);
    }
}