In many audit-related Salesforce applications, users often need to maintain a dynamic list of questions β especially in sectors like manufacturing where compliance and quality assurance require periodic updates. In this blog post, weβll walk through building a Lightning Web Component (LWC) that allows users to add, edit, and delete questions and store them in a custom field as JSON.
π§© Use Case
We want to allow auditors to manage a list of questions (add, edit, delete) directly from a record page, and save them in a field (QuestionSet_c) of a custom object (Auditor_Question_c).
π¦ Salesforce Schema Setup
Object: Auditor_Question__c
Fields Used:
Name (Standard)
QuestionSet__c (Long Text Area / Rich Text β stores JSON array of questions)
π§ Key Functionalities
- Fetch record data using
@wire(getRecord)
- Display a list of questions (parsed from JSON)
- Add new questions
- Edit existing questions
- Delete unwanted questions
- Update
QuestionSet__c
field with modified JSON usingupdateRecord
β
AuditorQuestionController.cls
public with sharing class AuditorQuestionController {
@AuraEnabled(cacheable=true)
public static Auditor_Question__c findRecordByName(String name) {
// Validate input
if (String.isBlank(name)) {
return null;
}
// Query the record by Name
List<Auditor_Question__c> results = [
SELECT Id, Name, QuestionSet__c
FROM Auditor_Question__c
WHERE Name = :name
LIMIT 1
];
// Return the first result or null if not found
return results.isEmpty() ? null : results[0];
}
}
π Notes:
The @AuraEnabled(cacheable=true)
annotation makes it usable in LWC wire adapters for better performance (read-only).
with sharing
ensures field-level security and record-level access are respected.
You can add fields like QuestionSet__c
as required for your use case.
You can also expand this controller with additional methods like:
updateQuestions(String recordId, String questionsJson)
createNewAuditorQuestion(...)
π» LWC Template (HTML)
<template>
<lightning-card title="Add New Question New">
<div class="slds-p-around_medium">
<p><strong>Record Name:</strong> {recordName}</p>
<lightning-input
label="Add New Question"
value={inputValue}
onchange={handleInputChange}>
</lightning-input>
<lightning-button
label="Add Question"
class="slds-m-top_medium"
onclick={handleAdd}>
</lightning-button>
<template if:true={questionArray}>
<template for:each={questionArray} for:item="q">
<div key={q.id} class="slds-box slds-m-top_small">
<template if:false={isEdit}>
<p>{q.Question}</p>
</template>
<template if:true={isEdit}>
<lightning-input
value={q.Question}
data-id={q.id}
onchange={handleChangeUpdateQuestion}
label="Edit Question">
</lightning-input>
</template>
<template if:false={isEdit}>
<lightning-button label="Edit" onclick={handleEdit}></lightning-button>
</template>
<template if:true={isEdit}>
<lightning-button label="Update" onclick={handleUpdate}></lightning-button>
<lightning-button label="Cancel" onclick={handleEdit}></lightning-button>
</template>
<template if:false={isEdit}>
<lightning-button
variant="destructive"
label="Delete"
data-id={q.id}
onclick={handleDelete}>
</lightning-button>
</template>
</div>
</template>
</template>
</div>
</lightning-card>
</template>
π§ LWC JavaScript (JS)
import { LightningElement, api, track, wire } from 'lwc';
import { getRecord, updateRecord } from 'lightning/uiRecordApi';
import QUESTION_SET_FIELD from '@salesforce/schema/Auditor_Question__c.QuestionSet__c';
import NAME_FIELD from '@salesforce/schema/Auditor_Question__c.Name';
const FIELDS = [NAME_FIELD, QUESTION_SET_FIELD];
export default class AddMoreQuestionsNew extends LightningElement {
@api recordId;
@track recordName = '';
@track inputValue = '';
@track questionArray = [];
@track isEdit = false;
@wire(getRecord, { recordId: '$recordId', fields: FIELDS })
wiredRecord({ error, data }) {
if (data) {
this.recordName = data.fields.Name.value;
try {
const decoded = data.fields.QuestionSet__c.value.replace(/"/g, '"');
this.questionArray = JSON.parse(decoded);
} catch (e) {
console.error("Failed to parse JSON:", e);
}
} else if (error) {
console.error('Error fetching record:', error);
}
}
handleInputChange(event) {
this.inputValue = event.target.value;
}
handleAdd() {
const trimmed = this.inputValue.trim();
if (trimmed) {
const newQuestion = {
id: Date.now(),
Question: this.inputValue
};
this.questionArray = [...this.questionArray, newQuestion];
this.inputValue = '';
this.updateQuestionSet(JSON.stringify(this.questionArray));
}
}
handleEdit() {
this.isEdit = !this.isEdit;
}
handleChangeUpdateQuestion(event) {
const id = parseInt(event.target.dataset.id, 10);
const updatedValue = event.target.value;
this.questionArray = this.questionArray.map(item =>
item.id === id ? { ...item, Question: updatedValue } : item
);
}
handleUpdate() {
this.updateQuestionSet(JSON.stringify(this.questionArray));
this.handleEdit();
}
handleDelete(event) {
const idToDelete = parseInt(event.target.dataset.id, 10);
this.questionArray = this.questionArray.filter(q => q.id !== idToDelete);
this.updateQuestionSet(JSON.stringify(this.questionArray));
}
updateQuestionSet(questionJsonString) {
const fields = {
Id: this.recordId,
[QUESTION_SET_FIELD.fieldApiName]: questionJsonString
};
updateRecord({ fields })
.then(() => {
// Success message or toast can be shown here
})
.catch(error => {
console.error('Update error:', error);
alert('Failed to update: ' + (error.body?.message || error.message));
});
}
}
β
AddMoreQuestionsNew.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>63.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
Top comments (0)