Skip to main content

Inline text editing

JointJS+ provides an TextEditor plugin that is a powerful (rich-text) inline text editor that can be used on any SVG text element.

Installation

Access TextEditor via the ui namespace. Then, start your text editor via the recommended ui.TextEditor.edit() method.

import { ui } from '@joint/plus';

paper.on('element:pointerdblclick', (elementView, evt) => {
ui.TextEditor.edit(evt.target, {
cellView: elementView,
textProperty: ['attrs', 'label', 'text'],
annotationsProperty: ['attrs', 'label', 'annotations']
});
});
There is also a UMD version available

Include ui.textEditor.js and ui.textEditor.css in your HTML:

index.html
<link rel="stylesheet" type="text/css" href="ui.textEditor.css">
<script src="js"></script>
<script src="ui.textEditor.js"></script>

Access TextEditor through the joint.ui namespace:

index.js
paper.on('element:pointerdblclick', (elementView, evt) => {
joint.ui.TextEditor.edit(evt.target, {
cellView: elementView,
textProperty: ['attrs', 'label', 'text'],
annotationsProperty: ['attrs', 'label', 'annotations']
});
});

How does TextEditor work?

TextEditor is a rich-text inline editor that can be used on any SVG text element. It provides features which users have come to expect from modern text editors and more such as:

  • rich-text editing of SVG text
  • caret & selections
  • caret & selections can be styled in CSS
  • double-click to select words, triple-click to select the entire text
  • keyboard navigation native to the underlying OS
  • API for programmatic access
  • support for editing scaled & rotated text
  • automatic URL hyperlinks detection and styling

There are two methods for starting up a text editor. ui.TextEditor.edit() is the easiest, and it's recommended. You can also create an instance of ui.TextEditor.

If you use the recommended way of creating the text editor via ui.TextEditor.edit(), you usually do not want to deal with the actual instance (even though you can always access the actual instance via the ui.TextEditor.ed property). To make it as easy working with the instance as it is with a black-box, the ui.TextEditor constructor exposes many of the methods directly (internally, it just proxies the methods to the actual instance.)

ui.TextEditor.edit() is a helper method that does a lot of things for us. First, it finds the nearest SVG <text> DOM element. Then, it creates an instance of the ui.TextEditor type, and binds handlers for the 'text:change' event where it stores the new text content to your JointJS cells (elements or links). It also deals with annotations in case of rich text editing and more.

Inline Text Editing

In the following example, we will show how to start inline text editing when the user double-clicks text inside a JointJS element or link. First, we handle the cell:pointerdblclick event triggered by the paper. Inside our handler, we call the ui.TextEditor.edit() method which starts the text editor on the SVG text under the target of the event. The only parameters we have to pass to this method are the SVG text DOM element, the cell view, and the property path that the text editor will use to store the resulting text. Moreover, we also show how easy it is to apply auto-sizing on the text inside our elements so that they stretch based on the bounding box inside it.

// RECOMMENDED

paper.on('element:pointerdblclick', (elementView, evt) => {
ui.TextEditor.edit(evt.target, {
cellView: elementView,
textProperty: ['attrs', 'label', 'text'],
annotationsProperty: ['attrs', 'label', 'annotations']
});
});

paper.on('link:pointerdblclick', (linkView, evt) => {
// Editing a link label
const index = Number(linkView.findAttribute('label-idx', evt.target));
ui.TextEditor.edit(evt.target, {
cellView: linkView,
textProperty: ['labels', index, 'attrs', 'text', 'text'],
annotationsProperty: ['labels', index, 'attrs', 'text', 'annotations']
});
});

function autoSize(element) {

const view = paper.findViewByModel(element);
const textVel = view.vel.findOne('text');
// Use bounding box without transformations so that our auto-sizing works
// even on e.g. rotated element.
const bbox = textVel.getBBox();
// 16 = 2*8 which is the translation defined via ref-x ref-y for our rb element.
element.resize(bbox.width + 16, bbox.height + 16);
}

graph.on('change:attrs', function(cell) { autoSize(cell) });

Inadvisable TextEditor usage

ui.TextEditor.edit() is the recommended way to start a text editor. However, for completeness, we will show another way to start text editing, and that is by manually creating the instance of the ui.TextEditor. This way of working with text editing is not recommended as you really need to know what you're doing.

// NOT RECOMMENDED

let ed;
paper.on('cell:pointerdblclick', (cellView, evt) => {
const text = ui.TextEditor.getTextElement(evt.target);
if (text) {
if (ed) ed.remove(); // Remove old editor if there was one.
ed = new ui.TextEditor({ text: text });
ed.render(paper.el);

ed.on('text:change', (newText) => {
// Set the new text to the property that you use to change text in your views.
cellView.model.attr('text/text', newText);
});
}
});
info

Using the recommended method, you can still access the instance of the text editor via the ui.TextEditor.ed property.