diff --git a/admin/javascript/LeftAndMain.Tree.js b/admin/javascript/LeftAndMain.Tree.js index eabaf1b98..0404dc792 100644 --- a/admin/javascript/LeftAndMain.Tree.js +++ b/admin/javascript/LeftAndMain.Tree.js @@ -253,11 +253,19 @@ parentNode = data.ParentID ? self.getNodeByID(data.ParentID) : false, newNode = $(html); + // Extract the state for the new node from the properties taken from the provided HTML template. + // This will correctly initialise the behaviour of the node for ajax loading of children. + var properties = {data: ''}; + if(newNode.hasClass('jstree-open')) { + properties.state = 'open'; + } else if(newNode.hasClass('jstree-closed')) { + properties.state = 'closed'; + } this.jstree( 'create_node', parentNode.length ? parentNode : -1, 'last', - '', + properties, function(node) { var origClasses = node.attr('class'); // Copy attributes @@ -265,6 +273,7 @@ var attr = newNode[0].attributes[i]; node.attr(attr.name, attr.value); } + // Substitute html from request for that generated by jstree node.addClass(origClasses).html(newNode.html()); callback(node); } @@ -356,13 +365,10 @@ // Duplicates can be caused by the subtree reloading through // a tree "open"/"select" event, while at the same time creating a new node self.getNodeByID(node.data('id')).not(node).remove(); - + + // Select this node self.jstree('deselect_all'); self.jstree('select_node', node); - // Similar to jstree's correct_state, but doesn't remove children - var hasChildren = (node.children('ul').length > 0); - node.toggleClass('jstree-leaf', !hasChildren); - if(!hasChildren) node.removeClass('jstree-closed jstree-open'); }; // TODO 'initially_opened' config doesn't apply here @@ -387,7 +393,7 @@ if(node.length) { self.updateNode(node, nodeData.html, nodeData); setTimeout(function() { - correctStateFn(node) ; + correctStateFn(node); }, 500); } else { includesNewNode = true; diff --git a/model/Hierarchy.php b/model/Hierarchy.php index 4f5b80b8b..3f20c1de9 100644 --- a/model/Hierarchy.php +++ b/model/Hierarchy.php @@ -275,12 +275,12 @@ class Hierarchy extends DataExtension { if($children) { foreach($children as $child) { $markingMatches = $this->markingFilterMatches($child); - // Filtered results should always show opened, since actual matches - // might be hidden by non-matching parent nodes. - if($this->markingFilter && $markingMatches) { - $child->markOpened(); - } - if(!$this->markingFilter || $markingMatches) { + if($markingMatches) { + // Filtered results should always show opened, since actual matches + // might be hidden by non-matching parent nodes. + if($this->markingFilter) { + $child->markOpened(); + } if($child->$numChildrenMethod()) { $child->markUnexpanded(); } else { @@ -310,18 +310,24 @@ class Hierarchy extends DataExtension { } /** - * Return CSS classes of 'unexpanded', 'closed', both, or neither, depending on - * the marking of this DataObject. + * Return CSS classes of 'unexpanded', 'closed', both, or neither, as well as a + * 'jstree-*' state depending on the marking of this DataObject. + * + * @return string */ public function markingClasses() { $classes = ''; if(!$this->isExpanded()) { - $classes .= " unexpanded jstree-closed"; + $classes .= " unexpanded"; } - if($this->isTreeOpened()) { - if($this->numChildren() > 0) $classes .= " jstree-open"; + + // Set jstree open state, or mark it as a leaf (closed) if there are no children + if(!$this->numChildren()) { + $classes .= " jstree-leaf closed"; + } elseif($this->isTreeOpened()) { + $classes .= " jstree-open"; } else { - $classes .= " closed"; + $classes .= " jstree-closed closed"; } return $classes; } diff --git a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php index 7f49a6c57..de80aa3f4 100644 --- a/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php +++ b/tests/behat/features/bootstrap/SilverStripe/Framework/Test/Behaviour/CmsUiContext.php @@ -180,13 +180,65 @@ class CmsUiContext extends BehatContext { } /** - * @When /^I click on "([^"]*)" in the tree$/ + * Applies a specific action to an element + * + * @param NodeElement $element Element to act on + * @param string $action Action, which may be one of 'hover', 'double click', 'right click', or 'left click' + * The default 'click' behaves the same as left click */ - public function stepIClickOnElementInTheTree($text) { + protected function interactWithElement($element, $action = 'click') { + switch($action) { + case 'hover': + $element->mouseOver(); + break; + case 'double click': + $element->doubleClick(); + break; + case 'right click': + $element->rightClick(); + break; + case 'left click': + case 'click': + default: + $element->click(); + break; + } + + } + + /** + * @When /^I (?P(?:(?:double |right |left |)click)|hover) on "(?P[^"]*)" in the context menu/ + */ + public function stepIClickOnElementInTheContextMenu($method, $link) { + $context = $this->getMainContext(); + // Wait until context menu has appeared + $this->getSession()->wait( + 1000, + "window.jQuery && window.jQuery('.jstree-apple-context').size() > 0" + ); + $regionObj = $context->getRegionObj('.jstree-apple-context'); + assertNotNull($regionObj, "Context menu could not be found"); + + $linkObj = $regionObj->findLink($link); + if (empty($linkObj)) { + throw new \Exception(sprintf( + 'The link "%s" was not found in the context menu on the page %s', + $link, + $this->getSession()->getCurrentUrl() + )); + } + + $this->interactWithElement($linkObj, $method); + } + + /** + * @When /^I (?P(?:(?:double |right |left |)click)|hover) on "(?P[^"]*)" in the tree$/ + */ + public function stepIClickOnElementInTheTree($method, $text) { $treeEl = $this->getCmsTreeElement(); $treeNode = $treeEl->findLink($text); assertNotNull($treeNode, sprintf('%s not found', $text)); - $treeNode->click(); + $this->interactWithElement($treeNode, $method); } /**