Pages

Friday, 27 December 2013

Browser Notifications with the Streaming API

The Streaming API is great for sending information to a user’s browser when a record matches the criteria for a subscribed Topic - there’s an example of this in the Developerforce Wiki. However, we all know that user attention spans are short and it is highly likely that they will have moved on to another window or tab, maybe to get on with some work in another Salesforce org or sandbox.

Browser notifications provide a way to notify a user of changes, even when they have minimised the browser window. The Notification API is still in draft and is currently supported by Chrome, Firefox and Safari - for more details see the caniuse page. In this post I’ll demonstrate how the to generate a notification to the user when a case that they own is updated.  I’ll use the Streaming API to subscribe to updates to all cases, and interrogate the update to determine if the case is owned by the currently logged in user.  If it is, a browser notification will be displayed. The code relies on the Streaming API JavaScript resources being installed as described in the introductory Developerforce Wiki page.

Notifications are different to browser popups, in that they require user approval before they will be shown. This is a one shot deal, so once permission to display has been granted for a Force.com instance it will be retained across multiple sessions. Unfortunately the permission cannot be requested programmatically - the first notification has to be in response to a user action,  clicking a button for example.

The Notification.permission property indicates if permission has been granted or refused for the current site. Unfortunately there is a bug in the Google Chrome implementation of Notifications, which means that the property is always set to ‘undefined’, which in reality indicates that the user has not been asked to grant permissions.  The only way to confirm this in Chrome is to create a new Notification and interrogate the permission property - if the user hasn’t already granted permission they will be asked to via a dialog, while if they have already granted permission the notification will be displayed.  For that reason, the test notification needs to display something non-threatening! 

The following code creates the test notification if required and checks the resulting permission.  Note that it also updates the Notification.permission property so that this only has to be done once.

if (Notification && typeof Notification.permission==="undefined")
{
   var testNotification = new window.Notification('This is a test');

   if (testNotification.permission)
   {
      Notification.permission=testNotification.permission;
   }
}

Once this code has executed, the Notification.permission can be relied on - it may still be undefined, in which case it means that the user hasn’t granted permission (remember, the permission cannot be requested programmatically, so if the user has never been asked, the code above will simply update the Notification.permission to undefined).  Based on the value of this property, I can conditionally show or hide the following section to encourage the user to click a button to enable notifications.

<button id="notifyon" style="display:none">Enable Chrome Notifications</button>
<button id="sendnotify" onclick="notify();">Send Notification</button>

The streaming API part of the code subscribes to a topic and writes information to the page whenever an update to the subscription is received.  It also executes the notify function when an update is received to a record that the currently logged in user owns:

$.cometd.subscribe('/topic/{!topic}', function(message) {
  $('#content').append('<p>Notification: ' +
              'Channel: ' + JSON.stringify(message.channel) + '<br>' +
              'Record name: ' + JSON.stringify(message.data.sobject.Name) +                        '<br>' +
              'Record owner: ' + JSON.stringify(message.data.sobject.OwnerId) +
              '<br>' +
              'Created: ' + JSON.stringify(message.data.event.createdDate) + '<br>' +
              'ID: ' + JSON.stringify(message.data.sobject.Id) + '<br>' +
              'Number: ' + JSON.stringify(message.data.sobject.CaseNumber) + '<br>' +
              'Event type: ' + JSON.stringify(message.data.event.type)+ '<br/>' +
              'Mine : ' + (message.data.sobject.OwnerId=='{!$User.id}'?'Yes':'No') +               '</p>');

    if (message.data.sobject.OwnerId=='{!$User.id}')
    {
        notify(message.data.sobject, message.data.event.type);
    }
});

The notify function instantiates a notification only if the user has granted permission:

function notify(sobject, eventtype)
{
	// If notifications are granted show the notification
	if (Notification && Notification.permission === "granted")
	{
		var millis=new Date().getTime();
	    	var tag = 'CU:' + millis;
	   	var n = new Notification("Case Update",  {
		   		icon : '{!URLFOR($Resource.notifyimg)}',
	  			tag: tag,
	   			body: 'Case ' + sobject.CaseNumber + ' ' + eventtype
	    	});
		n.onclick = function(){
    			window.focus();
    			this.cancel();
		};
	}
}

Each notification needs a unique identifier - if the browser recognises an identifier it won’t display the notification on the assumption the user has already seen this.

An onclick handler for the notification is created to allow the user to close the notification immediately rather than waiting for it to expire.

If I open the Visualforce page in a browser window and click the ‘Enable Chrome Notifications’ button, Chrome will request permission to display notifications:

Screen Shot 2013 12 27 at 15 30 21 

once I allow notifications, a notification confirming this is displayed:

 Screen Shot 2013 12 27 at 15 30 33 

I can then switch to another browser tab or minimise the window completely.  If I then update a case that I own in another browser, a notification is displayed to tell me that it has been updated:

Screen Shot 2013 12 27 at 15 34 41

The full code is available from the following Gists:

  

Tuesday, 26 November 2013

Freezing Users from Visualforce

One of the new features in the Winter 14 release of Salesforce is the ability to freeze a user, which stops them logging into the system.  The Salesforce help points towards using this functionality when you would like to deactivate a user but additional work is required as the user is part of other configuration, as the default lead owner for example.  This is one use for this functionality, but another occurred to me based on work that I carried out about 20 years ago.

In a former life I used to build deal capture and risk management systems for investment banks.  A requirement of many of the banks was that traders had to take a two-week holiday every year and had to be locked out of all systems for the entire two weeks  The thinking behind this was that if the trader had something to hide, it was likely to surface during this two-week period when they couldn’t take any action to cover it up. 

Freezing users is therefore a great fit for temporarily disabling a user’s access to the system, with the intention of re-instating their access after a period of time.  The downside to the feature is that it can only be accessed from the user record, which means that an administrator has to click into individual user accounts to freeze or unfreeze them. This is fine for the odd user, but becomes time-consuming when it has to be done on a regular basis for a number of users.  

After digging through the Apex Developer’s Guide and experimenting with the execute anonymous element of the developer console it quickly became clear that I couldn’t freeze a user in Apex.  Searching the SOAP API Developer’s Guide proved more productive when I came across the UserLogin object and its associated IsFrozen field.  While this still mean that I couldn’t use Apex, the SOAP API is accessible via the Ajax Toolkit which I can use from a Visualforce page.

It was then short work to create The Freezer - a Visualforce page to output all usernames present in the system and allow them to be frozen/defrosted at the click of a button.  The page is shown below:

Screen Shot 2013 11 03 at 17 49 39

clicking on the Freeze button next to the Customer User pops a dialog to detail the action being taken:

Screen Shot 2013 11 03 at 17 50 00

and a further popup displays the results:

Screen Shot 2013 11 03 at 17 50 14

before the page refreshes itself and displays the Defrost button for the Customer User:

Screen Shot 2013 11 03 at 17 50 26

and just to prove there’s no trickery, here’s the Customer User record with the Unfreeze button present:

Screen Shot 2013 11 03 at 17 51 14

The functionality is provided by a couple of JavaScript functions. The getUserInfo() pulls back all the UserInfo records in the system and stores them in the equivalent of a Map keyed by user id.  It then retrieves all of the active user records in the system, and dynamically builds the table of users including the action buttons:

 
function getUserInfo()
    {
      var userInfoById = {};
    
      var result = sforce.connection.query(
          "Select Id, UserId, IsFrozen, IsPasswordLocked From UserLogin order by UserId");
 
      var it = new sforce.QueryResultIterator(result);
 
      while(it.hasNext())
      {
         var record = it.next();
 
         userInfoById[record.UserId] = record;
      }
      
      
      var output='<table><tr><th>User</th><th>Action</th></tr>';
      
      result = sforce.connection.query(
          "Select Id, FirstName, LastName from User where IsActive=true");
 
      it = new sforce.QueryResultIterator(result);
 
      while(it.hasNext())
      {
        var record = it.next();
        
        if (record.Id in userInfoById)
        {
          var userInfo=userInfoById[record.Id];
          var name=record.FirstName + ' ' + record.LastName;
          output+='<tr><td>' + name + '</td><td>';
          if (userInfo.IsFrozen=='true')
          {
            output+="<button onclick=\"freeze('" + userInfo.Id + "', '" + name + "', false);\">Defrost</button>";
          }
          else
          {
            output+="<button onclick=\"freeze('" + userInfo.Id + "', '" + name + "', true);\">Freeze</button>";
          }
          output+='</td></tr>';
        }
      }
      
      output+='</table>';
      
      document.getElementById('output').innerHTML=output;
    }
  

The freeze function updates the UserLogin for the selected user to freeze or defrost them:

function freeze(id, name, freezerState)
  {
    alert("Freezing " + name);
    var userlogin = new sforce.SObject("UserLogin");
    userlogin.Id = id;
    userlogin.IsFrozen = freezerState;
    var result = sforce.connection.update([userlogin]);
 
    if (result[0].getBoolean("success")) {
        alert(name + " " + (freezerState?'frozen':'defrosted'));
    } else {
        alert("failed to " + name + " " + result[0]);
    }
    
    window.location.reload();
  }

The code is pretty basic - there’s not much error handling and it is unlikely to scale when there are a large number of users, but those elements are left as an exercise for the avid student. You can access the full page at this gist.

  

Thursday, 7 November 2013

Visualforce in Chatter Mobile

Chatter mobile 4.2 for iOS launched this week and one feature has generated a lot of interest - the ability to include Visualforce in the application. Daniel Hoechst produced a blog post explaining how to achieve this in record time.

At first glance it might seem that this doesn’t add much over and above a regular HTML5 application that is executed from the mobile device browser - in fact an application wouldn't even require the consumption of a tab to expose the Visualforce page.  

The difference is that the chatter application refreshes an oauth token to gain access to the pages and data, so users don’t need to rekey their user id and password each time they access the application.  This is a big deal.

To achieve this without the chatter application, you’d be looking at building a remote start hybrid application using Xcode on a mac and if my experience is anything to go by, spending a fair amount of time playing the provisioning/distribution profile guessing game. You’d also need to either purchase an enterprise distribution license or make your application available through the apple app store. Throw in the being a Salesforce partner building applications on behalf of customers, and things get even more complicated. Did I mention that this is a big deal?

I’ve had a quick play around with the iPhone and iPad variants - the fact that the iPad has more real-estate and supports landscape mode means I focused on that in the first instance.

Burning a tab for each Visualforce page that you wish to surface in the application is a bit of an overhead, especially in Enterprise Edition where you quickly become tab-poor if you build a number of custom applications. The good news is that the tab that you surface doesn’t have to be tied to a particular purpose, so you can build a jumping off page that allows you to access any amount of other pages.  Even better, only the page present in the tab needs to be marked as available for mobile.

To demonstrate this I’ve created a simple jQuery Mobile page that presents a listview with a couple of options. Each of these options opens a new jQuery Mobile page, one that I wrote ages ago to demonstrate navigation, and the other a sample application for swipe navigation that I blogged about a few months ago.  The page source is shown below:

<apex:page showheader="false" sidebar="false" standardstylesheets="false">
<html>
    <head>
    <title>Chatter Mobile Page</title>
    
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <apex:stylesheet value="https://ajax.aspnetcdn.com/ajax/jquery.mobile/1.3.0/jquery.mobile-1.3.0.min.css" />
    <apex:includeScript value="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.min.js"/>
    <apex:includeScript value="https://ajax.aspnetcdn.com/ajax/jquery.mobile/1.3.0/jquery.mobile-1.3.0.min.js"/>
</head>

<body>
  <ul data-role="listview">
    <li><a href="/apex/JQM">JQM Page</a></li>
    <li><a href="/apex/JQMSwipe">JQM Swipe</a></li>
  </ul>
</body>
</html>
</apex:page>

I then made this page available to mobile by ticking the “Available for Salesforce mobile apps” checkbox on the edit page, creating a Visualforce tab and adding this to the available Chatter mobile tabs via the Setup -> Administration Setup -> Mobile Administration -> Mobile Navigation setup menu option.  When I open the chatter mobile application the new page is available under the apps menu:

Screen Shot 2013 11 07 at 17 55 52

 

clicking the VF Page app opens my basic jQuery Mobile page:

 

Screen Shot 2013 11 07 at 17 57 08

 

clicking one of the links, the JQM Swipe for example, opens that page:

 

