404 lines
11 KiB
JavaScript
404 lines
11 KiB
JavaScript
|
/*!
|
||
|
* jquery.fancytree.edit.js
|
||
|
*
|
||
|
* Make node titles editable.
|
||
|
* (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
|
||
|
*
|
||
|
* Copyright (c) 2008-2021, Martin Wendt (https://wwWendt.de)
|
||
|
*
|
||
|
* Released under the MIT license
|
||
|
* https://github.com/mar10/fancytree/wiki/LicenseInfo
|
||
|
*
|
||
|
* @version 2.38.2
|
||
|
* @date 2022-06-30T18:24:06Z
|
||
|
*/
|
||
|
|
||
|
(function (factory) {
|
||
|
if (typeof define === "function" && define.amd) {
|
||
|
// AMD. Register as an anonymous module.
|
||
|
define(["jquery", "./jquery.fancytree"], factory);
|
||
|
} else if (typeof module === "object" && module.exports) {
|
||
|
// Node/CommonJS
|
||
|
require("./jquery.fancytree");
|
||
|
module.exports = factory(require("jquery"));
|
||
|
} else {
|
||
|
// Browser globals
|
||
|
factory(jQuery);
|
||
|
}
|
||
|
})(function ($) {
|
||
|
"use strict";
|
||
|
|
||
|
/*******************************************************************************
|
||
|
* Private functions and variables
|
||
|
*/
|
||
|
|
||
|
var isMac = /Mac/.test(navigator.platform),
|
||
|
escapeHtml = $.ui.fancytree.escapeHtml,
|
||
|
trim = $.ui.fancytree.trim,
|
||
|
unescapeHtml = $.ui.fancytree.unescapeHtml;
|
||
|
|
||
|
/**
|
||
|
* [ext-edit] Start inline editing of current node title.
|
||
|
*
|
||
|
* @alias FancytreeNode#editStart
|
||
|
* @requires Fancytree
|
||
|
*/
|
||
|
$.ui.fancytree._FancytreeNodeClass.prototype.editStart = function () {
|
||
|
var $input,
|
||
|
node = this,
|
||
|
tree = this.tree,
|
||
|
local = tree.ext.edit,
|
||
|
instOpts = tree.options.edit,
|
||
|
$title = $(".fancytree-title", node.span),
|
||
|
eventData = {
|
||
|
node: node,
|
||
|
tree: tree,
|
||
|
options: tree.options,
|
||
|
isNew: $(node[tree.statusClassPropName]).hasClass(
|
||
|
"fancytree-edit-new"
|
||
|
),
|
||
|
orgTitle: node.title,
|
||
|
input: null,
|
||
|
dirty: false,
|
||
|
};
|
||
|
|
||
|
// beforeEdit may want to modify the title before editing
|
||
|
if (
|
||
|
instOpts.beforeEdit.call(
|
||
|
node,
|
||
|
{ type: "beforeEdit" },
|
||
|
eventData
|
||
|
) === false
|
||
|
) {
|
||
|
return false;
|
||
|
}
|
||
|
$.ui.fancytree.assert(!local.currentNode, "recursive edit");
|
||
|
local.currentNode = this;
|
||
|
local.eventData = eventData;
|
||
|
|
||
|
// Disable standard Fancytree mouse- and key handling
|
||
|
tree.widget._unbind();
|
||
|
|
||
|
local.lastDraggableAttrValue = node.span.draggable;
|
||
|
if (local.lastDraggableAttrValue) {
|
||
|
node.span.draggable = false;
|
||
|
}
|
||
|
|
||
|
// #116: ext-dnd prevents the blur event, so we have to catch outer clicks
|
||
|
$(document).on("mousedown.fancytree-edit", function (event) {
|
||
|
if (!$(event.target).hasClass("fancytree-edit-input")) {
|
||
|
node.editEnd(true, event);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Replace node with <input>
|
||
|
$input = $("<input />", {
|
||
|
class: "fancytree-edit-input",
|
||
|
type: "text",
|
||
|
value: tree.options.escapeTitles
|
||
|
? eventData.orgTitle
|
||
|
: unescapeHtml(eventData.orgTitle),
|
||
|
});
|
||
|
local.eventData.input = $input;
|
||
|
if (instOpts.adjustWidthOfs != null) {
|
||
|
$input.width($title.width() + instOpts.adjustWidthOfs);
|
||
|
}
|
||
|
if (instOpts.inputCss != null) {
|
||
|
$input.css(instOpts.inputCss);
|
||
|
}
|
||
|
|
||
|
$title.html($input);
|
||
|
|
||
|
// Focus <input> and bind keyboard handler
|
||
|
$input
|
||
|
.focus()
|
||
|
.change(function (event) {
|
||
|
$input.addClass("fancytree-edit-dirty");
|
||
|
})
|
||
|
.on("keydown", function (event) {
|
||
|
switch (event.which) {
|
||
|
case $.ui.keyCode.ESCAPE:
|
||
|
node.editEnd(false, event);
|
||
|
break;
|
||
|
case $.ui.keyCode.ENTER:
|
||
|
node.editEnd(true, event);
|
||
|
return false; // so we don't start editmode on Mac
|
||
|
}
|
||
|
event.stopPropagation();
|
||
|
})
|
||
|
.blur(function (event) {
|
||
|
return node.editEnd(true, event);
|
||
|
});
|
||
|
|
||
|
instOpts.edit.call(node, { type: "edit" }, eventData);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* [ext-edit] Stop inline editing.
|
||
|
* @param {Boolean} [applyChanges=false] false: cancel edit, true: save (if modified)
|
||
|
* @alias FancytreeNode#editEnd
|
||
|
* @requires jquery.fancytree.edit.js
|
||
|
*/
|
||
|
$.ui.fancytree._FancytreeNodeClass.prototype.editEnd = function (
|
||
|
applyChanges,
|
||
|
_event
|
||
|
) {
|
||
|
var newVal,
|
||
|
node = this,
|
||
|
tree = this.tree,
|
||
|
local = tree.ext.edit,
|
||
|
eventData = local.eventData,
|
||
|
instOpts = tree.options.edit,
|
||
|
$title = $(".fancytree-title", node.span),
|
||
|
$input = $title.find("input.fancytree-edit-input");
|
||
|
|
||
|
if (instOpts.trim) {
|
||
|
$input.val(trim($input.val()));
|
||
|
}
|
||
|
newVal = $input.val();
|
||
|
|
||
|
eventData.dirty = newVal !== node.title;
|
||
|
eventData.originalEvent = _event;
|
||
|
|
||
|
// Find out, if saving is required
|
||
|
if (applyChanges === false) {
|
||
|
// If true/false was passed, honor this (except in rename mode, if unchanged)
|
||
|
eventData.save = false;
|
||
|
} else if (eventData.isNew) {
|
||
|
// In create mode, we save everything, except for empty text
|
||
|
eventData.save = newVal !== "";
|
||
|
} else {
|
||
|
// In rename mode, we save everyting, except for empty or unchanged text
|
||
|
eventData.save = eventData.dirty && newVal !== "";
|
||
|
}
|
||
|
// Allow to break (keep editor open), modify input, or re-define data.save
|
||
|
if (
|
||
|
instOpts.beforeClose.call(
|
||
|
node,
|
||
|
{ type: "beforeClose" },
|
||
|
eventData
|
||
|
) === false
|
||
|
) {
|
||
|
return false;
|
||
|
}
|
||
|
if (
|
||
|
eventData.save &&
|
||
|
instOpts.save.call(node, { type: "save" }, eventData) === false
|
||
|
) {
|
||
|
return false;
|
||
|
}
|
||
|
$input.removeClass("fancytree-edit-dirty").off();
|
||
|
// Unbind outer-click handler
|
||
|
$(document).off(".fancytree-edit");
|
||
|
|
||
|
if (eventData.save) {
|
||
|
// # 171: escape user input (not required if global escaping is on)
|
||
|
node.setTitle(
|
||
|
tree.options.escapeTitles ? newVal : escapeHtml(newVal)
|
||
|
);
|
||
|
node.setFocus();
|
||
|
} else {
|
||
|
if (eventData.isNew) {
|
||
|
node.remove();
|
||
|
node = eventData.node = null;
|
||
|
local.relatedNode.setFocus();
|
||
|
} else {
|
||
|
node.renderTitle();
|
||
|
node.setFocus();
|
||
|
}
|
||
|
}
|
||
|
local.eventData = null;
|
||
|
local.currentNode = null;
|
||
|
local.relatedNode = null;
|
||
|
// Re-enable mouse and keyboard handling
|
||
|
tree.widget._bind();
|
||
|
|
||
|
if (node && local.lastDraggableAttrValue) {
|
||
|
node.span.draggable = true;
|
||
|
}
|
||
|
|
||
|
// Set keyboard focus, even if setFocus() claims 'nothing to do'
|
||
|
tree.$container.get(0).focus({ preventScroll: true });
|
||
|
eventData.input = null;
|
||
|
instOpts.close.call(node, { type: "close" }, eventData);
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* [ext-edit] Create a new child or sibling node and start edit mode.
|
||
|
*
|
||
|
* @param {String} [mode='child'] 'before', 'after', or 'child'
|
||
|
* @param {Object} [init] NodeData (or simple title string)
|
||
|
* @alias FancytreeNode#editCreateNode
|
||
|
* @requires jquery.fancytree.edit.js
|
||
|
* @since 2.4
|
||
|
*/
|
||
|
$.ui.fancytree._FancytreeNodeClass.prototype.editCreateNode = function (
|
||
|
mode,
|
||
|
init
|
||
|
) {
|
||
|
var newNode,
|
||
|
tree = this.tree,
|
||
|
self = this;
|
||
|
|
||
|
mode = mode || "child";
|
||
|
if (init == null) {
|
||
|
init = { title: "" };
|
||
|
} else if (typeof init === "string") {
|
||
|
init = { title: init };
|
||
|
} else {
|
||
|
$.ui.fancytree.assert($.isPlainObject(init));
|
||
|
}
|
||
|
// Make sure node is expanded (and loaded) in 'child' mode
|
||
|
if (
|
||
|
mode === "child" &&
|
||
|
!this.isExpanded() &&
|
||
|
this.hasChildren() !== false
|
||
|
) {
|
||
|
this.setExpanded().done(function () {
|
||
|
self.editCreateNode(mode, init);
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
newNode = this.addNode(init, mode);
|
||
|
|
||
|
// #644: Don't filter new nodes.
|
||
|
newNode.match = true;
|
||
|
$(newNode[tree.statusClassPropName])
|
||
|
.removeClass("fancytree-hide")
|
||
|
.addClass("fancytree-match");
|
||
|
|
||
|
newNode.makeVisible(/*{noAnimation: true}*/).done(function () {
|
||
|
$(newNode[tree.statusClassPropName]).addClass("fancytree-edit-new");
|
||
|
self.tree.ext.edit.relatedNode = self;
|
||
|
newNode.editStart();
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* [ext-edit] Check if any node in this tree in edit mode.
|
||
|
*
|
||
|
* @returns {FancytreeNode | null}
|
||
|
* @alias Fancytree#isEditing
|
||
|
* @requires jquery.fancytree.edit.js
|
||
|
*/
|
||
|
$.ui.fancytree._FancytreeClass.prototype.isEditing = function () {
|
||
|
return this.ext.edit ? this.ext.edit.currentNode : null;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* [ext-edit] Check if this node is in edit mode.
|
||
|
* @returns {Boolean} true if node is currently beeing edited
|
||
|
* @alias FancytreeNode#isEditing
|
||
|
* @requires jquery.fancytree.edit.js
|
||
|
*/
|
||
|
$.ui.fancytree._FancytreeNodeClass.prototype.isEditing = function () {
|
||
|
return this.tree.ext.edit
|
||
|
? this.tree.ext.edit.currentNode === this
|
||
|
: false;
|
||
|
};
|
||
|
|
||
|
/*******************************************************************************
|
||
|
* Extension code
|
||
|
*/
|
||
|
$.ui.fancytree.registerExtension({
|
||
|
name: "edit",
|
||
|
version: "2.38.2",
|
||
|
// Default options for this extension.
|
||
|
options: {
|
||
|
adjustWidthOfs: 4, // null: don't adjust input size to content
|
||
|
allowEmpty: false, // Prevent empty input
|
||
|
inputCss: { minWidth: "3em" },
|
||
|
// triggerCancel: ["esc", "tab", "click"],
|
||
|
triggerStart: ["f2", "mac+enter", "shift+click"],
|
||
|
trim: true, // Trim whitespace before save
|
||
|
// Events:
|
||
|
beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
|
||
|
beforeEdit: $.noop, // Return false to prevent edit mode
|
||
|
close: $.noop, // Editor was removed
|
||
|
edit: $.noop, // Editor was opened (available as data.input)
|
||
|
// keypress: $.noop, // Not yet implemented
|
||
|
save: $.noop, // Save data.input.val() or return false to keep editor open
|
||
|
},
|
||
|
// Local attributes
|
||
|
currentNode: null,
|
||
|
|
||
|
treeInit: function (ctx) {
|
||
|
var tree = ctx.tree;
|
||
|
|
||
|
this._superApply(arguments);
|
||
|
|
||
|
this.$container
|
||
|
.addClass("fancytree-ext-edit")
|
||
|
.on("fancytreebeforeupdateviewport", function (event, data) {
|
||
|
var editNode = tree.isEditing();
|
||
|
// When scrolling, the TR may be re-used by another node, so the
|
||
|
// active cell marker an
|
||
|
if (editNode) {
|
||
|
editNode.info("Cancel edit due to scroll event.");
|
||
|
editNode.editEnd(false, event);
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
nodeClick: function (ctx) {
|
||
|
var eventStr = $.ui.fancytree.eventToString(ctx.originalEvent),
|
||
|
triggerStart = ctx.options.edit.triggerStart;
|
||
|
|
||
|
if (
|
||
|
eventStr === "shift+click" &&
|
||
|
$.inArray("shift+click", triggerStart) >= 0
|
||
|
) {
|
||
|
if (ctx.originalEvent.shiftKey) {
|
||
|
ctx.node.editStart();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
if (
|
||
|
eventStr === "click" &&
|
||
|
$.inArray("clickActive", triggerStart) >= 0
|
||
|
) {
|
||
|
// Only when click was inside title text (not aynwhere else in the row)
|
||
|
if (
|
||
|
ctx.node.isActive() &&
|
||
|
!ctx.node.isEditing() &&
|
||
|
$(ctx.originalEvent.target).hasClass("fancytree-title")
|
||
|
) {
|
||
|
ctx.node.editStart();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return this._superApply(arguments);
|
||
|
},
|
||
|
nodeDblclick: function (ctx) {
|
||
|
if ($.inArray("dblclick", ctx.options.edit.triggerStart) >= 0) {
|
||
|
ctx.node.editStart();
|
||
|
return false;
|
||
|
}
|
||
|
return this._superApply(arguments);
|
||
|
},
|
||
|
nodeKeydown: function (ctx) {
|
||
|
switch (ctx.originalEvent.which) {
|
||
|
case 113: // [F2]
|
||
|
if ($.inArray("f2", ctx.options.edit.triggerStart) >= 0) {
|
||
|
ctx.node.editStart();
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
case $.ui.keyCode.ENTER:
|
||
|
if (
|
||
|
$.inArray("mac+enter", ctx.options.edit.triggerStart) >=
|
||
|
0 &&
|
||
|
isMac
|
||
|
) {
|
||
|
ctx.node.editStart();
|
||
|
return false;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
return this._superApply(arguments);
|
||
|
},
|
||
|
});
|
||
|
// Value returned by `require('jquery.fancytree..')`
|
||
|
return $.ui.fancytree;
|
||
|
}); // End of closure
|