Simplifying JavaScript for Ribbon extensions

A ribbon extension in SharePoint is usually a mix of some declarative xml syntax, which sets up the ribbon component (be it a button or what have you), and some JavaScript that actually controls the commands that are triggered when you use the component (e.g. when you click the button and if the button should be enabled or not).

This article will not go into detail on how to implement a ribbon extension, AC has already written a great article about customizing the ribbon, but more focus, or give and alternative approach, on the actual JavaScript code needed for the commands to work.

As AC explains there are two options for implementing the actual command:

  • Command UI Handlers(in short, inline JavaScript inside the xml…that’s not a great way of doing it if you ask me)
  • Page Components (involves creating a JavaScript object that is implemented according to some standards layered out by Microsoft). It’s a lot of script (as you can see below…even though you can’t see what is says):

image

This post will actually use a combination of these two approaches and is best suited when you don’t need all the stuff that comes with a page component but when you still want to keep your JavaScript separate from your xml (which is always a good approach…separation of concerns and all you know).

So what’s the example then

Well, I’m going to create a button that is added to a document library. Moreover, the button will only light up when you have selected one or more items in the library and will trigger a command that will use the items to call some external service with the items as its input.

Custom Action

image

I’m going to hook up my ribbon button to only be applied to document libraries where a certain content type I’ve build is available (as the image above is meant to illustrate)

image

The content type inherits from document as you can see from the image above.

image

So by using the id of the content type and entering that on the RegistrationId tag on the Custom Action I connect that content type with a custom action…or so I thought. I quickly realized that that didn’t work (i.e. the ribbon button didn’t show up in the library ribbon) and the problem is that id of the content type has to be in uppercase.

image

The GUID has to be in uppercase…WTF!?

Yes, even though I created the content type through VS (with the tools built by Microsoft), which by the way creates the custom content type with the id in lowercase, it didn’t work. So after manually changing it to uppercase the button showed up. (that’s got to be a bug)

image

Now it’s showing in the document library where my content type is available.

Below is the entire custom action code (I’m not going to explain that in very any detail):

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

    <CustomAction Id="RibbonCommandsExample.Ribbon.MyCustomCTypeButton"
                  Location="CommandUI.Ribbon"
                  RegistrationId="0x010100FF2F8E0A837A42DEB23E08255F3A663F"
                  RegistrationType="ContentType">

        <CommandUIExtension>

            <CommandUIDefinitions>

                <CommandUIDefinition Location="Ribbon.Documents.New.Controls._children">

                    <Button Id="RibbonCommandsExample.Ribbon.MyCustomCTypeButton"
                            Sequence="5"
                            Image16by16="/_layouts/$Resources:core,Language;/images/ps16x16.png"
                                Image16by16Top="-192" Image16by16Left="-32"
                            Image32by32="/_layouts/$Resources:core,Language;/images/ps32x32.png"
                                Image32by32Top="-352" Image32by32Left="-352"
                            LabelText="New connection"
                            ToolTipDescription="Creates a new connection"
                            ToolTipTitle="Connection"
                            Command="RibbonCommandsExample.Ribbon.MyCustomCTypeButton.CommandHandler"
                            TemplateAlias="o1"></Button>

                </CommandUIDefinition>               

            </CommandUIDefinitions>

            <CommandUIHandlers>

                <CommandUIHandler Command="RibbonCommandsExample.Ribbon.MyCustomCTypeButton.CommandHandler"
                                  CommandAction="javascript:RibbonCommandsExample.Commands.MyCustomCTypeButton.OnClick();"
                                  EnabledScript="javascript:RibbonCommandsExample.Commands.MyCustomCTypeButton.OnEnabling();">
                </CommandUIHandler>
            </CommandUIHandlers>

        </CommandUIExtension>        

    </CustomAction>

</Elements>

One thing to notice. If you look at lines 33 and 34 you see the only JavaScript calls that I have in the xml declaration which calls an external JavaScript which we now will have a look at.

The JavaScript

You see that the commands are made by calling two functions on some object called RibbonCommandsExample.Commands.MyCustomCTypeButton which looks like a namespace and that’s totally intentional.

image

The JavaScript that I’m using lives in a .js file in the layouts folder. I see this file as a place where I will put all my ribbon commands and the way that I separate them is by using a namespace hierarchy.

Protecting your JavaScript code

Let’s start by looking at some parts of that JavaScript file and I’ll try to explain how I like to think when it comes to writing JavaScript.

image

