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:
- Hide the OOTB Save button and provide your own Save button
- 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:
- Main Save button (top left)
- Clicking on the drop down arrow on the main Save button above gives you these additional ones:
- Save
- Save and Keep Editing
- Stop Editing (clicking OK on the prompt will save the page)
- Main Check In button
- Check In button on drop down menu
- Main Publish button
- Publish button on drop down menu
- Submit button on drop down menu
- 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.
I am getting an error on the following line:
var pageStateComponent = pageManager.getPageComponentById(“PageState”);
it is not able to get this component
Had this problem to. Turns out you have to wait for ribbon to finish loading first. I wrapped it in a function and ran it in window.onload and it worked.
Where exactly would we put the above javascript?
This script should be placed on the publishing page, e.g. on document ready. In my testing I placed the script on the page layout. You should also be able to inject it using a Content Editor Web Part.
hey thanks, I figured that out, it is working now.