Adding a custom company menu tab with dynamic menu on the ribbon

The move to include the ribbon in SharePoint provides additional challenges to site designers. They have to include it somehow in their designs, as the ribbon is here to stay. One of our clients had an own navigation menu that he wanted to integrate with the ribbon. The problem is that this menu is dynamic and can change at any moment in time. And the SharePoint 2010 ribbon is static by nature. It is defined in XML files on startup.

That made me wonder whether it would be possible to dynamically add menu items to controls, tabs and groups. I then stumbled upon a blog post by a company called ICC here that described a method of dynamic flyout anchors. Another post described here by Tom Wilson described how we could change the look and feel of the ribbon to fit the internal company design.

I basically used the method described by ICC to dynamically construct my menu items on a separate tab. The source of the menu is a web service that provides the menu items in XML. An XSL transformation then creates the necessary xml definition for SharePoint, disconnecting the web service from technology specific implementations. This resulted in a combination of custom defined tabs, server side command handlers, page components and the works, meaning quite a comprehensive piece of code and a lot for this article. Although I have tried to be as complete as possible, I might have described some parts in less detail then I should. If so, feel free to leave a comment. Some stuff that will be covered in this one example:

  1. Creating Tabs and Groups on the ribbon
  2. Populating FlyoutAnchor dynamically
  3. Registering commands on the server
  4. Calling server side code using ICallbackEventHandler interface

Quite some stuff to cover here. Let us first take a look at an architectural picture on what we are trying to accomplish here:

Define the Tab, Group and Control elements.

 So, let us start coding. Open Visual Studio 2010 and create a new empty SharePoint project. Add a new empty element and call it MyCompany. Also add a mapping to the Layouts folder by right clicking the project and select Add à SharePoint Mapped Layouts folder. This will host our custom JavaScript files. Open the Elements.xml file in the MyCompany empty element and populate it with the following XML:

<?xml version=1.0encoding=utf-8?>
<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
<Control 
 
Id=AdditionalPageHead
 
Sequence=200
 
ControlClass=$SharePoint.Project.FileNameWithoutExtension$.CompanyTabLoader
 
ControlAssembly=$SharePoint.Project.AssemblyFullName$>
</Control>
<CustomAction
  Id=Boom.Ribbon.EnterpriseTabExample
  Title=Enterprise Menu
  Location=CommandUI.Ribbon>
<CommandUIExtension>
 
<CommandUIDefinitions>
   
<CommandUIDefinition Location=Ribbon.Tabs._children>
     
<Tab
        Id=Boom.Ribbon.EnterpriseTab
        Sequence=250
        Title=Enterprise Menu>
        
<Scaling Id=Boom.Ribbon.Enterprise.Scaling>
         
<MaxSize Id=Boom.Ribbon.Enterprise.Scaling.MyCompany.MaxSizeSequence=20GroupId=Boom.Ribbon.Enterprise.MyCompanySize=LargeMedium />
         
<MaxSize Id=Boom.Ribbon.Enterprise.Scaling.MyJob.MaxSizeSequence=40GroupId=Boom.Ribbon.Enterprise.MyJobSize=LargeMedium />
          
<MaxSize Id=Boom.Ribbon.Enterprise.Scaling.MyHR.MaxSizeSequence=40GroupId=Boom.Ribbon.Enterprise.MyHR” Size=LargeMedium />
         
<Scale Id=Boom.Ribbon.Enterprise.Scaling.MyCompany.MediumSmallSequence=100GroupId=Boom.Ribbon.Enterprise.MyCompanySize=MediumSmall />
         
<Scale Id=Boom.Ribbon.Enterprise.Scaling.MyJob.MediumSmallSequence=120GroupId=Boom.Ribbon.Enterprise.MyJobSize=MediumSmall />
         
<Scale Id=Boom.Ribbon.Enterprise.Scaling.MyHR.MediumSmallSequence=120GroupId=Boom.Ribbon.Enterprise.MyHRSize=MediumSmall />
        </Scaling>
       
<Groups Id=Boom.Ribbon.Enterprise.Groups>
         
<Group
            Id=Boom.Ribbon.Enterprise.MyCompany
            Title=My Company Menu
            Template=Ribbon.Templates.Flexible2
            Sequence=100>
           
<Controls Id=Boom.Ribbon.Enterprise.MyCompany.Controls>
             
<FlyoutAnchor
                Id=Boom.Ribbon.Enterprise.MyCompany.Menu

                Command=Boom.Ribbon.Enterprise.MyCompany.Menu

                Sequence=10

                Image16by16=/_layouts/$Resources:core,Language;/images/formatmap16x16.png
                Image16by16Top=-16
                Image16by16Left=-88

                Image32by32=/_layouts/$Resources:core,Language;/images/formatmap32x32.png
                Image32by32Top=-128
                Image32by32Left=-448

                LabelText=My Company Menu

                TemplateAlias=o1

                PopulateDynamically=true

                PopulateOnlyOnce=false

                PopulateQueryCommand=PopulateDymamicMenuItemsQueryCommand

                ToolTipTitle=FlyoutAnchor Dymamic

                ToolTipDescription=FlyoutAnchor with dymamic menu items />
             
</Controls>
           
</Group>
         
</Groups>
       
</Tab>
     
</CommandUIDefinition>
   
</CommandUIDefinitions>
   
<CommandUIHandlers>
     
<CommandUIHandler
        Command=Boom.Ribbon.Enterprise.MyCompany.Menu
       
CommandAction=“”
        EnabledScript=true />
     
<CommandUIHandler
        Command=DynamicButtonCommand

        CommandAction=JavaScript:alert(‘Dynamic Button ‘ + arguments[2].MenuItemId + ‘ clicked.’);

        EnabledScript=true />
    
</CommandUIHandlers>
  
</CommandUIExtension>
</CustomAction>
</Elements>
 

So, what did we just include? In this XML, we define a custom action that will add a Tab to our ribbon called Boom.Ribbon.EnterpriseTab. In there, we define some scaling on how the controls in the groups should be rendered and obviously, the groups itself. In this example, I only defined one group called MyCompany for simplicity, although I did added scaling for additional groups. For the purpose of this blog however, we keep it with one group. In the group, we define a single FlyoutAnchor control that will be the focus subject on this blog. For additional information about the Tab and Group definition, there are many articles out there that describe these. For instance, refer to the blogs by Chris O’Brien on this subject.

So let us take a closer look at the FlyoutAnchor node. The Id and Command attributes follow the naming convention (project-ribbon-tab-group) as best practice, although they can be anything you like. The following attributes are of more interest: PopulateDynamically, PopulateOnlyOnce and PopulateQueryCommand. These three attributes determine that the controls in this menu will be populated dynamically. PopulateOnlyOnce determines that this will only happen the first time it is loaded, which we set to false. The PopulateQueryCommand specifies the command that will be called to populate the control. The command has to set the properties.PopulationXml property with the exact declarative XML as we would use when not loading dynamically. We will get to that part later.

But, as one can see, the command mentioned in the PopulateQueryCommand is not registered in the CommandUIHandlers section of the XML. This is because we will do this registration on the server side. We do however register two commands that make the control enabled (Boom.Ribbon.Enterprise.MyCompany.Menu) and the command that each button will execute when clicked (DynamicButtonCommand). So, how do we now wire the commands to this definition? We will get to that when we solve another problem. The above declared tab will not show up in our interface if we deploy this solution now. That is because we have omitted the RegistrationId and RegistrationType attributes in our custom action, meaning it is not tied to any context yet. As we want the tab to appear always, we have to make sure that the tab is made visible when each page loads. We do this by inserting a custom control that will do this in the pre-render phase of the page, using the AdditionalPageHead control placeholder. And since we then have a control anyway, we can also use that to wire the commands of our tab. So, add a custom class to the project. I have not included it in its own directory, but you could. I called this class CompanyTabLoader.cs. How do we ensure this is loaded on each page? Well, that is accomplished by adding the Control element at the beginning of above XML (or end if you like).

Adding a Control to the AdditionalPageHead on each page

 If fact, this is quite simple to accomplish. The following declaration adds our control to the AdditionalPageHead section of the page:

<Control
 
Id=AdditionalPageHead
 
Sequence=200
 
ControlClass=$SharePoint.Project.FileNameWithoutExtension$.CompanyTabLoader
 
ControlAssembly=$SharePoint.Project.AssemblyFullName$>

Some nice bonus in above declaration. We use the VS 2010 placeholders for adding our complete assembly name upon deployment, so we do not have to fill that in ourselves ;-). By far, this is the easiest way to get code running on each page. As shown, this declaration tells SharePoint to include our custom control in each page by adding it to the AdditionalPageHead container. Now, we can add code to our custom control to complete the wiring of all our events.

Show the custom tab each time the page is loaded

 To make sure our tab is visible on each page we need to call some methods in the PreRender event of our control. Override the OnPreRender event of our base class and add the following code. (Make sure CompanyTabLoader.cs inhereits from System.Web.UI.WebControls.WebControl. Also add references to our project to the following assemblies: Microsoft.SharePoint.dll, System.Web.dll, Microsoft.Web.CommandUI.dll.

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

 
  if
(ribbon != null)
{
    const string initialTabId = “Boom.Ribbon.EnterpriseTab”;

    if (!ribbon.IsTabAvailable(initialTabId))
     
ribbon.MakeTabAvailable(initialTabId);
  
}
}

We first get a reference to the SPRibbon by calling the static GetCurrent method and passing the current page. We then call the MakeTabAvailable method of the SPRibbon class and pass the ID of the Tab to make available. If you do not have this method, then add the reference to the Microsoft.Web.CommandUI assembly. This will ensure our tab is shown on each page in the site collection. We can now proceed to creating our PageComponent JavaScript to handle the custom commands that we will create.

Registering and creating a PageComponent

 The page component is a JavaScript object that we will use to assign are commands to in code. This component will be the bridge between your ribbon and your commands. This component can be very confusing and complex; I know it was for me. But then again, I am no JavaScript guru. In any way, this component tends to be generic and can be extended to fit multiple needs and commands. Let me show you mine PageComponent.js in the basis. I have added this JavaScript file to the Layouts mapped folder in our solution, in its own subdirectory.

function ULS_SP() {
  if (ULS_SP.caller) {
   
ULS_SP.caller.ULSTeamName = “Windows SharePoint Services 4”;
   
ULS_SP.caller.ULSFileName = “/_layouts/Boom.DynamicMenuSample/PageComponent.js”;
  
}
}

Type.registerNamespace(‘Boom.DynamicMenuSample’);

// RibbonApp Page Component
Boom.DynamicMenuSample.PageComponent = function () {
 
ULS_SP();
 
Boom.DynamicMenuSample.PageComponent.initializeBase(this);
}

Boom.DynamicMenuSample.PageComponent.initialize = function () {
 
ULS_SP();
 
ExecuteOrDelayUntilScriptLoaded(Function.createDelegate(null, Boom.DynamicMenuSample.PageComponent.initializePageComponent), ‘SP.Ribbon.js’);
}

Boom.DynamicMenuSample.PageComponent.initializePageComponent = function () {
 
ULS_SP();
  var ribbonPageManager = SP.Ribbon.PageManager.get_instance();

  if (null !== ribbonPageManager) {
   
ribbonPageManager.addPageComponent(Boom.DynamicMenuSample.PageComponent.instance);
   
ribbonPageManager.get_focusManager().requestFocusForComponent(Boom.DynamicMenuSample.PageComponent.instance);
 
}
}

Boom.DynamicMenuSample.PageComponent.refreshRibbonStatus = function () {
 
SP.Ribbon.PageManager.get_instance().get_commandDispatcher().executeCommand(Commands.CommandIds.ApplicationStateChanged, null);
}

Boom.DynamicMenuSample.PageComponent.prototype = {
 
getFocusedCommands: function () {
   
ULS_SP();
    return [];
 
},
 
getGlobalCommands: function () {
   
ULS_SP();
    return getGlobalCommands();
 
},
  
isFocusable: function () {
   
ULS_SP();
  
return true;
 
},
  
receiveFocus: function () {
   
ULS_SP();
   
return true;
 
},
 
yieldFocus: function () {
   
ULS_SP();
    return true;
 
},
 
canHandleCommand: function (commandId) {
   
ULS_SP();
    return commandEnabled(commandId);
 
},
 
handleCommand: function (commandId, properties, sequence) {
   
ULS_SP();
    return handleCommand(commandId, properties, sequence);
 
}
}

// Register classes
Boom.DynamicMenuSample.PageComponent.registerClass(‘Boom.DynamicMenuSample.PageComponent’, CUI.Page.PageComponent);
Boom.DynamicMenuSample.PageComponent.instance = new Boom.DynamicMenuSample.PageComponent();

// Notify waiting jobs
NotifyScriptLoadedAndExecuteWaitingJobs(“/_layouts/Boom.DynamicMenuSample/PageComponent.js”);

No real specifics here. As you can see, I declare a namespace and a class (prototype) to handle various events on the ribbon. Also some constructors (initializers) are declared here. The actual wiring to our custom controls will be done in code. You can reuse this page component for other extensions to the ribbon if desired.

Registering our commands and the PageComponent

 Now we need to register our page component and our custom commands with the page. We move back to our custom control called CompanyTabLoader.cs. In the OnPreRender event, we create a new generic list of IRibbonCommand that will hold our custom commands. We add a new SPRibbonCommand to that list that wires our PopulateDynamicMenuItemsQueryCommand to a command on the ribbon.

var commands = new List<IRibbonCommand>();

// register the command at the ribbon. Include the callback to the server to generate the xml
commands.Add(new SPRibbonCommand(“PopulateDymamicMenuItemsQueryCommand”, “CreateServerMenu(”,”); properties.PopulationXML = menuXml;”));

As you can see in above code sample, we create a new SPRibbonCommand object with two arguments. The first is the name of the command to register, in our case the command mentioned in the declarative XML above. The second argument specifies the code to execute when this command is called. We have included two statements here. First we call CreateServerMenu with two empty arguments. That will be our server side method that will be wired in a few moments. Secondly, we set the properties.PopulationXML to the result of the server call that is loaded in the menuXml variable. We now need to register this command collection (with only one command in our case) with our page component. That is done using the following code:

//Register initialize function
var manager = new SPRibbonScriptManager();

var methodInfo = typeof(SPRibbonScriptManager).GetMethod(

“RegisterInitializeFunction”,

BindingFlags.Instance | BindingFlags.NonPublic);
methodInfo.Invoke(manager, new object[] {Page, “InitPageComponent”, “/_layouts/Boom.DynamicMenuSample/PageComponent.js”, false, “Boom.DynamicMenuSample.PageComponent.initialize()”});


// Register ribbon scripts
manager.RegisterGetCommandsFunction(Page, “getGlobalCommands”, commands);
manager.RegisterCommandEnabledFunction(Page, “commandEnabled”, commands);
manager.RegisterHandleCommandFunction(Page, “handleCommand”, commands);

We create an instance of the SPRibbonScriptManager class and add the initialize function of our page component. After that, we register our commands with the three events of our page component that return an instance of the event, getGlobalCommands, commandEnabled and handleCommand. The last will be called when a command is executed on the ribbon in our page component. We should now wire our server side method mentioned in the command.

Add a callback eventhandler for server side processing

 For this, we implement the ICallbackEventHandler on our class. That will add two methods called GetCallbackResult and RaiseCallbackEvent. The latter will be called by the JavaScript, the first will be processed prior to returning to the client. In our example, we just add the menu XML needed for our FlyoutAnchor in the first method. Using variables, we can pass values from one method to another. For simplicity sake, I did not. Here is the contents of our GetCallbackResult method.

/// <summary>
/// This method will return the caller with the menu. It will now return a static menu, but you can call any webservice from here or the

/// RaiseCallbackEvent event and perform transformation there.

/// </summary>

/// <returns></returns>

public string GetCallbackResult()
{
  string dynamicMenuXml =
“<Menu Id=’Boom.Ribbon.Enterprise.MyCompany.Menu.Menu’>”
  
+ “<MenuSection Id=’Boom.Ribbon.Enterprise.MyCompany.Menu.Section1′ DisplayMode=’Menu16′>”
 
+ “<Controls Id=’Boom.Ribbon.Enterprise.MyCompany.Menu.Section1.Controls’>”;

  string
buttonXML = String.Format(

    “<Button Id=’DynamicButton{0}’ “
   
+ “Command=’DynamicButtonCommand’ “
    
+ “MenuItemId='{0}’ “
   
+ “LabelText=’My Custom menu 1′ “
   
+ “ToolTipTitle=’My Custom menu 1′ “
   
+ “ToolTipDescription=’Dynamic Button’ />”, 0);

   buttonXML = buttonXML + String.Format(

     “<Button Id=’DynamicButton{0}’ “
    
+ “Command=’DynamicButtonCommand’ “
    
+ “MenuItemId='{0}’ “
     
+ “LabelText=’My Custom menu 2′ “
    
+ “ToolTipTitle=’My Custom menu 2′ “
    
+ “ToolTipDescription=’Dynamic Button’ />”, 1);

   buttonXML = buttonXML + String.Format(
     “<Button Id=’DynamicButton{0}’ “
     + “Command=’DynamicButtonCommand’ “
    
+ “MenuItemId='{0}’ “
    
+ “LabelText=’My Custom menu 3′ “
    
+ “ToolTipTitle=’My Custom menu 3′ “
    
+ “ToolTipDescription=’Dynamic Button’ />”, 2);

   buttonXML = buttonXML + String.Format(
     “<Button Id=’DynamicButton{0}’ “
    
+ “Command=’DynamicButtonCommand’ “
    
+ “MenuItemId='{0}’ “
    
+ “LabelText=’My Custom menu 4′ “
    
+ “ToolTipTitle=’My Custom menu 4′ “
    
+ “ToolTipDescription=’Dynamic Button’ />”, 3);

  dynamicMenuXml += buttonXML;
 
dynamicMenuXml += “</Controls>” + “</MenuSection>” + “</Menu>”;
 
  return
dynamicMenuXml;
}

