// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // require: cr.js // require: cr/ui.js // require: cr/ui/tree.js cr.define('chrome.sync', function() { /** * Gets all children of the given node and passes it to the given * callback. * @param {string} id The id whose children we want. * @param {function(Array.)} callback The callback to call * with the list of children summaries. */ function getSyncNodeChildrenSummaries(id, callback) { var timer = chrome.sync.makeTimer(); chrome.sync.getChildNodeIds(id, function(childNodeIds) { console.debug('getChildNodeIds took ' + timer.elapsedSeconds + 's to retrieve ' + childNodeIds.length + ' ids'); timer = chrome.sync.makeTimer(); chrome.sync.getNodeSummariesById( childNodeIds, function(childrenSummaries) { console.debug('getNodeSummariesById took ' + timer.elapsedSeconds + 's to retrieve summaries for ' + childrenSummaries.length + ' nodes'); callback(childrenSummaries); }); }); } /** * Creates a new sync node tree item. * @param {{id: string, title: string, isFolder: boolean}} * nodeSummary The nodeSummary object for the node (as returned * by chrome.sync.getNodeSummariesById()). * @constructor * @extends {cr.ui.TreeItem} */ var SyncNodeTreeItem = function(nodeSummary) { var treeItem = new cr.ui.TreeItem({ id_: nodeSummary.id }); treeItem.__proto__ = SyncNodeTreeItem.prototype; treeItem.label = nodeSummary.title; if (nodeSummary.isFolder) { treeItem.mayHaveChildren_ = true; // Load children asynchronously on expand. // TODO(akalin): Add a throbber while loading? treeItem.triggeredLoad_ = false; treeItem.addEventListener('expand', treeItem.handleExpand_.bind(treeItem)); } else { treeItem.classList.add('leaf'); } return treeItem; }; SyncNodeTreeItem.prototype = { __proto__: cr.ui.TreeItem.prototype, /** * Retrieves the details for this node. * @param {function(Object)} callback The callback that will be * called with the node details, or null if it could not be * retrieved. */ getDetails: function(callback) { chrome.sync.getNodeDetailsById([this.id_], function(nodeDetails) { callback(nodeDetails[0] || null); }); }, handleExpand_: function(event) { if (!this.triggeredLoad_) { getSyncNodeChildrenSummaries(this.id_, this.addChildNodes_.bind(this)); this.triggeredLoad_ = true; } }, /** * Adds children from the list of children summaries. * @param {Array.<{id: string, title: string, isFolder: boolean}>} * childrenSummaries The list of children summaries with which * to create the child nodes. */ addChildNodes_: function(childrenSummaries) { var timer = chrome.sync.makeTimer(); for (var i = 0; i < childrenSummaries.length; ++i) { var childTreeItem = new SyncNodeTreeItem(childrenSummaries[i]); this.add(childTreeItem); } console.debug('adding ' + childrenSummaries.length + ' children took ' + timer.elapsedSeconds + 's'); } }; /** * Updates the node detail view with the details for the given node. * @param {!Object} nodeDetails The details for the node we want * to display. */ function updateNodeDetailView(nodeDetails) { var nodeBrowser = document.getElementById('node-browser'); // TODO(akalin): Write a nicer detail viewer. nodeDetails.entry = JSON.stringify(nodeDetails.entry, null, 2); jstProcess(new JsEvalContext(nodeDetails), nodeBrowser); } /** * Creates a new sync node tree. * @param {Object=} opt_propertyBag Optional properties. * @constructor * @extends {cr.ui.Tree} */ var SyncNodeTree = cr.ui.define('tree'); SyncNodeTree.prototype = { __proto__: cr.ui.Tree.prototype, decorate: function() { cr.ui.Tree.prototype.decorate.call(this); this.addEventListener('change', this.handleChange_.bind(this)); chrome.sync.getRootNodeDetails(this.makeRoot_.bind(this)); }, /** * Creates the root of the tree. * @param {{id: string, title: string, isFolder: boolean}} * rootNodeSummary The summary info for the root node. */ makeRoot_: function(rootNodeSummary) { // The root node usually doesn't have a title. rootNodeSummary.title = rootNodeSummary.title || 'Root'; var rootTreeItem = new SyncNodeTreeItem(rootNodeSummary); this.add(rootTreeItem); }, handleChange_: function(event) { if (this.selectedItem) { this.selectedItem.getDetails(updateNodeDetailView); } } }; function decorateSyncNodeBrowser(syncNodeBrowser) { cr.ui.decorate(syncNodeBrowser, SyncNodeTree); } // This is needed because JsTemplate (which is needed by // updateNodeDetailView) is loaded at the end of the file after // everything else. // // TODO(akalin): Remove dependency on JsTemplate and get rid of // this. var domLoaded = false; var pendingSyncNodeBrowsers = []; function decorateSyncNodeBrowserAfterDOMLoad(id) { var e = document.getElementById(id); if (domLoaded) { decorateSyncNodeBrowser(e); } else { pendingSyncNodeBrowsers.push(e); } } document.addEventListener('DOMContentLoaded', function() { for (var i = 0; i < pendingSyncNodeBrowsers.length; ++i) { decorateSyncNodeBrowser(pendingSyncNodeBrowsers[i]); } domLoaded = true; }); return { decorateSyncNodeBrowser: decorateSyncNodeBrowserAfterDOMLoad }; });