25c8c0bbc500ee81f1ea356720ce660a535b87c4
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / PageTree / PageTreeDragDrop.js
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14 /**
15 * Module: TYPO3/CMS/Backend/PageTree/PageTreeDragDrop
16 *
17 * Provides drag&drop related functionality for the SVG page tree
18 */
19 define([
20 'jquery',
21 'd3',
22 'TYPO3/CMS/Backend/Modal',
23 'TYPO3/CMS/Backend/Severity',
24 ], function ($, d3, Modal, Severity) {
25 'use strict';
26
27 /**
28 * PageTreeDragDrop class
29 *
30 * @constructor
31 * @exports TYPO3/CMS/Backend/PageTree/PageTreeDragDrop
32 */
33 var PageTreeDragDrop;
34 PageTreeDragDrop = {
35
36 /**
37 * SVG <g> container for deleting drop zone
38 *
39 * @type {Selection}
40 */
41 dropZoneDelete: null,
42
43 init: function (svgTree) {
44 this.tree = svgTree;
45 },
46
47 /**
48 * Drag and drop for nodes
49 *
50 * Returns initialized d3.drag() function
51 */
52 drag: function () {
53 var self = {};
54 var _this = this;
55 var tree = _this.tree;
56
57 /**
58 * Returns deleting drop zone open 'transform' attribute value
59 *
60 * @param node
61 * @returns {string}
62 */
63 self.getDropZoneOpenTransform = function (node) {
64 var svgWidth = parseFloat(tree.svg.style('width')) || 300;
65
66 return 'translate(' + (svgWidth - 58 - node.x) + ', -10)';
67 };
68
69 /**
70 * Returns deleting drop zone close 'transform' attribute value
71 *
72 * @param node
73 * @returns {string}
74 */
75 self.getDropZoneCloseTransform = function (node) {
76 var svgWidth = parseFloat(tree.svg.style('width')) || 300;
77
78 return 'translate(' + (svgWidth - node.x) + ', -10)';
79 };
80
81 self.dragStart = function (node) {
82 if (tree.settings.isDragAnDrop !== true || node.depth === 0) {
83 return false;
84 }
85
86 self.isDragging = false;
87
88 _this.dropZoneDelete = null;
89
90 if ((!tree.settings.allowRecursiveDelete && !node.hasChildren) ||
91 tree.settings.allowRecursiveDelete
92 ) {
93 _this.dropZoneDelete = tree.nodesContainer
94 .select('.node[data-uid="' + node.identifier + '"]')
95 .append('g')
96 .attr('class', 'nodes-drop-zone')
97 .attr('height', tree.settings.nodeHeight);
98
99 tree.nodeIsOverDelete = false;
100
101 _this.dropZoneDelete.append('rect')
102 .attr('height', tree.settings.nodeHeight)
103 .attr('width', '50px')
104 .attr('x', 0)
105 .attr('y', 0)
106 .on('mouseover', function () {
107 tree.nodeIsOverDelete = true;
108 })
109 .on('mouseout', function () {
110 tree.nodeIsOverDelete = false;
111 });
112
113 _this.dropZoneDelete.append('text')
114 .text(TYPO3.lang.deleteItem)
115 .attr('dx', 5)
116 .attr('dy', 15);
117
118 _this.dropZoneDelete
119 .attr('transform', self.getDropZoneCloseTransform(node));
120 }
121
122 $.extend(self, _this.setDragStart());
123 };
124
125 self.dragDragged = function (node) {
126 if (_this.isDragNodeDistanceMore(self, 10)) {
127 self.startDrag = true;
128 } else {
129 return false;
130 }
131
132 if (tree.settings.isDragAnDrop !== true || node.depth === 0) {
133 return false;
134 }
135
136 self.isDragging = true;
137 tree.settings.nodeDrag = node;
138
139 var $svg = $(this).closest('svg');
140 var $nodesBg = $svg.find('.nodes-bg');
141 var $nodesWrap = $svg.find('.nodes-wrapper');
142 var $nodeBg = $nodesBg.find('.node-bg[data-uid=' + node.identifier + ']');
143 var $nodeDd = $svg.siblings('.node-dd');
144
145 if ($nodeBg.length && (!node.isDragged)) {
146 tree.settings.dragging = true;
147 node.isDragged = true;
148
149 $svg.after(_this.template(tree.getIconId(node), node.name));
150 $nodeBg.addClass('node-bg--dragging');
151
152 $svg
153 .find('.nodes-wrapper')
154 .addClass('nodes-wrapper--dragging');
155 }
156
157 var left = 18;
158 var top = 15;
159
160 if (d3.event.sourceEvent && d3.event.sourceEvent.pageX) {
161 left += d3.event.sourceEvent.pageX;
162 }
163
164 if (d3.event.sourceEvent && d3.event.sourceEvent.pageY) {
165 top += d3.event.sourceEvent.pageY;
166 }
167
168 $(document).find('.node-dd').css({
169 left: left,
170 top: top,
171 display: 'block',
172 });
173
174 tree.settings.nodeDragPosition = false;
175
176 if (node.isOver
177 || (tree.settings.nodeOver.node && tree.settings.nodeOver.node.parentsUid.indexOf(node.identifier) !== -1)
178 || !tree.isOverSvg) {
179
180 _this.addNodeDdClass({ $nodeDd: $nodeDd, $nodesWrap: $nodesWrap, className: 'nodrop' });
181
182 if (!tree.isOverSvg) {
183 _this.tree.nodesBgContainer
184 .selectAll('.node-bg__border')
185 .style('display', 'none');
186 }
187
188 if (_this.dropZoneDelete && _this.dropZoneDelete.attr('data-open') !== 'true' && tree.isOverSvg) {
189 _this.dropZoneDelete
190 .transition(300)
191 .attr('transform', self.getDropZoneOpenTransform(node))
192 .attr('data-open', 'true');
193 }
194 } else if (!tree.settings.nodeOver.node) {
195 _this.addNodeDdClass({ $nodeDd: $nodeDd, $nodesWrap: $nodesWrap, className: 'nodrop' });
196 _this.tree.nodesBgContainer
197 .selectAll('.node-bg__border')
198 .style('display', 'none');
199 } else {
200 if (_this.dropZoneDelete && _this.dropZoneDelete.attr('data-open') !== 'false') {
201 _this.dropZoneDelete
202 .transition(300)
203 .attr('transform', self.getDropZoneCloseTransform(node))
204 .attr('data-open', 'false');
205 }
206
207 _this.changeNodeClasses();
208 }
209 };
210
211 self.dragEnd = function (node) {
212 _this.setDragEnd();
213
214 if (!self.startDrag || tree.settings.isDragAnDrop !== true || node.depth === 0) {
215 return false;
216 }
217
218 if (_this.dropZoneDelete) {
219 _this.dropZoneDelete
220 .transition(300)
221 .attr('transform', self.getDropZoneCloseTransform(node))
222 .remove();
223 }
224
225 var $svg = $(this).closest('svg');
226 var $nodesBg = $svg.find('.nodes-bg');
227 var droppedNode = tree.settings.nodeOver.node;
228
229 node.isDragged = false;
230
231 _this.addNodeDdClass({
232 $nodesWrap: $svg.find('.nodes-wrapper'),
233 className: '',
234 rmClass: 'dragging',
235 setCanNodeDrag: false,
236 });
237
238 $nodesBg
239 .find('.node-bg.node-bg--dragging')
240 .removeClass('node-bg--dragging');
241
242 $svg
243 .siblings('.node-dd')
244 .remove();
245
246 tree
247 .nodesBgContainer
248 .selectAll('.node-bg__border')
249 .style('display', 'none');
250
251 if (
252 !(node.isOver
253 || (tree.settings.nodeOver.node && tree.settings.nodeOver.node.parentsUid.indexOf(node.identifier) !== -1)
254 || !tree.settings.canNodeDrag
255 || !tree.isOverSvg
256 )
257 ) {
258 var options = _this.changeNodePosition({ droppedNode: droppedNode });
259
260 var modalText = options.position === 'in' ? TYPO3.lang['mess.move_into'] : TYPO3.lang['mess.move_after'];
261 modalText = modalText.replace('%s', options.node.name).replace('%s', options.target.name);
262
263 Modal.confirm(
264 TYPO3.lang.move_page,
265 modalText,
266 Severity.warning, [
267 {
268 text: $(this).data('button-close-text') || TYPO3.lang['labels.cancel'] || 'Cancel',
269 active: true,
270 btnClass: 'btn-default',
271 name: 'cancel',
272 },
273 {
274 text: $(this).data('button-ok-text') || TYPO3.lang['cm.copy'] || 'Copy',
275 btnClass: 'btn-warning',
276 name: 'copy',
277 },
278 {
279 text: $(this).data('button-ok-text') || TYPO3.lang['button.move'] || 'Move',
280 btnClass: 'btn-warning',
281 name: 'move',
282 },
283 ])
284 .on('button.clicked', function (e) {
285 if (e.target.name === 'move') {
286 options.command = 'move';
287 tree.sendChangeCommand(options);
288 } else if (e.target.name === 'copy') {
289 options.command = 'copy';
290 tree.sendChangeCommand(options);
291 }
292
293 Modal.dismiss();
294 });
295 } else if (tree.nodeIsOverDelete) {
296 var options = _this.changeNodePosition({ droppedNode: droppedNode, command: 'delete' });
297 if (tree.settings.displayDeleteConfirmation) {
298 var $modal = Modal.confirm(
299 TYPO3.lang.deleteItem,
300 TYPO3.lang['mess.delete'].replace('%s', options.node.name),
301 Severity.warning, [
302 {
303 text: $(this).data('button-close-text') || TYPO3.lang['labels.cancel'] || 'Cancel',
304 active: true,
305 btnClass: 'btn-default',
306 name: 'cancel',
307 },
308 {
309 text: $(this).data('button-ok-text') || TYPO3.lang['cm.delete'] || 'Delete',
310 btnClass: 'btn-warning',
311 name: 'delete',
312 },
313 ]);
314
315 $modal.on('button.clicked', function (e) {
316 if (e.target.name === 'delete') {
317
318 tree.sendChangeCommand(options);
319 }
320
321 Modal.dismiss();
322 });
323 } else {
324 tree.sendChangeCommand(options);
325 }
326 }
327 };
328
329 return d3.drag()
330 .on('start', self.dragStart)
331 .on('drag', self.dragDragged)
332 .on('end', self.dragEnd);
333 },
334
335 changeNodeClasses: function () {
336 var elementNodeBg = this.tree.svg.select('.node-over');
337 var $svg = $(this.tree.svg.node());
338 var $nodesWrap = $svg.find('.nodes-wrapper');
339 var $nodeDd = $svg.siblings('.node-dd');
340 var nodeBgBorder = this.tree.nodesBgContainer.selectAll('.node-bg__border');
341
342 if (elementNodeBg.size() && this.tree.isOverSvg) {
343 //line between nodes
344 if (nodeBgBorder.empty()) {
345 nodeBgBorder = this.tree.nodesBgContainer
346 .append('rect')
347 .attr('class', 'node-bg__border')
348 .attr('height', '1px')
349 .attr('width', '100%');
350 }
351
352 var coordinates = d3.mouse(elementNodeBg.node());
353 var y = coordinates[1];
354
355 if (y < 3) {
356 nodeBgBorder
357 .attr('transform', 'translate(-8, ' + (this.tree.settings.nodeOver.node.y - 10) + ')')
358 .style('display', 'block');
359
360 if (this.tree.settings.nodeOver.node.depth === 0) {
361 this.addNodeDdClass({
362 $nodeDd: $nodeDd,
363 $nodesWrap: $nodesWrap,
364 className: 'nodrop',
365 });
366 } else if (this.tree.settings.nodeOver.node.firstChild) {
367 this.addNodeDdClass({
368 $nodeDd: $nodeDd,
369 $nodesWrap: $nodesWrap,
370 className: 'ok-above',
371 });
372 } else {
373 this.addNodeDdClass({
374 $nodeDd: $nodeDd,
375 $nodesWrap: $nodesWrap,
376 className: 'ok-between',
377 });
378 }
379
380 this.tree.settings.nodeDragPosition = 'before';
381 } else if (y > 17) {
382 nodeBgBorder
383 .style('display', 'none');
384
385 if (this.tree.settings.nodeOver.node.open && this.tree.settings.nodeOver.node.hasChildren) {
386 this.addNodeDdClass({
387 $nodeDd: $nodeDd,
388 $nodesWrap: $nodesWrap,
389 className: 'ok-append',
390 });
391 this.tree.settings.nodeDragPosition = 'in';
392 } else {
393 nodeBgBorder
394 .attr('transform', 'translate(-8, ' + (this.tree.settings.nodeOver.node.y + 10) + ')')
395 .style('display', 'block');
396
397 if (this.tree.settings.nodeOver.node.lastChild) {
398 this.addNodeDdClass({
399 $nodeDd: $nodeDd,
400 $nodesWrap: $nodesWrap,
401 className: 'ok-below',
402 });
403
404 } else {
405 this.addNodeDdClass({
406 $nodeDd: $nodeDd,
407 $nodesWrap: $nodesWrap,
408 className: 'ok-between',
409 });
410 }
411
412 this.tree.settings.nodeDragPosition = 'after';
413 }
414 } else {
415 nodeBgBorder
416 .style('display', 'none');
417
418 this.addNodeDdClass({
419 $nodeDd: $nodeDd,
420 $nodesWrap: $nodesWrap,
421 className: 'ok-append',
422 });
423 this.tree.settings.nodeDragPosition = 'in';
424 }
425 } else {
426 this.tree.nodesBgContainer
427 .selectAll('.node-bg__border')
428 .style('display', 'none');
429
430 this.addNodeDdClass({
431 $nodeDd: $nodeDd,
432 $nodesWrap: $nodesWrap,
433 className: 'nodrop',
434 });
435 }
436 },
437
438 addNodeDdClass: function (options) {
439 var clearClass = ' #prefix#--nodrop #prefix#--ok-append #prefix#--ok-below #prefix#--ok-between #prefix#--ok-above';
440 var rmClass = '';
441 var addClass = '';
442
443 if (options.$nodeDd) {
444 rmClass = (options.rmClass ? ' node-dd--' + options.rmClass : '');
445 addClass = (options.className ? 'node-dd--' + options.className : '');
446
447 options.$nodeDd
448 .removeClass(clearClass.replace(new RegExp('#prefix#', 'g'), 'node-dd') + rmClass)
449 .addClass(addClass);
450 }
451
452 if (options.$nodesWrap) {
453 rmClass = (options.rmClass ? ' nodes-wrapper--' + options.rmClass : '');
454 addClass = (options.className ? 'nodes-wrapper--' + options.className : '');
455
456 options.$nodesWrap
457 .removeClass(clearClass.replace(new RegExp('#prefix#', 'g'), 'nodes-wrapper') + rmClass)
458 .addClass(addClass);
459 }
460
461 if ((typeof options.setCanNodeDrag === 'undefined') || options.setCanNodeDrag) {
462 this.tree.settings.canNodeDrag = !(options.className === 'nodrop');
463 }
464 },
465
466 /**
467 * Check if node is dragged at least @distance
468 *
469 * @param {Object} data
470 * @param {Integer} distance
471 * @returns {boolean}
472 */
473 isDragNodeDistanceMore: function (data, distance) {
474 return (data.startDrag ||
475 (((data.startPageX - distance) > d3.event.sourceEvent.pageX) ||
476 ((data.startPageX + distance) < d3.event.sourceEvent.pageX) ||
477 ((data.startPageY - distance) > d3.event.sourceEvent.pageY) ||
478 ((data.startPageY + distance) < d3.event.sourceEvent.pageY)));
479 },
480
481 /**
482 * Sets the same parameters on start for method drag() and dragToolbar()
483 *
484 * @returns {{startPageX, startPageY, startDrag: boolean}}
485 */
486 setDragStart: function () {
487 $('body iframe').css({ 'pointer-events': 'none' });
488
489 return {
490 startPageX: d3.event.sourceEvent.pageX,
491 startPageY: d3.event.sourceEvent.pageY,
492 startDrag: false,
493 };
494 },
495
496 /**
497 * Sets the same parameters on end for method drag() and dragToolbar()
498 */
499 setDragEnd: function () {
500 $('body iframe').css({ 'pointer-events': '' });
501 },
502
503 /**
504 * Drag and drop for toolbar new elements
505 *
506 * Returns method from d3js
507 */
508 dragToolbar: function () {
509 var self = {};
510 var _this = this;
511 var tree = _this.tree;
512
513 self.dragStart = function () {
514 self.id = $(this).data('node-type');
515 self.name = $(this).attr('title');
516 self.tooltip = $(this).attr('tooltip');
517 self.icon = $(this).data('tree-icon');
518 self.isDragged = false;
519 $.extend(self, _this.setDragStart());
520 };
521
522 self.dragDragged = function () {
523 if (_this.isDragNodeDistanceMore(self, 10)) {
524 self.startDrag = true;
525 } else {
526 return;
527 }
528
529 var $svg = $(_this.tree.svg.node());
530
531 if (self.isDragged === false) {
532 _this.tree.settings.dragging = true;
533 self.isDragged = true;
534
535 $svg.after(_this.template('#icon-' + self.icon, self.name));
536
537 $svg
538 .find('.nodes-wrapper')
539 .addClass('nodes-wrapper--dragging');
540 }
541
542 var left = 18;
543 var top = 15;
544
545 if (d3.event.sourceEvent && d3.event.sourceEvent.pageX) {
546 left += d3.event.sourceEvent.pageX;
547 }
548
549 if (d3.event.sourceEvent && d3.event.sourceEvent.pageY) {
550 top += d3.event.sourceEvent.pageY;
551 }
552
553 $(document).find('.node-dd').css({
554 left: left,
555 top: top,
556 display: 'block',
557 });
558
559 _this.changeNodeClasses();
560 };
561
562 self.dragEnd = function () {
563 _this.setDragEnd();
564
565 if (!self.startDrag) {
566 return;
567 }
568
569 var $svg = $(_this.tree.svg.node());
570 var $nodesBg = $svg.find('.nodes-bg');
571
572 $svg
573 .siblings('.node-dd')
574 .remove();
575 $svg
576 .find('.nodes-wrapper')
577 .removeClass('nodes-wrapper--dragging');
578
579 self.isDragged = false;
580 _this.tree.settings.dragging = false;
581
582 _this.addNodeDdClass({
583 $nodesWrap: $svg.find('.nodes-wrapper'),
584 className: '',
585 rmClass: 'dragging',
586 setCanNodeDrag: false,
587 });
588
589 $nodesBg
590 .find('.node-bg.node-bg--dragging')
591 .removeClass('node-bg--dragging');
592
593 $svg
594 .siblings('.node-dd')
595 .remove();
596
597 _this
598 .tree
599 .nodesBgContainer
600 .selectAll('.node-bg__border')
601 .style('display', 'none');
602
603 if (_this.tree.settings.isDragAnDrop !== true || !_this.tree.settings.nodeOver.node || !_this.tree.isOverSvg) {
604 return false;
605 }
606
607 if (_this.tree.settings.canNodeDrag) {
608 var data = {
609 type: self.id,
610 name: self.name,
611 tooltip: self.tooltip,
612 icon: self.icon,
613 position: _this.tree.settings.nodeDragPosition,
614 command: 'new',
615 target: _this.tree.settings.nodeOver.node,
616 };
617
618 _this.addNewNode(data);
619 }
620 };
621
622 return d3.drag()
623 .on('start', self.dragStart)
624 .on('drag', self.dragDragged)
625 .on('end', self.dragEnd);
626 },
627
628 changeNodePosition: function (options) {
629 var _this = this;
630 var tree = _this.tree;
631 var nodes = tree.nodes;
632 var uid = tree.settings.nodeDrag.identifier;
633 var index = nodes.indexOf(options.droppedNode);
634 var position = tree.settings.nodeDragPosition;
635 var target = tree.settings.nodeDrag;
636
637 if (options.droppedNode) {
638 target = options.droppedNode;
639 }
640
641 if ((uid === target.identifier) && options.command !== 'delete') {
642 return;
643 }
644
645 tree.nodes.indexOf(tree.settings.nodeOver.node);
646
647 if (position === 'before') {
648 var positionAndTarget = this.setNodePositionAndTarget(tree.settings.nodeDrag.depth, index);
649 position = positionAndTarget[0];
650 target = positionAndTarget[1];
651 }
652
653 var data = {
654 node: tree.settings.nodeDrag,
655 uid: uid, //dragged node id
656 target: target, //hovered node
657 position: position, //before, in, after
658 command: options.command, //element is copied or moved
659 };
660
661 $.extend(data, options);
662
663 return data;
664 },
665
666 /**
667 * Returns Array of position and target node
668 *
669 * @param {Integer} nodeDepth
670 * @param {Integer} index
671 * @returns {Array} [position, target]
672 */
673 setNodePositionAndTarget: function (nodeDepth, index) {
674 if (index > 0) {
675 index--;
676 }
677
678 var target = this.tree.nodes[index];
679
680 if (this.tree.nodes[index].depth === nodeDepth) {
681 return ['after', target];
682 } else if (this.tree.nodes[index].depth < nodeDepth) {
683 return ['in', target];
684 } else {
685 for (var i = index; i >= 0; i--) {
686 if (this.tree.nodes[i].depth === nodeDepth) {
687 return ['after', this.tree.nodes[i]];
688 } else if (this.tree.nodes[i].depth < nodeDepth) {
689 return ['in', this.tree.nodes[i]];
690 }
691 }
692 }
693 },
694
695 /**
696 * Add new node
697 *
698 * @type {Object} options
699 */
700 addNewNode: function (options) {
701 var _this = this;
702 var target = options.target;
703 var index = _this.tree.nodes.indexOf(target);
704 var newNode = {};
705 var removeNode = function (newNode) {
706 _this.tree.nodes.splice(_this.tree.nodes.indexOf(newNode), 1);
707 _this.tree.setParametersNode(_this.tree.nodes);
708 _this.tree.prepareDataForVisibleNodes();
709 _this.tree.update();
710 _this.tree.removeEditedText();
711 };
712
713 newNode.command = 'new';
714 newNode.type = options.type;
715 newNode.identifier = -1;
716 newNode.target = target;
717 newNode.parents = target.parents;
718 newNode.parentsUid = target.parentsUid;
719 newNode.depth = target.depth;
720 newNode.position = options.position;
721 newNode.name = (typeof options.title !== 'undefined') ? options.title : TYPO3.lang['tree.defaultPageTitle'];
722
723 if (options.position === 'in') {
724 newNode.depth++;
725 _this.tree.nodes[index].open = true;
726 _this.tree.nodes[index].hasChildren = true;
727 }
728
729 if (options.position === 'in' || options.position === 'after') {
730 index++;
731 }
732
733 if (options.icon) {
734 newNode.icon = options.icon;
735 }
736
737 if (newNode.position === 'before') {
738 var positionAndTarget = this.setNodePositionAndTarget(this.tree.nodes[index].depth, index);
739 newNode.position = positionAndTarget[0];
740 newNode.target = positionAndTarget[1];
741 }
742
743 _this.tree.nodes.splice(index, 0, newNode);
744 _this.tree.setParametersNode(_this.tree.nodes);
745 _this.tree.prepareDataForVisibleNodes();
746 _this.tree.update();
747
748 _this.tree.removeEditedText();
749 _this.tree.nodeIsEdit = true;
750
751 d3.select(_this.tree.svg.node().parentNode)
752 .append('input')
753 .attr('class', 'node-edit')
754 .style('top', function () {
755 var top = _this.tree.data.nodes.indexOf(newNode) * _this.tree.settings.nodeHeight;
756 top = top + 15; //svg margin top
757 return top + 'px';
758 })
759 .style('left', (newNode.x + _this.tree.textPosition + 5) + 'px')
760 .style('width', _this.tree.settings.width - (newNode.x + _this.tree.textPosition + 20) + 'px')
761 .style('height', _this.tree.settings.nodeHeight + 'px')
762 .attr('text', 'text')
763 .attr('value', newNode.name)
764 .on('keydown', function () {
765 var code = d3.event.keyCode;
766
767 if (code === 13 || code === 9) { //enter || tab
768 _this.tree.nodeIsEdit = false;
769 var newName = this.value.trim();
770
771 if (newName.length) {
772 newNode.name = newName;
773 _this.tree.removeEditedText();
774 _this.tree.sendChangeCommand(newNode);
775 } else {
776 removeNode(newNode);
777 }
778 } else if (code === 27) { //esc
779 _this.tree.nodeIsEdit = false;
780 removeNode(newNode);
781 }
782 })
783 .on('blur', function () {
784 if (_this.tree.nodeIsEdit && (_this.tree.nodes.indexOf(newNode) > -1)) {
785 var newName = this.value.trim();
786
787 if (newName.length) {
788 newNode.name = newName;
789 _this.tree.removeEditedText();
790 _this.tree.sendChangeCommand(newNode);
791 } else {
792 removeNode(newNode);
793 }
794 }
795 })
796 .node()
797 .select();
798 },
799
800 /**
801 * Returns template for dragged node
802 *
803 * @returns {String}
804 */
805 template: function (icon, name) {
806 return '<div class="node-dd node-dd--nodrop">' +
807 '<div class="node-dd__ctrl-icon">' +
808 '</div>' +
809 '<div class="node-dd__text">' +
810 '<span class="node-dd__icon">' +
811 '<svg aria-hidden="true" width="16px" height="16px"><use xlink:href="' + icon + '"/></svg>' +
812 '</span>' +
813 '<span class="node-dd__name">' + name + '</span>' +
814 '</div>' +
815 '</div>';
816 },
817 };
818
819 return PageTreeDragDrop;
820 });