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.

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:
Live Demo
See this demo implementation in our live demos. We added this contextual toolbar to one of the live samples.
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.
Download and Fork This Sample on GitHub
We proudly host our sample code on github.com/TextControl.
Please fork and contribute.
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.
Related Posts
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.
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.
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…
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.
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…