Requirement:
Need to display a Question & Answer form based upon input received from 3rd party web service i.e. web service response will tell us
- What Question text to be displayed
- Answer type might be of Single/Multiple Choice/Free Text based (to keep it simple, I have not included other types like Date, Numeric etc)
- Some of questions are not mandatory to be answered
End of the implementation we can see slimier UI as displayed below:
Sample response (JSON) from 3rd party Service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | [
{
"questionId": "Q0001",
"question": "How many tickets do you have?",
"required": true,
"questionDisplayType": "PICKLIST",
"answerChoices": ["1","2","3"]
},
{
"questionId": "Q00002",
"question": "What is your real name?",
"required": false,
"readonly": false,
"questionDisplayType": "TEXT"
},
{
"questionId": "Q00003",
"question": "What is your favorite color?",
"required": true,
"readonly": false,
"questionDisplayType": "RADIOGROUP",
"answerChoices": ["Red","Blue","Yellow","Green"]
},
{
"questionId": "Q00004",
"question": "Select countries you've visited",
"required": true,
"readonly": false,
"questionDisplayType": "CHECKBOXGROUP",
"answerChoices": ["India","USA","UK"]
}
]
|
Solution Approach:
Will create a reusable component (CommonInputComponent.cmp) which will render/generate required input components based upon display type provided from parent (Q_A_Form.cmp) component. And finally will capture Answers from parent component itself.
Note: Also given code repository link at the button of this post.
Now its code time:
Code is very self -explanatory but I have explained using component level comments as required.
Child Component: CommonInputComponent.cmp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | <!-- @File Name : CommonInputComponent.cmp @Description : Input type is determined based on the display type provided @Author : Avijit Gorai @Group : @Last Modified By : Avijit Gorai @Last Modified On : 11/3/2020, 1:12:47 am @Modification Log : Ver Date Author Modification 1.0 11/3/2020 Avijit Gorai Initial Version --> <aura:component implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,forceCommunity:availableForAllPageTypes" access="global"> <!-- Attributes --> <aura:attribute name="questionId" type="String" required="true" /> <aura:attribute name="questionName" type="String" required="true" /> <aura:attribute name="questionDisplayType" type="String" required="true" /> <aura:attribute name="picklistOptions" type="Object[]" /> <aura:attribute name="required" type="Boolean" default="false" /> <aura:attribute name="disabled" type="Boolean" default="false" /> <aura:attribute name="readonly" type="Boolean" default="false" /> <aura:attribute name="fieldMetadata" type="Object" access="private" /> <aura:attribute name="fieldValue" type="Object" access="public" /> <aura:attribute name="fieldValueChb" type="List" access="public" /> <aura:attribute name="answerChoices" type="List" default="[]" /> <!-- aura method, getting called from parent component to check input validaty and show message accordingly --> <aura:method name="checkReportValidity" action="{!c.showReportValidity}" access="public"></aura:method> <!-- Handlers --> <aura:handler name="init" value="{!this}" action="{!c.doInit}" /> <!-- TEXT --> <aura:if isTrue="{!v.questionDisplayType == 'TEXT'}"> <lightning:input aura:id="inputField" value="{!v.fieldValue}" label="{!v.questionName}" onchange="{!c.handleFieldValueChanged}" maxlength="{!v.fieldMetadata.maxLength}" required="{!v.required}" disabled="{!v.readonly}" /> </aura:if> <!-- PICKLIST --> <aura:if isTrue="{!v.questionDisplayType == 'PICKLIST'}"> <lightning:select aura:id="inputField" label="{!v.questionName}" value="{!v.fieldValue}" required="{!v.required}" disabled="{!v.readonly}" onchange="{!c.handleFieldValueChanged}"> <aura:iteration items="{!v.picklistOptions}" var="picklistOption"> <option text="{!picklistOption.label}" value="{!picklistOption.value}" /> </aura:iteration> </lightning:select> </aura:if> <!-- RADIOGROUP --> <aura:if isTrue="{!v.questionDisplayType == 'RADIOGROUP'}"> <lightning:radioGroup aura:id="inputField" label="{!v.questionName}" options="{!v.picklistOptions}" value="{!v.fieldValue}" type="radio" onchange="{!c.handleFieldValueChanged}" required="{!v.required}" disabled="{!v.readonly}" class="customRadioCls" /> </aura:if> <!-- CHECKBOXGROUP --> <aura:if isTrue="{!v.questionDisplayType == 'CHECKBOXGROUP'}"> <lightning:checkboxGroup aura:id="inputField" label="{!v.questionName}" options="{!v.picklistOptions}" value="{!v.fieldValueChb}" onchange="{!c.handleFieldValueChanged}" required="{!v.required}" disabled="{!v.readonly}" /> </aura:if> </aura:component> |
CommonInputComponentController.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ({
//CommonInputComponentController.js
doInit: function (component, event, helper) {
helper.setFieldMetadata(component, event);
},
handleFieldValueChanged: function (component, event, helper) {
helper.handleFieldValueChanged(component, event);
},
//Shows the help message if the form control is in an invalid state.
showReportValidity: function (component, event, helper) {
component.find("inputField").showHelpMessageIfInvalid();
}
});
|
CommonInputComponentHelper.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | ({
//CommonInputComponentHelper.js
//setting metadata (options to select)
setFieldMetadata: function (component, event) {
var fieldMetadata = new Object();
fieldMetadata.questionDisplayType = component.get('v.questionDisplayType');
if (fieldMetadata.questionDisplayType === 'TEXT') {
fieldMetadata.maxLength = 180;
}
if (fieldMetadata.questionDisplayType === 'PICKLIST' ||
fieldMetadata.questionDisplayType === 'RADIOGROUP' ||
fieldMetadata.questionDisplayType === 'CHECKBOXGROUP') {
//console.log('answerChoices -> ' + component.get('v.answerChoices'));
var answerChoices = component.get('v.answerChoices');
if (answerChoices) {
console.log(answerChoices);
var result = [];
if (fieldMetadata.questionDisplayType !== 'RADIOGROUP' && fieldMetadata.questionDisplayType !== 'CHECKBOXGROUP') {
result.push({label: '', value: ''}); //inserting blank option
}
for (var i in answerChoices) {
result.push({
label: answerChoices[i],
value: answerChoices[i]
});
}
fieldMetadata.picklistOptions = result;
fieldMetadata.picklistOptions.sort((a, b) => (a.value > b.value) ? 1 : -1); //sorting
component.set('v.picklistOptions', fieldMetadata.picklistOptions);
}
}
component.set('v.fieldMetadata', fieldMetadata);
},
//onchange, assigning changed value to attribute 'fieldValue'.
//This is helpful to get/access value of the input component from parent component
handleFieldValueChanged: function (component, event) {
var newFieldValue = event.getParam('value') !== undefined ? event.getParam('value') : event.getSource().get('v.value');
component.set('v.fieldValue', newFieldValue);
}
})
|
Parent Component: Q_A_Form.cmp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <!-- @File Name : Q_A_Form.cmp @Description : @Author : Avijit Gorai @Group : @Last Modified By : Avijit Gorai @Last Modified On : 13/3/2020, 12:28:51 am @Modification Log : Ver Date Author Modification 1.0 11/3/2020 Avijit Gorai Initial Version --> <aura:component implements="forceCommunity:availableForAllPageTypes" access="global"> <aura:attribute name="questionAnswerMap" type="List" default="[]" /> <aura:handler name="init" value="{!this}" action="{!c.doInit}" /> <div class="slds-p-around_x-large"> <!--Rendering CommonInputComponent based upon data received, each of component having aura id = 'fieldId' --> <aura:iteration items="{!v.questionAnswerMap}" var="fieldValue"> <c:CommonInputComponent aura:id="fieldId" questionId="{!fieldValue.questionId}" questionName="{!fieldValue.question}" required="{!fieldValue.required}" readonly="{!fieldValue.readonly}" questionDisplayType="{!fieldValue.questionDisplayType}" answerChoices="{!fieldValue.answerChoices}" /> </aura:iteration> <br /><br /> <lightning:button onclick="{!c.submit}" label="Submit" iconName="utility:save" iconPosition="left" variant="brand" /> </div> </aura:component> |
Q_A_FormController.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | ({
//Q_A_FormController.js
doInit: function (component, event, helper) {
//data received from web service (JSON) - to keep this demo simple, I have omitted Webservice call
//this is the JSON equivalent data in Object format
var questions = [];
var qaData = new Object();
qaData.questionId = "Q0001";
qaData.question = "How many tickets do you have?";
qaData.required = true;
qaData.questionDisplayType = "PICKLIST";
qaData.answerChoices = ["1", "2", "3"];
questions.push(qaData);
qaData = new Object();
qaData.questionId = "Q00002";
qaData.question = "What is your real name?";
qaData.required = false;
qaData.readonly = false;
qaData.questionDisplayType = "TEXT";
questions.push(qaData);
qaData = new Object();
qaData.questionId = "Q00003";
qaData.question = "What is your favorite color?";
qaData.required = true;
qaData.readonly = false;
qaData.questionDisplayType = "RADIOGROUP";
qaData.answerChoices = ["Red", "Blue", "Yellow", "Green"];
questions.push(qaData);
qaData = new Object();
qaData.questionId = "Q00004";
qaData.question = "Select countries you've visited";
qaData.required = true;
qaData.readonly = false;
qaData.questionDisplayType = "CHECKBOXGROUP";
qaData.answerChoices = ["India", "USA", "UK"];
questions.push(qaData);
console.log(questions);
console.log(JSON.stringify(questions));
component.set("v.questionAnswerMap", questions);
},
submit: function (component, event, helper) {
var requiredMissing = false;
//finding list of rendered component based upon aura id = fieldId. This will give us a array of component
const cmps = component.find("fieldId");
if (!cmps) return;
//looping through to check if current component's value is required but input value has not been provided
//then calling checkReportValidity aura method to check its input validaty and show message accordingly
cmps.forEach(function (cmp) {
let selectedVal = cmp.get("v.fieldValue");
console.log(cmp.get("v.questionId") + " -- " + selectedVal);
if (cmp.get("v.required") && (!selectedVal || selectedVal.length == 0)) {
requiredMissing = true;
cmp.checkReportValidity();
console.log("Required field is missing for " + cmp.get("v.questionId"));
}
});
if (requiredMissing) {
console.log("requireFieldMissing");
} else {
//all fine then collecting input value from each of components and added to a Map for further use as per business need
let answersMap = new Map();
cmps.forEach(function (cmp) {
let fieldValue = cmp.get("v.fieldValue");
answersMap.set(cmp.get("v.questionId"), fieldValue === undefined ? "" : fieldValue);
});
console.log("answersMap --> ", answersMap);
let successMapStr = JSON.stringify(Object.fromEntries(answersMap.entries()));
helper.showTosteMessage(
component,
"",
"success",
successMapStr,
"dismissible"
);
}
}
});
|
Q_A_FormHelper.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ({
//Q_A_FormHelper.js
showTosteMessage: function(component, title, type, message, mode) {
var toastEvent = $A.get("e.force:showToast");
if (toastEvent) {
toastEvent.setParams({
title: title,
type: type,
message: message,
mode: mode
});
toastEvent.fire();
}
// if not running in LEX or SF1, toast is not available - use alert
else {
alert(title + ": " + message);
}
}
});
|
Code has been kept @ https://github.com/AvijitGoraiGitHub/dynamicUILightning
