🎉 JointJS has new documentation! 🥳
ui.TextEditor
is a powerful (rich-text) inline text editor that can be used on any SVG text element.
The ui.TextEditor
provides the following features:
Include joint.ui.textEditor.js
and joint.ui.textEditor.css
files to your HTML:
<link rel="stylesheet" type="text/css" href="joint.ui.textEditor.css">
<script src="joint.ui.textEditor.js"></script>
In the following example, we will show how to start inline text editing when the user
double-clicks a text inside a JointJS element or a link. First we handle the cell:pointerdblclick
event
triggered by the paper. Inside our handler, we call the joint.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 is the SVG text DOM element, the cell view and the property path
that will the text editor 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', function(elementView, evt) {
joint.ui.TextEditor.edit(evt.target, {
cellView: elementView,
textProperty: ['attrs', 'label', 'text'],
annotationsProperty: ['attrs', 'label', 'annotations']
});
});
paper.on('link:pointerdblclick', function(linkView, evt) {
// Editing a link label
var index = Number(linkView.findAttribute('label-idx', evt.target));
joint.ui.TextEditor.edit(evt.target, {
cellView: linkView,
textProperty: ['labels', index, 'attrs', 'text', 'text'],
annotationsProperty: ['labels', index, 'attrs', 'text', 'annotations']
});
});
function autoSize(element) {
var view = paper.findViewByModel(element);
var textVel = view.vel.findOne('text');
// Use bounding box without transformations so that our auto-sizing works
// even on e.g. rotated element.
var 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) });
Note that there are two methods for starting up a text editor.
The one we showed above (via the joint.ui.TextEditor.edit()
function)
is the easiest and recommended. The joint.ui.TextEditor.edit()
function
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 joint.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.
However, for completeness, we show another way to start text editing and that is
by manually creating the instance of the joint.ui.TextEditor
(not recommended
if you don't know what you're doing):
// NOT RECOMMENDED
var ed;
paper.on('cell:pointerdblclick', function(cellView, evt) {
var text = joint.ui.TextEditor.getTextElement(evt.target);
if (text) {
if (ed) ed.remove(); // Remove old editor if there was one.
ed = new joint.ui.TextEditor({ text: text });
ed.render(paper.el);
ed.on('text:change', function(newText) {
// Set the new text to the property that you use to change text in your views.
cellView.model.attr('text/text', newText);
});
}
});
Note that using the first method, you can still access the actual instance of the text editor
via joint.ui.TextEditor.ed
property.
See the source code of the demo below for a complete example on using the inline text editor, including inline text editing of link labels.
Source CodeThe following table lists options that you can pass to the ui.TextEditor.edit(el, opt)
method (this is the recommended way to start you text editor):
el | SVG <text> element or any of its descendants. Usually, you pass the event target object when the user doubleclicks your JointJS element/link (see examples above). |
---|---|
opt.cellView | The view for your cell. Usually, you either have a direct access to this view
(in the paper triggered 'cell:pointerclick' or 'cell:pointerdblclick' events)
but you can always find a view for any JointJS element or link by using paper.findViewByModel(cell) .
|
opt.textProperty | The path to the text property that causes the element/link to re-render the text inside it.
(e.g. 'attrs/text/text' ).
|
opt.annotationsProperty | The path to the property that holds the text annotations.
(e.g. 'attrs/text/annotations' ).
(Rich-text specific.)
|
opt.placeholder | Set to true to show a placeholder when the text is empty. The placeholder text and
visual look can be styled in CSS as follows:
|
opt.annotations | Annotations that will be set on the ui.TextEditor instance.
(Rich-text specific.)
|
opt.annotateUrls | Set to true to enable the text editor to automatically detect URL hyperlinks
and annotate them using the default urlAnnotation .
|
opt.urlAnnotation | The default annotation for detected URL hyperlinks:
|
opt.useNativeSelection |
ui.TextEditor uses native selections for
selecting text. This can be disabled by setting useNativeSelection to false in which case
ui.TextEditor renders its own selections. This is an advanced feature that is useful only in very rare occasions.
For example, if you, for any reason, need to select text in two different elements at the same time,
you should set the useNativeSelection to false . This is because
if native selections are used, two or more text elements cannot have focus at the same time.
|
opt.textareaAttributes |
Set attributes on the underlying (invisible) textarea that is used internally
by the text editor to handle keyboard text input. This is an advanced option
and is useful in very rare situations. The default value is:
Some jQuery UI users reported an issue with autocomplete attribute
being set on the textarea, resulting in the following error thrown in the jQuery UI:
Uncaught Error: cannot call methods on autocomplete prior to initialization; attempted to call method 'off'.
In this case, you might want to set your own textareaAttributes to overcome this issue
(in this specific case, omitting the autocomplete attribute).
|
The following table lists options that you can pass to the ui.TextEditor
constructor function (only for advanced use):
text | SVG <text> element. It is recommended to use the joint.ui.TextEditor.getTextElement(el)
method to find the container <text> SVG element wrapping all the lines. You can
use this method directly on the evt.target element in your event handlers
and just check if the returned value is truthy.
|
---|---|
placeholder | Set to true to show a placeholder when the text is empty. The placeholder text and
visual look can be styled in CSS as follows:
|
The following table lists methods that can be used on the joint.ui.TextEditor
instance.
If you, however, use the recommended way of creating the text editor via the
joint.ui.TextEditor.edit()
function, you usually do not want to deal with the actual
instance (even though you can as you can always access the actual instance via joint.ui.TextEditor.ed
property).
To make it easy to work with the instance as with a black-box, joint.ui.TextEditor
constructor
exposes many of the methods directly (internally, it just proxies the methods to the actual instance.)
The table below differentiates the instance methods from the class methods by using the full
namespace access for the class methods (such as joint.ui.TextEditor.getAnnotations()
= class method - vs
getAnnotations()
= instance method).
render([root]) | Render the Text Editor. Call this method right after you have constructed
the text editor instance and you're ready to display the inline text editor.
If root (HTML DOM element) is specified, the text editor HTML container will be appended
to that element instead of to the document.body . This is useful if you want the
text editor to scroll with some other container.
|
---|---|
remove() | Remove the Text Editor. Call this when you're done with inline text editing. |
selectAll() | Programmatically select all the text inside the text editor. |
select(selectionStart, selectionEnd) | Programmatically select portion of the text inside the text editor starting
at selectionStart ending at selectionEnd .
This method automatically swaps selectionStart and
selectionEnd if they are in a wrong order.
|
deselect() | Programmatically deselect all the selected text inside the text editor. |
getSelectionStart() | Return the start character position of the current selection. |
getSelectionEnd() | Return the end character position of the current selection. |
getSelectionRange() | Return an object of the form { start: Number, end: Number } containing the
start and end position of the current selection. Note that the start and end positions are returned
normalized. This means that the start index will always be lower than the end index even though
the user started selecting the text from the end back to the start.
|
getSelectionLength() | Return the number of characters in the current selection. |
getSelection() | Return the selected text. |
findAnnotationsUnderCursor(annotations, selectionStart) | Find all the annotations in the `annotations` array that the cursor at `selectionStart` position falls into. |
findAnnotationsInSelection(annotations, selectionStart, selectionEnd) | Find all the annotations that fall into the selection range specified by `selectionStart` and `selectionEnd`. This method assumes the selection range is normalized. |
setCaret(charNum [, opt]) | Programmatically set the caret position. If opt.silent is true ,
the text editor will not trigger the 'caret:change' event.
|
hideCaret() | Programmatically hide the caret. |
getTextContent() | Return the text content (including new line characters) inside the text editor. |
getWordBoundary(charNum) | Return the start and end character positions for a word
under charNum character position.
|
getURLBoundary(charNum) | Return the start and end character positions for a URL
under charNum character position. Return
undefined if there was no URL recognized at the charNum index.
|
getNumberOfChars() | Return the number of characters in the text. |
getCharNumFromEvent(evt) | Return the character position the user clicked on. If there is no such a position found, return the last one. |
setCurrentAnnotation(attrs) |
This method stores annotation attributes that will be used for the very next insert operation.
This is useful, for example, when we have a toolbar and the user changes text to e.g. bold.
At this point, we can just call setCurrentAnnotation({ 'font-weight': 'bold' }) and let the
text editor know that once the user starts typing, the text should be bold.
Note that the current annotation will be removed right after the first text operation came.
This is because after that, the next inserted character will already inherit properties
from the previous character which is our 'bold' text.
(Rich-text specific.)
|
setAnnotations(annotations) | Set annotations of the text inside the text editor. These annotations will be modified during the course of using the text editor. (Rich-text specific.) |
getAnnotations() | Return the annotations of the text inside the text editor. (Rich-text specific.) |
getCombinedAnnotationAttrsAtIndex(index, annotations) |
Get the combined (merged) attributes for a character at the position index
taking into account all the annotations that apply.
(Rich-text specific.)
|
getSelectionAttrs(range, annotations) |
Find a common annotation among all the annotations that fall into the
range (an object with start and end properties - normalized).
For characters that don't fall into any of the annotations , assume
defaultAnnotation (default annotation does not need start and end properties).
The common annotation denotes the attributes that all the characters in the range share.
If any of the attributes for any character inside range differ, undefined is returned.
This is useful e.g. when your toolbar needs to reflect the text attributes of a selection.
(Rich-text specific.)
|
joint.ui.TextEditor.getTextElement(el) | Return the containing SVG <text> element closest to the SVG element el .
This is especially handy in event handlers where you can just pass evt.target
element to this function and it will return the element that you can directly use
as a parameter to the ui.TextElement constructor function. If no such
element is exists, the function returns undefined .
|
joint.ui.TextEditor.edit(el, opt) | See the Configuration section for details. |
joint.ui.TextEditor.close() | Close the currently opened text editor (if there was one). Note that before actually
removing the editor (and if the annotateUrls option is set to true ), the close() method tries to find if there was a URL hyperlink
at the current cursor position and annotates it. The reason is that normally,
URL hyperlinks are detected after the user types a non-visible character such as SPACE or new line.
In this case though, the user closed the editor by e.g. clicking outside the element/link
and so if there was a URL hyperlink at the end of the text content, it would not have been annotated.
|
joint.ui.TextEditor.applyAnnotation(annotations) | Apply annotations to the current selection. This is useful if the user
has something selected in the text editor and then changes e.g. the font size via a toolbar.
In this case, you construct the annotations array from the toolbar changes and apply it
using this function on the current selection.
|
joint.ui.TextEditor.setCurrentAnnotation(attributes) | See the instance setCurrentAnnotation() method above.
|
joint.ui.TextEditor.getAnnotations() | See the instance getAnnotations() method above.
|
joint.ui.TextEditor.setCaret(charNum, opt) | See the instance setCaret() method above.
|
joint.ui.TextEditor.deselect() | See the instance deselect() method above.
|
joint.ui.TextEditor.selectAll() | See the instance selectAll() method above.
|
joint.ui.TextEditor.select(start, end) | See the instance select() method above.
|
joint.ui.TextEditor.getNumberOfChars() | See the instance getNumberOfChars() method above.
|
joint.ui.TextEditor.getCharNumFromEvent(evt) | See the instance getCharNumFromEvent() method above.
|
joint.ui.TextEditor.getWordBoundary() | See the instance getWordBoundary() method above.
|
joint.ui.TextEditor.findAnnotationsUnderCursor() | See the instance findAnnotationsUnderCursor() method above except the
annotations and index arguments are automatically added so this
method does not accept any arguments.
|
joint.ui.TextEditor.findAnnotationsInSelection() | See the instance findAnnotationsInSelection() method above except
the annotations and start and end index of the selection are automatically
added so this method does not accept any arguments.
|
joint.ui.TextEditor.getSelectionAttrs(annotations) | See the instance getSelectionAttrs() method above except the
range is automatically added.
|
joint.ui.TextEditor.getSelectionLength() | See the instance getSelectionLength() method above.
|
joint.ui.TextEditor.getSelectionRange() | See the instance getSelectionRange() method above.
|
The following table lists events that are triggered on the ui.TextEditor
instance but also on the joint.ui.TextEditor
class (which is useful if you
use the recommended way of creating a text editor via the joint.ui.TextEditor.edit()
function):
text:change | Triggered when the text inside the text editor changes. This is the most important event
and the one you want to always react on. The callback has the following signature:
function(newText) and is therefore called with the new text as
a parameter. You are assumed to set the new text to the property on your
JointJS element/link that is used for storing the associated text.
|
---|---|
select:change | Triggered when the text inside the text editor is being selected (or if the user
double-clicked to select a word or triple-clicked to select the entire text). The
callback has the following signature: function(selectionStart, selectionEnd) .
For example, you can see the selected text like this:
|
select:changed | Triggered when the user is finished selecting text inside the text editor. The
callback has the following signature: function(selectionStart, selectionEnd) .
For example, you can see the selected text like this:
|
caret:change | Triggered when the caret has changed position. The position is passed to the callback as the only argument. |
caret:out-of-range | Triggered when the caret is outside the visible text area. This is a very special
event that you don't usually deal with. The only
situation this event can occur is when ui.TextEditor is used on a text
rendered along a path (see Vectorizer#text(str, { textPath: '' })) ).
In this case, if the user moves his cursor outside the visible
text area, caret:out-of-range event is triggered so that the programmer
has chance to react (if he wants to because these situations
are handled seamlessly in ui.TextEditor by hiding the caret).
|