SharePoint Rich Text Editor jQuery Plugin: How It Works

If you haven't already reviewed, provided as an FYI:

The C# Solution:

There are many solutions posted around the net about how to register an RTE on the back end from C#. I've added a header and footer RTE to a content by query web part and even extended the existing RTE for custom layouts. However, it's nice for users to work in the WYSIWYG UI rather than in the ugly Web Part Editor. The C# solution is explained here: http://zootfroot.blogspot.ca/2010/09/develop-custom-editable-visual-web-part.html and unfortunately as it requires C# code, it will only run in On Premise environments.

Challenges of Client Side Approach:

1) Loading Scripts without errors:

Office 365 leverages ms.rte.js in addition to sp.ui.rte.js so need to ensure both scripts are loaded with ms.rte.js needing to load first. The plugin handles loading both these files in the listed order, however it presumes you've already loaded the core SP dependencies mentioned in Challenge 2.

    $.fn.spHtmlEditor.load = {
        o365: load_o365,
        onprem: load_onprem
    };

    function load_o365(ready, path) {
        var p = path || "https://cdn.sharepointonline.com/16290/_layouts/15/16.0.4208.1226/";
        if ((typeof (g_all_modules) == "undefined"
            || typeof (g_all_modules["ms.rte.js"]) == "undefined")
            && $("#msrtejs").length == 0)
            $('<script id="msrtejs" type="text/javascript" src="' + p + 'ms.rte.js">' + '</' + 'script>').appendTo("body");

        ExecuteOrDelayUntilScriptLoaded(function () {
            load_onprem(ready, p);
        }, "ms.rte.js");

    }

    function load_onprem(ready, path) {

        var p = path || "/_layouts/15/";

        if ((typeof (g_all_modules) == "undefined"
            || typeof (g_all_modules["sp.ui.rte.js"]) == "undefined")
            && $("#spuirtejs").length == 0)
            $('<script id="spuirtejs" type="text/javascript" src="' + p + 'sp.ui.rte.js">' + '</' + 'script>').appendTo("body");

        ExecuteOrDelayUntilScriptLoaded(function () {
            ready();
        }, "sp.ui.rte.js");

    }
2) Loading Publishing Dependencies:

Publishing dependencies are required for the Ribbon Tab to work, we've copied this script from SharePoint and is executed on standard SP page load by hooking into the body on load function (this was copied from SP generated JavaScript):

    function EnsurePublishingConsoleActionScripts() {
        EnsureScripts(
        [['SP.Ribbon.js', 'SP.Ribbon', true],
        ['SP.Publishing.Resources.resx', 'SP.Publishing.Resources', false],
        ['SP.UI.Pub.Ribbon.js', 'Pub.Ribbon', true]], PublishingRibbonUpdateRibbon);
    }

    if (_spBodyOnLoadFunctionNames != null && !_spBodyOnLoadCalled) {
        _spBodyOnLoadFunctionNames.push('EnsurePublishingConsoleActionScripts');
    } else {
        EnsurePublishingConsoleActionScripts();
    }
3) Modifying Ribbon Loading Functionality

This piece of code enables the ribbon to dynamically load the RTE tabs (Format Text, Insert, Image, Table etc.) and is my least favorite part of this solution. When a user clicks on a SharePoint tab, a sequence of methods are chained via the EnsureScript method. One of these methods is a global _ribbonInitFunc1. If the RTE wasn't included on the page, we need to rewire this method to get the RTE loading when an interaction with the ribbon occurs. The methodology we're using is to create a clone of the global method, give it different parameters, and append to the document as to overwrite the original. Essentially what we're doing is making _ribbonInitFunc1 appear as it would if we had an editable RTE on the page:

    function fixRibbonLoader() {

        var func = _ribbonInitFunc1.toString(),
            findStart = /'RibbonContainer',/,
            pos = findStart.exec(func);

        if (func.indexOf("'WSSRTE': true") == -1) {

            var endPos = func.indexOf("}", pos.index),
                startStr = func.substr(0, endPos),
                endStr = func.substr(endPos);

            func = startStr + ", 'Ribbon.Table.Design': true, 'Ribbon.RcaTabGroup.Rca': true, 'Ribbon.Image.Image': true, 'Ribbon.Table.Layout': true, 'Ribbon.WebPartOption': true, 'Ribbon.WebPartInsert.Tab': true, 'Ribbon.EditingTools.CPInsert': true, 'Ribbon.EditingTools.CPEditTab': true, 'Ribbon.Link.Link': true" + endStr;

            func = func.replace("{'PublishTabTrimmingVisibilityContext':true,'WSSPageStateVisibilityContext':true}, false, 0, false", "{'WSSRTE': true,'PublishTabTrimmingVisibilityContext':true,'WSSPageStateVisibilityContext':true}, true, 0, false");
            func = func.replace("'SP.Ribbon.PageManager.get_instance()', false, null, null, null,", "'SP.Ribbon.PageManager.get_instance()', false, { 'Ribbon.EditingTools': true }, null, null,");
            $("<script>" + func + "</" + "script>").appendTo("body");

            _ribbonDataInit('Ribbon.EditingTools', true);
            _ribbonDataInit('Ribbon.EditingTools.CPEditTab', false);

        }

        _ribbonStartInit("Ribbon.Read", false, null);

    }
4) Wiring the entire plugin together:

For the body of this plugin we're essentially building a markup structure that's identical to a Content Editor Web Part / Rich HTML Field control. We take the HTML of the initialized HTML Element and toss it into the markup structure then initialize the RTE using the magic RTE public method fixRegion, passing in the element id of the container element as such:

RTE.Canvas.fixRegion("elementid", false)  
5) Retrieving HTML on submit / gethtml method:

The SharePoint RTE does add some ugly HTML Elements (EX// the cursor) so I'd advise having it self sterilize by transferring content into the hidden input field before reading:

var editId = $(".edit-content", $("#elementid")).attr("id");  
RTE.RichTextEditor.transferContentsToInputField(editId);  
return $("input[type='hidden']", $("#elementid")).val();

I hope this provides some clarity for how this plugin works. If you're looking for help integrating or need some SharePoint Consulting hours to get your project back on track please reach out.

Get in Touch!

Matthew Stark

Founder, Making Things Work

Let's talk about your project & how I can help! Reach out at the below coordinates.

Connect on Linkedin read more about Matt »

Top Tags