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:
<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:
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);
});
}
});
Using the recommended method, you can still access the instance of the text editor via the ui.TextEditor.ed
property.