Navigation: Developer Force | Visualforce | Streaming Real-Time Data into Visualforce


Streaming Realtime Data into Visualforce Pages

by Ryan Sieve

Vis cover thumb.jpg

The salesforce.com Streaming API provides a near real-time feed of notifications from events occurring within your organization’s database. A client can subscribe to this feed of data from the server and can take action on the notifications received from the subscription channel. This chapter will provide an overview of the Streaming API and demonstrate how to setup and configure your Salesforce environment to subscribe to Push Topic channels in Visualforce pages. Additionally, we’ll explore a variety of techniques to process the event notifications and handle the interactions with Visualforce controllers.

We’ll work through three examples of how to implement the Streaming API in Visualforce pages. The first example will show how to subscribe to a channel, and handle the messages on the client side with JavaScript. This does not require a controller, but the data provided to the end user goes no farther than the extent of the channel notification. The next example will build upon the previous example, but query for additional information by invoking an @RemoteAction method from a controller to deliver additional reference fields. Finally, we’ll use a Visualforce ActionFunction to deliver notification data to a custom controller, which will process and retain the data in the view state for a feed of updates to the end user. All of these examples will be using the Case object as the subject for the Push Topic.

Streaming API Overview

The salesforce.com Streaming API operates on a topic publish/subscribe model. In this model, the server delivers messages within a specified channel (topic) to subscribers (clients). The subscriber performs an initial handshake with the server for authentication and designates the channel with which they want a subscription. Once the handshake is complete and a connection to a channel is established, the subscriber begins to receive a feed of notifications from the channel. The Streaming API is a forward moving, one-way feed of data. Subscribers receive notifications from the server upon relevant database events, but cannot respond to or request additional details from notifications received from the Streaming API. Furthermore, past notifications not received from a lost connection or other issues cannot be recovered or queried. Any additional actions or interactions based on the notifications must be performed via other means.

A channel, in terms of the Streaming API, is created by defining a Push Topic. A Push Topic is a record within Salesforce that defines a series of attributes about the channel. The most critical attribute of a Push Topic is the query. A Push Topic query follows the same syntax as any other SOQL query. This SOQL query defines which records qualify for notifications within the channel. For example:

SELECT Id, name, accountId, amount, stageName 
FROM Opportunity 
WHERE isClosed = false

In this example, only DML actions on records with the isClosed flag set to false will provide notifications to the channel. While the query attribute of a Push Topic is SOQL, you cannot use every feature of SOQL’s syntax. Most notably, Push Topic queries do not support relationships. Adding “account.name” to the query defined above will result in a failure while attempting to create or update a Push Topic. For a full list of supported Push Topic queries, check the salesforce.com Streaming API documentation.

There are two more important attributes of a Push Topic. NotifyForFields is an enum that is defined as All, Referenced, Select, or Where. If “All” is selected, a notification will be sent regardless of which fields are updated on the record. However, the notification message will only contain the fields defined in the query. The default value is “Referenced;” it will only issue notifications if a field that is referenced in the SOQL query has changed. Similarly, the “Select” and “Where” options generate notifications only if a field value has been updated in the SELECT or WHERE clauses, respectively. NotifyForOperations is another enum that is defined as All, Create, or Update. The default selection is “All” and generates notifications for both Create and Update actions in the database for records that are found by the Push Topic query. The Create and Update options limit notifications to their respective DML events.

It is important to remember that notifications delivered within channels on the Streaming API occur only upon DML actions on records in the database. Passive changes on fields via formulas, lookups being cleared, etc., will not initiate a notification. However, time-based workflows can initiate a DML event, and can therefore create channel notifications where applicable. Time-based workflow can be helpful in initiating proactive notifications from the system. An example can be a time-based workflow that checks a box or sets a date field based on business rules. When the workflow fires, the record is updated with a field update from a workflow action. The DML event can initiate a Push Topic notification that is filtering based on the field being updated from the time-based workflow action. The final result is a declarative mechanism for delivering notifications to subscribers without hands-on interaction from a user or scheduled jobs.

Environment Setup and Configuration

The Streaming API notification delivery is based on connectivity through long-polling, or Comet programming. This is achieved by transporting messages utilizing the Bayeux Protocol via an implementation of an AJAX-based framework called CometD. The technologies are very compelling, and can be explored further at:

http://cometd.org/documentation
http://cometd.org/documentation/bayeux

In order to negotiate access to the notification channels in Visualforce pages, we need to add a few static resources from CometD. The CometD compressed archive can be downloaded at:

http://download.cometd.org/cometd-2.2.0-distribution.tar.gz

Once you have a copy of this archive, you’ll need to extract it to a directory that can be easily accessed. We’ll be uploading some of the files from this extract to your organization’s static resources. Once the archive is extracted, we’ll need to take another step to extract the cometd.js file from the cometd-javascript-common-2.2.0.war web application archive. In a Mac environment, this can be achieved by opening a terminal in the extracted directory at:

[Your Path]/cometd-2.2.0/cometd-javascript/common/target

Once the terminal is open, execute the following command:

jar xvf cometd-javascript-common-2.2.0.war org/cometd.js

This will place the cometd.js file in the same directory listed above. Once this process is complete, navigate to your static resources in Salesforce by clicking:


[Your Name] > Setup > Develop > Static Resources

Create the following static resources from your extracted CometD archive.


Static Resource Name

CometD Extract Path

cometd

cometd-2.2.0/cometd-javascript/common/target/org/cometd.js

jquery

cometd-2.2.0/cometd-javascript/jquery/src/main/webapp/jquery/jquery-1.5.1.js

json2

cometd-2.2.0/cometd-javascript/jquery/src/main/webapp/jquery/json2.js

jquery_cometd

cometd-2.2.0/cometd-javascript/jquery/src/main/webapp/jquery/jquery.cometd.js

Your environment is now set up to subscribe to channels and accept notifications from the Streaming API.

Figure 1. A view of the available static resources.

Create a Push Topic

Push Topics can be created on almost all objects within Salesforce, including the CRM objects and custom objects. You can create a Push Topic by executing anonymous Apex via the developer console or through the IDE. To create a Push Topic from the developer console, click: [Your Name] > Developer Console.

Once the console loads, open an Execute Anonymous window by clicking:

Debug > Open Execute Anonymous Window

Execute the following code:

PushTopic pushTopic = new PushTopic();
pushTopic.ApiVersion = 28.0;
pushTopic.Name = ‘CaseNotifications’;
pushTopic.Description = ‘Case Notifications’;
pushTopic.NotifyForFields = ‘Referenced’;
pushTopic.NotifyForOperations = ‘All’;
pushTopic.Query = ‘SELECT Id, caseNumber, accountId, contactId, status, priority FROM Case’;
insert pushTopic;
system.debug(‘Created new PushTopic: ‘ + pushTopic.Id);

Now that your Push Topic has been created, we need to test that it’s working. There are a variety of ways to test, but the simplest way is to log into the Salesforce workbench at:

https://workbench.developerforce.com/login.php.

This is a tremendous tool for working with the Streaming API. It provides a reliable way to interact with the various channels by browsing and subscribing to Push Topics. It can also provide a sanity check if you ever lose your connection in a page and need to see if messages are being delivered in your channel. Once you have logged into the workbench, click: Queries > Streaming Push Topics.

Figure 2. Streaming Push Topics

You can view all of your Push Topics by clicking the drop down list labeled “Push Topics.” For our example, select the “CaseNotifications” channel and click the “Subscribe” button. You should see a dialog appear indicating that you have subscribed to /topic/CaseNotifications.

In a separate browser tab or browser, find a Case record in your organization and change the value in the status or priority field. Return to the workbench, and you should see a new dialog appear indicating that a message was received from the channel. See Figure 4.

Now that you have verified your channel is delivering messages as expected, you’re ready to start building pages to accept the feed of notifications from your channel.

Visualforce Page with Client Side Processing

The first Visualforce page example will handle the subscription to the Push Topic channel and the processing of the notification completely on the client side. It will not require a custom controller, and provides a quick and easy starting point to working with the Streaming API in a Visualforce page. This example simply takes notifications received from the channel, and updates a PanelGrid with the message details. The intention of this page is to be modular, and to fit within a Dashboard Component. The end result is a near real-time Dashboard Component that shows the latest Case update details, and requires no Dashboard refresh, or screen refresh.

Figure 3. Streaming Push Topics

Let’s begin by creating a new Visualforce page named, “SimpleCaseNotifications.” Add the following attributes to the opening page tag:

Attribute Name

Value

id

page

tabstyle

case

Next, we need to add the static resources to the page with the following tags:

        <apex:includeScript value=”{!$Resource.cometd}”/>
    	<apex:includeScript value=”{!$Resource.jquery}”/>
    	<apex:includeScript value=”{!$Resource.json2}”/>
    	<apex:includeScript value=”{!$Resource.jquery_cometd}”/>

