Friday, May 15, 2020

Mention a User in Chatter Post and Set Chatter Post from a Specific User through Apex Code

Use case:
Business requires when a new account is created with account type is partner or vendor, a chatter post is added to the Account page. The chatter user must be a specific user and chatter post must mention the department director.

Analysis:
1. A trigger is added to Account object.
2. How to add the required chatter post using Apex code is a bit complicated.
There are two points:
2.1. How to add a specific user as the chatter post created by user through Apex Code?
2. 2. How to mention a user in chatter post through Apex Code

We have two ways to add a chatter post, using FeedItem object and using ConnectAPI name space classes.

With FeedItem object we can assigned CreatedById when the record is created. It meets the requirement item 2.1. But it cannot mention a user.

With ConnectAPI name space classes, it is the other way. It can mention a user but cannot assign createdbyId.


Solution:
1. Add a trigger on Account object, call the method in step 2.

PostFeedItem(accountId,mentionedUserId,'Please have a look?   (--by autobot--)');

Please note '(--by autobot--)', this will be used to identify if the chatter post is created by apex code or real person input. This text will be used in the code below.

2. Create a method using ConnectAPI to generate chatter post.
This method has three parameters: parent record Id, mentioned User Id and the message.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public static void PostFeedItem(Id ParentId,Id mentionedUserId, string message)
{
    ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
    ConnectApi.MentionSegmentInput mentionSegmentInput = new ConnectApi.MentionSegmentInput();
    ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
    ConnectApi.TextSegmentInput textSegmentInput = new ConnectApi.TextSegmentInput();
    messageBodyInput.messageSegments = new List<ConnectApi.MessageSegmentInput>();
    mentionSegmentInput.id = mentionedUserId;
    messageBodyInput.messageSegments.add(mentionSegmentInput);
    textSegmentInput.text = message;
    messageBodyInput.messageSegments.add(textSegmentInput);
    feedItemInput.body = messageBodyInput;
    feedItemInput.feedElementType = ConnectApi.FeedElementType.FeedItem;
    feedItemInput.subjectId = ParentId;
    ConnectApi.FeedElement feedElement =
    ConnectApi.ChatterFeeds.postFeedElement(null, feedItemInput);
}


