How to create a custom color picker flyout in the SharePoint ribbon

If you want to customize the colors available in the color picker flyout menu, there are a couple of options available to you:

  • Pick an out of the box theme. This approach is the easiest since it requires no coding at all.
  • Create or customize a theme using PowerPoint or applications like Theme Builder for Office 2010.
  • You can also replace colors in your CSS files, using the following syntax:
    /* [ReplaceColor(themeColor:"Light2-Darkest")] */
    background-color:#707070;

The themes approach works fine in 90% of all cases. However, there are also some disadvantages:

  • In a highly branded publishing site, you need more control over the different colors to match the company style guide.
  • Sometimes you need more colors to be available in the color picker
  • You don't want the out of the box SharePoint pages to look ugly because you change a specific color:

The approach I've taken on a project is to add a custom color picker to the ribbon. This allows authors to select a color that matches the company style guide exactly. In fact, we implemented 2 color pickers: one for the font and one for the background.

How does this work?

The solution is implemented as feature which can be activated at the site collection level. The feature defines a custom action in the following way:

<?xml version="1.0" encoding="utf-8"?>

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

  <CustomAction Id="Ribbon.Custom" Location="CommandUI.Ribbon">

    <CommandUIExtension>

      <CommandUIDefinitions>

        <CommandUIDefinition
Location="Ribbon.EditingTools.CPEditTab.Groups._children" >

          <Group

              Id="Ribbon.Custom.Tab"

              Sequence="35"

              Description=""

              Title="Custom"

              Command="Ribbon.Custom.Tab"

              Template="Ribbon.Custom.TabTemplate">

            <Controls Id="Ribbon.Custom.Tab.Controls" >

              <FlyoutAnchor Id="Ribbon.Custom.FontColorPickerFlyout"

                            Command="Ribbon.Custom.FontColorPickerFlyout"

                            Sequence="10"

                            Image16by16="/_layouts/1033/images/formatmap16x16.png"

                            Image16by16Top="-80" Image16by16Left="-80"

                            Image32by32="/_layouts/1033/images/formatmap32x32.png"

                            Image32by32Top="-224" Image32by32Left="-448"

                            LabelText="Font color"

                            TemplateAlias="RowI"

                            PopulateDynamically="true"

                            PopulateOnlyOnce="true"

                            PopulateQueryCommand="GetDynamicColorPickerMenuXml"

                            ToolTipTitle=""

                            ToolTipDescription=""/>

              <FlyoutAnchor Id="Ribbon.Custom.BackgroundColorPickerFlyout"

                            Command="Ribbon.Custom.BackgroundColorPickerFlyout"

                            Sequence="20"

                            Image16by16="/_layouts/1033/images/formatmap16x16.png"

                            Image16by16Top="-80" Image16by16Left="-80"

                            Image32by32="/_layouts/1033/images/formatmap32x32.png"

                            Image32by32Top="-224" Image32by32Left="-448"

                            LabelText="Background color"

                            TemplateAlias="RowII"

                            PopulateDynamically="true"

                            PopulateOnlyOnce="true"

                            PopulateQueryCommand="GetDynamicBackgroundColorPickerMenuXml"

                            ToolTipTitle=""

                            ToolTipDescription=""/>

            </Controls>

          </Group>

        </CommandUIDefinition>

        <CommandUIDefinition Location="Ribbon.Templates._children" >

          <GroupTemplate Id="Ribbon.Custom.TabTemplate">

            <Layout Title="LayoutLarge" LayoutTitle="LayoutLarge">

              <Section Alignment ="Top" Type="OneRow">

                <Row>

                  <ControlRef DisplayMode ="Large" TemplateAlias="RowI"/>

                  <ControlRef DisplayMode ="Large" TemplateAlias="RowII"/>

                </Row>

              </Section>

            </Layout>

          </GroupTemplate>

        </CommandUIDefinition>

        <CommandUIDefinition Location="Ribbon.EditingTools.CPEditTab.Scaling._children">

          <MaxSize Id="Ribbon.Custom.Tab.Scaling.MaxSize"

                   Sequence="25"

                   GroupId="Ribbon.Custom.Tab"

                   Size="LayoutLarge"/>

        </CommandUIDefinition>

      </CommandUIDefinitions>

    </CommandUIExtension>

  </CustomAction>

  <Control Id="AdditionalPageHead" ControlSrc="~/_controltemplates/ColorPicker/RibbonColorPickerRegistration.ascx" />

</Elements>

 

Here's a short description of what we've done here. First of all, the custom action registers itself in the "CommandUI.Ribbon" location. Next, we define a new custom group that is located in the "Ribbon.EditingTools.CPEditTab". The CPEditTab ribbon will only be displayed if the cursor is in a Rich HTML content field.

The custom group contains 2 FlyoutAnchor controls, one for the font color and one for the background color. Notice the PopulateQueryCommand defined for each of the FlyoutAnchors. The actual colors themselves will be generated using client side script. We will take a look into this later.

Notice also the AdditinonalPageHead control. This control will include a custom ASCX, whose responsibility it is to register (serverside) the different commands that are available on the client. It will also be responsible for registering the initialization function so that everything is wired up.

First of all, how are the colors generated?

An excellent example of how the xml should look like can be found here. We need to generate the following xml and assign it to the PopulationXML property:

 <Menu Id='Ribbon.ListItem.Workflow.Controls.CPFlyout.Menu'>
                                                 
   <MenuSection
                       
     Id="..."
     Title="Custom Color Picker">
                                          
     <Controls Id="...">
                                                  
       <ColorPicker
                       
         Id="..."
         Command="...ColorPicker_CMD"
         CommandPreview="...ColorPicker_PRE_CMD"
         CommandRevert="...ColorPicker_REV_CMD" >
                                         
         <Colors>
                          
           <Color Title='#003781' DisplayColor='#003781' Color='#003781' />
                                                                                                 
           ...
         </Colors>
                           
       </ColorPicker>
                          
     </Controls>
                           
   </MenuSection>
                          
 </Menu>

 

Because we want to generate this on the client, let's introduce a CustomRibbon.js file and define the following function:

 function GetDynamicColorPickerMenuXmlFunction() {
     var $colormenu = new Sys.StringBuilder();
     $colormenu.append('<Menu Id=\'Ribbon.List.CustomViews.ModifyView.Menu\'>');
     $colormenu.append('<MenuSection Id=\'Ribbon.List.CustomViews.ModifyView.Menu.Manage\' Title=\'Custom Color Picker\'>');
     $colormenu.append('<Controls Id=\'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls\'>');
     $colormenu.append('<ColorPicker Id=\'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.ColorPicker\' Command=\'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.ColorPicker_CMD\' CommandPreview=\'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.ColorPicker_PRE_CMD\' CommandRevert=\'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.ColorPicker_REV_CMD\'>');
     $colormenu.append('<Colors>');

 

     var colors = new Array("003781", "426bb3", "819ccc", "009ee0", "4d4d4d", "898d94", "939598", "acacae", "cfd0d2", "e5e5e5", "a07400", "603d20", "862633", "653165", "330072", "002f6c", "003b5c", "004c45", "546223", "685c20", "cc8a00", "8b4720", "a50034", "87189d", "5f259f", "003da5", "00677f", "007a53", "658d1b", "949300", "f2a900", "e35205", "ce0037", "a51890", "685bc7", "0077c8", "009ca6", "009a44", "64a70b", "b5bd00", "ffcd00", "ff8200", "e4002b", "df1995", "7d55c7", "00a9e0", "00aec7", "00b74f", "97d700", "d0df00", "fae053", "ffa300", "f9423a", "f57eb6", "9678d3", "5bc2e7", "6ad1e3", "6eceb2", "a4d65e", "e2e868", "54585a", "707372", "898d8d", "9ea2a2", "c7c9c7", "eeeeee", "f0f0f0");

 

     for (var i = 0; i < colors.length; i++) {
         $colormenu.append('<Color Color=\'#' + colors[i] + '\' Title=\'#' + colors[i] + '\' DisplayColor=\'#' + colors[i] + '\'/>');
     }

 

     $colormenu.append('</Colors>');
     $colormenu.append('</ColorPicker>');
     $colormenu.append('</Controls>');
     $colormenu.append('</MenuSection>');
     $colormenu.append('</Menu>');
     return $colormenu.toString();
 }

 

This function will be called on the client when the ribbon wants to populate the controls (PopulateQueryCommand).

There is actually some more glue needed: we need to implement a client side RibbonAppPageComponent which hooks into the SharePoint Ribbon. The attached solution contains all the code required to do this, but I just want to highlight the different commands:

 SP.Ribbon.Custom.UI.RibbonAppPageComponent.prototype = {
     getGlobalCommands: function () {
         ULS_SP();
         return getGlobalCommands();
     },
     isFocusable: function () {
         ULS_SP();
         return true;
     },
     getFocusedCommands: function () {
         ULS_SP();
         return [];
     },
     receiveFocus: function () {
         ULS_SP();
         return true;
     },
     yieldFocus: function () {
         ULS_SP();
         return true;
     },
     canHandleCommand: function (commandId) {
         ULS_SP();
         return canHandleCommand(commandId);
     },
     handleCommand: function (commandId, properties, sequence) {
         ULS_SP();
         switch (commandId) {
             case 'GetDynamicColorPickerMenuXml':
                 properties.PopulationXML = GetDynamicColorPickerMenuXmlFunction();
                 return true;
                 break;

 

             case 'GetDynamicBackgroundColorPickerMenuXml':
                 properties.PopulationXML = GetDynamicBackgroundColorPickerMenuXmlFunction();
                 return true;
                 break;

                
 

             case 'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.ColorPicker_CMD':
                 RTE.PreviewManager.get_instance().endPreview();
                 RTE.FontCommands.setColor(properties.Color, false);
                 break;

 

             case 'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.BackgroundColorPicker_CMD':
                 RTE.PreviewManager.get_instance().endPreview();
                 RTE.FontCommands.setBackgroundColor(properties.Color, false);
                 break;

 

             case 'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.ColorPicker_PRE_CMD':
                 RTE.PreviewManager.get_instance().beginPreview();
                 RTE.FontCommands.setColor(properties.Color, false);
                 break;

 

             case 'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.BackgroundColorPicker_PRE_CMD':
                 RTE.PreviewManager.get_instance().beginPreview();
                 RTE.FontCommands.setBackgroundColor(properties.Color, false);
                 break;

 

             case 'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.ColorPicker_REV_CMD':
                 RTE.PreviewManager.get_instance().endPreview();
                 break;

 

             case 'Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.BackgroundColorPicker_REV_CMD':
                 RTE.PreviewManager.get_instance().endPreview();
                 break;

 

             default:
                 return handleCommand(commandId, properties, sequence);
                 break;
         }
     }
 }

 

All the actions that are defined on the FlyoutAnchor, ColorPicker, etc are handled here in this function.

  • There are commands to populate the different colors:
    • GetDynamicColorPickerMenuXmlFunction()
  • Commands to begin and end the preview
    • RTE.PreviewManager.get_instance().beginPreview();
    • RTE.PreviewManager.get_instance().endPreview();
  • Commands to apply the color selected.
    • RTE.FontCommands.setColor(properties.Color, false);
    • RTE.FontCommands.setBackgroundColor(properties.Color, false);

You also need to register the actions on the server side, so that the SharePoint ribbon knows about them and calls them when necessary. In order to do this, a server side user control which I called RibbonColorPickerRegistration.ascx is included in the AdditionalPageHead and registers the command in the following way:

 protected void Page_Load(object sender, EventArgs e)
 {
     // Create ribbon commands
                       
     List<IRibbonCommand> commands = new List<IRibbonCommand>();
     commands.Add(new SPRibbonCommand("Ribbon.Custom.Tab", ""));
     commands.Add(new SPRibbonCommand("Ribbon.Custom.FontColorPickerFlyout", ""));
     commands.Add(new SPRibbonCommand("Ribbon.Custom.BackgroundColorPickerFlyout", ""));

 

     commands.Add(new SPRibbonCommand("GetDynamicColorPickerMenuXml", ""));
     commands.Add(new SPRibbonCommand("GetDynamicBackgroundColorPickerMenuXml", ""));

 

     commands.Add(new SPRibbonCommand("Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.ColorPicker_CMD", ""));
     commands.Add(new SPRibbonCommand("Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.ColorPicker_PRE_CMD", ""));
     commands.Add(new SPRibbonCommand("Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.ColorPicker_REV_CMD", ""));
     commands.Add(new SPRibbonCommand("Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.BackgroundColorPicker_CMD", ""));
     commands.Add(new SPRibbonCommand("Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.BackgroundColorPicker_PRE_CMD", ""));
     commands.Add(new SPRibbonCommand("Ribbon.List.CustomViews.ModifyView.Menu.Manage.Controls.BackgroundColorPicker_REV_CMD", ""));

 

     // Register ribbon scripts
                      
     SPRibbonScriptManager spRibbonScriptManager = new SPRibbonScriptManager();
     spRibbonScriptManager.RegisterGetCommandsFunction(this.Page, "getGlobalCommands", commands);
     spRibbonScriptManager.RegisterCommandEnabledFunction(this.Page, "canHandleCommand", commands);
     spRibbonScriptManager.RegisterHandleCommandFunction(this.Page, "handleCommand", commands);

 

     //Register Ribbon Page Components
                       
     RegisterInitializeFunction(this, "InitPageComponent", "/_layouts/ColorPicker/", "SP.Ribbon.Custom.UI.js", false, "SP.Ribbon.Custom.UI.RibbonAppPageComponent.initialize()");

            
 

     //Register the Required .JS files, which will be used by Site page
                      
     ScriptLink.RegisterScriptAfterUI(this.Page, "/_layouts/ColorPicker/CustomRibbon.js", true, false);
 }

 

Notice it also registers the initialization function and registers the CustomRibbon.js file.

Happy coding!

The solution can be found here.