You have been asked to give profile level security for an apex class in your Salesforce org but you find a hard time to find it as there are thousands of apex class. This blog is going to provide you a native solution which can be used in Home page, Lightning App Page or Record page to allow to quickly find the apex class with lookup option.
Solution Design – Apex Class Explorer
We are going to have a Lightning Web Component and Apex Class controller. LWC has a search input and client side handler function bind to it defined in js file. The function invoke an apex method which receives the search term as input parameter and return list of apex class.
Metadata List – Apex Class Explorer
- apexClassExplorerController (Apex Class)
- apexClassExplorer (LWC)
apexClassExplorerController
fetchApexClass returns list of apex class and accept string input parameter.
public with sharing class apexClassExplorerController {
@AuraEnabled
public static List<ApexClass> fetchApexClass(String searchKey){
String key = '%' + searchKey + '%';
try {
return [SELECT Id, NamespacePrefix, Name, ApiVersion, Status, IsValid, BodyCrc, Body, LengthWithoutComments, CreatedDate, CreatedBy.Name, LastModifiedDate, LastModifiedBy.Name, SystemModstamp FROM ApexClass WHERE Name LIKE :key];
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
}
apexClassExplorer.html
The HTML file has lightning input of type search and an iterator which iterate through the apex class list returned by apex controller of the Lightning Web Component. HTML file also has buttons to edit, view the code and hide the inline apex code of selected apex class.
<template>
<lightning-card title="Apex Class Search" icon-name="custom:custom57">
<div class="slds-var-m-around_medium">
<lightning-input type="search" onchange={handleKeyChange}
class="slds-show slds-is-relative slds-var-m-bottom_small" label="Search">
</lightning-input>
<template if:true={apexClasses}>
<template for:each={apexClasses} for:item="apex">
<lightning-card key={apex.Id}>
<h3 slot="title">
<lightning-icon icon-name="doctype:xml" alternative-text="XML file" title="XML">
</lightning-icon>
{apex.Name}
</h3>
<lightning-button label="Edit" onclick={handleEdit} slot="actions" data-url={apex.Id}>
</lightning-button>
<lightning-button label="View" onclick={handleView} data-id={apex.Id} slot="actions">
</lightning-button>
<lightning-button if:true={apex.showCode} label="Hide" onclick={handleHide} data-id={apex.Id}
slot="actions">
</lightning-button>
<div class="slds-scrollable">
<div class="slds-text-longform">
<div class="slds-page-header__row slds-page-header__row_gutters">
<div class="slds-page-header__col-details">
<ul class="slds-page-header__detail-row" style="list-style: none;">
<li class="slds-page-header__detail-block">
<div class="slds-text-title slds-truncate" title="Api Version">Api
Version
</div>
<div class="slds-truncate" title={apex.ApiVersion}>{apex.ApiVersion}
</div>
</li>
<li class="slds-page-header__detail-block">
<div class="slds-text-title slds-truncate" title="Status">Status</div>
<div class="slds-truncate" title={apex.Status}>{apex.Status}</div>
</li>
<li class="slds-page-header__detail-block">
<div class="slds-text-title slds-truncate" title="Namespace Prefix">
Namespace Prefix
</div>
<div class="slds-truncate" title={apex.NamespacePrefix}>
{apex.NamespacePrefix}
</div>
</li>
<li class="slds-page-header__detail-block">
<div class="slds-text-title slds-truncate" title="IsValid">Created Date
</div>
<div class="slds-truncate" title={apex.IsValid}>{apex.CreatedDate}</div>
</li>
<li class="slds-page-header__detail-block">
<div class="slds-text-title slds-truncate" title="IsValid">Created By
Name
</div>
<div class="slds-truncate" title={apex.IsValid}>{apex.CreatedBy.Name}
</div>
</li>
<li class="slds-page-header__detail-block">
<div class="slds-text-title slds-truncate" title="IsValid">Last Modified
Date
</div>
<div class="slds-truncate" title={apex.IsValid}>{apex.LastModifiedDate}
</div>
</li>
<li class="slds-page-header__detail-block">
<div class="slds-text-title slds-truncate" title="IsValid">Last Modified
By Name
</div>
<div class="slds-truncate" title={apex.IsValid}>
{apex.LastModifiedBy.Name}
</div>
</li>
</ul>
</div>
</div>
<blockquote if:true={apex.showCode}>
<pre>
<code class="language-html">
{apex.Body}
</code>
</pre>
</blockquote>
</div>
</div>
</lightning-card>
</template>
</template>
</div>
</lightning-card>
</template>
ApexClassExplorer.js
JavaScript file of the Lightning Web Component has below methods:-
- handleKeyChange – It is bind to search input and fires on change of the value. It makes an apex call and return list of the apex class based on search input
- handleView – This method fires whenever users clicks on view button on iterated list of records. It basically display the apex code of selected apex class.
- handleHide – fires whenever users clicks on hide button on iterated list of records. It basically hides the apex code of selected apex class.
- handleEdit – Edit button takes user to standard record page of apex class in setup.
import { LightningElement, track } from 'lwc';
import fetchApexClass from '@salesforce/apex/apexClassExplorerController.fetchApexClass';
/** The delay used when debouncing event handlers before invoking Apex. */
const DELAY = 350;
export default class ApexClassExplorer extends LightningElement {
@track apexClasses;
error;
handleKeyChange(event) {
// Debouncing this method: Do not actually invoke the Apex call as long as this function is
// being called within a delay of DELAY. This is to avoid a very large number of Apex method calls.
window.clearTimeout(this.delayTimeout);
const searchKey = event.target.value;
// eslint-disable-next-line @lwc/lwc/no-async-operation
this.delayTimeout = setTimeout(() => {
fetchApexClass({ searchKey: searchKey })
.then((result) => {
result.forEach(element => {
element.showCode = false;
});
this.apexClasses = result;
this.error = undefined;
})
.catch((error) => {
this.error = error;
this.apexClasses = undefined;
});
}, DELAY);
}
handleView(event) {
const dataid = event.target.dataset.id;
this.apexClasses = this.apexClasses.map(x => {
if (x.Id == dataid) {
x.showCode = true;
}
return x;
});
}
handleHide(event) {
const dataid = event.target.dataset.id;
this.apexClasses = this.apexClasses.map(x => {
if (x.Id == dataid) {
x.showCode = false;
}
return x;
});
}
handleEdit(event) {
const url = window.location.origin + '/' + event.target.dataset.url;
window.open(url);
}
}
ApexClassExplorer.js-meta.xml
XML file of the component has target defined to expose it to Lightning App Builder for home, app and record page.
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
</LightningComponentBundle>
Demo- Apex Class Explorer
Keep checking this for more update on Lightning Web Component scenarios.
Awensome ….you are a genious