Screen Shot 2013 11 07 at 17 58 55

 

Once I’m done with this page I can click the left arrow icon at the top right of the page to go back to my jumping off page.

 

Sunday, 27 October 2013

Contest

Win Free E-Copies of Visualforce Development Cookbook

This week I’ve teamed up with Packt Publishing to organise a giveaway of my new book - Visualforce Development Cookbook.  

Three lucky winners will receive an e-copy of the book - keep reading to find out how you can be one of these lucky winners.

Overview

0808EN MockupCover Cookbook

  • Write effective controller tests
  • Maintain multiple records from a single page
  • Produce re-usable components for utility functions
  • Create custom charts to visualise single or multiple sets of data
  • Redraw part of a page in response to user input
  • Replace standard components with custom, brand able versions
  • Provide access to data via a public website
  • Allow users to create and retrieve data from a mobile device

How to enter?

All you need to do is head on over to the book page, look through the product description of the book and drop a line via the comments for this post, including your email address, to let us know what interests you about this book. Its that simple!  

Deadline

The contest will close on 3rd November 2013. Winners will be contact by email, so be sure to use your real email address when you comment!

Update 04/11/2013 - the contest is now closed.  Winners will be announced shortly.

Update 06/11/2013  - and the winners have been chosen.

Congratulations:  Dennis Onyango, King Koo, Anil Bathula.  You’ll hear from Packt pubishing via email on how to access your e-copy of my book.

Commiserations to those that entered but didn’t win - watch out for more contests in the coming weeks.

Good luck!

 

Sunday, 13 October 2013

My Dreamforce 2013 Sessions

The waiting is finally over - the agenda builder for 2013 is live.  For those who've been anxiously looking for the times of my sessions (me, at least) - here they are, with the added bonus that I'm a panelist on a third session on the Technical Architect certification. There's also a session that isn't mine, but you won't want to miss - the Developer Keynote.

Ticket to Ride

A 30 minute session in the Mobile Theatre, Moscone West, 1:45-2:15PM on Tuesday 19th November:

"Join Force.com's MVP Keir Bowden as he demonstrates a pair of hybrid applications that allow passengers to download tickets for use even when offline, and drivers to scan the ticket from the traveler's mobile device and register the passenger's presence on the journey. You'll see specific code examples of offline storage, QR code generation, and scanner integration."

Signup link.

Technical Architect Certification: Learn From Our Experts

 A one hour breakout session in room 3024, Moscone West, 4:30 - 5PM on Tuesday 19th November:

"Join us to navigate the pathway to salesforce.com's most difficult, but most prestigious certification: Technical Architect. Discover if you currently have the skills necessary to obtain this certification and if not, how to gain them. Learn from partners who have achieved the certification and the experience, skills and capabilities they leveraged to prepare for the certification process."

Signup link.

Mobilizing Your Visualforce Application with JQuery and KnockoutJS

 A 45 minute breakout session in room 2011, Moscone West, 5:15 - 6:00 PM on Wednesday 20th November:

"Join Force.com MVP Keir Bowden (aka Bob Buzzard) to learn how to mobilize your Visualforce applications. We'll take an existing survey application and make it mobile by creating pages based on the JQuery Mobile framework, replacing stateful controllers with Javascript remoting, and using Knockout.js to manage client-side data."

Signup link.

Developer Keynote: Develop Social and Mobile Apps Faster Than Ever

A 1 hour session in the Gateway Ballroom, Moscone South, 10:30 - 11:30AM on Wednesday 20th November:

With more than one million developers worldwide, Salesforce Platform is the world's leading enterprise cloud platform for building mobile and social apps. Join Adam Seligman, VP of Developer Relations, and the rest of the developer community to hear how any developer can build killer social and mobile apps faster with the latest platform innovations."

Signup link.

Sunday, 29 September 2013

Visualforce Development Cookbook

0808EN MockupCover Cookbook

Regular visitors to this blog may have noticed that my rate of blogging slowed considerably over the summer.  This is because since April I've been writing a book for Packt publishing, the Visualforce Development Cookbook.  This was published on 24th September 2013 and is available for purchase from Packt or a number of stores here.

I'm planning a blog post to cover the whole experience, but as I'm still in the thick of it (we're just entering the marketing phase at the moment) it will be a little while until I'm ready to write that post.

In the meantime, you can follow the book on twitter or like the facebook page - any news or offers will break there first. 

Tuesday, 24 September 2013

Highlight Empty Fields

A question that came up this week was how to highlight to a user that fields in a form don't have a value, but without stopping the form submission or annoying them with popups/confirmation dialogs. Essentially flagging up 'nice to have' fields that are empty, but leaving the required fields with the standard Salesforce decoration and the fields that nobody really cares about alone.

When the form is initially rendered, this is easy enough to achieve through conditional styling, but the problem with this is that it can't change the background when the user populates the field - instead, a form postback is required and even if rerendering is used to minimise the page updates, a full round trip every time a field is changed is a pretty hefty tax on the user.

This seemed like a good fit for jQuery, so I fired up the Force.com IDE and created a simple lead entry page that highlights a selection of empty fields:

Screen Shot 2013 09 24 at 19 26 14

In order to easily identify the nice to have fields, I gave them each an id that started with 'flagEmpty':

<apex:inputField id="flagEmptyFName" value="{!Lead.FirstName}" />
<apex:inputField id="flagEmptyEmail" value="{!Lead.Email}" />

Next, I wrote the function to apply the necessary style class.  This takes all or part of an id, finds any elements containing the id and for each match, checks if the field has value.  If it does, the 'fieldpopulated' class is applied, otherwise the 'fieldempty' class is applied.  When the appropriate class is applied, the other class is removed:

function flagEmpty(theId)
{
  $("[id*='" + theId + "']").each(function(index, ele) {
		if($(ele).val().length > 0)
		{
			$(ele).removeClass('fieldempty');
			$(ele).addClass('fieldpopulated');
		}
		else
		{
			$(ele).removeClass('fieldpopulated');
			$(ele).addClass('fieldempty');
		}
	});
}

 When the page is initially loaded, the id fragment 'flagEmpty' is passed to the function, which finds all of the elements I've marked in this fashion and highlights the background:

flagEmpty('flagEmpty');

Finally, an onchange handler is added to each element with an id containing 'flagEmpty'. This handler extracts the id of the element and executes the 'flagEmpty()' method, passing the id as the parameter:

$("[id*='flagEmpty']").change( function(event) {
	flagEmpty($(event.target).attr('id'));
});

The fields marked as 'flagEmpty' are originally rendered with a yellow background:

Screen Shot 2013 09 24 at 19 29 22

but after filling in a field and moving focus, the onchange handler fires and changes the background to white:

Screen Shot 2013 09 24 at 19 31 26

The Visualforce page is available at this gist

 

Monday, 2 September 2013

MVP Summit 2013

Mvps

This week I attended the 2013 MVP summit in San Francisco.  As my employers, BrightGen, were kind enough to allow me to take three days out to travel and attend, I've written this up on the company Facebook page at: https://www.facebook.com/BrightGen - while you are there, please take a moment to like the page!

 

Sunday, 11 August 2013

Swipe Navigation

I've written a few blog posts in the past around using JQuery Mobile to give a native (well, iOS) feel to web applications. In each of these cases, I've used buttons in the header or footer to navigate around the site and open panels.  These days, users expect to be able to swipe to navigate, even when running web applications through the built-in device browser.

Luckily, JQuery Mobile has a solution to this - if the user touches the screen and moves their finger left or right more than a certain number of pixels while maintaing contact, a swipeleft or swipe right event will be generated. These events can then be handled in custom JavaScript functions to navigate the user to another page.

To demonstrate this, I've created a sample application containing three pages that each displays a picture from my Customer Company Tour Developer Theatre session in May 2013. The header contains the buttons to open a panel and navigate between pages, as shown below:

Page1Page2

This is a single page application, where a single physical HTML page contains a number of logical application pages coded as stacked <div> elements with a data-role of page. The markup for the first page is as follows:

<div data-role="page" id="page1">
  <div data-role="panel" id="aboutpanel" data-theme="b">
    <h3>About</h3>
    <p>Welcome to the Bob Buzzard swipe demo web application.</p>
    <a href="#page1" data-role="button" data-rel="close">Close</a>
  </div> <!-- /panel -->
      
  <div data-role="header" class="header-large">
    <h3>Swipe Demo</h3>
	<a href="#aboutpanel" data-role="button" data-icon="info" data-iconpos="notext">About</a>
    <a href="#page2" class="ui-btn-right" data-icon="arrow-r" data-iconpos="notext">Page 2</a>
  </div><!-- /header -->
    	
  <div data-role="content" style="text-align:center">
    <h1>Getting Ready</h1>
    <div style="margin:auto"><apex:image styleclass="image" value="{!URLFOR($Resource.CCT, 'CCT1.jpg')}"/></div>
    <p><caption>A quick chat with Nick Tran before taking the stage</caption></p>
  </div> <!-- /content -->
    	
</div> <!-- /page -->

To allow swipe navigation from the logical page with the id of page1 to the logical page with the id of page2, I need to handle the swipeleft event when it takes place in page1. The JavaScript for this is straightforward, thanks to the event handling capabilities of JQuery:

$j('#page1').on( "swipeleft", function() {
    $j.mobile.changePage('#page2');
});

An added benefit is that the swipe behaviour works on the desktop too, as the following video attempts to show.  the first part shows navigation using the buttons, while in the second part I simply click, hold and drag the mouse/trackpad left or right.

If you'd like to try this application yourself, its available on my dev org site at the following short link:

http://bobbuzz.me.uk/18lXNSx

or you can scan the following QR code on your mobile device:

Swipe qrcode

The Visualforce page containing the application is available at this gist - note that this page relies on a static resource containing the images, so if you clone it you'll need to replace this with images of your own.

 

Saturday, 20 July 2013

Publisher Actions - Not Just Creating Records

The Summer 13 Salesforce release introduced Publisher Actions - these allow additional chatter actions to be added to the publisher, over and above the standard Post, File, Link and Poll actions.   Some setup is required before you can use Publisher Actions - this is detailed in the Salesforce help here.

There are four types of Publisher Actions - Create and Custom for a specific object, and Global Create and Global Custom.  The Create actions allow a new record to be created directly from the chatter feed.  Create for a specific object allows a new record to be created that is related to the object the feed appears on - for example, to create a new Opportunity from an Account chatter feed where the Opportunity account id is set to the id of the Account the feed appears on. Global Create allows a new record to be created from a feed, but with no relationship to any existing object.  One very useful aspect of the Create actions is that you can specify default values for fields, which goes some way to removing the need for URL-hacking.

The Custom actions allow a Visualforce page to be displayed in a chatter feed - the specific object variant of this means that the standard controller must be used, as the id of the record the chatter feed appears on will be passed to the page, while the Global variant uses a custom controller. An example of a Custom action is to display the location of an account on a map - while you can also achieve this via an embedded Visualforce page on the standard record view, making it a Publisher Action means it is displayed on demand, so those users that aren't interested in that information don't have to wait for the map to render.

Custom actions can also create information. In this post I'll demonstrate how to create a Publisher action to post summary information to a feed.

Here at BrightGen, we have a Service Management offering for Salesforce. Rather than waiting until the end of the month to find out how we are doing for a particular customer, its useful to push information out on a regular basis. In this post I'll demonstrate how to create a Custom action that posts a snapshot of case information to an account record.

The snapshot will contain details of cases created this month, cases closed this month and the current number of open cases for the account.

As this is an object specific action, the page uses the account standard controller and the additional functionality is provided by a controller extension. This executes a number of queries to get the case information required.  The snippet below determines the number of cases closed this month:

Date startDate=System.today().toStartOfMonth();
Date endDate=System.today().addMonths(1).toStartOfMonth();
List<AggregateResult> ars=[select COUNT(Id) closedCount
		 	   from Case
		 	   where Status='Closed'
		 	   and AccountId=:accId
			   and ClosedDate>=:startDate
		   	   and ClosedDate<:endDate];

Integer result=0;
if (ars.size()>0)
{
	AggregateResult ar=ars[0];
	result=(Integer) ar.get('closedCount');
}

Once all the information is gathered, this is posted to the feed programmatically:

FeedItem item=new FeedItem();
item.ParentId=accId;
item.body='Service snapshot\n' +
          'As of ' + System.now().format('dd/MM/yyyy')  +
          '\nNew Cases : ' + getNewCases() +
          '\nClosed Cases : ' + getClosedCases() +
          '\nOpen Cases : ' + getOpenCases();
		
insert item;
		
ApexPages.addMessage(new
ApexPages.Message(ApexPages.Severity.INFO, 'Snapshot Posted'));
posted=true;

Note the final line of this snippet, which sets the boolean property posted to true - this property is used in to automatically refresh the page, otherwise the post does not get displayed to the user.

The page simply displays the details and provides a button for the user to post the details to the feed:

  <apex:pageBlock title="Service Snapshot">
    <apex:pageBlockButtons location="bottom">
      <apex:commandButton value="Post to Feed" action="{!post}" />
    </apex:pageBlockButtons>
    <apex:pageBlockSection columns="1">
      <apex:pageBlockSectionItem >
        <apex:outputLabel value="Closed Cases" />
        <apex:outputText value="{!closedCases}" />
      </apex:pageBlockSectionItem>
      <apex:pageBlockSectionItem >
        <apex:outputLabel value="New Cases" />
        <apex:outputText value="{!newCases}" />
      </apex:pageBlockSectionItem>
      <apex:pageBlockSectionItem >
        <apex:outputLabel value="Open Cases" />
        <apex:outputText value="{!openCases}" />
      </apex:pageBlockSectionItem>
    </apex:pageBlockSection>
  </apex:pageBlock>

The automatic refresh (which is based on the technique described in this blog post) is provided by the following, conditionally rendered, JavaScript:

  <apex:outputPanel rendered="{!posted}">
   <script>
      window.top.location='/{!Account.id}';
   </script>
 </apex:outputPanel>

The code for the controller extension can be found in this gist, and the Visualforce page in this gist. You'll need to save these into your Salesforce edition to complete the next steps.

Next, create a publisher action for this page - navigate to Setup -> Customize -> Accounts -> Buttons, Links, and Actions and click the New Action button.

Scroll down to the Chatter Feed section and fill out the details as shown in the screenshot below and click the Save button.

Screen Shot 2013 07 20 at 13 14 52

(note that this assumes you have saved the Visualforce page with the name ChatterAccountSnapshot - if that is not the case, pick the name that you saved the page under from the Visualforce Page pick list).

To add the Publisher Action to the chatter feed, edit the page layout for the Account record.  Scroll down to the Chatter Feed section - if you see the following text, click the 'overidde the global layout' to make the actions editable for that layout:

Screen Shot 2013 07 20 at 13 21 34

The Chatter Feed section will then contain the following standard actions:

Screen Shot 2013 07 20 at 13 23 19

Next, select the Actions entry from the menu at the top left of the page layout editor:

Screen Shot 2013 07 20 at 13 24 50

Drag the Snapshot entry from the right hand side to the Chatter Feed section - note that this can be placed anywhere in the list of actions:

Screen Shot 2013 07 20 at 13 25 40

and finally save the page layout.

 

Now lets take a look at the Publisher Action in action:

Navigating to an account view page shows the new Snapshot action in the Chatter Publisher. Clicking the Snapshot button displays the Visualforce page with the case detais:

Screen Shot 2013 07 20 at 13 27 01

Clicking the Post to Feed button posts the information to the chatter feed and then refreshes the page to display the post to the user:

Screen Shot 2013 07 20 at 13 29 17

 

Saturday, 29 June 2013

Meetups and More

Meetup with Apex Product Managers

On 20th June the London Salesforce Developers welcomed Apex Product Managers Andrew Waite and Josh Kaplan to our Meetup at SkillsMatter. There was a real buzz of anticipation for this event and it didn't disappoint.  After a short run through of some of the Apex specific enhancements for the Summer 13 release, and a sneak peek at a pre-release version of the chatter mobile app, we then entered a no-holds barred Q&A session.  One issue with this sort of informal session is that it can descend into a forum to air grievances and bad experiences - although we flirted with this, in general we managed to maintain the positive vibe that we all had at the start of the meeting. Something I always enjoy when I have a chance to speak to the product managers is finding out which enhancements that I view as simple and can't understand why they aren't complete already are actually incredibly hard to do and require a complete overhaul of a section of the platform. 

After the event Andrew and Josh joined us for our traditional post-meetup drinks, where we were able to continue pitching our ideas, as well as shooting the breeze about Apex, Force.com and Salesforce in particular.

The session was recorded by the good folks at SkillsMatter and is available to watch here.

If you aren't already a member of the Salesforce London Developers Meetup group, you can join via the meetup site.

Second Introduction to Force.com Workshop

After the success of the first Introduction to Force.com workshop in April, we ran another one of these on 25th June, led by myself with hosting and assistance from John Stevenson (@jr0cket), one of the Salesforce Developer Evangelists here in the UK.

The format was broadly the same as before - get a group of people keen to learn about Force.com together at Salesforce offices and go through the Force.com workbook as a group, with hopefully useful tips and advice from the hosts as the evening progresses.  April Nassi, Developer Program Manager at Salesforce, sent us through a pack of hardcopy workbooks and cheat sheets which were a useful addition - not only does it give the attendees something to take away, it meant that those who were struggling a little could catch up at their own pace by following the instructions in the book when we took a break at the end of each chapter. April also sent through some schwag so some of the attendees went away with new Force.com wear.

We'll be running more of these workshops in the future, so keep an eye on the meetup group for the next one - if you'd like to help out or host one, let us know.

 

Thursday, 30 May 2013

Book Reviewers Wanted

NewImage

Earlier this year I acted as a Technical Reviewer for the CRM Admin Cookbook.  This means its a little tricky for me to review in this blog, as I obviously ensured everything was perfect and there was no way to improve it :)

Packt Publishing are offering free copies of Salesforce CRM Admin Cookbook : http://www.packtpub.com/salesforce-crm-admin-cookbook/book in exchange for a review either on your blog or on the title’s Amazon page.

The book covers the following areas:

  • Building home page components and creating custom links to provide additional functionality and improve the Home Tab layout
  • Advanced user interface techniques to improve the look and feel of Salesforce CRM with the presentation of graphical elements
  • Exposing hacks and hidden features to enhance and override native aspects of Salesforce CRM
  • Automatic data capture and improving data quality in Salesforce CRM
  • Implementing an approval process to control the way approvals are managed for records in Salesforce CRM
  • Increasing productivity using tools and features to provide advanced administration
  • Configuring and installing Salesforce for Microsoft Outlook email integration
  • Integrating Salesforce CRM with external online tools to provide enhanced functionality and extend the power of Salesforce CRM 

If you’re a Salesforce CRM user or interested in getting to grips with it, this is a good way to bag yourself a free guide (current retail price £28).

Free review copies are available until Monday 5th June 2013

If you’re interested, email Harleen Kaur Bagga at: harleenb@packtpub.com

 

Sunday, 26 May 2013

Mobile Web Apps with HTML5 and JQuery Mobile

At the Salesforce Customer Company Tour in London on 2nd May I presented a session in the Developer Theatre on Building Mobile Web Apps with HTML5 and JQuery Mobile, using a Todo list application as the example.

Yes, We've Got a Video

The session wasn't recorded, but since then I've recorded a replay of it which is available on youtube:

Get the Source

I've made this application available as an unmanaged package at: https://login.salesforce.com/packaging/installPackage.apexp?p0=04ti0000000VGqW

The main contents of this package are:

  • Custom Todo app
  • Custom objects for Group and Task, with tabs
  • Visualforce TodoJQM page and tab, also called TodoJQM
  • A few static resources for JQuery Mobile
  • Visualforce components for the custom CSS and JavaScript for the Todo pages - obviously in a  production system these would be in static resources and the JavaScript would be minified.  However, in this case I've put them into custom components so that they can be viewed without downloading and easily changed.

Feel free to post any questions you might have in the comments section below.

 

Tuesday, 30 April 2013

Community Service

This has been a busy few weeks in the Salesforce developer community; mobile dev week arrived at short notice and a week later it's the Salesforce Customer Company Tour with the attendant Dev Zone, which is the main attraction of the event if you are a developer.  As I've been or am getting involved in a number of these events, I thought I'd write a short blog about them.

Intro to Force.com

This took place on 11th April at the Salesforce offices in Tower 42.  Lead by Frances Pindar (@radnip) with assistance from myself, it was a couple of hours or so session to introduce the next wave of Force.com developers to the platform, working through the exercises in the Developerforce Force.com workbook in a group environment. The advantage to using the workbook is that the attendees are then able to continue with the exercises once the session is finished, given that it isn't feasible to get through the whole thing in the time allowed.

Here's a picture of everyone hard at work:

Intro

I'm planning on running another one of these sessions in the next few months, so if you're near London and are interested in learning more about the Force.com platform, stay tuned.

Mobile Developer Week Meetup

As part of Salesforce Mobile Developer week, the London Salesforce Developer's Group had a meetup at the new T'Quila offices above Smithfields.

Rob Woodward of Salesforce gave us an overview of the new Mobile Packs and demonstrated a todo list application based on the Angular JavaScript framework.

I was up next, and demonstrated a couple of applications that I'd built using the Salesforce mobile SDK and Apache Cordova under the heading of "Ticket to Ride".  These consist of a ticketing application, that allows users to pull down tickets that they have purchased onto their mobile device, stores them in the Salesforce smart store and allows offline access to the ticket details and a QR code.  The companion application is a driver app that scans the ticket, confirms it is being presented for the correct service and updates the ticket with a used indicator and timestamp.  Force.com provides the object storage and the applications access this via the Rest API.

The powerpoint presentation for this talk can be downloaded here. If you'd like to see it in action, come to the BrightGen stand at the Salesforce Customer Company Tour and I'll be happy to show you.

Just to prove I'm not making it up, here's a photo from the event courtesy of @Anup:

Mobile dug

Salesforce Customer Company Tour

On 2nd May the Salesforce Customer Company Tour comes to London. My company, BrightGen are a Platinum Sponsor this year, so we'll have a big stand and plenty of people to man it.  This event incorporates a Dev Zone and I'll be giving a Developer Theatre session on Building a mobile web app with HTML5 and JQueryMobile, from 4:15 to 4:45 - hopefully I'll see some of you there. If you haven't signed up yet, you can do so here.

Saturday, 23 March 2013

Book Review - Force.com Tips and Tricks

Disclaimer - I haven't purchased this book - I was sent a copy to review by the publisher.


4743EN Cover

Force.com Tips and Tricks is written by a couple of my fellow Force.com MVPs, Abhinav Gupta and Ankit Arora. It is aimed at both developers and administrators and packs a lot of information into the 200+ pages.

For Administrators

Administrators get a whole chapter dedicated to tools to make their lives easier, including those that come with the platform as standard (e.g. data loader) and a long list of code share and app exchange packages. There is also a deep dive on Salesforce analytics, covering topics such as custom report types, adding formulae to a report and setting up analytic snapshots - all of which are important for administrators wanting to get the most of the platform.  

For Developers

The book starts off with an introduction to the Force.com platform, covering key concepts for anyone starting out with Force.com development such as multi-tenancy, the metadata driven development model and governor limits. Later chapters take you through setting up development environments and migrating changes between those environments, useful tools for developers from both Salesforce and the wider community, and concludes with a couple of chapters that give great advice on writing flexible and scalable Apex and Visualforce code, as well as tips for troubleshooting when code isn't performing as expected.

For Both

For administrators wanting to understand more about maintaining and extending Force.com applications, the developer specific sections will prove very useful.  By the same token, development in Force.com starts with clicks rather than code, so the administrator-centric chapters are something that every developer should read.

 

This book isn't a training manual for Force.com development - where I think it would have been really useful in the past was as I was starting to carry out serious development with the platform.  I understood enough about the basics to start being productive, but when I was trying something new, I'd be searching the discussion boards and reading blogs to get more information.  With a book like this to hand I'd either have the information that I required right there, or be pointed in the right direction to find out more with minimal effort.  

 I always hope to learn something I didn't know from these books, and this time it was a number of the tools available, particularly to enforce CRUD and FLS when running in the system context.  Its certainly a useful addition to my Salesforce library and one I'm sure I'll be coming back to regularly.

Saturday, 2 March 2013

London Salesforce Developers February Meetup

Feb meetup 1 jpg large 

