Extending EPiServer link in TinyMCE to support anchors on page


Extending EPiServer link in TinyMCE to support anchors on page

Extending EPiServer link in TinyMCE to support anchors on page


Recently, on a couple of projects, we were in need to add an anchor navigation inside content pages. This resulted in clients asking for a possibility to link to some specific part of content and not only to the top of the page. So, here is how.


So, this was the plan:

  1. Create custom TinyMCE button from C#
  2. Add dojo to define the button
  3. Populate the anchor dropdown once a page is selected.


So, let's see step-by-step guide.

Creating a custom TinyMCE button

In order to create the button, I created four classes:

  1. AnchorsOnPageEditorDescriptor
  2. ExtendedEPiLinkModel
  3. ExtendedEPiLinkConfigurationHandler
  4. ExtendedEPiLink

Anchors editor descriptor

The first one, is simply an editor descriptor for a dropdown list, that currently only sets the width for the widget. If the dropdown could be populated statically, then, I could've written a selection factory. However, since the dropdown needs to be populated per page (with all anchors from the specific page), this was not an option.

Extended model for the link

ExtendedEPiLinkModel is taken from LinkModel found in the namespace EPiServer.Cms.Shell.UI.ObjectEditing.InternalMetadata. The change is the new field:

            Name = "/contenttypes/icontentdata/properties/anchoronpage/caption",
            Order = 50)]
        public string AnchorOnPage { get; set; }

and the methods that are converting the client to server model and vice-versa. * However, the breakpoint never gets hit in any of these methods, most of the conversion logic is in dojo widgets.

Extended TinyMCE link configuration handler

Extended configuration handler is necessary in order to fetch the FullName of the extended link, so this is the important part:

    dictionary["extendedepilinkmodel_type"] = typeof(ExtendedEPiLinkModel).FullName;

The key in the dictionary will be used inside the dojo code and the configuration handler inside the button definition.

The button definition

        ButtonName = "extendedepilink", 
        ButtonSortIndex = 1, 
        DynamicConfigurationOptionsHandler = typeof(ExtendedEPiLinkConfigurationHandler), 
        GroupName = "media",
        IconUrl = "/ClientResources/Images/icons/extended-link-icon.gif",
        LanguagePath = "/admin/tinymce/plugins/epilink/epilink",
        PlugInName = "extendedepilink")]
    public class ExtendedEPiLink

The language path can be changed and the image needs to be added to the path specified.

The IconUrl needs to be placed where specified. However, without further changes, the icon will be visible in admin mode only. To have the icon in Edit mode as well, the proper CSS needs to be added:

    .epi-lightSkin .mceIcon.mce_extendedepilink {
        background: url(/ClientResources/Images/icons/extended-link-icon.gif) no-repeat;

I've placed TinyMce.css to ClientResources/Styles and referenced the styles in modules.config. This is also a mandatory step. See module.config file here.

Add JavaScript to define the new button

Now, you should see your icon in admin mode, however, if you add it to the editor, this will break editing functionality, since the engine won't be able to find the button definition. The file needs to be placed in the project_path/util/editor/tinymce/plugins/extendedepilink/editor_plugin.js. The file can be found here.

The important points are:

  • The name of the button is "tinymce.plugins.extendedepilink"
  • Corresponding definitions need to include proper naming as well (extendedepilink, etc.)
  • Converting href to Page + anchor and setting back the href value from the Page and anchor

As for the last part, it's quite easy - what is saved to tinymce is a href+anchor, so when you load an already populated link, you need to set the Page + anchorOnPage:

    href = dom.getAttrib(selectedLink, "href")), href.length && (
        linkObject.href = href.indexOf("#") != -1 ? href.substring(0, href.indexOf("#") - 1) : href,
        linkObject.targetName = dom.getAttrib(selectedLink, "target"),
        linkObject.anchorOnPage = href.indexOf("#") != -1 ? href.substring(href.indexOf("#") + 1) : "",
        linkObject.title = dom.getAttrib(selectedLink, "title"));

The other piece of a puzzle is to save it back to href for the markup:

    var linkAttributes = {
        href: value.anchorOnPage != null && value.anchorOnPage != '' ?
            value.href + '#' + value.anchorOnPage : value.href,
        title: value.title,
        target: value.target ? value.target : null

Populate the anchor dropdown once a page is selected

While 1 and 2 were not super quick to do, the real challenge is to get ahead of dojo stuff.

Details about the dojo code (included in the file are explained in more details here).