Products Technologies Demo Docs Blog Support Company

Document Editor: Implementing a Contextual Toolbar

Contextual toolbars can be used to provide users quick access to the most common functions and settings. This article shows how to implement a contextual toolbar to manipulate form fields in a document.

Document Editor: Implementing a Contextual Toolbar

Contextual toolbars are very helpful to provide users quick access to the most common functions and settings. A smart implementation helps to increase the productivity and user experience. This article shows how to implement a contextual toolbar to manipulate form fields in a document.

Sample Implementation

The following screen video shows the sample implementation of a contextual toolbar to select all text and to remove text from a form field:

Contextual Toolbar

Live Demo

See this demo implementation in our live demos. We added this contextual toolbar to one of the live samples.

Live Demo

The contextual toolbar can be any DIV element you design in your page or a partial view:

<div id="formFieldContextMenu" data-toggle="tooltip" data-placement="left" title="To select text, click form field again when it is active">
    <img title="Selects all text" onclick="ContextMenu.select()" class="tx-context-btn" src="~/Images/SelectAll.svg" />
    <img title="Removes all text" onclick="ContextMenu.removeText()" class="tx-context-btn" src="~/Images/FormFieldRemoveContent.svg" />
    <input id="cmFieldName" disabled type="text" value="[Field name]" />
</div>

In case the user enters a form field, the textFieldEntered event is fired:

TXTextControl.addEventListener("textFieldEntered", attachClickEvent);

In the event handler, the context menu is shown (using the drawContextMenu method) in case the field is an editable form field:

function attachClickEvent(field) {

    // remove active listeners
    TXTextControl.removeEventListener("textFieldClicked", selectField);

    // handle only editable form text fields
    if (field.textField.type === "TEXTFORMFIELD" && field.textField.editable === true) {

        // store current field for later comparison
        _currentField = field;

        // set the input value
        document.querySelector("#cmFieldName").value = field.textField.name;

        drawContextMenu();
        TXTextControl.addEventListener("textFieldClicked", selectField);
    }
}

The drawContextMenu function positions the context menu under the selected form field and shows it by changing the display CSS property:

function drawContextMenu() {

    $("#formFieldContextMenu").tooltip("hide");

    // get the singleton context menu
    _divOvr = document.getElementById("formFieldContextMenu");

    // retrieve the field location in the document
    var fldPos = _currentField.textField.bounds;

    // and calculate the offset location and zoom factor
    var x = _textView.offsetLeft + (fldPos.location.x / 15 - _txtViewLoc.x) * _zoom;
    var y = _textView.offsetTop + ((fldPos.location.y / 15) 
                                + (fldPos.size.height / 15) - _txtViewLoc.y) * _zoom;

    // set position and size
    _divOvr.style.zIndex = _textView.style.zIndex;
    _divOvr.style.left = x + "px";
    _divOvr.style.top = y + 5 + "px";
    _divOvr.style.display = "flex";

    // show tooltip once
    if (_tooltipShown === false) {
        $("#formFieldContextMenu").tooltip("show");
        _tooltipShown = true;
    }
    else
        $("#formFieldContextMenu").tooltip("disable");
}

The full JavaScript function is shown below. All functions are encapsulated in a private object ContextMenu with 2 public methods select and removeText:

var ContextMenu = (function (tx) {
    var _txtViewLoc = { x: 0, y: 0 };
    var _divOvr;
    var _textView;
    var _zoom = 1.0;
    var _currentField;
    var _container;
    var _tooltipShown = false;

    function drawContextMenu() {

        $("#formFieldContextMenu").tooltip("hide");

        // get the singleton context menu
        _divOvr = document.getElementById("formFieldContextMenu");

        // retrieve the field location in the document
        var fldPos = _currentField.textField.bounds;

        // and calculate the offset location and zoom factor
        var x = _textView.offsetLeft + (fldPos.location.x / 15 - _txtViewLoc.x) * _zoom;
        var y = _textView.offsetTop + ((fldPos.location.y / 15) 
                                    + (fldPos.size.height / 15) - _txtViewLoc.y) * _zoom;

        // set position and size
        _divOvr.style.zIndex = _textView.style.zIndex;
        _divOvr.style.left = x + "px";
        _divOvr.style.top = y + 5 + "px";
        _divOvr.style.display = "flex";

        // show tooltip once
        if (_tooltipShown === false) {
            $("#formFieldContextMenu").tooltip("show");
            _tooltipShown = true;
        }
        else
            $("#formFieldContextMenu").tooltip("disable");
    }

    function attachClickEvent(field) {

        // remove active listeners
        TXTextControl.removeEventListener("textFieldClicked", selectField);

        // handle only editable form text fields
        if (field.textField.type === "TEXTFORMFIELD" && field.textField.editable === true) {

            // store current field for later comparison
            _currentField = field;

            // set the input value
            document.querySelector("#cmFieldName").value = field.textField.name;

            drawContextMenu();
            TXTextControl.addEventListener("textFieldClicked", selectField);
        }
    }

    function selectField(field) {

        // field is given through "textFieldClicked" event
        if (field !== null) {

            // return in case another field is clicked
            if (_currentField.textField.start !== field.textField.start)
                return;

            // detach event handler
            TXTextControl.removeEventListener("textFieldClicked", selectField);
        }

        // find field at input position and select it
        TXTextControl.formFields.getItem(function (field) {

            field.getStart(function (start) {
                field.getLength(function (length) {
                    TXTextControl.select(start - 1, length);
                });
            });

        });
    }

    function removeText() {

        // get field at input position and remove text
        TXTextControl.formFields.getItem(function (field) {
            field.setText("");
        });
    }

    function textViewLocationChangedHandler(e) {
        _txtViewLoc = e.location;
        if (!_divOvr) return;
        drawContextMenu()
    }

    function zoomFactorChangedHandler(e) {
        _zoom = e.zoomFactor / 100.0;
        if (!_divOvr) return;
        drawContextMenu();
    }

    function hideContextMenu() {
        _currentField = null;
        $("#formFieldContextMenu").tooltip("hide");
        _divOvr.style.display = "none";
    }

    function window_load() {

        _textView = document.getElementById("mainCanvas");
        _container = document.getElementById("txTemplateDesignerContainer");

        TXTextControl.addEventListener("textControlLoaded", function () {
            TXTextControl.setDrawingMarkerLines(false);
            TXTextControl.setTextFrameMarkerLines(false);

            TXTextControl.addEventListener("textViewLocationChanged",
                                           textViewLocationChangedHandler);
            TXTextControl.addEventListener("zoomFactorChanged", zoomFactorChangedHandler);

            TXTextControl.addEventListener("textFieldEntered", attachClickEvent);
            TXTextControl.addEventListener("textFieldChanged", attachClickEvent);
            TXTextControl.addEventListener("textFieldLeft", hideContextMenu);

            _divOvr = document.getElementById("formFieldContextMenu");
            _container.appendChild(_divOvr);

            // demo only
            TXTextControl.formFields.addTextFormField(2000);

        });

    }

    window.addEventListener("load", window_load);

    // exported (public) methods accessible through the ContextMenu object
    // usage: ContextMenu.select();
    tx = {

        select: function () { return selectField(null); },
        removeText: function () { return removeText(); }

    }

    return tx;

})(ContextMenu || {});

The full implementation can be found in the demo project on GitHub.

Stay in the loop!

Subscribe to the newsletter to receive the latest updates.

GitHub

Download and Fork This Sample on GitHub

We proudly host our sample code on github.com/TextControl.

Please fork and contribute.

Download ZIP

Open on GitHub

Open in Visual Studio

Requirements for this sample

  • TX Text Control .NET Server 30.0
  • Visual Studio 2022

Angular

Integrate document processing, editing, sharing, collaboration, creation, electronic signatures, and PDF generation into your Angular Web applications.

Learn more about Angular

Related Posts

AngularASP.NETDocumentEditor

Announcing New Signature Features: Initials and SVG

We are working on new DocumentViewer features for electronic signatures. The next version supports initials and SVG signatures for better export quality.


AngularASP.NETDocumentEditor

Angular: Loading Documents from an ASP.NET Core Backend

This sample shows how to load documents into the Angular document editor from an ASP.NET Core backend application using Web API Http requests.


AngularASP.NETBlazor

Building an ASP.NET Core Backend (Linux and Windows) for the Document Editor…

This article shows how to create a backend for the Document Editor and Viewer using ASP.NET Core. The backend can be hosted on Windows and Linux and can be used in Blazor, Angular, JavaScript, and…


AngularASP.NETASP.NET Core

Impressions from .NET Developer Conference DDC 2024

This week we sponsored the .NET Developer Conference DDC 2024 in Cologne, Germany. It was a great event with many interesting talks and workshops. Here are some impressions of the conference.


AngularASP.NETASP.NET Core

Back from Florida: Impressions from VSLive! Orlando 2024

We had an incredible time showcasing our digital document processing SDKs at VSLive! Orlando 2024 as a silver sponsor! Throughout the event, we interacted with hundreds of developers, shared…