Handling pre-save action for publishing pages in JavaScript for SharePoint

In SharePoint 2013 it is fairly well known that you can catch and handle the pre-save action in JavaScript for list item forms by implementing the PreSaveAction() method. This however does not work for publishing pages as this method is not called by SharePoint for publishing pages.

You essentially have two options for catching and handling the pre-save action for publishing pages in JavaScript:

  1. Hide the OOTB Save button and provide your own Save button
  2. Override the JavaScript function that the OOTB Save button uses

Hiding the OOTB Save button and providing your own Save button

The problem with this option is that there are multiple buttons that could trigger the save event, including:

  1. Main Save button (top left)
  2. Clicking on the drop down arrow on the main Save button above gives you these additional ones:
    1. Save
    2. Save and Keep Editing
    3. Stop Editing (clicking OK on the prompt will save the page)
  3. Main Check In button
    1. Check In button on drop down menu
  4. Main Publish button
    1. Publish button on drop down menu
    2. Submit button on drop down menu
  5. Save icon at top right

You’d need to replace all of the above buttons to ensure that you always catch the pre-save action.

If you do choose this option, then here is a good guide on implementing this: http://sharepoint.stackexchange.com/questions/20646/attach-a-javascript-function-to-an-ootb-ribbon-button.

The trick is to call the handler function of the original OOTB button, which can be done using the method SP.Ribbon.PageManager.get_instance().executeRootCommand (thanks to article above).

The other trick is working out the command ID of the OOTB button. More on this toward the end of this post.

Overriding the JavaScript function that the OOTB Save button uses

With this option you’d store the OOTB function into a variable and replace the OOTB function with your own. In your custom function, you’d execute your pre-save logic, and then invoke the OOTB function using the variable that you stored previously.

With this option there are less things to override as most of the OOTB buttons (that trigger the save event) use the same function.

The trick with this option is to locate the function that the OOTB buttons use. In case you are not already aware, JavaScript for ribbon buttons in SharePoint are defined in a thing call Page Component (or declaratively in XML – but not for the buttons we need to worry about in this scenario). Here is a good introduction to implementing Page Component: http://www.andrewconnell.com/blog/Implementing-Page-Components-in-the-SharePoint-Ribbon.

Each Page Component has a handleCommand() function, and it is this function that performs the actual work of the ribbon button. It is this function that we need to replace.

Using the method SP.Ribbon.PageManager.get_instance().getPageComponentById() we can retrieve a Page Component by its ID. The Page Component ID that we want in this scenario is PageState.

The handleCommand() function however can be used to handle multiple ribbon buttons. Each button on the ribbon has a command ID, and this is passed to the handleCommand() function. In our custom function, we will need to check the incoming command ID and apply our pre-save logic only if the command ID is that of a button we are interested in.

The command IDs in this scenario are as follow (more on how to work out the command ID toward the end of this post):

Button Command ID
Main Save button (top left) PageStateGroupSaveSplit
Save (in drop down menu from main Save button) PageStateGroupSaveAndStop
Save and Keep Editing (in drop down menu from main Save button) PageStateGroupSave
Stop Editing (in drop down menu from main Save button – clicking OK on the prompt that appears will save the page) PageStateGroupStopEditing* (see special note below regarding this button)
Main Check In button  PageStateGroupCheckinSplit
Check In (in drop down menu from main Check In button)  PageStateGroupCheckin
Main Publish button  PageStateGroupPublishSplit
Publish (in drop down menu from main Publish button)  PageStateGroupPublish
Submit (in drop down menu from main Publish button)  PageStateGroupSubmitForApproval
Save icon at top right  PageStateGroupSaveAndStop

The JS below demonstrates how to hook in our pre-save action for each of the above buttons. One thing to note is that when invoking the OOTB handler, we need to restore the original this context back for the handler, i.e. the pageStateComponent object – and this is why the call method is used to invoke the original OOTB handler.

SP.SOD.executeOrDelayUntilScriptLoaded(function () {
	var pageManager = SP.Ribbon.PageManager.get_instance();
	var pageStateComponent = pageManager.getPageComponentById("PageState");

	var originalHandler = pageStateComponent.handleCommand;
		
	pageStateComponent.handleCommand = function (commandId, properties, sequence) {
		if (commandId === "PageStateGroupSaveSplit") {
			alert("Main Save button");
		} else if (commandId === "PageStateGroupSaveAndStop") {
			alert("Save button in drop down menu or Save icon at top right corner");
		} else if (commandId === "PageStateGroupSave") {
			alert("Save and Keep Editing button in drop down menu");
		} else if (commandId === "PageStateGroupStopEditing") {
			alert("Stop Editing button in drop down menu");
		} else if (commandId === "PageStateGroupCheckinSplit") {
			alert("Main Check In button");
		} else if (commandId === "PageStateGroupCheckin") {
			alert("Check In button in drop down menu");
		} else if (commandId === "PageStateGroupPublishSplit") {
			alert("Main Publish button");
		} else if (commandId === "PageStateGroupPublish") {
			alert("Publish button in drop down menu");
		} else if (commandId === "PageStateGroupSubmitForApproval") {
			alert("Submit button in drop down menu");
		}
		originalHandler.call(pageStateComponent, commandId, properties, sequence);
	}

	SP.Ribbon.PageState.ImportedNativeData.CommandHandlers["PageStateGroupSaveAndStop"] = "alert('Chose to save before continuing');" +
		SP.Ribbon.PageState.ImportedNativeData.CommandHandlers["PageStateGroupSaveAndStop"];
}, "sp.ribbon.js");

Special note for Stop Editing button

The one special case above is the Stop Editing button (on the drop down menu of the main Save button). This button presents the user with a JS confirmation box, and allows them to save the page before navigating away by clicking OK on the confirmation box. As you can see in the code above, handling the PageStateGroupStopEditing command ID only allows you to catch when the user has clicked the Stop Editing button – and not when they actually saves the page by clicking OK on the JS confirmation box.

When you click OK on the JS confirmation box, SharePoint evals a JS string that is stored in SP.Ribbon.PageState.ImportedNativeData.CommandHandlers[“PageStateGroupSaveAndStop”]. Therefore, to add a pre-save action for this particular scenario we’d need to update this JS with our custom code, such as below:

SP.Ribbon.PageState.ImportedNativeData.CommandHandlers["PageStateGroupSaveAndStop"] = "alert('Chose to save before continuing');" +
	SP.Ribbon.PageState.ImportedNativeData.CommandHandlers["PageStateGroupSaveAndStop"];

Working out the command ID of OOTB buttons

You can work out the command ID of most OOTB buttons by inspecting the HTML rendered on the page, and then cross-check with the CMDUI.xml file at SharePointRoot\TEMPLATE\GLOBAL\XML (which is the file that defines all OOTB ribbon buttons).

For example, inspect the Save button (in the drop down menu) and you can see that the aria-describedby (and also the id) attributes contain a value such as Ribbon.EditingTools.CPEditTab.EditAndCheckout.SaveEdit.Menu.SaveEdit.SaveAndStop.

Now search the CMDUI.xml file for this value and you should find a match such as below:

<Button Id="Ribbon.EditingTools.CPEditTab.EditAndCheckout.SaveEdit.Menu.SaveEdit.SaveAndStop" MenuItemId="Ribbon.EditingTools.CPEditTab.EditAndCheckout.SaveEdit.Menu.SaveEdit.SaveAndStop" Sequence="20" Alt="$Resources:core,save_15;" Command="PageStateGroupSaveAndStop" CommandValueId="PageStateGroupSaveAndStop" Image16by16="/_layouts/15/$Resources:core,Language;/images/formatmap16x16.png?rev=23" Image16by16Top="-197" Image16by16Left="-37" Image32by32="/_layouts/15/$Resources:core,Language;/images/formatmap32x32.png?rev=23" Image32by32Top="-103" Image32by32Left="-409" LabelText="$Resources:core,save_15;" ToolTipTitle="$Resources:core,save_15;" ToolTipDescription="$Resources:core,cui_STT_save;" />