These IncludeScript tags load the JavaScript libraries from the static resources into the page, and will be used to manage the subscription to the channel and ongoing long-polling. We need to add one more block of JavaScript to the page, directly under the IncludeScript tags. This JavaScript is used to initiate the connection to the Streaming API, and subscribe to the “CaseNotifications” channel.

<script type=”text/javascript”>
    
	    var j$ = jQuery.noConflict();
	    j$(document).ready(function() {
	 
         	j$.cometd.init({
	            url: window.location.protocol+’//’+window.location.hostname+’/cometd/28.0/’,
	            requestHeaders: { Authorization: ‘OAuth {!$Api.Session_ID}’}
	        });
	
	        j$.cometd.subscribe(‘/topic/CaseNotifications’, function(message) {
		  		document.getElementById(‘{!$Component.page.block.casenumber}’).innerText = message.data.sobject.CaseNumber;
		  		document.getElementById(‘{!$Component.page.block.casestatus}’).innerText = message.data.sobject.Status;
		  		document.getElementById(‘{!$Component.page.block.casepriority}’).innerText = message.data.sobject.Priority;
		  		console.log(message);
         	});
	   });
	   
   </script>

Finally, add the body of the page as a simple page block and panel grid with text details. When the notifications are received in the channel, the JavaScript will update the elements in the DOM with the details. As additional notifications are received, these attributes will be overwritten to show the latest notification details.

<apex:sectionHeader title=”Simple” subTitle=”Case Notifications”/>
<apex:pageBlock id=”block”>
	<apex:panelGrid columns=”2”>
		<apex:outputLabel value=”Case Number: “ for=”casenumber” 
style=”font-weight: bold;”/>
		<apex:outputText value=”” id=”casenumber”/>
		<apex:outputLabel value=”Case Status:  “ 
for=”casestatus” style=”font-weight: bold;”/>
		<apex:outputText value=”” id=”casestatus”/>
		<apex:outputLabel value=”Case Priority:  “ 
for=”casepriority” style=”font-weight: bold;”/>
		<apex:outputText value=”” id=”casepriority”/>
	</apex:panelGrid>
</apex:pageBlock>

Save your changes to the page. Your final markup should look like this:

<apex:page id=”page” tabStyle=”Case”>

    <apex:includeScript value=”{!$Resource.cometd}”/>
    <apex:includeScript value=”{!$Resource.jquery}”/>
    <apex:includeScript value=”{!$Resource.json2}”/>
    <apex:includeScript value=”{!$Resource.jquery_cometd}”/>
    
    <script type=”text/javascript”>
    
	    var j$ = jQuery.noConflict();
	    j$(document).ready(function() {
	 
         	j$.cometd.init({
	            url: window.location.protocol+’//’+window.location.hostname+’/cometd/28.0/’,
	            requestHeaders: { Authorization: ‘OAuth {!$Api.Session_ID}’}
	        });
	
	        j$.cometd.subscribe(‘/topic/CaseNotifications’, function(message) {
		  		document.getElementById(‘{!$Component.page.block.casenumber}’).innerText = message.data.sobject.CaseNumber;
		  		document.getElementById(‘{!$Component.page.block.casestatus}’).innerText = message.data.sobject.Status;
		  		document.getElementById(‘{!$Component.page.block.casepriority}’).innerText = message.data.sobject.Priority;
		  		console.log(message);
         	});
	   });
	   
   </script>
   	<apex:sectionHeader title=”Simple” subTitle=”Case Notifications”/>
	<apex:pageBlock id=”block”>
		<apex:panelGrid columns=”2”>
<apex:outputLabel value=”Case Number: “ 
for=”casenumber” style=”font-weight: bold;”/>
			<apex:outputText value=”” id=”casenumber”/>
			<apex:outputLabel value=”Case Status:  “ 
for=”casestatus” style=”font-weight: bold;”/>
			<apex:outputText value=”” id=”casestatus”/>
			<apex:outputLabel value=”Case Priority:  “ 
for=”casepriority” style=”font-weight: bold;”/>
			<apex:outputText value=”” id=”casepriority”/>
		</apex:panelGrid>
	</apex:pageBlock>
</apex:page>

Navigate to your new page and verify that it loads without content in the PageBlock. Open another tab or browser and update the status or priority fields on a Case record, similar to how you tested using the workbench. You should see the page update with the notification details. You can also check for the same notification in the Workbench to explore additional details.

Figure 4. The Case Notifications window.

