Index: theme/standard/styles_layout.css
===================================================================
RCS file: /cvsroot/moodle/moodle/theme/standard/styles_layout.css,v
retrieving revision 1.698
diff -u -r1.698 styles_layout.css
--- theme/standard/styles_layout.css	9 Jul 2009 07:35:05 -0000	1.698
+++ theme/standard/styles_layout.css	13 Jul 2009 23:43:19 -0000
@@ -1674,6 +1674,24 @@
   margin-right:4px;
 }
 
+.block_tree, .block_tree ul {
+  padding-left: 16px;
+  list-style: none;
+}
+.block_tree .tree_item {
+  padding-left: 16px;
+  margin-left: -16px;
+}
+.block_tree .tree_item.parent {
+    background: url(../../pix/t/expanded.png) center left no-repeat;
+}
+.block_tree .collapsed .tree_item.parent {
+  background-image: url(../../pix/t/collapsed.png);
+}
+.block_tree .collapsed ul {
+  display: none;
+}
+
 .blockconfigtable {
   margin-top: 0;
   margin-right: auto;
Index: lib/javascript-static.js
===================================================================
RCS file: /cvsroot/moodle/moodle/lib/javascript-static.js,v
retrieving revision 1.76
diff -u -r1.76 javascript-static.js
--- lib/javascript-static.js	9 Jul 2009 07:35:07 -0000	1.76
+++ lib/javascript-static.js	13 Jul 2009 23:43:18 -0000
@@ -702,18 +702,45 @@
             fn();
     }
 }