The value of the Command attribute is the command ID for this button.

Conclusion

To attach a custom pre-save action your options include providing your custom ribbon buttons, or overriding the OOTB JS handler. Depending on the ribbon button/functionality you are targeting, one approach could be more advantageous than the other. The approach described in this post was specifically for the Save event, but should also be applicable to other events that are initiated from the ribbon.

Advertisements
Posted in SharePoint, SharePoint 2013 | 4 Comments

Tip for identifying button/command ID in Ribbon Workbench

Ribbon Workbench is a great tool for customising the ribbon in CRM. When you need to customise an OOTB button/command however, it can be a bit difficult to identify the correct button/command to work with. This is particularly true when some of the OOTB buttons appear the same on the Ribbon Workbench’s UI, such as the Add Existing buttons below.

One way to identify the correct button/command is to inspect the HTML on the CRM form.

In the example below, I can see that the ID of the + (add existing record) button on a sub-grid is Mscrm.AddExistingRecordFromSubGridAssociated.

I can then pick the right button to customise in Ribbon Workbench by selecting the button with the same ID for CommandCore:

Hope this helps increase your productivity with Ribbon Workbench.

Posted in CRM | Leave a comment

‘An unexpected error occurred’ and using early-binding in CRM plugins

Our plugin was throwing the error below when creating a new task record:

‘An unexpected error occurred.’

