3. Complex Event Handling
The preceding examples have shown you how to handle
events using embedded JavaScript; although this works well, as the
JavaScript becomes more complex, it gets increasingly difficult to
manage. A common approach to dealing with this problem is to maintain
scripts as separate files. Let’s look at how we can use external scripts
when handling events.
Adding Script Links Using a Delegate Control
The first thing that we need to do when using
external scripts is to find a way to add a link to the script to our
page. The best method to achieve this is to use a delegate control. The out-of-the-box master page that
ships with SharePoint 2010 includes a delegate control named
AdditionalPageHead in the page header. By adding our own content to this
delegate, we can add references to our external scripts.
The
easiest way to implement a delegate control is to add a user control to
the %SPROOT%/TEMPLATE/CONTROLTEMPLATES folder. This folder is mounted
on all SharePoint sites as/_ControlTemplates/, and user controls
contained here can
use code-behind in assemblies that are stored in the Global Assembly
Cache (GAC). To add a new user control, choose Project | Add New Item.
Select User control in the Add New Item dialog and set the name to
CustomRibbonHeader.ascx, as shown next:
The
new user control will be placed in the ControlTemplates/Example folder
by default. In the CustomRibbonHeader.ascx file, add the following
markup:
<SharePoint:ScriptLink Name="SP.js" LoadAfterUI="true"
OnDemand="false" Localizable="false" runat="server" ID="ScriptLink1" />
<SharePoint:ScriptLink Name="CUI.js" LoadAfterUI="true"
OnDemand="false" Localizable="false" runat="server" ID="ScriptLink2" />
<SharePoint:ScriptLink Name="/_layouts/Example/Example.PageComponent.js"
LoadAfterUI="true" OnDemand="false" Localizable="false"
runat="server" ID="ScriptLink3" />
This code snippet uses ScriptLink
controls to ensure that all required scripts are loaded. SP.js contains
core functions for the JavaScript Client Object Model, whereas CUI.js
contains functions necessary for the operation of the ribbon.
In the Elements.xml file containing our CustomAction element, add the following element after the closing tag of the CustomAction element:
<Control Id="AdditionalPageHead"
Sequence="49"
ControlSrc="~/_controltemplates/Example/CustomRibbonHeader.ascx"/>
This code hooks up our delegate control to the AdditionalPageHead delegate.
Since
the whole purpose of adding a delegate control is to include a link to
our custom external script file, our next step is to create the file.
Select the Example project node in the Solution Explorer pane, and then
choose Project | Add SharePoint Layouts Mapped Folder.
Add
a new JavaScript file into the Layouts\Example\ folder named Example.
PageComponent.js. The final ScriptLink control in the preceding code
snippet will ensure that a reference to this file appears on the page.
Creating a Page Component
A page component is effectively a JavaScript
code-behind file for our ribbon customization. Page components are
derived from the CUI.Page.PageComponent class that can be found in
cui.js, the out-of-the-box JavaScript file that is concerned with
delivering the core functionality of the ribbon.
Tip
Many script files used in SharePoint have a debug
version that is easier to read. These files commonly have a .debug.js
extension, such as CUI.debug.js.
By creating a custom page component and overriding
the appropriate methods, we can encapsulate the event handlers for our
tab in a separate file. Follow these steps to create a simple page
component to support the demo tab that we added earlier.
In the Example.PageComponent.Js file that we created earlier, add the following code:
Type.registerNamespace('Example.PageComponent');
Example.PageComponent = function () {
Example.PageComponent.initializeBase(this);
}
Example.PageComponent.initialize = function () {
ExecuteOrDelayUntilScriptLoaded(Function.createDelegate(null, →
Example.PageComponent.initializePageComponent), 'SP.Ribbon.js');
}
Example.PageComponent.initializePageComponent = function () {
var ribbonPageManager = SP.Ribbon.PageManager.get_instance();
if (null !== ribbonPageManager) {
ribbonPageManager.addPageComponent(Example.PageComponent.instance);
}
}
Example.PageComponent.prototype = {
init: function () { },
getFocusedCommands: function () {
return [''];
},
getGlobalCommands: function () {
return ['Example.HelloWorldCommand']
},
canHandleCommand: function (commandId) {
if (commandId == 'Example.HelloWorldCommand') {
return true;
}
else {
return false;
}
},
handleCommand: function (commandId, properties, sequence) {
if (commandId === 'Example.HelloWorldCommand') {
var notificationId =
SP.UI.Notify.addNotification('Hello World from page component');
}
},
isFocusable: function () { return true; },
receiveFocus: function () { return true; },
yieldFocus: function () { return true; }
}
Example.PageComponent.registerClass('Example.PageComponent',
CUI.Page.PageComponent);
Example.PageComponent.instance = new Example.PageComponent();
NotifyScriptLoadedAndExecuteWaitingJobs("Example.PageComponent.js");
This script defines a new class, Example.PageComponent, which inherits from CUI.Page.PageComponent.
By using this object, we can add all of our event handling code within a
separate file rather than having everything contained within the CommandAction attribute of CommandUIHandler elements.
The getGlobalCommands
method returns a list of the commands that are supported by the page
component—in our case, we’re supporting only one command. The canHandleCommand method is used to specify whether an item should be disabled or not, and the handleCommand
method is where we can add the actual implementations for our event
handlers. Commonly, these will take the form of method calls.
Since we’re no longer using the inline script in our CommandUIHandler element, replace the attribute as follows:
<CommandUIHandlers>
<CommandUIHandler Command="Example.HelloWorldCommand" CommandAction=""/>
</CommandUIHandlers>
To initialize our custom page component, add the following JavaScript to the CustomRibbonHeader.ascx file after the ScriptLink controls:
<script type="text/javascript">
//<![CDATA[
function initExampleRibbon() {
Example.PageComponent.initialize();
}
ExecuteOrDelayUntilScriptLoaded(initExampleRibbon, 'Example.PageComponent.js');
//]]></script>
We’re now ready to deploy the revised solution. This
time, clicking the Hello World button will return the message specified
in the Example.PageComponent.js file.
Server-Side Event Handling
So far, you’ve seen how to add ribbon customizations
and handle events using inline JavaScript as well as via a custom page
component. While this is very much the recommended approach, for some
functionality, access to the Server Object Model may be a requirement.
As well as creating page component files and manually
writing JavaScript to handle our events, SharePoint 2010 also provides
server-side objects that can be used to add commands to an existing
custom page component. We’ll add a new button for the purposes of this
demonstration.
In the Elements.xml file, add the following Group element after the closing tag for the existing Group element:
<Group Id="Example.SecondDemoGroup"
Description="Contains Demo controls"
Title="Demo Group 2"
Sequence="51"
Template="Ribbon.Templates.SingleButton">
<Controls Id="Example.SecondDemoGroup.Controls">
<Button
Id="Example.SecondDemoGroup.HelloWorld"
Command="Example.HelloWorldServerCommand"
Sequence="15"
Image16by16="/_layouts/images/NoteBoard_16x16.png"
Image32by32="/_layouts/images/NoteBoard_32x32.png"
Description="Displays a Hello World message"
LabelText="Hello World"
TemplateAlias="c1"/>
</Controls>
</Group>
So that the new group will display properly, we need to add a Scaling/MaxSize element. Between the existing MaxSize and Scale elements, add the following element:
<MaxSize Id="Example.SecondDemoGroup.MaxSize"
GroupId="Example.SecondDemoGroup" Size="OneLarge"/>
Add the following code to the CustomRibbonHeader.ascx.cs file:
public partial class CustomRibbonHeader : UserControl,IPostBackEventHandler
{
public void RaisePostBackEvent(string eventArgument)
{
SPRibbonPostBackEvent pbEvent =
SPRibbonPostBackCommand.DeserializePostBackEvent(eventArgument);
SPContext.Current.List.Title = "Updated " + DateTime.Now;
SPContext.Current.List.Update();
}
protected override void OnPreRender(EventArgs e)
{
List<IRibbonCommand> commands = new List<IRibbonCommand>();
commands.Add(new SPRibbonPostBackCommand("Example.HelloWorldServerCommand",
this, "true"));
SPRibbonScriptManager sm = new SPRibbonScriptManager();
sm.RegisterGetCommandsFunction(this.Page, "getGlobalCommands", commands);
sm.RegisterCommandEnabledFunction(this.Page, "canHandleCommand", commands);
sm.RegisterHandleCommandFunction(this.Page, "handleCommand", commands);
}
}
Make the following modification to the Example.PageComponent.js file:
Example.PageComponent.prototype = {
init: function () { },
getFocusedCommands: function () {return [''];},
isFocusable: function () { return true; },
receiveFocus: function () { return true; },
yieldFocus: function () { return true; },
getGlobalCommands: function () {
var commands = getGlobalCommands();
return commands.concat(['Example.HelloWorldCommand']);
},
canHandleCommand: function (commandId) {
if (commandId == 'Example.HelloWorldCommand') {
return true;
}
else {
return commandEnabled(commandId);
}
},
handleCommand: function (commandId, properties, sequence) {
if (commandId === 'Example.HelloWorldCommand') {
var notificationId =
SP.UI.Notify.addNotification('Hello World from page component');
}
else {
return handleCommand(commandId, properties, sequence);
}
}
}
Deploy the revised solution. The Example
tab will now contain two buttons: When the first button is clicked, a
notification will be displayed as before. When the second button is
clicked, the name of the current list will be updated to include a time
stamp, confirming that our server-side code is being executed.
In this sample, we’ve used the SPRibbonPostBackCommand
to create a command programmatically that emits the JavaScript code
necessary to perform a server post back. When using this technique, bear
in mind that a custom page component is required. The page component
must make calls into the base class for getGlobalCommands, commandEnabled, and handleCommand to hook up the server side event handler properly.