Conditional Detail Page Buttons – Hacking the Platform

UPDATE: Due to changes in the way Salesforce serves up home page components from a different domain, this will no longer work as javascript no longer has access to the DOM.

Introduction

I’m going to start this post off with a BIG RED CAVEAT:

keyboard

This makes use of undocumented techniques on the Salesforce platform. This is risky business as platform behaviour may change without notice or warning leading to broken code. You have been warned!

Ok, so the warning aside, I’m going to show you how to ‘hack’ the platform to make those Salesforce detail page buttons render according to rules on the target record.

The motive for this exercise was a conversation I once had with a client who wanted to know if it was easy to disable the buttons based on some criteria on the record to which I replied, ‘No’. I decided at some point I’d have a play around and see if I could come up with a solution that didn’t involve having to write Visualforce pages to replace the standard detail pages by way of a challenge if nothing else. This is the fruit of that which you are free to play around with but only if you heed my caution! 😉

I think Salesforce are missing a trick by not including this behaviour as standard. An ideas posting me thinks.

The Approach

There are 2 parts to this. One is the logic that determines whether a button should be hidden or not and the other is the actual hiding of the button.

The show/hide logic

This is the simpler part. I decided the easiest way was to create a Custom Object that allows a User or Sys Admin to define which buttons should be hidden by binding the button name to a Checkbox field on the object. If the checkbox is checked then the button should be hidden. Using a checkbox field on the object rather than writing some complex condition parser means the user can simply add a Checkbox formula field and leverage the platforms own condition parser.

The Condition Set custom object below:

Screen Shot 2013-07-28 at 15.45.32It’s a master detail. The header record refers to the SObject API name that we want to add conditions for. The key prefix is the 3 character Id that you can use to determine the object type from an Id. I’ve written trigger to populate this so you only need enter the SObjects API name. I’ve added a few standard button hide fields as well a picklist option to decide whether to Hide or Disable the buttons. For the Standard buttons you need to specify the API name of a checkbox field on the object to bind to. This checkbox must resolve to TRUE in order for the associated button to be hidden. For custom buttons (and other standard buttons) you can add Custom Conditions via the related list. In this case you need to specify the API Name of the Button and the API name of the check box to bind to. There’s no reason why you can’t bind more than one button to the same checkbox either.

The Hiding of the Buttons

This is where a bit of platform black magic comes in. I’ve created a Javascript Homepage Component that you can add to the standard sidebar.

Screen Shot 2013-07-28 at 16.01.36

Using the Chrome debugging console I  worked out the name attribute of the standard buttons, eg. edit, delete, submit, clone. I also found that any custom buttons have a name attribute that matches the API name albeit in lower case. On a detail page the Id of the object being viewed can been obtained from a property pinned on the window object: window.sfdcPage.entityId

So armed with this information I created a Web Service that takes an SObject Id and resolves that to a Condition Set using the SObjects Key Prefix. (The three character code that identifies an SObjects type.) It then queries the SObject itself to determine which buttons to hide using the bound fields from the Condition Set.

The homepage component contains the Javascript that uses the Ajax Toolkit to call the web service and retrieve the list of buttons to hide. If the component is able to locate a Condition Set then the buttons are hidden or disabled with a bit of jQuery. The component itself is hidden so as not to consume any screen real estate.

One complication arose using the Ajax Toolkit which was where to get the session token from as the Home Page Components don’t have access to any merge fields. I found a trick that Ron Hess had posted up on the Salesforce boards that extracts the Session Id from the page cookie to get round this.

An Account Detail Page with Disabled Buttons

So to show you it in action here’s the Account Detail Page using the above Condition Set:

Screen Shot 2013-07-28 at 16.28.02

Cool, now can I see the Code?

To make life easier I’ve uploaded it all into an unmanaged package that you can get here: http://j.mp/1c2R5FF

The package consists of:

  • A Trigger: ConditionSetTrigger.trigger – The trigger uses the describe information for the entered SObject API Name to populate the 3 character Key Prefix.
  • A Web Service: ConditionSetWebService.cls – The service provides a method that takes an SObject Id and returns the hide button data used by the Home Page Component.
  • A Home Page Component: Conditional Butons – This is the javascript that hides the buttons.
  • A Test Class: ConditionSetTests – A couple of unit tests for the service and the trigger.
  • 2 Custom objects: Condition Set / Custom Condition – Provide the Condition Set data.
  • A Tab: Condition Set – Tab for defining the Condition Sets.