The February London Salesforce Developers Meetup took place this week, with 30+ people (Kingmakers!) attending. While the usual suspects were in attendance, there were quite a few new faces, which bodes well for the group.

John Stevenson gave an update on the events that are being run for the community - I can definitely say that the Git and Github Kickstarter Workshops are popular, as every time I receive a notification that one is available, by the time I click through to the details its full!

John Mahoney gave a demonstration of the steroid component library - I didn't get to see as much of this as I'd have liked to, as I was advising someone on customer portal setup in the corridor!

And in a startling development, which nobody could have predicted, I gave a short talk on Salesforce Certification!  As I served as a judge on the EMEA Technical Architect Review Board in January, I'm in a tricky position when talking about this certification. I've spent the last year trying to spread the word of how to pass the board, but that's clearly no longer appropriate. I was therefore fortunate to be joined by my colleague, and newly Certified Technical Architect, Chris Eales (@theEalesie), who talked about his experience and gave advice on how to prepare for the board, plus details of the mechanics of the process.  

The slide deck for this talk can be accessed here. (On a related note, I've added a Salesforce Certfication page to this site that contains links to all my certification related posts - you can access this from the right hand sidebar).

The meetups take place every month and include beer, pizza and plenty of time for networking.  If you haven't made one yet, we'd love to see you. Make sure to join the Meetup group to get notified of all upcoming events.
 

Sunday, 3 February 2013

Improved Force.com Discussion Boards

This week saw a welcome revamp of the Force.com discussion boards.  There's been quite a bit of debate recently about the relative merits of the discussion boards versus the Salesforce Stack Exchange beta site.  Personally, I think there's room for both as they serve different purposes.  I also think that the more channels for the community to work together the better - for one to succeed there's no reason that others have to fail.  I realise that this is an area of religious wars for some people, so bear in mind that this post represents my opinions on this - if you disagree, there's nothing stopping you writing up your own post!

My thoughts of some of the strengths and weaknesses of these sites are as follows:

  • Gamification.  
    This is an area where Stack Exchange is very strong.  There's a plethora of badges that can be earned, each user has a reputation score based on up or down votes for questions and answers, answers marked as accepted etc.  Another interesting ranking is a user's acceptance rate.  This indicates how often a user is accepting answers, and a low score indicates they are asking for a lot of help but not marking answers solved for the benefits of others.  The Force.com boards, by contrast, have previously ranked users by the number of posts that they have made - e.g. 150 for Trusted Contributor and topping out at 500 for Super Contributor.  Where this is lacking is rating the quality of posts - I've seen at least one instance in the past where a user has earned Trusted Contributor by making 150 'please write my test case' style posts.  This is an area where the revamp has improved matters through the introduction of kudos - you can read about how this works in detail at the developer relations blog post.  What matters though, is the leader board is now focused on kudos rather than tags, which should lead to a lot less meaningless tags on messages.
     
  • Participation.
    This is an area where the Force.com boards win hands down - the number of users and volume of posts far exceeds the Stack Exchange site.  Of course boards have the advantage that they've been around longer, but I feel there is more to it than this, as I'll cover in my next point.  The quality of the posts on the boards is hugely variable though, whereas the up/down voting system on Stack Exchange tends to ensure that the quality of posts is high - users can also vote for questions to be closed if they feel they are asking for help with homework, could be answered by a simple search etc.
     
  • Support for Newbies.
    This is likely to be my most contentious point.  I don't think that Stack Exchange is a good place to go for help if you are starting out with Force.com.  Its good for advanced users who have hit a roadblock, researched the problem and been unable to find a solution. Its also reasonably good for intermediates who know there is a specific problem with their code but can't figure out exactly where its going wrong.  Where it really falls down is for those who don't know where to start.  Post up a question asking for basic, generic help and you'll be down voted and closed very quickly. There's also a touch of mob mentality or swarming about this - questions can be quickly hammered down, but the voting up doesn't seem to happen with the same pace.  The boards are much more tolerant of beginner questions - sometimes there are pages of responses attempting to get to the bottom of what the problem actually is before even attempting to solve it.  In case you think I'm being too hard on Stack Exchange, the format doesn't really support this kind of question discovery, so I can understand why some questions are treated in this way.  I often describe the boards as the place to ask bad questions, or a home for those who don't know what they don't know.
     
  • Existing answers.
    This is another area where Stack Exchange cannot hope to compete, due to the amount of time that the boards have been around.  You'll probably have to work a little harder to find answers to esoteric questions on the boards, due to the variable quality of the posts, but there are tens of thousands of solutions in there.  In fact, I'd lay money that before posting on Stack Exchange most, if not all, users will have searched the discussion boards. And to be fair and equitable, if they've done that research before posting on Stack Exchange their question will be upvoted and answered.

Reading the above, you might think that I'm not a fan of Stack Exchange - in fact, nothing could be further from the truth, I'm on it most days and I'm in the top 5 for reputation.  There's a place for both sites and, while there is some overlap, they generally serve different purposes.  That said, I do prefer the discussion boards - partly as I have four years history with them, but mainly because I'm interested in getting people started on the platform and improving beginners. 

But enough about how the boards fit into the wider landscape, the important point is that they are getting some love, having been left to fend for themselves for a while  Its great that the board owners are doing this, but there are plenty of things that we participants can do to improve things.  To that end, here's some of my thoughts on board etiquette:

  • If you post a question and you get a solution, mark it.  
    It can be disheartening to put hours of effort into drilling down to find the actual problem and coming up with a solution, only to find the original poster then moves on to their next question without a backward glance.  It also means that another user researching a similar problem doesn't know that there's a solution somewhere in the thread.

  • Don't beg for marks in your signature.
    Having said that you should always mark solutions, I can see why some people don't.  If I posted a question and every response, even a 'tell me more', demanded that it be marked as the solution, I'd start to tune out the whole solution side of it.

  • Don't just post up requirements.
    Its fair enough to post up a question asking how to get started with a particular piece of functionality (e.g. how can I submit a form via javascript), but if you are copying and pasting your requirements/homework, (e.g. 'I have to build a system that manages projects') , your post is likely to remain alone with zero responses. My response to these is often 'good luck' or 'thanks for keeping us in the loop'!

  • Have a go first.
    If you have any idea where to start, have a stab.  Nobody gets flamed for posting bad or incomplete code.  It shows that you are trying to help yourself and, certainly in my case, is likely to make people want to help you.

  • Don't post pages of code.
    If you do this, really what you are saying is 'figure out my code and tell me what is wrong with it', which is a big ask.  Try to narrow down where you think the problem is and post discrete section.

  • Post some code!
    You have to give us a chance!  If you post up that you are getting a null pointer exception with no context and no code, its highly unlikely you'll get a solution.  If an error points to a particular line of code, there's no excuse for not posting it.

  • Don't contact helpful people directly
    If someone is knowledgeable and solves a few problems for you, don't ask for direct contact details.  Essentially what you are saying there is you'd like free, private support from them when it suits you.  Most of us on the boards have day jobs where we charge people for our skills and experience, so you have the option to pay for an engagement if you need a personal service.  Receiving message after message asking for "a way to get hold of you so we don't have to wait" can quickly become irritating,

  • Don't send questions in private messages.
    Think about what you are doing here - you've taken advantage of the community to find someone knowledgeable, but then you don't want the discussion to be public to help others.  I don't even respond to these messages any more I'm afraid, as I get so many of them.
     
  • Don't use it as your blog.
    Every now and then we'll get some blog posts under the guise of discussion posts.  I can see why people do this, as it gives a blog a huge, unearned audience.  The home for these is Developerforce, which accepts blog posts and code recipes from the community. 

  • Be polite.
    If you think a question or response is bad, there's no reason to be rude.  This is, thankfully, very rare on the boards.  Its always worth restating though, as the negative effects can be significant - I've been involved with some communities  where its felt like there was active effort to discourage anyone apart from experts from posting, and to do this in a particularly unpleasant manner.  If you think the question is bad, try to find out what the user is trying to ask.  If you think an answer contains bad advice, frame your criticism with the correct solution - remember, you might be wrong and they might be right!

  • Get (more) involved.
    Most of the above are about what not to do.  If none of those apply, keep doing what you are doing, but do more of it.  If you are relatively new to the platform but starting to get the hang of it, start posting answers - nobody will jump on you if you make a mistake.

We have a great community, so lets do all we can to grow and improve it for everyone.

 

Saturday, 12 January 2013

Send SMS Messages from Force.com

One of the app exchange offerings of my company, BrightGen, is BrightSMS. This is a free app that allows the sending of single or bulk SMS messages to UK or international numbers - you only pay for the messages that you send.

Earlier versions of BrightSMS provided functionality to send SMS messages from Visualforce pages, based on the user typing the message in and selecting a mobile number/contact/lead as the message recipient.   Version 3.0 introduces support for sending SMS from @future methods, which means that messages can be sent from triggers when record details change.

Getting Started

Installation and setup is covered in the Installation Guide.  Once you have installed the package, follow the User Guide to register an account.  The first account you register in your Salesforce org gets 10 free messages.  Note that the account is tied to a mobile phone number and you can only register a number once - so the 10 free messages is a single shot deal.  Once I've been through the installation I usually send myself an SMS through the packaged pages to confirm its all working.

The Code

My example trigger is on the case sobject, and sends an SMS to the contact associated with the case when the status changes, as long as the contact has supplied a mobile phone number. 

trigger Case_au on Case (after update)
{
	List<Id> caseIdsToSMS=new List<Id>();

	// get the contact ids
	Set<Id> contactIds=new Set<Id>();
	for (Case cs : trigger.new)
	{
		if (null!=cs.contactId)
		{
			contactIds.add(cs.contactId);
		}
	}
	
	Map<Id, Contact> contsById=new Map<Id, Contact>();
	contsById.putAll([select id, MobilePhone from Contact where id in:contactIds]);

	// pull back the contact and check the mobile number is supplied
	
	for (Case cs : trigger.new)
	{
		if (null!=cs.ContactId)
		{
			Contact cnt=contsById.get(cs.ContactId);
			if ( (cs.Status != trigger.oldMap.get(cs.id).Status) &&
		    	 ( (null!=cnt.MobilePhone) && (cnt.MobilePhone.length()>0) )
		   	)
			{
				caseIdsToSMS.add(cs.id);
			}
		}
	}
	
        // delegate to a future method, as callouts aren't allowed in triggers
	CaseUtils.SendCaseUpdatedSMS(caseIdsToSMS);
}

and the future method that sends the SMS:

public with sharing class CaseUtils
{
    @Future(callout=true)
    public static void SendCaseUpdatedSMS(list<Id> csIds)
    {
	for (Case cs : [Select CaseNumber, Contact.MobilePhone, Contact.FirstName, Contact.LastName, Status from Case where Id IN : csIds] )
	{
	BG_SMS.BrightSMSSubmitSMSMessage bsms = new BG_SMS.BrightSMSSubmitSMSMessage();
		bsms.brightSmsAccountId = 'AccountIdGoesHere';
		bsms.rateCode = '1';
		bsms.senderID = 'BrightGen';
		bsms.mobileNumber = cs.Contact.MobilePhone.trim();
		bsms.smsMessage = 'Hi ' + cs.Contact.FirstName +
				  ', Just to let you know the status of your case {' +
				  cs.CaseNumber +
				  '} has been changed to ' +
				  cs.Status;
		bsms.submitSMSMessage();
   	}
    }
}

Replace AccountIdGoesHere with the id of the SMS account that you registered in the Getting Started session.  The senderId will be displayed as the sender of the SMS, so replace this with something specific to you or your company.  Finally, the smsMessage contains the message text that will be sent.  BrightSMS allows you to send up to a maximum of 459 characters, but these will be sent as 3 individual SMS messages, as the maximum individual SMS message size is 153 characters.

Note that as there are a limit of 10 callouts per transaction, if there are more than 10 case updates to be sent, this code will produce an error.  If you have a large amount of messages to send on a regular basis, its better to use Batch Apex and split the processing up into multiple transactions.

Sending Messages

First up, I create a contact in my org and supply a mobile number (note, this is not my real mobile number):

 

Screen Shot 2013 01 12 at 11 14 16

 

I then create a case and associate my contact record with it:

Screen Shot 2013 01 12 at 11 18 23

 

If the status of the case is then changed from New to Working, I automatically receive an SMS informing me of the change:

IMG 0705