Skip navigation

Category Archives: UI

One of the most hyped, and in my opinion least documented features of the Summer 13 release of Salesforce is custom Chatter Publisher Actions. Various Salesforce bloggers have written about how easy it is to create and use regular Chatter Publisher Actions, which, basically, just let you quickly create records related to a primary record from your Chatter Feed (e.g. from an Account’s Chatter feed, creating a Contact or Opportunity, without having to go down to the corresponding related lists. For one excellent discussion of regular Publisher Actions, see Daniel Hoechst’s post. The release notes also give examples of regular Publisher Actions, so I won’t go into any more depth on them here.

However, neither the release notes, nor any other bloggers, nor Salesforce themselves in their recent release webinars, have yet shown any working examples of custom Publisher Actions. What are they? Here’s a brief overview:

  • They come in two flavors: Global and Object-Specific.
    • Global custom Actions: can be added to any Chatter Publisher Layout, anywhere.
    • Object-specific custom Actions: can only be added to one specific object’s Publisher Layouts.
  • You create them using Visualforce Pages.
    • For Global custom actions, your Visualforce page must either have no controller, or a custom controller that is NOT an extension.
    • For Object-specific custom Actions, your Visualforce page must use the standard controller of the object you’d like to use the Action with.
  • Chatter implements custom Actions using iFrames.
    • When creating a new custom Action, you must specify a height for your Action — this is setting the height of the iFrame in which your page will be included.
    • This iFrame is always run from within the Visualforce Page’s domain. So, if it’s a local VF Page, the domain will be something like “c.na14.visual.force.com”, whereas if the page is included in a managed package, the domain will be something like “skuid.cs15.visual.force.com”.

So custom Publisher Actions are, by virtue of having to be created through Visualforce Pages, and being embedded in iFrames, already more complicated to develop. This begs the question: what sort of “Action” would be compelling enough to warrant going through this hassle? What’s a good use case?

A use case: create a Contact and linked Opportunity Contact Role all at once, from an Opportunity

Many of the examples I thought of all fall into one category: creating records of multiple objects all at once, in particular, creating a junction object and creating the joined record if it doesn’t exist yet. For example, from an Opportunity record, you often want to create a new Opportunity Contact Role, but can’t because the related Contact doesn’t exist yet. Or, in a hypothetical iTunes-on-Force.com app, from an Album’s page, create a “Genre Association” junction object as well as a new Genre if it doesn’t exist yet.

I finally settled on the “Create a Contact and Opportunity Contact Role all at once” example.

Making it happen

The first step in creating a Custom Publisher Action is to create the Action’s associated Visualforce Page. For this example, my Custom Action is specific to the Opportunity object, so our Page must use the Opportunity Standard Controller. As for the body of the page, since I’m using Skuid, I just need to specify the name of a Skuid Page I want to include, which we’ll build later.

VisualforcePage_ForFeedAction

Now that we have a Visualforce Page associated to the Opportunity standard controller, we can create our Action!

The first step is to go to the newly-reorganized Opportunity “Buttons, Links, and Actions” section. In Summer 13, Salesforce has consolidated Custom Buttons, Custom Links, Standard Actions and Overrides (e.g. the place where you can override what happens for the “View”, “Edit”, “New”, and “Tab” action for each object), and the new Chatter Publisher Actions, into this one list for each Object.

Click on “New Action”, and then enter the following:

ButtonsLinksAndActions NewAction

Notice two things:

  1. The “Height” attribute corresponds directly to the height of the iFrame which will embed your Visualforce page
  2. The “Label” is the actual label that users will see for your new Publisher Action, e.g. they’ll see “Link”, “File”, “Post”, “Poll”, and “Add Contact”.

Now that we’ve created the action, we need to actually add it to one of our Opportunity page layout’s Publisher Actions area. By default, all objects will inherit from the default “Global” Publisher Action area, so we’ll need to explicitly “break” this inheritance to allow the set of Publisher Actions that are displayed on our Opportunity layout(s) to be different than the Publisher Actions for all other objects. TO do this, click “override the global layout” in the “Publisher Actions” area of one of your Opportunity page layouts.

OverrideTheGlobalLayout

Next, drag in the “Add Contact” Publisher Action we just created, and position it as you’d like relative to the other Publisher Actions:

DragInNewAction

Once we save the Page Layout, we’re ready to roll! This Publisher Action should show up in our Opportunity page’s Chatter feed. Because we positioned it last in the list, and there’s more than 4 Publisher Actions, our Action shows up under the “More” area:

SelectingThePublisherAction

Now, we’ll show how we built this with Skuid later, but here’s what our custom Publisher Action actually looks like:

PublisherAction_InAction

We’re presented with two columns of data. In the first column, we enter some bare bones info for a new Contact record we want to create that’s associated with this Opportunity’s Account. In the second column, we define the Opportunity Contact Role. Once we click Save, what Skuid will do is the following:

  1. Create a new Contact record using the fields we filled out, with the AccountId of the Opportunity in context (“Acme”)
  2. Create a new Opportunity Contact Role record, using the fields we entered, and associated to the Opportunity in context (“Acme”) and our newly created Contact (“Bilbo Baggins”)
  3. Create a new post in this Opportunity’s Chatter Feed describing what just happened.
  4. Refreshing the page — we’ll describe why this is necessary in a second.

In the Skuid page I built in place of the standard Opportunity detail page, I display Opportunity Contact Roles in a “Key People” tab. As you can see, a new “Bilbo Baggins” contact record was created, and an Opportunity Contact Role record linking him to the “Acme – 500 Widgets” Opportunity was created as well.

ContactRole_CreatedByFeedAction

Finally, a new FeedItem was created that records the addition of the new Contact and his Role:

FeedItem_CreatedByAction

The Skuid page that made the real magic happen

Getting the actual Publisher Action configured is pretty quick once you’ve got a Visualforce Page on hand to use. But it’s this Visualforce Page, of course, which has to do all the hard work to make the custom Publisher Action useful.

Using Skuid as the content of the Publisher Action made the process very fast and fairly painless. In a nutshell, here’s what I did:

(1) Create a new Skuid Page using the “New Page” template on the Contact object.

The “New Page” template, here, scaffolds a basic Skuid Page for use in creating brand new Contact records. It sets up a Model on the Contact page with “Create Default Row if None Exists” pre-checked, and adds a Field Editor component with the FirstName and LastName fields already thrown in:

NewSkuidPage

ContactModelProperties

(2) Add in 3 additional Models: on the Opportunity, OpportunityContactRole, and FeedItem objects:

AddlModels

The Opportunity Model grabs the Opportunity record in context from the page, using the “id” URL Parameter. We then grab the AccountId lookup field from the Opportunity, so that we can use this to automatically prepopulate the AccountId of our newly-created Contact:

OppConditions OppFields

For the Opportunity Contact Role model, we pull in the Role and IsPrimary fields so that we can display them in the page, and we add 2 “Model Merge” Conditions to associate the new record to the context Opportunity and the newly-created Contact:

OppCRConditions

Finally, for the FeedItem model, we add a single condition to automatically associate the new FeedItem to the Opportunity in context, and we add in the Body field, which we will edit in our Save action:

FeedItemConditions FeedItemFields

(3) Place 2 Field Editors, one each for the Contact and ContactRole Models, in separate Panels:

PanelSetLayout

(4) Embed this Panelset within the first step of a new Wizard Component

-We could have used a PageTitle component instead, but we do this because it allows for easy transition into multi-step wizards. We could easily have put the Opportunity Contact Role fields within a separate step of the wizard, and had a “Next Step”/”Previous Step” sequential navigation here.

Wizard

(5) Create a custom Inline (Snippet) to use for our “Save All” Wizard Step action

Skuid’s standard wizard action types almost suffice here for achieving everything we want to do, but we want to automatically populate the Body field of our new FeedItem, which takes a little code, and,  because we’re using this Page as a Chatter Publisher Action, which is run in an iFrame, we run in to a Gotcha involving using the URL Redirect action, as we need to reload the location of our frame’s parent, not the frame itself (window).

First, we customize our Action to execute a Snippet called Save All:

SaveAllStepAction

Finally, we create the Inline (Snippet) itself:

InlineSnippet

//
// Get our Models
var contactModel = skuid.model.getModel('ContactData'),
    contactRoleModel = skuid.model.getModel('ContactRole'),
    feedItemModel = skuid.model.getModel('FeedItem');

// Get the first rows in each of our Models
var contact = contactModel.getFirstRow(),
    contactRole = contactRoleModel.getFirstRow(),
    feedItem = feedItemModel.getFirstRow();

var bodyText = 'Added new ' + contactRole.Role
    + ' to this Opportunity: '
    + contact.FirstName + ' ' + contact.LastName
    + ' (' + contact.Title + ').';

// Update the Body of our new Feed Item
feedItemModel.updateRow(feedItem,'Body',bodyText);

// Save our Models,
// and when we're done,
// refresh the iframe's parent

skuid.model.save(
    [contactModel,contactRoleModel,feedItemModel]
,{ callback: function() {
    parent.location.reload();
}});

In the Snippet, we do the following:

  1. Get references to 3 of our Models
  2. Get references to their first rows
  3. Populate the “Body” field of the FeedItem model’s first row with the Role field from our OpportunityContactRole record and the First and Last Name fields from our Contact record
  4. Save all 3 of our Models in sequence, starting with the Contact model (VERY important) so that the later models can use the newly-created Contact’s Id to populate their new records.
  5. Once the save is done, we reload our parent window. This is necessary because this Skuid page will be stuck into an iFrame by Chatter.

Thorny Gotchas, and the undocumented Chatter Publisher API

The basic buildout of this took less than 30 minutes — seriously! However, we spent some time trying to work out how best to “talk” to the Chatter Publisher using the undocumented Chatter Publisher API — an effort which ended in nothing but frustration.

The thorny gotchas to avoid are mostly related to the fact that custom Publisher Actions are implemented using iFrames:

  1. Any  JavaScript you write in your child Visualforce Page will be unable to talk to the parent page UNLESS both your page and the page containing the Chatter component are in the same domain, due to cross-domain script issues — which is impossible unless BOTH pages are Visualforce. Consider these scenarios:
    1. Chatter Feed in “na15.salesforce.com”, Custom Action VF Page in “c.na15.salesforce.com” — BLOCKED.
    2. Chatter Feed in “c.na15.visual.force.com”, Custom Action VF Page in “skuid.na15.visual.force.com” — BLOCKED.
    3. Chatter Feed in “c.na15.visual.force.com”, Custom Action VF Page in “c.na15.visual.force.com” — SUCCEEDS.
    4. Chatter Feed in “skuid.na15.visual.force.com”, Custom Action VF Page in “skuid.na15.visual.force.com” — SUCCEEDS.
  2. We couldn’t find a way, using the undocumented Chatter API, to “refresh” the Chatter Feed to show our newly-created Feed Item, so we had to do a full page refresh to get the Chatter Feed to show this. We tried the following methods, none of which worked:
    1. chatter.getPublisher().resetPublisher()
    2. chatter.getPublisher().submit()
    3. chatter.getFeed().refresh()
    4. chatter.getFeed().showNewUpdates()

We would have LOVED to use one of these methods in our Skuid “saveAll” Snippet, but we’ll have to wait for Chatter to document its API.

Screen shot 2013-05-15 at 1.04.16 PM

Advertisements

To help foster an ongoing conversation among Salesforce ISV and OEM partners — aka developers of Salesforce AppExchange apps — I started this discussion on the Salesforce ISV Partners LinkedIn group, which I encourage fellow ISV’s/OEM’s to join:

Let’s pool our thoughts – best practices for Salesforce ISV/OEM app development

One of the best practices I brought up was the need to properly “protect” or “sandbox” your application’s references to external JavaScript libraries within complex “mash-up” pages that include JavaScript code written by various other ISV’s / OEM’s / consultants / developers, as well as underlying Salesforce.com JavaScript code.

These days, more and more apps being developed on the Salesforce Platform rely heavily on external JavaScript libraries such as jQuery, jQuery UI, jQuery Mobile, ExtJS, Sencha Touch, KnockoutJS, AngularJS, Backbone, MustacheJS, Underscore, JSONSelect, etc. Leveraging these libraries is definitely a best practice — don’t reinvent the wheel! As jQuery quips, “write less — do more!” As a Salesforce consultant, I think, this is generally the goal 🙂

Problems emerge, though when multiple ISV’s include different versions of these JavaScript libraries as Global Variables within their Visualforce Pages or Visualforce Components — because whichever version is loaded last will, by default, overwrite the earlier version. This makes it very difficult to mash-up / integrate Visualforce Components or Pages from multiple ISV’s into a single page. When faced with this, a common developer response is to stop using the latest version of the external library and trying to make their code work against the earlier version of the library forcibly included in the page (perhaps by a managed Visualforce Component or embedded Visualforce Page).

Fortunately, there IS a better way to avoid this.

In a nutshell, the solution is: “protect” or “localize” your references to any external libraries, preferably in a JavaScript namespace corresponding to your managed package.

For instance, if your Salesforce application has the namespace “skuid”, you’re already going to probably have various JS Remoting functions available within the “skuid” JavaScript object that Salesforce automatically creates in pages with controllers with JS Remoting methods — and as an ISV, you are ensured of your managed app’s namespace being Salesforce-unique. So this namespace is just about the safest global variable you can use in the Salesforce world (anyone else who messes with it is being very, very naughty)

As a brief side-note, here’s how to ensure that your app’s “namespace global” has been defined before using it:

// Check that our Namespace Global has been defined as already,
// and if not, create it.
skuid || (window.skuid = {});

To protect your external library references, store a unique reference to these libraries within your namespace’s object, e.g. IMMEDIATELY after loading in an external library, such as MustacheJS,

// Load in Mustache -- will be stored as a global,
// thus accessible from window.Mustache
(function(){ /* MustacheJS 0.7.2 */ })()

// Store a protected reference to the MustacheJS library that WE loaded,
// so that we can safely refer to it later.
skuid.Mustache = window.Mustache;

Then, even if some other VF Component or Page loads in a different version of this library later on, you will still have access to the version you loaded (0.7.2)

// (other ISV's code)
window.Mustache = (function(){ /* MustacheJS 0.3.1 */ })()

// THIS code will run safely against 0.7.2!
skuid.Mustache.render('{{FirstName}} {{LastName}}',contactRecord);

// THIS code, however, would run against the latest version loaded in, e.g. 0.3.1,
// and thus FAILS, (since Mustache 0.3.1 has no render() method)
Mustache.render('{{Account.Name}}',contactRecord);

How to avoid having to use global references all the time

Some of you are probably thinking, “Great, now I have to prepend this global namespace all over the place!” Fortunately, with JavaScript, that’s not necessary. By wrapping your code in closures, you can safely keep using your familiar shorthand references to external libraries without worrying about version conflicts.

For instance, say that your application uses jQuery 1.8.2, but other Visualforce Components on the page are using jQuery as old as 1.3.2! (SO ancient… 🙂 What’s a developer to do?

Well, jQuery provides the helpful jQuery.noConflict() method, which allows you to easily obtain a safe reference to a version of jQuery immediately after you load it into your page. So, as an example, in YOUR code, you need to pull in jQuery 1.8.2:

<!-- load jQuery 1.8.3 -->
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script><script type="text/javascript">// <![CDATA[
// Get a safe reference to jQuery 1.8.3 var jQuery_1_8_3 = $.noConflict(true);
// ]]></script>

Then, to your horror, another Visualforce Component, that the customer you’re developing for has “got to have” in this same page (and which you don’t want to iframe…), has loaded in jQuery 1.3.2, but not bothered to namespace it!!! Therefore, both of the commonly-used jQuery globals (jQuery and $), now refer to jQuery 1.3.2!

Fortunately, FOR YOU, you’re safe! You got a protected reference to jQuery 1.8.3, and your code can carry on using $ without any issues, as long as you wrap it in a closure:

(function($){

   $('.nx-table').on('click','tr',function(){
       // Add "edit-mode" styles to this table row
       $(this).toggleClass('edit-mode');
   });

// Identify jQuery 1.8.3 as what we are referring to within this closure,
// so that we can carry on with $ as a shorthand reference to jQuery
// and be merry and happy like usual!
})(jQuery_1_8_3);

We at Skoodat are considering exposing the platform for we use for rapidly building and deploying killer custom UI’s on Force.com — aka Skoodat Skuid. But we’re looking for your feedback: are we crazy, or does this totally excite you? Let us know!

Skoodat Skuid — Wicked cool UI platform for Force.com, or not?