Skip to content

Instantly share code, notes, and snippets.

@jagmohansingh
Last active October 31, 2022 06:23
Show Gist options
  • Save jagmohansingh/78510b97f36c67f52fe5a8f8b16b98ec to your computer and use it in GitHub Desktop.
Save jagmohansingh/78510b97f36c67f52fe5a8f8b16b98ec to your computer and use it in GitHub Desktop.
Server-side pagination using Apex on large datasets
public class LoadRecordsCtrl {
@RemoteAction
public static Map<String, Object> fetchRecords(String sObjectName, String lastKnownId, Integer pageSize, Integer direction) {
Map<String, Object> result = new Map<String, Object>();
try {
SObject sObj = (SObject) Type.forName(sObjectName).newInstance();
Integer totalRecords = SObjectQueryService.getTotalCount(sObjectName);
Integer totalPages = (Integer) Math.ceil((Decimal) totalRecords / pageSize);
Integer maxResults = pageSize;
if (String.isBlank(lastKnownId) && direction == -1) {
// when querying the last page, get the number of records on last page
maxResults = SObjectQueryService.getLastPageRecordCount(totalRecords, pageSize);
}
List<SObject> records = SObjectQueryService.queryRecords(sObjectName, lastKnownId, maxResults, direction);
// remove extra record and set next / previous state
result.putAll(SObjectQueryService.getPageState(records, lastKnownId, totalPages, pageSize, direction));
// sort and set records
if (direction == -1) records.sort();
result.put('totalRecords', totalRecords);
result.put('records', records);
result.put('status', true);
} catch (Exception ex) {
result.put('status', false);
result.put('error', ex.getMessage());
}
return result;
}
}
public with sharing class SObjectQueryService {
public static Integer getTotalCount(String sObjectName) {
Integer totalCount = 0;
String query = 'select count(Id) totalCount from ' + sObjectName;
List<AggregateResult> res = (List<AggregateResult>) Database.query(query);
if (!res.isEmpty()) {
totalCount = (Integer) res.get(0).get('totalCount');
}
return totalCount;
}
public static Integer getLastPageRecordCount(Integer totalRecords, Integer pageSize) {
Integer totalPages = (Integer) Math.ceil((Decimal) totalRecords / pageSize);
Integer recordCount = totalRecords - ((totalPages - 1) * pageSize);
return recordCount;
}
public static List<SObject> queryRecords(String sObjectName, String lastKnownId, Integer maxResults, Integer direction) {
List<String> fields = describeSObjectFields(sObjectName);
String query = 'select ' + String.join(fields, ',');
query += ' from ' + sObjectName;
if (String.isNotBlank(lastKnownId)) {
query += ' where Id ' + (direction == 1 ? '>' : '<') + ' \'' + lastKnownId + '\' ';
}
if (maxResults != null) {
// query one extra record
maxResults = maxResults + 1;
query += ' order by Id ' + (direction == 1 ? 'asc' : 'desc') + ' limit :maxResults';
}
return Database.query(query);
}
public static Map<String, Object> getPageState(List<SObject> records, String lastKnownId, Integer totalPages, Integer pageSize, Integer direction) {
Map<String, Object> pageState = new Map<String, Object>();
if (String.isBlank(lastKnownId)) {
if (records.size() > pageSize) {
records.remove(records.size() - 1);
}
if (direction == -1) {
// loading last page
pageState.put('hasPrevious', totalPages > 1);
pageState.put('hasNext', false);
} else {
// loading first page
pageState.put('hasPrevious', false);
pageState.put('hasNext', totalPages > 1);
}
} else if (records.size() > pageSize) {
records.remove(records.size() - 1);
pageState.put('hasPrevious', true);
pageState.put('hasNext', true);
} else {
if (direction == -1) {
// reached first page while navigating backwards
pageState.put('hasPrevious', false);
pageState.put('hasNext', true);
} else {
// reached last page while navigating forwards
pageState.put('hasPrevious', true);
pageState.put('hasNext', false);
}
}
if (!records.isEmpty()) {
// set first and last Id on the page
if (direction == -1) {
pageState.put('firstId', records.get(records.size() - 1).get('Id'));
pageState.put('lastId', records.get(0).get('Id'));
} else {
pageState.put('firstId', records.get(0).get('Id'));
pageState.put('lastId', records.get(records.size() - 1).get('Id'));
}
}
return pageState;
}
private static List<String> describeSObjectFields(String sObjectName) {
List<String> fields = new List<String>();
SObject sObj = (SObject) Type.forName(sObjectName).newInstance();
DescribeSObjectResult objResult = sObj.getSObjectType().getDescribe();
Map<String, SObjectField> fieldsMap = objResult.fields.getMap();
for (String field : fieldsMap.keySet()) {
DescribeFieldResult fieldResult = fieldsMap.get(field).getDescribe();
if (fieldResult.isAccessible() && fieldResult.getType() != Schema.DisplayType.BASE64) {
fields.add(fieldResult.getName());
if (fieldResult.getType() == Schema.DisplayType.REFERENCE) {
List<SObjectType> sObjectTypes = fieldResult.getReferenceTo();
SObjectType referenceTo = sObjectTypes.get(0);
String relName = fieldResult.getRelationshipName();
String nameField = getNameField(referenceTo);
fields.add(relName + '.' + nameField);
}
}
}
return fields;
}
private static String getNameField(SObjectType sObjType) {
String fieldName = null;
DescribeSObjectResult relObjResult = sObjType.getDescribe();
Map<String, SObjectField> fieldsMap = relObjResult.fields.getMap();
for (String field : fieldsMap.keySet()) {
DescribeFieldResult fieldResult = fieldsMap.get(field).getDescribe();
if (fieldResult.isNameField()) {
fieldName = fieldResult.getName();
break;
}
}
return fieldName;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment