OLD | NEW |
| (Empty) |
1 // Copyright 2013 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. | |
4 | |
5 | |
6 cr.define('bmm', function() { | |
7 /** | |
8 * The id of the bookmark root. | |
9 * @type {string} | |
10 * @const | |
11 */ | |
12 var ROOT_ID = '0'; | |
13 | |
14 /** @const */ var Tree = cr.ui.Tree; | |
15 /** @const */ var TreeItem = cr.ui.TreeItem; | |
16 | |
17 var treeLookup = {}; | |
18 var tree; | |
19 | |
20 // Manager for persisting the expanded state. | |
21 var expandedManager = { | |
22 /** | |
23 * A map of the collapsed IDs. | |
24 * @type {Object} | |
25 */ | |
26 map: 'bookmarkTreeState' in localStorage ? | |
27 JSON.parse(localStorage['bookmarkTreeState']) : {}, | |
28 | |
29 /** | |
30 * Set the collapsed state for an ID. | |
31 * @param {string} The bookmark ID of the tree item that was expanded or | |
32 * collapsed. | |
33 * @param {boolean} expanded Whether the tree item was expanded. | |
34 */ | |
35 set: function(id, expanded) { | |
36 if (expanded) | |
37 delete this.map[id]; | |
38 else | |
39 this.map[id] = 1; | |
40 | |
41 this.save(); | |
42 }, | |
43 | |
44 /** | |
45 * @param {string} id The bookmark ID. | |
46 * @return {boolean} Whether the tree item should be expanded. | |
47 */ | |
48 get: function(id) { | |
49 return !(id in this.map); | |
50 }, | |
51 | |
52 /** | |
53 * Callback for the expand and collapse events from the tree. | |
54 * @param {!Event} e The collapse or expand event. | |
55 */ | |
56 handleEvent: function(e) { | |
57 this.set(e.target.bookmarkId, e.type == 'expand'); | |
58 }, | |
59 | |
60 /** | |
61 * Cleans up old bookmark IDs. | |
62 */ | |
63 cleanUp: function() { | |
64 for (var id in this.map) { | |
65 // If the id is no longer in the treeLookup the bookmark no longer | |
66 // exists. | |
67 if (!(id in treeLookup)) | |
68 delete this.map[id]; | |
69 } | |
70 this.save(); | |
71 }, | |
72 | |
73 timer: null, | |
74 | |
75 /** | |
76 * Saves the expanded state to the localStorage. | |
77 */ | |
78 save: function() { | |
79 clearTimeout(this.timer); | |
80 var map = this.map; | |
81 // Save in a timeout so that we can coalesce multiple changes. | |
82 this.timer = setTimeout(function() { | |
83 localStorage['bookmarkTreeState'] = JSON.stringify(map); | |
84 }, 100); | |
85 } | |
86 }; | |
87 | |
88 // Clean up once per session but wait until things settle down a bit. | |
89 setTimeout(expandedManager.cleanUp.bind(expandedManager), 1e4); | |
90 | |
91 /** | |
92 * Creates a new tree item for a bookmark node. | |
93 * @param {!Object} bookmarkNode The bookmark node. | |
94 * @constructor | |
95 * @extends {TreeItem} | |
96 */ | |
97 function BookmarkTreeItem(bookmarkNode) { | |
98 var ti = new TreeItem({ | |
99 label: bookmarkNode.title, | |
100 bookmarkNode: bookmarkNode, | |
101 // Bookmark toolbar and Other bookmarks are not draggable. | |
102 draggable: bookmarkNode.parentId != ROOT_ID | |
103 }); | |
104 ti.__proto__ = BookmarkTreeItem.prototype; | |
105 treeLookup[bookmarkNode.id] = ti; | |
106 return ti; | |
107 } | |
108 | |
109 BookmarkTreeItem.prototype = { | |
110 __proto__: TreeItem.prototype, | |
111 | |
112 /** | |
113 * The ID of the bookmark this tree item represents. | |
114 * @type {string} | |
115 */ | |
116 get bookmarkId() { | |
117 return this.bookmarkNode.id; | |
118 } | |
119 }; | |
120 | |
121 /** | |
122 * Asynchronousy adds a tree item at the correct index based on the bookmark | |
123 * backend. | |
124 * | |
125 * Since the bookmark tree only contains folders the index we get from certain | |
126 * callbacks is not very useful so we therefore have this async call which | |
127 * gets the children of the parent and adds the tree item at the desired | |
128 * index. | |
129 * | |
130 * This also exoands the parent so that newly added children are revealed. | |
131 * | |
132 * @param {!cr.ui.TreeItem} parent The parent tree item. | |
133 * @param {!cr.ui.TreeItem} treeItem The tree item to add. | |
134 * @param {Function=} f A function which gets called after the item has been | |
135 * added at the right index. | |
136 */ | |
137 function addTreeItem(parent, treeItem, opt_f) { | |
138 chrome.bookmarks.getChildren(parent.bookmarkNode.id, function(children) { | |
139 var index = children.filter(bmm.isFolder).map(function(item) { | |
140 return item.id; | |
141 }).indexOf(treeItem.bookmarkNode.id); | |
142 parent.addAt(treeItem, index); | |
143 parent.expanded = true; | |
144 if (opt_f) | |
145 opt_f(); | |
146 }); | |
147 } | |
148 | |
149 | |
150 /** | |
151 * Creates a new bookmark list. | |
152 * @param {Object=} opt_propertyBag Optional properties. | |
153 * @constructor | |
154 * @extends {HTMLButtonElement} | |
155 */ | |
156 var BookmarkTree = cr.ui.define('tree'); | |
157 | |
158 BookmarkTree.prototype = { | |
159 __proto__: Tree.prototype, | |
160 | |
161 decorate: function() { | |
162 Tree.prototype.decorate.call(this); | |
163 this.addEventListener('expand', expandedManager); | |
164 this.addEventListener('collapse', expandedManager); | |
165 | |
166 bmm.tree = this; | |
167 }, | |
168 | |
169 handleBookmarkChanged: function(id, changeInfo) { | |
170 var treeItem = treeLookup[id]; | |
171 if (treeItem) | |
172 treeItem.label = treeItem.bookmarkNode.title = changeInfo.title; | |
173 }, | |
174 | |
175 handleChildrenReordered: function(id, reorderInfo) { | |
176 var parentItem = treeLookup[id]; | |
177 // The tree only contains folders. | |
178 var dirIds = reorderInfo.childIds.filter(function(id) { | |
179 return id in treeLookup; | |
180 }).forEach(function(id, i) { | |
181 parentItem.addAt(treeLookup[id], i); | |
182 }); | |
183 }, | |
184 | |
185 handleCreated: function(id, bookmarkNode) { | |
186 if (bmm.isFolder(bookmarkNode)) { | |
187 var parentItem = treeLookup[bookmarkNode.parentId]; | |
188 var newItem = new BookmarkTreeItem(bookmarkNode); | |
189 addTreeItem(parentItem, newItem); | |
190 } | |
191 }, | |
192 | |
193 handleMoved: function(id, moveInfo) { | |
194 var treeItem = treeLookup[id]; | |
195 if (treeItem) { | |
196 var oldParentItem = treeLookup[moveInfo.oldParentId]; | |
197 oldParentItem.remove(treeItem); | |
198 var newParentItem = treeLookup[moveInfo.parentId]; | |
199 // The tree only shows folders so the index is not the index we want. We | |
200 // therefore get the children need to adjust the index. | |
201 addTreeItem(newParentItem, treeItem); | |
202 } | |
203 }, | |
204 | |
205 handleRemoved: function(id, removeInfo) { | |
206 var parentItem = treeLookup[removeInfo.parentId]; | |
207 var itemToRemove = treeLookup[id]; | |
208 if (parentItem && itemToRemove) | |
209 parentItem.remove(itemToRemove); | |
210 }, | |
211 | |
212 insertSubtree: function(folder) { | |
213 if (!bmm.isFolder(folder)) | |
214 return; | |
215 var children = folder.children; | |
216 this.handleCreated(folder.id, folder); | |
217 for (var i = 0; i < children.length; i++) { | |
218 var child = children[i]; | |
219 this.insertSubtree(child); | |
220 } | |
221 }, | |
222 | |
223 /** | |
224 * Returns the bookmark node with the given ID. The tree only maintains | |
225 * folder nodes. | |
226 * @param {string} id The ID of the node to find. | |
227 * @return {BookmarkTreeNode} The bookmark tree node or null if not found. | |
228 */ | |
229 getBookmarkNodeById: function(id) { | |
230 var treeItem = treeLookup[id]; | |
231 if (treeItem) | |
232 return treeItem.bookmarkNode; | |
233 return null; | |
234 }, | |
235 | |
236 /** | |
237 * Returns the selected bookmark folder node as an array. | |
238 * @type {!Array} Array of bookmark nodes. | |
239 */ | |
240 get selectedFolders() { | |
241 return this.selectedItem && this.selectedItem.bookmarkNode ? | |
242 [this.selectedItem.bookmarkNode] : []; | |
243 }, | |
244 | |
245 /** | |
246 * Fetches the bookmark items and builds the tree control. | |
247 */ | |
248 reload: function() { | |
249 /** | |
250 * Recursive helper function that adds all the directories to the | |
251 * parentTreeItem. | |
252 * @param {!cr.ui.Tree|!cr.ui.TreeItem} parentTreeItem The parent tree | |
253 * element to append to. | |
254 * @param {!Array.<BookmarkTreeNode>} bookmarkNodes A list of bookmark | |
255 * nodes to be added. | |
256 * @return {boolean} Whether any directories where added. | |
257 */ | |
258 function buildTreeItems(parentTreeItem, bookmarkNodes) { | |
259 var hasDirectories = false; | |
260 for (var i = 0, bookmarkNode; bookmarkNode = bookmarkNodes[i]; i++) { | |
261 if (bmm.isFolder(bookmarkNode)) { | |
262 hasDirectories = true; | |
263 var item = new BookmarkTreeItem(bookmarkNode); | |
264 parentTreeItem.add(item); | |
265 var anyChildren = buildTreeItems(item, bookmarkNode.children); | |
266 item.expanded = anyChildren && expandedManager.get(bookmarkNode.id); | |
267 } | |
268 } | |
269 return hasDirectories; | |
270 } | |
271 | |
272 var self = this; | |
273 chrome.bookmarkManagerPrivate.getSubtree('', true, function(root) { | |
274 self.clear(); | |
275 buildTreeItems(self, root[0].children); | |
276 cr.dispatchSimpleEvent(self, 'load'); | |
277 }); | |
278 }, | |
279 | |
280 /** | |
281 * Clears the tree. | |
282 */ | |
283 clear: function() { | |
284 // Remove all fields without recreating the object since other code | |
285 // references it. | |
286 for (var id in treeLookup) { | |
287 delete treeLookup[id]; | |
288 } | |
289 this.textContent = ''; | |
290 }, | |
291 | |
292 /** @override */ | |
293 remove: function(child) { | |
294 Tree.prototype.remove.call(this, child); | |
295 if (child.bookmarkNode) | |
296 delete treeLookup[child.bookmarkNode.id]; | |
297 } | |
298 }; | |
299 | |
300 return { | |
301 BookmarkTree: BookmarkTree, | |
302 BookmarkTreeItem: BookmarkTreeItem, | |
303 treeLookup: treeLookup, | |
304 tree: tree, | |
305 ROOT_ID: ROOT_ID | |
306 }; | |
307 }); | |
OLD | NEW |