Social Applications - Development with the Force.com Platform: Building Business Applications in the Cloud, Third Edition (2014)

Development with the Force.com Platform: Building Business Applications in the Cloud, Third Edition (2014)

12. Social Applications

This chapter introduces Chatter, a layer of functionality that spans all Salesforce applications and the Force.com platform. Chatter provides the means for users to communicate with each other in the context of the applications and data central to their work, privately and entirely internal to their company. It is delivered securely to their Web browsers and most mobile devices. In adopting Chatter, Salesforce customers, partners, and application developers gain the best features of consumer services such as Facebook that form a social glue that makes interacting at work a compelling, relevant, and professional experience.

Chatter is a collection of collaboration features, including user profiles, forums, polls, questions and answers, file sharing, and private messaging. This chapter focuses on the integration of the most basic Chatter features into custom applications. Brief descriptions of its sections follow:

Image Overview of the Chatter data model—The heart of Chatter is the data model, standard objects in the Force.com database that allow any application to participate in the conversation and automate Chatter interactions. Once you have an understanding of its data model, incorporating Chatter into your Apex programs is straightforward.

Image Using Chatter in Apex—Although the Chatter data model is available, it’s the lowest-level way to access Chatter features. Chatter in Apex is a built-in library that provides Chatter features as first-class Apex classes.

Image Introduction to the Chatter REST API—The Chatter REST API is valuable for integrating Chatter into applications residing outside of the Force.com platform. Like Chatter in Apex, it hides implementation details of Chatter that would otherwise be exposed by direct access to the data model.

Image Working with Chatter Visualforce components—Learn how to add Chatter functionality to your custom user interfaces with minimal effort using standard Visualforce components.

Image Sample application—Modify the Services Manager sample application to make staying in touch with resources on a project team using Chatter easy.


Note

The code listings in this chapter are available in a GitHub Gist at http://goo.gl/FfsbSo.


Overview of the Chatter Data Model

Chatter posts, comments, and the list of records followed in Chatter are stored in standard database objects, accessible in SOQL, SOSL, Apex code, the Web Services API, and generally anywhere you need them. With this developer-friendly approach, you can build any number of interesting Chatter-aware programs. You can automatically follow a set of records based on user actions, batch process posts and comments to identify patterns, build an alternative user interface for Chatter, and even extend Chatter outside of your organization by integrating it with external applications.

After you have a good grasp of the data model, all of these scenarios are trivial to implement on the platform. But compared with the standard platform objects such as Contacts and Accounts, Chatter has a slightly more complex data model, including objects with some distinctive qualities, summarized here:

Image Dynamic—The objects in the Chatter schema can appear and disappear based on the Chatter configuration. For example, when Chatter is disabled in an organization, the Chatter objects are completely hidden, as if they never existed. Also, objects containing Chatter posts are dynamically created when Chatter is enabled for a custom object.

Image Relationship-rich—The whole purpose of Chatter is to link social and business data, so Chatter objects consist primarily of foreign keys to other objects.

Image Designed for high volume—Chatter objects usually do not allow records to be updated. Some objects can’t even be queried directly and must be referenced indirectly from a parent object.

This section introduces you to the Chatter data model by exploring these four areas:

Image Chatter posts—Learn how to query, create, and delete the three main types of Chatter posts, based on the parent record’s object type.

Image Feed-tracked changes—Feed-tracked change records are created automatically by Force.com to provide an audit trail of database activity. They can be queried but never directly created, updated, or deleted.

Image Chatter comments—You can query, create, and delete Chatter comments, given a parent post.

Image Followed records—Get a list of followers for a record, and follow and unfollow records by creating and deleting simple Chatter configuration records.

Chatter Posts

Chatter posts are stored using a series of relationships that follow a common pattern, illustrated in Figure 12.1. Starting from the right of the diagram, a Feed object, suffixed with the word Feed, contains Chatter posts. Feed objects exist for each Chatter-enabled parent object type. The parent object is on the left, and the line between them indicates that a single parent record can have zero to many posts.

Image

Figure 12.1 Chatter post schema pattern


Note

Feed objects are unusual for Force.com in that they are read-only. To insert or delete Chatter posts, you must use the generic FeedItem object, discussed later in this chapter.


The Feed objects appear and disappear based on the Chatter configuration. For example, if Chatter is enabled on the Project__c custom object, then an object named Project__Feed exists, the object used to store posts related to Projects. If Chatter is later disabled for Project__c, the Project__Feed object is removed from the Force.com database.

The five types of post content, indicated by the Type field of the Feed objects, are described here:

Image Text (TextPost)—This is the default type of Chatter post. It contains plaintext, with no HTML markup or rich formatting allowed. The text is contained in the Body field. The sample code in this chapter focuses on the text post type because the other post types behave almost identically, differing only on the fields used to store data.

Image URL (LinkPost)—The Chatter user interface allows you to attach a single URL to a post, which appears immediately below the post text. The URL value is stored in the LinkUrl field, with the URL label in Title.

Image File (ContentPost)—From the Chatter user interface, you can select a file to attach to a post. The file can be a reference to another Chatter-attached file or uploaded from your local computer. The file content is base-64 encoded and placed in the ContentData field. Several additional file-related metadata fields are also stored with the file: ContentFileName and ContentDescription (input by the user during upload), ContentType (file MIME type), and ContentSize (file size in bytes).

Image Field change (TrackedChange)—This post type is relevant only to feed-tracked changes. It is generated by Force.com itself and cannot be created by users or programs.

Image Status update (UserStatus)—Chatter users can change their status from their profile page or any Chatter user interface. This action triggers Force.com to insert a status update Chatter post, with the Body field set to the new status.

The remainder of this subsection contains SOQL queries and Apex code snippets to demonstrate how to work with posts and their parent feed objects. They are organized into the following four scenarios:

Image Standard object feeds—When Chatter is enabled for an organization, most standard objects have corresponding Chatter feeds.

Image Custom object feeds—Every custom object that is Chatter-enabled by the administrator has its own feed.

Image User feeds—Separate feeds exist for the Chatter user profile as well as the standard User object.

Image Home tab feed—The Home tab has its own feed, called NewsFeed. This contains a collection of all the activity in followed records.


Caution

Understanding posts and feeds is critical because the rest of the section builds upon this knowledge.


Standard Object Feeds

When Chatter is enabled for an organization, feed objects exist for every standard object that supports Chatter. Listing 12.1 is an example of retrieving the ten most recent Chatter posts on the Contact object using the ContactFeed object.

Listing 12.1 Chatter Query on Standard Object


SELECT ParentId, Body, Type, CreatedBy.Name, CreatedDate
FROM ContactFeed
ORDER BY CreatedDate DESC LIMIT 10


To create a post on the Contact object, you need the Id of a Contact record to serve as the parent of the post. This Id becomes the ParentId column in FeedItem. Force.com takes care of determining which feeds the post belongs to based on the type of object referenced by theParentId. This means you can use the same code to create posts regardless of the type of object you’re posting about.

The sample code in Listing 12.2 contains a method for creating a Chatter post. Pass it the Id of a Contact record in the recordId argument, and the text of the post body in the text argument. Make a note of the return value because it is used later to remove the post.

Listing 12.2 Creating a Chatter Post


public Id post(Id recordId, String text) {
FeedItem post = new FeedItem(ParentId = recordId, Body = text);
insert post;
return post.Id;
}



Tip

You can quickly test the method in Listing 12.2 using the Execute Anonymous feature in the Developer Console or the Force.com IDE. For example: Id i = post([SELECT Id FROM Contact LIMIT 1].Id, 'test');


Unlike creating posts, the code to delete posts is object-specific, not generic. It requires the specific feed object containing the post to be known. For example, if you created a post with a Contact record as the ParentId, delete the post from the ContactFeed object, as shown in Listing 12.3.

Listing 12.3 Deleting a Chatter Post


public void deleteContactPost(Id postId) {
ContactFeed post = [ SELECT Id FROM ContactFeed
WHERE Id = :postId ];
delete post;
}


