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 * This is a view class showing tree-menu.
7 * @param {Object} profiler Must have addListener method.
8 * @construct
9 */
10var MenuView = function(profiler) {
11  this.profiler_ = profiler;
12  this.placeholder_ = '#category-menu';
13
14  // Update graph view and menu view when profiler model changed.
15  profiler.addListener('changed', this.redraw_.bind(this));
16  profiler.addListener('changed:selected', this.selectNode_.bind(this));
17};
18
19/**
20 * Highlight the node being selected.
21 * @param {string|null} id Model id.
22 * @param {Object} pos Clicked position. Not used
23 * @private
24 */
25MenuView.prototype.selectNode_ = function(id) {
26  var $tree = this.$tree_;
27
28  if (id == null) {
29    $tree.tree('selectNode', null);
30    return;
31  }
32
33  var node = $tree.tree('getNodeById', id);
34  $tree.tree('selectNode', node);
35};
36
37/**
38 * Update menu view when model updated.
39 * @param {Array.<Object>} models
40 * @private
41 */
42MenuView.prototype.redraw_ = function(models) {
43  function convert(origin, target) {
44    target.label = origin.name;
45    target.id = origin.id;
46
47    if ('children' in origin) {
48      target.children = [];
49      origin.children.forEach(function(originChild) {
50        var targetChild = {};
51        target.children.push(targetChild);
52        convert(originChild, targetChild);
53      });
54    }
55  }
56
57  function merge(origin, target) {
58    if (!('children' in origin))
59      return;
60    if (!('children' in target)) {
61      target.children = origin.children;
62      return;
63    }
64
65    origin.children.forEach(function(child) {
66      // Find child with the same label in target tree.
67      var index = target.children.reduce(function(previous, current, index) {
68        if (child.label === current.label)
69        return index;
70        return previous;
71      }, -1);
72      if (index === -1)
73        target.children.push(child);
74      else
75        merge(child, target.children[index]);
76    });
77  }
78
79  var self = this;
80
81  // Merge trees in all snapshots.
82  var union = null;
83  models.forEach(function(model) {
84    var data = {};
85    convert(model, data);
86    if (!union)
87      union = data;
88    else
89      merge(data, union);
90  });
91
92  // Draw breakdown menu.
93  var data = [union];
94  if (!this.$tree_) {
95    this.$tree_ = $(this.placeholder_).tree({
96      data: data,
97      autoOpen: true,
98      onCreateLi: function(node, $li) {
99        // TODO(junjianx): Add checkbox to decide the breakdown visibility.
100      }
101    });
102
103    // Delegate events
104    this.$tree_.bind('tree.click', function(event) {
105      event.preventDefault();
106      self.profiler_.setSelected(event.node.id);
107    });
108    this.$tree_.bind('tree.close', function(event) {
109      event.preventDefault();
110      self.profiler_.unsetSub(event.node.id);
111      self.profiler_.setSelected(event.node.id);
112    });
113  } else {
114    this.$tree_.tree('loadData', data);
115  }
116};
117