1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/domplate", 5 "firebug/lib/locale", 6 "firebug/chrome/reps", 7 "firebug/lib/dom", 8 "firebug/lib/css", 9 "firebug/lib/array", 10 ], 11 function(Domplate, Locale, FirebugReps, Dom, Css, Arr) { 12 13 // ********************************************************************************************* // 14 // Constants 15 16 // ********************************************************************************************* // 17 18 with (Domplate) { 19 FirebugReps.Table = domplate(Firebug.Rep, 20 { 21 className: "table", 22 tableClassName: "dataTable", 23 tag: 24 DIV({"class": "dataTableSizer", "tabindex": "-1" }, 25 TABLE({"class": "$tableClassName", cellspacing: 0, cellpadding: 0, width: "100%", 26 "role": "grid"}, 27 THEAD({"class": "dataTableThead", "role": "presentation"}, 28 TR({"class": "headerRow focusRow dataTableRow subFocusRow", "role": "row", 29 onclick: "$onClickHeader"}, 30 FOR("column", "$object.columns", 31 TH({"class": "headerCell a11yFocus", "role": "columnheader", 32 $alphaValue: "$column.alphaValue"}, 33 DIV({"class": "headerCellBox"}, 34 "$column.label" 35 ) 36 ) 37 ) 38 ) 39 ), 40 TBODY({"class": "dataTableTbody", "role": "presentation"}, 41 FOR("row", "$object.data|getRows", 42 TR({"class": "focusRow dataTableRow subFocusRow", "role": "row"}, 43 FOR("column", "$row|getColumns", 44 TD({"class": "a11yFocus dataTableCell", "role": "gridcell"}, 45 TAG("$column|getValueTag", {object: "$column"}) 46 ) 47 ) 48 ) 49 ) 50 ) 51 ) 52 ), 53 54 getValueTag: function(object) 55 { 56 var rep = Firebug.getRep(object); 57 return rep.shortTag || rep.tag; 58 }, 59 60 getRows: function(data) 61 { 62 var props = this.getProps(data); 63 if (!props.length) 64 return []; 65 return props; 66 }, 67 68 getColumns: function(row) 69 { 70 if (typeof(row) != "object") 71 return [row]; 72 73 var cols = []; 74 for (var i=0; i<this.columns.length; i++) 75 { 76 var prop = this.columns[i].property; 77 78 // Object property is not set for this column, so display entire 79 // row-value in the cell. This can happen in cases where a generic 80 // object is logged using table layout. In such case there is one 81 // column (no property associated) and each row represents a member 82 // of the object. 83 if (typeof prop == "undefined") 84 { 85 value = row; 86 } 87 else if (typeof row[prop] == "undefined") 88 { 89 var props = (typeof(prop) == "string") ? prop.split(".") : [prop]; 90 91 var value = row; 92 for (var p in props) 93 value = (value && value[props[p]]) || undefined; 94 } 95 else 96 { 97 value = row[prop]; 98 } 99 100 cols.push(value); 101 } 102 return cols; 103 }, 104 105 getProps: function(obj) 106 { 107 if (typeof(obj) != "object") 108 return [obj]; 109 110 if (FBTrace.DBG_CONSOLE) 111 FBTrace.sysout("FirebugReps.Table.getProps", {obj: obj, arr: Arr.cloneArray(obj)}); 112 113 if (obj.length) 114 return Arr.cloneArray(obj); 115 116 var arr = []; 117 for (var p in obj) 118 { 119 var value = obj[p]; 120 if (this.domFilter(value, p)) 121 arr.push(value); 122 } 123 124 return arr; 125 }, 126 127 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 128 // Sorting 129 130 onClickHeader: function(event) 131 { 132 var table = Dom.getAncestorByClass(event.target, "dataTable"); 133 var header = Dom.getAncestorByClass(event.target, "headerCell"); 134 if (!header) 135 return; 136 137 var numerical = !Css.hasClass(header, "alphaValue"); 138 139 var colIndex = 0; 140 for (header = header.previousSibling; header; header = header.previousSibling) 141 ++colIndex; 142 143 this.sort(table, colIndex, numerical); 144 }, 145 146 sort: function(table, colIndex, numerical) 147 { 148 var tbody = Dom.getChildByClass(table, "dataTableTbody"); 149 var thead = Dom.getChildByClass(table, "dataTableThead"); 150 151 var values = []; 152 for (var row = tbody.childNodes[0]; row; row = row.nextSibling) 153 { 154 var cell = row.childNodes[colIndex]; 155 var value = numerical ? parseFloat(cell.textContent) : cell.textContent; 156 values.push({row: row, value: value}); 157 } 158 159 values.sort(function(a, b) { return a.value < b.value ? -1 : 1; }); 160 161 var headerRow = thead.firstChild; 162 var headerSorted = Dom.getChildByClass(headerRow, "headerSorted"); 163 Css.removeClass(headerSorted, "headerSorted"); 164 if (headerSorted) 165 headerSorted.removeAttribute('aria-sort'); 166 167 var header = headerRow.childNodes[colIndex]; 168 Css.setClass(header, "headerSorted"); 169 170 if (!header.sorted || header.sorted == 1) 171 { 172 Css.removeClass(header, "sortedDescending"); 173 Css.setClass(header, "sortedAscending"); 174 header.setAttribute("aria-sort", "ascending"); 175 176 header.sorted = -1; 177 178 for (var i = 0; i < values.length; ++i) 179 tbody.appendChild(values[i].row); 180 } 181 else 182 { 183 Css.removeClass(header, "sortedAscending"); 184 Css.setClass(header, "sortedDescending"); 185 header.setAttribute("aria-sort", "descending") 186 187 header.sorted = 1; 188 189 for (var i = values.length-1; i >= 0; --i) 190 tbody.appendChild(values[i].row); 191 } 192 }, 193 194 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 195 // Console logging 196 197 log: function(data, cols, context, object) 198 { 199 // No arguments passed into console.table method, bail out for now, 200 // but some error message could be displayed in the future. 201 if (!data) 202 return; 203 204 var columns = this.computeColumns(data, cols); 205 206 // Don't limit strings in the table. It should be mostly ok. In case of 207 // complaints we need an option. 208 var prevValue = Firebug.stringCropLength; 209 Firebug.stringCropLength = -1; 210 211 try 212 { 213 this.columns = columns; 214 215 var object = object || {}; 216 object.data = data; 217 object.columns = columns; 218 219 var row = Firebug.Console.log(object, context, "table", this, true); 220 221 // Set vertical height for scroll bar. 222 var tBody = row.querySelector(".dataTableTbody"); 223 var maxHeight = Firebug.tabularLogMaxHeight; 224 if (maxHeight > 0 && tBody.clientHeight > maxHeight) 225 tBody.style.height = maxHeight + "px"; 226 } 227 catch (err) 228 { 229 if (FBTrace.DBG_CONSOLE) 230 FBTrace.sysout("consoleInjector.table; EXCEPTION " + err, err); 231 } 232 finally 233 { 234 Firebug.stringCropLength = prevValue; 235 delete this.columns; 236 } 237 }, 238 239 computeColumns: function(data, cols) 240 { 241 // Get header info from passed argument (can be null). 242 var columns = []; 243 for (var i=0; cols && i<cols.length; i++) 244 { 245 var col = cols[i]; 246 var prop = (typeof(col.property) != "undefined") ? col.property : col; 247 var label = (typeof(col.label) != "undefined") ? col.label : prop; 248 249 columns.push({ 250 property: prop, 251 label: label, 252 alphaValue: true 253 }); 254 } 255 256 if (FBTrace.DBG_CONSOLE) 257 FBTrace.sysout("consoleInjector.table; columns", columns); 258 259 // Generate header info from the data dynamically. 260 if (!columns.length) 261 columns = this.getHeaderColumns(data); 262 263 return columns; 264 }, 265 266 /** 267 * Analyse data and return dynamically created list of columns. 268 * @param {Object} data 269 */ 270 getHeaderColumns: function(data) 271 { 272 // Get the first row in the object. 273 var firstRow; 274 for (var p in data) 275 { 276 firstRow = data[p]; 277 break; 278 } 279 280 if (typeof(firstRow) != "object") 281 return [{label: Locale.$STR("firebug.reps.table.ObjectProperties")}]; 282 283 // Put together a column property, label and type (type for default sorting logic). 284 var header = []; 285 for (var p in firstRow) 286 { 287 var value = firstRow[p]; 288 if (!this.domFilter(value, p)) 289 continue; 290 291 header.push({ 292 property: p, 293 label: p, 294 alphaValue: (typeof(value) != "number") 295 }); 296 } 297 298 return header; 299 }, 300 301 /** 302 * Filtering based on options set in the DOM panel. 303 * @param {Object} value - a property value under inspection. 304 * @param {String} name - name of the property. 305 * @returns true if the value should be displayed, otherwise false. 306 */ 307 domFilter: function(object, name) 308 { 309 var domMembers = Dom.getDOMMembers(object, name); 310 311 if (typeof(object) == "function") 312 { 313 if (Dom.isDOMMember(object, name) && !Firebug.showDOMFuncs) 314 return false; 315 else if (!Firebug.showUserFuncs) 316 return false; 317 } 318 else 319 { 320 if (Dom.isDOMMember(object, name) && !Firebug.showDOMProps) 321 return false; 322 else if (Dom.isDOMConstant(object, name) && !Firebug.showDOMConstants) 323 return false; 324 else if (!Firebug.showUserProps) 325 return false; 326 } 327 328 return true; 329 } 330 })}; 331 332 // ********************************************************************************************* // 333 // Registration 334 335 return FirebugReps.Table; 336 337 // ********************************************************************************************* // 338 }); 339