Installation Steps:

  • Install the package.
  • Add the Home Page Component to the Home Page Layout. (Setup -> Customize -> Home -> Homepage Layouts)
  • Make sure the Sidebar always shows custom components.  (Setup -> Customize -> User Interface -> Show Custom Sidebar Components on All Pages)
  • Create any Custom Checkbox Fields of Checkbox Formula Fields on the SObjects whose buttons you want to control.
  • Create the Condition Sets and bind the Hide Fields to the Checkbox fields on your SObject. Note, when entering API names they must match exactly the API names in your org with namespace prefixes as appropriate.
  • Be aware that it uses Google hosted libraries for the jQuery library. Make sure this content is not blocked: //ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js

The homepage component code is a bit compressed and unreadable so I’ve provided it here as well in a more tabulated form:

<script src="/soap/ajax/28.0/connection.js"></script>
<script src="/soap/ajax/28.0/apex.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
		/* Document Ready */
		$(document).ready(function() {
			/* Hide the Sidebar component */
			$('#ConditionalButtonsComponent').closest('.sidebarModule').hide();
		});

		/* Window Loaded */
		$(window).load(function() {
			/* Namespaced Javascript functions */
			var TS =
			{
				hideButtons: function()
				{
					/* Get the object Id of the detail page */
					var objectId = window.sfdcPage.entityId;

					/* If we have an object id, we can pass it to the webservice */
					if (typeof objectId != 'undefined')
					{
						/* Extract the Session Id from the page cookie */
						var sid = TS.readCookie('sid');
						sforce.connection.sessionId = sid;

						/* Make an asynchronus call to the webservice */
						sforce.apex.execute('ConditionSetWebService',
											'getHiddenButtons',
											{Id: objectId},
											{
												onSuccess: TS.doHide,
												onFailure: function(error) { TS.logToConsole(error); }
											});
					}
				},

				doHide: function(response)
				{
					var responseObject = JSON.parse(response);

					for (var i = 0; i < responseObject.HiddenButtons.length; i++)
					{
						var button = responseObject.HiddenButtons[i].toLowerCase();

						if (responseObject.HideOption == 'Disable')
						{
							$('input[name="' + button + '"]').attr('disabled', 'disabled').fadeTo('fast', 0.4);
						}
						else
						{
							$('input[name="' + button + '"]').hide();
						}

					}
				},

				/**
				 * readCookie function by Ron Hess
				 */
				readCookie: function(name)
				{
					var nameEQ = name + "=";

					var ca = document.cookie.split(';');

					for(var i = 0; i < ca.length; i++)
					{
						var c = ca[i];

						while (c.charAt(0) == ' ')
						{
							c = c.substring(1, c.length);
						}

						if (c.indexOf(nameEQ) == 0)
						{
							return c.substring(nameEQ.length, c.length);
						}
					}

					return null;
				},

				/**
				 * Chrome only
				 */
				logToConsole: function(message)
				{
					if(typeof console == "object")
					{
						console.log(message);
					}
				}
			};

			/* Hide the buttons */
			TS.hideButtons();
		});
</script>
<span id='ConditionalButtonsComponent'>Conditional Buttons</span>
Advertisements

Author, brainstormer, coder, dad, explorer, four chord trickster, gig goer, home worker, inquisitor, joker, knowledge seeker, likes: marmite, note scribbler, opinionator, poet, quite likes converse, roller skater, six music listener, tea drinker, urban dweller, vinyl spinner, word wrangler, x-factor hater, Yorkshireman (honorary), zombie slayer (lie).

Posted in apex, code, force.com, salesforce
About Me
Product Services Developer at:
FinancialForce.com
All views expressed here are my own. More about me and contact details here.

Enter your email address to follow this blog and receive notifications of new posts by email.

Copyright (there isn’t any, feel free to reuse!)

CC0
To the extent possible under law, Tony Scott has waived all copyright and related or neighboring rights to MeltedWires.com Examples and Code Samples. This work is published from: United Kingdom.

%d bloggers like this: