diff --git a/lib/navigationlib.php b/lib/navigationlib.php index 1e4be902921..bd8e9aae7ef 100644 --- a/lib/navigationlib.php +++ b/lib/navigationlib.php @@ -237,8 +237,8 @@ class navigation_node implements renderable { } /** - * Adds a navigation node as a child of this node. - * + * Creates a navigation node, ready to add it as a child using add_node + * function. (The created node needs to be added before you can use it.) * @param string $text * @param moodle_url|action_link $action * @param int $type @@ -247,11 +247,8 @@ class navigation_node implements renderable { * @param pix_icon $icon * @return navigation_node */ - public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) { - // First convert the nodetype for this node to a branch as it will now have children - if ($this->nodetype !== self::NODETYPE_BRANCH) { - $this->nodetype = self::NODETYPE_BRANCH; - } + public static function create($text, $action=null, $type=self::TYPE_CUSTOM, + $shorttext=null, $key=null, pix_icon $icon=null) { // Properties array used when creating the new navigation node $itemarray = array( 'text' => $text, @@ -269,18 +266,58 @@ class navigation_node implements renderable { if ($icon!==null) { $itemarray['icon'] = $icon; } - // Default the key to the number of children if not provided - if ($key === null) { - $key = $this->children->count(); - } // Set the key $itemarray['key'] = $key; + // Construct and return + return new navigation_node($itemarray); + } + + /** + * Adds a navigation node as a child of this node. + * + * @param string $text + * @param moodle_url|action_link $action + * @param int $type + * @param string $shorttext + * @param string|int $key + * @param pix_icon $icon + * @return navigation_node + */ + public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) { + // Create child node + $childnode = self::create($text, $action, $type, $shorttext, $key, $icon); + + // Add the child to end and return + return $this->add_node($childnode); + } + + /** + * Adds a navigation node as a child of this one, given a $node object + * created using the create function. + * @param navigation_node $childnode Node to add + * @param int|string $key The key of a node to add this before. If not + * specified, adds at end of list + * @return navigation_node The added node + */ + public function add_node(navigation_node $childnode, $beforekey=null) { + // First convert the nodetype for this node to a branch as it will now have children + if ($this->nodetype !== self::NODETYPE_BRANCH) { + $this->nodetype = self::NODETYPE_BRANCH; + } // Set the parent to this node - $itemarray['parent'] = $this; + $childnode->parent = $this; + + // Default the key to the number of children if not provided + if ($childnode->key === null) { + $childnode->key = $this->children->count(); + } + // Add the child using the navigation_node_collections add method - $node = $this->children->add(new navigation_node($itemarray)); - // If the node is a category node or the user is logged in and its a course - // then mark this node as a branch (makes it expandable by AJAX) + $node = $this->children->add($childnode, $beforekey); + + // If added node is a category node or the user is logged in and it's a course + // then mark added node as a branch (makes it expandable by AJAX) + $type = $childnode->type; if (($type==self::TYPE_CATEGORY) || (isloggedin() && $type==self::TYPE_COURSE)) { $node->nodetype = self::NODETYPE_BRANCH; } @@ -288,7 +325,7 @@ class navigation_node implements renderable { if ($this->hidden) { $node->hidden = true; } - // Return the node (reference returned by $this->children->add() + // Return added node (reference returned by $this->children->add() return $node; } @@ -647,13 +684,16 @@ class navigation_node_collection implements IteratorAggregate { /** * Adds a navigation node to the collection * - * @param navigation_node $node - * @return navigation_node + * @param navigation_node $node Node to add + * @param string $beforekey If specified, adds before a node with this key, + * otherwise adds at end + * @return navigation_node Added node */ - public function add(navigation_node $node) { + public function add(navigation_node $node, $beforekey=null) { global $CFG; $key = $node->key; $type = $node->type; + // First check we have a 2nd dimension for this type if (!array_key_exists($type, $this->orderedcollection)) { $this->orderedcollection[$type] = array(); @@ -662,15 +702,50 @@ class navigation_node_collection implements IteratorAggregate { if ($CFG->debug && array_key_exists($key, $this->orderedcollection[$type])) { debugging('Navigation node intersect: Adding a node that already exists '.$key, DEBUG_DEVELOPER); } - // Add the node to the appropriate place in the ordered structure. + + // Find the key to add before + $newindex = $this->count; + $last = true; + if ($beforekey !== null) { + foreach ($this->collection as $index => $othernode) { + if ($othernode->key === $beforekey) { + $newindex = $index; + $last = false; + break; + } + } + if ($newindex === $this->count) { + $allkeys = ''; + foreach ($this->collection as $othernode) { + $allkeys .= ' ' . $othernode->key; + } + debugging('Navigation node add_before: Reference node not found ' . $beforekey . + ', options: ' . $allkeys, DEBUG_DEVELOPER); + } + } + + // Add the node to the appropriate place in the by-type structure (which + // is not ordered, despite the variable name) $this->orderedcollection[$type][$key] = $node; + if (!$last) { + // Update existing references in the ordered collection (which is the + // one that isn't called 'ordered') to shuffle them along if required + for ($oldindex = $this->count; $oldindex > $newindex; $oldindex--) { + $this->collection[$oldindex] = $this->collection[$oldindex - 1]; + } + } // Add a reference to the node to the progressive collection. - $this->collection[$this->count] = &$this->orderedcollection[$type][$key]; - // Update the last property to a reference to this new node. - $this->last = &$this->orderedcollection[$type][$key]; + $this->collection[$newindex] = &$this->orderedcollection[$type][$key]; + // Reorder the array by index if needed + if (!$last) { + ksort($this->collection); + } else { + // Update the last property to a reference to this new node. + $this->last = &$this->orderedcollection[$type][$key]; + } $this->count++; // Return the reference to the now added node - return $this->last; + return $node; } /** diff --git a/lib/simpletest/testnavigationlib.php b/lib/simpletest/testnavigationlib.php index 808ca53e6da..b75d5ccff9d 100644 --- a/lib/simpletest/testnavigationlib.php +++ b/lib/simpletest/testnavigationlib.php @@ -98,6 +98,28 @@ class navigation_node_test extends UnitTestCase { $this->assertReference($node3, $this->node->get($node3->key, $node3->type)); } + public function test_add_before() { + global $CFG; + // Create 3 nodes + $node1 = navigation_node::create('test_add_1', null, navigation_node::TYPE_CUSTOM, + 'test 1', 'testadd1'); + $node2 = navigation_node::create('test_add_2', null, navigation_node::TYPE_CUSTOM, + 'test 2', 'testadd2'); + $node3 = navigation_node::create('test_add_3', null, navigation_node::TYPE_CUSTOM, + 'test 3', 'testadd3'); + // Add node 2, then node 1 before 2, then node 3 at end + $this->node->add_node($node2); + $this->node->add_node($node1, 'testadd2'); + $this->node->add_node($node3); + // Check the last 3 nodes are in 1, 2, 3 order and have those indexes + foreach($this->node->children as $child) { + $keys[] = $child->key; + } + $this->assertEqual('testadd1', $keys[count($keys)-3]); + $this->assertEqual('testadd2', $keys[count($keys)-2]); + $this->assertEqual('testadd3', $keys[count($keys)-1]); + } + public function test_add_class() { $node = $this->node->get('demo1'); $this->assertIsA($node, 'navigation_node');