Chatter Code Recipes

Abstract

Salesforce Chatter provides an extensive set of collaboration features and capabilities to applications built on the Force.com platform. This article presents a series of recipes, or code samples, covering the primary use cases a developer may encounter when customizing Chatter. If you're new to Chatter, begin by reading An Introduction to Salesforce Chatter and the Chatter Resource Page.

Introduction

Chatter provides a set of collaboration features that transform the way data, applications and people communicate within a company. Like any other aspect of Force.com, it can be extended, customized, and made your own. One of the best ways to get started customizing any application is by seeing what other people have done. For developers, code samples, or recipes as they are often referred to, are a great way to share these samples and best practices.

This article describes several recipes covering the major activities a developer may need when customizing Chatter.


Recipe 1: Update A User's Status

Problem

I want to update my status using either Apex or from within a custom Visualforce page.

Discussion

User Status updates are managed through the CurrentStatus field on the User object. Updating the User.CurrentStatus also updates the User.CurrentStatusLastModified timestamp, which is used on the People tab, and is displayed on the User Profile page.

Example Code

Visualforce Page

 
<apex:inputText value="{!status}" id="status" maxlength="100" /> 
<apex:commandLink style="button" value="Update User Status" action="{!doUserStatus}" />

Custom Controller

   
public string status     { get; set; }

public PageReference doUserStatus()   {
       User user = [select id, CurrentStatus from User where id = :UserInfo.getUserId()];
       user.CurrentStatus = status;
       update user;
       return null;
}


Recipe 2: Display a User's Profile Image

Problem

I want to display a User's profile image on my custom Chatter page (or anywhere else).

Discussion

You can access the current user profile image via either of the following fields on the User object, if Chatter is enabled:

  1. FullPhotoUrl—The URL for a user's profile photo.
  2. SmallPhotoUrl—The URL for a thumbnail of a user's profile photo.

Once received, displaying the profile image is simply a matter of adding the URL to an <apex:image> tag.

Example Code

Visualforce Page


<apex:image id="profileImage" url="{!profileImageUrl}" />

Custom Controller

public string profileImageUrl    { get; set; }
	
profileImageUrl = [select FullPhotoUrl from User where Id = '1234'];


Recipe 3: List a User's Followers

Problem

I want to retrieve a list of all the users who are following me (or a record).

Discussion

The solution to this problem depends on where and how you need to display the list of followers. If you're developing a custom Visualforce page that needs to display such a list with the same look-and-feel as what's displayed in the standard Chatter app, then the answer is very simple. Just drop in either the <chatter:feedWithFollowers> or <chatter:followers> standard Visualforce tag (depending on whether or not you also want to display the Chatter feed) into the appropriate place in the Visualforce markup. This will result in the standard Chatter followers component being displayed in your Visualforce page. Both tags require a 'entityId' attribute which maps to the Id for the user or record for whom you want to display followers.

If on the other hand, you want to display this list of followers in a custom UI (say on a mobile device), or if you need this data for some other purpose, you can perform a simple SOQL query on the EntitySubscription object. EntitySubscription is a junction object between the subscriber (subscriberId) and the user/record they're following (parentId). Therefore to get the list of followers, the SOQL query just has to find all EntitySubscription records where the parentId is the user/record in question.

Code Example

Visualforce tags

<chatter:followers entityId="User Id or record Id"/>
<chatter:feedWithFollowers entityId="User Id or record Id"/>

SOQL query

   List<EntitySubscription> followers = 
        [select id, subscriberid, subscriber.name
         from EntitySubscription
         where parentid =:uid]; // the id of the user/record for which you want to list followers
         
   Integer noOfFollowers = followers.size();


Recipe 4: List who a User is Following

Problem

I want to see all the Users that I'm following.

Discussion