The method creates the menu XML as expected by the control. One could also call a web service in the RaiseCallbackEvent method to get the basic menu xml and execute a XSL transformation in this method to construct this XML. I kept it simple and just did it in code here. The objective (have it done on the server), was already achieved. Once here, you can do whatever you like. Now we should wire these methods to a callback reference, so that our JavaScript can call this.

Wire server side callback to JavaScript

 So, we return to our OnPreRender event. In there, we add the following:

// register the client callbacks so that the JavaScript can call the server.
ClientScriptManager cm = this.Page.ClientScript;

String cbReference = cm.GetCallbackEventReference(this, “arg”, “ReceiveServerMenu”, “”);

String callbackScript = “function CreateServerMenu(arg, context) {“ + cbReference + “; }”;
cm.RegisterClientScriptBlock(this.GetType(), “CreateServerMenu”, callbackScript, true);


//Register script files

ScriptLink.RegisterScriptAfterUI(Page, “/_layouts/Boom.DynamicMenuSample/Boom.DynamicMenuSample.js”, false, true);

We instantiate the ClientScriptManager and add a callback reference to our class. The third argument in this reference is the name of the JavaScript function that will receive the response from our server, in our case ReceiveServerMenu. We then construct the client side script that wires our CreateServerMenu function to the callback and register it all as a client script block. Pffff, what a lot of wiring 😉 But we are almost there. Just hang on for a couple of more lines 😉

Add the JavaScript that will receive the server response

 Final thing we should do is write the client side JavaScript that receives the response and assign the response to the menuXml variable, as mentioned in our custom command. For this, I have added yet another .js (Boom.DynamicMenuSample.js) file in our layouts folder, just to separate the page component from other scripts. To use this extra JavaScript file, we need to register it. This is done using the last line in the previous code sample. In the Boom.DynamicMenuSample.js file, add the following couple of (simple) lines :

// variable to hold the server menu
var menuXml;

// This function will receive the callback from the server with the menu items.
function ReceiveServerMenu(arg, context) {
 
menuXml = arg;
}

The menuXml variable is used to assign it to the properties.PopulationXML in our command declaration. The ReceiveServerMenu function is referenced in our callback reference declaration above. This is the callback function that will be called when the server completes its request. The return value of that function will be assigned to the ‘arg’ argument passed. In the method, we then assign the return value to the menuXml variable (which in turn will be assigned to the properties.PopulationXML in the command), which completes our circle.

Build and deploy

 Now that we have completed our coding, all we have to do is click and deploy and watch the fruits of our labor. If you are lucky, you will see something similar to below. Be patient on the first load though, as the web application still needs to spin up.

Some considerations

Now that we have completed this exercise, one could argue whether this would be the approach to create a menu. Some advantages:
Although I have not tried it, the controls that are dynamically created do not have to be buttons. It could also be FlyoutAnchor’s again, which creates nested menus.
Because we can customize also the look and feel of the ribbon, it would be possible to make it look like a normal menu, as seen many times on sites.
Menu items could easily be added or removed, without the need to redeploy code.

Disadvantages can primarily be found on networks with high latency, where this approach would not be valuable. The menu would simple take too long to load.
Secondly, this approach would be rather ‘chatty’, meaning a lot of request would go back and forth. Some kind of caching mechanism on the server side is preferred.