3. Add a trigger on FeedItem, when a new FeedItem record is created, change the createdbyId.
In the trigger, it take text '(--by autobot--)' as identifier if createdbyid needs to be assigned to a specific user.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
trigger FeedItemTrigger on FeedItem (before insert) {

    List<FeedItem> chatterPosts = new List<FeedItem>();
    for(FeedItem chatterPost : trigger.new)
    {
        if (chatterPost.body.contains('(--by autobot--)'))
        {
            chatterPosts.add(chatterPost);
        }
    }
    FeedItemTriggerHanlder(chatterPosts);
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public with sharing class FeedItemTriggerHanlder {
    public static void UpdateCreatedById(List<FeedItem> chatterPosts) {
        Id userId = [SELECT Id FROM User where isActive=true AND userName = 'xxxxx' LIMIT 1].Id;
        for (FeedItem chatterPost : chatterPosts)
        {
            chatterPost.createdById = userId;
        }

    }
}


How to use the code

You need to copy the code above in design section 2 and 3 and build you own Account trigger. As a reminder, it should be a AFTER trigger.





Saturday, May 9, 2020

Five Easy Steps to Implement Autocomplete in Salesforce Visual Force Page


Almost all website uses Autocomplete function when doing search. Autocomplete gives users suggestions on what you could be searching for.

For example, if you type “sales” in Google search box, you can see a list of most popular searched words or sentences listed under the search box; if one of them is what you want to search, just move mouse to the word and click it. This saves you time and helps you avoiding typo.

When doing Visual Force page design, developers find Salesforce does not provide native autocomplete function.


In this blog, I will how to implement Autocomplete in Visual Force Page with the help of Jquery UI STEP BY STEP.

If you don't  have time to read the details of the implementation, do the following and your code should work.

  • Copy the code of step 1, 3
  • Refer to Step 2, find search input text <apex:inputText> component in your Visual force Page, add class "contact_search" to this component, and copy line 4 of section 3 to your code.
  • Copy code of step 4, change line 31 of code in section 4 to your own Apex class method.
  • Refer to step 5, make sure your Apex Class method works. (You can't copy the code for Apex method. 😃



1. Add JQuery JavaScript library and css Style to Visual Force Page
Within Visual Force Page, this can be done by referencing static resource or simply by referencing the library through <script> tag. I prefer just add the line right after <apex:page>  

Style ui-autocomplete will be used in displaying the autocomplete list. It is very important.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<apex:Page controller="customController">
    <style type="text/css">
    .ui-autocomplete {
        max-height: 80%;
        overflow-y: auto;
        overflow-x: hidden;
        min-width: 250px;
    }
    </style>
    <script src="https://code.jquery.com/jquery-1.8.2.js"></script>
    <script src="https://code.jquery.com/ui/1.9.0/jquery-ui.js"></script>
    <link rel="stylesheet" href="https://code.jquery.com/ui/1.9.1/themes/base/jquery-ui.css"/>
      ......


2. Add <apex:ActionSupport> component to search <apex:inputText> component.

The following code present user an input text box and a search button.



1
2
3
4
5
<apex:outputPanel id="searchPanel">
    <legend class="slds-form-element__legend slds-form-element__label ">Search</legend>
    <apex:inputText value="{!searchString}" styleClass="slds-m-right_x-small"/>
    <apex:commandButton action="{!search}" value="Search" reRender="formId" status="spinner"  id="searchButtonId" />
</apex:outputPanel>
Just add <apex:actionSupport> to <apex:inputText> component and add class “contact_search” to <apex:inputText> component.
Note:  “contact_search” will be used in JQuery to identify which input text box to have list value populated.

1
2
3
4
5
6
7
<apex:outputPanel id="searchPanel">
    <legend class="slds-form-element__legend slds-form-element__label ">Search</legend>
    <apex:inputText value="{!searchString}" styleClass="contact_search slds-m-right_x-small">
        <apex:actionSupport event="onchange" rerender="refreshAutocomplete"/>
    </apex:inputText>
    <apex:commandButton action="{!search}" value="Search" reRender="formId" status="spinner"  id="searchButtonId" />
</apex:outputPanel>


3. Add <apex:actionFunction> component called “refreshAutocomplete”.
<apex:actionFunction component that provides support for invoking controller action methods directly from JavaScript code using an AJAX request.

You can add the following line to line just right after <apex:form>


1
2
3
<apex:form id="formId">
   <apex:actionFunction name="refreshAutocomplete" id="refreshAutocomplete" 
    rerender="autocompletePanel"/>


In step 2, input text box change triggers AJAX request refreshing component id “refreshAutocomplete”.
So we add a <apex:actionFunction> with id=”refreshAutocomplete”. When this component refreshes, it will rerender component “autocompltePanel


4.Add <apex:outputPane id=”autocompletePanel”> with JQuery JavaScript functions.
Copy the following line to the end of your VF page. It could be out of </apex:form> tag.
This is the core function of autocomplete with JQuery. I will explain line by line after JavaScript code.

NoteautocompletePanel, it matches the one in step 3.

 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
    <apex:outputPanel id="autocompletePanel">
        <script type="text/javascript">

            $(function() {
                try {
                    var ac = $( "[class*='contact_search']" ).autocomplete({
                        minLength: 2,
                        source: function(request, callback){
                            var term = request.term;
                            queryContactCallback(term, callback);
                        },
                        select: function( event, ui ) {
                            $( "input[class*='contact_search']" ).val(ui.item.label);
                            return false;
                        }
                    }).data( "ui-autocomplete" )._renderItem = function( ul, item ) {
                        return $( "<li>" )
                        .append( "<a class='slds-text-title'>"+ item.label + "</a>" )
                        .appendTo( ul );
                    };
                } catch(e) {
                    console.log('error:' + e);
                 }
            });

            function queryContactCallback(query, callback)
            {
                var data = [];

                Visualforce.remoting.Manager.invokeAction(
                    '{!$RemoteAction.COT_ContactController.getAutoCompleteContact}',
                    query,
                    function(lstresult, event){
                        for (var n=0; n<lstresult.length; n++){
                            var result = {
                                value: lstresult[n],
                                label: lstresult[n]
                            };
                            data.push(result);
                        }
                        callback(data);
                    }
                );

            }

        </script>
    </apex:outputPanel>

Line 6 : var ac = $( "[class*='contact_search']" ).autocomplete({
Make sure the class value matches the input text class added in step 2. It is ‘contact_search’.

Line 7: minLenght: 2
This line means only when there are at least to characters entered in the box, the Remote call will be made to the controller.

Line 9: var term = request.term;
requet.term is what user enters in input text box.

Line 10: queryContactCallback(term, callback);
This lines calls JavaScript function queryContactCallback.  queryContactCallback is function at line 26.

Line 12 to Line 14:
            select: function( event, ui ) {
                            $( "input[class*='contact_search']" ).val(ui.item.label);
                            return false;
                        }
         
This four lines handle when user select a value from autocomplete dropdown list.

It basically gets the item label and put in input text box.



Line 16 to Line 19: 
            data( "ui-autocomplete" )._renderItem = function( ul, item ) {
                    return $( "<li>" )
                    .append( "<a class='slds-text-title'>"+ item.label + "</a>" )
                   .appendTo( ul );

Do you still remember "ui-autocomplete"?  We added it to VF Page in step 1.  Yes, it is used in line 16.
Line 16 to Line 19 get the return values from controller method and populate into a list. If needed, developer can modify this line .append( "<a class='slds-text-title'>"+ item.label + "</a>" )  to make the dropdown more fancy.

Line 26: Function queryContactCallback
This method calls the controller method and return list value.
{!$RemoteAction.customController.getAutoCompleteContact}',
This line tells Salesforce to call method getAutoCompleteContract in Apex Class called “customController”.
If there are multiple parameters passing to the method, you can do like this:

            Visualforce.remoting.Manager.invokeAction(
                        {!$RemoteAction.customController.getAutoCompleteContact}',
                        parameter1,
                        parameter2,
                        function(lstresult, event){

Line 34 to Line 39: Return values from Controller method call
It iterates the return list value from controller and put in list. 
You can define your own attribute by replacing value, label.
In the following example, you can set attribute to firstname and lastname, but make sure in selection function Line 13, the right attribute is referenced, change ui.item.label to the right attribute ui.item.firstname.


1
2
3
4
5
6
7
 for (var n=0; n<lstresult.length; n++){
     var result = {
         firstname: lstresult[n],
         lastname: lstresult[n]
     };
     data.push(result);
 }

5.Add Controller Method

This is standard salesforce method. Don’t forget adding @RemoteAction annotation and setting method as Static.





1
2
3
4
5
6
7
8
9
    @RemoteAction
    public Static List<String> getAutoCompleteContact(String searchString)
    {
        List<String> result = new List<String>();

        ....

        Return return;
    }

Let's take a look how autocomplete looks like. Look pretty good!