ExtEdit - mar10/fancytree GitHub Wiki
About Fancytree edit extension.
- Status: production
- example
Allow to change node titles or create new nodes using inline editing.
For ext-table users, the recommended way to edit columns other than title is by using input elements to hold the column values. See ext-table+more for an example.
-
adjustWidthOfs, type: {int}, optional, default: 4
When switching to edit mode, the title is replaced with an input control. The input width is calculated from the current title text width in pixels plus this increment.
Ifnull
, don't adjust input size to content. -
inputCss, type: {object}, optional, default: { minWidth: "3em" }
Additional CSS style for the new input control. -
triggerStart, type: {string[]}, optional,
default: ["f2", "mac+enter", "shift+click"]
Possible trigger values:
- "clickActive": Click again into an already active node title.
- "dblclick": Double-click a node.
- "f2": Press F2.
- "mac+enter": Press Enter (only on macOS).
- "shift+click": Click a node while holding Shift.
- "clickActive": Click again into an already active node title.
-
beforeEdit
Fired before edit mode starts. Returnfalse
to prevent edit mode. -
edit
Fired when the editor is opened. The text input is available as a jQuery element indata.input
. -
beforeClose
Fired before closing the editor. Returnfalse
to prevent cancel/save.data.input
is available. -
save
Fired when the "beforeClose" event succeeds. Savedata.input.val()
or returnfalse
to keep the editor open. -
close
Fired after editor was removed.
-
{void} node.editStart()
Start inline editing of current node title. -
{void} node.editEnd(applyChanges)
Stop inline editing.
{boolean} [applyChanges=false] -
{void} node.editCreateNode(mode, init)
Create a new child or sibling node and start edit mode.
{string} [mode='child'] 'before', 'after', or 'child'
{object} [init] NodeData (or simple title string)
In addition to jQuery, jQuery UI, and Fancytree, include jquery.fancytree.edit.js
:
<script src="//code.jquery.com/jquery-3.6.0.min.js"></script>
<link href="skin-win8/ui.fancytree.css" rel="stylesheet">
<script src="js/jquery-ui-dependencies/jquery.fancytree.ui-deps.js"></script>
<script src="js/jquery.fancytree.js"></script>
<script src="js/jquery.fancytree.edit.js"></script>
Enable edit
extension and pass options:
$("#tree").fancytree({
extensions: ["edit"],
edit: {
// Available options with their default:
adjustWidthOfs: 4, // null: don't adjust input size to content
inputCss: { minWidth: "3em" },
triggerStart: ["clickActive", "f2", "dblclick", "shift+click", "mac+enter"],
beforeEdit: $.noop, // Return false to prevent edit mode
edit: $.noop, // Editor was opened (available as data.input)
beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
save: $.noop, // Save data.input.val() or return false to keep editor open
close: $.noop, // Editor was removed
},
[...]
});
For example:
$("#tree").fancytree({
extensions: ["edit"],
...
edit: {
beforeEdit: function(event, data){
// `data.node` is about to be edited.
// Return false to prevent this.
},
edit: function(event, data){
// `data.node` switched into edit mode.
// The <input> element was created (available as jQuery object `data.input`)
// and contains the original `data.node.title`.
},
beforeClose: function(event, data){
// Editing is about to end (either cancel or save).
// Additional information is available:
// - `data.dirty`: true if text was modified by user.
// - `data.input`: The input element (jQuery object).
// `data.input.val()` returns the new node title.
// - `data.isNew`: true if this node was newly created using `editCreateNode()`.
// - `data.orgTitle`: The previous node title text.
// - `data.originalEvent`:
// Contains the originating event (i.e. blur, mousdown, keydown).
// - `data.save`: false if saving is not required, i.e. user pressed
// cancel or text is unchanged.
// This value may be changed to override default behavior.
// Return false to prevent this (keep the editor open), for example when
// validations fail.
},
save: function(event, data){
// Only called when the text was modified and the user pressed enter or
// the <input> lost focus.
// Additional information is available (see `beforeClose`).
// Return false to keep editor open, for example when validations fail.
// Otherwise the user input is accepted as `node.title` and the <input>
// is removed.
// Typically we would also issue an Ajax request here to send the new data
// to the server (and handle potential errors when the asynchronous request
// returns).
},
close: function(event, data){
// Editor was removed.
// Additional information is available (see `beforeClose`).
}
}
});
New nodes can be created in edit mode like this. Hitting Esc would cancel and Enter will accept:
node.editCreateNode("after", {
title: "Node title",
folder: true
});
By default, editing ends, when we get ESC, ENTER,
blur, or outer mousedown events.
ESC will discard.
Apps may want to stop editing depending on the current input, mouse button, or
even mouse pointer position. Also the decision whether to save or discard may
depend on various conditions.
We can evaluate the source event in the beforeClose
callback to override the
default behavior, for example:
$("#tree").fancytree({
extensions: ["edit"],
...
edit: {
beforeClose: function(event, data) {
if( data.originalEvent.type === "mousedown" ) {
// We could prevent the mouse click from generating a blur event
// (which would then again close the editor) and return `false` to keep
// the editor open:
// data.originalEvent.preventDefault();
// return false;
// Or go on with closing the editor, but discard any changes:
data.save = false;
}
},
...
},
...
});
Here is an example for a robust handling of node renaming with user feedback.
$("#tree").fancytree({
extensions: ["edit"],
...
edit: {
save: function(event, data){
var node = data.node;
// Save data.input.val() or return false to keep the editor open
$.ajax({
url: "saveNode",
data: { key: node.key, title: data.input.val() }
}).done(function(result){
// Server might return an error or a modified title
node.setTitle(result.acceptedTitle); // in case server modified it
// Maybe also check for non-ajax errors, e.g. 'title invalid', ...
}).fail(function(result){
// Ajax error: reset title (and maybe issue a warning)
node.setTitle(data.orgTitle);
}).always(function(){
data.input.removeClass("pending");
});
// Optimistically assume that save will succeed. Accept the user input
return true;
},
close: function(event, data){
// Editor was removed. If we started an async request, mark the node as pending
if( data.save ) {
$(data.node.span).addClass("pending");
}
},
},
...
});
We also might add some CSS for a nice 'pending' style:
span.pending span.fancytree-title {
font-style: italic;
}
span.pending span.fancytree-title:after {
content: "\2026"; /* ellipsis */
}
This approach pessimistically keeps the edit control open until the server responds 'ok'.
$("#tree").fancytree({
extensions: ["edit"],
...
edit: {
...
save: function(event, data){
var node = data.node,
newTitle = data.input.val(),
originalTitle = data.orgTitle;
// Save data.input.val() asynchr. but also return false to keep the editor open
// (optionally disable the input control)
$.ajax({
<your_ajax_call_to_validate_and_update_title_in_server|db)>
}).done(function(result){
// Server might return an application error
if (result.retcode>0) { // validation error
// Do something related to application error here
// (optionally enable the input control again)
} else {
// server returned OK
node.setTitle('<server_updated_value>');
// Important: exit sucessfull editing
node.editEnd(false);
}
}).fail(function(result){
// maybe update title with original value
node.setTitle(originalTitle);
// (optionally enable the input control again)
}).always(function(){
$(data.node.span).removeClass("pending");
});
// Always return false except when receives server validation successfully
return false;
},
},
});
Fancytree uses the standard jQuery UI draggable which prevents mouse clicks from setting the focus (details).
In combination with the ext-edit extension, this can prevent that keyboard input
(such as [F2]) is delivered to the tree.
Use the dnd.focusOnClick: true
option in this case.
$("#tree").fancytree({
extensions: ["dnd", "edit"],
dnd: {
focusOnClick: true, // Focus, although draggable cancels mousedown event
...
},
edit: {
...
},
[...]
});