In the Spring ’13 developer preview webinar, Pat Patterson and I went over a lot of the new and improved facets of the platform as of the newest API release. One of the topics I covered was Chatter in Apex, also known as the Connect API. Chatter in Apex, which is currently in developer preview and can be enabled in your DE upon request if you don’t already have it,  exposes Chatter API resources as objects in Apex in order to build native custom Chatter integrations on Force.com. Chatter in Apex extends current functionality of accessing Chatter data using Apex, and exposes Chatter data in a simpler way.

Building Your First Custom Feed

One easy to implement feature in Chatter in Apex is the ability to execute methods in the context of specific communites. Communities are a relatively new SObject to the Chatter schema, and facilitate aggregating chatter amongst different groups, like an internal chatter group and an external customer facing feed for example. Communities currently in pilot but are a huge part of Chatter in Apex. All Chatter in Apex methods include the communityId as a parameter, and execute methods in the context of: the default community (by specifying internal or null), a specific community (by using the community id), or nothing (by specifying null) if communities is not enabled for your org.

Reassembling a feed to brand or customize it traditionally took several SOQL queries, but with the Connect API it takes a single method call. From there you build a simple list which can be used in a repeat on a Visualforce page and picked apart however you would like to break it up. The method within the class would look something like the following:

global List<FeedItemInfo> getFeedItems() {
    ConnectApi.FeedItemPage feedPage = getFeed();

    List<FeedItemInfo> result = new List<FeedItemInfo>();
    for (ConnectApi.FeedItem item : feedPage.items) {
        result.add(new FeedItemInfo(item));
    }

    return result;
}

The FeedItemInfo class (a custom helper class example) uses various methods to assemble the Apex output to display @mentions, hashtags, embedded links, and other rich text in HTML. Using this returned result set, you have access to both FeedItems and their associated FeedComments. FeedItems are part of the FeedItem class, and these items roll up to a FeedItemPage class like you can see in the code above. From within each individual item, the comments property returns the first page of FeedComments (in the CommentPage class containing a collection of comments in the Comment class) for the given FeedItem. You don’t need to make a separate call to grab these comments because they are inherently connected from within the FeedItemInfo list elements. Below is an example of displaying the result set on a Visualforce page for both FeedItems and their associated FeedComments.

<apex:repeat value="{!feedItems}" var="feedItemInfo">
    <div class="feedItemOuterDiv">
        <div class="feedItemInnerDiv">
            <div class="feedPhotoDiv">
                <apex:image class="imageMargin" width="25" url="{!feedItemInfo.feedItem.photoUrl}"/>
            </div>
            <div class="feedItemBodyDiv">
                <b>{!feedItemInfo.userName}</b><br/>
                <apex:outputText value="{!feedItemInfo.formattedText}" escape="false"/>
            </div>
            <apex:outputPanel rendered="{!IF(feedItemInfo.imageUrl == null, false, true)}" >
                <div class="feedImageDiv" >
                    <apex:image class="imageMargin" width="100" url="{!feedItemInfo.imageUrl}"/>
                </div>
            </apex:outputPanel>
         </div>

         <apex:outputPanel rendered="{!IF(feedItemInfo.comments.size > 0, true, false)}">
             <div class="feedCommentOuterDiv">
                 <apex:repeat value="{!feedItemInfo.comments}" var="commentInfo">
                 <div class="feedCommentInnerDiv">
                     <div class="feedPhotoDiv">
                         <apex:image class="imageMargin" width="25" url="{!commentInfo.comment.user.photo.smallPhotoUrl}"/>
                     </div>
                     <div class="FeedCommentBodyDiv">
                         <b>{!commentInfo.userName}</b><br/>
                         <apex:outputText value="{!commentInfo.formattedText}" escape="false"/>
                     </div>
                     <apex:outputPanel rendered="{!IF(commentInfo.imageUrl == null, false, true)}" >
                         <div class="feedImageDiv" >
                             <apex:image class="imageMargin" width="100" url="{!commentInfo.imageUrl}"/>
                         </div>
                         <div style="clear: both;"/>
                     </apex:outputPanel>
                 </div>
                 </apex:repeat>
             </div>
         </apex:outputPanel>
     </div>
</apex:repeat>

If you build custom chatter applications with Chatter in Apex it is simple to not only build a feed, but also to style it with any custom branding by using custom CSS with style classes tied to the various HTML elements above. Life gets simpler on the back end of the chatter app as well. If you scroll through the documentation on the various classes, you’ll see that there are several methods geared towards replicating chatter functionality programmatically using the Connect API. In addition, you have more flexibility to build your own custom functionality like an aggregate of multiple feeds mashed into one giant feed by adding extra sort logic in the controller method demonstrated above.

Creating Rich Posts with Hashtags and Mentions

This is one of the other great benefits of building with Chatter in Apexyou can programmatically create all types of rich feed posts including @ mentions! I know what you’re thinking…#ORLY…#awesome…#iwannatry…To insert a post from the top of the feed, the procedure is very similar to a standard web form that you would create on a Visualforce page.

<apex:inputText value="{!feedItemInputText}" id="feedItemInput" class="inputBox" />
     <apex:commandLink styleClass="feedItemInputButton" reRender="feedOutputPanel" action="{!postNewFeedItem}" value="Post">
            <!-- Remember the feed context -->
            <apex:param name="communityId" value="{!communityId}" assignTo="{!communityId}"/>
            <apex:param name="feedId" value="{!feedId}" assignTo="{!feedId}"/>
            <apex:param name="feedType" value="{!feedType}" assignTo="{!feedType}"/>
     </apex:commandLink>

The commandLink above is connected to a few variables within the controller for recognizing the context of the feed. The post uses the standard Connect API call for posting the FeedItem to the Chatter feed, but a separate method for parsing out message segments. Message segments are content elements in a text body, and MessageSegment is the super class for adding rich message segments to FeedItems and FeedComments containing hashtags, links, mentions, or plain text. When the user clicks to ‘Post’ on the page above, the controller takes the input and sends it to a custom method (addMessageSegment) to parse out mentions. It stores these parsed out values as message segments for the post method on line 8.

 global void postNewFeedItem() {
        ConnectApi.FeedItemInput feedItemInput = new ConnectApi.FeedItemInput();
        ConnectApi.MessageBodyInput messageBodyInput = new ConnectApi.MessageBodyInput();
        feedItemInput.body = messageBodyInput;
        addMessageSegments(messageBodyInput, feedItemInputText);

        ConnectApi.FeedType targetFeedType = FeedFormatter.getFeedType(feedType);
        ConnectApi.ChatterFeeds.postFeedItem(communityId, targetFeedType, feedId, feedItemInput, null);
 }

 global static void addMessageSegments(ConnectApi.MessageBodyInput messageInput, String text) {
        List<ConnectApi.MessageSegmentInput> segments = messageInput.messageSegments;

        // Break out @-mentions like [First Last] from input text.
        Integer openBracket = text.indexOf('[');

        //if #1: no [ implies no @[first last]
        if (openBracket == -1) {
            ConnectApi.TextSegmentInput textSegment = new ConnectApi.TextSegmentInput();
            textSegment.text = text;
            segments.add(textSegment);
        }

        else {
            Integer nextInput = 0;
            while (openBracket != -1) {
                Integer closeBracket = text.indexOf(']', openBracket);
                //if #2: no ] means there are no more @[first last] to identify
                if (closeBracket == -1) {
                    break;
                }

                //if #3: grab all text before the [ and store as a text segment
                if (openBracket > nextInput) {
                    ConnectApi.TextSegmentInput beforeText = new ConnectApi.TextSegmentInput();
                    beforeText.text = text.substring(nextInput, openBracket);
                    segments.add(beforeText);
                }

                //if #4: grab text between [ ] and query for user
                if (closeBracket - openBracket > 0) {
                    String userName = text.substring(openBracket + 1, closeBracket);
                    User theUser = [SELECT Id FROM User WHERE Name = :userName];
                    ConnectApi.MentionSegmentInput mention = new ConnectApi.MentionSegmentInput();
                    mention.id = theUser.id;
                    segments.add(mention);
                }

                //check for more @[first last] after the one we just identified
                nextInput = closeBracket + 1;
                openBracket = text.indexOf('[', nextInput);
            }

            //if #5: grab all text after the last mention and store as a text segment
            if (nextInput < text.length() - 1) {
                ConnectApi.TextSegmentInput endText = new ConnectApi.TextSegmentInput();
                endText.text = text.substring(nextInput);
                segments.add(endText);
            }
        }
    }
}

There are two important assumptions I am making with the above code:

  1. I am assuming that the end user with this interface will insert a mention using the exact '@[firstname lastname]' notation. This simple example covers parsing and creating segments, but it does not include automated formatting to reduce user error.
  2. I am assuming that the user being entered will correspond to a user record in my org. In production, a smart lookup or some other similar functionality would be ideal to help the end users autocomplete mentions.
Working off this example, you could iterate further into the text segments being created to check for other message segments like hashtags or links as well and parse those out in a similar fashion. It is not necessary to parse out hashtags as the platform will automatically parse the hashtags out when you post a text segment. If you do parse out hashtags into segments, however, the platform will put spaces around them (if they are missing) to ensure that Chatter picks them up. This example aims to show how to take a user input and build it appropriately to post a FeedItem programmatically including rich segments like a mention.

Try it out!

Looking to build your own social application or page on Force.com? Check out these fully baked code recipes and the documentation page to get started building your own custom chatter apps. Chatter in Apex is still in developer preview, and it will have more updates and additions when it becomes generally available, but if you want to test it out now and you don't see it in your DE, contact salesforce to get it enabled for your DE org or spin up a new DE to check it out.

tagged , , , , , , , , Bookmark the permalink. Trackbacks are closed, but you can post a comment.
  • http://www.facebook.com/pandeyharshit Harshit Pandey

    Thanks for sharing :-)

  • Ankur

    Thanks. Article is very nice. I have a query can we create mix of text and link post. Ex Case 4545454 is assigned to you. Here I want 4545454 as link for case.