/*! * 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.0 * @date 2021-02-09T20:03:49Z */ (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, 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 = $("", { 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 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.0", // 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