[BUGFIX] Change behavior of new element during drag & drop into node 61/55061/7
authorTymoteusz Motylewski <t.motylewski@gmail.com>
Wed, 13 Dec 2017 14:32:07 +0000 (15:32 +0100)
committerAndreas Fernandez <typo3@scripting-base.de>
Sat, 16 Dec 2017 13:42:53 +0000 (14:42 +0100)
- place input correctly
- open node with children while holding the mouse over
this node for one second (when drag-adding new node)
- add chevron and expand it when adding first child
- refactor node.open to node.expand because they did the same thing
- show icons for custom doktypes (fix regex)

Releases: master
Resolves: #83306
Change-Id: I0255fa50f836d002392ed7791eed1ac3517cb400
Reviewed-on: https://review.typo3.org/55061
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Tymoteusz Motylewski <t.motylewski@gmail.com>
Tested-by: Tymoteusz Motylewski <t.motylewski@gmail.com>
Reviewed-by: Andreas Fernandez <typo3@scripting-base.de>
Tested-by: Andreas Fernandez <typo3@scripting-base.de>
typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTree.js
typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeDragDrop.js
typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeToolbar.js
typo3/sysext/backend/Resources/Public/JavaScript/SvgTree.js

index bd247ae..23259dc 100644 (file)
@@ -412,7 +412,7 @@ define(['jquery',
         .append('input')
         .attr('class', 'node-edit')
         .style('top', function () {
-          var top = node.y + 15; //svg margin top
+          var top = node.y + _this.settings.marginTop;
           return top + 'px';
         })
         .style('left', (node.x + _this.textPosition + 5) + 'px')
index d07b8f1..31e4a10 100644 (file)
@@ -173,8 +173,10 @@ define([
 
         tree.settings.nodeDragPosition = false;
 
+        _this.openNodeTimeout();
+
         if (node.isOver
-          || (tree.settings.nodeOver.node && tree.settings.nodeOver.node.parentsUid.indexOf(node.stateIdentifier) !== -1)
+          || (tree.settings.nodeOver.node && tree.settings.nodeOver.node.parentsStateIdentifier.indexOf(node.stateIdentifier) !== -1)
           || !tree.isOverSvg) {
 
           _this.addNodeDdClass({ $nodeDd: $nodeDd, $nodesWrap: $nodesWrap, className: 'nodrop' });
@@ -250,7 +252,7 @@ define([
 
         if (
           !(node.isOver
-            || (tree.settings.nodeOver.node && tree.settings.nodeOver.node.parentsUid.indexOf(node.stateIdentifier) !== -1)
+            || (tree.settings.nodeOver.node && tree.settings.nodeOver.node.parentsStateIdentifier.indexOf(node.stateIdentifier) !== -1)
             || !tree.settings.canNodeDrag
             || !tree.isOverSvg
           )
@@ -332,6 +334,33 @@ define([
         .on('end', self.dragEnd);
     },
 
+    /**
+     * Open node with children while holding the node/element over this node for one second
+     */
+    openNodeTimeout: function () {
+      var _this = this;
+
+      if (!_this.timeout) {
+        _this.timeout = {}
+      }
+
+      if (_this.tree.settings.nodeOver.node.hasChildren && !_this.tree.settings.nodeOver.node.expanded) {
+        if (_this.timeout.node != _this.tree.settings.nodeOver.node) {
+          _this.timeout.node = _this.tree.settings.nodeOver;
+          clearTimeout(_this.timeout.time);
+          _this.timeout.time = setTimeout(function () {
+            if (_this.tree.settings.nodeOver.node) {
+              _this.tree.showChildren(_this.tree.settings.nodeOver.node);
+              _this.tree.prepareDataForVisibleNodes();
+              _this.tree.update();
+            }
+          }, 1000);
+        }
+      } else {
+        clearTimeout(_this.timeout.time);
+      }
+    },
+
     changeNodeClasses: function () {
       var elementNodeBg = this.tree.svg.select('.node-over');
       var $svg = $(this.tree.svg.node());
@@ -340,7 +369,7 @@ define([
       var nodeBgBorder = this.tree.nodesBgContainer.selectAll('.node-bg__border');
 
       if (elementNodeBg.size() && this.tree.isOverSvg) {
-        //line between nodes
+        // line between nodes
         if (nodeBgBorder.empty()) {
           nodeBgBorder = this.tree.nodesBgContainer
             .append('rect')
@@ -382,7 +411,7 @@ define([
           nodeBgBorder
             .style('display', 'none');
 
-          if (this.tree.settings.nodeOver.node.open && this.tree.settings.nodeOver.node.hasChildren) {
+          if (this.tree.settings.nodeOver.node.expanded && this.tree.settings.nodeOver.node.hasChildren) {
             this.addNodeDdClass({
               $nodeDd: $nodeDd,
               $nodesWrap: $nodesWrap,
@@ -550,6 +579,8 @@ define([
           top += d3.event.sourceEvent.pageY;
         }
 
+        _this.openNodeTimeout();
+
         $(document).find('.node-dd').css({
           left: left,
           top: top,
@@ -652,10 +683,10 @@ define([
 
       var data = {
         node: tree.settings.nodeDrag,
-        uid: uid, //dragged node id
-        target: target, //hovered node
-        position: position, //before, in, after
-        command: options.command, //element is copied or moved
+        uid: uid, // dragged node id
+        target: target, // hovered node
+        position: position, // before, in, after
+        command: options.command, // element is copied or moved
       };
 
       $.extend(data, options);
@@ -703,8 +734,16 @@ define([
       var index = _this.tree.nodes.indexOf(target);
       var newNode = {};
       var removeNode = function (newNode) {
-        _this.tree.nodes.splice(_this.tree.nodes.indexOf(newNode), 1);
-        _this.tree.setParametersNode(_this.tree.nodes);
+        var index = _this.tree.nodes.indexOf(newNode);
+
+        // if newNode is only one child
+        if (_this.tree.nodes[index - 1].depth != newNode.depth
+          && (!_this.tree.nodes[index + 1] || _this.tree.nodes[index + 1].depth != newNode.depth)) {
+          _this.tree.nodes[index - 1].hasChildren = false;
+        }
+
+        _this.tree.nodes.splice(index, 1);
+        _this.tree.setParametersNode();
         _this.tree.prepareDataForVisibleNodes();
         _this.tree.update();
         _this.tree.removeEditedText();
@@ -715,15 +754,19 @@ define([
       newNode.identifier = -1;
       newNode.target = target;
       newNode.parents = target.parents;
-      newNode.parentsUid = target.parentsUid;
+      newNode.parentsStateIdentifier = target.parentsStateIdentifier;
       newNode.depth =  target.depth;
       newNode.position =  options.position;
       newNode.name = (typeof options.title !== 'undefined') ? options.title : TYPO3.lang['tree.defaultPageTitle'];
+      newNode.y = newNode.y || newNode.target.y;
+      newNode.x = newNode.x || newNode.target.x;
 
       if (options.position === 'in') {
         newNode.depth++;
-        _this.tree.nodes[index].open = true;
+        newNode.parents.unshift(index);
+        newNode.parentsStateIdentifier.unshift(_this.tree.nodes[index].stateIdentifier);
         _this.tree.nodes[index].hasChildren = true;
+        _this.tree.showChildren(_this.tree.nodes[index]);
       }
 
       if (options.position === 'in' || options.position === 'after') {
@@ -741,21 +784,17 @@ define([
       }
 
       _this.tree.nodes.splice(index, 0, newNode);
-      _this.tree.setParametersNode(_this.tree.nodes);
+      _this.tree.setParametersNode();
       _this.tree.prepareDataForVisibleNodes();
       _this.tree.update();
-
       _this.tree.removeEditedText();
       _this.tree.nodeIsEdit = true;
 
       d3.select(_this.tree.svg.node().parentNode)
         .append('input')
         .attr('class', 'node-edit')
-        .style('top', function () {
-          var top = newNode.y + 15; //svg margin top
-          return top + 'px';
-        })
-        .style('left', (newNode.x + _this.tree.textPosition + 5) + 'px')
+        .style('top', newNode.y + _this.tree.settings.marginTop + 'px')
+        .style('left', newNode.x + _this.tree.textPosition + 5 + 'px')
         .style('width', _this.tree.settings.width - (newNode.x + _this.tree.textPosition + 20) + 'px')
         .style('height', _this.tree.settings.nodeHeight + 'px')
         .attr('text', 'text')
@@ -763,7 +802,7 @@ define([
         .on('keydown', function () {
           var code = d3.event.keyCode;
 
-          if (code === 13 || code === 9) { //enter || tab
+          if (code === 13 || code === 9) { // enter || tab
             _this.tree.nodeIsEdit = false;
             var newName = this.value.trim();
 
@@ -774,7 +813,7 @@ define([
             } else {
               removeNode(newNode);
             }
-          } else if (code === 27) { //esc
+          } else if (code === 27) { // esc
             _this.tree.nodeIsEdit = false;
             removeNode(newNode);
           }
index 060ceec..705d94b 100644 (file)
@@ -204,16 +204,16 @@ define(['jquery',
       var _this = this;
       var name = $(input).val();
 
-      this.tree.nodes[0].open = false;
+      this.tree.nodes[0].expanded = false;
       this.tree.nodes.forEach(function (node) {
         var regex = new RegExp(name, 'i');
         if (regex.test(node.name) || regex.test(node.alias)) {
           _this.showParents(node);
-          node.open = true;
+          node.expanded = true;
           node.hidden = false;
         } else if (node.depth !== 0) {
           node.hidden = true;
-          node.open = false;
+          node.expanded = false;
         }
       });
 
@@ -235,11 +235,11 @@ define(['jquery',
         this.tree.nodes.forEach(function (node) {
           if (node.checked) {
             _this.showParents(node);
-            node.open = true;
+            node.expanded = true;
             node.hidden = false;
           } else {
             node.hidden = true;
-            node.open = false;
+            node.expanded = false;
           }
         });
       } else {
@@ -267,7 +267,7 @@ define(['jquery',
       parent.hidden = false;
 
       //expand parent node
-      parent.open = true;
+      parent.expanded = true;
       this.showParents(parent);
     };
 
index 6cce082..448fcfd 100644 (file)
@@ -37,6 +37,7 @@ define(
         showCheckboxes: false,
         showIcons: false,
         allowRecursiveDelete: false,
+        marginTop: 15,
         nodeHeight: 20,
         indentWidth: 16,
         width: 300,
@@ -283,29 +284,41 @@ define(
           }
 
           var nodes = Array.isArray(json) ? json : [];
-          _this.setParametersNode(nodes);
-          _this.dispatch.call('loadDataAfter', _this);
-          _this.prepareDataForVisibleNodes();
-          _this.nodesContainer.selectAll('.node').remove();
-          _this.nodesBgContainer.selectAll('.node-bg').remove();
-          _this.linksContainer.selectAll('.link').remove();
-          _this.update();
+          _this.replaceData(nodes);
           _this.nodesRemovePlaceholder();
         });
       },
 
       /**
-       * Set parameters like node parents, parentsUid, checked
+       * Delete old tree and create new one
+       *
+       * @param {Node[]} nodes
+       */
+      replaceData: function (nodes) {
+        var _this = this;
+
+        _this.setParametersNode(nodes);
+        _this.dispatch.call('loadDataAfter', _this);
+        _this.prepareDataForVisibleNodes();
+        _this.nodesContainer.selectAll('.node').remove();
+        _this.nodesBgContainer.selectAll('.node-bg').remove();
+        _this.linksContainer.selectAll('.link').remove();
+        _this.update();
+      },
+
+      /**
+       * Set parameters like node parents, parentsStateIdentifier, checked
        *
        * @param {Node[]} nodes
        */
       setParametersNode: function (nodes) {
         var _this = this;
 
+        nodes = nodes || this.nodes;
         nodes = nodes.map(function (node, index) {
-          node.open = (_this.settings.expandUpToLevel !== null) ? node.depth < _this.settings.expandUpToLevel : Boolean(node.expanded);
+          node.expanded = (_this.settings.expandUpToLevel !== null) ? node.depth < _this.settings.expandUpToLevel : Boolean(node.expanded);
           node.parents = [];
-          node.parentsUid = [];
+          node.parentsStateIdentifier = [];
           node._isDragged = false;
           if (node.depth > 0) {
             var currentDepth = node.depth;
@@ -313,12 +326,12 @@ define(
               var currentNode = nodes[i];
               if (currentNode.depth < currentDepth) {
                 node.parents.push(i);
-                node.parentsUid.push(nodes[i].stateIdentifier);
+                node.parentsStateIdentifier.push(nodes[i].stateIdentifier);
                 currentDepth = currentNode.depth;
               }
             }
           } else if (node.hasChildren) {
-            node.open = true;
+            node.expanded = true;
           }
 
           // create stateIdentifier if doesn't exist (for category tree)
@@ -347,7 +360,7 @@ define(
 
       nodesAddPlaceholder: function (node) {
         if (node) {
-          $('.svg-tree').find('.node-loader').css({ top: node.y + 15 }).show();
+          $('.svg-tree').find('.node-loader').css({ top: node.y + this.settings.marginTop }).show();
         } else {
           $('.svg-tree').find('.svg-tree-loader').show();
         }
@@ -363,7 +376,7 @@ define(
 
         var blacklist = {};
         this.nodes.map(function (node, index) {
-          if (!node.open) {
+          if (!node.expanded) {
             blacklist[index] = true;
           }
         });
@@ -424,7 +437,7 @@ define(
             icon: '',
           };
           Icons.getIcon(iconName, Icons.sizes.small, null, null, 'inline').done(function (icon) {
-            _this.data.icons[iconName].icon = icon.match(/<svg.*<\/svg>/im)[0];
+            _this.data.icons[iconName].icon = icon.match(/<svg.*<\/svg>/is)[0];
 
             if (update) {
               _this.update();
@@ -467,7 +480,7 @@ define(
           .attr('class', function (node, i) {
             return _this.getNodeBgClass(node, i, nodeBgClass);
           })
-          .attr('style', function (node, i) {
+          .attr('style', function (node) {
             return node.backgroundColor ? 'fill: ' + node.backgroundColor + ';' : '';
           });
 
@@ -486,6 +499,10 @@ define(
           .style('fill', this.getChevronColor)
           .attr('class', this.getChevronClass);
 
+        nodes
+          .select('.toggle')
+          .attr('visibility', this.getToggleVisibility);
+
         if (this.settings.showIcons) {
           nodes
             .select('use.node-icon')
@@ -661,7 +678,9 @@ define(
           .attr('class', 'toggle')
           .attr('visibility', this.getToggleVisibility)
           .attr('transform', 'translate(-8, -8)')
-          .on('click', _this.chevronClick.bind(this));
+          .on('click', function (node) {
+            _this.chevronClick(node);
+          });
 
         // improve usability by making the click area a 16px square
         chevron
@@ -864,7 +883,7 @@ define(
        * @returns {String}
        */
       getChevronTransform: function (node) {
-        return node.open ? 'translate(16,0) rotate(90)' : ' rotate(0)';
+        return node.expanded ? 'translate(16,0) rotate(90)' : ' rotate(0)';
       },
 
       /**
@@ -874,7 +893,7 @@ define(
        * @returns {String}
        */
       getChevronColor: function (node) {
-        return node.open ? '#000' : '#8e8e8e';
+        return node.expanded ? '#000' : '#8e8e8e';
       },
 
       /**
@@ -894,7 +913,7 @@ define(
        * @returns {String}
        */
       getChevronClass: function (node) {
-        return 'chevron ' + (node.open ? 'expanded' : 'collapsed');
+        return 'chevron ' + (node.expanded ? 'expanded' : 'collapsed');
       },
 
       /**
@@ -1066,7 +1085,7 @@ define(
        * @param {Node} node
        */
       chevronClick: function (node) {
-        if (node.open) {
+        if (node.expanded) {
           this.hideChildren(node);
         } else {
           this.showChildren(node);
@@ -1082,7 +1101,7 @@ define(
        * @param {Node} node
        */
       hideChildren: function (node) {
-        node.open = false;
+        node.expanded = false;
       },
 
       /**
@@ -1091,7 +1110,7 @@ define(
        * @param {Node} node
        */
       showChildren: function (node) {
-        node.open = true;
+        node.expanded = true;
       },
 
       /**