Streaming API allows to push data (whenever records are created or updated in the backend) to a visualforce page opened by a user without refreshing page. It is very much similar to push notifications in mobile apps. You can refer Streaming API developer guide to get additional details regarding streaming API. Since it is an API in Salesforce, you can connect to it using multiple ways. You can build a java client application to connect to Salesforce. In this post, we will cover how to create a visualforce page using streaming API. This page will show newly created opportunities without reloading the page.
Steps to Use
1) Create a push notification in Salesforce
Streaming API works on the model of subscribing to push notifications. So first step is to create a push notification at the salesforce end to which we can subscribe from the visualforce page we are going to create. In this post we will create a push notification on opportunity object. Easiest way to create this is execute below code in developer console,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
//Creating push notification for new opportunities PushTopic pushTopic = new PushTopic(); pushTopic.Name = 'AllOpenOps'; pushTopic.Query = 'SELECT Id,Name,IsClosed,Amount,StageName,Probability FROM Opportunity'; pushTopic.ApiVersion = 33.0; pushTopic.NotifyForOperationCreate = true; pushTopic.NotifyForFields = 'Referenced'; insert pushTopic; 2) Create visualforce page and controller to subscribe to this push notification
Cometd JavaScript library is used to get data in JSON format to visualforce page. Then using this JavaScript data can be rendered to visualforce page. In the example given below, controller queries 10 opportunity records and displays as a table in page level. Thereafter whenever new opportunities are created in the backend, streaming API pushes data as JSON to page and using JavaScript this data is appended at the end of the table.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
<apex:page standardStylesheets="false" controller="StreamingController" showHeader="TRUE" sidebar="false"> <link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" /> <div class="container"> <!--Displaying initial 10 opporunities in a table--> <table id="oppTable" class="table table-hover table-bordered table-striped"> <thead> <tr> <th> Name </th> <th> Stage </th> <th> Amount </th> </tr> </thead> <tbody> <apex:repeat value="{!oppList}" var="opp"> <tr> <td> {!opp.Name} </td> <td> {!opp.StageName} </td> <td> {!opp.amount} </td> </tr> </apex:repeat> </tbody> </table> </div> <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> <script src="{!URLFOR($Resource.jsUtilities, '/js/json2.js')}"></script> <script src="{!URLFOR($Resource.jsUtilities, '/js/cometd.js')}"></script> <script src="{!URLFOR($Resource.jsUtilities, '/js/jquery.cometd.js')}"></script> <script> // initizlizing Streaming API (function($) { $(document).ready(function() { //Initializing cometd library to cometd endpoint $.cometd.init({ url: window.location.protocol + '//' + window.location.hostname + '/cometd/33.0/', requestHeaders: { Authorization: 'OAuth {!$Api.Session_ID}' }, appendMessageTypeToURL : false }); // Subscribing to the push topic. //JSON-encoded data will be returned in the callback each time whenever opportunity is created $.cometd.subscribe('/topic/AllOpenOps', function(message) { if (message.data.event.type == 'created') { var newOpp =message.data.sobject; //Appeding newly created opportunity data to the table of opportunities as last row $("#oppTable > tbody").append('<tr class="success"><td>'+newOpp.Name+'</td><td>'+newOpp.StageName+'</td><td>'+newOpp.Amount+'</td></tr>'); } }); }); })(jQuery) </script> </apex:page>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
//This controller queries 10 opprotunities randomly public class StreamingController { public List<Opportunity> oppList{get;set;} public streamingController(){ oppList = [Select Id,Name,Amount,StageName FROM Opportunity limit 10]; } }
In the page a static resource is used with JavaScript libraries like cometd, JSON2 etc. You can find the static resource in Salesforce Sample Codes Github project.
Please note that streaming API usage is counted against a 24hour daily limit. Exact limit varies with edition of salesforce. For developer edition it is 10000 in 24 hours.
How to create visualforce page using Streaming API
How to build autocomplete fields in visualforce pages
It is a very handy option to have autocomplete in picklists/dropdowns with large number of options. In this blog we will cover how to use a multiselect autocomplete field in a visualforce page.
In this example we will display a list of email addresses configured in a custom setting as autocomplete options in an input field. It will also demonstrate how to pass the value that user is selecting to the controller.
It works based on jQuery UI autocomplete feature. Necessary css and JavaScript is available in CDN. you can find links to these CDN files in the visualforce page code.
Steps to Use
1) Create a custom setting and create records
In this example, the list of email addresses that are displayed in autocomplete is pulled from name field in a custom setting. Please create a list custom setting with name AutocompleteEmails(AutocompleteEmails__c). Then create some email addresses as entry in the custom setting.
2) Create a page and controller
Code used for achieving it can be found below,
<apex:page controller="AutoCompleteController" ID="page" standardStylesheets="false" sidebar="false"> | |
<apex:stylesheet value="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"/> | |
<apex:stylesheet value="//code.jquery.com/ui/1.11.3/themes/sunny/jquery-ui.css"/> | |
<apex:includeScript value="//code.jquery.com/jquery-1.10.2.js"/> | |
<apex:includeScript value="//code.jquery.com/ui/1.11.3/jquery-ui.js"/> | |
<apex:form id="form"> | |
<div class="container"> | |
<!-- we cannot bind jquery UI autocomplete directly to apex input fields. So it needs to be bind to some regular input field then on form submission, the values need to be copied to some hidden apex input field--> | |
<input type="text" id="autocomplete" class="input-lg"/> | |
<apex:inputhidden value="{!selectedEmails}" id="selectedEmails"/> | |
| |
<apex:commandButton value="Save" action="{!receiveValues}" onclick="setHiddenInput();" styleClass="btn btn-success"/> | |
</div> | |
</apex:form> | |
<script> | |
//Method needed for adding autocomplete feature in TO field. | |
var autocompleteEmails ={!toEmailJSON}; | |
function split( val ) { | |
return val.split( /,\s*/ ); | |
} | |
function extractLast( term ) { | |
return split( term ).pop(); | |
} | |
$(function() { | |
$( "#autocomplete" ) | |
// don't navigate away from the field on tab when selecting an item | |
.bind( "keydown", function( event ) { | |
if ( event.keyCode === $.ui.keyCode.TAB && | |
$( this ).autocomplete( "instance" ).menu.active ) { | |
event.preventDefault(); | |
} | |
}) | |
.autocomplete({ | |
minLength: 0, | |
source: function( request, response ) { | |
// delegate back to autocomplete, but extract the last term | |
response( $.ui.autocomplete.filter( | |
autocompleteEmails, extractLast( request.term ) ) ); | |
}, | |
focus: function() { | |
// prevent value inserted on focus | |
return false; | |
}, | |
select: function( event, ui ) { | |
var terms = split( this.value ); | |
// remove the current input | |
terms.pop(); | |
// add the selected item | |
terms.push( ui.item.value ); | |
// add placeholder to get the comma-and-space at the end | |
terms.push( "" ); | |
this.value = terms.join( ", " ); | |
return false; | |
} | |
}); | |
}); | |
//Method to set value from normal HTML input to hidden apex input field. | |
function setHiddenInput(){ | |
var selectedEmails = $("#autocomplete").val(); | |
document.getElementById("page:form:selectedEmails").value = selectedEmails; | |
} | |
</script> | |
</apex:page> |
public class AutoCompleteController { | |
//String property to pass JSON data to page | |
public String toEmailJSON{get;set;} | |
//String property to save the values selected by user | |
public String selectedEmails{get;set;} | |
//Constructor of the class where email addresses are obtained from custom setting and converted to JSON | |
public autocompleteController(){ | |
Set<String> autocompleteEmailSet = AutocompleteEmails__c.getAll().keyset(); | |
toEmailJSON = JSON.serialize(autocompleteEmailSet); | |
} | |
//Method to demonstrate that values are coming to controller on button click | |
public PageReference receiveValues() { | |
//Debugging the value passed from page | |
System.debug(selectedEmails); | |
return null; | |
} | |
} |
In the constructor of the class, email addresses are queried from the custom setting. Then these values are converted to JSON. This JSON file is assigned to a property in the controller, which is accessible in the page. Then in page level, using this JSON, autocomplete feature is created. Since <apex:inputText does not support applying autocomplete feature on it, autocomplete is created in a regular input field. The values that user select in this regular input is passed to controller using an <apex:inputHidden field. In the save method, this value is passed to controller and it is printed using a System.debug() statement.
Usage
This feature will be very useful when users are using a dropdown with large number of options in a visualforce page. This will help users to find the value they are searching for very easily.
How to send email using apex
From Salesforce you can send emails using multiple methods. Commonly we will be
using workflows to send emails. But it is common to come across requirements that require more sophisticated email which might contain attachments. Please check below to see sample code to send email with attachments using apex,
Steps to Use
1) Get details regarding email body, attachments, to address etc.
You need get some basic details and take some design considerations before starting development. Salesforce allows only 10 emails to be sent in one apex transaction. So if you are sending email from a trigger for each record it will fail, when data is loaded through data loader in bulk. In addition to this, there will be organization level limit in number of emails that can be sent in one day. But this limit is usually high(1000 x number of users).
3) Reuse below code to send simple email (minimum example)
Please check below code sample to send a very simple email from apex.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public class EmailUtility{ //Simple code to send email to a number of email addresses public static void sendSimpleEmail(List<String> recipientList, String subject, String emailBody){ Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); email.setToAddresses(recipientList); email.setSubject(subject); email.setPlainTextBody(emailBody); email.setHtmlBody(emailBody); Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email}); } /* After saving this class execute below code from developer console * EmailUtility.sendSimpleEmail(new List<String>{'myemail@gmail.com'}, 'Subject Here', 'Body Here'); */ }
3) Reuse below code to send email with attachment (full example)
Please check below code sample to send email from apex.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public class EmailUtility{ public Boolean sendEmail(String emailTo, String emailCC, String emailSubject, String emailBody){ //Flag to track whether email is sent successfully Boolean isSuccess = false; //Body of attachment in email. It can be replaced with standard sf attachment Blob body = Blob.valueOf('Sample Body for email attachment'); //Creating email attachmentattachment specifying content type and file name Messaging.EmailFileAttachment attach = new Messaging.EmailFileAttachment(); attach.setContentType('text/plain'); attach.setFileName('testAttachment.txt'); //Setting attachment as non-inline attachment. attach.setInline(false); //Assigning blob to email body attach.Body = body; //Creating singleEmailMessage object Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); mail.setUseSignature(false); //Splitting TO email addresses and adding in array String[] toAddresses = new String[]{}; for(String emailId:emailTo.split(',')){ if(emailId != null && emailId !=''&& emailId != ' '){ toAddresses.add(emailId.trim()); } } //Assigning TO address array mail.setToAddresses(toAddresses); //Splitting CC email addresses and adding in array String[] ccAddresses = new String[]{}; for(String emailId:emailCC.split(',')){ if(emailId != null && emailId !=''&& emailId != ' '){ ccAddresses.add(emailId); } } //Assigning CC address array mail.setCCAddresses(ccAddresses); //Setting reply to email address //mail.setReplyTo('replyto email address'); mail.setSubject(emailSubject); //Setting content of the email mail.setPlainTextBody((emailBody==null?'This email body is empty':emailBody)); //Optionally you can use below line to add HTML formatted body for email //mail.setHtmlBody('<h1>Dear User</h1>'); //Assigning attachment to email mail.setFileAttachments(new Messaging.EmailFileAttachment[] { attach }); //Sending email. If any execption occurs, it will be displayed in page try{ List<Messaging.SendEmailResult> results = Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail }); if(results[0].success){ isSuccess = TRUE; }else{ isSuccess = FALSE; } } catch(Exception ex){ isSuccess = FALSE; } return isSuccess; } }
This sample code receives a string of TO email addresses separated by commas, a String of CC email addresses separated by commas, email subject and email body as string. This method also creates a text attachment in the email.
4) Additional reference
In addition to the methods used above there are many built in salesforce methods and supporting class related to sending emails. Please check below links for additional details,
How to use Google Charts in Visualforce pages
Usually you might come across requirements to show charts in visualforce page.
Salesforce’s built in analytics solutions is good. You can even embed those charts in your regular visualforce pages without much difficulty. But the only limitations is that some types of charts are not available in visualforce page. Another one common use case is community pages. Salesforce charts can be used in community, only if users have partner community or community user plus licenses, which are bit more costly. In such cases, you will have to go for third party charting solutions like Google Charts, Fusion charts etc. Here is an example of how to embed a Google chart in visualforce page.
Steps to Use
1) Understand the data requirements of the chart you need
You can find the list of all Google charts at charts gallery. Once you have found the right chart for your requirement, you need to check the data requirements of the chart. Each chart requires data in some specific format.
2) Format data in visualforce page controller in the required format
Once you have found the right format for data, next step is to format data in that format before sending to chart.
3) Visualforce page and controller implementation of doughnut chart
Check out below code where a doughnut chart is displayed with attendance of student. In the controller different attendance values are aggregated and passed that to page level using properties,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
<apex:page showheader="false" sidebar="false" controller="myAttendanceController"> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <apex:include pageName="header"/> <script type="text/javascript"> google.load("visualization", "1", {packages:["corechart"]}); google.setOnLoadCallback(drawChart); function drawChart() { var data = google.visualization.arrayToDataTable([ ['Status', 'Count'], ['Attended', {!attendedCount}], ['Partially Attended', {!partialCount}], ['Absent', {!absentCount}], ]); var options = { title: 'My Attendance', pieHole: 0.4, }; var chart = new google.visualization.PieChart(document.getElementById('donutchart')); chart.draw(data, options); } </script> <div class="container"> <div id="donutchart" style="width: 900px; height: 500px;"></div> </div> </apex:page>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public class myAttendanceController { public Id contactId; public Integer attendedCount{get;set;} public Integer partialCount{get;set;} public Integer absentCount{get;set;} public myAttendanceController(){ attendedCount = 0; partialCount = 0; absentCount = 0; User loggedInUser = [Select Id,contactId FROM User WHERE ID=:userinfo.getUserId()]; contactId = loggedInUser.contactId; List<AggregateResult> aggList =[Select Attendence_Status__c,Count(Id) FROM Attendance__c WHERE Enrollment__r.Student__c=:contactId GROUP BY Attendence_Status__c]; for(AggregateResult aggResult:aggList){ if(aggResult.get('Attendence_Status__c')=='Partially Attended'){ partialCount= (Integer)aggResult.get('expr0'); }else if(aggResult.get('Attendence_Status__c')=='Attended'){ attendedCount = (Integer)aggResult.get('expr0'); }else if(aggResult.get('Attendence_Status__c')=='Absent'){ absentCount= (Integer)aggResult.get('expr0'); } } } }
Use Cases
- Whenever charts need to be shown in visualforce pages
- Wherever standard Salesforce charts are not fitting your requirements
- If you have regular community licenses and want to show charts to users, Google charts is a good alternative.