You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

404 lines
11 KiB

  1. /*!
  2. * jquery.fancytree.edit.js
  3. *
  4. * Make node titles editable.
  5. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  6. *
  7. * Copyright (c) 2008-2023, Martin Wendt (https://wwWendt.de)
  8. *
  9. * Released under the MIT license
  10. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  11. *
  12. * @version 2.38.3
  13. * @date 2023-02-01T20:52:50Z
  14. */
  15. (function (factory) {
  16. if (typeof define === "function" && define.amd) {
  17. // AMD. Register as an anonymous module.
  18. define(["jquery", "./jquery.fancytree"], factory);
  19. } else if (typeof module === "object" && module.exports) {
  20. // Node/CommonJS
  21. require("./jquery.fancytree");
  22. module.exports = factory(require("jquery"));
  23. } else {
  24. // Browser globals
  25. factory(jQuery);
  26. }
  27. })(function ($) {
  28. "use strict";
  29. /*******************************************************************************
  30. * Private functions and variables
  31. */
  32. var isMac = /Mac/.test(navigator.platform),
  33. escapeHtml = $.ui.fancytree.escapeHtml,
  34. trim = $.ui.fancytree.trim,
  35. unescapeHtml = $.ui.fancytree.unescapeHtml;
  36. /**
  37. * [ext-edit] Start inline editing of current node title.
  38. *
  39. * @alias FancytreeNode#editStart
  40. * @requires Fancytree
  41. */
  42. $.ui.fancytree._FancytreeNodeClass.prototype.editStart = function () {
  43. var $input,
  44. node = this,
  45. tree = this.tree,
  46. local = tree.ext.edit,
  47. instOpts = tree.options.edit,
  48. $title = $(".fancytree-title", node.span),
  49. eventData = {
  50. node: node,
  51. tree: tree,
  52. options: tree.options,
  53. isNew: $(node[tree.statusClassPropName]).hasClass(
  54. "fancytree-edit-new"
  55. ),
  56. orgTitle: node.title,
  57. input: null,
  58. dirty: false,
  59. };
  60. // beforeEdit may want to modify the title before editing
  61. if (
  62. instOpts.beforeEdit.call(
  63. node,
  64. { type: "beforeEdit" },
  65. eventData
  66. ) === false
  67. ) {
  68. return false;
  69. }
  70. $.ui.fancytree.assert(!local.currentNode, "recursive edit");
  71. local.currentNode = this;
  72. local.eventData = eventData;
  73. // Disable standard Fancytree mouse- and key handling
  74. tree.widget._unbind();
  75. local.lastDraggableAttrValue = node.span.draggable;
  76. if (local.lastDraggableAttrValue) {
  77. node.span.draggable = false;
  78. }
  79. // #116: ext-dnd prevents the blur event, so we have to catch outer clicks
  80. $(document).on("mousedown.fancytree-edit", function (event) {
  81. if (!$(event.target).hasClass("fancytree-edit-input")) {
  82. node.editEnd(true, event);
  83. }
  84. });
  85. // Replace node with <input>
  86. $input = $("<input />", {
  87. class: "fancytree-edit-input",
  88. type: "text",
  89. value: tree.options.escapeTitles
  90. ? eventData.orgTitle
  91. : unescapeHtml(eventData.orgTitle),
  92. });
  93. local.eventData.input = $input;
  94. if (instOpts.adjustWidthOfs != null) {
  95. $input.width($title.width() + instOpts.adjustWidthOfs);
  96. }
  97. if (instOpts.inputCss != null) {
  98. $input.css(instOpts.inputCss);
  99. }
  100. $title.html($input);
  101. // Focus <input> and bind keyboard handler
  102. $input
  103. .focus()
  104. .change(function (event) {
  105. $input.addClass("fancytree-edit-dirty");
  106. })
  107. .on("keydown", function (event) {
  108. switch (event.which) {
  109. case $.ui.keyCode.ESCAPE:
  110. node.editEnd(false, event);
  111. break;
  112. case $.ui.keyCode.ENTER:
  113. node.editEnd(true, event);
  114. return false; // so we don't start editmode on Mac
  115. }
  116. event.stopPropagation();
  117. })
  118. .blur(function (event) {
  119. return node.editEnd(true, event);
  120. });
  121. instOpts.edit.call(node, { type: "edit" }, eventData);
  122. };
  123. /**
  124. * [ext-edit] Stop inline editing.
  125. * @param {Boolean} [applyChanges=false] false: cancel edit, true: save (if modified)
  126. * @alias FancytreeNode#editEnd
  127. * @requires jquery.fancytree.edit.js
  128. */
  129. $.ui.fancytree._FancytreeNodeClass.prototype.editEnd = function (
  130. applyChanges,
  131. _event
  132. ) {
  133. var newVal,
  134. node = this,
  135. tree = this.tree,
  136. local = tree.ext.edit,
  137. eventData = local.eventData,
  138. instOpts = tree.options.edit,
  139. $title = $(".fancytree-title", node.span),
  140. $input = $title.find("input.fancytree-edit-input");
  141. if (instOpts.trim) {
  142. $input.val(trim($input.val()));
  143. }
  144. newVal = $input.val();
  145. eventData.dirty = newVal !== node.title;
  146. eventData.originalEvent = _event;
  147. // Find out, if saving is required
  148. if (applyChanges === false) {
  149. // If true/false was passed, honor this (except in rename mode, if unchanged)
  150. eventData.save = false;
  151. } else if (eventData.isNew) {
  152. // In create mode, we save everything, except for empty text
  153. eventData.save = newVal !== "";
  154. } else {
  155. // In rename mode, we save everyting, except for empty or unchanged text
  156. eventData.save = eventData.dirty && newVal !== "";
  157. }
  158. // Allow to break (keep editor open), modify input, or re-define data.save
  159. if (
  160. instOpts.beforeClose.call(
  161. node,
  162. { type: "beforeClose" },
  163. eventData
  164. ) === false
  165. ) {
  166. return false;
  167. }
  168. if (
  169. eventData.save &&
  170. instOpts.save.call(node, { type: "save" }, eventData) === false
  171. ) {
  172. return false;
  173. }
  174. $input.removeClass("fancytree-edit-dirty").off();
  175. // Unbind outer-click handler
  176. $(document).off(".fancytree-edit");
  177. if (eventData.save) {
  178. // # 171: escape user input (not required if global escaping is on)
  179. node.setTitle(
  180. tree.options.escapeTitles ? newVal : escapeHtml(newVal)
  181. );
  182. node.setFocus();
  183. } else {
  184. if (eventData.isNew) {
  185. node.remove();
  186. node = eventData.node = null;
  187. local.relatedNode.setFocus();
  188. } else {
  189. node.renderTitle();
  190. node.setFocus();
  191. }
  192. }
  193. local.eventData = null;
  194. local.currentNode = null;
  195. local.relatedNode = null;
  196. // Re-enable mouse and keyboard handling
  197. tree.widget._bind();
  198. if (node && local.lastDraggableAttrValue) {
  199. node.span.draggable = true;
  200. }
  201. // Set keyboard focus, even if setFocus() claims 'nothing to do'
  202. tree.$container.get(0).focus({ preventScroll: true });
  203. eventData.input = null;
  204. instOpts.close.call(node, { type: "close" }, eventData);
  205. return true;
  206. };
  207. /**
  208. * [ext-edit] Create a new child or sibling node and start edit mode.
  209. *
  210. * @param {String} [mode='child'] 'before', 'after', or 'child'
  211. * @param {Object} [init] NodeData (or simple title string)
  212. * @alias FancytreeNode#editCreateNode
  213. * @requires jquery.fancytree.edit.js
  214. * @since 2.4
  215. */
  216. $.ui.fancytree._FancytreeNodeClass.prototype.editCreateNode = function (
  217. mode,
  218. init
  219. ) {
  220. var newNode,
  221. tree = this.tree,
  222. self = this;
  223. mode = mode || "child";
  224. if (init == null) {
  225. init = { title: "" };
  226. } else if (typeof init === "string") {
  227. init = { title: init };
  228. } else {
  229. $.ui.fancytree.assert($.isPlainObject(init));
  230. }
  231. // Make sure node is expanded (and loaded) in 'child' mode
  232. if (
  233. mode === "child" &&
  234. !this.isExpanded() &&
  235. this.hasChildren() !== false
  236. ) {
  237. this.setExpanded().done(function () {
  238. self.editCreateNode(mode, init);
  239. });
  240. return;
  241. }
  242. newNode = this.addNode(init, mode);
  243. // #644: Don't filter new nodes.
  244. newNode.match = true;
  245. $(newNode[tree.statusClassPropName])
  246. .removeClass("fancytree-hide")
  247. .addClass("fancytree-match");
  248. newNode.makeVisible(/*{noAnimation: true}*/).done(function () {
  249. $(newNode[tree.statusClassPropName]).addClass("fancytree-edit-new");
  250. self.tree.ext.edit.relatedNode = self;
  251. newNode.editStart();
  252. });
  253. };
  254. /**
  255. * [ext-edit] Check if any node in this tree in edit mode.
  256. *
  257. * @returns {FancytreeNode | null}
  258. * @alias Fancytree#isEditing
  259. * @requires jquery.fancytree.edit.js
  260. */
  261. $.ui.fancytree._FancytreeClass.prototype.isEditing = function () {
  262. return this.ext.edit ? this.ext.edit.currentNode : null;
  263. };
  264. /**
  265. * [ext-edit] Check if this node is in edit mode.
  266. * @returns {Boolean} true if node is currently beeing edited
  267. * @alias FancytreeNode#isEditing
  268. * @requires jquery.fancytree.edit.js
  269. */
  270. $.ui.fancytree._FancytreeNodeClass.prototype.isEditing = function () {
  271. return this.tree.ext.edit
  272. ? this.tree.ext.edit.currentNode === this
  273. : false;
  274. };
  275. /*******************************************************************************
  276. * Extension code
  277. */
  278. $.ui.fancytree.registerExtension({
  279. name: "edit",
  280. version: "2.38.3",
  281. // Default options for this extension.
  282. options: {
  283. adjustWidthOfs: 4, // null: don't adjust input size to content
  284. allowEmpty: false, // Prevent empty input
  285. inputCss: { minWidth: "3em" },
  286. // triggerCancel: ["esc", "tab", "click"],
  287. triggerStart: ["f2", "mac+enter", "shift+click"],
  288. trim: true, // Trim whitespace before save
  289. // Events:
  290. beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
  291. beforeEdit: $.noop, // Return false to prevent edit mode
  292. close: $.noop, // Editor was removed
  293. edit: $.noop, // Editor was opened (available as data.input)
  294. // keypress: $.noop, // Not yet implemented
  295. save: $.noop, // Save data.input.val() or return false to keep editor open
  296. },
  297. // Local attributes
  298. currentNode: null,
  299. treeInit: function (ctx) {
  300. var tree = ctx.tree;
  301. this._superApply(arguments);
  302. this.$container
  303. .addClass("fancytree-ext-edit")
  304. .on("fancytreebeforeupdateviewport", function (event, data) {
  305. var editNode = tree.isEditing();
  306. // When scrolling, the TR may be re-used by another node, so the
  307. // active cell marker an
  308. if (editNode) {
  309. editNode.info("Cancel edit due to scroll event.");
  310. editNode.editEnd(false, event);
  311. }
  312. });
  313. },
  314. nodeClick: function (ctx) {
  315. var eventStr = $.ui.fancytree.eventToString(ctx.originalEvent),
  316. triggerStart = ctx.options.edit.triggerStart;
  317. if (
  318. eventStr === "shift+click" &&
  319. $.inArray("shift+click", triggerStart) >= 0
  320. ) {
  321. if (ctx.originalEvent.shiftKey) {
  322. ctx.node.editStart();
  323. return false;
  324. }
  325. }
  326. if (
  327. eventStr === "click" &&
  328. $.inArray("clickActive", triggerStart) >= 0
  329. ) {
  330. // Only when click was inside title text (not aynwhere else in the row)
  331. if (
  332. ctx.node.isActive() &&
  333. !ctx.node.isEditing() &&
  334. $(ctx.originalEvent.target).hasClass("fancytree-title")
  335. ) {
  336. ctx.node.editStart();
  337. return false;
  338. }
  339. }
  340. return this._superApply(arguments);
  341. },
  342. nodeDblclick: function (ctx) {
  343. if ($.inArray("dblclick", ctx.options.edit.triggerStart) >= 0) {
  344. ctx.node.editStart();
  345. return false;
  346. }
  347. return this._superApply(arguments);
  348. },
  349. nodeKeydown: function (ctx) {
  350. switch (ctx.originalEvent.which) {
  351. case 113: // [F2]
  352. if ($.inArray("f2", ctx.options.edit.triggerStart) >= 0) {
  353. ctx.node.editStart();
  354. return false;
  355. }
  356. break;
  357. case $.ui.keyCode.ENTER:
  358. if (
  359. $.inArray("mac+enter", ctx.options.edit.triggerStart) >=
  360. 0 &&
  361. isMac
  362. ) {
  363. ctx.node.editStart();
  364. return false;
  365. }
  366. break;
  367. }
  368. return this._superApply(arguments);
  369. },
  370. });
  371. // Value returned by `require('jquery.fancytree..')`
  372. return $.ui.fancytree;
  373. }); // End of closure