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