Microsoft MVP Logo

In my previous post I talked about how you implement the events when a user clicks on a button or other control in the ribbon. There are two options available to you in implementing these controls: a more declarative option using <CommandUIHandler /> (SDK link) elements in the Feature element manifests or using a page component. I covered the <CommandUIHandler /> option in the last post. The other option, page components, is covered here.

What is a Page Component?

A page component is a client-side object that tells the ribbon on the page which commands it can handle. This object needs to be added to the page and is not directly coupled with the ribbon UX customizations. This is what tripped me up. I couldn't figure out how to get the page component "registered." It actually was a lot more straightforward than I thought it would be. Once the script is added to the page, you add some commands that create a new instance of the page component and register it with the ribbon on the page.

Why Use a Page Component?

As you'll quickly see, creating a page component requires more work than the alternative. However, there are times when you'll want to create a page component. First of all, and probably the most important, anything that needs to be implemented with a ton of script will be hard to maintain in the XML file.

The second reason is because the page component is an actual client object, you have all the power of an object such as private methods and fields if you need to store state.

Building a Page Component

Building a page component can be broken down into to a few steps once you've written the UX customization pieces (the stuff that creates tabs/groups/buttons):

  1. Build the page component
  2. Make sure any script dependencies your page component needs are on the page
  3. Add the page component to the page
  4. Initialize the page component

The command I walk through in this post is the page component equivalent to the declarative one I blogged about previously.

The first thing you need to do is create a new script file and add something will register the object on the page.

// register the page component object
Type.registerNamespace('ApplyDocumentPrefix');

Next, write a few methods that will act as the helpers for setting up the component.

// helper methods to setup & init the page component
ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent = 
  function ApplyDocumentPrefix_PageComponent() {
    ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent.initializeBase(this);
  }

ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent.initialize = 
  function () {
    ExecuteOrDelayUntilScriptLoaded(
      Function.createDelegate(null, 
          ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent.initializePageComponent)
      , 'SP.Ribbon.js');
  }

ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent.initializePageComponent = 
  function () {
    var ribbonPageManager = SP.Ribbon.PageManager.get_instance();
    if (ribbonPageManager !== null) {
      ribbonPageManager.addPageComponent(ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent.instance);
    }
  }

OK, now its time to create the page component:

// page component object
ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent.prototype = {

Now, create the initialization portion of the page component. This method, init(), will do a few things. I want my page component to respond to a specific command called ApplyDocumentPrefix.OnApplyDocPrefixPageComponent. The way I'm doing this is I'm adding it to an array and wiring up that name with a method in my page component named onApplyDocPrefixPageComponent(). I'm going to do the same thing that will be called to check to see if the command is available. The SharePoint SDK talks about a few other ways of doing this, but I prefer this option:

init: function ApplyDocumentPrefix_PageComponento$init() {
  // array of commands that can be handled associated with handler methods
  this.handledCommands = new Object;
  this.handledCommands['ApplyDocumentPrefix.OnApplyDocPrefixPageComponent'] = 
    this.onApplyDocPrefixPageComponent;

  // array of commands that can be handled associated with canHandler methods
  this.canHandledCommands = new Object;
  this.canHandledCommands['ApplyDocumentPrefix.OnApplyDocPrefixPageComponent'] = 
    this.onApplyDocPrefixPageComponent_canExecute;

  // array of commands
  this.commandList = ['ApplyDocumentPrefix.OnApplyDocPrefixPageComponent'];
},

Two more parts of page component are the getFocusedCommands() and getGlobalCommands() methods. I'll cover these in a later post, but for now my command will be a global command.

getFocusedCommands: function ApplyDocumentPrefixPageComponent_PageComponent$getFocusedCommands() {
  return [];
},

getGlobalCommands: function ApplyDocumentPrefixPageComponent_PageComponent$getGlobalCommands() {
  return this.commandList;
},

So now you need to implement the two methods that check to see if the command is available (canHandleCommand()) and the one that handles the commands (handleCommand()). These use the array I created earlier and call the associated methods:

canHandleCommand: function ApplyDocumentPrefixPageComponent_PageComponent$canHandleCommand(commandID) {
  var canHandle = this.handledCommands[commandID];
  if (canHandle)
    return this.canHandledCommands[commandID]();
  else
    return false;
},

handleCommand: function ApplyDocumentPrefixPageComponent_PageComponent$handleCommand(
  commandID, 
  properties, 
  sequence) {
    return this.handledCommands[commandID](commandID, properties, sequence);
},

Now, implement the methods that do the work of handling the command and if the command is available. I'll spare you the one the checks if it is available.

onApplyDocPrefixPageComponent: function () {
  var selectedItems = SP.ListOperation.Selection.getSelectedItems();
  var selectedItemIds = '';
  var selectedItemIndex;
  for (selectedItemIndex in selectedItems) {
    selectedItemIds += '|' + selectedItems[selectedItemIndex].id;
  }

  var dialogOptions = {
    url: '/_layouts/ApplyDocumentPrefixRibbon/DocPrefixPrompt.aspx?selectedItems=' 
          + selectedItemIds + '&ListId=' 
          + SP.ListOperation.Selection.getSelectedList(),
    title: 'Set Document Prefix',
    allowMaximize: false,
    showClose: false,
    width: 500,
    height: 400,
    dialogReturnValueCallback: PageComponentCallback
  };

  SP.UI.ModalDialog.showModalDialog(dialogOptions);
},

Last but certainly not least is the piece that actually initializes and registers the page component with the ribbon on the page:

ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent.registerClass(
  'ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent', CUI.Page.PageComponent);
ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent.instance = 
  new ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent();
ApplyDocumentPrefix.ApplyDocumentPrefixPageComponent.initialize();
SP.SOD.notifyScriptLoadedAndExecuteWaitingJobs("ApplyDocumentPrefix.UI.js");

Here's a link to the complete *.JS file if you're having trouble looking at pictures.

Implementing a Page Component

So now that you've built this JavaScript object, how are you supposed to get it on the page? There are a few different options an it really depends where your ribbon customizations are located. In any case, you're likely going to want to add it from code. If you're doing it from an application page, site page or a Web Part you're going to do it the same way.

First you get a reference to the ribbon on the page and then make sure all the necessary scripts have been loaded before you add your page component (as there are dependencies in it). Then you add the page component to the page.

The following figure shows how I did this with a page component that added buttons to the Document tab when users are in the document library. It is in a server control that I registered on all pages in a particular site by adding the server control to the AdditionalPageHead placeholder control:

protected override void OnPreRender(EventArgs e) {
  SPRibbon ribbon = SPRibbon.GetCurrent(this.Page);

  // make sure ribbon exists & current list is 
  //   a document library (otherwise no need for extra JS load)
  if (ribbon != null && SPContext.Current.List is SPDocumentLibrary) {
    // load dependencies if not already don the page
    ScriptLink.RegisterScriptAfterUI(this.Page, "SP.Ribbon.js", false, true);

    // load page component
    ScriptLink.RegisterScriptAfterUI(this.Page, 
      "ApplyDocumentPrefixRibbon/ApplyDocumentPrefix.UI.js", 
      false, 
      true);
  }

  base.OnPreRender(e);
}

Once the page component has been added to the page, it will run the init statements in the script file that load create a instance of the page component and register it with the ribbon on the page.

I prefer Page Components

Sure, there's a lot more work, but I like the page component option a lot more. Why? I just don't feel right putting my script in XML files. I find it easier to manage the script in a separate file. A side benefit here is that the external library can be cached downstream, but that isn't really a reason I like it because it's such a small file usually.

While there's a lot more script to write, I do like the added control the page component gives me. Your page component can receive focus and only respond to events when it is focused (more on this in another post).

Not only do I prefer them, but in my informal survey, three of four doctors do as well.

Comments powered by Disqus