1c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Use of this source code is governed by a BSD-style license that can be
3c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// found in the LICENSE file.
4c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
5c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochcr.define('cr.ui', function() {
6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // require cr.ui.define
7c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // require cr.ui.limitInputWidth
8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
9c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
10c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * The number of pixels to indent per level.
11c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @type {number}
12c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
13c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const INDENT = 20;
14c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
15c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
16c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * Returns the computed style for an element.
17c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @param {!Element} el The element to get the computed style for.
18c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @return {!CSSStyleDeclaration} The computed style.
19c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  function getComputedStyle(el) {
21c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return el.ownerDocument.defaultView.getComputedStyle(el);
22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
23c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
24c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
25c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * Helper function that finds the first ancestor tree item.
26c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @param {!Element} el The element to start searching from.
27c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @return {cr.ui.TreeItem} The found tree item or null if not found.
28c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
29c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  function findTreeItem(el) {
30c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    while (el && !(el instanceof TreeItem)) {
31c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      el = el.parentNode;
32c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
33c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return el;
34c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
35c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
36c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
37c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * Creates a new tree element.
38c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @param {Object=} opt_propertyBag Optional properties.
39c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @constructor
40c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @extends {HTMLElement}
41c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
42c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  var Tree = cr.ui.define('tree');
43c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
44c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  Tree.prototype = {
45c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    __proto__: HTMLElement.prototype,
46c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
47c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
48c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Initializes the element.
49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
50c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    decorate: function() {
51c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // Make list focusable
52c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (!this.hasAttribute('tabindex'))
53c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.tabIndex = 0;
54c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
55c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.addEventListener('click', this.handleClick);
56c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.addEventListener('mousedown', this.handleMouseDown);
57c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.addEventListener('dblclick', this.handleDblClick);
58c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.addEventListener('keydown', this.handleKeyDown);
59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
60c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
61c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
62c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Returns the tree item that are children of this tree.
63c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
64c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get items() {
65c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.children;
66c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
67c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
68c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
69c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Adds a tree item to the tree.
70c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {!cr.ui.TreeItem} treeItem The item to add.
71c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
72c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    add: function(treeItem) {
73c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.addAt(treeItem, 0xffffffff);
74c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
75c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
76c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
77c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Adds a tree item at the given index.
78c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {!cr.ui.TreeItem} treeItem The item to add.
79c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {number} index The index where we want to add the item.
80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
81c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    addAt: function(treeItem, index) {
82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.insertBefore(treeItem, this.children[index]);
83c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      treeItem.setDepth_(this.depth + 1);
84c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
85c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
86c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
87c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Removes a tree item child.
88c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {!cr.ui.TreeItem} treeItem The tree item to remove.
89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
90c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    remove: function(treeItem) {
91c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.removeChild(treeItem);
92c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
93c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
94c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
95c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The depth of the node. This is 0 for the tree itself.
96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {number}
97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get depth() {
99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return 0;
100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Handles click events on the tree and forwards the event to the relevant
104c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * tree items as necesary.
105c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {Event} e The click event object.
106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    handleClick: function(e) {
108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var treeItem = findTreeItem(e.target);
109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (treeItem)
110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        treeItem.handleClick(e);
111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
112c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
113c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    handleMouseDown: function(e) {
114c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (e.button == 2) // right
115c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.handleClick(e);
116c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Handles double click events on the tree.
120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {Event} e The dblclick event object.
121c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
122c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    handleDblClick: function(e) {
123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var treeItem = findTreeItem(e.target);
124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (treeItem)
125c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        treeItem.expanded = !treeItem.expanded;
126c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
127c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
128c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Handles keydown events on the tree and updates selection and exanding
130c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * of tree items.
131c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {Event} e The click event object.
132c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    handleKeyDown: function(e) {
134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var itemToSelect;
135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (e.ctrlKey)
136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        return;
137c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var item = this.selectedItem;
139c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var rtl = getComputedStyle(item).direction == 'rtl';
141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
142c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      switch (e.keyIdentifier) {
143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'Up':
144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          itemToSelect = item ? getPrevious(item) :
145c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              this.items[this.items.length - 1];
146c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          break;
147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'Down':
148c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          itemToSelect = item ? getNext(item) :
149c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              this.items[0];
150c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          break;
151c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'Left':
152c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'Right':
153c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          // Don't let back/forward keyboard shortcuts be used.
154c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          if (!cr.isMac && e.altKey || cr.isMac && e.metaKey)
155c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            break;
156c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
157c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          if (e.keyIdentifier == 'Left' && !rtl ||
158c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              e.keyIdentifier == 'Right' && rtl) {
159c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            if (item.expanded)
160c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              item.expanded = false;
161c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            else
162c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              itemToSelect = findTreeItem(item.parentNode);
163c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          } else {
164c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            if (!item.expanded)
165c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              item.expanded = true;
166c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            else
167c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              itemToSelect = item.items[0];
168c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          }
169c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          break;
170c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'Home':
171c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          itemToSelect = this.items[0];
172c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          break;
173c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'End':
174c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          itemToSelect = this.items[this.items.length - 1];
175c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          break;
176c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
177c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
178c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (itemToSelect) {
179c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        itemToSelect.selected = true;
180c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        e.preventDefault();
181c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
182c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
183c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
184c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
185c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The selected tree item or null if none.
186c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {cr.ui.TreeItem}
187c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
188c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get selectedItem() {
189c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.selectedItem_ || null;
190c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
191c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    set selectedItem(item) {
192c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var oldSelectedItem = this.selectedItem_;
193c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (oldSelectedItem != item) {
194c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // Set the selectedItem_ before deselecting the old item since we only
195c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // want one change when moving between items.
196c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.selectedItem_ = item;
197c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
198c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (oldSelectedItem)
199c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          oldSelectedItem.selected = false;
200c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
201c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (item)
202c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          item.selected = true;
203c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
204c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        cr.dispatchSimpleEvent(this, 'change');
205c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
206c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
207c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
208c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
209c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @return {!ClientRect} The rect to use for the context menu.
210c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
211c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    getRectForContextMenu: function() {
212c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // TODO(arv): Add trait support so we can share more code between trees
213c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // and lists.
214c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (this.selectedItem)
215c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        return this.selectedItem.rowElement.getBoundingClientRect();
216c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.getBoundingClientRect();
217c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
218c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  };
219c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
220c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
221c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * This is used as a blueprint for new tree item elements.
222c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @type {!HTMLElement}
223c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
224c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  var treeItemProto = (function() {
225c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    var treeItem = cr.doc.createElement('div');
226c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    treeItem.className = 'tree-item';
227c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    treeItem.innerHTML = '<div class=tree-row>' +
228c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        '<span class=expand-icon></span>' +
229c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        '<span class=tree-label></span>' +
230c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        '</div>' +
231c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        '<div class=tree-children></div>';
232c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return treeItem;
233c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  })();
234c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
235c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
236c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * Creates a new tree item.
237c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @param {Object=} opt_propertyBag Optional properties.
238c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @constructor
239c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @extends {HTMLElement}
240c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
241c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  var TreeItem = cr.ui.define(function() {
242c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return treeItemProto.cloneNode(true);
243c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  });
244c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
245c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  TreeItem.prototype = {
246c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    __proto__: HTMLElement.prototype,
247c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
248c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
249c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Initializes the element.
250c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
251c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    decorate: function() {
252c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
253c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
254c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
255c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
256c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The tree items children.
257c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
258c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get items() {
259c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.lastElementChild.children;
260c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
261c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
262c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
263c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The depth of the tree item.
264c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {number}
265c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
266c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    depth_: 0,
267c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get depth() {
268c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.depth_;
269c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
270c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
271c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
272c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Sets the depth.
273c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {number} depth The new depth.
274c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @private
275c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
276c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    setDepth_: function(depth) {
277c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (depth != this.depth_) {
278c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.rowElement.style.WebkitPaddingStart = Math.max(0, depth - 1) *
279c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            INDENT + 'px';
280c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.depth_ = depth;
281c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        var items = this.items;
282c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        for (var i = 0, item; item = items[i]; i++) {
283c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          item.setDepth_(depth + 1);
284c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        }
285c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
286c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
287c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
288c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
289c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Adds a tree item as a child.
290c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {!cr.ui.TreeItem} child The child to add.
291c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
292c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    add: function(child) {
293c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.addAt(child, 0xffffffff);
294c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
295c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
296c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
297c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Adds a tree item as a child at a given index.
298c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {!cr.ui.TreeItem} child The child to add.
299c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {number} index The index where to add the child.
300c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
301c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    addAt: function(child, index) {
302c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.lastElementChild.insertBefore(child, this.items[index]);
303c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (this.items.length == 1)
304731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick        this.hasChildren = true;
305c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      child.setDepth_(this.depth + 1);
306c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
307c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
308c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
309c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Removes a child.
310c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {!cr.ui.TreeItem} child The tree item child to remove.
311c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
312c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    remove: function(child) {
313c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // If we removed the selected item we should become selected.
314c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var tree = this.tree;
315c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var selectedItem = tree.selectedItem;
316c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (selectedItem && child.contains(selectedItem))
317c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.selected = true;
318c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
319c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.lastElementChild.removeChild(child);
320c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (this.items.length == 0)
321731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick        this.hasChildren = false;
322c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
323c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
324c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
325c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The parent tree item.
326c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {!cr.ui.Tree|cr.ui.TreeItem}
327c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
328c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get parentItem() {
329c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var p = this.parentNode;
330c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      while (p && !(p instanceof TreeItem) && !(p instanceof Tree)) {
331c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        p = p.parentNode;
332c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
333c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return p;
334c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
335c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
336c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
337c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The tree that the tree item belongs to or null of no added to a tree.
338c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {cr.ui.Tree}
339c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
340c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get tree() {
341c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var t = this.parentItem;
342c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      while (t && !(t instanceof Tree)) {
343c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        t = t.parentItem;
344c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
345c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return t;
346c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
347c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
348c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
349c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Whether the tree item is expanded or not.
350c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {boolean}
351c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
352c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get expanded() {
353c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.hasAttribute('expanded');
354c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
355c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    set expanded(b) {
356c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (this.expanded == b)
357c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        return;
358c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
359c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var treeChildren = this.lastElementChild;
360c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
361c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (b) {
362c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (this.mayHaveChildren_) {
363c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          this.setAttribute('expanded', '');
364c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          treeChildren.setAttribute('expanded', '');
365c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          cr.dispatchSimpleEvent(this, 'expand', true);
366c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          this.scrollIntoViewIfNeeded(false);
367c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        }
368c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      } else {
369c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        var tree = this.tree;
370c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (tree && !this.selected) {
371c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          var oldSelected = tree.selectedItem;
372c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          if (oldSelected && this.contains(oldSelected))
373c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            this.selected = true;
374c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        }
375c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.removeAttribute('expanded');
376c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        treeChildren.removeAttribute('expanded');
377c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        cr.dispatchSimpleEvent(this, 'collapse', true);
378c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
379c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
380c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
381c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
382c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Expands all parent items.
383c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
384c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    reveal: function() {
385c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var pi = this.parentItem;
386c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      while (pi && !(pi instanceof Tree)) {
387c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        pi.expanded = true;
388c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        pi = pi.parentItem;
389c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
390c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
391c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
392c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
393c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The element representing the row that gets highlighted.
394c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {!HTMLElement}
395c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
396c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get rowElement() {
397c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.firstElementChild;
398c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
399c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
400c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
401c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The element containing the label text and the icon.
402c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {!HTMLElement}
403c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
404c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get labelElement() {
405c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.firstElementChild.lastElementChild;
406c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
407c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
408c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
409c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The label text.
410c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {string}
411c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
412c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get label() {
413c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.labelElement.textContent;
414c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
415c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    set label(s) {
416c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.labelElement.textContent = s;
417c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
418c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
419c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
420c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The URL for the icon.
421c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {string}
422c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
423c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get icon() {
424c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return getComputedStyle(this.labelElement).backgroundImage.slice(4, -1);
425c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
426c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    set icon(icon) {
427c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.labelElement.style.backgroundImage = url(icon);
428c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
429c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
430c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
431c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Whether the tree item is selected or not.
432c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {boolean}
433c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
434c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get selected() {
435c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.hasAttribute('selected');
436c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
437c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    set selected(b) {
438c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (this.selected == b)
439c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        return;
440c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var rowItem = this.firstElementChild;
441c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var tree = this.tree;
442c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (b) {
443c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.setAttribute('selected', '');
444c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        rowItem.setAttribute('selected', '');
445c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.reveal();
446c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.labelElement.scrollIntoViewIfNeeded(false);
447c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (tree)
448c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          tree.selectedItem = this;
449c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      } else {
450c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.removeAttribute('selected');
451c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        rowItem.removeAttribute('selected');
452c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (tree && tree.selectedItem == this)
453c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          tree.selectedItem = null;
454c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
455c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
456c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
457c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
458c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Whether the tree item has children.
459c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {boolean}
460c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
461c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get mayHaveChildren_() {
462c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.hasAttribute('may-have-children');
463c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
464c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    set mayHaveChildren_(b) {
465c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var rowItem = this.firstElementChild;
466c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (b) {
467c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.setAttribute('may-have-children', '');
468c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        rowItem.setAttribute('may-have-children', '');
469c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      } else {
470c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.removeAttribute('may-have-children');
471c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        rowItem.removeAttribute('may-have-children');
472c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
473c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
474c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
475c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
476c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Whether the tree item has children.
477c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {boolean}
478c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
479c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get hasChildren() {
480c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return !!this.items[0];
481c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
482c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
483c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
484c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Whether the tree item has children.
485c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {boolean}
486c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
487731df977c0511bca2206b5f333555b1205ff1f43Iain Merrick    set hasChildren(b) {
488c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var rowItem = this.firstElementChild;
489c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.setAttribute('has-children', b);
490c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      rowItem.setAttribute('has-children', b);
491c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (b)
492c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.mayHaveChildren_ = true;
493c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
494c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
495c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
496c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Called when the user clicks on a tree item. This is forwarded from the
497c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * cr.ui.Tree.
498c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {Event} e The click event.
499c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
500c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    handleClick: function(e) {
501c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (e.target.className == 'expand-icon')
502c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.expanded = !this.expanded;
503c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      else
504c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.selected = true;
505c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
506c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
507c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
508c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Makes the tree item user editable. If the user renamed the item a
509c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * bubbling {@code rename} event is fired.
510c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {boolean}
511c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
512c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    set editing(editing) {
513c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var oldEditing = this.editing;
514c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (editing == oldEditing)
515c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        return;
516c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
517c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var self = this;
518c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var labelEl = this.labelElement;
519c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var text = this.label;
520c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var input;
521c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
522c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // Handles enter and escape which trigger reset and commit respectively.
523c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      function handleKeydown(e) {
524c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // Make sure that the tree does not handle the key.
525c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        e.stopPropagation();
526c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
527c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // Calling tree.focus blurs the input which will make the tree item
528c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // non editable.
529c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        switch (e.keyIdentifier) {
530c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          case 'U+001B':  // Esc
531c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            input.value = text;
532c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            // fall through
533c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          case 'Enter':
534c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            self.tree.focus();
535c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        }
536c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
537c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
538c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      function stopPropagation(e) {
539c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        e.stopPropagation();
540c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
541c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
542c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (editing) {
543c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.selected = true;
544c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.setAttribute('editing', '');
545c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.draggable = false;
546c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
547c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // We create an input[type=text] and copy over the label value. When
548c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // the input loses focus we set editing to false again.
549c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        input = this.ownerDocument.createElement('input');
550c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        input.value = text;
551c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (labelEl.firstChild)
552c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          labelEl.replaceChild(input, labelEl.firstChild);
553c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        else
554c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          labelEl.appendChild(input);
555c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
556c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        input.addEventListener('keydown', handleKeydown);
5573345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick        input.addEventListener('blur', (function() {
558c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          this.editing = false;
5593345a6884c488ff3a535c2c9acdd33d74b37e311Iain Merrick        }).bind(this));
560c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
561c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // Make sure that double clicks do not expand and collapse the tree
562c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // item.
563c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        var eventsToStop = ['mousedown', 'mouseup', 'contextmenu', 'dblclick'];
564c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        eventsToStop.forEach(function(type) {
565c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          input.addEventListener(type, stopPropagation);
566c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        });
567c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
568c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        // Wait for the input element to recieve focus before sizing it.
569c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        var rowElement = this.rowElement;
570c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        function onFocus() {
571c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          input.removeEventListener('focus', onFocus);
572c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          // 20 = the padding and border of the tree-row
573c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          cr.ui.limitInputWidth(input, rowElement, 20);
574c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        }
575c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        input.addEventListener('focus', onFocus);
576c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        input.focus();
577c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        input.select();
578c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
579c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.oldLabel_ = text;
580c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      } else {
581c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.removeAttribute('editing');
582c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.draggable = true;
583c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        input = labelEl.firstChild;
584c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        var value = input.value;
585c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (/^\s*$/.test(value)) {
586c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          labelEl.textContent = this.oldLabel_;
587c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        } else {
588c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          labelEl.textContent = value;
589c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          if (value != this.oldLabel_) {
590c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            cr.dispatchSimpleEvent(this, 'rename', true);
591c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          }
592c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        }
593c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        delete this.oldLabel_;
594c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
595c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
596c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
597c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get editing() {
598c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.hasAttribute('editing');
599c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
600c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  };
601c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
602c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
603c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * Helper function that returns the next visible tree item.
604c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @param {cr.ui.TreeItem} item The tree item.
605c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @retrun {cr.ui.TreeItem} The found item or null.
606c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
607c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  function getNext(item) {
608c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (item.expanded) {
609c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var firstChild = item.items[0];
610c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (firstChild) {
611c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        return firstChild;
612c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
613c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
614c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
615c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return getNextHelper(item);
616c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
617c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
618c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
619c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * Another helper function that returns the next visible tree item.
620c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @param {cr.ui.TreeItem} item The tree item.
621c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @retrun {cr.ui.TreeItem} The found item or null.
622c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
623c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  function getNextHelper(item) {
624c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (!item)
625c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return null;
626c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
627c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    var nextSibling = item.nextElementSibling;
628c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (nextSibling) {
629c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return nextSibling;
630c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
631c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return getNextHelper(item.parentItem);
632c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
633c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
634c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
635c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * Helper function that returns the previous visible tree item.
636c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @param {cr.ui.TreeItem} item The tree item.
637c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @retrun {cr.ui.TreeItem} The found item or null.
638c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
639c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  function getPrevious(item) {
640c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    var previousSibling = item.previousElementSibling;
641c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return previousSibling ? getLastHelper(previousSibling) : item.parentItem;
642c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
643c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
644c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
645c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * Helper function that returns the last visible tree item in the subtree.
646c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @param {cr.ui.TreeItem} item The item to find the last visible item for.
647c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @return {cr.ui.TreeItem} The found item or null.
648c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
649c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  function getLastHelper(item) {
650c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (!item)
651c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return null;
652c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    if (item.expanded && item.hasChildren) {
653c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var lastChild = item.items[item.items.length - 1];
654c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return getLastHelper(lastChild);
655c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
656c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    return item;
657c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  }
658c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
659c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Export
660c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return {
661c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    Tree: Tree,
662c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    TreeItem: TreeItem
663c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  };
664c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch});
665