[BUGFIX] Add missing translation value for Modals
[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-state-id="' + node.stateIdentifier + '"]')
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-state-id=' + node.stateIdentifier + ']');
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 _this.openNodeTimeout();
177
178 if (node.isOver
179 || (tree.settings.nodeOver.node && tree.settings.nodeOver.node.parentsStateIdentifier.indexOf(node.stateIdentifier) !== -1)
180 || !tree.isOverSvg) {
181
182 _this.addNodeDdClass({$nodeDd: $nodeDd, $nodesWrap: $nodesWrap, className: 'nodrop'});
183
184 if (!tree.isOverSvg) {
185 _this.tree.nodesBgContainer
186 .selectAll('.node-bg__border')
187 .style('display', 'none');
188 }
189
190 if (_this.dropZoneDelete && _this.dropZoneDelete.attr('data-open') !== 'true' && tree.isOverSvg) {
191 _this.dropZoneDelete
192 .transition(300)
193 .attr('transform', self.getDropZoneOpenTransform(node))
194 .attr('data-open', 'true');
195 }
196 } else if (!tree.settings.nodeOver.node) {
197 _this.addNodeDdClass({$nodeDd: $nodeDd, $nodesWrap: $nodesWrap, className: 'nodrop'});
198 _this.tree.nodesBgContainer
199 .selectAll('.node-bg__border')
200 .style('display', 'none');
201 } else {
202 if (_this.dropZoneDelete && _this.dropZoneDelete.attr('data-open') !== 'false') {
203 _this.dropZoneDelete
204 .transition(300)
205 .attr('transform', self.getDropZoneCloseTransform(node))
206 .attr('data-open', 'false');
207 }
208
209 _this.changeNodeClasses();
210 }
211 };
212
213 self.dragEnd = function(node) {
214 _this.setDragEnd();
215
216 if (!self.startDrag || tree.settings.isDragAnDrop !== true || node.depth === 0) {
217 return false;
218 }
219
220 if (_this.dropZoneDelete) {
221 _this.dropZoneDelete
222 .transition(300)
223 .attr('transform', self.getDropZoneCloseTransform(node))
224 .remove();
225 }
226
227 var $svg = $(this).closest('svg');
228 var $nodesBg = $svg.find('.nodes-bg');
229 var droppedNode = tree.settings.nodeOver.node;
230
231 node.isDragged = false;
232
233 _this.addNodeDdClass({
234 $nodesWrap: $svg.find('.nodes-wrapper'),
235 className: '',
236 rmClass: 'dragging',
237 setCanNodeDrag: false
238 });
239
240 $nodesBg
241 .find('.node-bg.node-bg--dragging')
242 .removeClass('node-bg--dragging');
243
244 $svg
245 .siblings('.node-dd')
246 .remove();
247
248 tree
249 .nodesBgContainer
250 .selectAll('.node-bg__border')
251 .style('display', 'none');
252
253 if (
254 !(node.isOver
255 || (tree.settings.nodeOver.node && tree.settings.nodeOver.node.parentsStateIdentifier.indexOf(node.stateIdentifier) !== -1)
256 || !tree.settings.canNodeDrag
257 || !tree.isOverSvg
258 )
259 ) {
260 var options = _this.changeNodePosition({droppedNode: droppedNode});
261 var modalText = options.position === 'in' ? TYPO3.lang['mess.move_into'] : TYPO3.lang['mess.move_after'];
262 modalText = modalText.replace('%s', options.node.name).replace('%s', options.target.name);
263
264 Modal.confirm(
265 TYPO3.lang.move_page,
266 modalText,
267 Severity.warning, [
268 {
269 text: $(this).data('button-close-text') || TYPO3.lang['labels.cancel'] || 'Cancel',
270 active: true,
271 btnClass: 'btn-default',
272 name: 'cancel'
273 },
274 {
275 text: $(this).data('button-ok-text') || TYPO3.lang['cm.copy'] || 'Copy',
276 btnClass: 'btn-warning',
277 name: 'copy'
278 },
279 {
280 text: $(this).data('button-ok-text') || TYPO3.lang['labels.move'] || 'Move',
281 btnClass: 'btn-warning',
282 name: 'move'
283 }
284 ])
285 .on('button.clicked', function(e) {
286 if (e.target.name === 'move') {
287 options.command = 'move';
288 tree.sendChangeCommand(options);
289 } else if (e.target.name === 'copy') {
290 options.command = 'copy';
291 tree.sendChangeCommand(options);
292 }
293
294 Modal.dismiss();
295 });
296 } else if (tree.nodeIsOverDelete) {
297 var options = _this.changeNodePosition({droppedNode: droppedNode, command: 'delete'});
298 if (tree.settings.displayDeleteConfirmation) {
299 var $modal = Modal.confirm(
300 TYPO3.lang.deleteItem,
301 TYPO3.lang['mess.delete'].replace('%s', options.node.name),
302 Severity.warning, [
303 {
304 text: $(this).data('button-close-text') || TYPO3.lang['labels.cancel'] || 'Cancel',
305 active: true,
306 btnClass: 'btn-default',
307 name: 'cancel'
308 },
309 {
310 text: $(this).data('button-ok-text') || TYPO3.lang['cm.delete'] || 'Delete',
311 btnClass: 'btn-warning',
312 name: 'delete'
313 }
314 ]);
315
316 $modal.on('button.clicked', function(e) {
317 if (e.target.name === 'delete') {
318
319 tree.sendChangeCommand(options);
320 }
321
322 Modal.dismiss();
323 });
324 } else {
325 tree.sendChangeCommand(options);
326 }
327 }
328 };
329
330 return d3.drag()
331 .on('start', self.dragStart)
332 .on('drag', self.dragDragged)
333 .on('end', self.dragEnd);
334 },
335
336 /**
337 * Open node with children while holding the node/element over this node for one second
338 */
339 openNodeTimeout: function() {
340 var _this = this;
341
342 if (!_this.timeout) {
343 _this.timeout = {}
344 }
345
346 if (_this.tree.settings.nodeOver.node.hasChildren && !_this.tree.settings.nodeOver.node.expanded) {
347 if (_this.timeout.node != _this.tree.settings.nodeOver.node) {
348 _this.timeout.node = _this.tree.settings.nodeOver;
349 clearTimeout(_this.timeout.time);
350 _this.timeout.time = setTimeout(function() {
351 if (_this.tree.settings.nodeOver.node) {
352 _this.tree.showChildren(_this.tree.settings.nodeOver.node);
353 _this.tree.prepareDataForVisibleNodes();
354 _this.tree.update();
355 }
356 }, 1000);
357 }
358 } else {
359 clearTimeout(_this.timeout.time);
360 }
361 },
362
363 changeNodeClasses: function() {
364 var elementNodeBg = this.tree.svg.select('.node-over');
365 var $svg = $(this.tree.svg.node());
366 var $nodesWrap = $svg.find('.nodes-wrapper');
367 var $nodeDd = $svg.siblings('.node-dd');
368 var nodeBgBorder = this.tree.nodesBgContainer.selectAll('.node-bg__border');
369
370 if (elementNodeBg.size() && this.tree.isOverSvg) {
371 // line between nodes
372 if (nodeBgBorder.empty()) {
373 nodeBgBorder = this.tree.nodesBgContainer
374 .append('rect')
375 .attr('class', 'node-bg__border')
376 .attr('height', '1px')
377 .attr('width', '100%');
378 }
379
380 var coordinates = d3.mouse(elementNodeBg.node());
381 var y = coordinates[1];
382
383 if (y < 3) {
384 nodeBgBorder
385 .attr('transform', 'translate(-8, ' + (this.tree.settings.nodeOver.node.y - 10) + ')')
386 .style('display', 'block');
387
388 if (this.tree.settings.nodeOver.node.depth === 0) {
389 this.addNodeDdClass({
390 $nodeDd: $nodeDd,
391 $nodesWrap: $nodesWrap,
392 className: 'nodrop'
393 });
394 } else if (this.tree.settings.nodeOver.node.firstChild) {
395 this.addNodeDdClass({
396 $nodeDd: $nodeDd,
397 $nodesWrap: $nodesWrap,
398 className: 'ok-above'
399 });
400 } else {
401 this.addNodeDdClass({
402 $nodeDd: $nodeDd,
403 $nodesWrap: $nodesWrap,
404 className: 'ok-between'
405 });
406 }
407
408 this.tree.settings.nodeDragPosition = 'before';
409 } else if (y > 17) {
410 nodeBgBorder
411 .style('display', 'none');
412
413 if (this.tree.settings.nodeOver.node.expanded && this.tree.settings.nodeOver.node.hasChildren) {
414 this.addNodeDdClass({
415 $nodeDd: $nodeDd,
416 $nodesWrap: $nodesWrap,
417 className: 'ok-append'
418 });
419 this.tree.settings.nodeDragPosition = 'in';
420 } else {
421 nodeBgBorder
422 .attr('transform', 'translate(-8, ' + (this.tree.settings.nodeOver.node.y + 10) + ')')
423 .style('display', 'block');
424
425 if (this.tree.settings.nodeOver.node.lastChild) {
426 this.addNodeDdClass({
427 $nodeDd: $nodeDd,
428 $nodesWrap: $nodesWrap,
429 className: 'ok-below'
430 });
431
432 } else {
433 this.addNodeDdClass({
434 $nodeDd: $nodeDd,
435 $nodesWrap: $nodesWrap,
436 className: 'ok-between'
437 });
438 }
439
440 this.tree.settings.nodeDragPosition = 'after';
441 }
442 } else {
443 nodeBgBorder
444 .style('display', 'none');
445
446 this.addNodeDdClass({
447 $nodeDd: $nodeDd,
448 $nodesWrap: $nodesWrap,
449 className: 'ok-append'
450 });
451 this.tree.settings.nodeDragPosition = 'in';
452 }
453 } else {
454 this.tree.nodesBgContainer
455 .selectAll('.node-bg__border')
456 .style('display', 'none');
457
458 this.addNodeDdClass({
459 $nodeDd: $nodeDd,
460 $nodesWrap: $nodesWrap,
461 className: 'nodrop'
462 });
463 }
464 },
465
466 addNodeDdClass: function(options) {
467 var clearClass = ' #prefix#--nodrop #prefix#--ok-append #prefix#--ok-below #prefix#--ok-between #prefix#--ok-above';
468 var rmClass = '';
469 var addClass = '';
470
471 if (options.$nodeDd) {
472 rmClass = (options.rmClass ? ' node-dd--' + options.rmClass : '');
473 addClass = (options.className ? 'node-dd--' + options.className : '');
474
475 options.$nodeDd
476 .removeClass(clearClass.replace(new RegExp('#prefix#', 'g'), 'node-dd') + rmClass)
477 .addClass(addClass);
478 }
479
480 if (options.$nodesWrap) {
481 rmClass = (options.rmClass ? ' nodes-wrapper--' + options.rmClass : '');
482 addClass = (options.className ? 'nodes-wrapper--' + options.className : '');
483
484 options.$nodesWrap
485 .removeClass(clearClass.replace(new RegExp('#prefix#', 'g'), 'nodes-wrapper') + rmClass)
486 .addClass(addClass);
487 }
488
489 if ((typeof options.setCanNodeDrag === 'undefined') || options.setCanNodeDrag) {
490 this.tree.settings.canNodeDrag = !(options.className === 'nodrop');
491 }
492 },
493
494 /**
495 * Check if node is dragged at least @distance
496 *
497 * @param {Object} data
498 * @param {Integer} distance
499 * @returns {boolean}
500 */
501 isDragNodeDistanceMore: function(data, distance) {
502 return (data.startDrag ||
503 (((data.startPageX - distance) > d3.event.sourceEvent.pageX) ||
504 ((data.startPageX + distance) < d3.event.sourceEvent.pageX) ||
505 ((data.startPageY - distance) > d3.event.sourceEvent.pageY) ||
506 ((data.startPageY + distance) < d3.event.sourceEvent.pageY)));
507 },
508
509 /**
510 * Sets the same parameters on start for method drag() and dragToolbar()
511 *
512 * @returns {{startPageX, startPageY, startDrag: boolean}}
513 */
514 setDragStart: function() {
515 $('body iframe').css({'pointer-events': 'none'});
516
517 return {
518 startPageX: d3.event.sourceEvent.pageX,
519 startPageY: d3.event.sourceEvent.pageY,
520 startDrag: false
521 };
522 },
523
524 /**
525 * Sets the same parameters on end for method drag() and dragToolbar()
526 */
527 setDragEnd: function() {
528 $('body iframe').css({'pointer-events': ''});
529 },
530
531 /**
532 * Drag and drop for toolbar new elements
533 *
534 * Returns method from d3js
535 */
536 dragToolbar: function() {
537 var self = {};
538 var _this = this;
539 var tree = _this.tree;
540
541 self.dragStart = function() {
542 self.id = $(this).data('node-type');
543 self.name = $(this).attr('title');
544 self.tooltip = $(this).attr('tooltip');
545 self.icon = $(this).data('tree-icon');
546 self.isDragged = false;
547 $.extend(self, _this.setDragStart());
548 };
549
550 self.dragDragged = function() {
551 if (_this.isDragNodeDistanceMore(self, 10)) {
552 self.startDrag = true;
553 } else {
554 return;
555 }
556
557 var $svg = $(_this.tree.svg.node());
558
559 if (self.isDragged === false) {
560 _this.tree.settings.dragging = true;
561 self.isDragged = true;
562
563 $svg.after(_this.template('#icon-' + self.icon, self.name));
564
565 $svg
566 .find('.nodes-wrapper')
567 .addClass('nodes-wrapper--dragging');
568 }
569
570 var left = 18;
571 var top = 15;
572
573 if (d3.event.sourceEvent && d3.event.sourceEvent.pageX) {
574 left += d3.event.sourceEvent.pageX;
575 }
576
577 if (d3.event.sourceEvent && d3.event.sourceEvent.pageY) {
578 top += d3.event.sourceEvent.pageY;
579 }
580
581 _this.openNodeTimeout();
582
583 $(document).find('.node-dd').css({
584 left: left,
585 top: top,
586 display: 'block'
587 });
588
589 _this.changeNodeClasses();
590 };
591
592 self.dragEnd = function() {
593 _this.setDragEnd();
594
595 if (!self.startDrag) {
596 return;
597 }
598
599 var $svg = $(_this.tree.svg.node());
600 var $nodesBg = $svg.find('.nodes-bg');
601
602 $svg
603 .siblings('.node-dd')
604 .remove();
605 $svg
606 .find('.nodes-wrapper')
607 .removeClass('nodes-wrapper--dragging');
608
609 self.isDragged = false;
610 _this.tree.settings.dragging = false;
611
612 _this.addNodeDdClass({
613 $nodesWrap: $svg.find('.nodes-wrapper'),
614 className: '',
615 rmClass: 'dragging',
616 setCanNodeDrag: false
617 });
618
619 $nodesBg
620 .find('.node-bg.node-bg--dragging')
621 .removeClass('node-bg--dragging');
622
623 $svg
624 .siblings('.node-dd')
625 .remove();
626
627 _this
628 .tree
629 .nodesBgContainer
630 .selectAll('.node-bg__border')
631 .style('display', 'none');
632
633 if (_this.tree.settings.isDragAnDrop !== true || !_this.tree.settings.nodeOver.node || !_this.tree.isOverSvg) {
634 return false;
635 }
636
637 if (_this.tree.settings.canNodeDrag) {
638 var data = {
639 type: self.id,
640 name: self.name,
641 tooltip: self.tooltip,
642 icon: self.icon,
643 position: _this.tree.settings.nodeDragPosition,
644 command: 'new',
645 target: _this.tree.settings.nodeOver.node
646 };
647
648 _this.addNewNode(data);
649 }
650 };
651
652 return d3.drag()
653 .on('start', self.dragStart)
654 .on('drag', self.dragDragged)
655 .on('end', self.dragEnd);
656 },
657
658 changeNodePosition: function(options) {
659 var _this = this;
660 var tree = _this.tree;
661 var nodes = tree.nodes;
662 var uid = tree.settings.nodeDrag.identifier;
663 var index = nodes.indexOf(options.droppedNode);
664 var position = tree.settings.nodeDragPosition;
665 var target = tree.settings.nodeDrag;
666
667 if (options.droppedNode) {
668 target = options.droppedNode;
669 }
670
671 if ((uid === target.identifier) && options.command !== 'delete') {
672 return;
673 }
674
675 if (position === 'before') {
676 var positionAndTarget = this.setNodePositionAndTarget(index);
677 position = positionAndTarget[0];
678 target = positionAndTarget[1];
679 }
680
681 var data = {
682 node: tree.settings.nodeDrag,
683 uid: uid, // dragged node id
684 target: target, // hovered node
685 position: position, // before, in, after
686 command: options.command // element is copied or moved
687 };
688
689 $.extend(data, options);
690
691 return data;
692 },
693
694 /**
695 * Returns Array of position and target node
696 *
697 * @param {Integer} index of node which is over mouse
698 * @returns {Array} [position, target]
699 */
700 setNodePositionAndTarget: function(index) {
701 var nodes = this.tree.nodes;
702 var nodeOver = nodes[index];
703 var nodeOverDepth = nodeOver.depth;
704 if (index > 0) {
705 index--;
706 }
707 var nodeBefore = nodes[index];
708 var nodeBeforeDepth = nodeBefore.depth;
709 var target = this.tree.nodes[index];
710
711 if (nodeBeforeDepth === nodeOverDepth) {
712 return ['after', target];
713 } else if (nodeBeforeDepth < nodeOverDepth) {
714 return ['in', target];
715 } else {
716 for (var i = index; i >= 0; i--) {
717 if (nodes[i].depth === nodeOverDepth) {
718 return ['after', this.tree.nodes[i]];
719 } else if (nodes[i].depth < nodeOverDepth) {
720 return ['in', nodes[i]];
721 }
722 }
723 }
724 },
725
726 /**
727 * Add new node
728 *
729 * @type {Object} options
730 */
731 addNewNode: function(options) {
732 var _this = this;
733 var target = options.target;
734 var index = _this.tree.nodes.indexOf(target);
735 var newNode = {};
736 var removeNode = function(newNode) {
737 var index = _this.tree.nodes.indexOf(newNode);
738
739 // if newNode is only one child
740 if (_this.tree.nodes[index - 1].depth != newNode.depth
741 && (!_this.tree.nodes[index + 1] || _this.tree.nodes[index + 1].depth != newNode.depth)) {
742 _this.tree.nodes[index - 1].hasChildren = false;
743 }
744
745 _this.tree.nodes.splice(index, 1);
746 _this.tree.setParametersNode();
747 _this.tree.prepareDataForVisibleNodes();
748 _this.tree.update();
749 _this.tree.removeEditedText();
750 };
751
752 newNode.command = 'new';
753 newNode.type = options.type;
754 newNode.identifier = -1;
755 newNode.target = target;
756 newNode.parents = target.parents;
757 newNode.parentsStateIdentifier = target.parentsStateIdentifier;
758 newNode.depth = target.depth;
759 newNode.position = options.position;
760 newNode.name = (typeof options.title !== 'undefined') ? options.title : TYPO3.lang['tree.defaultPageTitle'];
761 newNode.y = newNode.y || newNode.target.y;
762 newNode.x = newNode.x || newNode.target.x;
763
764 if (options.position === 'in') {
765 newNode.depth++;
766 newNode.parents.unshift(index);
767 newNode.parentsStateIdentifier.unshift(_this.tree.nodes[index].stateIdentifier);
768 _this.tree.nodes[index].hasChildren = true;
769 _this.tree.showChildren(_this.tree.nodes[index]);
770 }
771
772 if (options.position === 'in' || options.position === 'after') {
773 index++;
774 }
775
776 if (options.icon) {
777 newNode.icon = options.icon;
778 }
779
780 if (newNode.position === 'before') {
781 var positionAndTarget = this.setNodePositionAndTarget(index);
782 newNode.position = positionAndTarget[0];
783 newNode.target = positionAndTarget[1];
784 }
785
786 _this.tree.nodes.splice(index, 0, newNode);
787 _this.tree.setParametersNode();
788 _this.tree.prepareDataForVisibleNodes();
789 _this.tree.update();
790 _this.tree.removeEditedText();
791 _this.tree.nodeIsEdit = true;
792
793 d3.select(_this.tree.svg.node().parentNode)
794 .append('input')
795 .attr('class', 'node-edit')
796 .style('top', newNode.y + _this.tree.settings.marginTop + 'px')
797 .style('left', newNode.x + _this.tree.textPosition + 5 + 'px')
798 .style('width', _this.tree.settings.width - (newNode.x + _this.tree.textPosition + 20) + 'px')
799 .style('height', _this.tree.settings.nodeHeight + 'px')
800 .attr('text', 'text')
801 .attr('value', newNode.name)
802 .on('keydown', function() {
803 var code = d3.event.keyCode;
804
805 if (code === 13 || code === 9) { // enter || tab
806 _this.tree.nodeIsEdit = false;
807 var newName = this.value.trim();
808
809 if (newName.length) {
810 newNode.name = newName;
811 _this.tree.removeEditedText();
812 _this.tree.sendChangeCommand(newNode);
813 } else {
814 removeNode(newNode);
815 }
816 } else if (code === 27) { // esc
817 _this.tree.nodeIsEdit = false;
818 removeNode(newNode);
819 }
820 })
821 .on('blur', function() {
822 if (_this.tree.nodeIsEdit && (_this.tree.nodes.indexOf(newNode) > -1)) {
823 var newName = this.value.trim();
824
825 if (newName.length) {
826 newNode.name = newName;
827 _this.tree.removeEditedText();
828 _this.tree.sendChangeCommand(newNode);
829 } else {
830 removeNode(newNode);
831 }
832 }
833 })
834 .node()
835 .select();
836 },
837
838 /**
839 * Returns template for dragged node
840 *
841 * @returns {String}
842 */
843 template: function(icon, name) {
844 return '<div class="node-dd node-dd--nodrop">' +
845 '<div class="node-dd__ctrl-icon">' +
846 '</div>' +
847 '<div class="node-dd__text">' +
848 '<span class="node-dd__icon">' +
849 '<svg aria-hidden="true" width="16px" height="16px"><use xlink:href="' + icon + '"/></svg>' +
850 '</span>' +
851 '<span class="node-dd__name">' + name + '</span>' +
852 '</div>' +
853 '</div>';
854 }
855 };
856
857 return PageTreeDragDrop;
858 });