Posted on:
Categories: SharePoint;CodePlex
Description:

Previously I wrote a blog posting where I showed how to easily inject client-side code via delegates in SharePoint 2010. Now, I'd like to show how I extended that idea to allow authors in a SharePoint 2010 site to insert Code (i.e. C#, JavaScript, Powershell, etc) through the Content Editor interface, and automatically apply Syntax Highlighting.

The Problem

Recently we've been working on updating Softlanding's own internet site, including our Blog. When I started writing the article, I started copying in some code snip-its, and realized it didn't look so great using out-of-the-box SharePoint styles. So, I started reading some blog posts online, which show how to utilize the SyntaxHighlighter code from Alex Gorbatchev from within SharePoint.

At first, I thought great!...until I realized that this involved lots of manual insertion of Javascript into pages, and requiring users to edit the html markup in order to make use of the syntax highlighting. And so my next thought was, how can we integrate this into the SharePoint UI so that our users can easily include nicely-formatted code and scripts into their blog posts?

The Idea

My answer was to combine client-side code injection, with a custom SharePoint Ribbon button. This way, users would be able to just select their code, pick the language, save their post, and Voila! Syntax Highlighting in a SharePoint Blog! I'll briefly highlight the steps to implement this below, and if you'd like to see the final product on CodePlex, click here...And although this post is targeting only the SharePoint blog, with some simple xml changes you should be able to use this code for any Enhanced Rich Text field anywhere in SharePoint 2010!

The Solution

First, start a new SharePoint 2010 Project, and add in the class and module mentioned in my previous post. Now you should have the project all set up for script injection. All we need to do here is change what we're injecting into the page, so just modify the CreateChildControls() method as follows:

base.CreateChildControls();

