Find myself writing similar page in several requirements - page that display records from specific objects, support pagination, filtering, sorting.
Thought that it's about time to take 1 of the pages I had and make it generic. To be rendered based on configuration.
How to do it?
1.Create new custom metadata for the configuration.
2.Create apex class
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | public with sharing class SearchObjectController { public Search_Object_Setup__mdt searchPageSetup {get; set;} public list<FieldToShow> l_fields {get; set;} public list<FilterInPage> l_filters {get; set;} public String filterParamIds {get; set;} private String soql; private String soqlCtr {get;set;} public Integer ctrRecords {get;set;} Public Integer noOfRecords{get; set;} // Number of total records in Query // the collection of contacts to display private list<sObject> l_records; private Integer pageSize; private Integer totalRecords = 3000; public String pageTitle {get; set;} //set controller private ApexPages.StandardSetController setCon; // standart set controller supporting pagination. CANNOT be Transietnt (results in Error) public list<sObject> getl_records(){ return this.setCon.getRecords(); } public String sortDir { get; set; } public String sortField { get; set; } // toggles the sorting of query from asc<-->desc public void toggleSort() { // simply toggle the direction sortDir = sortDir.equals('asc NULLS FIRST ') ? 'desc NULLS LAST ' : 'asc NULLS FIRST '; runQuery(); } // init the controller and display some initial data when the page loads public SearchObjectController () { //String searchObjectPageId = Apexpages.currentPage().getParameters().get('pId'); String searchObjectName = Apexpages.currentPage().getParameters().get('pn'); searchPageSetup = [ select DeveloperName,Page_Size__c,Page_Title__c,Fields_to_Display__c,Filters__c,Object_API_Name__c from Search_Object_Setup__mdt where DeveloperName = :searchObjectName]; pageSize = searchPageSetup.Page_Size__c != null ? Integer.valueOf(searchPageSetup.Page_Size__c) : 20; pageTitle = searchPageSetup.Page_Title__c; l_fields = new list<FieldToShow>(); l_filters = new list<FilterInPage>(); //Get object schema Schema.SObjectType objSchema = Schema.getGlobalDescribe().get(searchPageSetup.Object_API_Name__c); filterParamIds = ''; if(! String.isBlank(searchPageSetup.Filters__c)){ Integer filterIndex = 1; for(String fieldFilter : searchPageSetup.Filters__c.split(',')){ l_filters.add(new FilterInPage(fieldFilter, ('prmid_' + String.valueOf(filterIndex)), objSchema.getDescribe().fields.getMap().get(fieldFilter).getDescribe().getLabel())); filterParamIds += ('prmid_' + String.valueOf(filterIndex)) + ','; filterIndex++; } filterParamIds = filterParamIds.removeEnd(','); } soql = 'select '; for(String fieldAPI : searchPageSetup.Fields_to_Display__c.split(',')){ soql += fieldAPI + ','; l_fields.add(new FieldToShow(fieldAPI, objSchema.getDescribe().fields.getMap().get(fieldAPI).getDescribe().getLabel())); } soql = soql.removeEnd(','); soql += ' from ' + searchPageSetup.Object_API_Name__c; soqlCtr = 'SELECT Count() from ' + searchPageSetup.Object_API_Name__c; sortField = 'Name'; sortDir = 'desc NULLS LAST'; runQuery(); } //Runs the actual query, with parameters as set by the User public void runQuery() { try { System.debug(soql + ' order by ' + String.escapeSingleQuotes(sortField) + ' ' + String.escapeSingleQuotes(sortDir) + ' limit ' + totalRecords); l_records = Database.query(soql + ' order by ' + String.escapeSingleQuotes(sortField) + ' ' + String.escapeSingleQuotes(sortDir) + ' limit ' + totalRecords); this.setCon= new ApexPages.StandardSetController(l_records); this.setCon.setpageNumber(1); // First page to display this.setCon.setPageSize(pageSize); // First chuck of 20 records to display noOfRecords = this.setCon.getResultSize(); // # records in the resulst set,, driven by limit and ViewState size ctrRecords = Database.countQuery(soqlCtr ); // Total number of records for the Query (no Limit used) } catch (Exception ex) { ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Error during query: ' + ex.getMessage())); } } //Run the search with parameters passed via Javascript. //Invoked when user chnages seach criteria or page size on the page public PageReference runSearch() { System.debug('Run Search with params'); soql = 'select '; for(FieldToShow fieldPage : l_fields){ soql += fieldPage.field + ','; } soql = soql.removeEnd(','); soql += ' from ' + searchPageSetup.Object_API_Name__c; String whereClause = ' where'; String inputFilters = ApexPages.currentPage().getParameters().get('allInputParams'); System.debug('inputFilters : ' + inputFilters ); list<String> l_inputFilters = inputFilters.split(','); System.debug('l_inputFilters : ' + l_inputFilters ); Integer index = 0; for(index = 0; index < l_filters.size() ; index ++) { if(l_inputFilters.size() > index){ l_inputFilters.get(index); if(! String.isBlank(l_inputFilters.get(index))){ whereClause += (whereClause == ' where' ? ' ' : ' and') + ' ' + l_filters.get(index).field + ' LIKE \'%' + String.escapeSingleQuotes(l_inputFilters.get(index)) + '%\''; } } } if(whereClause != ' where'){ soql += whereClause; } runQuery(); return null; } //Advance to next page public void doNext(){ if(this.setCon.getHasNext()) this.setCon.next(); } //Advance to previous page public void doPrevious(){ if(this.setCon.getHasPrevious()) this.setCon.previous(); } //Get whether previous page exists public Boolean getHasPrevious(){ return this.setCon.getHasPrevious(); } //Get whether next page exists public Boolean getHasNext(){ return this.setCon.getHasNext(); } //Get page number public Integer getPageNumber(){ return this.setCon.getPageNumber(); } //Get total pages in the current Query Public Integer getTotalPages(){ Decimal totalSize = this.setCon.getResultSize(); Decimal pageSize = this.setCon.getPageSize(); Decimal pages = totalSize/pageSize; return (Integer)pages.round(System.RoundingMode.CEILING); } //First page of pagination public void doFirst() { setCon.first(); } //Last page of pagination public void doLast() { setCon.last(); } public Integer getpageSize() { return pageSize; } //Invoked when the user change page size on page, viajs doSeacrh() calling runSearch() public void setpageSize(Integer pageSize) { this.pageSize = integer.valueof(pageSize); } public class FieldToShow { public String field {get; set;} public String label {get; set;} public FieldToShow (String f, String l){ field = f; label = l; } } public class FilterInPage{ public String field {get; set;} public String paramId {get; set;} public String label {get; set;} public FilterInPage (String f, String p, String l){ field = f; paramId = p; label = l; } } } |
3.Create Visualforce page
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | <apex:page controller="SearchObjectController" sidebar="false"> <script type="text/javascript"> var delayTimer; function doSearch() { // Invoked every time a key is pressed //### filter for real chars clearTimeout(delayTimer); // clear timeout if less than 1000 msec passed, so we do not call for every key pressed var paramsIds = '{!filterParamIds}'; var paramsList = paramsIds.split(','); var params = ''; for(var i=0; i < paramsList.length; i++ ) { params += document.getElementById(paramsList[i]).value + ','; } //params = params.substring(0, params.length - 1); console.log('###' + params); delayTimer = setTimeout(function() { // if 1 sec passed with no keys pressed, invoke the backend search // Do the ajax stuff searchServer(params); }, 1000); // Will do the ajax stuff after 1000 ms, or 1 s } //doSearch() </script> <apex:form Id="theForm"> <apex:pageMessages id="errors" /> <apex:pageBlock title="{!pageTitle}" mode="edit"> <table width="100%" border="0"> <tr> <td width="200" valign="top"> <apex:pageBlock title="Parameters" mode="edit" id="criteria"> <table cellpadding="2" cellspacing="2"> <apex:repeat value="{!l_filters}" var="filterInPage"> <tr> <td style="font-weight:bold;">{!filterInPage.label}<br/> <input type="text" id="{!filterInPage.paramId}" onkeyup="doSearch();"/> </td> </tr> </apex:repeat> </table> <apex:actionStatus id="queryIndicator" > <apex:facet name="start"> <apex:image url="{!$Resource.LoadingProgress}" /> </apex:facet> <apex:facet name="stop" > </apex:facet> </apex:actionStatus> </apex:pageBlock> <apex:pageBlock id="ctr"> Total records for Query: <apex:outputText value="{!ctrRecords}" /> </apex:pageBlock> </td> <td valign="top"> <apex:pageBlock mode="edit" id="results"> <apex:pageBlockTable value="{!l_records}" var="rec"> <apex:repeat value="{!l_fields}" var="fToShow"> <apex:column > <apex:facet name="header"> <apex:commandLink value="{!fToShow.label}" action="{!toggleSort}" rerender="results,debug, ctr" status="queryIndicator"> <apex:param name="sortField" value="{!fToShow.field}" assignTo="{!sortField}"/> </apex:commandLink> </apex:facet> <apex:outputField value="{!rec[fToShow.field]}"/> </apex:column> </apex:repeat> </apex:pageBlockTable> <apex:panelGrid columns="10"> <apex:commandButton status="fetchStatus" reRender="results" value="|<" action="{!doFirst}" disabled="{!!hasPrevious}" title="First Page" /> <apex:commandButton status="fetchStatus" reRender="results" value="<" action="{!doPrevious}" disabled="{!!hasPrevious}" title="Previous Page" /> <apex:commandButton status="fetchStatus" reRender="results" value=">" action="{!doNext}" disabled="{!!hasNext}" title="Next Page" /> <apex:commandButton status="fetchStatus" reRender="results" value=">|" action="{!doLast}" disabled="{!!hasNext}" title="Last Page" /> <apex:outputLabel value=" [page {!pageNumber} of {!totalPages}] " /> <apex:outputText > [Items {!(pageNumber * pageSize)+1-pageSize}-{!IF((pageNumber * pageSize)>noOfRecords, noOfRecords,(pageNumber * pageSize))} of {!noOfRecords} Records] </apex:outputText> Select Records per Page: <apex:selectList value="{!pageSize}" multiselect="false" size="1" onchange="doSearch();"> <apex:selectOption itemValue="10" itemLabel="10"/> <apex:selectOption itemValue="20" itemLabel="20"/> <apex:selectOption itemValue="50" itemLabel="50"/> <apex:selectOption itemValue="500" itemLabel="500"/> <apex:selectOption itemValue="1000" itemLabel="1000"/> </apex:selectList><p/> <apex:outputPanel style="color:#4AA02C;font-weight:bold"> <apex:actionStatus id="fetchStatus" startText="Fetching..." stopText=""/> </apex:outputPanel> </apex:panelGrid> </apex:pageBlock> </td> </tr> </table> </apex:pageBlock> <apex:actionFunction name="searchServer" action="{!runSearch}" rerender="results,debug,errors,ctr" status="queryIndicator"> <apex:param name="allInputParams" value="" /> </apex:actionFunction> </apex:form> </apex:page> |
4.The above steps were for the infra components.
Now we can reuse it by create custom metadata record.
For example:
-Redirect to the page URL
/apex/SearchObject?pn=Search_Account