Roll src/third_party/WebKit 9f7fb92:f103b33 (svn 202621:202622)
[chromium-blink-merge.git] / components / sync_driver / resources / sync_node_browser.js
blob1232a4c23f1469ce4c0f2d515fd18dc4b8d08e1e
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.
5 // require: cr.js
6 // require: cr/ui.js
7 // require: cr/ui/tree.js
9 (function() {
10   /**
11    * A helper function to determine if a node is the root of its type.
12    *
13    * @param {!Object} node The node to check.
14    */
15   var isTypeRootNode = function(node) {
16     return node.PARENT_ID == 'r' && node.UNIQUE_SERVER_TAG != '';
17   };
19   /**
20    * A helper function to determine if a node is a child of the given parent.
21    *
22    * @param {!Object} parent node.
23    * @param {!Object} node The node to check.
24    */
25   var isChildOf = function(parentNode, node) {
26     if (node.PARENT_ID != '') {
27       return node.PARENT_ID == parentNode.ID;
28     }
29     else {
30       return node.modelType == parentNode.modelType;
31     }
32   };
34   /**
35    * A helper function to sort sync nodes.
36    *
37    * Sorts by position index if possible, falls back to sorting by name, and
38    * finally sorting by METAHANDLE.
39    *
40    * If this proves to be slow and expensive, we should experiment with moving
41    * this functionality to C++ instead.
42    */
43   var nodeComparator = function(nodeA, nodeB) {
44     if (nodeA.hasOwnProperty('positionIndex') &&
45         nodeB.hasOwnProperty('positionIndex')) {
46       return nodeA.positionIndex - nodeB.positionIndex;
47     } else if (nodeA.NON_UNIQUE_NAME != nodeB.NON_UNIQUE_NAME) {
48       return nodeA.NON_UNIQUE_NAME.localeCompare(nodeB.NON_UNIQUE_NAME);
49     } else {
50       return nodeA.METAHANDLE - nodeB.METAHANDLE;
51     }
52   };
54   /**
55    * Updates the node detail view with the details for the given node.
56    * @param {!Object} node The struct representing the node we want to display.
57    */
58   function updateNodeDetailView(node) {
59     var nodeDetailsView = $('node-details');
60     nodeDetailsView.hidden = false;
61     jstProcess(new JsEvalContext(node.entry_), nodeDetailsView);
62   }
64   /**
65    * Updates the 'Last refresh time' display.
66    * @param {string} The text to display.
67    */
68   function setLastRefreshTime(str) {
69     $('node-browser-refresh-time').textContent = str;
70   }
72   /**
73    * Creates a new sync node tree item.
74    *
75    * @constructor
76    * @param {!Object} node The nodeDetails object for the node as returned by
77    *     chrome.sync.getAllNodes().
78    * @extends {cr.ui.TreeItem}
79    */
80   var SyncNodeTreeItem = function(node) {
81     var treeItem = new cr.ui.TreeItem();
82     treeItem.__proto__ = SyncNodeTreeItem.prototype;
84     treeItem.entry_ = node;
85     treeItem.label = node.NON_UNIQUE_NAME;
86     if (node.IS_DIR) {
87       treeItem.mayHaveChildren_ = true;
89       // Load children on expand.
90       treeItem.expanded_ = false;
91       treeItem.addEventListener('expand',
92                                 treeItem.handleExpand_.bind(treeItem));
93     } else {
94       treeItem.classList.add('leaf');
95     }
96     return treeItem;
97   };
99   SyncNodeTreeItem.prototype = {
100     __proto__: cr.ui.TreeItem.prototype,
102     /**
103      * Finds the children of this node and appends them to the tree.
104      */
105     handleExpand_: function(event) {
106       var treeItem = this;
108       if (treeItem.expanded_) {
109         return;
110       }
111       treeItem.expanded_ = true;
113       var children = treeItem.tree.allNodes.filter(
114           isChildOf.bind(undefined, treeItem.entry_));
115       children.sort(nodeComparator);
117       children.forEach(function(node) {
118         treeItem.add(new SyncNodeTreeItem(node));
119       });
120     },
121   };
123   /**
124    * Creates a new sync node tree.  Technically, it's a forest since it each
125    * type has its own root node for its own tree, but it still looks and acts
126    * mostly like a tree.
127    *
128    * @param {Object=} opt_propertyBag Optional properties.
129    * @constructor
130    * @extends {cr.ui.Tree}
131    */
132   var SyncNodeTree = cr.ui.define('tree');
134   SyncNodeTree.prototype = {
135     __proto__: cr.ui.Tree.prototype,
137     decorate: function() {
138       cr.ui.Tree.prototype.decorate.call(this);
139       this.addEventListener('change', this.handleChange_.bind(this));
140       this.allNodes = [];
141     },
143     populate: function(nodes) {
144       var tree = this;
146       // We store the full set of nodes in the SyncNodeTree object.
147       tree.allNodes = nodes;
149       var roots = tree.allNodes.filter(isTypeRootNode);
150       roots.sort(nodeComparator);
152       roots.forEach(function(typeRoot) {
153         tree.add(new SyncNodeTreeItem(typeRoot));
154       });
155     },
157     handleChange_: function(event) {
158       if (this.selectedItem) {
159         updateNodeDetailView(this.selectedItem);
160       }
161     }
162   };
164   /**
165    * Clears any existing UI state.  Useful prior to a refresh.
166    */
167   function clear() {
168     var treeContainer = $('sync-node-tree-container');
169     while (treeContainer.firstChild) {
170       treeContainer.removeChild(treeContainer.firstChild);
171     }
173     var nodeDetailsView = $('node-details');
174     nodeDetailsView.hidden = true;
175   }
177   /**
178    * Fetch the latest set of nodes and refresh the UI.
179    */
180   function refresh() {
181     $('node-browser-refresh-button').disabled = true;
183     clear();
184     setLastRefreshTime('In progress since ' + (new Date()).toLocaleString());
186     chrome.sync.getAllNodes(function(nodeMap) {
187       // Put all nodes into one big list that ignores the type.
188       var nodes = nodeMap.
189           map(function(x) { return x.nodes; }).
190           reduce(function(a, b) { return a.concat(b); });
192       var treeContainer = $('sync-node-tree-container');
193       var tree = document.createElement('tree');
194       tree.setAttribute('id', 'sync-node-tree');
195       tree.setAttribute('icon-visibility', 'parent');
196       treeContainer.appendChild(tree);
198       cr.ui.decorate(tree, SyncNodeTree);
199       tree.populate(nodes);
201       setLastRefreshTime((new Date()).toLocaleString());
202       $('node-browser-refresh-button').disabled = false;
203     });
204   }
206   document.addEventListener('DOMContentLoaded', function(e) {
207     $('node-browser-refresh-button').addEventListener('click', refresh);
208     cr.ui.decorate('#sync-node-splitter', cr.ui.Splitter);
210     // Automatically trigger a refresh the first time this tab is selected.
211     $('sync-browser-tab').addEventListener('selectedChange', function f(e) {
212       if (this.selected) {
213         $('sync-browser-tab').removeEventListener('selectedChange', f);
214         refresh();
215       }
216     });
217   });
219 })();