I’ve collapsed some parts of the JavaScript for reading purposes (the entire script will be available further down in the post). Let’s walk through the different parts.

  1. The first line begins by setting up the root of the namespace which is RibbonCommandsExample. It’s constructed by declaring a variable with that name which in turn is a function, a self-invoking function that takes one parameter which has the name undefined. Why? We’ll come to that part in a bit.
  2. The nature of a self-invoking function is that it ends by calling itself which we can see at number 2. Something odd though is that I call it without any parameters. By doing that the runtime will impose the value of undefined onto the parameter that I call undefined. That’s important, the value of undefined. Undefined is just a value with the value of undefined (it’s not null)…get it?? (for sake of simplicity, think of it as null then)
  3. These are some private variables that I will hold inside RibbonCommandsExample (which is a function). One of the values that I will hold is undefined. This is important since undefined is a value that someone else other than me can change. Now I’m ensuring that when I make comparisons, like if(myvalue === undefined), the comparison will work like expect it to. Here’s how: if(myvalue === _undefined)
  4. These are all private functions which means that they can’t be accessed from outside RibbonCommandsExample.
  5. Here’s how that works. The function RibbonCommandsExample will start (when it’s called/accessed) by returning the stuff after the return keyword and since it’s a self-invoking function that’s exactly what will happen.
    The function actually returns something called an object literal. You can think of it like a json object but I’m using it for sub-namespacing RibbonCommandsExample with Commands – MyCustomCtype and finally the two functions that I’m using in the custom action declaration.

Protection is important

In some “old” JavaScript you might have seen something like this:

image

The problem with that is every function or variable in JavaScript has a scope and the default scope in a browser is the window object. So that code really says Window.RibbonCommandsExample which is really bad since someone else could write a function with the exact same name and your implementation of that function is then replaced, just like with undefined.

Try it by writing this code and then calling alert and see what happens:

image

Let’s recap

What I’ve established here is an object that works like a namespace.
I’ve protected the internal workings of my object and only exposed the stuff I want.
I’m not using the global scope.

image

Finally, you can see for yourself in VS intellisense that the object structure works.

OK, now let’s look at some of those “private” functions

image

You might have seen that those two public functions, OnClick and OnEnabling, really only make sub calls to some private functions.
OnClick is not really that interesting, let’s just say that it picks up the ids of the items that where selected and passes those ids onto another service.

OnEnabling on the other hand is more interesting since it uses a function which can be re-used by other commands. It calls a function called EnsureItemsAreSelected with a value of 1.

image

Here you can see that I’m making use of _undefined since we could call the function without any parameters (remember JavaScript is a dynamic language) and then I presume you mean 0 items and the button (or what have you) will light up without any items being selected.

Here’s the entire JavaScript:

var RibbonCommandsExample = (function (undefined) {

    var _this = this;
    var _undefined = undefined;

    function SelectedItemIds() {
        var items = SP.ListOperation.Selection.getSelectedItems();
        var array = new Array();
        for (i in items) {
            array.push(items[i].id);
        }

        return array.join("|");
    }

    function SelecedListId() {
        var listID = new SP.Guid(SP.ListOperation.Selection.getSelectedList());
        return listID.toString("B");
    }

    function EstablishConnection() {

        var items = SelectedItemIds();
        // left to do: call some service
    }

    function EnsureItemsAreSelected(items) {
        if (items === _undefined) {
            items = 0;
        }

        var count = CountDictionary(SP.ListOperation.Selection.getSelectedItems());
        return (count >= items);
    }

    return {
        Commands: {
            MyCustomCTypeButton: {
                OnClick: function () {
                    EstablishConnection();
                },
                OnEnabling: function () {
                    return EnsureItemsAreSelected(1);
                }
            }
        }

    }

} ());

Putting it all together

The last thing to do in order for this to work is to get my script onto the page. There are a couple of alternatives (as AC explains in his post) but I prefer using a delegate control. AC goes though how to do it but here is just a quick recap of it.

Create a control that adds the script onto the page:

namespace RibbonCommandsExample.Controls
{
    public class PageComponentScriptLoader : System.Web.UI.Control    {

        public string ScriptPath { get; set; }

        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)
            {
                ScriptLink.RegisterScriptAfterUI(this.Page, "SP.Ribbon.js", false, true);

                ScriptLink.RegisterScriptAfterUI(this.Page, ScriptPath, false, true);
            }

            base.OnPreRender(e);
        }
    }
}

Then create a feature that adds that control to the page head tag (through a delegate control):

    <Control Id="AdditionalPageHead"
         ControlAssembly="$SharePoint.Project.AssemblyFullName$"
         ControlClass="RibbonCommandsExample.Controls.PageComponentScriptLoader">
        <Property Name="ScriptPath">RibbonCommandsExample/Scripts/CommandUIHandlers.UI.js</Property>
    </Control>

OK, that’s about it I think….happy coding!!

, , , ,

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: