TX Text Control .NET Server

Custom Tabs

Topic

Functions of your application, such as inserting data or saving to your own storage, can sit in the ribbon of the Document Editor next to the built-in tools. You add tabs, groups and buttons of your own, connect them to the JavaScript API, add a group to a built-in tab and switch to your tab.

The examples change the view of the ASP.NET Core tutorial. How the ribbon is built, and how built-in elements are removed, is described in Ribbon.

How Custom Tabs Work

The JavaScript API has no methods that create tabs, groups or buttons. The ribbon consists of HTML elements and CSS, so you add your own elements to it with the methods of the browser's DOM:

  1. Write the HTML of the tab, group or button with the CSS classes of the ribbon, so that it looks like the built-in elements.
  2. Insert the elements when the editor raises ribbonTabsLoaded. Before that event, the ribbon isn't complete.
  3. Handle the clicks on your buttons and call the JavaScript API.
  4. For a new tab, show its content when its header is clicked, and hide it when the user selects another tab.

The element IDs and CSS classes of the ribbon come from the blog articles and samples of Text Control, not from the API reference. In version 34.0, the ribbons of the editor on the Core engine were changed, so test your additions after every upgrade.

Add a Custom Tab

A tab consists of a header and a content element:

  • The header is a list item with a link, added to the list of tab headers (#ribbonbar ul.tabs). The rel attribute of the link contains the ID of the content element.
  • The content is a div element with the class tab-content, added to the container of all tab contents (#txRibbonTabContentContainer). It contains the groups of the tab.

The following view adds a Custom tab with a Document group and two buttons: Insert Name inserts a name at the input position, and Save as HTML logs the document as HTML. Replace the content of Views/Home/Index.cshtml, and put two icons of 32 by 32 pixels as name.png and save.png into the wwwroot/images folder of the project:

@using TXTextControl.Web.MVC

@{
    var sDocument = "<html><body><p>Welcome to <strong>Text Control</strong></p></body></html>";
}

@Html.TXTextControl().TextControl(settings => {
    settings.UserNames = new string[] { "Tim Typer" };
}).LoadText(sDocument, TXTextControl.Web.StringStreamType.HTMLFormat).Render()

<!-- The header of the custom tab -->
<template id="customTabHeader">
    <li>
        <a id="tabCustom" tabindex="-1" rel="ribbonTabCustom" data-applicationmodes="0" data-text="Custom">
            <div class="txTabTextContainer">Custom</div>
        </a>
    </li>
</template>

<!-- The content of the custom tab: one group with two buttons -->
<template id="customTabContent">
    <div class="tab-content" id="ribbonTabCustom" style="display: none;">
        <div class="ribbon-group" id="ribbonGroupCustomDocument" data-applicationmodes="0">
            <div class="ribbon-group-content">
                <div class="ribbon-button ribbon-button-big" id="btnCustomName" role="button">
                    <div class="ribbon-button-big-image-container">
                        <img src="/images/name.png" alt="Insert Name" class="ribbon-button-big-image" style="height: 32px; width: 32px;">
                    </div>
                    <div class="ribbon-button-big-label-container">
                        <p class="ribbon-button-label"><span class="ribbon-button-label-text">Insert Name</span></p>
                    </div>
                </div>
                <div class="ribbon-button ribbon-button-big" id="btnCustomSave" role="button">
                    <div class="ribbon-button-big-image-container">
                        <img src="/images/save.png" alt="Save as HTML" class="ribbon-button-big-image" style="height: 32px; width: 32px;">
                    </div>
                    <div class="ribbon-button-big-label-container">
                        <p class="ribbon-button-label"><span class="ribbon-button-label-text">Save as HTML</span></p>
                    </div>
                </div>
            </div>
            <div class="ribbon-group-footer">
                <div class="ribbon-group-label-container">
                    <p class="ribbon-group-label">Document</p>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    // Elements can only be added when the ribbon has been loaded completely
    TXTextControl.addEventListener("ribbonTabsLoaded", function () {
        var tabs = document.querySelector("#ribbonbar ul.tabs");
        var contentContainer = document.getElementById("txRibbonTabContentContainer");

        if (!tabs || !contentContainer) {
            console.error("Ribbon elements not found");
            return;
        }

        // Add the header after the last tab and the content to the content container
        tabs.appendChild(document.getElementById("customTabHeader").content.cloneNode(true));
        contentContainer.appendChild(document.getElementById("customTabContent").content.cloneNode(true));

        document.getElementById("tabCustom").addEventListener("click", activateCustomTab);

        document.getElementById("btnCustomName").addEventListener("click", function () {
            TXTextControl.selection.setText("Tim Typer");
        });

        document.getElementById("btnCustomSave").addEventListener("click", function () {
            TXTextControl.saveDocument(TXTextControl.StreamType.HTMLFormat, function (result) {
                console.log(result.data);
            });
        });
    });

    // Hide all tab contents, show the custom one and mark its header as selected
    function activateCustomTab() {
        TXTextControl.ribbon.expand();

        document.querySelectorAll("#txRibbonTabContentContainer div.tab-content").forEach(function (content) {
            content.style.display = "none";
        });
        // The link and the text container of each header carry the class "selected"
        document.querySelectorAll("#ribbonbar ul.tabs a, #ribbonbar ul.tabs .txTabTextContainer").forEach(function (header) {
            header.classList.remove("selected");
        });

        document.getElementById("ribbonTabCustom").style.display = "inline-block";
        document.getElementById("tabCustom").classList.add("selected");
        document.querySelector("#tabCustom .txTabTextContainer").classList.add("selected");
    }

    // Hide the custom tab when the user selects a built-in tab
    TXTextControl.addEventListener("ribbonTabIndexChanged", function () {
        var content = document.getElementById("ribbonTabCustom");
        if (content) {
            content.style.display = "none";
            document.getElementById("tabCustom").classList.remove("selected");
        }
    });
</script>

The code works as follows:

  • The HTML of the tab is in template elements, which the browser doesn't display. cloneNode copies their content when the ribbon is ready.
  • The ribbonTabsLoaded handler inserts the header into the list of tab headers and the content into #txRibbonTabContentContainer. It checks first that both elements exist, because their IDs can change between versions.
  • The buttons get click handlers with addEventListener. Insert Name calls Selection.setText, which inserts the text at the input position. Save as HTML calls saveDocument, whose SaveDocumentResult.data contains the HTML as plain text.
  • activateCustomTab switches to the custom tab: Ribbon.expand expands a minimized ribbon, the contents of all other tabs are hidden and the class selected marks the header of the custom tab, on its link and on its txTabTextContainer element, as the built-in headers have it.
  • The ribbonTabIndexChanged handler hides the custom tab again when the editor switches to one of its own tabs.

Run the application: the ribbon has a Custom tab after its built-in tabs. Click it, place the cursor in the text and click Insert Name.

Ribbon of the Document Editor with the added Custom tab selected, showing the Document group with the buttons Insert Name and Save as HTML

The markup follows the built-in groups of the Document Editor of TXTextControl.Web 34.5.0, in which the large buttons are direct children of ribbon-group-content. With an extra wrapper element around each button, the buttons of the group are stacked below each other in that version instead of being placed side by side.

Give every element you add an ID of its own. An ID that a built-in element already uses, such as ribbonGroupClipboard, makes both elements hard to address.

The CSS Classes of the Ribbon

ElementCSS classes
Tab contenttab-content
Groupribbon-group, with ribbon-group-content for the buttons and ribbon-group-footer with ribbon-group-label-container and ribbon-group-label for its name
Large buttonribbon-button ribbon-button-big, placed directly in ribbon-group-content
Button imageribbon-button-big-image in a ribbon-button-big-image-container
Button labelribbon-button-label with ribbon-button-label-text, in a ribbon-button-big-label-container

Give buttons role="button" and their images an alt text, so that screen readers can describe them. The icons are files of your application; host them in wwwroot or another folder your application serves.

Add a Group to a Built-In Tab

A group can also be added to a built-in tab, next to one of its groups. The following code inserts a group with a Toggle Bold button in front of the Font group of the Home tab. Add the template and the script to the view above, and put an icon bold.png into wwwroot/images:

<template id="customHomeGroup">
    <div class="ribbon-group" id="ribbonGroupCustomHome" data-applicationmodes="0">
        <div class="ribbon-group-content">
            <div class="ribbon-button ribbon-button-big" id="btnCustomBold" role="button">
                <div class="ribbon-button-big-image-container">
                    <img src="/images/bold.png" alt="Toggle Bold" class="ribbon-button-big-image" style="height: 32px; width: 32px;">
                </div>
                <div class="ribbon-button-big-label-container">
                    <p class="ribbon-button-label"><span class="ribbon-button-label-text">Toggle Bold</span></p>
                </div>
            </div>
        </div>
        <div class="ribbon-group-footer">
            <div class="ribbon-group-label-container">
                <p class="ribbon-group-label">Custom</p>
            </div>
        </div>
    </div>
</template>

<script>
    TXTextControl.addEventListener("ribbonTabsLoaded", function () {
        var fontGroup = document.getElementById("ribbonGroupFont");

        if (!fontGroup) {
            console.error("Font group not found");
            return;
        }

        // Insert the group in front of the Font group of the Home tab
        fontGroup.parentNode.insertBefore(
            document.getElementById("customHomeGroup").content.cloneNode(true), fontGroup);

        document.getElementById("btnCustomBold").addEventListener("click", function () {
            TXTextControl.selection.getBold(function (bold) {
                TXTextControl.selection.setBold(!bold);
            });
        });
    });
</script>
  • ribbonGroupFont is the ID of the Font group. insertBefore inserts the new group in front of it, in the same tab.
  • Selection.getBold passes whether the selected text is bold, and setBold changes it. Text and Paragraph Formatting describes the other formatting methods.

To find the IDs of other groups, inspect the ribbon with the developer tools of the browser.

Add a File Tab

The ribbon of the ASP.NET Core Document Editor has no File menu for opening and saving documents. The blog article Build a Custom Backstage View in ASP.NET Core with TX Text Control adds a File tab as the first tab. It opens an overlay over the editor in which users choose documents in formats such as DOCX, RTF, TX and PDF, which a controller loads on the server. Its complete source code, which uses TXTextControl.Web 34.1.0, is available on GitHub: TXTextControl.Web.Backstage. The functions of such a tab use the methods described in Loading and Saving Documents.

Custom Tabs in Other Frontends

The HTML and the handlers are the same in every frontend. Only the place where the ribbonTabsLoaded handler is attached differs:

  • Angular. Attach it in a handler of the component's txDocumentEditorLoaded event. The blog article Customizing the Ribbon in TX Text Control for Angular loads the HTML of a group from a file and inserts it this way.
  • React. Attach it in the function of the onLoad property, as shown in React.
  • Plain JavaScript page. Attach it after the call of TXTextControl.init, as shown in JavaScript.

Next Steps