The standard <chatter:feedWithFollowers> or <chatter:followers> Visualforce tags only display the list of followers, not the list of users/records that a user is following. Therefore the only solution is to run a SOQL query against the EntitySubscription object. This recipe is almost identical to Recipe 3 with two differences:

  1. instead of setting the parentid to the user (which we do when we want to retrieve all their followers), we need to set the subscriberid to the user id of the user who we want to find the followers of, and
  2. it limits the results of who I am following to Users only, and not objects such as Accounts, Contacts, or any custom Objects.

Whoa, hold up a minute---has all this talk of parentids and subscriberids got you confused yet? Don't worry, it's pretty logical when you break it down. Try thinking of the relationships like this:

  • I (parentid) have people (subscriberid) subscribed to my feeds.
  • I (subscriberid) am also subscribed to other peoples (parentid) feeds.

Code Example

SOQL query

// Everyone you're following
EntitySubscription[] followingES = [select id, parentid, subscriberid, parent.name 
                                    from EntitySubscription
                                    where subscriberid =:uid];//Set to the User's Id

List<EntitySubscription> following = new List<EntitySubscription> ();
String userSObjectPrefix =  User.sObjectType.getDescribe().getKeyPrefix();
for( EntitySubscription es: followingES )
{
      if( ('' + es.parentid).substring(0,3) == 
                        userSObjectPrefix) // users only
      {
             following.add(es);
      }       
}
Integer followingUserCount = following.size();


Recipe 5: Follow a User or record

Problem

I want to follow another user or record (e.g. Account, Opportunity, Custom Object).

Discussion

Like Recipe 3, the solution to this problem depends on the context. If you'd like to simply recreate the standard Chatter follow functionality (with the green '+' icon) on a Visualforce page, you can use the standard <chatter:follow> Visualforce tag. For all other use cases, you can follow a user or record by adding a record in the EntitySubscription object. Similarly, you can stop following a user or record by deleting the respective EntitySubscription record.

Code Example

Visualforce tag

<chatter:follow entityId="User or Record Id that you want to follow"/>

Apex code

EntitySubscription follow = new EntitySubscription (
                                 parentId = '<User Record Id you want to follow'
                                 subscriberid = :UserInfo.getUserId); //your User Id
insert follow;


Recipe 6: Add a FeedItem

Problem

I want to add a new post to an object's feed.

Discussion

A new post can be added to an object's feed by inserting a new FeedItem record and setting the parentId to the respective Record Id. Similarly, you can post to a user or group feed by setting the parentId to the respective User/Group Id.

You are required to set different fields on the FeedItem object depending on the type of post. The code sample below shows which FeedItem fields apply to which types of FeedItem posts. Note also that you're not required to set the 'Type' field of a FeedItem when inserting a new record. The system automatically assigns the correct type based on the FeedItem fields that you populate.

Note that the code samples only cover 3 FeedItem types - TextPost, LinkPost and ContentPost. UserStatus type posts are indirectly inserted by updating the 'CurrentStatus' field on a User record as shown in Recipe 1. TrackedChange type posts are created automatically by the system based on the Chatter Feed Tracking configuration of the Org and cannot be manually inserted.

Code Example

Apex code

//Adding a Text post
FeedItem post = new FeedItem();
post.ParentId = oId; //eg. Opportunity id, custom object id..
post.Body = 'Enter post text here';
insert post;

//Adding a Link post
FeedItem post = new FeedItem();
post.ParentId = oId; //eg. Opportunity id, custom object id..
post.Body = 'Enter post text here';
post.LinkUrl = 'http://www.someurl.com';
insert post;

//Adding a Content post
FeedItem post = new FeedItem();
post.ParentId = oId; //eg. Opportunity id, custom object id..
post.Body = 'Enter post text here';
post.ContentData = base64EncodedFileData;
post.ContentFileName = 'sample.pdf';
insert post;


Recipe 7: Add a Comment to a FeedItem

Problem

I want to add a comment to a Chatter post.

Discussion

Adding a comment to a FeedItem is very similar to adding a new FeedItem. The biggest difference is that we're now inserting a FeedComment record and we need to know the Id of the FeedItem on which we we want to leave the comment. Bare with me in this recipe and just assume we know how to get the FeedItem Id. We will cover that in the next two recipes in this article.

Code Example

Apex code

FeedComment fcomment = new FeedComment();
fcomment.FeedItemId = fId; //Id of the FeedItem on which you want to comment
fcomment.CommentBody = 'Enter your comment here';
insert fcomment;


Recipe 8: Display my News Feed

Problem

I want to display all of the posts in my News Feed including any comments, likes and mentions other users have left. This would basically recreate the list of updates that I see on the Home tab.

Discussion

There is a lot going on in this recipe: we are going to retrieve all of the FeedItems in our NewsFeed, including any related comments and likes for each post. In the example below, we are going to query all NewsFeed Items regardless of type. If you wanted to limit your results to a particular Feed Type, you can add a clause WHERE Type = 'Whatever type you want to limit by. e.g: TextPost'.

You will notice that we are limiting the results to the last twenty FeedItems. It is important to limit NewsFeed queries (and most Chatter queries for that matter) given the potentially large data volume associated with a user's Chatter feed. In fact, when querying from NewsFeed and UserProfileFeed, it is required (unless the user has 'View All Data' permission). If you want to paginate through the results and return more, you will need to handle this by creating a wrapper object with next and previous functions.

Another point of note is the use of the ORDER BY clause in the query. You typically want to display a NewsFeed is reverse chronological order (i.e. latest post first) - the same way that the standard Chatter app displays it. Hence the use of the 'ORDER BY CreatedDate, Id DESC' in the outer query. FeedComments however are typically displayed in chronological order (i.e. first comment first) - again the same way that the standard Chatter app displays it. The FeedComment inner query therefore includes the 'ORDER BY CreatedDate' clause.

You might have also noticed the inclusion of the 'Id DESC' clause in the query. Adding this sort order, while of no consequence to the query result, significantly improves the performance of Chatter feed queries. It is therefore a general best practice to include 'Id DESC' sort order in all Chatter feed queries.

Code Example

Apex code

List<NewsFeed> myfeed = [SELECT Id, Type, 
                         CreatedById, CreatedBy.FirstName, CreatedBy.LastName,
                         ParentId, Parent.Name, 
                         Body, Title, LinkUrl, ContentData, ContentFileName,
                             (SELECT Id, FieldName, OldValue, NewValue 
                              FROM FeedTrackedChanges ORDER BY Id DESC), 
                             (SELECT Id, CommentBody, CreatedDate,
                              CreatedBy.FirstName, CreatedBy.LastName
                              FROM FeedComments ORDER BY CreatedDate LIMIT 10),
                             (SELECT CreatedBy.FirstName, CreatedBy.LastName
                              FROM FeedLikes)
                         FROM NewsFeed
                         ORDER BY CreatedDate DESC, Id DESC
                         LIMIT 20];


Recipe 9: Display a record's entity feed

Problem

I want to display the entity feed for an Account record.

Discussion

This recipe is very similar to Recipe 8 with the difference being that instead of looking at a user's news feed, you're interested in a record's entity feed. This is the same list that appears at the top of every record's detail page in the standard Salesforce.com application. The sample below queries the AccountFeed object, but the code can just as easily be applied to any standard or custom object's entity feed (e.g. OpportunityFeed, My_Custom_Object__Feed etc.).

Code Example

Apex code

List<AccountFeed> myfeed = [SELECT Id, Type, 
                         CreatedById, CreatedBy.FirstName, CreatedBy.LastName,
                         ParentId, Parent.Name, 
                         Body, Title, LinkUrl, ContentData, ContentFileName,
                             (SELECT Id, FieldName, OldValue, NewValue 
                              FROM FeedTrackedChanges ORDER BY Id DESC), 
                             (SELECT Id, CommentBody, CreatedDate,
                              CreatedBy.FirstName, CreatedBy.LastName
                              FROM FeedComments ORDER BY CreatedDate LIMIT 10),
                             (SELECT CreatedBy.FirstName, CreatedBy.LastName
                              FROM FeedLikes)
                         FROM AccountFeed
                         WHERE ParentID = '<Account Id>'
                         ORDER BY CreatedDate DESC, Id DESC
                         LIMIT 20];


Recipe 10: Who has a particular file been shared with

Problem

You want to display the list of users that a particular file is shared with. This is the same list that appears in the 'Shared With' section of the file detail page.

Discussion

As a key component of any collaboration tool, Chatter allows users to share files and content. You can either share a file publicly with all Chatter users, or privately with selected users. The ContentDocumentLink object represents the link between a Salesforce CRM Content document or Chatter file and where it's shared.

Note that you can perform the reverse query (i.e. find all files that a user/group/record has shared) by filtering on LinkedEntityId instead of ContentDocumentId.

Code Example

Apex code

List<ContentDocumentLink> shares = [SELECT id, LinkedEntityId, ContentDocumentId 
                                    FROM ContentDocumentLink
                                    WHERE ContentDocumentId = '<File Id>'];


Recipe 11: Monitor specific keywords on an entity feed

Problem

You want to take an action every time someone posts a specific keyword(s) to an entity feed. For example, you may want to perform an action on the record based on a special keyword (e.g a '!close' post closes an Opportunity).

Discussion

You can create Apex triggers on the FeedItem and FeedComment objects, letting you trigger real time business actions every time that someone posts to a user or entity feed or when someone comments on an existing post.

The above requirement can therefore be met by writing a trigger on the FeedItem object as shown in the code sample. A couple of things to highlight with Chatter triggers. First, notice that you write the trigger on the FeedItem object. You can't write triggers on the UserProfileFeed, NewsFeed or AccountFeed/OpportunityFeed etc. objects. That means that the trigger can fire as a result of several different actions - someone posting to a user's feed or someone posting to the entity feed of any object for which Chatter is enabled (Account, Opportunity, custom object etc.). Therefore, if your trigger code is only applicable to a specific user or entity feed (in the sample below, Opportunity), you need to filter for those posts in the trigger code. The best way to do so is to compare the prefix of the polymorphic 'parentId' field on the FeedItem object (which would map to the user/group/record for which the Chatter post was added) with the key prefix of the entity that you're interested in.

Another point of note is the events that can trigger (no pun intended) a FeedItem or FeedComment Trigger. Since Chatter posts/comments can only be inserted or deleted (they cannot be updated or undeleted by a user), the triggers only support the 'insert' and 'delete' events.

A final note. FeedItems of type 'UserStatus' and 'TrackedChange' don't invoke the triggers. In other words, if a user updates his/her own status in Chatter or if a tracked field changes value, those events won't fire any triggers. Instead, you can write a trigger on the respective SObject if you want to act on a change to a field value and you can write a trigger on the User object (as demonstrated in the next recipe) for taking some action based on a user updating his/her Chatter status.

Code Example

Apex code

trigger CheckChatterPostsOnOpportunity on FeedItem (before insert) {
    //Get the key prefix for the Opportunity object via a describe call.
    String oppKeyPrefix = Opportunity.sObjectType.getDescribe().getKeyPrefix();
    
    for (FeedItem f: trigger.new)
    {
        String parentId = f.parentId;
        //We compare the start of the 'parentID' field to the Opportunity key prefix to
        //restrict the trigger to act on posts made to the Opportunity object.
        if (parentId.startsWith(oppKeyPrefix) && 
            f.Body == '!close')
        {            
            //Add business logic here
        }
    }
}


Recipe 12: Monitor user status updates in Chatter

Problem

You want to take an action every time someone updates their status in Chatter.

Discussion

This is a similar requirement to the one in Recipe 11, except that we now have to monitor a user's status updates instead of monitoring an entity feed. If you remember Recipe 1 (I know, that feels eons ago) a user's Chatter status is stored in the 'CurrentStatus' field of the User object. Therefore updates that a user makes to his/her own status (i.e. when s/he writes something in the 'What are you working on?' text box) will fire triggers on the User object (and not the FeedItem object).

Note that the problem described in this recipe is somewhat contrived and incomplete. In most cases, you would be required to monitor all Chatter posts - i.e. user status updates as well as a user posting to another user/entity/group feed. Such a requirement would require two triggers. One on the User object (as shown below) and another on the FeedItem object (similar to the one shown in Recipe 11).

Code Example

Apex code

trigger MonitorUserStatusUpdates on User (before update) {
    for (Integer i = 0; i < trigger.new.size(); i++)
    {
        if (trigger.old[i].CurrentStatus != trigger.new[i].CurrentStatus)
        {
            //User has changed their Chatter status - take necessary action here.
        }
    }
    
}


Recipe 13: Integrate Chatter with other social networks OR Migrate Chatter data from one Org to another

Problem

You want to develop an integration between Chatter and an external social network (LinkedIn, Twitter, Facebook etc.) whereby a user's Chatter feed is updated (i.e. Chatter posts and comments are created) based on updates in another social network.

The same recipe also applies to another, unrelated, use case - that of migrating existing Chatter data from one Salesforce Org to another.

Discussion

This recipe will not discuss the mechanics of integrating with another social network like LinkedIn, Twitter etc. since those details vary based on the network and the API/security protocol that it supports. Instead this recipe focuses on how best to create Chatter posts and comments in a integration/migration use case.

At first blush, it may seem that creating posts and comments in a Chatter integration/migration use case is no different than what has already been covered in Recipes 6 and 7. FeedItem and FeedComment records have to be created in both cases**. There is however a subtle but important nuance that's specific to Chatter integration/migration use cases. In such cases, you want to manually set the 'CreatedById' and 'CreatedDate' audit fields instead of letting the system automatically assign them based on the logged-in user.

For Chatter integration/migration use cases, the logged-in user should have the 'Insert System Field Values for Chatter Feeds' user permission enabled for their profile. That allows you to manually set the CreatedById and CreatedDate system fields for the FeedItem, FeedComment and FeedLike objects to match the original post author and creation date/time. That solves one problem, but raises another. If you manually set the standard system audit fields for Chatter objects during integration/migration, you lose all audit trail of who triggered the integration/migration. Such audit information may be critical for regulatory or other reasons. Enter 'InsertedById'. This field on the FeedItem, FeedComment and FeedLike objects is the ID of the user who added this object to the feed. This field is automatically set by the system to the logged-in user and cannot be updated by any user (even one with the 'Insert System Field Values for Chatter Feeds' user permission). In most cases, the 'InsertedById' will be the same as the 'CreatedById' (both automatically assigned by the system) but the values may be different in an integration/migration use case.

**Note: In the case of migrating existing Chatter data from one Salesforce Org to another, you are likely to use a data manipulation tool like the Apex Data Loader or an 3rd party ETL tool instead of writing Apex code.

Code Example

Apex code


//Note that this code can only be executed by a user with the 'Insert System Field Values
//for Chatter Feeds' permission
FeedItem post = new FeedItem();
post.ParentId = oId; //eg. Opportunity id, custom object id..
post.Body = 'Enter post text here';
post.CreatedById = <Salesforce User Id of the original post author>;
post.CreatedDate = <Date and time of original post>;
insert post;

FeedItem f = [select InsertedById from FeedItem where id =:post.Id];
System.assertEquals (f.InsertedById, UserInfo.getUserId());

Summary

This article provides several recipes for customizing Chatter. The recipes cover the primary requirements a developer may need when using the Chatter data model. Most Chatter development will be some variation of one or more of the above recipes and therefore this article is a good jumping off point for Chatter developers.

Related Links

About the Author

Quinton Wall is a Developer Evangelist at Salesforce.com, and a published fantasy author. He is a regular speaker at cloud events, contributor to the developer blogs, and twittersphere. When he is not working with the Force.com platform, or writing books, you can find him on the web.

Sandeep Bhanot is a Developer Evangelist at Salesforce.com. When not working on the Force.com platform, he spends most of his time reading Quinton's fantasy books.

Thanks to Rob Woollen, and Jon Mountjoy for keeping us honest---again.