if (SPContext.Current.FormContext.FormMode != SPControlMode.Edit &&
 SPContext.Current.FormContext.FormMode != SPControlMode.New)
{
 CssLink coreCss = new CssLink();
 coreCss.DefaultUrl = 
 "http://alexgorbatchev.com/pub/sh/current/styles/shCore.css";
 this.Controls.Add(coreCss);

 CssLink coreDefaultCss = new CssLink();
 coreDefaultCss.DefaultUrl = 
 "http://alexgorbatchev.com/pub/sh/current/styles/shCoreDefault.css";
 this.Controls.Add(coreDefaultCss);

 //TODO: can we use ScriptLink objects instead using external urls?
 this.Page.ClientScript.RegisterClientScriptInclude("shCore",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shAutoloader",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shAutoloader.js");


 //Load all brushes for languages that have been 
 // added to the SharePoint ribbon UI
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushCSharp",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushJScript",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushCpp",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCpp.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushCss",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCss.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushJava",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJava.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushPerl",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPerl.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushPhp",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPhp.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushPlain",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPlain.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushPowerShell",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPowerShell.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushPython",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPython.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushSql",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushSql.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushVb",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushVb.js");
 this.Page.ClientScript.RegisterClientScriptInclude("shBrushXml",
  "http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js");

 //lastly, load our file that contains any of our own 
 // javascript related to rendering syntax highlighting
 this.Page.ClientScript.RegisterClientScriptInclude(
 "slsyntaxrenderonly",
  @"/_layouts/Softlanding.SyntaxHighlighter/
   SLSyntaxHighlightRenderOnly.js");
}
else
{
 ScriptLink editOnlyScript = new ScriptLink();
 editOnlyScript.Name = 
 "/_layouts/Softlanding.SyntaxHighlighter/SLSyntaxHighlightEditOnly.js";
 editOnlyScript.Language = "javascript";
 editOnlyScript.Localizable = false;
 this.Controls.Add(editOnlyScript);
}

Next, we need to add the Ribbon button, so that users can make use of all this Javascript and CSS that we've just injected. To do that, open up the module that was added in my previous blog post (I called it ClientSideDelegateCtlElement), and inside the Elements tag above the Control tag, insert a CustomAction that declaratively creates the ribbon button while editing blog posts only. Note: I've only included the option to highlight C# for the sake of brevity, but you can add additional language options as desired.

<CustomAction
Id="SplitButtonRibbonControl"
RegistrationType="ContentType"
RegistrationId="0x0110"
Location="CommandUI.Ribbon">
<CommandUIExtension>
  <CommandUIDefinitions>
 <CommandUIDefinition
    Location="Ribbon.EditingTools.CPEditTab.Styles.Controls._children">
   <SplitButton
  Id="Ribbon.SplitButton"
  Sequence="0"
  Alt="Ribbon.SplitButton.Controls.CPFlyout_ALT"
  LabelText="Code Syntax"
  PopulateDynamically="false"
  PopulateOnlyOnce="true"
  TemplateAlias="o1"
  Image16by16=
  "/_layouts/Softlanding.SyntaxHighlighter/objectscript_16.png"
  Image32by32=
  "/_layouts/Softlanding.SyntaxHighlighter/objectscript_32.png" 
  ToolTipTitle="Code Syntax"
  ToolTipDescription="Apply Code Block Style"
  Command="ApplyDefault">
  <Menu Id="Ribbon.SplitButton.Menu">
    <MenuSection Id="Ribbon.SplitButton.Menu.MenuSection">
   <Controls Id="Ribbon.SplitButton.Menu.MenuSection.Controls">
     <Button
      Id="Ribbon.SplitButton.Menu.MenuSection.Controls.Button1"
      Alt=""
      Sequence="0"
      Command="ApplyCSharp"
      ToolTipTitle=""
      ToolTipDescription=""
      LabelText="C#"
      TemplateAlias="o1"/>
   </Controls>
    </MenuSection>
  </Menu>
   </SplitButton>
 </CommandUIDefinition>
  </CommandUIDefinitions>
  <CommandUIHandlers>
 <CommandUIHandler
  Command="ApplyDefault"
  CommandAction="javascript:ApplyCodeBlock('defaultbutton');"
  />
 <CommandUIHandler
  Command="ApplyCSharp"
  CommandAction="javascript:ApplyCodeBlock('csharp');"
  />
  </CommandUIHandlers>
</CommandUIExtension>
</CustomAction>

And lastly, you will need to add the javascript which ties everything together! You may have noticed above that in the server side code, I was injecting 2 JavaScript files from my /_layouts/ folder - called SLSyntaxHighlightRenderOnly.js and SLSyntaxHighlightEditOnly.js. As their names imply, each file contains the client side code necessary to handle user actions in edit mode, and the rendering of the syntax highlighting when the user is not editing the blog article.

The Catch

I noticed that the first time I tried to inject all of the SyntaxHighlighter JavaScript and CSS to every page, it broke some SharePoint content editing functionality, mainly the ribbon - so you have to be careful what javascript you inject when. I found that if I only injected the shCore.js, the brush js, and your rendering js in Display & Invalid mode, it behaved nicely. So then in Edit & New mode, you inject only the javascript you require for handling the ribbon actions (in my case, the ApplyCodeBlock() function and its helper methods).

So, now that I've explained my reasoning for these 2 files, here they are:

SLSyntaxHighlightEditOnly.js:

//NOTE: this has been tested in IE only; cross browser support
//   may require some extra cases to find selection text.
function ApplyCodeBlock(langName) {

    if (langName == "defaultbutton") {
        return;
    }

    var selectedText = (document.all) ? 
  document.selection.createRange() : 
  document.getSelection(); 

    if (selectedText != null && 
  selectedText.text != null && 
  selectedText.text.length > 0) {
        wrapSelectionWithPreTags(langName, selectedText.text);
    }
}


// This function sets the current selection to the selected text, 
//  wrapped inside of a pre-tag that Syntax Highlighting 
//  javascript will read and re-format
function wrapSelectionWithPreTags(langName, selectedTextStr) {
    var sel, range, node;

    var html = "<pre class='brush: " + langName + "'>" + 
    selectedTextStr + "</pre>";

    if (typeof window.getSelection != "undefined") {
        // IE 9 and other non-IE browsers
        sel = window.getSelection();

        // Test that the Selection object contains at least one Range
        if (sel.getRangeAt && sel.rangeCount) {
            // Get the first Range (only Firefox supports more than one)
            range = window.getSelection().getRangeAt(0);
            range.deleteContents();

            // Create a DocumentFragment to insert and populate it with HTML
            // Need to test for the existence of range.createContextualFragment
            // because it's non-standard and IE 9 does not support it
            if (range.createContextualFragment) {
                node = range.createContextualFragment(html);
            } else {
                // In IE 9 we need to use innerHTML of a temporary element
                var div = document.createElement("div"), child;
                div.innerHTML = html;
                node = document.createDocumentFragment();
                while ((child = div.firstChild)) {
                //while (child == div.firstChild) {
                    node.appendChild(child);
                }
            }
            range.insertNode(node);
        }
    } else if (document.selection && document.selection.type != "Control") {

        // IE 8 and below
        range = document.selection.createRange();
        range.pasteHTML(html);
    }
}

SLSyntaxHighlightRenderOnly.js:

//this just performs any required syntax highlighting on SP load.
SyntaxHighlighter.all();

So now, deploy to your Blog site, and start editing a new or existing Blog article. You should notice a new ribbon button that looks something like this (but with only C# in the drop down menu):

Syntax Ribbon Button

So paste in your plain text code, select it, click the new ribbon button, chose your language, and save your blog - and now view it....you should now have some amazing looking syntax highlighting, just like all the example text I've included in this blog entry!

And that should be it! And if you missed it, click here to view the full source on CodePlex. If you encounter any issues or have any questions/comments, feel free to leave a comment on this page and I'll try and respond.