-
-function getElementsByClassName(oElm, strTagName, oClassNames) {
+/**
+ * Replacement for getElementsByClassName in browsers that aren't cool enough
+ * 
+ * Relying on the built-in getElementsByClassName is far, far faster than
+ * using YUI.
+ * 
+ * Note: the third argument used to be an object with odd behaviour. It now
+ * acts like the 'name' in the HTML5 spec, though the old behaviour is still
+ * mimicked if you pass an object.
+ *
+ * @param {Node} oElm The top-level node for searching. To search a whole
+ *                    document, use `document`.
+ * @param {String} strTagName filter by tag names
+ * @param {String} name same as HTML5 spec
+ */
+function getElementsByClassName(oElm, strTagName, name) {
+    // for backwards compatibility
+    if(typeof name == "object") {
+        var names = new Array();
+        for(var i=0; i<name.length; i++) names.push(names[i]);
+        name = names.join('');
+    }
+    // use native implementation if possible
+    if (oElm.getElementsByClassName && Array.filter) {
+        if (strTagName == '*') {
+            return oElm.getElementsByClassName(name);
+        } else {
+            return Array.filter(oElm.getElementsByClassName(name), function(el) {
+                return el.nodeName.toLowerCase() == strTagName.toLowerCase();
+            });
+        }
+    }
+    // native implementation unavailable, fall back to slow method
     var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
     var arrReturnElements = new Array();
     var arrRegExpClassNames = new Array();
-    if(typeof oClassNames == "object") {
-        for(var i=0; i<oClassNames.length; i++) {
-            arrRegExpClassNames.push(new RegExp("(^|\\s)" + oClassNames[i].replace(/\-/g, "\\-") + "(\\s|$)"));
-        }
-    }
-    else{
-        arrRegExpClassNames.push(new RegExp("(^|\\s)" + oClassNames.replace(/\-/g, "\\-") + "(\\s|$)"));
+    var names = name.split(' ');
+    for(var i=0; i<names.length; i++) {
+        arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
     }
     var oElement;
     var bMatchesAll;
@@ -1084,3 +1111,31 @@
 function create_UFO_object(eid) {
     UFO.create(FO, eid);
 }
+
+/**
+ * Adds the collapse event handler to objects of the right clas
+ * 
+ * @see block_tree_item_toggle
+ */
+function block_tree_setup() {
+    var items = getElementsByClassName(document, '*', 'tree_item parent');
+    for (var i = 0; i<items.length; i++) {
+        YAHOO.util.Event.addListener(items[i], 'click', block_tree_item_toggle);
+    }
+}
+
+/**
+ * Event handler for list items, toggles their collapse state
+ * 
+ * @return boolean
+ */
+function block_tree_item_toggle() {
+   node = this.parentNode;
+   if (YAHOO.util.Dom.hasClass(node, 'collapsed')) {
+       YAHOO.util.Dom.removeClass(node, 'collapsed')
+   } else {
+       YAHOO.util.Dom.addClass(node, 'collapsed')
+   }
+   /* disable any other event handlers.*/
+   return false;
+}
\ No newline at end of file
Index: lib/outputlib.php
===================================================================
RCS file: /cvsroot/moodle/moodle/lib/outputlib.php,v
retrieving revision 1.32
diff -u -r1.32 outputlib.php
--- lib/outputlib.php	13 Jul 2009 20:33:56 -0000	1.32
+++ lib/outputlib.php	13 Jul 2009 23:43:19 -0000
@@ -2106,6 +2106,47 @@
         }
         return $this->output_tag('ul', array('class' => 'list'), implode("\n", $lis));
     }
+    /**
+     * Make nested HTML lists out of the items
+     * 
+     * The resulting list will look something like this:
+     *
+     * <pre> 
+     * <<ul>>
+     * <<li>><div class='tree_item parent'>(item contents)</div>
+     *      <<ul>
+     *      <<li>><div class='tree_item'>(item contents)</div><</li>>
+     *      <</ul>>
+     * <</li>>
+     * <</ul>>
+     * </pre>
+     *
+     * @param array[]tree_item $items
+     * @param array[string]string $attrs html attributes passed to the top of
+     * the list
+     * @return string HTML
+     */
+    function tree_block_contents($items, $attrs=array()) {
+        // exit if empty, we don't want an empty ul element
+        if (empty($items)) return '';
+        // array of nested li elements
+        $lis = array();
+        
+        foreach ($items as $item) {
+            // this applies to the li item which contains all child lists too
+            $liattr = $item->collapse ? array('class'=>'collapsed') : null;
+            // class attribute on the div item which only contains the item content
+            $class = 'tree_item';
+            if (!empty($item->children)) $class .= ' parent';
+            if (!empty($item->classname)) $class .= ' ' . $item->classname;
+            
+            $lis[] = $this->output_tag('li', $liattr,
+                $this->output_tag('div', array('class' => $class), $item->content($this)) .
+                $this->tree_block_contents($item->children)
+            );
+        }
+        return $this->output_tag('ul', $attrs, implode("\n", $lis));
+    }
 
     /**
      * Output all the blocks in a particular region.
Index: blocks/moodleblock.class.php
===================================================================
RCS file: /cvsroot/moodle/moodle/blocks/moodleblock.class.php,v
retrieving revision 1.121
diff -u -r1.121 moodleblock.class.php
--- blocks/moodleblock.class.php	13 Jul 2009 08:37:34 -0000	1.121
+++ blocks/moodleblock.class.php	13 Jul 2009 23:43:18 -0000
@@ -1,4 +1,4 @@
-<?php  // $Id: moodleblock.class.php,v 1.121 2009/07/13 08:37:34 tjhunt Exp $
+<?php
 
 /**
  * This file contains the parent class for moodle blocks, block_base.
@@ -18,6 +18,10 @@
  * Block type of text. Contents of block should be set to standard html text in the content object as items ($this->content->text). Optionally include footer text in $this->content->footer.
  */
 define('BLOCK_TYPE_TEXT',    2);
+/**
+ * Block type of tree. $this->content->items is a list of tree_item objects and $this->content->footer is a string.
+ */
+define('BLOCK_TYPE_TREE',    3);
 
 /**
  * Class for describing a moodle block, all Moodle blocks derive from this class
@@ -476,7 +480,7 @@
             $errors[] = 'title_not_set';
             $correct = false;
         }
-        if (!in_array($this->get_content_type(), array(BLOCK_TYPE_LIST, BLOCK_TYPE_TEXT))) {
+        if (!in_array($this->get_content_type(), array(BLOCK_TYPE_LIST, BLOCK_TYPE_TEXT, BLOCK_TYPE_TREE))) {
             $errors[] = 'invalid_content_type';
             $correct = false;
         }
@@ -797,4 +801,228 @@
     }
 }
 
-?>
+/**
+ * Specialized class for displaying a tree menu.
+ * 
+ * The {@link get_content()} method involves setting the content of
+ * <code>$this->content->items</code> with an array of {@link tree_item}
+ * objects (these are the top-level nodes). The {@link tree_item::children}
+ * property may contain more tree_item objects, and so on. The tree_item class
+ * itself is abstract and not intended for use, use one of it's subclasses.
+ * 
+ * Unlike {@link block_list}, the icons are specified as part of the items,
+ * not in a separate array.
+ *
+ * @author Alan Trick
+ * @package blocks
+ * @internal this extends block_list so we get is_empty() for free
+ */
+class block_tree extends block_list {
+    
+    /**
+     * @var int specifies the manner in which contents should be added to this
+     * block type. In this case <code>$this->content->items</code> is used with
+     * {@link tree_item}s.
+     */
+    public $content_type = BLOCK_TYPE_TREE;
+    
+    /**
+     * Make the formatted HTML ouput.
+     * 
+     * Also adds the required javascript call to the page output.
+     *
+     * @param moodle_core_renderer $output
+     * @return string HTML
+     */
+    protected function formatted_contents($output) {
+        // based of code in admin_tree
+        global $PAGE; // TODO change this when there is a proper way for blocks to get stuff into head.
+        $PAGE->requires->js_function_call('block_tree_setup')->on_dom_ready();
+        
+        $this->get_content();
+        return $output->tree_block_contents($this->content->items,
+            array('class'=>'block_tree'));
+    }
+}
+
+/**
+ * Base abstract class for tree items.
+ * 
+ * This is not intendent for actuall use, it just provides the barebones of
+ * the tree_item classes. You will want to use on of it's subclasses.
+ * 
+ * Each different type of tree item has it's own class. This makes it easier
+ * to cover up implementation details that are irrelevant to the block
+ * creators.
+ * 
+ * @author Alan Trick
+ * @package blocks
+ */
+abstract class tree_item {
+    
+    /**
+     * @var boolean setting this will toggle the initial state of the item
+     */
+    public $collapse = false;
+    /**
+     * @var array[]tree_item this item's child nodes
+     */
+    public $children;
+    /**
+     * @var array[]string class name added on output (this is appended to
+     * the classname "tree_item" which is always include). This can be useful
+     * if a particular style is wanted on a particular list item.
+     */
+    public $classname;
+    /**
+     * Base constructor
+     * 
+     * Please call this when overriding it.
+     */
+    function __construct() {
+        $this->children = array();
+    }
+    /**
+     * Render the content in the list item, not including sub-lists
+     * 
+     * @param moodle_renderer_base $render
+     * @return string probably HTML, depends on the renderer 
+     */
+    abstract function content($render);
+}
+
+/**
+ * A tree item with plain text content
+ */
+class tree_item_text extends tree_item {
+    
+    /**
+     * @var string
+     */
+    protected $text;
+    
+    /**
+     * A plain text-only item
+     * 
+     * @param string $text plain text (will be escaped)
+     */
+    function __construct($text) {
+        $this->text = $text;
+        parent::__construct();
+    }
+    /**
+     * @see tree_item::content()
+     */
+    function content($render) {
+        return s($this->text);
+    }
+}
+/**
+ * A tree item with plain text content that links to somewhere 
+ */
+class tree_item_link extends tree_item_text {
+    
+    /**
+     * @var string
+     */
+    protected $href;
+    /**
+     * @var string
+     */
+    protected $title;
+    
+    /**
+     * An item that contains a link (and possibly a title, aka hover text)
+     * 
+     * @param string $text plain text (will be escaped)
+     * @param string $url fully qualified URL
+     * @param string $title used for title attribute (optional)
+     */
+    function __construct($text, $href, $title = null) {
+        $this->href = $href;
+        if (isset($title)) $this->title = $title;
+        parent::__construct($text);
+    }
+    /**
+     * @see tree_item::content()
+     */
+    function content($render) {
+        /*
+         * TODO: this would be nice, but output_tag is protected (MDL-19777)
+        $attrs = array();
+        $attrs['href'] = s($this->href);
+        if (isset($this->title)) {
+            $attrs['title'] = s($this->title);
+        }
+        return $render->output_tag('a', $attrs, s($this->text)); */
+        
+        $title = isset($this->title) ? " title='".s($this->title)."'" : '';
+        return sprintf("<a href='%s'%s>%s</a>", s($this->href), $title, s($this->text));
+    }
+}
+
+/**
+ * This class includes an icon and plain text
+ * 
+ * The output format is very similar to that of the block_list items.
+ */
+class tree_item_icon_text extends tree_item_text {
+    
+    /**
+     * @var string
+     */
+    protected $ico;
+    
+    /**
+     * @param string $ico icon path. Something like 'i/edit'. The same as
+     * what is used by {@link moodle_renderer_base::old_icon_url}
+     * @param string $text plain text (will be escaped)
+     */
+    function __construct($ico, $text) {
+        $this->ico = $ico;
+        parent::__construct($text);
+    }
+    /**
+     * @see tree_item::content()
+     */
+    function content($render) {
+        $link = parent::content($render);
+        return sprintf("<div class='icon column c0'><img src='%s' alt=''>" .
+            "</div><div class='column c1'>%s</div>",
+            s($render->old_icon_url($this->ico)), $link);
+    }
+}
+/**
+ * This class includes an icon and a link
+ * 
+ * The output format is very similar to that of the block_list items.
+ */
+class tree_item_icon_link extends tree_item_link {
+    
+    /**
+     * @var string
+     */
+    protected $ico;
+    
+    /**
+     * @param string $ico icon path. Something like 'i/edit'. The same as
+     * what is used by {@link moodle_renderer_base::old_icon_url}
+     * @param string $text plain text (will be escaped)
+     * @param string $url fully qualified URL
+     * @param string $title used for title attribute (optional)
+     */
+    function __construct($ico, $text, $href, $title = null) {
+        $this->ico = $ico;
+        parent::__construct($text, $href, $title);
+    }
+    /**
+     * @see tree_item::content()
+     */
+    function content($render) {
+        $link = parent::content($render);
+        return sprintf("<div class='icon column c0'><img src='%s' alt=''>" .
+            "</div><div class='column c1'>%s</div>",
+            s($render->old_icon_url($this->ico)), $link);
+    }
+}
+
Index: blocks/tree_test/block_tree_test.php
===================================================================
RCS file: blocks/tree_test/block_tree_test.php
diff -N blocks/tree_test/block_tree_test.php
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ blocks/tree_test/block_tree_test.php	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,28 @@
+<?php
+
+class block_tree_test extends block_tree {
+
+    function init() {
+        $this->title = 'block_tree test';
+        $this->version = 2009262400;
+    }
+
+    function get_content() {
+        global $CFG;
+        
+        $item = new tree_item_text('Hello World');
+        
+        $child = new tree_item_text('Don\'t look down');
+        $child->children[] = new tree_item_icon_text('i/questions', 'You looked down!');
+        $item->children[] = $child;
+        
+        $child = new tree_item_link('Moodle.org', 'http://moodle.org');
+        $item->children[] = $child;
+        
+        $child = new tree_item_icon_link('i/edit', 'I\'m running out of things to say', 'http://moodle.org');
+        $child->children[] = new tree_item_text('You can\'t collapse leaf nodes');
+        $item->children[] = $child;
+        
+        $this->content->items = array($item);
+    }
+}
