Use Case
User wants to search on Case Comment's body based on search input. The nature of functionality will as follows:
- User will be navigated from Account Details page. Account records can have may cases and each case records may have many case comments.
- User will provide the search input and based on the search input, data will be displayed at Visualforce page.
- Keyword search needs to be performed on body of Case Comment.
- Data will be displayed in the paginated way.
- The keyword will be highlighted in yellow color.
The screen will be look like this:
Approach
Reason behind this, StandardSetController cannot take a List of Task, Event or CaseComment records.
Secondly, in my last post I have used the Container Page that is main Visualforce page which doesn't have Controller. If I try to use Controller along with server side ajax which is using another Visualforce page then cross origin violation error will occur.
But the main page, I have to use Controller which might needed for other operations. Like I can have different buttons and can perform other actions etc (through it is not related to the small use case, but it is quite justified for most the scenarios).
Thirdly, for pagination purpose I can use jQuery Datatable, but the datatable search box will not use. As because, it will perform server side search and then pull the records from the Salesforce database.
Solution
Visualforce
1. For searching, declare textbox like this
<apex:inputText value="{!searchString}" label="Search in Comments"/>
2. Create a pageBlockTable which will be used as jQuery Datatable
3. Since Datatable's searchbox should be hidden, use this style
.dataTables_wrapper .dataTables_filter {
float: right;
text-align: right;
visibility: hidden;
}
4. Since we need to highlight the value, so we will assign datatable's
"oSearch" attribute with the search input like this
"oSearch": {"sSearch": "{!searchString}"}
Is that sound interesting!!!
The full page will be as follows
1: <apex:page id="AdvancedSearchCaseCommentPage" controller="AdvancedCommentSearchController" showHeader="false" action="{!init}" >
2: <head>
3: <apex:includescript value="//code.jquery.com/jquery-1.11.1.min.js" / >
4: <apex:includescript value="//cdn.datatables.net/1.10.4/js/jquery.dataTables.min.js" />
5: <apex:stylesheet value="//cdn.datatables.net/1.10.4/css/jquery.dataTables.css" />
6: <!-- Search Highlight -->
7: <apex:includeScript value="//bartaz.github.io/sandbox.js/jquery.highlight.js" />
8: <apex:includeScript value="//cdn.datatables.net/plug-ins/1.10.9/features/searchHighlight/dataTables.searchHighlight.min.js"/>
9: <apex:stylesheet value="//cdn.datatables.net/plug-ins/1.10.9/features/searchHighlight/dataTables.searchHighlight.css"/>
10: <style>
11: .filterMatches {
12: background-color: #BFFF00;
13: }
14: .tertiaryPalette {
15: color: #000 !important;
16: }
17: .dt-buttons {
18: margin-left: 10px;
19: }
20: .dataTables_wrapper .dataTables_filter {
21: float: right;
22: text-align: right;
23: visibility: hidden;
24: }
25: </style>
26: <script>
27: j$ = jQuery.noConflict();
28: j$(document).ready( function () {
29: var contactTable = j$('[id$="commentTable"]').DataTable({
30: "oSearch": {"sSearch": "{!searchString}"},
31: searchHighlight: true
32: });
33: });
34: </script>
35: </head>
36: <apex:form id="formid">
37: <apex:pageMessages id="msgsId"/>
38: <apex:pageBlock title="Advanced Search">
39: <apex:pageBlockSection >
40: <apex:outputText value="{!accountObj.Name}" label="Account:"/>
41: <br/>
42: <apex:inputText value="{!searchString}" label="Search in Comments"/>
43: <br/>
44: </apex:pageBlockSection>
45: <apex:pageBlockButtons location="bottom">
46: <apex:commandButton value="Search" action="{!searchComment}" />
47: <apex:commandButton value="Cancel" action="{!cancel}" />
48: </apex:pageBlockButtons>
49: </apex:pageBlock>
50: <apex:pageBlock title="Results" rendered="{!showResult}">
51: <apex:pageBlockTable value="{!lstRecords}" var="obj" rendered="{!showResult}" html-cid="commentTable" id="commentTable">
52: <apex:column headerValue="Case Number">
53: <apex:outputLink value="/{!obj.cont.ParentId}" target="_blank">{!obj.CaseNumber}</apex:outputLink>
54: </apex:column>
55: <apex:column headerValue="Comments">
56: {!obj.cont.CommentBody}
57: </apex:column>
58: </apex:pageBlockTable>
59: </apex:pageBlock>
60: </apex:form>
61: </apex:page>
Controller
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
| /**
Class: AdvancedCommentSearchController
Author: Santanu Boral
Description: To search string from Case Comments.
------------------------------------------------------------------------------
*/
public class AdvancedCommentSearchController {
public String accountId {get;set;}
public Account accountObj {get;set;}
public String searchString{get;set;}
public Boolean showResult{get;set;}
public List<CaseCommentWrapper> lstWrapper {get;set;}
public List<CaseCommentWrapper> lstRecords{get;set;}
String errorStr = '';
public Boolean hasError {
get {
return ApexPages.hasMessages();
}
}
public AdvancedCommentSearchController()
{
lstWrapper = new List<CaseCommentWrapper>();
lstRecords = new List<CaseCommentWrapper>();
}
public void init()
{
accountId = ApexPages.CurrentPage().getParameters().get('id');
accountObj = [SELECT Name FROM Account WHERE Id =:accountId];
}
public void searchComment()
{
lstWrapper = new List<CaseCommentWrapper>();
Map<Id,Id> commentToCaseIdMap = new Map<Id,Id>(); //commentId, CaseId
try
{
//perform validations
if(String.isNotBlank(searchString) && searchString.length()>2)
{
//retrieve the list of case comments
List<CaseComment> lstComment = [FIND :searchString
IN ALL FIELDS RETURNING CaseComment (id,ParentId,
CommentBody
WHERE Parent.AccountId = :accountId
ORDER BY CreatedDate DESC)
LIMIT 1000][0];
if(lstComment.size()>0)
{
showResult = true;
//loop through the case comments and assign values to wrapper object.
for(CaseComment cont : lstComment )
{
commentToCaseIdMap.put(cont.Id,cont.ParentId); //commentId, CaseId
CaseCommentWrapper objWrapper = new CaseCommentWrapper();
objWrapper.cont = cont;
objWrapper.Id = cont.Id;
objWrapper.ParentId = cont.ParentId;
objWrapper.CommentBody = cont.CommentBody;
lstWrapper.add(objWrapper);
}
//since we need to display CaseNumber so it should be retrieved via separate query
Map<Id, Case> mapCaseCommentQuery = new Map<Id, Case>([SELECT Id, CaseNumber
FROM Case
WHERE Id IN:commentToCaseIdMap.values()]);
//assign CaseNumber to the respective comment entries.
for(CaseCommentWrapper wrapper:lstWrapper)
{
if(mapCaseCommentQuery.containsKey(wrapper.ParentId))
{
wrapper.CaseNumber = mapCaseCommentQuery.get(wrapper.ParentId).CaseNumber;
}
}
//create an instance of Iterable object
CustomCaseCommentIterable obj = new CustomCaseCommentIterable (lstWrapper);
//assign page size
obj.setPageSize = lstComment.size();
//perform next
lstRecords = obj.next();
}
else
{
showResult = false;
ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'No Records Found.'));
}
}
else if(searchString.length()<=2)
{
ApexPages.addmessage(new ApexPages.message(ApexPages.severity.INFO,'Please enter at-least 2 characters'));
}
}
catch(Exception ex)
{
errorStr ='Error Occured while Searching.';
ApexPages.addmessage(new ApexPages.message(ApexPages.severity.ERROR, 'errorStr:' +ex.getMessage()));
}
}
public PageReference cancel()
{
PageReference pRef = (new ApexPages.StandardController (new Account(Id=accountId))).cancel();
pRef.setRedirect(true);
return pRef;
}
}
|
The visualforce page will be called from Custom Button which needs to be placed on Account page layout.
Code behind button
/apex/AdvancedSearchCaseCommentPage?id=<accountId>
Results
Initially user will be given to this page.
User will provide at-least 3 digit input and then controller's action method will populate the data based on SOSL search.
Conclusion
I have found this approach is really helpful in terms of performance and leveraging datatable's attributes mapping to the use case.
References