You are here:
Configure Column Components in a Gift Entry Grid Template in Nonprofit
Set up a component column to use custom Lightning web components for display and edit behavior in your Gift Entry Grid template.
Required Editions
| REQUIRED EDITIONS |
|---|
| Available in: Lightning Experience |
Available in: Enterprise, Performance, Unlimited, and Developer Editions with Education Cloud Available in: Enterprise, Unlimited, and Developer Editions with Nonprofit Cloud |
| User Permissions Needed | |
|---|---|
| To configure column components: | FundraisingAccess permission set OR Education Cloud Full Access permission set |
- In Display Columns, for the column that you want to configure, click Edit column.
- Select Component as the column type.
- Enter a column label.
- From Cell Display Component, select a Lightning web component that represents your display component.
- From Cell Edit Component, select a Lightning web component that represents your edit component.
- From Field API Name, select a field to map to your Lightning web component.
- Save your changes.
Example: Use a Custom Component to Match and Populate Data
Create a custom component that overrides the Donor column from the default template with a barcode option. When a user enters a value in the edit component, the system looks for a matching value in a custom Bar Code object. If a match is found, values from that record populate other columns in the Gift Entry Grid.
| REQUIRED EDITIONS |
|---|
| Available in: Lightning Experience |
Available in: Enterprise, Performance, Unlimited, and Developer Editions with Education Cloud Available in: Enterprise, Unlimited, and Developer Editions with Nonprofit Cloud |
Cell Display Components
By default, the Designations column component shows a blank value. If one designation is associated with the Gift Entry row, the column component concatenates the amount to that designation and displays it in the grid, such as $100. If there are two or more designations, the grid shows a summarized value, such as 2 Designations.
Cell Display components have these requirements:
- A tagName value in the JavaScriptjs file.
- @api params that acts as inbound property passed from the grid.
In this sample, the display component shows a BarCode column. Before any data is entered, it shows as pending. When the system finds a matching value, the column updates to show the donor’s name.
giftEntryGridBarCodeDisplayColumn.html
<template>
<p>{columnDisplayValue}</p>
</template>
giftEntryGridBarCodeDisplayColumn.js
import { api, LightningElement } from 'lwc';
export default class GiftEntryGridBarCodeDisplayColumn extends LightningElement {
/**
* params is the only inbound property passed to this component from Gift Entry Grid
*/
@api params;
/**
* Example Method:
* This getter function displays various fields in a display component
* that is specified in the yaml file configuration.
* @returns {*|string}
*/
get columnDisplayValue() {
let response = this.params?.data?.properties?.barCodeValue;
if (this.params?.data?.LastName) {
response = this.params?.data?.FirstName + " " + this.params?.data?.LastName;
if (this.params?.data?.Salutation) {
response = this.params?.data?.Salutation + " " + response;
}
}
return response || '(pending)';
}
}
// Tagname is required for the component to function
GiftEntryGridBarCodeDisplayColumn.tagName = 'c-gift-entry-grid-bar-code-display-column';
Cell Edit Components
In the default template, the edit component for the Designations column has a Lightning-input field that functions similarly to a lookup field. When the user enters a value, the system looks for a matching value in a custom Bar Code object. If a match is found, values from that record populate other columns in the Gift Entry Grid.
Cell Edit components have these requirements:
- A
tagNameparameter in the JavaScript file. - An
isPopupparameter in the JavaScript file that's set to true. - Usage of a Gift Entry Grid Lightning Message Service channel from
@salesforce/messageChannel/lightning__giftEntryGridComponentAction. - Other methods and variables as called out in the following sample.
giftEntryGridBarCodeColumnComponent.html
<template>
<lightning-input
label="Bar Code"
name="barCode"
value={barCode}
onblur={handleBarCodeChanged}
onerror={handleError}
placeholder="Scan bar code here"
variant="label-hidden">
</lightning-input>
</template>
giftEntryGridBarCodeColumnComponent.js
import { LightningElement, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
/**
* Apex controller used by this example to handle data queries, etc.
*/
import getBarCodeScanData from '@salesforce/apex/GiftEntryBarCodeScanLookupController.getBarCodeScanData';
/**
* Lightning Message Service to enable this component to communicate back to Gift Entry Grid
*/
import { publish, createMessageContext } from 'lightning/messageService';
import GiftEntryGridComponentAction from "@salesforce/messageChannel/lightning__giftEntryGridComponentAction";
const messageContext = createMessageContext();
const DEFAULT_GIFT_AMOUNT = 100.00;
const DEFAULT_PAYMENT_METHOD = "Credit Card";
export default class GiftEntryGridBarCodeColumnComponent extends LightningElement {
/**
* Required for the underlying grid component to properly render this as a gift entry grid cell
*/
static delegatesFocus = true;
/**
* The data entry property used in this example
*/
@api barCode = "";
/**
* The column definition as defined within the underlying grid component
* Set from data in params in the connectedCallback.
*/
_columnDefinition;
/**
* Contains properties that represent each field on the GiftEntry record/row within the grid.
* Set from data in params in the connectedCallback.
*/
_rowData = {};
/**
* _params is the only inbound property passed to this component from Gift Entry Grid
* The properties retrieved in the example below are the primary ones required for grid functionality
*/
_params;
@api
get params() {
return this._params;
}
set params(value) {
this._params = value;
this._rowData = this._params?.data;
this._columnDefinition = this._params?.colDef;
const _headerLabel = this._columnDefinition?.headerName; // informational
// Useful mechanism to pull in the previously entered barcode as the default value
// Values in the `properties` object of the row are transient for the current session only. They persist until the page is closed/refreshed
if (this._rowData && this._rowData?.properties?.barCode) {
this.barCode = this._rowData?.properties?.barCode;
}
}
/**
* Recommended Method: Ensures the column width is set properly on load
*/
connectedCallback() {
this.template.host.style.setProperty('--min-width', this._params?.column?.actualWidth + 'px');
}
/**
* Example Method:
* Handle a change to the entered bar code in the field
* @param {*} event
*/
handleBarCodeChanged(event) {
event.stopPropagation();
try {
if (event.target?.value && event.target?.value !== "") {
this.sendDataBackToGrid(event.target.value);
} else if (event.target?.value === "") {
this.clearCurrentRowOfAllData();
}
} catch (e) {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error',
message: 'An error occurred while querying the entered barcode: ' + (e.body?.message || e.message),
variant: 'error'
})
);
}
this.applyFocus();
}
/**
* Example Method:
* Call the apex controller with the entered bar code
* Parse the response and pass that data back to GiftEntryGrid using the Lightning Message Service channel
* @param {*} barCode
*/
async sendDataBackToGrid(barCode) {
// Call the Apex controller to query and retrieve data
const result = await getBarCodeScanData({ barcodeValue: barCode });
if (result && result !== null) {
// Convert the query result from the apex controller into an object of GiftEntry fields to apply to the row in the grid
const giftEntryFields = this.buildResponse(result);
// Build the "message" obhect to send back to Gift Entry Grid following the documented contract
const message = {
action: "ColumnEdit", // Required property
componentName: this.tagName, // Required property (use as is)
colId: this._columnDefinition.colId, // Required property (use as is)
details: { // Optional property - include if there is data to write to the row in Gift Entry
rowId: this.params?.data?.id, // Required (use as is)
rowIndex: this.params?.data?.rowIndex, // Required (use as is)
giftEntryFields, // Required - all GiftEntry fields to write to rowData
rowProperties: { // Optional - use this to persist values in memory in the grid for use elsewhere, such as the Display Component
barCode, // Example: Used to persist the value of the barcode on the row temporarily to retrieve elsewhere
barCodeValue: result.donorName // Example: This is used by GiftEntryGridColumnDisplay
},
matchingGiftTransactionId: result?.matchingGiftTransactionId, // Optional, to set a matching gift on the row
matchingGiftTransactionName: result?.matchingGiftTransactionName // Optional, to set a matching gift on the row
}
}
publish(messageContext, GiftEntryGridComponentAction, message);
}
}
/**
* Example Method:
* Clear out the entire row if the barCode is set to null
*/
clearCurrentRowOfAllData() {
const giftEntryFields = {
DonorId: null,
Donor: null,
GiftType: "Individual"
};
if (this.params && this.params.data) {
const keepKeys = ['id', 'Id', 'rowId', 'rowIndex', 'properties', 'GiftType'];
Object.keys(this.params.data).forEach(key => {
if (!keepKeys.includes(key)) {
giftEntryFields[key] = null;
}
});
}
const message = {
action: "ColumnEdit", // Required property
componentName: this.tagName, // Required property (use as is)
colId: this._columnDefinition.colId, // Required property (use as is)
details: { // Optional property - include if there is data to write to the row in Gift Entry
rowId: this.params?.data?.id, // Required (use as is)
rowIndex: this.params?.data?.rowIndex, // Required (use as is)
giftEntryFields,
rowProperties: {
barCode : null,
barCodeValue: null
}
}
};
publish(messageContext, GiftEntryGridComponentAction, message);
}
/**
* Example Method:
*
* Notes
* - Lookup fields such as Campaign and OutreachSourceCode should be represented as an object with "id" and "name" to allow them
* render properly in the grid. This does not apply to DonorId
* - Pick List fields, such as PaymentMethod, can a string representing the pick list API Name or an object as {apiName: x, value: y}
* - To take advantage of Gift Entry Grid's built in Donor logic that retrieves all donor fields, including custom mapped fields, the
* response object should include a property for DonorId and GiftType ("Individual" or "Organizational" only)
* - Any field on GiftEntry can be included in this resonse and it will populate fields on the grid
* @param {*} result from call to Apex with query results
* @returns an object that represents fields on the GiftEntry object to be updated on the GiftEntryGrid row
*/
buildResponse(result) {
const response = {
GiftType: result.giftType, // Required to use the built-in donor logic
DonorId: result.donorid, // Required to use the built-in donor logic
// Examples(s) Populate other fields on the row
GiftReceivedDate: new Date().toISOString().split('T')[0],
PaymentMethod: DEFAULT_PAYMENT_METHOD,
GiftAmount: DEFAULT_GIFT_AMOUNT,
CampaignId: result.campaign,
Campaign: {
id: result.campaign,
Name: result.campaignName
},
OutreachSourceCodeId: result.outreachSourceCode,
OutreachSourceCode: {
id: result.outreachSourceCode,
Name: result.outreachSourceCodeName
}
};
return response;
}
/**
* For keyboard accessibility, allows the lookup component to be focused on keyboard tabbing
*/
applyFocus() {
const _gridElement = this.template.querySelector('lightning-input');
return new Promise((resolve) => {
Promise.resolve().then(() => {
_gridElement.focus();
resolve();
});
});
}
/**
* Example Method
*/
handleError(event) {
event.stopPropagation();
}
}
// Required Properties for Column Components:
GiftEntryGridBarCodeColumnComponent.tagName = 'c-gift-entry-grid-bar-code-column-component';
GiftEntryGridBarCodeColumnComponent.isPopup = true;
Query and Pass Back Data
Use this Apex code to query and pass back data to the Gift Entry Grid custom component.
GiftEntryGridBarCodeScannerLookupController.apex
/**
* Salesforce Fundraising - Gift Entry Grid Custom Component Example
*
* Controller class for handling barcode scan data lookup from a column component
*/
public with sharing class GiftEntryBarCodeScanLookupController {
/**
* Wrapper class to return the selected donor and any other info that would be applied to the grid row
*/
public class ReponseObject {
@AuraEnabled public String barCode { get; set; }
@AuraEnabled public String giftType { get; set; }
@AuraEnabled public String donorid { get; set; }
@AuraEnabled public String donorName { get; set; }
@AuraEnabled public String campaign { get; set; }
@AuraEnabled public String campaignName { get; set; }
@AuraEnabled public String giftDesignation { get; set; }
@AuraEnabled public String giftDesignationName { get; set; }
@AuraEnabled public String outreachSourceCode { get; set; }
@AuraEnabled public String outreachSourceCodeName { get; set; }
@AuraEnabled public String matchingGiftTransactionId { get; set; }
@AuraEnabled public String matchingGiftTransactionName { get; set; }
@AuraEnabled public Double amount { get; set; }
}
/**
* Retrieves barcode scan data based on the provided barcode value
* @param barcodeValue The barcode value to search for
* @return ReponseObject object with all fields or null if not found
*/
@AuraEnabled(cacheable=false)
public static ReponseObject getBarCodeScanData(String barcodeValue) {
try {
if (String.isBlank(barcodeValue)) {
return null;
}
// Query the BarCodeScanData__c object for the matching barcode
// Include related fields from Donor (Account), Campaign, and Outreach Source Code
List<BarCodeScanData__c> results = [
SELECT Id,
BarCode__c,
Donor__c,
Donor__r.Name,
Donor__r.IsPersonAccount,
Campaign__c,
Campaign__r.Name,
OutreachSourceCode__c,
OutreachSourceCode__r.Name
FROM BarCodeScanData__c
WHERE BarCode__c = :barcodeValue
LIMIT 1
];
if (results.isEmpty()) {
return null;
}
BarCodeScanData__c barCodeRecord = results[0];
ReponseObject response = new ReponseObject();
// a value for DonorId and GiftType required for the donor logic to work
response.donorid = barCodeRecord.Donor__c;
response.giftType = (barCodeRecord.Donor__r.IsPersonAccount ? 'Individual' : 'Organizational');
// Extra fields used within this example component
response.barCode = barCodeRecord.BarCode__c;
response.donorName = barCodeRecord.Donor__r?.Name;
response.campaign = barCodeRecord.Campaign__c;
response.campaignName = barCodeRecord.Campaign__r?.Name;
response.outreachSourceCode = barCodeRecord.OutreachSourceCode__c;
response.outreachSourceCodeName = barCodeRecord.OutreachSourceCode__r?.Name;
// Is there an unpaid gift transaction installment for a commitment for this donor?
// If so, populate the GiftTransactionId field to simulate selecting a matching gift
List<GiftTransaction> matchingGifts = [
SELECT Id, Name,
OriginalAmount, TransactionDueDate,
CampaignId, Campaign.Name,
OutreachSourceCodeId, OutreachSourceCode.Name,
GiftCommitmentId
FROM GiftTransaction
WHERE DonorId = :response.donorid
AND Status = 'Unpaid'
ORDER BY TransactionDueDate ASC
LIMIT 1
];
if (matchingGifts.size() > 0) {
response.matchingGiftTransactionId = matchingGifts[0].Id;
response.matchingGiftTransactionName = matchingGifts[0].Name;
response.amount = matchingGifts[0].OriginalAmount;
response.campaign = matchingGifts[0].CampaignId;
response.campaignName = matchingGifts[0].Campaign.Name;
response.outreachSourceCode = matchingGifts[0].OutreachSourceCodeId;
response.outreachSourceCodeName = matchingGifts[0].OutreachSourceCode.Name;
}
return response;
} catch (Exception e) {
// Log the error and throw a user-friendly exception
System.debug(LoggingLevel.ERROR, 'Error in getBarCodeScanData: ' + e.getMessage());
throw new AuraHandledException('Error retrieving barcode scan data: ' + e.getMessage());
}
}
}

