1// Copyright (c) 2012 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
5var SourceRow = (function() {
6  'use strict';
7
8  /**
9   * A SourceRow represents the row corresponding to a single SourceEntry
10   * displayed by the EventsView.
11   *
12   * @constructor
13   */
14  function SourceRow(parentView, sourceEntry) {
15    this.parentView_ = parentView;
16
17    this.sourceEntry_ = sourceEntry;
18    this.isSelected_ = false;
19    this.isMatchedByFilter_ = false;
20
21    // Used to set CSS class for display.  Must only be modified by calling
22    // corresponding set functions.
23    this.isSelected_ = false;
24    this.isMouseOver_ = false;
25
26    // Mirror sourceEntry's values, so we only update the DOM when necessary.
27    this.isError_ = sourceEntry.isError();
28    this.isInactive_ = sourceEntry.isInactive();
29    this.description_ = sourceEntry.getDescription();
30
31    this.createRow_();
32    this.onSourceUpdated();
33  }
34
35  SourceRow.prototype = {
36    createRow_: function() {
37      // Create a row.
38      var tr = addNode(this.parentView_.tableBody_, 'tr');
39      tr._id = this.getSourceId();
40      tr.style.display = 'none';
41      this.row_ = tr;
42
43      var selectionCol = addNode(tr, 'td');
44      var checkbox = addNode(selectionCol, 'input');
45      checkbox.title = this.getSourceId();
46      selectionCol.style.borderLeft = '0';
47      checkbox.type = 'checkbox';
48
49      var idCell = addNode(tr, 'td');
50      idCell.style.textAlign = 'right';
51
52      var typeCell = addNode(tr, 'td');
53      var descriptionCell = addNode(tr, 'td');
54      this.descriptionCell_ = descriptionCell;
55
56      // Connect listeners.
57      checkbox.onchange = this.onCheckboxToggled_.bind(this);
58
59      var onclick = this.onClicked_.bind(this);
60      idCell.onclick = onclick;
61      typeCell.onclick = onclick;
62      descriptionCell.onclick = onclick;
63
64      tr.onmouseover = this.onMouseover_.bind(this);
65      tr.onmouseout = this.onMouseout_.bind(this);
66
67      // Set the cell values to match this source's data.
68      if (this.getSourceId() >= 0) {
69        addTextNode(idCell, this.getSourceId());
70      } else {
71        addTextNode(idCell, '-');
72      }
73      var sourceTypeString = this.sourceEntry_.getSourceTypeString();
74      addTextNode(typeCell, sourceTypeString);
75      this.updateDescription_();
76
77      // Add a CSS classname specific to this source type (so CSS can specify
78      // different stylings for different types).
79      var sourceTypeClass = sourceTypeString.toLowerCase().replace(/_/g, '-');
80      this.row_.classList.add('source-' + sourceTypeClass);
81
82      this.updateClass_();
83    },
84
85    onSourceUpdated: function() {
86      if (this.sourceEntry_.isInactive() != this.isInactive_ ||
87          this.sourceEntry_.isError() != this.isError_) {
88        this.updateClass_();
89      }
90
91      if (this.description_ != this.sourceEntry_.getDescription())
92        this.updateDescription_();
93
94      // Update filters.
95      var matchesFilter = this.parentView_.currentFilter_(this.sourceEntry_);
96      this.setIsMatchedByFilter(matchesFilter);
97    },
98
99    /**
100     * Changes |row_|'s class based on currently set flags.  Clears any previous
101     * class set by this method.  This method is needed so that some styles
102     * override others.
103     */
104    updateClass_: function() {
105      this.isInactive_ = this.sourceEntry_.isInactive();
106      this.isError_ = this.sourceEntry_.isError();
107
108      // Each element of this list contains a property of |this| and the
109      // corresponding class name to set if that property is true.  Entries
110      // earlier in the list take precedence.
111      var propertyNames = [
112        ['isSelected_', 'selected'],
113        ['isMouseOver_', 'mouseover'],
114        ['isError_', 'error'],
115        ['isInactive_', 'inactive'],
116      ];
117
118      // Loop through |propertyNames| in order, checking if each property
119      // is true.  For the first such property found, if any, add the
120      // corresponding class to the SourceEntry's row.  Remove classes
121      // that correspond to any other property.
122      var noStyleSet = true;
123      for (var i = 0; i < propertyNames.length; ++i) {
124        var setStyle = noStyleSet && this[propertyNames[i][0]];
125        if (setStyle) {
126          this.row_.classList.add(propertyNames[i][1]);
127          noStyleSet = false;
128        } else {
129          this.row_.classList.remove(propertyNames[i][1]);
130        }
131      }
132    },
133
134    getSourceEntry: function() {
135      return this.sourceEntry_;
136    },
137
138    setIsMatchedByFilter: function(isMatchedByFilter) {
139      if (this.isMatchedByFilter() == isMatchedByFilter)
140        return;  // No change.
141
142      this.isMatchedByFilter_ = isMatchedByFilter;
143
144      this.setFilterStyles(isMatchedByFilter);
145
146      if (isMatchedByFilter) {
147        this.parentView_.incrementPostfilterCount(1);
148      } else {
149        this.parentView_.incrementPostfilterCount(-1);
150        // If we are filtering an entry away, make sure it is no longer
151        // part of the selection.
152        this.setSelected(false);
153      }
154    },
155
156    isMatchedByFilter: function() {
157      return this.isMatchedByFilter_;
158    },
159
160    setFilterStyles: function(isMatchedByFilter) {
161      // Hide rows which have been filtered away.
162      if (isMatchedByFilter) {
163        this.row_.style.display = '';
164      } else {
165        this.row_.style.display = 'none';
166      }
167    },
168
169    isSelected: function() {
170      return this.isSelected_;
171    },
172
173    setSelected: function(isSelected) {
174      if (isSelected == this.isSelected())
175        return;
176
177      this.isSelected_ = isSelected;
178
179      this.setSelectedStyles(isSelected);
180      this.parentView_.modifySelectionArray(this.getSourceId(), isSelected);
181      this.parentView_.onSelectionChanged();
182    },
183
184    setSelectedStyles: function(isSelected) {
185      this.isSelected_ = isSelected;
186      this.getSelectionCheckbox().checked = isSelected;
187      this.updateClass_();
188    },
189
190    setMouseoverStyle: function(isMouseOver) {
191      this.isMouseOver_ = isMouseOver;
192      this.updateClass_();
193    },
194
195    onClicked_: function() {
196      this.parentView_.clearSelection();
197      this.setSelected(true);
198      if (this.isSelected())
199        this.parentView_.scrollToSourceId(this.getSourceId());
200    },
201
202    onMouseover_: function() {
203      this.setMouseoverStyle(true);
204    },
205
206    onMouseout_: function() {
207      this.setMouseoverStyle(false);
208    },
209
210    updateDescription_: function() {
211      this.description_ = this.sourceEntry_.getDescription();
212      this.descriptionCell_.innerHTML = '';
213      addTextNode(this.descriptionCell_, this.description_);
214    },
215
216    onCheckboxToggled_: function() {
217      this.setSelected(this.getSelectionCheckbox().checked);
218      if (this.isSelected())
219        this.parentView_.scrollToSourceId(this.getSourceId());
220    },
221
222    getSelectionCheckbox: function() {
223      return this.row_.childNodes[0].firstChild;
224    },
225
226    getSourceId: function() {
227      return this.sourceEntry_.getSourceId();
228    },
229
230    /**
231     * Returns source ID of the entry whose row is currently above this one's.
232     * Returns null if no such node exists.
233     */
234    getPreviousNodeSourceId: function() {
235      var prevNode = this.row_.previousSibling;
236      if (prevNode == null)
237        return null;
238      return prevNode._id;
239    },
240
241    /**
242     * Returns source ID of the entry whose row is currently below this one's.
243     * Returns null if no such node exists.
244     */
245    getNextNodeSourceId: function() {
246      var nextNode = this.row_.nextSibling;
247      if (nextNode == null)
248        return null;
249      return nextNode._id;
250    },
251
252    /**
253     * Moves current object's row before |entry|'s row.
254     */
255    moveBefore: function(entry) {
256      this.row_.parentNode.insertBefore(this.row_, entry.row_);
257    },
258
259    /**
260     * Moves current object's row after |entry|'s row.
261     */
262    moveAfter: function(entry) {
263      this.row_.parentNode.insertBefore(this.row_, entry.row_.nextSibling);
264    }
265  };
266
267  return SourceRow;
268})();
269