1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 cr.define('bmm', function() {
10 * The id of the bookmark root.
16 /** @const */ var Tree = cr.ui.Tree;
17 /** @const */ var TreeItem = cr.ui.TreeItem;
18 /** @const */ var localStorage = window.localStorage;
22 // Manager for persisting the expanded state.
23 var expandedManager = /** @type {EventListener} */({
25 * A map of the collapsed IDs.
28 map: 'bookmarkTreeState' in localStorage ?
29 /** @type {Object} */(JSON.parse(localStorage['bookmarkTreeState'])) :
33 * Set the collapsed state for an ID.
34 * @param {string} id The bookmark ID of the tree item that was expanded or
36 * @param {boolean} expanded Whether the tree item was expanded.
38 set: function(id, expanded) {
48 * @param {string} id The bookmark ID.
49 * @return {boolean} Whether the tree item should be expanded.
52 return !(id in this.map);
56 * Callback for the expand and collapse events from the tree.
57 * @param {!Event} e The collapse or expand event.
59 handleEvent: function(e) {
60 this.set(e.target.bookmarkId, e.type == 'expand');
64 * Cleans up old bookmark IDs.
67 for (var id in this.map) {
68 // If the id is no longer in the treeLookup the bookmark no longer
70 if (!(id in treeLookup))
79 * Saves the expanded state to the localStorage.
82 clearTimeout(this.timer);
84 // Save in a timeout so that we can coalesce multiple changes.
85 this.timer = setTimeout(function() {
86 localStorage['bookmarkTreeState'] = JSON.stringify(map);
91 // Clean up once per session but wait until things settle down a bit.
92 setTimeout(expandedManager.cleanUp.bind(expandedManager), 1e4);
95 * Creates a new tree item for a bookmark node.
96 * @param {!Object} bookmarkNode The bookmark node.
100 function BookmarkTreeItem(bookmarkNode) {
101 var ti = new TreeItem({
102 label: bookmarkNode.title,
103 bookmarkNode: bookmarkNode,
104 // Bookmark toolbar and Other bookmarks are not draggable.
105 draggable: bookmarkNode.parentId != ROOT_ID
107 ti.__proto__ = BookmarkTreeItem.prototype;
108 treeLookup[bookmarkNode.id] = ti;
112 BookmarkTreeItem.prototype = {
113 __proto__: TreeItem.prototype,
116 * The ID of the bookmark this tree item represents.
120 return this.bookmarkNode.id;
125 * Asynchronousy adds a tree item at the correct index based on the bookmark
128 * Since the bookmark tree only contains folders the index we get from certain
129 * callbacks is not very useful so we therefore have this async call which
130 * gets the children of the parent and adds the tree item at the desired
133 * This also exoands the parent so that newly added children are revealed.
135 * @param {!cr.ui.TreeItem} parent The parent tree item.
136 * @param {!cr.ui.TreeItem} treeItem The tree item to add.
137 * @param {Function=} opt_f A function which gets called after the item has
138 * been added at the right index.
140 function addTreeItem(parent, treeItem, opt_f) {
141 chrome.bookmarks.getChildren(parent.bookmarkNode.id, function(children) {
143 * @type {function (BookmarkTreeNode, number,
144 * Array<(BookmarkTreeNode)>)}
146 var index = children.filter(isFolder).map(function(item) {
148 }).indexOf(treeItem.bookmarkNode.id);
149 parent.addAt(treeItem, index);
150 parent.expanded = true;
158 * Creates a new bookmark list.
159 * @param {Object=} opt_propertyBag Optional properties.
161 * @extends {cr.ui.Tree}
163 var BookmarkTree = cr.ui.define('tree');
165 BookmarkTree.prototype = {
166 __proto__: Tree.prototype,
168 decorate: function() {
169 Tree.prototype.decorate.call(this);
170 this.addEventListener('expand', expandedManager);
171 this.addEventListener('collapse', expandedManager);
176 handleBookmarkChanged: function(id, changeInfo) {
177 var treeItem = treeLookup[id];
179 treeItem.label = treeItem.bookmarkNode.title = changeInfo.title;
184 * @param {ReorderInfo} reorderInfo
186 handleChildrenReordered: function(id, reorderInfo) {
187 var parentItem = treeLookup[id];
188 // The tree only contains folders.
189 var dirIds = reorderInfo.childIds.filter(function(id) {
190 return id in treeLookup;
191 }).forEach(function(id, i) {
192 parentItem.addAt(treeLookup[id], i);
196 handleCreated: function(id, bookmarkNode) {
197 if (bmm.isFolder(bookmarkNode)) {
198 var parentItem = treeLookup[bookmarkNode.parentId];
199 var newItem = new BookmarkTreeItem(bookmarkNode);
200 addTreeItem(parentItem, newItem);
206 * @param {MoveInfo} moveInfo
208 handleMoved: function(id, moveInfo) {
209 var treeItem = treeLookup[id];
211 var oldParentItem = treeLookup[moveInfo.oldParentId];
212 oldParentItem.remove(treeItem);
213 var newParentItem = treeLookup[moveInfo.parentId];
214 // The tree only shows folders so the index is not the index we want. We
215 // therefore get the children need to adjust the index.
216 addTreeItem(newParentItem, treeItem);
220 handleRemoved: function(id, removeInfo) {
221 var parentItem = treeLookup[removeInfo.parentId];
222 var itemToRemove = treeLookup[id];
223 if (parentItem && itemToRemove)
224 parentItem.remove(itemToRemove);
227 insertSubtree: function(folder) {
228 if (!bmm.isFolder(folder))
230 var children = folder.children;
231 this.handleCreated(folder.id, folder);
232 for (var i = 0; i < children.length; i++) {
233 var child = children[i];
234 this.insertSubtree(child);
239 * Returns the bookmark node with the given ID. The tree only maintains
241 * @param {string} id The ID of the node to find.
242 * @return {BookmarkTreeNode} The bookmark tree node or null if not found.
244 getBookmarkNodeById: function(id) {
245 var treeItem = treeLookup[id];
247 return treeItem.bookmarkNode;
252 * Returns the selected bookmark folder node as an array.
253 * @type {!Array} Array of bookmark nodes.
255 get selectedFolders() {
256 return this.selectedItem && this.selectedItem.bookmarkNode ?
257 [this.selectedItem.bookmarkNode] : [];
261 * Fetches the bookmark items and builds the tree control.
265 * Recursive helper function that adds all the directories to the
267 * @param {!cr.ui.Tree|!cr.ui.TreeItem} parentTreeItem The parent tree
268 * element to append to.
269 * @param {!Array<BookmarkTreeNode>} bookmarkNodes A list of bookmark
271 * @return {boolean} Whether any directories where added.
273 function buildTreeItems(parentTreeItem, bookmarkNodes) {
274 var hasDirectories = false;
275 for (var i = 0, bookmarkNode; bookmarkNode = bookmarkNodes[i]; i++) {
276 if (bmm.isFolder(bookmarkNode)) {
277 hasDirectories = true;
278 var item = new BookmarkTreeItem(bookmarkNode);
279 parentTreeItem.add(item);
280 var children = assert(bookmarkNode.children);
281 var anyChildren = buildTreeItems(item, children);
282 item.expanded = anyChildren && expandedManager.get(bookmarkNode.id);
285 return hasDirectories;
289 chrome.bookmarkManagerPrivate.getSubtree('', true, function(root) {
291 buildTreeItems(self, root[0].children);
292 cr.dispatchSimpleEvent(self, 'load');
300 // Remove all fields without recreating the object since other code
302 for (var id in treeLookup) {
303 delete treeLookup[id];
305 this.textContent = '';
309 remove: function(child) {
310 Tree.prototype.remove.call(this, child);
311 if (child.bookmarkNode)
312 delete treeLookup[child.bookmarkNode.id];
317 BookmarkTree: BookmarkTree,
318 BookmarkTreeItem: BookmarkTreeItem,
319 treeLookup: treeLookup,
320 tree: /** @type {Element} */(null), // Set when decorated.