at Microsoft.Crm.Extensibility.OrganizationSdkServiceInternal.Create(Entity entity, CorrelationToken correlationToken, CallerOriginToken callerOriginToken, WebServiceType serviceType, Boolean checkAdminMode, Dictionary`2 optionalParameters)
at Microsoft.Crm.Extensibility.InprocessServiceProxy.CreateCore(Entity entity)

We were not doing anything fancy that led to this error – except we used early-binding classes:

var task = new Task
{
	Subject = "New task",
	Description = "Some description..."
};

task.Id = _organisationService.Create(task);

Our plugin was deployed to the database, and we used ILMerge to package everything into a single DLL as required by CRM under this deployment type. This was not the cause of our problem.

The strange thing is the plugin only error when running asynchronously. Changing the plugin step to be synchronous and everything works fine.

It turns out that this is because there are different behaviours between the CRM execution context for synchronous versus that for asynchronous.

Let’s take a step back in order to fully understand this. The IOrganizationService instance has a non-public property _proxyTypesAssembly that points to the assembly where the early-binding classes are defined. This must be set for the service instance to be able to work with early-binding classes properly. When things are good, this is what it looks like when you debug:

The error occurs when this property is null.

So how does this property get set? In plugins we typically use an instance of IOrganizationServiceFactory to get access to an instance of IOrganisationService:

serviceFactory = (IOrganizationServiceFactory) serviceProvider.GetService(typeof (IOrganizationServiceFactory));
service = serviceFactory.CreateOrganizationService(userId);

It is at this point that the _proxyTypesAssembly property is set for the IOrganizationService instance by the IOrganizationServiceFactory.

Below is a screenshot of inspecting the IOrganizationServiceFactory when the plugin in running synchronously. Note that the public ProxyTypesAssembly property is automatically set.

Also note that the type of the IOrganizationServiceFactory instance (serviceFactory) is Microsoft.Crm.Extensibility.PipelineExecutionContext.

Below is a screenshot of the same plugin running asynchronously. Note that the public ProxyTypesAssembly property is null, and the type of the IOrganizationServiceFactory is different compare to when running synchronously.

This is why by default plugins that use early-binding work fine in synchronous mode, but fail when running asynchronously.

To fix this issue we need to set the ProxyTypesAssembly property of the IOrganizationServiceFactory instance. While this property is not exposed on this interface, Darko Micic has found that the IOrganizationServiceFactory interface can be casted to IProxyTypesAssemblyProvider, which allows us to set this property.

The code to retrieve an instance of IOrganizationService therefore becomes:

var serviceFactory = (IOrganizationServiceFactory) serviceProvider.GetService(typeof (IOrganizationServiceFactory));
var proxyTypesAssemblyProvider = serviceFactory as IProxyTypesAssemblyProvider;

if (proxyTypesAssemblyProvider != null)
{
	proxyTypesAssemblyProvider.ProxyTypesAssembly = Assembly.GetExecutingAssembly();
}

service = serviceFactory.CreateOrganizationService(userId);

Note that in Darko’s post he mentioned casting the execution context to IProxyTypesAssemblyProvider. In my debugging I have found that IOrganizationServiceFactory and IPluginExecutionContext actually points to the same instance when retrieved from the service provider. I therefore have chosen to cast the IOrganizationServiceFactory instance instead.

Posted in CRM | Leave a comment

How to set EntityCollection property of service response for unit testing purposes [CRM]

When unit testing CRM C# codes, you may find that you need to mock the response of calls to CRM’s Organisation service. A number of response classes in the SDK contain an EntityCollection property. This property however is null and is read-only, which means you cannot populate it with mock entries – or at least it seems that way.

This will not work:

var mockResponse = new InstantiateTemplateRequest();

//This won't compile as EntityCollection property does not have a setter
mockResponse.EntityCollection = new EntityCollection();

Neither will this:

var mockResponse = new InstantiateTemplateRequest();

//This will give you a run-time exception as EntityCollection property is null.
mockResponse.EntityCollection.Add(new Entity());   

Behind the scene, the EntityCollection property is actually a facade for reading a key from the Results property. The Results property is defined on the base class of all response classes.

You therefore can set and populate the EntityCollection property as follow:

//The mock response
var response = new InstantiateTemplateResponse();
response.Results["EntityCollection"] = new EntityCollection(new List<Entity> { new Entity() });

//response.EntityCollection will now contain one mock entity that we have just configured above.
Posted in CRM, Unit Test | Leave a comment

Getting entity type name, entity code, and entity display name in JavaScript [CRM]

This is a rehash of Michaël Hompus’ post https://blog.hompus.nl/2015/05/28/entity-type-code-using-javascript-only/ to help me remember this very useful trick.

Note that the scripts described in this post are unsupported.

In JavaScript, use Mscrm.EntityPropUtil.EntityTypeName2CodeMap to retrieve the entity type code for a given entity type name, e.g.:

Mscrm.EntityPropUtil.EntityTypeName2CodeMap.new_application   //returns 10008

Use Mscrm.EntityPropUtil.EntityLogicalNameToLocalizedNameMap to retrieve the entity display name for a given entity type name, e.g.:

Mscrm.EntityPropUtil.EntityLogicalNameToLocalizedNameMap.new_application   //returns Credit Application

Depending on how your script was loaded, Mscrm may be null, in which case try looking for it under the parent object instead, i.e. parent.Mscrm.EntityPropUtil…

Posted in CRM | 2 Comments

Mixing early-binding and late-binding with nameof operator in C# [CRM]

There are many benefits of using early-binding when coding against CRM. There are times however when you need to fall back to late-binding, for example when you need to query relationships, or when you need to call Retrieve to optimise performance (as this service call allows you to load only selected fields of a record).

Rather than using literal string, you can use the nameof operator in C# 6 to get the attributes and relationship names. This works because the properties of the early-binding classes are generated based on the attributes and relationship names in CRM.

For example, to get the AccountNumber field of an Account record:

var client = new CrmServiceClient(ConfigurationManager.ConnectionStrings["CRM"].ConnectionString);

//Remember to convert to lowercase.
var accountNumberAttributeName = nameof(Account.AccountNumber).ToLower();

var entity = client.Retrieve(Account.EntityLogicalName, Guid.NewGuid(), new ColumnSet(accountNumberAttributeName));
var accountNumber = entity.ToEntity<Account>().AccountNumber;

And there you have it: late-binding with all the early-binding zen!

Posted in CRM | 2 Comments

Apply filtering to subgrid’s inline lookup in CRM

A common requirement is to apply filtering to a subgrid’s inline lookup on CRM form. For example, you might want to allow users to add only contacts of a particular account (selected in another field) to a contact subgrid on the form.

Just to be clear, the highlighted lookup below is what we want to filter (and what this post is about). There are several posts on the web that talk about filtering the subgrid; but they are about filtering the records that appear on a subgrid, and not the records that appear in the inline lookup that is use to add a record to the subgrid.

1-lookup

Note: The method described in this post is unsupported.

The inline lookup of a subgrid is actually a normal lookup control (more or less), and therefore has the same methods as a normal lookup control. We therefore can apply filtering to this control using the standard addPreSearch and addCustomFilter methods of the control.

It is however difficult to gain access to this control (for a subgrid) as it is not exposed in any of the API. We therefore will need to use unsupported methods to access it.

The subgrid’s lookup control is actually given a control name by CRM, and this name is based on the name of the subgrid. For example, if the subgrid is named grdContacts in the form, the name of the subgrid’s lookup control would be lookup_grdContacts. We can use this name to retrieve the lookup control – but wait..

While you can use Xrm.Page.getControl(“lookup_grdContacts”) to retrieve the subgrid’s lookup, you can only do so after the user has clicked the + icon on the subgrid to bring up the lookup control. Therefore, our code to retrieve the lookup control and call the addPreSearch and addCustomFilter methods on it would need to happen within (or triggered by) the click event of the + icon.

The HTML ID of the + icon has the format [gridName]_addImageButton, e.g. grdContacts_addImageButton. Using this ID we can retrieve the + icon from the DOM and attach code to the click event to retrieve the lookup control and apply our filter.

Below is the code. Please review the in-code comments.


function setSubgridLookupFiltering() {
	var subgridAddButtonId = "grdContacts_addImageButton";

	//Try to get the element from both the current and the parent document.
	var subgridAddButton = document.getElementById(subgridAddButtonId) || window.parent.document.getElementById(subgridAddButtonId);

	//This script may run before the subgrid has been fully loaded on the form. If this is the case,
	//delay and retry until the subgrid has been loaded.
	if (subgridAddButton == null) {
		setTimeout(setSubgridLookupFiltering, 2000);
		return;
	}

	//Local function to retrieve the lookup control and apply the filter. We will queue this function in the click event handler of the
	//Add button's click event.
	var getSubgridLookupAndAddFilter = function() {
		var subgridLookup = Xrm.Page.getControl("lookup_grdContacts");

		//Delay and retry until we can locate the lookup.
		if (subgridLookup == null) {
			setTimeout(getSubgridLookupAndAddFilter, 200);
			return;
		}

		//This is a custom property we are tagging on to the lookup control to ensure that we will
		//apply the custom filter only once when the Add button is first clicked.
		if (subgridLookup.customFilterAdded) {
			return;
		}

		subgridLookup.addPreSearch(function() {
			//Standard logic to build up the filter query string
			var filterQuery = "";

			//filterQuery = "<filter type='and'.....></filter>";
			//..
			//..

			//Standard call to add filter to the lookup control
			subgridLookup.addCustomFilter(filterQuery, "contact");
		});

		//Mark the fact that we have already added the filter so that we won't do it again the next time the user clicks the Add button.
		subgridLookup.customFilterAdded = true;
	};

	//Attach the function to retrieve the lookup and apply the filter to the Add button's click event. Remember that we
	//can only get the lookup after the user has clicked the Add button.
	subgridAddButton.addEventListener("click", function() {
		setTimeout(getSubgridLookupAndAddFilter, 200);
	});
}

Here is another post that uses a similar approach: http://www.bobylog.com/microsoft-dynamics-crm-2015-subgrid-lookup-filter/. This however retrieves the lookup control using a magic sequence of object properties, which differ between versions of CRM, and in my opinion is more prone to breaking in future versions.

Posted in CRM | 1 Comment