Notice the console.log(message) command in the supplemental JavaScript block. In Chrome, this logs the message received from the channel for review. Take some time and use the browser debugging tools of your choice, or the Salesforce Developer Workbench, to examine these messages. You’ll notice the message content is delivered in JSON, and has more attributes than what is being added to the page. These can be valuable both in debugging, or displaying to the end user.

Streaming listing5.png

This simple page can also be dropped into a dashboard component to show a near real-time feed of updates to the user without requiring a screen refresh, or dashboard refresh.

This example is intentionally very simple. The goal is to demonstrate how a page can subscribe to a Push Topic channel, receive notifications, and take action to display the data to the user. This is achieved without the use of an Apex controller, and relies on client-side JavaScript to update elements in the DOM with the message details.

Figure 5. Case Notifications.

Visualforce Page with RemoteAction

In the first example, the message contains all of the data needed to display to the end user. Oftentimes, requirements dictate that additional information from parent or child objects are displayed for user reference or interaction. While the channel notification holds vital pieces of information from the record being created or updated, it’s reasonable to assume that additional supporting information might be needed at the time a notification is received.

RemoteAction methods can provide a simple and fast (both in development time and response time) way to query the database for additional data regarding the channel notification. A RemoteAction method can return an sObject record that has queried additional reference fields from the database that cannot be provided in the channel notification. This page will differ from the first example in that the notification serves as input parameters to the RemoteAction method. However, it resembles the first example by taking the RemoteAction response, and updating the DOM with the queried data.

This example page will show additional details regarding the name of the Account, Contact, and Owner related to the Case. These reference attributes are not delivered in the notification, nor will a push topic query support the delivery of the related fields. We are able to return these pieces of data to the user by invoking a RemoteAction method upon receipt of a channel notification. The RemoteAction will accept data delivered from the notification, and return the queried record with the additional reference data.

Begin by creating a new “with sharing” Apex class named “RemoteActionCaseNotificationsController.” We’ll only need a single method in this controller. Create a new method, using the @RemoteAction annotation, that returns a Case, as shown below.

public with sharing class RemoteActionCaseNotificationsController {
	
	@RemoteAction
	public static Case returnCase(string caseId){
		
		return [SELECT Id, caseNumber, status, priority, 
owner.name, account.name, contact.name 
			FROM Case
			WHERE Id = :caseId];
		
	}

}

This query can be expanded to pull additional reference fields, as well as records in child relationships. Save your changes to this class. Create a new Visualforce page named “RemoteActionCaseNotifications,” with the same page tag attributes as the first example. Add the same <apex:includeScript> tags as well. The body of the page should resemble the first example; except we need to add additional <apex:outputLabel> and <apex:outputText> tags for the additional reference fields, as shown below.

<apex:sectionHeader title=”Remote Action” subTitle=”Case Notifications”/>
<apex:pageBlock id=”block”>
	<apex:panelGrid columns=”2”>
		<apex:outputLabel value=”Case Number: “ 
for=”casenumber” style=”font-weight: bold;”/>
		<apex:outputText value=”” id=”casenumber”/>
		<apex:outputLabel value=”Case Status:  “ 
for=”casestatus” style=”font-weight: bold;”/>
		<apex:outputText value=”” id=”casestatus”/>
		<apex:outputLabel value=”Case Priority:  “ 
for=”casepriority” style=”font-weight: bold;”/>
		<apex:outputText value=”” id=”casepriority”/>
		<apex:outputLabel value=”Case Owner:  “ 
for=”caseowner” style=”font-weight: bold;”/>
		<apex:outputText value=”” id=”caseowner”/>
		<apex:outputLabel value=”Account Name:  “ 
for=”accountname” style=”font-weight: bold;”/>
		<apex:outputText value=”” id=”accountname”/>
		<apex:outputLabel value=”Contact Name:  “ 
for=”contactname” style=”font-weight: bold;”/>
		<apex:outputText value=”” id=”contactname”/>
	</apex:panelGrid>
</apex:pageBlock>
<apex:outputPanel layout=”block” id=”responseErrors”></apex:outputPanel>

Following the first example, add a new JavaScript block, with the “ready,” “init,” and “subscribe” functions. However, leave the content of the subscribe method empty.

<script type=”text/javascript”>
    	
var j$ = jQuery.noConflict();
j$(document).ready(function() {
	 
		j$.cometd.init({
url: window.location.protocol+’//’+window.location.hostname+’/cometd/28.0/’,
	            requestHeaders: { Authorization: ‘OAuth {!$Api.Session_ID}’}
			});
	
       		j$.cometd.subscribe(‘/topic/CaseNotifications’, function(message) {
	    		
       		});
	            
       })
  </script>

Now create another Javascript function named “getRemoteCase,” with a signature that will accept the ID of the record received in the channel notification. The contents of this method should invoke the RemoteAction method from the controller, and update the elements in the DOM for the user to review. Finally, add a call to the getRemoteCase method from within the subscribe method. Your final JavaScript code should look like:

<script type=”text/javascript”>
    	
	    var j$ = jQuery.noConflict();
	    j$(document).ready(function() {
	 
	        j$.cometd.init({
	            url: window.location.protocol+’//’+window.location.hostname+’/cometd/28.0/’,
	            requestHeaders: { Authorization: ‘OAuth {!$Api.Session_ID}’}
			});
	
           	j$.cometd.subscribe(‘/topic/CaseNotifications’, function(message) {
	    		getRemoteCase(message.data.sobject.Id);
            });
	            
            function getRemoteCase(caseId) {
		        Visualforce.remoting.Manager.invokeAction(
	            	‘{!$RemoteAction.RemoteActionCaseNotificationsController.returnCase}’,
		            caseId, 
	            	function(result, event){
		                if (event.status) {
							document.getElementById(‘{!$Component.page.block.casenumber}’).innerText = result.CaseNumber;
				    		document.getElementById(‘{!$Component.page.block.casestatus}’).innerText = result.Status;
				    		document.getElementById(‘{!$Component.page.block.casepriority}’).innerText = result.Priority;
				    		document.getElementById(‘{!$Component.page.block.caseowner}’).innerText = result.Owner.Name;
				    		document.getElementById(‘{!$Component.page.block.accountname}’).innerText = result.Account.Name;
				    		document.getElementById(‘{!$Component.page.block.contactname}’).innerText = result.Contact.Name;
		                } else if (event.type === ‘exception’) {
		                    document.getElementById(“responseErrors”).innerHTML = 
		                        event.message + “<br/>\n<pre>” + event.where + “</pre>”;
		                } else {
		                    document.getElementById(“responseErrors”).innerHTML = event.message;
		                }
		            }, 
		            {escape: true}
		        );
		    }
	   })
</script>
Recall the <apex:outputPanel> with the ID of “responseErrors.” This panel will render a
on the page where error messages will appear if there is a problem with the query or RemoteAction method. The error message HTML delivers in the exception conditions of the JavaScript can be changed to fit your requirements.

You can test this page in the same fashion as the first example. Once the notification is received, the RemoteAction will fire and update the elements in the DOM with the queried reference data.

It’s important to consider the performance of the page while adding the call to the server with the RemoteAction. There will be a slight delay between the channel notification and providing the information to the user once the additional reference fields are queried. In high-level performance benchmarks, this example typically returns data to the page from the RemoteAction within ~200-250 ms. This duration will likely extend if the query returns more fields, child relationships, or has other processing prior to returning the data. While the delay is nominal in this example, it should not be overlooked as you extend this functionality to support more complex interactions on the server and client side.

== Visualforce Page with ActionFunction ==

The previous example illustrates how a channel notification can be used to initiate interaction with the server to obtain additional data not available in the channel notification. This example also treats the notification as a signal to interact with the server for additional data, but will also be adding and managing elements in the view state, and rerender components in the form. This is achieved through the utilization of an <apex:actionFunction> tag. When the page loads, the ActionFunction tag generates a JavaScript function in the browser that handles an AJAX request from the user. We are taking advantage of this by generating an ActionFunction that allows the calling action to pass a parameter, and deliver it to the controller for further action.

This example will deliver the ID from the Case in the channel notification to the controller, which queries and retains Case details in the view state even after additional notifications have been received. The ActionFunction will also be utilized to rerender elements on the page to reflect updated data in the view state from the controller upon receipt of the data from the channel notification. The rerender will also be used to refresh Visualforce charts that reflect summary data on open Cases in the database.

Similar to the RemoteAction controller example, begin by creating a new “with sharing” Apex class named “ActionFunctionCaseNotificationController.” Before we build any methods to process the channel notifications, we need to define a class wrapper to store the data queried on the incoming notifications. The class wrapper is simple, only containing a Case sObject and DateTime as member variables. It will also need a Comparable interface to store the incoming Cases in a descending sequence based on the DateTime it was received. As channel notifications are received, additional data will be queried from the system, stored in instances of this class wrapper, and tagged with a time stamp. For more information on the Comparable interface, please check the Apex documentation. Add the following class to your controller.