Custom Object Feeds

Chatter posts on custom objects behave identically to standard objects, with two exceptions. The naming scheme for the feed objects is slightly different, and a feed object does not exist until Chatter is enabled on the custom object. For example, if you enable Chatter on the Project__cobject, the Project__Feed Chatter object becomes available.

Listing 12.4 demonstrates a query for posts on the Project__c object. As you can see, the columns are identical to that of the standard feed, but the FROM clause refers to the Project__c-specific feed object. To get any feed object’s name, strip the __c from the end of your custom object’s API name and then add the __Feed suffix. You can follow this pattern to access the posts of any custom object.

Listing 12.4 Chatter Query on Custom Object


SELECT ParentId, Body, Type, CreatedBy.Name, CreatedDate
FROM Project__Feed



Note

The procedure for creating and deleting Chatter posts in custom objects is identical to that of standard objects.


User Feeds

Two feeds contain user-related Chatter posts:

Image UserFeed—UserFeed contains feed-tracked changes for fields on your User object, as well as posts by other users on your profile. You cannot query another user’s UserFeed unless you log in to Force.com as that user.

Image UserProfileFeed—The UserProfileFeed is a superset of the UserFeed. It includes Chatter from other objects followed by the user, such as groups. It requires the use of the Chatter REST API to query it, described later in this chapter.

The SOQL in Listing 12.5 returns the Chatter posts for the current user, the user logged in to Force.com and executing the query.

Listing 12.5 Chatter Query on UserFeed


SELECT ParentId, Id, Type, CreatedById, CreatedDate
FROM UserFeed



Note

The procedure for creating and deleting Chatter posts in UserFeed is identical to that of standard objects.


News Feed

If you’ve experimented with Chatter in the Force.com user interface, you might have noticed that the Home tab aggregates all the posts and comments you follow in one place. The Chatter appearing on the Home tab is accessible only via the Chatter REST API.

Chatter Comments

The handling of Chatter comments is slightly different from that of other Chatter data. Comment data is stored in a single, large object called FeedComment that cannot be queried directly. The Feed object becomes a junction object, associating Chatter posts to the subject of the post and zero or more comments. This three-way relationship is shown in Figure 12.2, with the left side the parent of the post and the right side the list of comments.

Image

Figure 12.2 Chatter comment schema pattern

The relationship between the Feed junction object and the FeedComment object is called FeedComments. Listing 12.6 provides an example of querying it. The result is all the posts in the Project__c custom object feed and all of the comments for each post.

Listing 12.6 Chatter Query for Comments


SELECT ParentId, Type, CreatedById, CreatedDate, Body,
(SELECT CommentBody, CreatedById, CreatedDate FROM FeedComments)
FROM Project__Feed


To create a comment, insert a record into the FeedComment object. Listing 12.7 provides a sample method for doing this. To test it, you need the Id value of a record in a Feed object. For example, if you want to add a comment to an Account post, get the Id of the post to comment on from the AccountFeed object. This Id value is then passed into the method as the first argument, postId. The second argument is the text of the comment to create. Save the postId and the value returned by this method, as these are needed to delete the comment.

Listing 12.7 Creating a Chatter Comment


public Id comment(Id postId, String text) {
FeedComment comment = new FeedComment(
FeedItemId = postId, CommentBody = text);
insert comment;
return comment.Id;
}


You cannot update a FeedComment record, but you can delete it. Like with deleting posts, deleting comments is tricky because you cannot directly query the FeedComment object to retrieve the record to delete. If your program creates or queries FeedComment records and can keep them around in a cache, that is ideal. If this is not possible, you must query the FeedComment object in order to delete it.

Listing 12.8 shows a sample method for deleting a comment by querying it first via its parent post. To use it, you must pass the FeedItemId of the parent post in the Project__Feed object as the postId, and the Id of the FeedComment record as commentId, returned by the comment sample method. Although this example operates on comments in Project__Feed only, the same pattern can be applied to comments in all feeds.

Listing 12.8 Deleting a Chatter Comment


public void deleteComment(Id postId, Id commentId) {
Project__Feed post = [ SELECT Id,
(SELECT Id from FeedComments WHERE Id = :commentId)
FROM Project__Feed WHERE Id = :postId ];
delete post.FeedComments[0];
}


Feed-Tracked Changes

Feed-tracked changes provide an audit trail of modifications to a set of fields. For each record in an object that has feed-tracked changes enabled, there can be many corresponding feed-tracked change records. Each change record captures the original field value, the new field value, the field name, and the new and old currencies if multicurrency is enabled in the organization and the field is a currency type.

The change records for all objects in an organization with feed-tracked changes enabled are stored in a single object called FeedTrackedChange. The schema pattern for this object is illustrated in Figure 12.3.

Image

Figure 12.3 Chatter feed-tracked changes schema pattern

FeedTrackedChange cannot be queried or modified in any way by any user, even an administrator. Like Chatter comments, it must be queried indirectly via its junction object. Listing 12.9 shows an example of querying all posts on Contact records and their corresponding FeedTrackedChange records.

Listing 12.9 Querying Chatter Feed-Tracked Changes


SELECT ParentId, Type, CreatedById, CreatedDate,
(SELECT FeedItemId, FieldName, OldValue, NewValue
FROM FeedTrackedChanges)
FROM ContactFeed


To see the query in action, enable feed-tracked changes on the Contact Phone field; then change the Phone value on a record and run the query. You should see a new record with a Type value of TrackedChange containing a nested FeedTrackedChange record. The nested record has the old and new Phone values along with the full field name, Contact.Phone. Had you changed two feed-tracked change fields within the same transaction, you would see two nested FeedTrackedChange records instead of one.

Followed Records

Users register interest in the Chatter activity of a record by clicking Follow icons in the Force.com user interface or by automatically following owned records. Users can follow other users as well as records in standard and custom objects. The information about followers is prominently displayed throughout the standard user interface, and used to email digests and notifications to users if Chatter is configured to do so.

All of this functionality hinges upon a single, simple object, called EntitySubscription. Its two important fields are ParentId, the record being followed, and SubscriberId, the Id of the user doing the following. For every record-to-user relationship in the organization, a unique record in EntitySubscription exists to express it.

With simple queries on the EntitySubscription object, you can retrieve a list of records followed by a user, or the users following a specific record. Less useful might be a query for the full set of following relationships in the entire organization, as shown in Listing 12.10.

Listing 12.10 Querying Chatter Following Relationships


SELECT ParentId, SubscriberId, CreatedById, CreatedDate
FROM EntitySubscription


To follow a record programmatically, insert a new ParentId and SubscriberId pair into the EntitySubscription object. Listing 12.11 provides a sample method to do this. Test it by passing in the Id of a record to follow and the Id of a User record to follow it.

Listing 12.11 Method for Following a Record


public Id follow(Id recordId, Id userId) {
EntitySubscription e = new EntitySubscription(
ParentId = recordId, SubscriberId = userId);
insert e;
return e.Id;
}


For example, call it with the Id of an Account record and your user’s Id value; then refresh the Account’s view page to see yourself instantly listed as a follower. Make a note of the Id value returned by the method. This is used later to unfollow the record.


Note

Each EntitySubscription record uniquely identifies a relationship between parent record and User record, so a runtime error is thrown if a new record matches an existing record’s ParentId and SubscriberId.


Unfollowing a record involves deleting the appropriate row in the EntitySubscription object that relates the record to the user. Listing 12.12 provides a sample method for doing just that. To use the method, pass the EntitySubscription record identifier returned by the follow sample method in Listing 12.11.

Listing 12.12 Method for Unfollowing a Record


public void unfollow(Id subscriptionId) {
delete [ SELECT Id FROM EntitySubscription
WHERE Id = :subscriptionId ];
}


Although this simple example can work, it’s unlikely that your program would possess the unique identifier of the EntitySubscription record. You could just as easily delete records on more readily available information, such as the EntitySubscription’s ParentId or SubscriberId.

Using Chatter in Apex

Although Chatter data is accessible in Apex using SOQL queries, Chatter in Apex provides a simpler solution. It consists of a series of Apex classes called ConnectApi that expose Chatter features in a simpler way, as an API rather than a data model. With Chatter in Apex, Chatter data is preformatted for display, and many features can be accessed with a single method call. Using the data model is typically not as easy or concise.


Note

For more information about Chatter in Apex, visit the online documentation at http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_connect_api.htm.


Listing 12.13 and Listing 12.14 are the Visualforce controller and page to display the current user’s feed items and comments. The Chatter in Apex getFeedItemsFromFeed method returns the posts and comments for the current user (the 'me' argument), and these are iterated over in the Visualforce page using nested repeat components.

Listing 12.13 Visualforce Controller for Chatter Example


public with sharing class MyPageController12_13 {
public List<ConnectApi.FeedItem> getFeedItems() {
return ConnectApi.ChatterFeeds.getFeedItemsFromFeed(null,
ConnectApi.FeedType.Record, 'me').items;
}
}


Listing 12.14 Visualforce Page for Chatter Example


<apex:page controller="MyPageController12_14">
<style>
img { margin: 4px; width: 25px; }
.actor { font-weight: bold; }
.comments { margin-left: 40px; }
</style>
<apex:repeat value="{!feedItems}" var="feedItem">
<div>
<apex:image url="{!feedItem.photoUrl}"/>
<span class="actor">{!feedItem.actor.name}</span>:
<span class="text">{!feedItem.body.text}</span>
<apex:outputPanel >
<apex:repeat value="{!feedItem.comments.comments}"
var="comment">
<div class="comments">
<apex:image url="{!comment.user.photo.smallPhotoUrl}"/>
<span class="actor">{!comment.user.name}</span>:
<span class="text">{!comment.body.text}</span>
</div>
</apex:repeat>
</apex:outputPanel>
</div>
</apex:repeat>
</apex:page>


Introduction to the Chatter REST API

The Chatter REST API provides access to Chatter functionality, including feeds, users, groups, followers, and files. Being a REST API, it can be integrated in Web, mobile, and desktop applications built in any technology that is capable of making HTTP requests. It is a valuable alternative to using the Chatter data model directly, hiding the details of how Chatter data is represented and offering a high-level API instead.


Note

For more information about the Chatter REST API, consult the Chatter REST API Developer’s Guide, found at http://www.salesforce.com/us/developer/docs/chatterapi/index.htm.


To get started with Chatter REST API, examine some examples of REST requests for common Chatter functionality. Like other REST examples in the book, the following three listings can be run from the command line. They assume you have an authorization token set in the TOKENenvironment variable, and that you replace the instance na15 with your own Salesforce instance.

Listing 12.15 requests the News Feed of the current user, which is the Chatter feed found on the Home tab. To request a different user’s News Feed, replace me with the user record’s unique identifier.

Listing 12.15 Sample Request for News Feed


curl https://na15.salesforce.com/services/data/v28.0\
/chatter/feeds/news/me/feed-items\
-H "Authorization: OAuth "$TOKEN -H "X-PrettyPrint:1"


Listing 12.16 returns a list of all of the records followed by the current user.

Listing 12.16 Sample Request for Followed Records


curl https://na15.salesforce.com/services/data/v28.0\
/chatter/users/me/following\
-H "Authorization: OAuth "$TOKEN -H "X-PrettyPrint:1"


To create a simple text-type feed post, follow the sample found in Listing 12.17.

Listing 12.17 Sample Request for Posting a Feed Item


echo '{ "body" : { "messageSegments" :\
[ { "type": "Text", "text" : "Hello world" } ] } }' |\
curl -X POST -H 'Content-type: application/json'\
-H "Authorization: OAuth "$TOKEN -H "X-PrettyPrint:1" -d @-\
https://na15.salesforce.com/services/data/v28.0\
/chatter/feeds/news/me/feed-items



Tip