Anyway, it does show all the possibilities you have with the ribbon. Dynamic controls, server side processing, dynamic command registration on the server, declarative commands, tabs and groups. Have fun playing around with this.

The example project with code can be downloaded here.

UPDATE 24-7-2013: 

It seems like multiple people have issues with the pageComponent.js class as at regular times, it throws a JS error. This is also true for SP 2013. It occurs with getGlobalCommands() function. In this post people solved it by implementing the exact commands to execute. Unfortunately, I have no other solution.

47 thoughts on “Adding a custom company menu tab with dynamic menu on the ribbon”

  1. Hi,

    I tried this sample on sharepoint 2010 beta and i got the tab and group with control displaying, but only for the first time when the page loads.
    When i navigate to any other link and come back to the same tab, the group with control is vanishing immediately.

    Appreciate your comments on this issue.
    Thanks,
    Jawahar.

    1. Strange. The code was developed on SharePoint 2010 RTM, so maybe there is a difference there. I do think that between beta 1 and 2, the messed around with the Registration(Id|Type) on the custom action node in the declaration, which makes it behave differently. The code works on the RTM version. I just switched back and forth on my development machine; the tab is always visible and the menu available.

      I do not have a Beta 2 version or RC version installed at the moment, but between Beta 2 and RC1 a lot of things were changed. As the control also registers the javascripts, are they included in the page when you do not see your tab? You can use the developer tools from IE to find out.

      Also, if I read your comment correctly, your tab disappears if you select another tab or control, so without a page reload. Maybe you could elaborate some more on the issue?

  2. Hi,

    Wonderful post. I have a little different requirement though.

    I want to show the Ribbon menu in a Document Library. Users can select multiple items in the list and will click on my custom ribbon menu. Now, I need to call an aspx page with the List ID and the list of Item IDs that are selected.

    Any idea how we can achieve this?

    1. Quite easy to achieve actually. If you take a look at my post Send Unique Link Button, most of the code you need is already included. Use the Client Object model to collect the ID’s of all the items selected. In my sample, I only got the first selected, but it is an array, so actually all the selected items are included. Then just call the aspx with the selected items in the query string OR use a callback function to handle it directly without navigating to a different page. With the examples from both posts, you can work it out. Good luck!

  3. Hello Patrick! Nice post and solution!

    I got a problem when use it in my ribbon. The tab appears lovely in all site content, its ok. But the button doesnt open the “dropdown” and i cant choose the DynamicButton… some idea?
    Look.. i just need put a normal button (without lists, etc).. i tried to change the FlyoutAnchor to a button, looking always for the properties.. its make my ribbon locked! Can you help me? any solution can send for my email if you prefer…

    Thanks again and NICE BLOG!!!!!!!

    1. Hi and thanks for your comment!

      Could you elaborate a little bit more on what you are trying to achieve? It would then be also usefull for other visitors.

      Patrick

      1. Ok. I took your sample, change the Site URL to my own and deploy the solution. The “My Company Menu“ appears but when i click it, nothing happens. In your example, should rise a DynamicButton, with 4 others buttos, right? But in my personal problem, i dont need this DynamicButton, just a simple button, redirecting to other page.
        I tried to change this FlyoutAnchor for a Button, but no sucess. The Ribbon make locked and i dont know what do anymore. My beg is if you can show a little sample only with a simple button taking all Home, site pages and site content.

        I appreciate all help, thanks for attention :

  4. Navarro,

    You can download my other post about the Email Unique Button to create a simple button. If combine the two projects, so the button code for the button and the tab and section code for the rest, you probably have what you want.

    As to the nothing happening once installed. My only guess would be that your web application still needs to spin up, which causes a delay in the showing of the menu as that is generated server side. After the web applicatioh has warmed up, there is no noticable delay.

  5. Great article. I have gotten it working for the most part but for some reason my callback is in an infinite loop…sort of. Can you think of any reason this would happen?

    As mentioned, when I employ your sample it takes about 15-20 seconds for the menu to display. If I put a break point in the javascript it appears to be in an endless loop and the ReceiveServerMenu never gets called. When I run it in non-debug mode it comes back in about 20 seconds give or take and then as mentioned it is very swift. When I try to employ my own code using this method it rarely comes back at all so I chose to go the client script route and use the Client Object Model and when I setup call backs using javascript I get the same issue. The server maximiizes it’s CPU and nothing is ever returned. A few times I have seen my menu show up with the results from my custom list but most of the time nothing. I can wait for several minutes in debug mode and then put a breakpoint in the javascript and it will immediately hit it because it is continuously rerunning the same code. I was beginning to think something is messed up in my custom master but I am using callbacks from javascript in other places without issue. Any ideas what could cause this behavior? I am thinking it is something to do with the ClientScriptManger and the GetCallbackEventReference but that is just a guess.

    1. Hi Chuck,

      Thanks for trying out my sample. So many people, so many different ways. Could you send me your version of the code? The wiring is complex and quite error prone. It took me a while to get it right. But let me take a look to see if I can help you. You can send the code to my email address, as mentioned in the contact information.

      Patrick

      1. Hi Patrick,
        Are you found solution? 🙂

        I try you code too. And have a same issue, with server request looping when I try open dropdown menu for button:

        think that a problem with GetCallbackEventReference – this method work async always, but you code want sync request for fill variable

        CreateServerMenu(“”,””); <– this method completed momentally without wait any server response
        properties.PopulationXML = menuXml; <– empty menuXml

        function ReceiveServerMenu(arg, context) {
        menuXml = arg; <– this line called when all above are completed :((
        }

      2. Hi Evgeny,

        You could be right, but I need to test it out though. Probably, the menu gets filled the second time around, as the variable then contains the menu.

        I need to boot into my VM for this, but I will try to take a look at this today.

        Patrick

      3. Hi Evgeny,

        Took a look at the code. And indeed, you are right. The menu gets populated on the second time around. The first time, due to the synchronous order, the properties.PopulationXml is assigned an empty field. The scond time around, the variable is populated and assigned.

        The ways to get around it though. I did it the easy and dirty way, that is, providing a while loop checking for the menuXMl field to not be empty. You can also do it by using AJAX techniques, which would be the better way.

  6. To get past the looping issue I wrote the dynamic menu options (XML) on the server site, pre_render event and then assigned them to the javascript variable. It still requires the callback event handler but it is very fast. I am still working out one javascript error I get because one of my .js files doesn’t always get downloaded but I have not been able to attribute that issue to this sample. None the less it is a good demonstration and provides a way to build a dynamic flyout menu using the ribbon bar.

    1. Chuck, thanks for your feedback. Given the amount of reactions on this post, I guess a lot of peope are interested and working on this. I you could sent me your easy workaround, it would be much appreciated!

      Thanks for visiting my blog!

  7. Hey Patrick, send me your email via email and I can send over my code. I would post in here but it will not look very good.

  8. Hi Patrik
    I followed the steps as said by you. But I am observing a weird issue i.e. some times PageComponent.js file is not loaded into the browser and ribbon button gets disabled if I do a browser refresh at this point I am able to see the buttons. I am able to see this issue quite frequently. I came to conclusion that PageComponent is not laoded because I included an error in the JS file and executed the code, then ribbon buttons got disabled. So I feel some times this PageComponent.js file is not being loaded.

    My question is do you have any idea where in this PageComponent.js may not load. This is a generic question, but pls provide some wide guesses.

    Thanks!

    1. Sorry, hard to say where it goes wrong. The component is generic and does not contain any specific actions.

      I would take take a look at the canhandleCommand and handleCommand methods to see if there are typos in there. Same goes for the registration of the namespace at the bottom.

    2. Hi Saikiran,

      I am facing same issue for PageComponent.js, what you are. It would be great, if you can share the resolution, if you have resolved it.
      FYI – This issue is not noted in FireFox/Chrome, but in IE.

      Regards,
      Anwar

      1. FYI-
        To resolve the PageComponent.js issue, modified the “getGlobalCommands” function.

        We replaced “return getGlobalCommands();” with following (for e.g.) :

        return [‘PopulateDymamicMenuQueryCommand1’, ‘PopulateDymamicMenuQueryCommand2’, ‘PopulateDymamicMenuQueryCommand3’];

        This means we hard-coded the command names, which we were expecting to get dynamically generated. We found that the issue is intermittent and probably some dependant JavaScript file doesn’t seem to load internally. We are not very sure of the cause though. But, this worked in our case.

        Hope this help others in need.

        Regards,
        Anwar

  9. Hi,

    Have you been able to implement multiple tabs?

    I have been trying


    This is not working, I even tried making both tabs available, still the second tab is not showing

    Also tried


    1. CommandUIDefinition Location=“Ribbon.Tabs._children“
      Tab Id=”1″
      Tab Id=”2″

      then tried

      CommandUIDefinition Location=“Ribbon.Tabs._children“
      Tabs Id=”Tabs1″

      Tab Id=”1″
      Tab Id=”2″

  10. Hi Patrick,

    I have used your sample as reference for creating menu for my sample site. I understood most of code except one thing i.e.
    “arguments[2]”. What exactly is this and from where we are getting this? It would be great if you can elaborate more on this.

  11. Hi Patrick,
    really good example but I have a problem which I cannot figure out. The tab and the group is shown on my SharePoint site but the buttons are not. There are multiple postbacks to my site when I analyze the behavior with the development tool and therefor the browser will crash.

    Do you know what can be wrong. I just put the project and deployed it with success to my SharePoint 2010.

    Greetings
    Thomas

    1. Did you ever get your question resolved, that is really weird that we have the same name. Let me know if I can be of any assistance. Do you know how to get rid of a root user on a mac 10.5.8 because I have been hacked into in every facet you could imagine.

  12. Hmm is anyone else encountering problems with the images on
    this blog loading? I’m trying to determine if its a problem on my end or if it’s the blog.
    Any feedback would be greatly appreciated.

  13. I was curious if you ever thought of changing the structure of
    your blog? Its very well written; I love what youve got to say.
    But maybe you could a little more in the way of content so people
    could connect with it better. Youve got an awful lot of text for only having 1 or two pictures.

    Maybe you could space it out better?

  14. It’s actually a cool and useful piece of information. I’m happy that you just shared this helpful info with us.
    Please stay us up to date like this. Thanks for sharing.

  15. Did anyone got this working on SharePoint 2013? We gave it a try but after loading the custom ribbon Tab, some default ribbon buttons (like Edit Properties, Show Properties, Workflows) inside a Document Library stay inactive. It seems like it fails when loading the following Script:

    ScriptLink.RegisterScriptAfterUI(this.Page, “SP.Ribbon.js”, false, true);

    But we got no Javascript error.

    After that the buttons stay inactive. Our custom ribbon Tab itself is working fine. Maybe something changed in SP 2013 with these javascripts?

    1. Could not get it working either in SP 2013. Need more research into this to find out, but lacking time.
      To me, the behaviour looks like the JS is not attached and the server side code is never called.

    2. Hi Add,

      I will share the answer here, as it is relevant for all visitors. This solution relies heaviy on JS scripts that are located in the layouts folder of SharePoint. Although upgrading the solution does re-route the feature to the 15 hive, SharePoint 2013 introduces compatibility with SP 2010. In short, this means running two infrastructures side by side, including a 14 and 15 hive. In SP 2013, they solved the references to _layouts in IIS by mapping the virtual directories to the 14 and 15 hives.

      So, “_layouts” points to the 14 hive, which does not contain any files because the solution pointed them to the 15 hive and “_layouts/15” points to the 15 hive! So you need to update all path references in code and scripts that referenced the old path 😉 So replace “_layouts” with “/layouts/15”.

      More info thanks to Keith

      1. Thanks Patrick! It’s working fine now. With Easyribbon there’s no flexibility with the group template, we needed that.

  16. hello there and thank you for your info – I have certainly picked up anything new from right here.
    I did however expertise several technical issues using this
    website, as I experienced to reload the website a lot of times previous to
    I could get it to load properly. I had been
    wondering if your web hosting is OK? Not that I am complaining,
    but slow loading instances times will sometimes affect your placement in google
    and could damage your high quality score if advertising and marketing
    with Adwords. Well I’m adding this RSS to my email and could look out for a lot more of your respective interesting content. Ensure that you update this again very soon.

  17. Hi just wanted to give you a brief heads up and
    let you know a few of the pictures aren’t loading properly. I’m not
    sure why but I think its a linking issue.

    I’ve tried it in two different browsers and both show the same results.

  18. Oh my goodness! Awesome article dude! Many thanks, However I am encountering problems
    with your RSS. I don’t understand why I cannot subscribe to it. Is there anyone else having similar RSS problems? Anyone that knows the answer can you kindly respond? Thanx!!

Leave a comment