public class CaseWrapper implements Comparable{
		public Case c {get;set;}
		public DateTime ts {get;set;}
		
		//Return a descending timestamp sort, 
//with the latest timestamp first after sort
		public integer compareTo(Object compareTo){
			CaseWrapper wrapperCompareTo = (CaseWrapper)compareTo;
			if(ts == wrapperCompareTo.ts){
				return 0;
			}else if(ts > wrapperCompareTo.ts){
				return -1;
			}else{
				return 1;
			}
		}
	}

We need to add a couple of public variables in the controller to support the incremental Case data being stored in a collection. Add the following variables to your controller.

public string caseId {get;set;}
public Map<Id, CaseWrapper> mCaseWrappers {get;set;} 
{ mCaseWrappers = new Map<Id, CaseWrapper>();}

The caseId will be delivered from the page, and will be used to query for additional Case data. The Map<Id, CaseWrapper> will be used for multiple reasons. We want to ensure that we’re displaying a unique list of Cases to the user, so storing the CaseWrappers in a Map and using the Case ID as the key will prevent duplicate instances of the wrapper for the same case, even if multiple channel notifications are received in a short time frame.

Add a method to query for the required Case data, based on the caseId attribute supplied from the page, and put an instance of the CaseWrapper class in the Map with the Case ID as the key value.

public void addCaseId(){
		Case tmpCase = [SELECT Id, caseNumber, status, priority, 
owner.name, account.name, contact.name,
				(SELECT Id, commentBody, createdBy.Name, createdDate
				FROM CaseComments
				ORDER BY CreatedDate desc
				LIMIT 5)
			FROM Case
				WHERE Id = :caseId];
		CaseWrapper cw = new CaseWrapper();
		cw.c = tmpCase;
		cw.ts = system.now();
		mCaseWrappers.put(cw.c.Id, cw);

Understanding that a keyset in a Map cannot retain a particular order, we’ll put the Comparable interface to work to return the list of CaseWrappers to the page in the sequence we desire. Add another method that returns a list of CaseWrappers from the Map values. The CaseWrapper list returned by the Map values can be sorted, and then returned to the page.

 public List<CaseWrapper> getCases(){
		List<CaseWrapper> cases = mCaseWrappers.values();
		cases.sort();
		return cases;
	}

In order to support the Visualforce pie charts, we need to add another class wrapper that will store the queried summary data from the system based on Case Priority and Case Status. This simple class wrapper contains a string and integer member variables. Instances of this class wrapper will hold the name of a case attribute, and the integer will represent how many Cases reflect that attribute. Add the following code:

public class CaseData{
		public string value {get;private set;}
		public integer nRecs {get;private set;}
		
		public CaseData(string val, integer n){
			value = val;
			nRecs = n;
		}
}

Using Case Status as an example, we can expect to see data similar to the table below:


Status

nRecs

New

4

Working

12

Escalated

1

We need to add two methods that return a list of CaseData wrappers to the page. We’ll use an AggregateResult query in each to quickly group on Case Status and Priority, with the count of records in each group. For more information on AggregateResults, please refer to the Apex documentation. Add the following code to your controller.

public List<CaseData> getStatusData(){
		List<CaseData> statusData = new List<CaseData>();
		List<AggregateResult> arStatuses = [SELECT status status, 
COUNT(Id) nRecs
							FROM Case
							WHERE isClosed = false									GROUP BY status];
		for(AggregateResult ar : arStatuses){
			statusData.add(new CaseData((string)ar.get(‘status’), 
							(integer)ar.get(‘nRecs’)));
		}
		return statusData;
	}
	
	public List<CaseData> getPriorityData(){
		List<CaseData> priorityData = new List<CaseData>();
		List<AggregateResult> arPriorities = [SELECT priority priority, 
COUNT(Id) nRecs
							FROM Case
							WHERE isClosed = false
							GROUP BY priority];
		for(AggregateResult ar : arPriorities){
			priorityData.add(new CaseData((string)ar.get(‘priority’),
 (integer)ar.get(‘nRecs’)));
		}
		return priorityData;
	}

We have completed the controller and can now move on to the Visualforce page. Create a new Visualforce page named “ActionFunctionCaseNotifications” with the same <apex:page> tag attributes and <apex:includeScript > tags as the previous examples. Similar to the other examples, we’ll need to add a JavaScript block to subscribe to the channel notifications, and pass the delivered messages to our ActionFunction. Add the following JavaScript to your page below your IncludeScript tags:

<script type=”text/javascript”>
    	var j$ = jQuery.noConflict();
	    j$(document).ready(function() {
            // Connect to the CometD endpoint 
    
            j$.cometd.init({
               url: window.location.protocol+’//’+window.location.hostname+’/cometd/28.0/’,
               requestHeaders: { Authorization: ‘OAuth {!$Api.Session_ID}’}
           	});

           	j$.cometd.subscribe(‘/topic/CaseNotifications’, function(message) {
	    		findCaseDetails(message.data.sobject.Id);
            });
            
        });
</script>

Notice the findCaseDetails method invocation within the subscribe method. This method will call out to the ActionFunction, which will be created after adding a few more components to the page.

The page will be segmented into two major sections: the left panel with the pie charts, and the right panel with the Case details and comments. In order to achieve this presentation, we’ll use an <apex:panelGrid> with two small style classes for the columns. These classes instruct the panels on their width, and to align with the top of their containing elements. Add the following styles:

<style>
	.panelLeft {
	    	width: 25%;
	    	vertical-align: top;
	}
.panelRight {
	    	width: 75%;
	    	vertical-align: top;
	}
</style>

Now add a sectionHeader and form to the page.

<apex:sectionHeader title=”Action Function” subTitle=”Case Notifications” />
<apex:form id=”form”>
</apex:form>

Add an <apex:panelGrid> tag as a direct child to the form with two columns, spanning the entire width of the page, and reflecting the styleClasses defined from above.

<apex:panelGrid columns=”2” width=”100%” columnClasses=”panelLeft,panelRight”>


</apex:panelGrid>

Create a grouping for the pie charts by adding an <apex:panelGroup> tag inside the PanelGrid as the content for the first column,. Add the following code inside the PanelGroup to reflect the AggregateResult data returned for the pie charts from the controller.

<apex:panelGroup >
<apex:pageBlock title=”Case Status”>
		   				
		<apex:chart data=”{!statusData}” height=”200” width=”250” 
						background=”#F5F5F5”>
			<apex:legend position=”bottom”/>
		<apex:pieSeries labelField=”value” dataField=”nRecs” donut=”50”>
		        	<apex:chartLabel display=”middle” orientation=”vertical” 
					            font=”bold 12px Helvetica”/>
		 </apex:pieSeries>
		</apex:chart>
					
	</apex:pageBlock>
	<apex:pageBlock title=”Case Priority”>	
					
<apex:chart data=”{!priorityData}” height=”200” width=”250” 
				background=”#F5F5F5”>
<apex:legend position=”bottom”/>
			<apex:pieSeries labelField=”value” dataField=”nRecs” donut=”50”>
		        	<apex:chartLabel display=”middle” orientation=”vertical” 
					            font=”bold 12px Helvetica”/>
		</apex:pieSeries>
		</apex:chart>
		   				
	</apex:pageBlock>
 </apex:panelGroup>

This will create two donut-style pie charts representing the number of Cases by Status and Priority. Any additional elements added to this PanelGroup will appear in the narrow column on the left.

The content for the column on the right is Case detail information and related comments. Add an <apex:pageBlock> tag after the closing </apex:panelGroup> tag. All content added to this component will appear in the wide column on the right. The content in this column is generated by an <apex:repeat> tag that creates PageBlockSections for each instance of a CaseWrapper returned by the getCases method in the controller. The first PageBlockSection contains Case Detail information, and the second contains a PageBlockTable to display any comments that have been created on this case. Each wrapper in the list will have both PageBlockSections to display content. Notice that the repeat tag is wrapped with an <apex:outputPanel> with styles to handle content overflow. This helps create a clean page in two ways. First, it prevents the content in the column on the right from growing exceedingly long and cumbersome looking. Second, and more importantly, regardless of how long the content becomes, the pie charts will hold a static position on the page relative to any scrolling occurring in the column on the right.

<apex:pageBlock title=”Recent Case Updates”>
<apex:outputPanel layout=”block” style=”height:500px;overflow-y:scroll;”>
	<apex:repeat value=”{!cases}” var=”cw”>
			<apex:pageBlockSection title=”{!cw.c.caseNumber}” columns=”2”>
				<apex:outputField value=”{!cw.c.caseNumber}”/>
				<apex:outputField value=”{!cw.c.status}”/>
				<apex:outputField value=”{!cw.c.priority}”/>
				<apex:pageBlockSectionItem >
					<apex:outputLabel value=”Owner Name”/>
					<apex:outputField value=”{!cw.c.owner.name}”/>
				</apex:pageBlockSectionItem>
				<apex:pageBlockSectionItem >
					<apex:outputLabel value=”Account Name”/>
					<apex:outputField value=”{!cw.c.account.name}”/>
				</apex:pageBlockSectionItem>
				<apex:pageBlockSectionItem >
					<apex:outputLabel value=”Contact Name”/>
					<apex:outputField value=”{!cw.c.contact.name}”/>
				</apex:pageBlockSectionItem>
			</apex:pageBlockSection>
			<apex:pageBlockSection columns=”1”>
				<apex:pageBlockTable value=”{!cw.c.CaseComments}” var=”cc” 
					rendered=”{!cw.c.CaseComments.size > 0}”>
					<apex:column value=”{!cc.createdBy.Name}”/>
					<apex:column value=”{!cc.createdDate}”/>
					<apex:column value=”{!cc.commentBody}”/>
				</apex:pageBlockTable>
				<apex:outputText value=”There are no comments on this case.” rendered=”{!cw.c.CaseComments.size == 0}” style=”font-weight:bold”/>
			</apex:pageBlockSection>
		</apex:repeat>
	</apex:outputPanel>
</apex:pageBlock> 

The final and most important tag that we need to add is the <apex:actionFunction>. This tag will generate JavaScript upon page load that acts as a conduit between the client JavaScript and the controller. Add the following code directly after the closing </apex:panelGrid> tag, but still inside the form:

<apex:actionFunction action=”{!addCaseId}” name=”findCaseDetails” rerender=”form”>
<apex:param name=”caseId” assignTo=”{!caseId}” value=”” />
</apex:actionFunction>


The ActionFunction tag is analogous to a method declaration in JavaScript. By creating this ActionFunction, we are essentially adding another JavaScript method that can be called upon and interacted with as any other. In our example, the child tag <apex:param> is analogous to the method parameters, expecting a string value to be passed when calling this method. Review the JavaScript block that was created earlier in this example; the subscribe method is calling the findCaseDetails method and passing the ID of the Case provided in the channel notification. When the ActionFunction is invoked, it calls the controller method addCaseId and rerenders the form to reflect the latest data from the server.

Test this page as you have with the other examples. You should see case details appear alongside the charts as you update cases.

Figure 6. Testing the page shows case details and related charts.

Generally speaking, the performance of an ActionFunction is not as fast as a RemoteAction. This page typically returned results within ~750 ms of a channel notification, despite the additional queries and processing. Following the model in the second example, two pages were benchmarked with RemoteAction and ActionFunction. The RemoteAction page consistently outperformed the ActionFunction by at least ~100-150 ms. Again, this difference is nominal, but must be considered in extending functionality in these design patterns.

Chapter Summary

The examples in this chapter are intended to help illustrate the various ways to implement the Streaming API in a Visualforce page. In the first example, there is no server-side logic, and everything is handled in the page upon receipt of the channel notification. If the page is using data delivered within a channel notification for informational purposes only, this can be a quick and clean way to deliver the information to the end user. If additional data is needed from the database for display on the page or user interaction, RemoteAction and ActionFunction methods can be invoked upon receipt of a channel notification. This allows the page to receive and display data in a near real-time fashion, while still having access to the controller, view state, or other vital pieces of Apex code. Ultimately, RemoteAction and ActionFunction implementations interact with the CometD framework via JavaScript to bridge the gap between a channel notification, and meaningful interaction with the user on a page. RemoteAction is typically faster, requires more JavaScript, and has greater flexibility in managing pages than Visualforce AJAX components. On the other hand, ActionFunction is typically easier to implement, requires little to no JavaScript, and allows you to rerender other Visualforce components.

When designing a solution that utilizes the Streaming API, consider which objects are supported, what events should issue notifications so Limits are not exceeded, and how timely the notifications and interactions should be with the user. Your Push Topic queries should only consider the information that is needed, rather than issuing notifications for every record that’s committed for a particular object. It’s also important to choose the implementation that scales best over time. If you expect to have your page grow in complexity while maintaining quick performance, then design your initial solution with RemoteActions. If the page is moderately simple or response time is not of the utmost importance, then ActionFunction can be the simplest and fastest solution to implement.

About the Author

Ryan Sieve is a frequent contributor to Salesforce Developers and a co-author of Visualforce in Practice.

This article was excerpted from Visualforce in Practice, published by salesforce.com, November 2013 and released at Dreamforce '13.