To adapt the command in Listing 12.17 and other listings in this chapter to run in Windows Command Prompt, remove the single quotation mark characters (') in the echo statement, replace the single quotation mark characters around the Content-type header with double quotation mark characters ("), remove the backslash (\) line-continuation characters and concatenate the lines into a single line, and replace $TOKEN with %TOKEN%.


Working with Chatter Visualforce Components

When Chatter is enabled on an object, users viewing a record of that object see a rich user interface to manage posts and comments, followers, and their interest in following the record. This same native user interface functionality is also available to Visualforce developers. Using Chatter components, you can embed the same Chatter toolbar, in its entirety or in pieces, within your custom user interfaces.

Chatter is supported in Visualforce through eight dedicated components in the chatter namespace, and an additional Chatter-specific attribute on the generic detail component, as described here:

Image feed—This component renders a list of Chatter posts and comments for the selected record. It also provides a text box at the top for creating new posts. The selected record is specified using the entityId attribute.

Image feedWithFollowers—This component embeds the full Chatter toolbar. It includes the functionality of the feed component, and adds the list of followers to the right side, the Show/Hide Chatter buttons, and the Follow/Unfollow buttons.

Image feedWithFollowers—This component embeds the full Chatter toolbar. It includes the functionality of the feed component, and adds the list of followers to the right side, the Show/Hide Chatter buttons, and the Follow/Unfollow buttons.

Image newsFeed—Use this component to render the News Feed for the current user, the same feed data shown on the Home tab.

Image follow—Including this component on a page renders a Follow button if the user is not following the record and an Unfollow button otherwise.

Image followers—The followers component simply displays a list of users following the current record. Users are represented as thumbnail photos, which can be clicked to drill into their profiles.

Image showChatter—This attribute of the detail component, if set to true, includes the full Chatter toolbar at the top of the detail page.

Image userPhotoUpload—This component allows you to upload a photo for the current user’s Chatter profile.

To try one of the Chatter components, create a new Visualforce page that uses a standard controller. Pick an object that you know has Chatter enabled. Listing 12.18 shows a custom Project__c page that includes the feedWithFollowers component, and Figure 12.4 is the result of visiting the custom page. There are no posts, comments, or followers of the Project__c record, but the feedWithFollowers component has made creating and viewing all of these items using the standard Force.com-styled user interface possible.

Image

Figure 12.4 Output of Visualforce page with Chatter component

Listing 12.18 Visualforce Page with Chatter Component


<apex:page standardController="Project__c">
<apex:sectionHeader title="Project"
subtitle="{!record.Id}" />
<apex:pageBlock title="Chatter Components">
<chatter:feedWithFollowers entityId="{!record.Id}" />
</apex:pageBlock>
</apex:page>


You should be aware of a few gotchas with Visualforce Chatter components as you begin using them:

Image A Visualforce page cannot contain more than one of the five Chatter components at one time. If you attempt to use more than one, the page cannot be saved.

Image Chatter components cannot be added to a Visualforce page unless the API version of the page is at least 20.0. If the API version is set incorrectly, an Unknown Component error will prevent the page from being saved.

Image You cannot use Chatter components with Visualforce Sites. The Chatter components will be invisible to Sites users.

Sample Application: Follow Project Team

One of the initial challenges with using Chatter is building up a relevant set of records to follow. Salesforce’s automatic following of owned records is a good start. But users of your Services Manager sample application would like a quick-and-easy way to follow all the resources assigned to a consulting project.

This section walks through a sample implementation of a custom button called Follow Team, added to the Project object’s layout. The button launches a Visualforce page that uses the standard Project__c controller and a controller extension. Because the page is shown when the user clicks the button, the action attribute of the page invokes the custom controller code to perform the following logic immediately, without additional user action. The results of the following logic are displayed in a page message.

Following records in Chatter using Apex code involves adding records to the EntitySubscription object. The sample code in Listing 12.19 is the full controller extension implementation.

Listing 12.19 Controller Extension Code


public with sharing class FollowProjectControllerExtension {
private ApexPages.StandardController controller;
public FollowProjectControllerExtension(
ApexPages.StandardController stdController) {
this.controller = stdController;
}
public PageReference followProject() {
Id currentUserId = UserInfo.getUserId();
Set<Id> userIds = new Set<Id>();
for (List<Assignment__c> assignments :
[ SELECT Contact__r.User__c FROM Assignment__c WHERE
Project__c = :controller.getRecord().Id ]) {
for (Assignment__c assignment : assignments) {
Id uid = assignment.Contact__r.User__c;
if (currentUserId != uid && uid != null) {
userIds.add(uid);
}
}
}
if (userIds.size() == 0) {
error('Project has no assignments.');
return null;
}
Set<String> subs = new Set<String>();
for (List<EntitySubscription> recs :
[ SELECT ParentId FROM EntitySubscription
WHERE SubscriberId = :currentUserId
AND ParentId IN :userIds ]) {
for (EntitySubscription rec : recs) {
subs.add(rec.ParentId);
}
}
Integer followCount = 0;
List<EntitySubscription> adds = new List<EntitySubscription>();
for (Id userId : userIds) {
if (!subs.contains(userId)) {
adds.add(new EntitySubscription(
ParentId = userId, SubscriberId = currentUserId));
followCount++;
}
}
insert adds;
info(followCount + ' users followed');
return null;
}
private static void info(String text) {
ApexPages.Message msg = new ApexPages.Message(
ApexPages.Severity.INFO, text);
ApexPages.addMessage(msg);
}
private static void error(String text) {
ApexPages.Message msg = new ApexPages.Message(
ApexPages.Severity.ERROR, text);
ApexPages.addMessage(msg);
}
}


Two tricky areas of the implementation are as follows:

Image Duplicate records cannot be added, so existing EntitySubscription records on the assigned users must be checked first. This is done by building a set of record identifiers that are already followed, storing them in the subs variable, and consulting them before creating a new EntitySubscription.

Image Retrieving the users to follow from a project is somewhat indirect. Start with the list of Assignment records for the Project record. Each Assignment record contains a Contact that is assigned to the project. Each Contact includes a User__c field, which optionally contains a reference to a Salesforce User record. The User record identifier becomes the ParentId, the record to follow.

The Visualforce page behind the custom Follow Team button is provided in Listing 12.20. Key points in the page are the action attribute to invoke the following logic when the page is shown, and the pageMessages component to provide feedback to the user about the newly followed records, if any.

Listing 12.20 Visualforce Page for Custom Button


<apex:page standardController="Project__c"
extensions="FollowProjectControllerExtension"
action="{!followProject}">
<apex:pageMessages />
</apex:page>



Caution

Invoking a controller method upon Visualforce page load is bad practice for security reasons, as it can be exploited in a Cross Site Request Forgery (CSRF) attack. Visualforce pages are normally protected from CSRF using hidden variables that prevent a hijacker from redirecting the browser to a simple URL. To protect a page like the one in Listing 12.20, you could add a token that is checked in the controller before executing the logic. For more information, examine the security-related documents available atwiki.developerforce.com/index.php/Security.


After you have created the controller extension class and the page, add a custom button on the Project custom object called Follow Team. Figure 12.5 shows the button configuration.

Image

Figure 12.5 Custom button configuration

To test the new feature, add the button to the Project’s page layout. Then visit a Project record that has at least one Assignment and where the Assignment has a Contact with a non-null User__c field. Note that if a project has assignments but none of the contacts assigned have an associated user record, you will receive the “Project has no assignments” error message. Click the Follow Team button. Refresh the current user’s profile to verify that the assigned user is followed.

Summary

Chatter provides the building blocks for developers to create socially aware applications inside and outside the Force.com platform. As you review the key integration features of Chatter, consider the potential it brings to drive new applications and interactions in your organization:

Image Chatter is itself a platform, consisting of a public data model, user interface components, and tight integration with the greater Force.com platform. This provides flexibility for any application to exercise and extend Chatter functionality.

Image With Chatter in Apex, you can access Chatter data and metadata from your Apex code without the overhead and complexity of dealing with the raw database records. The Chatter REST API offers the same advantages but can be used with any technology.