OLD | NEW |
1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
2 <!-- | 2 <!-- |
3 Copyright (c) 2014 The Chromium Authors. All rights reserved. | 3 Copyright (c) 2014 The Chromium Authors. All rights reserved. |
4 Use of this source code is governed by a BSD-style license that can be | 4 Use of this source code is governed by a BSD-style license that can be |
5 found in the LICENSE file. | 5 found in the LICENSE file. |
6 --> | 6 --> |
7 | 7 |
8 <link rel="import" href="/tracing/ui/base/dom_helpers.html"> | 8 <link rel="import" href="/tracing/ui/base/dom_helpers.html"> |
9 <link rel="import" href="/tracing/ui/base/utils.html"> | 9 <link rel="import" href="/tracing/ui/base/utils.html"> |
10 | 10 |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
50 LEFT: 0 /* default */, | 50 LEFT: 0 /* default */, |
51 RIGHT: 1 | 51 RIGHT: 1 |
52 }; | 52 }; |
53 | 53 |
54 return { | 54 return { |
55 TableFormat: TableFormat | 55 TableFormat: TableFormat |
56 }; | 56 }; |
57 }); | 57 }); |
58 </script> | 58 </script> |
59 | 59 |
60 <dom-module id='tr-ui-b-table'> | 60 <dom-module id="tr-ui-b-table"> |
61 <template> | 61 <template> |
62 <style> | 62 <style> |
63 :host { | 63 :host { |
64 display: flex; | 64 display: flex; |
65 flex-direction: column; | 65 flex-direction: column; |
66 } | 66 } |
67 | 67 |
68 table { | 68 table { |
69 font-size: 12px; | 69 font-size: 12px; |
70 | 70 |
71 flex: 1 1 auto; | 71 flex: 1 1 auto; |
72 align-self: stretch; | 72 align-self: stretch; |
73 border-collapse: separate; | 73 border-collapse: separate; |
74 border-spacing: 0; | 74 border-spacing: 0; |
75 border-width: 0; | 75 border-width: 0; |
76 -webkit-user-select: initial; | 76 -webkit-user-select: initial; |
77 } | 77 } |
78 | 78 |
79 tr > td { | 79 tr > td { |
80 padding: 2px 4px 2px 4px; | 80 padding: 2px 4px 2px 4px; |
81 vertical-align: text-top; | 81 vertical-align: top; |
82 } | 82 } |
83 | 83 |
84 tr:focus, | 84 tr:focus, |
85 td:focus { | 85 td:focus { |
86 outline: 1px dotted rgba(0,0,0,0.1); | 86 outline: 1px dotted rgba(0,0,0,0.1); |
87 outline-offset: 0; | 87 outline-offset: 0; |
88 } | 88 } |
89 | 89 |
90 button.toggle-button { | 90 button.toggle-button { |
91 height: 15px; | 91 height: 15px; |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
146 tr:not(.empty-row):not([selected]):hover, | 146 tr:not(.empty-row):not([selected]):hover, |
147 table > tbody[cell-highlight-style="dark"] > | 147 table > tbody[cell-highlight-style="dark"] > |
148 tr:not(.empty-row):not([selected]) > td:hover { | 148 tr:not(.empty-row):not([selected]) > td:hover { |
149 background-color: #e6e6e6; /* grey */ | 149 background-color: #e6e6e6; /* grey */ |
150 } | 150 } |
151 table > tbody[row-highlight-style="dark"] > tr:hover[selected], | 151 table > tbody[row-highlight-style="dark"] > tr:hover[selected], |
152 table > tbody[cell-highlight-style="dark"] > tr[selected] > td:hover { | 152 table > tbody[cell-highlight-style="dark"] > tr[selected] > td:hover { |
153 background-color: rgb(171, 217, 202); /* semi-light turquoise */ | 153 background-color: rgb(171, 217, 202); /* semi-light turquoise */ |
154 } | 154 } |
155 | 155 |
| 156 table > colgroup > col[selected] { |
| 157 background-color: #e6e6e6; /* grey */ |
| 158 } |
| 159 |
156 table > tbody > tr.empty-row > td { | 160 table > tbody > tr.empty-row > td { |
157 color: #666; | 161 color: #666; |
158 font-style: italic; | 162 font-style: italic; |
159 text-align: center; | 163 text-align: center; |
160 } | 164 } |
161 | 165 |
162 table > tbody.has-footer > tr:last-child > td { | 166 table > tbody.has-footer > tr:last-child > td { |
163 border-bottom: 1px solid #aaa; | 167 border-bottom: 1px solid #aaa; |
164 } | 168 } |
165 | 169 |
166 table > tfoot > tr:first-child > td { | 170 table > tfoot > tr:first-child > td { |
167 border-top: 1px solid #ffffff; | 171 border-top: 1px solid #ffffff; |
168 } | 172 } |
169 | 173 |
170 expand-button { | 174 expand-button { |
171 -webkit-user-select: none; | 175 -webkit-user-select: none; |
172 display: inline-block; | 176 display: inline-block; |
173 cursor: pointer; | 177 cursor: pointer; |
174 font-size: 9px; | 178 font-size: 9px; |
175 min-width: 8px; | 179 min-width: 8px; |
176 max-width: 8px; | 180 max-width: 8px; |
177 } | 181 } |
178 | 182 |
179 .button-expanded { | 183 .button-expanded { |
180 transform: rotate(90deg); | 184 transform: rotate(90deg); |
181 } | 185 } |
182 </style> | 186 </style> |
183 <table> | 187 <table> |
| 188 <colgroup id="cols"> |
| 189 </colgroup> |
184 <thead id="head"> | 190 <thead id="head"> |
185 </thead> | 191 </thead> |
186 <tbody id="body"> | 192 <tbody id="body"> |
187 </tbody> | 193 </tbody> |
188 <tfoot id="foot"> | 194 <tfoot id="foot"> |
189 </tfoot> | 195 </tfoot> |
190 </table> | 196 </table> |
191 </template> | 197 </template> |
192 </dom-module> | 198 </dom-module> |
193 <script> | 199 <script> |
194 'use strict'; | 200 'use strict'; |
195 (function() { | 201 (function() { |
196 var RIGHT_ARROW = String.fromCharCode(0x25b6); | 202 var RIGHT_ARROW = String.fromCharCode(0x25b6); |
197 var UNSORTED_ARROW = String.fromCharCode(0x25BF); | 203 var UNSORTED_ARROW = String.fromCharCode(0x25BF); |
198 var ASCENDING_ARROW = String.fromCharCode(0x25B4); | 204 var ASCENDING_ARROW = String.fromCharCode(0x25B4); |
199 var DESCENDING_ARROW = String.fromCharCode(0x25BE); | 205 var DESCENDING_ARROW = String.fromCharCode(0x25BE); |
200 var BASIC_INDENTATION = 8; | 206 var BASIC_INDENTATION = 8; |
201 | 207 |
202 var SelectionMode = tr.ui.b.TableFormat.SelectionMode; | 208 var SelectionMode = tr.ui.b.TableFormat.SelectionMode; |
203 var HighlightStyle = tr.ui.b.TableFormat.HighlightStyle; | 209 var HighlightStyle = tr.ui.b.TableFormat.HighlightStyle; |
204 var ColumnAlignment = tr.ui.b.TableFormat.ColumnAlignment; | 210 var ColumnAlignment = tr.ui.b.TableFormat.ColumnAlignment; |
205 | 211 |
206 Polymer({ | 212 Polymer({ |
207 is: 'tr-ui-b-table', | 213 is: 'tr-ui-b-table', |
208 | 214 |
209 created: function() { | 215 created: function() { |
210 this.selectionMode_ = SelectionMode.NONE; | 216 this.selectionMode_ = SelectionMode.NONE; |
211 this.rowHighlightStyle_ = HighlightStyle.DEFAULT; | 217 this.rowHighlightStyle_ = HighlightStyle.DEFAULT; |
212 this.cellHighlightStyle_ = HighlightStyle.DEFAULT; | 218 this.cellHighlightStyle_ = HighlightStyle.DEFAULT; |
213 this.selectedTableRowInfo_ = undefined; | 219 this.selectedTableRowInfo_ = undefined; |
214 this.selectedColumnIndex_ = undefined; | 220 this.selectedColumnIndex_ = undefined; |
215 | 221 |
216 this.tableColumns_ = []; | 222 this.tableColumns_ = []; |
217 this.tableRows_ = []; | 223 this.tableRows_ = []; |
218 this.tableRowsInfo_ = new WeakMap(); | 224 this.tableRowsInfo_ = new WeakMap(); |
219 this.tableFooterRows_ = []; | 225 this.tableFooterRows_ = []; |
220 this.tableFooterRowsInfo_ = new WeakMap(); | 226 this.tableFooterRowsInfo_ = new WeakMap(); |
| 227 this.sortColumnIndex_ = undefined; |
| 228 this.sortDescending_ = false; |
| 229 this.columnsWithExpandButtons_ = []; |
| 230 this.headerCells_ = []; |
| 231 this.showHeader_ = true; |
| 232 this.emptyValue_ = undefined; |
| 233 this.subRowsPropertyName_ = 'subRows'; |
| 234 this.customizeTableRowCallback_ = undefined; |
| 235 }, |
| 236 |
| 237 ready: function() { |
| 238 this.$.body.addEventListener( |
| 239 'keydown', this.onKeyDown_.bind(this), true); |
| 240 }, |
| 241 |
| 242 clear: function() { |
| 243 this.selectionMode_ = SelectionMode.NONE; |
| 244 this.rowHighlightStyle_ = HighlightStyle.DEFAULT; |
| 245 this.cellHighlightStyle_ = HighlightStyle.DEFAULT; |
| 246 this.selectedTableRowInfo_ = undefined; |
| 247 this.selectedColumnIndex_ = undefined; |
| 248 |
| 249 Polymer.dom(this).textContent = ''; |
| 250 this.tableColumns_ = []; |
| 251 this.tableRows_ = []; |
| 252 this.tableRowsInfo_ = new WeakMap(); |
| 253 this.tableFooterRows_ = []; |
| 254 this.tableFooterRowsInfo_ = new WeakMap(); |
| 255 this.sortColumnIndex_ = undefined; |
| 256 this.sortDescending_ = false; |
| 257 this.columnsWithExpandButtons_ = []; |
| 258 this.headerCells_ = []; |
| 259 this.subRowsPropertyName_ = 'subRows'; |
| 260 this.defaultExpansionStateCallback_ = undefined; |
| 261 }, |
| 262 |
| 263 get showHeader() { |
| 264 return this.showHeader_; |
| 265 }, |
| 266 |
| 267 set showHeader(showHeader) { |
| 268 this.showHeader_ = showHeader; |
| 269 this.scheduleRebuildHeaders_(); |
| 270 }, |
| 271 |
| 272 set subRowsPropertyName(name) { |
| 273 this.subRowsPropertyName_ = name; |
| 274 }, |
| 275 |
| 276 /** |
| 277 * This callback will be called whenever a body row is built |
| 278 * for a userRow that has subRows and does not have an explicit |
| 279 * isExpanded field. |
| 280 * The callback should return true if the row should be expanded, |
| 281 * or false if the row should be collapsed. |
| 282 * @param {function(userRow, parentUserRow): boolean} cb The callback. |
| 283 */ |
| 284 set defaultExpansionStateCallback(cb) { |
| 285 this.defaultExpansionStateCallback_ = cb; |
| 286 this.scheduleRebuildBody_(); |
| 287 }, |
| 288 |
| 289 /** |
| 290 * This callback will be called whenever a body row is built. |
| 291 * The callback's return value is ignored. |
| 292 * @param {function(userRow, trElement)} cb The callback. |
| 293 */ |
| 294 set customizeTableRowCallback(cb) { |
| 295 this.customizeTableRowCallback_ = cb; |
| 296 this.scheduleRebuildBody_(); |
| 297 }, |
| 298 |
| 299 get emptyValue() { |
| 300 return this.emptyValue_; |
| 301 }, |
| 302 |
| 303 set emptyValue(emptyValue) { |
| 304 var previousEmptyValue = this.emptyValue_; |
| 305 this.emptyValue_ = emptyValue; |
| 306 if (this.tableRows_.length === 0 && emptyValue !== previousEmptyValue) |
| 307 this.scheduleRebuildBody_(); |
| 308 }, |
| 309 |
| 310 /** |
| 311 * Data objects should have the following fields: |
| 312 * mandatory: title, value |
| 313 * optional: width {string}, cmp {function}, colSpan {number}, |
| 314 * showExpandButtons {boolean}, |
| 315 * align {tr.ui.b.TableFormat.ColumnAlignment} |
| 316 * |
| 317 * @param {Array} columns An array of data objects. |
| 318 */ |
| 319 set tableColumns(columns) { |
| 320 // Figure out the columns with expand buttons... |
| 321 var columnsWithExpandButtons = []; |
| 322 for (var i = 0; i < columns.length; i++) { |
| 323 if (columns[i].showExpandButtons) |
| 324 columnsWithExpandButtons.push(i); |
| 325 } |
| 326 if (columnsWithExpandButtons.length === 0) { |
| 327 // First column if none have specified. |
| 328 columnsWithExpandButtons = [0]; |
| 329 } |
| 330 |
| 331 // Sanity check columns. |
| 332 for (var i = 0; i < columns.length; i++) { |
| 333 var colInfo = columns[i]; |
| 334 if (colInfo.width === undefined) |
| 335 continue; |
| 336 |
| 337 var hasExpandButton = columnsWithExpandButtons.indexOf(i) !== -1; |
| 338 |
| 339 var w = colInfo.width; |
| 340 if (w) { |
| 341 if (/\d+px/.test(w)) { |
| 342 continue; |
| 343 } else if (/\d+%/.test(w)) { |
| 344 if (hasExpandButton) { |
| 345 throw new Error('Columns cannot be %-sized and host ' + |
| 346 ' an expand button'); |
| 347 } |
| 348 } else { |
| 349 throw new Error('Unrecognized width string'); |
| 350 } |
| 351 } |
| 352 } |
| 353 |
| 354 // Commit the change. |
| 355 this.tableColumns_ = columns; |
| 356 this.headerCells_ = []; |
| 357 this.columnsWithExpandButtons_ = columnsWithExpandButtons; |
| 358 this.sortColumnIndex = undefined; |
| 359 this.scheduleRebuildHeaders_(); |
| 360 |
| 361 // Blow away the table rows, too. |
| 362 this.tableRows = this.tableRows_; |
| 363 }, |
| 364 |
| 365 get tableColumns() { |
| 366 return this.tableColumns_; |
| 367 }, |
| 368 |
| 369 /** |
| 370 * @param {Array} rows An array of 'row' objects with the following |
| 371 * fields: |
| 372 * optional: subRows An array of objects that have the same 'row' |
| 373 * structure. Set subRowsPropertyName to use an |
| 374 * alternative field name. |
| 375 */ |
| 376 set tableRows(rows) { |
| 377 this.selectedTableRowInfo_ = undefined; |
| 378 this.selectedColumnIndex_ = undefined; |
| 379 this.maybeUpdateSelectedRow_(); |
| 380 this.tableRows_ = rows; |
| 381 this.tableRowsInfo_ = new WeakMap(); |
| 382 this.scheduleRebuildBody_(); |
| 383 }, |
| 384 |
| 385 get tableRows() { |
| 386 return this.tableRows_; |
| 387 }, |
| 388 |
| 389 set footerRows(rows) { |
| 390 this.tableFooterRows_ = rows; |
| 391 this.tableFooterRowsInfo_ = new WeakMap(); |
| 392 this.scheduleRebuildFooter_(); |
| 393 }, |
| 394 |
| 395 get footerRows() { |
| 396 return this.tableFooterRows_; |
| 397 }, |
| 398 |
| 399 set sortColumnIndex(number) { |
| 400 if (number === this.sortColumnIndex_) |
| 401 return; |
| 402 |
| 403 if (number === undefined) { |
221 this.sortColumnIndex_ = undefined; | 404 this.sortColumnIndex_ = undefined; |
222 this.sortDescending_ = false; | 405 this.updateHeaderArrows_(); |
223 this.columnsWithExpandButtons_ = []; | 406 this.dispatchSortingChangedEvent_(); |
224 this.headerCells_ = []; | 407 return; |
225 this.showHeader_ = true; | 408 } |
226 this.emptyValue_ = undefined; | 409 |
227 this.subRowsPropertyName_ = 'subRows'; | 410 if (this.tableColumns_.length <= number) |
228 this.customizeTableRowCallback_ = undefined; | 411 throw new Error('Column number ' + number + ' is out of bounds.'); |
229 }, | 412 if (!this.tableColumns_[number].cmp) |
230 | 413 throw new Error('Column ' + number + ' does not have a comparator.'); |
231 ready: function() { | 414 |
232 this.$.body.addEventListener( | 415 this.sortColumnIndex_ = number; |
233 'keydown', this.onKeyDown_.bind(this), true); | 416 this.updateHeaderArrows_(); |
234 }, | 417 this.scheduleRebuildBody_(); |
235 | 418 this.dispatchSortingChangedEvent_(); |
236 clear: function() { | 419 }, |
237 this.selectionMode_ = SelectionMode.NONE; | 420 |
238 this.rowHighlightStyle_ = HighlightStyle.DEFAULT; | 421 get sortColumnIndex() { |
239 this.cellHighlightStyle_ = HighlightStyle.DEFAULT; | 422 return this.sortColumnIndex_; |
240 this.selectedTableRowInfo_ = undefined; | 423 }, |
241 this.selectedColumnIndex_ = undefined; | 424 |
242 | 425 set sortDescending(value) { |
243 Polymer.dom(this).textContent = ''; | 426 var newValue = !!value; |
244 this.tableColumns_ = []; | 427 |
245 this.tableRows_ = []; | 428 if (newValue !== this.sortDescending_) { |
246 this.tableRowsInfo_ = new WeakMap(); | 429 this.sortDescending_ = newValue; |
247 this.tableFooterRows_ = []; | |
248 this.tableFooterRowsInfo_ = new WeakMap(); | |
249 this.sortColumnIndex_ = undefined; | |
250 this.sortDescending_ = false; | |
251 this.columnsWithExpandButtons_ = []; | |
252 this.headerCells_ = []; | |
253 this.subRowsPropertyName_ = 'subRows'; | |
254 this.defaultExpansionStateCallback_ = undefined; | |
255 }, | |
256 | |
257 get showHeader() { | |
258 return this.showHeader_; | |
259 }, | |
260 | |
261 set showHeader(showHeader) { | |
262 this.showHeader_ = showHeader; | |
263 this.scheduleRebuildHeaders_(); | |
264 }, | |
265 | |
266 set subRowsPropertyName(name) { | |
267 this.subRowsPropertyName_ = name; | |
268 }, | |
269 | |
270 /** | |
271 * This callback will be called whenever a body row is built | |
272 * for a userRow that has subRows and does not have an explicit | |
273 * isExpanded field. | |
274 * The callback should return true if the row should be expanded, | |
275 * or false if the row should be collapsed. | |
276 * @param {function(userRow, parentUserRow): boolean} cb The callback. | |
277 */ | |
278 set defaultExpansionStateCallback(cb) { | |
279 this.defaultExpansionStateCallback_ = cb; | |
280 this.scheduleRebuildBody_(); | |
281 }, | |
282 | |
283 /** | |
284 * This callback will be called whenever a body row is built. | |
285 * The callback's return value is ignored. | |
286 * @param {function(userRow, trElement)} cb The callback. | |
287 */ | |
288 set customizeTableRowCallback(cb) { | |
289 this.customizeTableRowCallback_ = cb; | |
290 this.scheduleRebuildBody_(); | |
291 }, | |
292 | |
293 get emptyValue() { | |
294 return this.emptyValue_; | |
295 }, | |
296 | |
297 set emptyValue(emptyValue) { | |
298 var previousEmptyValue = this.emptyValue_; | |
299 this.emptyValue_ = emptyValue; | |
300 if (this.tableRows_.length === 0 && emptyValue !== previousEmptyValue) | |
301 this.scheduleRebuildBody_(); | |
302 }, | |
303 | |
304 /** | |
305 * Data objects should have the following fields: | |
306 * mandatory: title, value | |
307 * optional: width {string}, cmp {function}, colSpan {number}, | |
308 * showExpandButtons {boolean}, | |
309 * align {tr.ui.b.TableFormat.ColumnAlignment} | |
310 * | |
311 * @param {Array} columns An array of data objects. | |
312 */ | |
313 set tableColumns(columns) { | |
314 // Figure out the columns with expand buttons... | |
315 var columnsWithExpandButtons = []; | |
316 for (var i = 0; i < columns.length; i++) { | |
317 if (columns[i].showExpandButtons) | |
318 columnsWithExpandButtons.push(i); | |
319 } | |
320 if (columnsWithExpandButtons.length === 0) { | |
321 // First column if none have specified. | |
322 columnsWithExpandButtons = [0]; | |
323 } | |
324 | |
325 // Sanity check columns. | |
326 for (var i = 0; i < columns.length; i++) { | |
327 var colInfo = columns[i]; | |
328 if (colInfo.width === undefined) | |
329 continue; | |
330 | |
331 var hasExpandButton = columnsWithExpandButtons.indexOf(i) !== -1; | |
332 | |
333 var w = colInfo.width; | |
334 if (w) { | |
335 if (/\d+px/.test(w)) { | |
336 continue; | |
337 } else if (/\d+%/.test(w)) { | |
338 if (hasExpandButton) { | |
339 throw new Error('Columns cannot be %-sized and host ' + | |
340 ' an expand button'); | |
341 } | |
342 } else { | |
343 throw new Error('Unrecognized width string'); | |
344 } | |
345 } | |
346 } | |
347 | |
348 // Commit the change. | |
349 this.tableColumns_ = columns; | |
350 this.headerCells_ = []; | |
351 this.columnsWithExpandButtons_ = columnsWithExpandButtons; | |
352 this.sortColumnIndex = undefined; | |
353 this.scheduleRebuildHeaders_(); | |
354 | |
355 // Blow away the table rows, too. | |
356 this.tableRows = this.tableRows_; | |
357 }, | |
358 | |
359 get tableColumns() { | |
360 return this.tableColumns_; | |
361 }, | |
362 | |
363 /** | |
364 * @param {Array} rows An array of 'row' objects with the following | |
365 * fields: | |
366 * optional: subRows An array of objects that have the same 'row' | |
367 * structure. Set subRowsPropertyName to use an | |
368 * alternative field name. | |
369 */ | |
370 set tableRows(rows) { | |
371 this.selectedTableRowInfo_ = undefined; | |
372 this.selectedColumnIndex_ = undefined; | |
373 this.maybeUpdateSelectedRow_(); | |
374 this.tableRows_ = rows; | |
375 this.tableRowsInfo_ = new WeakMap(); | |
376 this.scheduleRebuildBody_(); | |
377 }, | |
378 | |
379 get tableRows() { | |
380 return this.tableRows_; | |
381 }, | |
382 | |
383 set footerRows(rows) { | |
384 this.tableFooterRows_ = rows; | |
385 this.tableFooterRowsInfo_ = new WeakMap(); | |
386 this.scheduleRebuildFooter_(); | |
387 }, | |
388 | |
389 get footerRows() { | |
390 return this.tableFooterRows_; | |
391 }, | |
392 | |
393 set sortColumnIndex(number) { | |
394 if (number === this.sortColumnIndex_) | |
395 return; | |
396 | |
397 if (number === undefined) { | |
398 this.sortColumnIndex_ = undefined; | |
399 this.updateHeaderArrows_(); | |
400 this.dispatchSortingChangedEvent_(); | |
401 return; | |
402 } | |
403 | |
404 if (this.tableColumns_.length <= number) | |
405 throw new Error('Column number ' + number + ' is out of bounds.'); | |
406 if (!this.tableColumns_[number].cmp) | |
407 throw new Error('Column ' + number + ' does not have a comparator.'); | |
408 | |
409 this.sortColumnIndex_ = number; | |
410 this.updateHeaderArrows_(); | 430 this.updateHeaderArrows_(); |
411 this.scheduleRebuildBody_(); | 431 this.scheduleRebuildBody_(); |
412 this.dispatchSortingChangedEvent_(); | 432 this.dispatchSortingChangedEvent_(); |
413 }, | 433 } |
414 | 434 }, |
415 get sortColumnIndex() { | 435 |
416 return this.sortColumnIndex_; | 436 get sortDescending() { |
417 }, | 437 return this.sortDescending_; |
418 | 438 }, |
419 set sortDescending(value) { | 439 |
420 var newValue = !!value; | 440 updateHeaderArrows_: function() { |
421 | 441 for (var i = 0; i < this.headerCells_.length; i++) { |
422 if (newValue !== this.sortDescending_) { | 442 if (!this.tableColumns_[i].cmp) { |
423 this.sortDescending_ = newValue; | 443 this.headerCells_[i].sideContent = ''; |
424 this.updateHeaderArrows_(); | 444 continue; |
425 this.scheduleRebuildBody_(); | 445 } |
426 this.dispatchSortingChangedEvent_(); | 446 if (i !== this.sortColumnIndex_) { |
427 } | 447 this.headerCells_[i].sideContent = UNSORTED_ARROW; |
428 }, | 448 continue; |
429 | 449 } |
430 get sortDescending() { | 450 this.headerCells_[i].sideContent = this.sortDescending_ ? |
431 return this.sortDescending_; | 451 DESCENDING_ARROW : ASCENDING_ARROW; |
432 }, | 452 } |
433 | 453 }, |
434 updateHeaderArrows_: function() { | 454 |
435 for (var i = 0; i < this.headerCells_.length; i++) { | 455 sortRows_: function(rows) { |
436 if (!this.tableColumns_[i].cmp) { | 456 rows.sort(function(rowA, rowB) { |
437 this.headerCells_[i].sideContent = ''; | 457 if (this.sortDescending_) |
438 continue; | 458 return this.tableColumns_[this.sortColumnIndex_].cmp( |
| 459 rowB.userRow, rowA.userRow); |
| 460 return this.tableColumns_[this.sortColumnIndex_].cmp( |
| 461 rowA.userRow, rowB.userRow); |
| 462 }.bind(this)); |
| 463 // Sort expanded sub rows recursively. |
| 464 for (var i = 0; i < rows.length; i++) { |
| 465 if (this.getExpandedForUserRow_(rows[i])) |
| 466 this.sortRows_(rows[i][this.subRowsPropertyName_]); |
| 467 } |
| 468 }, |
| 469 |
| 470 generateHeaderColumns_: function() { |
| 471 this.headerCells_ = []; |
| 472 Polymer.dom(this.$.head).textContent = ''; |
| 473 if (!this.showHeader_) |
| 474 return; |
| 475 |
| 476 var tr = this.appendNewElement_(this.$.head, 'tr'); |
| 477 for (var i = 0; i < this.tableColumns_.length; i++) { |
| 478 var td = this.appendNewElement_(tr, 'td'); |
| 479 |
| 480 var headerCell = document.createElement('tr-ui-b-table-header-cell'); |
| 481 headerCell.column = this.tableColumns_[i]; |
| 482 |
| 483 headerCell.addEventListener('selected-column-changed', |
| 484 this.onSelectedColumnChanged_.bind(this)); |
| 485 |
| 486 // If the table can be sorted by this column, attach a tap callback |
| 487 // to the column. |
| 488 if (this.tableColumns_[i].cmp) { |
| 489 Polymer.dom(td).classList.add('sensitive'); |
| 490 headerCell.tapCallback = this.createSortCallback_(i); |
| 491 // Set arrow position, depending on the sortColumnIndex. |
| 492 if (this.sortColumnIndex_ === i) |
| 493 headerCell.sideContent = this.sortDescending_ ? |
| 494 DESCENDING_ARROW : ASCENDING_ARROW; |
| 495 else |
| 496 headerCell.sideContent = UNSORTED_ARROW; |
| 497 } |
| 498 |
| 499 Polymer.dom(td).appendChild(headerCell); |
| 500 this.headerCells_.push(headerCell); |
| 501 } |
| 502 }, |
| 503 |
| 504 onSelectedColumnChanged_: function(event) { |
| 505 // Unselect all other columns. |
| 506 for (var i = 0; i < this.headerCells_.length; ++i) { |
| 507 var colElement = Polymer.dom(this.$.cols).children[i]; |
| 508 var headerCell = this.headerCells_[i]; |
| 509 if ((event.column === headerCell.column) && event.selected) { |
| 510 Polymer.dom(colElement).setAttribute('selected', true); |
| 511 } else { |
| 512 headerCell.selected = false; |
| 513 Polymer.dom(colElement).removeAttribute('selected'); |
| 514 } |
| 515 } |
| 516 }, |
| 517 |
| 518 applySizes_: function() { |
| 519 if (this.tableRows_.length === 0 && !this.showHeader) |
| 520 return; |
| 521 var rowToRemoveSizing; |
| 522 var rowToSize; |
| 523 if (this.showHeader) { |
| 524 rowToSize = Polymer.dom(this.$.head).children[0]; |
| 525 rowToRemoveSizing = Polymer.dom(this.$.body).children[0]; |
| 526 } else { |
| 527 rowToSize = Polymer.dom(this.$.body).children[0]; |
| 528 rowToRemoveSizing = Polymer.dom(this.$.head).children[0]; |
| 529 } |
| 530 for (var i = 0; i < this.tableColumns_.length; i++) { |
| 531 if (rowToRemoveSizing && Polymer.dom(rowToRemoveSizing).children[i]) { |
| 532 var tdToRemoveSizing = Polymer.dom(rowToRemoveSizing).children[i]; |
| 533 tdToRemoveSizing.style.minWidth = ''; |
| 534 tdToRemoveSizing.style.width = ''; |
| 535 } |
| 536 |
| 537 // Apply sizing. |
| 538 var td = Polymer.dom(rowToSize).children[i]; |
| 539 |
| 540 var delta; |
| 541 if (this.columnsWithExpandButtons_.indexOf(i) !== -1) { |
| 542 td.style.paddingLeft = BASIC_INDENTATION + 'px'; |
| 543 delta = BASIC_INDENTATION + 'px'; |
| 544 } else { |
| 545 delta = undefined; |
| 546 } |
| 547 |
| 548 function calc(base, delta) { |
| 549 if (delta) |
| 550 return 'calc(' + base + ' - ' + delta + ')'; |
| 551 else |
| 552 return base; |
| 553 } |
| 554 |
| 555 var w = this.tableColumns_[i].width; |
| 556 if (w) { |
| 557 if (/\d+px/.test(w)) { |
| 558 td.style.minWidth = calc(w, delta); |
| 559 } else if (/\d+%/.test(w)) { |
| 560 td.style.width = w; |
| 561 } else { |
| 562 throw new Error('Unrecognized width string: ' + w); |
439 } | 563 } |
440 if (i !== this.sortColumnIndex_) { | 564 } |
441 this.headerCells_[i].sideContent = UNSORTED_ARROW; | 565 } |
442 continue; | 566 }, |
| 567 |
| 568 createSortCallback_: function(columnNumber) { |
| 569 return function() { |
| 570 var previousIndex = this.sortColumnIndex; |
| 571 this.sortColumnIndex = columnNumber; |
| 572 if (previousIndex !== columnNumber) |
| 573 this.sortDescending = false; |
| 574 else |
| 575 this.sortDescending = !this.sortDescending; |
| 576 }.bind(this); |
| 577 }, |
| 578 |
| 579 generateTableColNodes_: function() { |
| 580 for (var i = 0; i < this.headerCells_.length; ++i) { |
| 581 var colElement = document.createElement('col'); |
| 582 if (this.headerCells_[i].selected) |
| 583 Polymer.dom(colElement).setAttribute('selected', true); |
| 584 Polymer.dom(this.$.cols).appendChild(colElement); |
| 585 } |
| 586 }, |
| 587 |
| 588 generateTableRowNodes_: function(tableSection, userRows, rowInfoMap, |
| 589 indentation, lastAddedRow, |
| 590 parentRowInfo) { |
| 591 if (this.sortColumnIndex_ !== undefined && |
| 592 tableSection === this.$.body) { |
| 593 userRows = userRows.slice(); // Don't mess with the input data. |
| 594 userRows.sort(function(rowA, rowB) { |
| 595 var c = this.tableColumns_[this.sortColumnIndex_].cmp( |
| 596 rowA, rowB); |
| 597 if (this.sortDescending_) |
| 598 c = -c; |
| 599 return c; |
| 600 }.bind(this)); |
| 601 } |
| 602 |
| 603 for (var i = 0; i < userRows.length; i++) { |
| 604 var userRow = userRows[i]; |
| 605 var rowInfo = this.getOrCreateRowInfoFor_(rowInfoMap, userRow, |
| 606 parentRowInfo); |
| 607 var htmlNode = this.getHTMLNodeForRowInfo_( |
| 608 tableSection, rowInfo, rowInfoMap, indentation); |
| 609 |
| 610 if (lastAddedRow === undefined) { |
| 611 // Put first into the table. |
| 612 Polymer.dom(tableSection).insertBefore( |
| 613 htmlNode, Polymer.dom(tableSection).firstChild); |
| 614 } else { |
| 615 // This is shorthand for insertAfter(htmlNode, lastAdded). |
| 616 var nextSiblingOfLastAdded = Polymer.dom(lastAddedRow).nextSibling; |
| 617 Polymer.dom(tableSection).insertBefore( |
| 618 htmlNode, nextSiblingOfLastAdded); |
| 619 } |
| 620 this.updateTabIndexForTableRowNode_(htmlNode); |
| 621 |
| 622 lastAddedRow = htmlNode; |
| 623 if (!rowInfo.isExpanded) |
| 624 continue; |
| 625 |
| 626 // Append subrows now. |
| 627 lastAddedRow = this.generateTableRowNodes_( |
| 628 tableSection, userRow[this.subRowsPropertyName_], rowInfoMap, |
| 629 indentation + 1, lastAddedRow, rowInfo); |
| 630 } |
| 631 return lastAddedRow; |
| 632 }, |
| 633 |
| 634 getOrCreateRowInfoFor_: function(rowInfoMap, userRow, parentRowInfo) { |
| 635 var rowInfo = undefined; |
| 636 |
| 637 if (rowInfoMap.has(userRow)) { |
| 638 rowInfo = rowInfoMap.get(userRow); |
| 639 } else { |
| 640 rowInfo = { |
| 641 userRow: userRow, |
| 642 htmlNode: undefined, |
| 643 parentRowInfo: parentRowInfo |
| 644 }; |
| 645 rowInfoMap.set(userRow, rowInfo); |
| 646 } |
| 647 |
| 648 // Recompute isExpanded in case defaultExpansionStateCallback_ has |
| 649 // changed. |
| 650 rowInfo.isExpanded = this.getExpandedForUserRow_(userRow); |
| 651 |
| 652 return rowInfo; |
| 653 }, |
| 654 |
| 655 customizeTableRow_: function(userRow, trElement) { |
| 656 if (!this.customizeTableRowCallback_) |
| 657 return; |
| 658 this.customizeTableRowCallback_(userRow, trElement); |
| 659 }, |
| 660 |
| 661 getHTMLNodeForRowInfo_: function(tableSection, rowInfo, |
| 662 rowInfoMap, indentation) { |
| 663 if (rowInfo.htmlNode) { |
| 664 this.customizeTableRow_(rowInfo.userRow, rowInfo.htmlNode); |
| 665 return rowInfo.htmlNode; |
| 666 } |
| 667 |
| 668 var INDENT_SPACE = indentation * 16; |
| 669 var INDENT_SPACE_NO_BUTTON = indentation * 16 + BASIC_INDENTATION; |
| 670 var trElement = this.ownerDocument.createElement('tr'); |
| 671 rowInfo.htmlNode = trElement; |
| 672 rowInfo.indentation = indentation; |
| 673 trElement.rowInfo = rowInfo; |
| 674 this.customizeTableRow_(rowInfo.userRow, trElement); |
| 675 |
| 676 for (var i = 0; i < this.tableColumns_.length;) { |
| 677 var td = this.appendNewElement_(trElement, 'td'); |
| 678 td.columnIndex = i; |
| 679 |
| 680 var column = this.tableColumns_[i]; |
| 681 var value = column.value(rowInfo.userRow); |
| 682 var colSpan = column.colSpan ? column.colSpan : 1; |
| 683 td.style.colSpan = colSpan; |
| 684 |
| 685 switch (column.align) { |
| 686 case undefined: |
| 687 case ColumnAlignment.LEFT: |
| 688 break; |
| 689 |
| 690 case ColumnAlignment.RIGHT: |
| 691 td.style.textAlign = 'right'; |
| 692 break; |
| 693 |
| 694 default: |
| 695 throw new Error('Invalid alignment of column at index=' + i + |
| 696 ': ' + column.align); |
| 697 } |
| 698 |
| 699 if (this.doesColumnIndexSupportSelection(i)) |
| 700 Polymer.dom(td).classList.add('supports-selection'); |
| 701 |
| 702 if (this.columnsWithExpandButtons_.indexOf(i) != -1) { |
| 703 if (rowInfo.userRow[this.subRowsPropertyName_] && |
| 704 rowInfo.userRow[this.subRowsPropertyName_].length > 0) { |
| 705 td.style.paddingLeft = INDENT_SPACE + 'px'; |
| 706 var expandButton = this.appendNewElement_(td, |
| 707 'expand-button'); |
| 708 Polymer.dom(expandButton).textContent = RIGHT_ARROW; |
| 709 if (rowInfo.isExpanded) |
| 710 Polymer.dom(expandButton).classList.add('button-expanded'); |
| 711 } else { |
| 712 td.style.paddingLeft = INDENT_SPACE_NO_BUTTON + 'px'; |
443 } | 713 } |
444 this.headerCells_[i].sideContent = this.sortDescending_ ? | 714 } |
445 DESCENDING_ARROW : ASCENDING_ARROW; | 715 |
446 } | 716 if (value !== undefined) { |
447 }, | 717 Polymer.dom(td).appendChild( |
448 | 718 tr.ui.b.asHTMLOrTextNode(value, this.ownerDocument)); |
449 sortRows_: function(rows) { | 719 } |
450 rows.sort(function(rowA, rowB) { | 720 |
451 if (this.sortDescending_) | 721 i += colSpan; |
452 return this.tableColumns_[this.sortColumnIndex_].cmp( | 722 } |
453 rowB.userRow, rowA.userRow); | 723 |
454 return this.tableColumns_[this.sortColumnIndex_].cmp( | 724 var isSelectable = tableSection === this.$.body; |
455 rowA.userRow, rowB.userRow); | 725 var isExpandable = rowInfo.userRow[this.subRowsPropertyName_] && |
456 }.bind(this)); | 726 rowInfo.userRow[this.subRowsPropertyName_].length; |
457 // Sort expanded sub rows recursively. | 727 |
458 for (var i = 0; i < rows.length; i++) { | 728 if (isSelectable || isExpandable) { |
459 if (this.getExpandedForUserRow_(rows[i])) | 729 trElement.addEventListener('click', function(e) { |
460 this.sortRows_(rows[i][this.subRowsPropertyName_]); | 730 e.stopPropagation(); |
461 } | 731 if (e.target.tagName == 'EXPAND-BUTTON') { |
462 }, | 732 this.setExpandedForUserRow_( |
463 | 733 tableSection, rowInfoMap, |
464 generateHeaderColumns_: function() { | 734 rowInfo.userRow, !rowInfo.isExpanded); |
465 this.headerCells_ = []; | 735 return; |
466 Polymer.dom(this.$.head).textContent = ''; | |
467 if (!this.showHeader_) | |
468 return; | |
469 | |
470 var tr = this.appendNewElement_(this.$.head, 'tr'); | |
471 for (var i = 0; i < this.tableColumns_.length; i++) { | |
472 var td = this.appendNewElement_(tr, 'td'); | |
473 | |
474 var headerCell = document.createElement('tr-ui-b-table-header-cell'); | |
475 headerCell.cellTitle = this.tableColumns_[i].title; | |
476 headerCell.align = this.tableColumns_[i].align; | |
477 | |
478 // If the table can be sorted by this column, attach a tap callback | |
479 // to the column. | |
480 if (this.tableColumns_[i].cmp) { | |
481 Polymer.dom(td).classList.add('sensitive'); | |
482 headerCell.tapCallback = this.createSortCallback_(i); | |
483 // Set arrow position, depending on the sortColumnIndex. | |
484 if (this.sortColumnIndex_ === i) | |
485 headerCell.sideContent = this.sortDescending_ ? | |
486 DESCENDING_ARROW : ASCENDING_ARROW; | |
487 else | |
488 headerCell.sideContent = UNSORTED_ARROW; | |
489 } | 736 } |
490 | 737 |
491 Polymer.dom(td).appendChild(headerCell); | 738 function getTD(cur) { |
492 this.headerCells_.push(headerCell); | 739 if (cur === trElement) |
493 } | 740 throw new Error('woah'); |
494 }, | 741 if (cur.parentElement === trElement) |
495 | 742 return cur; |
496 applySizes_: function() { | 743 return getTD(cur.parentElement); |
497 if (this.tableRows_.length === 0 && !this.showHeader) | |
498 return; | |
499 var rowToRemoveSizing; | |
500 var rowToSize; | |
501 if (this.showHeader) { | |
502 rowToSize = this.$.head.children[0]; | |
503 rowToRemoveSizing = this.$.body.children[0]; | |
504 } else { | |
505 rowToSize = this.$.body.children[0]; | |
506 rowToRemoveSizing = this.$.head.children[0]; | |
507 } | |
508 for (var i = 0; i < this.tableColumns_.length; i++) { | |
509 if (rowToRemoveSizing && rowToRemoveSizing.children[i]) { | |
510 var tdToRemoveSizing = rowToRemoveSizing.children[i]; | |
511 tdToRemoveSizing.style.minWidth = ''; | |
512 tdToRemoveSizing.style.width = ''; | |
513 } | 744 } |
514 | 745 |
515 // Apply sizing. | 746 // If the row/cell can be selected and it's not selected yet, |
516 var td = rowToSize.children[i]; | 747 // select it. |
517 | 748 if (isSelectable && this.selectionMode_ !== SelectionMode.NONE) { |
518 var delta; | 749 var shouldSelect = false; |
519 if (this.columnsWithExpandButtons_.indexOf(i) !== -1) { | 750 var columnIndex = getTD(e.target).columnIndex; |
520 td.style.paddingLeft = BASIC_INDENTATION + 'px'; | 751 switch (this.selectionMode_) { |
521 delta = BASIC_INDENTATION + 'px'; | 752 case SelectionMode.ROW: |
522 } else { | 753 shouldSelect = this.selectedTableRowInfo_ !== rowInfo; |
523 delta = undefined; | 754 break; |
524 } | 755 |
525 | 756 case SelectionMode.CELL: |
526 function calc(base, delta) { | 757 if (this.doesColumnIndexSupportSelection(columnIndex)) { |
527 if (delta) | 758 shouldSelect = this.selectedTableRowInfo_ !== rowInfo || |
528 return 'calc(' + base + ' - ' + delta + ')'; | 759 this.selectedColumnIndex_ !== columnIndex; |
529 else | 760 } |
530 return base; | 761 break; |
531 } | 762 |
532 | 763 default: |
533 var w = this.tableColumns_[i].width; | 764 throw new Error('Invalid selection mode ' + |
534 if (w) { | 765 this.selectionMode_); |
535 if (/\d+px/.test(w)) { | 766 } |
536 td.style.minWidth = calc(w, delta); | 767 if (shouldSelect) { |
537 } else if (/\d+%/.test(w)) { | 768 this.didTableRowInfoGetClicked_(rowInfo, columnIndex); |
538 td.style.width = w; | 769 return; |
539 } else { | |
540 throw new Error('Unrecognized width string: ' + w); | |
541 } | 770 } |
542 } | 771 } |
543 } | 772 |
544 }, | 773 // Otherwise, if the row is expandable, expand/collapse it. |
545 | 774 if (isExpandable) { |
546 createSortCallback_: function(columnNumber) { | 775 this.setExpandedForUserRow_(tableSection, rowInfoMap, |
547 return function() { | 776 rowInfo.userRow, !rowInfo.isExpanded); |
548 var previousIndex = this.sortColumnIndex; | |
549 this.sortColumnIndex = columnNumber; | |
550 if (previousIndex !== columnNumber) | |
551 this.sortDescending = false; | |
552 else | |
553 this.sortDescending = !this.sortDescending; | |
554 }.bind(this); | |
555 }, | |
556 | |
557 generateTableRowNodes_: function(tableSection, userRows, rowInfoMap, | |
558 indentation, lastAddedRow, | |
559 parentRowInfo) { | |
560 if (this.sortColumnIndex_ !== undefined && | |
561 tableSection === this.$.body) { | |
562 userRows = userRows.slice(); // Don't mess with the input data. | |
563 userRows.sort(function(rowA, rowB) { | |
564 var c = this.tableColumns_[this.sortColumnIndex_].cmp( | |
565 rowA, rowB); | |
566 if (this.sortDescending_) | |
567 c = -c; | |
568 return c; | |
569 }.bind(this)); | |
570 } | |
571 | |
572 for (var i = 0; i < userRows.length; i++) { | |
573 var userRow = userRows[i]; | |
574 var rowInfo = this.getOrCreateRowInfoFor_(rowInfoMap, userRow, | |
575 parentRowInfo); | |
576 var htmlNode = this.getHTMLNodeForRowInfo_( | |
577 tableSection, rowInfo, rowInfoMap, indentation); | |
578 | |
579 if (lastAddedRow === undefined) { | |
580 // Put first into the table. | |
581 Polymer.dom(tableSection).insertBefore( | |
582 htmlNode, Polymer.dom(tableSection).firstChild); | |
583 } else { | |
584 // This is shorthand for insertAfter(htmlNode, lastAdded). | |
585 var nextSiblingOfLastAdded = Polymer.dom(lastAddedRow).nextSibling; | |
586 Polymer.dom(tableSection).insertBefore( | |
587 htmlNode, nextSiblingOfLastAdded); | |
588 } | 777 } |
589 this.updateTabIndexForTableRowNode_(htmlNode); | 778 }.bind(this)); |
590 | 779 } |
591 lastAddedRow = htmlNode; | 780 |
592 if (!rowInfo.isExpanded) | 781 return rowInfo.htmlNode; |
593 continue; | 782 }, |
594 | 783 |
595 // Append subrows now. | 784 removeSubNodes_: function(tableSection, rowInfo, rowInfoMap) { |
596 lastAddedRow = this.generateTableRowNodes_( | 785 if (rowInfo.userRow[this.subRowsPropertyName_] === undefined) |
597 tableSection, userRow[this.subRowsPropertyName_], rowInfoMap, | 786 return; |
598 indentation + 1, lastAddedRow, rowInfo); | 787 for (var i = 0; |
599 } | 788 i < rowInfo.userRow[this.subRowsPropertyName_].length; i++) { |
600 return lastAddedRow; | 789 var subRow = rowInfo.userRow[this.subRowsPropertyName_][i]; |
601 }, | 790 var subRowInfo = rowInfoMap.get(subRow); |
602 | 791 if (!subRowInfo) |
603 getOrCreateRowInfoFor_: function(rowInfoMap, userRow, parentRowInfo) { | 792 continue; |
604 var rowInfo = undefined; | 793 |
605 | 794 var subNode = subRowInfo.htmlNode; |
606 if (rowInfoMap.has(userRow)) { | 795 if (subNode && Polymer.dom(subNode).parentNode === tableSection) { |
607 rowInfo = rowInfoMap.get(userRow); | 796 Polymer.dom(tableSection).removeChild(subNode); |
| 797 this.removeSubNodes_(tableSection, subRowInfo, rowInfoMap); |
| 798 } |
| 799 } |
| 800 }, |
| 801 |
| 802 scheduleRebuildHeaders_: function() { |
| 803 this.headerDirty_ = true; |
| 804 this.scheduleRebuild_(); |
| 805 }, |
| 806 |
| 807 scheduleRebuildBody_: function() { |
| 808 this.bodyDirty_ = true; |
| 809 this.scheduleRebuild_(); |
| 810 }, |
| 811 |
| 812 scheduleRebuildFooter_: function() { |
| 813 this.footerDirty_ = true; |
| 814 this.scheduleRebuild_(); |
| 815 }, |
| 816 |
| 817 scheduleRebuild_: function() { |
| 818 if (this.rebuildPending_) |
| 819 return; |
| 820 this.rebuildPending_ = true; |
| 821 setTimeout(function() { |
| 822 this.rebuildPending_ = false; |
| 823 this.rebuild(); |
| 824 }.bind(this), 0); |
| 825 }, |
| 826 |
| 827 rebuildIfNeeded_: function() { |
| 828 this.rebuild(); |
| 829 }, |
| 830 |
| 831 rebuild: function() { |
| 832 var wasBodyOrHeaderDirty = this.headerDirty_ || this.bodyDirty_; |
| 833 |
| 834 if (this.headerDirty_) { |
| 835 this.generateHeaderColumns_(); |
| 836 this.headerDirty_ = false; |
| 837 } |
| 838 if (this.bodyDirty_) { |
| 839 Polymer.dom(this.$.cols).textContent = ''; |
| 840 this.generateTableColNodes_(); |
| 841 Polymer.dom(this.$.body).textContent = ''; |
| 842 this.generateTableRowNodes_( |
| 843 this.$.body, |
| 844 this.tableRows_, this.tableRowsInfo_, 0, |
| 845 undefined, undefined); |
| 846 if (this.tableRows_.length === 0 && this.emptyValue_ !== undefined) { |
| 847 var trElement = this.ownerDocument.createElement('tr'); |
| 848 Polymer.dom(this.$.body).appendChild(trElement); |
| 849 Polymer.dom(trElement).classList.add('empty-row'); |
| 850 var td = this.ownerDocument.createElement('td'); |
| 851 Polymer.dom(trElement).appendChild(td); |
| 852 td.colSpan = this.tableColumns_.length; |
| 853 var emptyValue = this.emptyValue_; |
| 854 Polymer.dom(td).appendChild( |
| 855 tr.ui.b.asHTMLOrTextNode(emptyValue, this.ownerDocument)); |
| 856 } |
| 857 this.bodyDirty_ = false; |
| 858 } |
| 859 |
| 860 if (wasBodyOrHeaderDirty) |
| 861 this.applySizes_(); |
| 862 |
| 863 if (this.footerDirty_) { |
| 864 Polymer.dom(this.$.foot).textContent = ''; |
| 865 this.generateTableRowNodes_( |
| 866 this.$.foot, |
| 867 this.tableFooterRows_, this.tableFooterRowsInfo_, 0, |
| 868 undefined, undefined); |
| 869 if (this.tableFooterRowsInfo_.length) { |
| 870 Polymer.dom(this.$.body).classList.add('has-footer'); |
608 } else { | 871 } else { |
609 rowInfo = { | 872 Polymer.dom(this.$.body).classList.remove('has-footer'); |
610 userRow: userRow, | 873 } |
611 htmlNode: undefined, | 874 this.footerDirty_ = false; |
612 parentRowInfo: parentRowInfo | 875 } |
613 }; | 876 }, |
614 rowInfoMap.set(userRow, rowInfo); | 877 |
615 } | 878 appendNewElement_: function(parent, tagName) { |
616 | 879 var element = parent.ownerDocument.createElement(tagName); |
617 // Recompute isExpanded in case defaultExpansionStateCallback_ has | 880 Polymer.dom(parent).appendChild(element); |
618 // changed. | 881 return element; |
619 rowInfo.isExpanded = this.getExpandedForUserRow_(userRow); | 882 }, |
620 | 883 |
621 return rowInfo; | 884 getExpandedForTableRow: function(userRow) { |
622 }, | 885 this.rebuildIfNeeded_(); |
623 | 886 var rowInfo = this.tableRowsInfo_.get(userRow); |
624 customizeTableRow_: function(userRow, trElement) { | 887 if (rowInfo === undefined) |
625 if (!this.customizeTableRowCallback_) | 888 throw new Error('Row has not been seen, must expand its parents'); |
| 889 return rowInfo.isExpanded; |
| 890 }, |
| 891 |
| 892 getExpandedForUserRow_: function(userRow) { |
| 893 if (userRow[this.subRowsPropertyName_] === undefined) |
| 894 return false; |
| 895 if (userRow[this.subRowsPropertyName_].length === 0) |
| 896 return false; |
| 897 if (userRow.isExpanded) |
| 898 return true; |
| 899 if (userRow.isExpanded === false) |
| 900 return false; |
| 901 |
| 902 var rowInfo = this.tableRowsInfo_.get(userRow); |
| 903 if (rowInfo && rowInfo.isExpanded) |
| 904 return true; |
| 905 |
| 906 if (this.defaultExpansionStateCallback_ === undefined) |
| 907 return false; |
| 908 |
| 909 var parentUserRow = undefined; |
| 910 if (rowInfo && rowInfo.parentRowInfo) |
| 911 parentUserRow = rowInfo.parentRowInfo.userRow; |
| 912 |
| 913 return this.defaultExpansionStateCallback_( |
| 914 userRow, parentUserRow); |
| 915 }, |
| 916 |
| 917 setExpandedForTableRow: function(userRow, expanded) { |
| 918 this.rebuildIfNeeded_(); |
| 919 var rowInfo = this.tableRowsInfo_.get(userRow); |
| 920 if (rowInfo === undefined) |
| 921 throw new Error('Row has not been seen, must expand its parents'); |
| 922 return this.setExpandedForUserRow_(this.$.body, this.tableRowsInfo_, |
| 923 userRow, expanded); |
| 924 }, |
| 925 |
| 926 setExpandedForUserRow_: function(tableSection, rowInfoMap, |
| 927 userRow, expanded) { |
| 928 this.rebuildIfNeeded_(); |
| 929 |
| 930 var rowInfo = rowInfoMap.get(userRow); |
| 931 if (rowInfo === undefined) |
| 932 throw new Error('Row has not been seen, must expand its parents'); |
| 933 |
| 934 rowInfo.isExpanded = !!expanded; |
| 935 // If no node, then nothing further needs doing. |
| 936 if (rowInfo.htmlNode === undefined) |
| 937 return; |
| 938 |
| 939 // If its detached, then nothing needs doing. |
| 940 if (rowInfo.htmlNode.parentElement !== tableSection) |
| 941 return; |
| 942 |
| 943 // Otherwise, rebuild. |
| 944 var expandButton = |
| 945 Polymer.dom(rowInfo.htmlNode).querySelector('expand-button'); |
| 946 if (rowInfo.isExpanded) { |
| 947 Polymer.dom(expandButton).classList.add('button-expanded'); |
| 948 var lastAddedRow = rowInfo.htmlNode; |
| 949 if (rowInfo.userRow[this.subRowsPropertyName_]) { |
| 950 this.generateTableRowNodes_( |
| 951 tableSection, |
| 952 rowInfo.userRow[this.subRowsPropertyName_], rowInfoMap, |
| 953 rowInfo.indentation + 1, |
| 954 lastAddedRow, rowInfo); |
| 955 } |
| 956 } else { |
| 957 Polymer.dom(expandButton).classList.remove('button-expanded'); |
| 958 this.removeSubNodes_(tableSection, rowInfo, rowInfoMap); |
| 959 } |
| 960 |
| 961 this.maybeUpdateSelectedRow_(); |
| 962 }, |
| 963 |
| 964 get selectionMode() { |
| 965 return this.selectionMode_; |
| 966 }, |
| 967 |
| 968 set selectionMode(selectionMode) { |
| 969 if (!tr.b.dictionaryContainsValue(SelectionMode, selectionMode)) |
| 970 throw new Error('Invalid selection mode ' + selectionMode); |
| 971 this.rebuildIfNeeded_(); |
| 972 this.selectionMode_ = selectionMode; |
| 973 this.didSelectionStateChange_(); |
| 974 }, |
| 975 |
| 976 get rowHighlightStyle() { |
| 977 return this.rowHighlightStyle_; |
| 978 }, |
| 979 |
| 980 set rowHighlightStyle(rowHighlightStyle) { |
| 981 if (!tr.b.dictionaryContainsValue(HighlightStyle, rowHighlightStyle)) |
| 982 throw new Error('Invalid row highlight style ' + rowHighlightStyle); |
| 983 this.rebuildIfNeeded_(); |
| 984 this.rowHighlightStyle_ = rowHighlightStyle; |
| 985 this.didSelectionStateChange_(); |
| 986 }, |
| 987 |
| 988 get resolvedRowHighlightStyle() { |
| 989 if (this.rowHighlightStyle_ !== HighlightStyle.DEFAULT) |
| 990 return this.rowHighlightStyle_; |
| 991 switch (this.selectionMode_) { |
| 992 case SelectionMode.NONE: |
| 993 return HighlightStyle.NONE; |
| 994 case SelectionMode.ROW: |
| 995 return HighlightStyle.DARK; |
| 996 case SelectionMode.CELL: |
| 997 return HighlightStyle.LIGHT; |
| 998 default: |
| 999 throw new Error('Invalid selection mode ' + selectionMode); |
| 1000 } |
| 1001 }, |
| 1002 |
| 1003 get cellHighlightStyle() { |
| 1004 return this.cellHighlightStyle_; |
| 1005 }, |
| 1006 |
| 1007 set cellHighlightStyle(cellHighlightStyle) { |
| 1008 if (!tr.b.dictionaryContainsValue(HighlightStyle, cellHighlightStyle)) |
| 1009 throw new Error('Invalid cell highlight style ' + cellHighlightStyle); |
| 1010 this.rebuildIfNeeded_(); |
| 1011 this.cellHighlightStyle_ = cellHighlightStyle; |
| 1012 this.didSelectionStateChange_(); |
| 1013 }, |
| 1014 |
| 1015 get resolvedCellHighlightStyle() { |
| 1016 if (this.cellHighlightStyle_ !== HighlightStyle.DEFAULT) |
| 1017 return this.cellHighlightStyle_; |
| 1018 switch (this.selectionMode_) { |
| 1019 case SelectionMode.NONE: |
| 1020 case SelectionMode.ROW: |
| 1021 return HighlightStyle.NONE; |
| 1022 case SelectionMode.CELL: |
| 1023 return HighlightStyle.DARK; |
| 1024 default: |
| 1025 throw new Error('Invalid selection mode ' + selectionMode); |
| 1026 } |
| 1027 }, |
| 1028 |
| 1029 setHighlightStyle_: function(highlightAttribute, resolvedHighlightStyle) { |
| 1030 switch (resolvedHighlightStyle) { |
| 1031 case HighlightStyle.NONE: |
| 1032 Polymer.dom(this.$.body).removeAttribute(highlightAttribute); |
| 1033 break; |
| 1034 case HighlightStyle.LIGHT: |
| 1035 Polymer.dom(this.$.body).setAttribute(highlightAttribute, 'light'); |
| 1036 break; |
| 1037 case HighlightStyle.DARK: |
| 1038 Polymer.dom(this.$.body).setAttribute(highlightAttribute, 'dark'); |
| 1039 break; |
| 1040 default: |
| 1041 throw new Error('Invalid resolved highlight style ' + |
| 1042 resolvedHighlightStyle); |
| 1043 } |
| 1044 }, |
| 1045 |
| 1046 didSelectionStateChange_: function() { |
| 1047 this.setHighlightStyle_('row-highlight-style', |
| 1048 this.resolvedRowHighlightStyle); |
| 1049 this.setHighlightStyle_('cell-highlight-style', |
| 1050 this.resolvedCellHighlightStyle); |
| 1051 |
| 1052 for (var i = 0; i < Polymer.dom(this.$.body).children.length; i++) { |
| 1053 this.updateTabIndexForTableRowNode_( |
| 1054 Polymer.dom(this.$.body).children[i]); |
| 1055 } |
| 1056 this.maybeUpdateSelectedRow_(); |
| 1057 }, |
| 1058 |
| 1059 maybeUpdateSelectedRow_: function() { |
| 1060 if (this.selectedTableRowInfo_ === undefined) |
| 1061 return; |
| 1062 |
| 1063 // Selection may be off. |
| 1064 if (this.selectionMode_ === SelectionMode.NONE) { |
| 1065 this.removeSelectedState_(); |
| 1066 this.selectedTableRowInfo_ = undefined; |
| 1067 return; |
| 1068 } |
| 1069 |
| 1070 // selectedUserRow may not be visible |
| 1071 function isVisible(rowInfo) { |
| 1072 if (!rowInfo.htmlNode) |
| 1073 return false; |
| 1074 return !!rowInfo.htmlNode.parentElement; |
| 1075 } |
| 1076 if (isVisible(this.selectedTableRowInfo_)) { |
| 1077 this.updateSelectedState_(); |
| 1078 return; |
| 1079 } |
| 1080 |
| 1081 this.removeSelectedState_(); |
| 1082 var curRowInfo = this.selectedTableRowInfo_; |
| 1083 while (curRowInfo && !isVisible(curRowInfo)) |
| 1084 curRowInfo = curRowInfo.parentRowInfo; |
| 1085 |
| 1086 this.selectedTableRowInfo_ = curRowInfo; |
| 1087 if (this.selectedTableRowInfo_) |
| 1088 this.updateSelectedState_(); |
| 1089 }, |
| 1090 |
| 1091 didTableRowInfoGetClicked_: function(rowInfo, columnIndex) { |
| 1092 switch (this.selectionMode_) { |
| 1093 case SelectionMode.NONE: |
626 return; | 1094 return; |
627 this.customizeTableRowCallback_(userRow, trElement); | 1095 |
628 }, | 1096 case SelectionMode.CELL: |
629 | 1097 if (!this.doesColumnIndexSupportSelection(columnIndex)) |
630 getHTMLNodeForRowInfo_: function(tableSection, rowInfo, | 1098 return; |
631 rowInfoMap, indentation) { | 1099 if (this.selectedColumnIndex !== columnIndex) |
632 if (rowInfo.htmlNode) { | 1100 this.selectedColumnIndex = columnIndex; |
633 this.customizeTableRow_(rowInfo.userRow, rowInfo.htmlNode); | 1101 // Fall through. |
634 return rowInfo.htmlNode; | 1102 |
635 } | 1103 case SelectionMode.ROW: |
636 | 1104 if (this.selectedTableRowInfo_ !== rowInfo) |
637 var INDENT_SPACE = indentation * 16; | 1105 this.selectedTableRow = rowInfo.userRow; |
638 var INDENT_SPACE_NO_BUTTON = indentation * 16 + BASIC_INDENTATION; | 1106 } |
639 var trElement = this.ownerDocument.createElement('tr'); | 1107 }, |
640 rowInfo.htmlNode = trElement; | 1108 |
641 rowInfo.indentation = indentation; | 1109 /** |
642 trElement.rowInfo = rowInfo; | 1110 * If the selectionMode is CELL and a cell is selected, |
643 this.customizeTableRow_(rowInfo.userRow, trElement); | 1111 * return an object containing the row, column, and value of the selected |
644 | 1112 * cell. |
645 for (var i = 0; i < this.tableColumns_.length;) { | 1113 * |
646 var td = this.appendNewElement_(trElement, 'td'); | 1114 * @return {undefined|!Object} |
647 td.columnIndex = i; | 1115 */ |
648 | 1116 get selectedCell() { |
649 var column = this.tableColumns_[i]; | 1117 var row = this.selectedTableRow; |
650 var value = column.value(rowInfo.userRow); | 1118 var columnIndex = this.selectedColumnIndex; |
651 var colSpan = column.colSpan ? column.colSpan : 1; | 1119 if (row === undefined || columnIndex === undefined || |
652 td.style.colSpan = colSpan; | 1120 this.tableColumns_.length <= columnIndex) |
653 | 1121 return undefined; |
654 switch (column.align) { | 1122 var column = this.tableColumns_[columnIndex]; |
655 case undefined: | 1123 return { |
656 case ColumnAlignment.LEFT: | 1124 row: row, |
657 break; | 1125 column: column, |
658 | 1126 value: column.value(row) |
659 case ColumnAlignment.RIGHT: | 1127 }; |
660 td.style.textAlign = 'right'; | 1128 }, |
661 break; | 1129 |
662 | 1130 /** |
663 default: | 1131 * If a selectable column is selected, return the object describing the |
664 throw new Error('Invalid alignment of column at index=' + i + | 1132 * selected column. |
665 ': ' + column.align); | 1133 * |
666 } | 1134 * Columns with |selectable:true| can be selected independently of rows |
667 | 1135 * and cells. So it is possible to select column 0 and cell [0,0], or |
668 if (this.doesColumnIndexSupportSelection(i)) | 1136 * column 1 and cell [0,0], for example. See |selectedCell| for how to |
669 Polymer.dom(td).classList.add('supports-selection'); | 1137 * access the selected cell when the selectionMode is CELL. |
670 | 1138 * |
671 if (this.columnsWithExpandButtons_.indexOf(i) != -1) { | 1139 * |selectedTableColumn| is entirely independent of |selectedColumnIndex|. |
672 if (rowInfo.userRow[this.subRowsPropertyName_] && | 1140 * When the table selectionMode is CELL, use |selectedTableRow| and |
673 rowInfo.userRow[this.subRowsPropertyName_].length > 0) { | 1141 * |selectedColumnIndex| to find the selected cell. |
674 td.style.paddingLeft = INDENT_SPACE + 'px'; | 1142 * When one or more columns have |selectable:true|, then use |
675 var expandButton = this.appendNewElement_(td, | 1143 * |selectedTableColumn| to find the selected column, which may be either |
676 'expand-button'); | 1144 * the same as or different from |selectedColumnIndex|, if a cell is also |
677 Polymer.dom(expandButton).textContent = RIGHT_ARROW; | 1145 * selected. |
678 if (rowInfo.isExpanded) | 1146 * |
679 Polymer.dom(expandButton).classList.add('button-expanded'); | 1147 * @return {undefined|!Object} column |
680 } else { | 1148 */ |
681 td.style.paddingLeft = INDENT_SPACE_NO_BUTTON + 'px'; | 1149 get selectedTableColumn() { |
| 1150 for (var i = 0; i < this.headerCells_.length; i++) { |
| 1151 if (this.headerCells_[i].selected) |
| 1152 return this.tableColumns_[i]; |
| 1153 } |
| 1154 return undefined; |
| 1155 }, |
| 1156 |
| 1157 /** |
| 1158 * See |get selectedTableColumn()|. |column| must be one of the elements |
| 1159 * of this.tableColumns. |
| 1160 * |
| 1161 * @param {!Object} column |
| 1162 */ |
| 1163 set selectedTableColumn(column) { |
| 1164 if (column !== undefined) { |
| 1165 var index = this.tableColumns.indexOf(column); |
| 1166 if (index < 0) |
| 1167 throw new Error('Cannot select unknown column', column); |
| 1168 |
| 1169 if (!column.selectable) |
| 1170 throw new Error('Cannot select un-selectable column', column); |
| 1171 |
| 1172 var cell = this.headerCells_[index]; |
| 1173 cell.selected = true; |
| 1174 } |
| 1175 var e = new tr.b.Event('selected-column-changed'); |
| 1176 e.column = column; |
| 1177 e.selected = column !== undefined; |
| 1178 cell.dispatchEvent(e); |
| 1179 }, |
| 1180 |
| 1181 get selectedTableRow() { |
| 1182 if (!this.selectedTableRowInfo_) |
| 1183 return undefined; |
| 1184 return this.selectedTableRowInfo_.userRow; |
| 1185 }, |
| 1186 |
| 1187 set selectedTableRow(userRow) { |
| 1188 this.rebuildIfNeeded_(); |
| 1189 if (this.selectionMode_ === SelectionMode.NONE) |
| 1190 throw new Error('Selection is off.'); |
| 1191 |
| 1192 var rowInfo; |
| 1193 if (userRow === undefined) { |
| 1194 rowInfo = undefined; |
| 1195 } else { |
| 1196 rowInfo = this.tableRowsInfo_.get(userRow); |
| 1197 if (!rowInfo) |
| 1198 throw new Error('Row has not been seen, must expand its parents.'); |
| 1199 } |
| 1200 |
| 1201 var e = this.prepareToChangeSelection_(); |
| 1202 this.selectedTableRowInfo_ = rowInfo; |
| 1203 |
| 1204 if (this.selectedTableRowInfo_ === undefined) { |
| 1205 this.selectedColumnIndex_ = undefined; |
| 1206 this.removeSelectedState_(); |
| 1207 } else { |
| 1208 switch (this.selectionMode_) { |
| 1209 case SelectionMode.ROW: |
| 1210 this.selectedColumnIndex_ = undefined; |
| 1211 break; |
| 1212 |
| 1213 case SelectionMode.CELL: |
| 1214 if (this.selectedColumnIndex_ === undefined) { |
| 1215 var i = this.getFirstSelectableColumnIndex_(); |
| 1216 if (i == -1) |
| 1217 throw new Error('Cannot find a selectable column.'); |
| 1218 this.selectedColumnIndex_ = i; |
682 } | 1219 } |
683 } | 1220 break; |
684 | 1221 |
685 if (value !== undefined) | 1222 default: |
686 Polymer.dom(td).appendChild( | 1223 throw new Error('Invalid selection mode ' + this.selectionMode_); |
687 tr.ui.b.asHTMLOrTextNode(value, this.ownerDocument)); | 1224 } |
688 | 1225 this.updateSelectedState_(); |
689 i += colSpan; | 1226 } |
690 } | 1227 |
691 | 1228 this.dispatchEvent(e); |
692 var isSelectable = tableSection === this.$.body; | 1229 }, |
693 var isExpandable = rowInfo.userRow[this.subRowsPropertyName_] && | 1230 |
694 rowInfo.userRow[this.subRowsPropertyName_].length; | 1231 updateTabIndexForTableRowNode_: function(row) { |
695 | 1232 if (this.selectionMode_ === SelectionMode.ROW) |
696 if (isSelectable || isExpandable) { | 1233 row.tabIndex = 0; |
697 trElement.addEventListener('click', function(e) { | 1234 else |
698 e.stopPropagation(); | 1235 Polymer.dom(row).removeAttribute('tabIndex'); |
699 if (e.target.tagName == 'EXPAND-BUTTON') { | 1236 |
700 this.setExpandedForUserRow_( | 1237 var enableCellTab = this.selectionMode_ === SelectionMode.CELL; |
701 tableSection, rowInfoMap, | 1238 for (var i = 0; i < this.tableColumns_.length; i++) { |
702 rowInfo.userRow, !rowInfo.isExpanded); | 1239 var cell = Polymer.dom(row).children[i]; |
| 1240 if (enableCellTab && this.doesColumnIndexSupportSelection(i)) |
| 1241 cell.tabIndex = 0; |
| 1242 else |
| 1243 Polymer.dom(cell).removeAttribute('tabIndex'); |
| 1244 } |
| 1245 }, |
| 1246 |
| 1247 prepareToChangeSelection_: function() { |
| 1248 var e = new tr.b.Event('selection-changed'); |
| 1249 var previousSelectedRowInfo = this.selectedTableRowInfo_; |
| 1250 if (previousSelectedRowInfo) |
| 1251 e.previousSelectedTableRow = previousSelectedRowInfo.userRow; |
| 1252 else |
| 1253 e.previousSelectedTableRow = undefined; |
| 1254 |
| 1255 this.removeSelectedState_(); |
| 1256 |
| 1257 return e; |
| 1258 }, |
| 1259 |
| 1260 removeSelectedState_: function() { |
| 1261 this.setSelectedState_(false); |
| 1262 }, |
| 1263 |
| 1264 updateSelectedState_: function() { |
| 1265 this.setSelectedState_(true); |
| 1266 }, |
| 1267 |
| 1268 setSelectedState_: function(select) { |
| 1269 if (this.selectedTableRowInfo_ === undefined) |
| 1270 return; |
| 1271 |
| 1272 // Row selection. |
| 1273 var rowNode = this.selectedTableRowInfo_.htmlNode; |
| 1274 if (select) |
| 1275 Polymer.dom(rowNode).setAttribute('selected', true); |
| 1276 else |
| 1277 Polymer.dom(rowNode).removeAttribute('selected'); |
| 1278 |
| 1279 // Cell selection (if applicable). |
| 1280 var cellNode = Polymer.dom(rowNode).children[this.selectedColumnIndex_]; |
| 1281 if (!cellNode) |
| 1282 return; |
| 1283 if (select) |
| 1284 Polymer.dom(cellNode).setAttribute('selected', true); |
| 1285 else |
| 1286 Polymer.dom(cellNode).removeAttribute('selected'); |
| 1287 }, |
| 1288 |
| 1289 doesColumnIndexSupportSelection: function(columnIndex) { |
| 1290 var columnInfo = this.tableColumns_[columnIndex]; |
| 1291 var scs = columnInfo.supportsCellSelection; |
| 1292 if (scs === false) |
| 1293 return false; |
| 1294 return true; |
| 1295 }, |
| 1296 |
| 1297 getFirstSelectableColumnIndex_: function() { |
| 1298 for (var i = 0; i < this.tableColumns_.length; i++) { |
| 1299 if (this.doesColumnIndexSupportSelection(i)) |
| 1300 return i; |
| 1301 } |
| 1302 return -1; |
| 1303 }, |
| 1304 |
| 1305 getSelectableNodeGivenTableRowNode_: function(htmlNode) { |
| 1306 switch (this.selectionMode_) { |
| 1307 case SelectionMode.ROW: |
| 1308 return htmlNode; |
| 1309 |
| 1310 case SelectionMode.CELL: |
| 1311 return Polymer.dom(htmlNode).children[this.selectedColumnIndex_]; |
| 1312 |
| 1313 default: |
| 1314 throw new Error('Invalid selection mode ' + this.selectionMode_); |
| 1315 } |
| 1316 }, |
| 1317 |
| 1318 get selectedColumnIndex() { |
| 1319 if (this.selectionMode_ !== SelectionMode.CELL) |
| 1320 return undefined; |
| 1321 return this.selectedColumnIndex_; |
| 1322 }, |
| 1323 |
| 1324 set selectedColumnIndex(selectedColumnIndex) { |
| 1325 this.rebuildIfNeeded_(); |
| 1326 if (this.selectionMode_ === SelectionMode.NONE) |
| 1327 throw new Error('Selection is off.'); |
| 1328 if (selectedColumnIndex < 0 || |
| 1329 selectedColumnIndex >= this.tableColumns_.length) |
| 1330 throw new Error('Invalid index'); |
| 1331 if (!this.doesColumnIndexSupportSelection(selectedColumnIndex)) |
| 1332 throw new Error('Selection is not supported on this column'); |
| 1333 |
| 1334 var e = this.prepareToChangeSelection_(); |
| 1335 this.selectedColumnIndex_ = selectedColumnIndex; |
| 1336 if (this.selectedColumnIndex_ === undefined) |
| 1337 this.selectedTableRowInfo_ = undefined; |
| 1338 this.updateSelectedState_(); |
| 1339 |
| 1340 this.dispatchEvent(e); |
| 1341 }, |
| 1342 |
| 1343 onKeyDown_: function(e) { |
| 1344 if (this.selectionMode_ === SelectionMode.NONE) |
| 1345 return; |
| 1346 if (this.selectedTableRowInfo_ === undefined) |
| 1347 return; |
| 1348 |
| 1349 var code_to_command_names = { |
| 1350 13: 'ENTER', |
| 1351 37: 'ARROW_LEFT', |
| 1352 38: 'ARROW_UP', |
| 1353 39: 'ARROW_RIGHT', |
| 1354 40: 'ARROW_DOWN' |
| 1355 }; |
| 1356 var cmdName = code_to_command_names[e.keyCode]; |
| 1357 if (cmdName === undefined) |
| 1358 return; |
| 1359 |
| 1360 e.stopPropagation(); |
| 1361 e.preventDefault(); |
| 1362 this.performKeyCommand_(cmdName); |
| 1363 }, |
| 1364 |
| 1365 performKeyCommand_: function(cmdName) { |
| 1366 this.rebuildIfNeeded_(); |
| 1367 |
| 1368 var rowInfo = this.selectedTableRowInfo_; |
| 1369 var htmlNode = rowInfo.htmlNode; |
| 1370 if (cmdName === 'ARROW_UP') { |
| 1371 var prev = htmlNode.previousElementSibling; |
| 1372 if (prev) { |
| 1373 tr.ui.b.scrollIntoViewIfNeeded(prev); |
| 1374 this.selectedTableRow = prev.rowInfo.userRow; |
| 1375 this.focusSelected_(); |
| 1376 return; |
| 1377 } |
| 1378 return; |
| 1379 } |
| 1380 |
| 1381 if (cmdName === 'ARROW_DOWN') { |
| 1382 var next = htmlNode.nextElementSibling; |
| 1383 if (next) { |
| 1384 tr.ui.b.scrollIntoViewIfNeeded(next); |
| 1385 this.selectedTableRow = next.rowInfo.userRow; |
| 1386 this.focusSelected_(); |
| 1387 return; |
| 1388 } |
| 1389 return; |
| 1390 } |
| 1391 |
| 1392 if (cmdName === 'ARROW_RIGHT') { |
| 1393 switch (this.selectionMode_) { |
| 1394 case SelectionMode.ROW: |
| 1395 if (rowInfo.userRow[this.subRowsPropertyName_] === undefined) |
| 1396 return; |
| 1397 if (rowInfo.userRow[this.subRowsPropertyName_].length === 0) |
| 1398 return; |
| 1399 |
| 1400 if (!rowInfo.isExpanded) |
| 1401 this.setExpandedForTableRow(rowInfo.userRow, true); |
| 1402 this.selectedTableRow = |
| 1403 htmlNode.nextElementSibling.rowInfo.userRow; |
| 1404 this.focusSelected_(); |
| 1405 return; |
| 1406 |
| 1407 case SelectionMode.CELL: |
| 1408 var newIndex = this.selectedColumnIndex_ + 1; |
| 1409 if (newIndex >= this.tableColumns_.length) |
| 1410 return; |
| 1411 if (!this.doesColumnIndexSupportSelection(newIndex)) |
| 1412 return; |
| 1413 this.selectedColumnIndex = newIndex; |
| 1414 this.focusSelected_(); |
| 1415 return; |
| 1416 |
| 1417 default: |
| 1418 throw new Error('Invalid selection mode ' + this.selectionMode_); |
| 1419 } |
| 1420 } |
| 1421 |
| 1422 if (cmdName === 'ARROW_LEFT') { |
| 1423 switch (this.selectionMode_) { |
| 1424 case SelectionMode.ROW: |
| 1425 if (rowInfo.isExpanded) { |
| 1426 this.setExpandedForTableRow(rowInfo.userRow, false); |
| 1427 this.focusSelected_(); |
703 return; | 1428 return; |
704 } | 1429 } |
705 | 1430 |
706 function getTD(cur) { | 1431 // Not expanded. Select parent... |
707 if (cur === trElement) | 1432 var parentRowInfo = rowInfo.parentRowInfo; |
708 throw new Error('woah'); | 1433 if (parentRowInfo) { |
709 if (cur.parentElement === trElement) | 1434 this.selectedTableRow = parentRowInfo.userRow; |
710 return cur; | 1435 this.focusSelected_(); |
711 return getTD(cur.parentElement); | 1436 return; |
712 } | 1437 } |
713 | 1438 return; |
714 // If the row/cell can be selected and it's not selected yet, | 1439 |
715 // select it. | 1440 case SelectionMode.CELL: |
716 if (isSelectable && this.selectionMode_ !== SelectionMode.NONE) { | 1441 var newIndex = this.selectedColumnIndex_ - 1; |
717 var shouldSelect = false; | 1442 if (newIndex < 0) |
718 var columnIndex = getTD(e.target).columnIndex; | 1443 return; |
719 switch (this.selectionMode_) { | 1444 if (!this.doesColumnIndexSupportSelection(newIndex)) |
720 case SelectionMode.ROW: | 1445 return; |
721 shouldSelect = this.selectedTableRowInfo_ !== rowInfo; | 1446 this.selectedColumnIndex = newIndex; |
722 break; | 1447 this.focusSelected_(); |
723 | 1448 return; |
724 case SelectionMode.CELL: | 1449 |
725 if (this.doesColumnIndexSupportSelection(columnIndex)) { | 1450 default: |
726 shouldSelect = this.selectedTableRowInfo_ !== rowInfo || | 1451 throw new Error('Invalid selection mode ' + this.selectionMode_); |
727 this.selectedColumnIndex_ !== columnIndex; | 1452 } |
728 } | 1453 } |
729 break; | 1454 |
730 | 1455 if (cmdName === 'ENTER') { |
731 default: | |
732 throw new Error('Invalid selection mode ' + | |
733 this.selectionMode_); | |
734 } | |
735 if (shouldSelect) { | |
736 this.didTableRowInfoGetClicked_(rowInfo, columnIndex); | |
737 return; | |
738 } | |
739 } | |
740 | |
741 // Otherwise, if the row is expandable, expand/collapse it. | |
742 if (isExpandable) { | |
743 this.setExpandedForUserRow_(tableSection, rowInfoMap, | |
744 rowInfo.userRow, !rowInfo.isExpanded); | |
745 } | |
746 }.bind(this)); | |
747 } | |
748 | |
749 return rowInfo.htmlNode; | |
750 }, | |
751 | |
752 removeSubNodes_: function(tableSection, rowInfo, rowInfoMap) { | |
753 if (rowInfo.userRow[this.subRowsPropertyName_] === undefined) | 1456 if (rowInfo.userRow[this.subRowsPropertyName_] === undefined) |
754 return; | 1457 return; |
755 for (var i = 0; | 1458 if (rowInfo.userRow[this.subRowsPropertyName_].length === 0) |
756 i < rowInfo.userRow[this.subRowsPropertyName_].length; i++) { | |
757 var subRow = rowInfo.userRow[this.subRowsPropertyName_][i]; | |
758 var subRowInfo = rowInfoMap.get(subRow); | |
759 if (!subRowInfo) | |
760 continue; | |
761 | |
762 var subNode = subRowInfo.htmlNode; | |
763 if (subNode && Polymer.dom(subNode).parentNode === tableSection) { | |
764 Polymer.dom(tableSection).removeChild(subNode); | |
765 this.removeSubNodes_(tableSection, subRowInfo, rowInfoMap); | |
766 } | |
767 } | |
768 }, | |
769 | |
770 scheduleRebuildHeaders_: function() { | |
771 this.headerDirty_ = true; | |
772 this.scheduleRebuild_(); | |
773 }, | |
774 | |
775 scheduleRebuildBody_: function() { | |
776 this.bodyDirty_ = true; | |
777 this.scheduleRebuild_(); | |
778 }, | |
779 | |
780 scheduleRebuildFooter_: function() { | |
781 this.footerDirty_ = true; | |
782 this.scheduleRebuild_(); | |
783 }, | |
784 | |
785 scheduleRebuild_: function() { | |
786 if (this.rebuildPending_) | |
787 return; | 1459 return; |
788 this.rebuildPending_ = true; | 1460 this.setExpandedForTableRow(rowInfo.userRow, !rowInfo.isExpanded); |
789 setTimeout(function() { | 1461 this.focusSelected_(); |
790 this.rebuildPending_ = false; | 1462 return; |
791 this.rebuild(); | 1463 } |
792 }.bind(this), 0); | 1464 |
793 }, | 1465 throw new Error('Unrecognized command ' + cmdName); |
794 | 1466 }, |
795 rebuildIfNeeded_: function() { | 1467 |
796 this.rebuild(); | 1468 focusSelected_: function() { |
797 }, | 1469 if (!this.selectedTableRowInfo_) |
798 | 1470 return; |
799 rebuild: function() { | 1471 var node = this.getSelectableNodeGivenTableRowNode_( |
800 var wasBodyOrHeaderDirty = this.headerDirty_ || this.bodyDirty_; | 1472 this.selectedTableRowInfo_.htmlNode); |
801 | 1473 node.focus(); |
802 if (this.headerDirty_) { | 1474 }, |
803 this.generateHeaderColumns_(); | 1475 |
804 this.headerDirty_ = false; | 1476 dispatchSortingChangedEvent_: function() { |
805 } | 1477 var e = new tr.b.Event('sort-column-changed'); |
806 if (this.bodyDirty_) { | 1478 e.sortColumnIndex = this.sortColumnIndex_; |
807 Polymer.dom(this.$.body).textContent = ''; | 1479 e.sortDescending = this.sortDescending_; |
808 this.generateTableRowNodes_( | 1480 this.dispatchEvent(e); |
809 this.$.body, | 1481 } |
810 this.tableRows_, this.tableRowsInfo_, 0, | 1482 }); |
811 undefined, undefined); | 1483 })(); |
812 if (this.tableRows_.length === 0 && this.emptyValue_ !== undefined) { | 1484 </script> |
813 var trElement = this.ownerDocument.createElement('tr'); | 1485 |
814 Polymer.dom(this.$.body).appendChild(trElement); | 1486 <dom-module id="tr-ui-b-table-header-cell"> |
815 Polymer.dom(trElement).classList.add('empty-row'); | |
816 var td = this.ownerDocument.createElement('td'); | |
817 Polymer.dom(trElement).appendChild(td); | |
818 td.colSpan = this.tableColumns_.length; | |
819 var emptyValue = this.emptyValue_; | |
820 Polymer.dom(td).appendChild( | |
821 tr.ui.b.asHTMLOrTextNode(emptyValue, this.ownerDocument)); | |
822 } | |
823 this.bodyDirty_ = false; | |
824 } | |
825 | |
826 if (wasBodyOrHeaderDirty) | |
827 this.applySizes_(); | |
828 | |
829 if (this.footerDirty_) { | |
830 Polymer.dom(this.$.foot).textContent = ''; | |
831 this.generateTableRowNodes_( | |
832 this.$.foot, | |
833 this.tableFooterRows_, this.tableFooterRowsInfo_, 0, | |
834 undefined, undefined); | |
835 if (this.tableFooterRowsInfo_.length) { | |
836 Polymer.dom(this.$.body).classList.add('has-footer'); | |
837 } else { | |
838 Polymer.dom(this.$.body).classList.remove('has-footer'); | |
839 } | |
840 this.footerDirty_ = false; | |
841 } | |
842 }, | |
843 | |
844 appendNewElement_: function(parent, tagName) { | |
845 var element = parent.ownerDocument.createElement(tagName); | |
846 Polymer.dom(parent).appendChild(element); | |
847 return element; | |
848 }, | |
849 | |
850 getExpandedForTableRow: function(userRow) { | |
851 this.rebuildIfNeeded_(); | |
852 var rowInfo = this.tableRowsInfo_.get(userRow); | |
853 if (rowInfo === undefined) | |
854 throw new Error('Row has not been seen, must expand its parents'); | |
855 return rowInfo.isExpanded; | |
856 }, | |
857 | |
858 getExpandedForUserRow_: function(userRow) { | |
859 if (userRow[this.subRowsPropertyName_] === undefined) | |
860 return false; | |
861 if (userRow[this.subRowsPropertyName_].length === 0) | |
862 return false; | |
863 if (userRow.isExpanded) | |
864 return true; | |
865 if (userRow.isExpanded === false) | |
866 return false; | |
867 | |
868 var rowInfo = this.tableRowsInfo_.get(userRow); | |
869 if (rowInfo && rowInfo.isExpanded) | |
870 return true; | |
871 | |
872 if (this.defaultExpansionStateCallback_ === undefined) | |
873 return false; | |
874 | |
875 var parentUserRow = undefined; | |
876 if (rowInfo && rowInfo.parentRowInfo) | |
877 parentUserRow = rowInfo.parentRowInfo.userRow; | |
878 | |
879 return this.defaultExpansionStateCallback_( | |
880 userRow, parentUserRow); | |
881 }, | |
882 | |
883 setExpandedForTableRow: function(userRow, expanded) { | |
884 this.rebuildIfNeeded_(); | |
885 var rowInfo = this.tableRowsInfo_.get(userRow); | |
886 if (rowInfo === undefined) | |
887 throw new Error('Row has not been seen, must expand its parents'); | |
888 return this.setExpandedForUserRow_(this.$.body, this.tableRowsInfo_, | |
889 userRow, expanded); | |
890 }, | |
891 | |
892 setExpandedForUserRow_: function(tableSection, rowInfoMap, | |
893 userRow, expanded) { | |
894 this.rebuildIfNeeded_(); | |
895 | |
896 var rowInfo = rowInfoMap.get(userRow); | |
897 if (rowInfo === undefined) | |
898 throw new Error('Row has not been seen, must expand its parents'); | |
899 | |
900 rowInfo.isExpanded = !!expanded; | |
901 // If no node, then nothing further needs doing. | |
902 if (rowInfo.htmlNode === undefined) | |
903 return; | |
904 | |
905 // If its detached, then nothing needs doing. | |
906 if (rowInfo.htmlNode.parentElement !== tableSection) | |
907 return; | |
908 | |
909 // Otherwise, rebuild. | |
910 var expandButton = Polymer.dom(rowInfo.htmlNode) | |
911 .querySelector('expand-button'); | |
912 if (rowInfo.isExpanded) { | |
913 Polymer.dom(expandButton).classList.add('button-expanded'); | |
914 var lastAddedRow = rowInfo.htmlNode; | |
915 if (rowInfo.userRow[this.subRowsPropertyName_]) { | |
916 this.generateTableRowNodes_( | |
917 tableSection, | |
918 rowInfo.userRow[this.subRowsPropertyName_], rowInfoMap, | |
919 rowInfo.indentation + 1, | |
920 lastAddedRow, rowInfo); | |
921 } | |
922 } else { | |
923 Polymer.dom(expandButton).classList.remove('button-expanded'); | |
924 this.removeSubNodes_(tableSection, rowInfo, rowInfoMap); | |
925 } | |
926 | |
927 this.maybeUpdateSelectedRow_(); | |
928 }, | |
929 | |
930 get selectionMode() { | |
931 return this.selectionMode_; | |
932 }, | |
933 | |
934 set selectionMode(selectionMode) { | |
935 if (!tr.b.dictionaryContainsValue(SelectionMode, selectionMode)) | |
936 throw new Error('Invalid selection mode ' + selectionMode); | |
937 this.rebuildIfNeeded_(); | |
938 this.selectionMode_ = selectionMode; | |
939 this.didSelectionStateChange_(); | |
940 }, | |
941 | |
942 get rowHighlightStyle() { | |
943 return this.rowHighlightStyle_; | |
944 }, | |
945 | |
946 set rowHighlightStyle(rowHighlightStyle) { | |
947 if (!tr.b.dictionaryContainsValue(HighlightStyle, rowHighlightStyle)) | |
948 throw new Error('Invalid row highlight style ' + rowHighlightStyle); | |
949 this.rebuildIfNeeded_(); | |
950 this.rowHighlightStyle_ = rowHighlightStyle; | |
951 this.didSelectionStateChange_(); | |
952 }, | |
953 | |
954 get resolvedRowHighlightStyle() { | |
955 if (this.rowHighlightStyle_ !== HighlightStyle.DEFAULT) | |
956 return this.rowHighlightStyle_; | |
957 switch (this.selectionMode_) { | |
958 case SelectionMode.NONE: | |
959 return HighlightStyle.NONE; | |
960 case SelectionMode.ROW: | |
961 return HighlightStyle.DARK; | |
962 case SelectionMode.CELL: | |
963 return HighlightStyle.LIGHT; | |
964 default: | |
965 throw new Error('Invalid selection mode ' + selectionMode); | |
966 } | |
967 }, | |
968 | |
969 get cellHighlightStyle() { | |
970 return this.cellHighlightStyle_; | |
971 }, | |
972 | |
973 set cellHighlightStyle(cellHighlightStyle) { | |
974 if (!tr.b.dictionaryContainsValue(HighlightStyle, cellHighlightStyle)) | |
975 throw new Error('Invalid cell highlight style ' + cellHighlightStyle); | |
976 this.rebuildIfNeeded_(); | |
977 this.cellHighlightStyle_ = cellHighlightStyle; | |
978 this.didSelectionStateChange_(); | |
979 }, | |
980 | |
981 get resolvedCellHighlightStyle() { | |
982 if (this.cellHighlightStyle_ !== HighlightStyle.DEFAULT) | |
983 return this.cellHighlightStyle_; | |
984 switch (this.selectionMode_) { | |
985 case SelectionMode.NONE: | |
986 case SelectionMode.ROW: | |
987 return HighlightStyle.NONE; | |
988 case SelectionMode.CELL: | |
989 return HighlightStyle.DARK; | |
990 default: | |
991 throw new Error('Invalid selection mode ' + selectionMode); | |
992 } | |
993 }, | |
994 | |
995 setHighlightStyle_: function(highlightAttribute, resolvedHighlightStyle) { | |
996 switch (resolvedHighlightStyle) { | |
997 case HighlightStyle.NONE: | |
998 Polymer.dom(this.$.body).removeAttribute(highlightAttribute); | |
999 break; | |
1000 case HighlightStyle.LIGHT: | |
1001 Polymer.dom(this.$.body).setAttribute(highlightAttribute, 'light'); | |
1002 break; | |
1003 case HighlightStyle.DARK: | |
1004 Polymer.dom(this.$.body).setAttribute(highlightAttribute, 'dark'); | |
1005 break; | |
1006 default: | |
1007 throw new Error('Invalid resolved highlight style ' + | |
1008 resolvedHighlightStyle); | |
1009 } | |
1010 }, | |
1011 | |
1012 didSelectionStateChange_: function() { | |
1013 this.setHighlightStyle_('row-highlight-style', | |
1014 this.resolvedRowHighlightStyle); | |
1015 this.setHighlightStyle_('cell-highlight-style', | |
1016 this.resolvedCellHighlightStyle); | |
1017 | |
1018 for (var i = 0; i < this.$.body.children.length; i++) | |
1019 this.updateTabIndexForTableRowNode_(this.$.body.children[i]); | |
1020 this.maybeUpdateSelectedRow_(); | |
1021 }, | |
1022 | |
1023 maybeUpdateSelectedRow_: function() { | |
1024 if (this.selectedTableRowInfo_ === undefined) | |
1025 return; | |
1026 | |
1027 // Selection may be off. | |
1028 if (this.selectionMode_ === SelectionMode.NONE) { | |
1029 this.removeSelectedState_(); | |
1030 this.selectedTableRowInfo_ = undefined; | |
1031 return; | |
1032 } | |
1033 | |
1034 // selectedUserRow may not be visible | |
1035 function isVisible(rowInfo) { | |
1036 if (!rowInfo.htmlNode) | |
1037 return false; | |
1038 return !!rowInfo.htmlNode.parentElement; | |
1039 } | |
1040 if (isVisible(this.selectedTableRowInfo_)) { | |
1041 this.updateSelectedState_(); | |
1042 return; | |
1043 } | |
1044 | |
1045 this.removeSelectedState_(); | |
1046 var curRowInfo = this.selectedTableRowInfo_; | |
1047 while (curRowInfo && !isVisible(curRowInfo)) | |
1048 curRowInfo = curRowInfo.parentRowInfo; | |
1049 | |
1050 this.selectedTableRowInfo_ = curRowInfo; | |
1051 if (this.selectedTableRowInfo_) | |
1052 this.updateSelectedState_(); | |
1053 }, | |
1054 | |
1055 didTableRowInfoGetClicked_: function(rowInfo, columnIndex) { | |
1056 switch (this.selectionMode_) { | |
1057 case SelectionMode.NONE: | |
1058 return; | |
1059 | |
1060 case SelectionMode.CELL: | |
1061 if (!this.doesColumnIndexSupportSelection(columnIndex)) | |
1062 return; | |
1063 if (this.selectedColumnIndex !== columnIndex) | |
1064 this.selectedColumnIndex = columnIndex; | |
1065 // Fall through. | |
1066 | |
1067 case SelectionMode.ROW: | |
1068 if (this.selectedTableRowInfo_ !== rowInfo) | |
1069 this.selectedTableRow = rowInfo.userRow; | |
1070 } | |
1071 }, | |
1072 | |
1073 get selectedTableRow() { | |
1074 if (!this.selectedTableRowInfo_) | |
1075 return undefined; | |
1076 return this.selectedTableRowInfo_.userRow; | |
1077 }, | |
1078 | |
1079 set selectedTableRow(userRow) { | |
1080 this.rebuildIfNeeded_(); | |
1081 if (this.selectionMode_ === SelectionMode.NONE) | |
1082 throw new Error('Selection is off.'); | |
1083 | |
1084 var rowInfo; | |
1085 if (userRow === undefined) { | |
1086 rowInfo = undefined; | |
1087 } else { | |
1088 rowInfo = this.tableRowsInfo_.get(userRow); | |
1089 if (!rowInfo) | |
1090 throw new Error('Row has not been seen, must expand its parents.'); | |
1091 } | |
1092 | |
1093 var e = this.prepareToChangeSelection_(); | |
1094 this.selectedTableRowInfo_ = rowInfo; | |
1095 | |
1096 if (this.selectedTableRowInfo_ === undefined) { | |
1097 this.selectedColumnIndex_ = undefined; | |
1098 this.removeSelectedState_(); | |
1099 } else { | |
1100 switch (this.selectionMode_) { | |
1101 case SelectionMode.ROW: | |
1102 this.selectedColumnIndex_ = undefined; | |
1103 break; | |
1104 | |
1105 case SelectionMode.CELL: | |
1106 if (this.selectedColumnIndex_ === undefined) { | |
1107 var i = this.getFirstSelectableColumnIndex_(); | |
1108 if (i == -1) | |
1109 throw new Error('Cannot find a selectable column.'); | |
1110 this.selectedColumnIndex_ = i; | |
1111 } | |
1112 break; | |
1113 | |
1114 default: | |
1115 throw new Error('Invalid selection mode ' + this.selectionMode_); | |
1116 } | |
1117 this.updateSelectedState_(); | |
1118 } | |
1119 | |
1120 this.dispatchEvent(e); | |
1121 }, | |
1122 | |
1123 updateTabIndexForTableRowNode_: function(row) { | |
1124 if (this.selectionMode_ === SelectionMode.ROW) | |
1125 row.tabIndex = 0; | |
1126 else | |
1127 Polymer.dom(row).removeAttribute('tabIndex'); | |
1128 | |
1129 var enableCellTab = this.selectionMode_ === SelectionMode.CELL; | |
1130 for (var i = 0; i < this.tableColumns_.length; i++) { | |
1131 var cell = row.children[i]; | |
1132 if (enableCellTab && this.doesColumnIndexSupportSelection(i)) | |
1133 cell.tabIndex = 0; | |
1134 else | |
1135 Polymer.dom(cell).removeAttribute('tabIndex'); | |
1136 } | |
1137 }, | |
1138 | |
1139 prepareToChangeSelection_: function() { | |
1140 var e = new tr.b.Event('selection-changed'); | |
1141 var previousSelectedRowInfo = this.selectedTableRowInfo_; | |
1142 if (previousSelectedRowInfo) | |
1143 e.previousSelectedTableRow = previousSelectedRowInfo.userRow; | |
1144 else | |
1145 e.previousSelectedTableRow = undefined; | |
1146 | |
1147 this.removeSelectedState_(); | |
1148 | |
1149 return e; | |
1150 }, | |
1151 | |
1152 removeSelectedState_: function() { | |
1153 this.setSelectedState_(false); | |
1154 }, | |
1155 | |
1156 updateSelectedState_: function() { | |
1157 this.setSelectedState_(true); | |
1158 }, | |
1159 | |
1160 setSelectedState_: function(select) { | |
1161 if (this.selectedTableRowInfo_ === undefined) | |
1162 return; | |
1163 | |
1164 // Row selection. | |
1165 var rowNode = this.selectedTableRowInfo_.htmlNode; | |
1166 if (select) | |
1167 Polymer.dom(rowNode).setAttribute('selected', true); | |
1168 else | |
1169 Polymer.dom(rowNode).removeAttribute('selected'); | |
1170 | |
1171 // Cell selection (if applicable). | |
1172 var cellNode = rowNode.children[this.selectedColumnIndex_]; | |
1173 if (!cellNode) | |
1174 return; | |
1175 if (select) | |
1176 Polymer.dom(cellNode).setAttribute('selected', true); | |
1177 else | |
1178 Polymer.dom(cellNode).removeAttribute('selected'); | |
1179 }, | |
1180 | |
1181 doesColumnIndexSupportSelection: function(columnIndex) { | |
1182 var columnInfo = this.tableColumns_[columnIndex]; | |
1183 var scs = columnInfo.supportsCellSelection; | |
1184 if (scs === false) | |
1185 return false; | |
1186 return true; | |
1187 }, | |
1188 | |
1189 getFirstSelectableColumnIndex_: function() { | |
1190 for (var i = 0; i < this.tableColumns_.length; i++) { | |
1191 if (this.doesColumnIndexSupportSelection(i)) | |
1192 return i; | |
1193 } | |
1194 return -1; | |
1195 }, | |
1196 | |
1197 getSelectableNodeGivenTableRowNode_: function(htmlNode) { | |
1198 switch (this.selectionMode_) { | |
1199 case SelectionMode.ROW: | |
1200 return htmlNode; | |
1201 | |
1202 case SelectionMode.CELL: | |
1203 return htmlNode.children[this.selectedColumnIndex_]; | |
1204 | |
1205 default: | |
1206 throw new Error('Invalid selection mode ' + this.selectionMode_); | |
1207 } | |
1208 }, | |
1209 | |
1210 get selectedColumnIndex() { | |
1211 if (this.selectionMode_ !== SelectionMode.CELL) | |
1212 return undefined; | |
1213 return this.selectedColumnIndex_; | |
1214 }, | |
1215 | |
1216 set selectedColumnIndex(selectedColumnIndex) { | |
1217 this.rebuildIfNeeded_(); | |
1218 if (this.selectionMode_ === SelectionMode.NONE) | |
1219 throw new Error('Selection is off.'); | |
1220 if (selectedColumnIndex < 0 || | |
1221 selectedColumnIndex >= this.tableColumns_.length) | |
1222 throw new Error('Invalid index'); | |
1223 if (!this.doesColumnIndexSupportSelection(selectedColumnIndex)) | |
1224 throw new Error('Selection is not supported on this column'); | |
1225 | |
1226 var e = this.prepareToChangeSelection_(); | |
1227 this.selectedColumnIndex_ = selectedColumnIndex; | |
1228 if (this.selectedColumnIndex_ === undefined) | |
1229 this.selectedTableRowInfo_ = undefined; | |
1230 this.updateSelectedState_(); | |
1231 | |
1232 this.dispatchEvent(e); | |
1233 }, | |
1234 | |
1235 onKeyDown_: function(e) { | |
1236 if (this.selectionMode_ === SelectionMode.NONE) | |
1237 return; | |
1238 if (this.selectedTableRowInfo_ === undefined) | |
1239 return; | |
1240 | |
1241 var code_to_command_names = { | |
1242 13: 'ENTER', | |
1243 37: 'ARROW_LEFT', | |
1244 38: 'ARROW_UP', | |
1245 39: 'ARROW_RIGHT', | |
1246 40: 'ARROW_DOWN' | |
1247 }; | |
1248 var cmdName = code_to_command_names[e.keyCode]; | |
1249 if (cmdName === undefined) | |
1250 return; | |
1251 | |
1252 e.stopPropagation(); | |
1253 e.preventDefault(); | |
1254 this.performKeyCommand_(cmdName); | |
1255 }, | |
1256 | |
1257 performKeyCommand_: function(cmdName) { | |
1258 this.rebuildIfNeeded_(); | |
1259 | |
1260 var rowInfo = this.selectedTableRowInfo_; | |
1261 var htmlNode = rowInfo.htmlNode; | |
1262 if (cmdName === 'ARROW_UP') { | |
1263 var prev = htmlNode.previousElementSibling; | |
1264 if (prev) { | |
1265 tr.ui.b.scrollIntoViewIfNeeded(prev); | |
1266 this.selectedTableRow = prev.rowInfo.userRow; | |
1267 this.focusSelected_(); | |
1268 return; | |
1269 } | |
1270 return; | |
1271 } | |
1272 | |
1273 if (cmdName === 'ARROW_DOWN') { | |
1274 var next = htmlNode.nextElementSibling; | |
1275 if (next) { | |
1276 tr.ui.b.scrollIntoViewIfNeeded(next); | |
1277 this.selectedTableRow = next.rowInfo.userRow; | |
1278 this.focusSelected_(); | |
1279 return; | |
1280 } | |
1281 return; | |
1282 } | |
1283 | |
1284 if (cmdName === 'ARROW_RIGHT') { | |
1285 switch (this.selectionMode_) { | |
1286 case SelectionMode.ROW: | |
1287 if (rowInfo.userRow[this.subRowsPropertyName_] === undefined) | |
1288 return; | |
1289 if (rowInfo.userRow[this.subRowsPropertyName_].length === 0) | |
1290 return; | |
1291 | |
1292 if (!rowInfo.isExpanded) | |
1293 this.setExpandedForTableRow(rowInfo.userRow, true); | |
1294 this.selectedTableRow = | |
1295 Polymer.dom(htmlNode).nextElementSibling.rowInfo.userRow; | |
1296 this.focusSelected_(); | |
1297 return; | |
1298 | |
1299 case SelectionMode.CELL: | |
1300 var newIndex = this.selectedColumnIndex_ + 1; | |
1301 if (newIndex >= this.tableColumns_.length) | |
1302 return; | |
1303 if (!this.doesColumnIndexSupportSelection(newIndex)) | |
1304 return; | |
1305 this.selectedColumnIndex = newIndex; | |
1306 this.focusSelected_(); | |
1307 return; | |
1308 | |
1309 default: | |
1310 throw new Error('Invalid selection mode ' + this.selectionMode_); | |
1311 } | |
1312 } | |
1313 | |
1314 if (cmdName === 'ARROW_LEFT') { | |
1315 switch (this.selectionMode_) { | |
1316 case SelectionMode.ROW: | |
1317 if (rowInfo.isExpanded) { | |
1318 this.setExpandedForTableRow(rowInfo.userRow, false); | |
1319 this.focusSelected_(); | |
1320 return; | |
1321 } | |
1322 | |
1323 // Not expanded. Select parent... | |
1324 var parentRowInfo = rowInfo.parentRowInfo; | |
1325 if (parentRowInfo) { | |
1326 this.selectedTableRow = parentRowInfo.userRow; | |
1327 this.focusSelected_(); | |
1328 return; | |
1329 } | |
1330 return; | |
1331 | |
1332 case SelectionMode.CELL: | |
1333 var newIndex = this.selectedColumnIndex_ - 1; | |
1334 if (newIndex < 0) | |
1335 return; | |
1336 if (!this.doesColumnIndexSupportSelection(newIndex)) | |
1337 return; | |
1338 this.selectedColumnIndex = newIndex; | |
1339 this.focusSelected_(); | |
1340 return; | |
1341 | |
1342 default: | |
1343 throw new Error('Invalid selection mode ' + this.selectionMode_); | |
1344 } | |
1345 } | |
1346 | |
1347 if (cmdName === 'ENTER') { | |
1348 if (rowInfo.userRow[this.subRowsPropertyName_] === undefined) | |
1349 return; | |
1350 if (rowInfo.userRow[this.subRowsPropertyName_].length === 0) | |
1351 return; | |
1352 this.setExpandedForTableRow(rowInfo.userRow, !rowInfo.isExpanded); | |
1353 this.focusSelected_(); | |
1354 return; | |
1355 } | |
1356 | |
1357 throw new Error('Unrecognized command ' + cmdName); | |
1358 }, | |
1359 | |
1360 focusSelected_: function() { | |
1361 if (!this.selectedTableRowInfo_) | |
1362 return; | |
1363 var node = this.getSelectableNodeGivenTableRowNode_( | |
1364 this.selectedTableRowInfo_.htmlNode); | |
1365 node.focus(); | |
1366 }, | |
1367 | |
1368 dispatchSortingChangedEvent_: function() { | |
1369 var e = new tr.b.Event('sort-column-changed'); | |
1370 e.sortColumnIndex = this.sortColumnIndex_; | |
1371 e.sortDescending = this.sortDescending_; | |
1372 this.dispatchEvent(e); | |
1373 } | |
1374 }); | |
1375 })(); | |
1376 </script> | |
1377 | |
1378 <dom-module id='tr-ui-b-table-header-cell'> | |
1379 <template> | 1487 <template> |
1380 <style> | 1488 <style> |
1381 :host { | 1489 :host { |
1382 -webkit-user-select: none; | 1490 -webkit-user-select: none; |
1383 display: flex; | 1491 display: flex; |
1384 } | 1492 } |
1385 | 1493 |
1386 span { | 1494 span { |
1387 flex: 0 1 auto; | 1495 flex: 0 1 auto; |
1388 } | 1496 } |
1389 | 1497 |
1390 side-element { | 1498 #side { |
1391 -webkit-user-select: none; | 1499 -webkit-user-select: none; |
1392 flex: 0 0 auto; | 1500 flex: 0 0 auto; |
1393 padding-left: 4px; | 1501 padding-left: 2px; |
| 1502 padding-right: 2px; |
1394 vertical-align: top; | 1503 vertical-align: top; |
1395 font-size: 15px; | 1504 font-size: 15px; |
1396 font-family: sans-serif; | 1505 font-family: sans-serif; |
1397 display: inline; | 1506 display: inline; |
1398 line-height: 85%; | 1507 line-height: 85%; |
| 1508 margin-left: 5px; |
| 1509 } |
| 1510 |
| 1511 #button { |
| 1512 font-weight: bold; |
| 1513 font-size: 12px; |
| 1514 } |
| 1515 |
| 1516 #title:empty, #button:empty, #side:empty { |
| 1517 display: none; |
| 1518 } |
| 1519 |
| 1520 #button[selected] { |
| 1521 background: darkgrey; |
1399 } | 1522 } |
1400 </style> | 1523 </style> |
1401 | 1524 |
1402 <span id="title"></span><side-element id="side"></side-element> | 1525 <span id="title"></span> |
| 1526 <button id="button"></button> |
| 1527 <button id="side"></button> |
1403 </template> | 1528 </template> |
1404 </dom-module> | 1529 </dom-module> |
1405 | |
1406 <script> | 1530 <script> |
1407 'use strict'; | 1531 'use strict'; |
1408 | 1532 |
1409 var ColumnAlignment = tr.ui.b.TableFormat.ColumnAlignment; | 1533 var ColumnAlignment = tr.ui.b.TableFormat.ColumnAlignment; |
1410 | 1534 |
1411 Polymer({ | 1535 Polymer({ |
1412 is: 'tr-ui-b-table-header-cell', | 1536 is: 'tr-ui-b-table-header-cell', |
1413 | 1537 |
1414 listeners: { | |
1415 'tap': 'onTap_' | |
1416 }, | |
1417 | |
1418 created: function() { | 1538 created: function() { |
1419 this.tapCallback_ = undefined; | 1539 this.tapCallback_ = undefined; |
1420 this.cellTitle_ = ''; | 1540 this.cellTitle_ = ''; |
1421 this.align_ = undefined; | 1541 this.align_ = undefined; |
| 1542 this.selectable_ = false; |
| 1543 this.column_ = undefined; |
| 1544 }, |
| 1545 |
| 1546 ready: function() { |
| 1547 this.$.button.addEventListener('click', this.onSelect_.bind(this)); |
| 1548 this.$.side.addEventListener('click', this.onTap_.bind(this)); |
| 1549 }, |
| 1550 |
| 1551 onSelect_: function() { |
| 1552 this.selected = this.$.button.getAttribute('selected') !== 'true'; |
| 1553 var e = new tr.b.Event('selected-column-changed'); |
| 1554 e.column = this.column; |
| 1555 e.selected = this.selected; |
| 1556 this.dispatchEvent(e); |
| 1557 }, |
| 1558 |
| 1559 set column(column) { |
| 1560 this.column_ = column; |
| 1561 this.selectable = column.selectable; |
| 1562 this.align = column.align; |
| 1563 this.cellTitle = column.title; |
| 1564 }, |
| 1565 |
| 1566 get column() { |
| 1567 return this.column_; |
| 1568 }, |
| 1569 |
| 1570 set selectable(selectable) { |
| 1571 this.selectable_ = selectable; |
| 1572 this.cellTitle = this.$.title.childNodes[0] || |
| 1573 this.$.button.childNodes[0]; |
| 1574 }, |
| 1575 |
| 1576 get selectable() { |
| 1577 return this.selectable_; |
| 1578 }, |
| 1579 |
| 1580 set selected(selected) { |
| 1581 if (selected) |
| 1582 this.$.button.setAttribute('selected', true); |
| 1583 else |
| 1584 this.$.button.removeAttribute('selected'); |
| 1585 }, |
| 1586 |
| 1587 get selected() { |
| 1588 if (!this.selectable_) |
| 1589 return false; |
| 1590 return this.$.button.getAttribute('selected') === 'true'; |
1422 }, | 1591 }, |
1423 | 1592 |
1424 set cellTitle(value) { | 1593 set cellTitle(value) { |
1425 this.cellTitle_ = value; | 1594 this.cellTitle_ = value; |
1426 | 1595 |
1427 var titleNode = tr.ui.b.asHTMLOrTextNode( | 1596 var titleNode = tr.ui.b.asHTMLOrTextNode( |
1428 this.cellTitle_, this.ownerDocument); | 1597 this.cellTitle_, this.ownerDocument); |
1429 | 1598 |
1430 this.$.title.innerText = ''; | 1599 this.$.title.innerText = ''; |
1431 Polymer.dom(this.$.title).appendChild(titleNode); | 1600 this.$.button.innerText = ''; |
| 1601 |
| 1602 if (this.selectable_) |
| 1603 this.$.button.appendChild(titleNode); |
| 1604 else |
| 1605 this.$.title.appendChild(titleNode); |
1432 }, | 1606 }, |
1433 | 1607 |
1434 get cellTitle() { | 1608 get cellTitle() { |
1435 return this.cellTitle_; | 1609 return this.cellTitle_; |
1436 }, | 1610 }, |
1437 | 1611 |
1438 set align(align) { | 1612 set align(align) { |
1439 switch (align) { | 1613 switch (align) { |
1440 case undefined: | 1614 case undefined: |
1441 case ColumnAlignment.LEFT: | 1615 case ColumnAlignment.LEFT: |
1442 this.style.justifyContent = ''; | 1616 this.style.justifyContent = ''; |
1443 break; | 1617 break; |
1444 | 1618 |
1445 case ColumnAlignment.RIGHT: | 1619 case ColumnAlignment.RIGHT: |
1446 this.style.justifyContent = 'flex-end'; | 1620 this.style.justifyContent = 'flex-end'; |
1447 break; | 1621 break; |
1448 | 1622 |
1449 default: | 1623 default: |
1450 throw new Error('Invalid alignment of column (title=\'' + | 1624 throw new Error('Invalid alignment of column (title=\'' + |
1451 this.cellTitle_ + '\'): ' + align); | 1625 this.cellTitle_ + '\'): ' + align); |
1452 } | 1626 } |
1453 this.align_ = align; | 1627 this.align_ = align; |
1454 }, | 1628 }, |
1455 | 1629 |
1456 get align() { | 1630 get align() { |
1457 return this.align_; | 1631 return this.align_; |
1458 }, | 1632 }, |
1459 | 1633 |
1460 clearSideContent: function() { | 1634 clearSideContent: function() { |
1461 Polymer.dom(this.$.side).textContent = ''; | 1635 this.$.side.textContent = ''; |
1462 }, | 1636 }, |
1463 | 1637 |
1464 set sideContent(content) { | 1638 set sideContent(content) { |
1465 Polymer.dom(this.$.side).textContent = content; | 1639 this.$.side.textContent = content; |
| 1640 this.$.side.style.display = content ? 'inline' : 'none'; |
1466 }, | 1641 }, |
1467 | 1642 |
1468 get sideContent() { | 1643 get sideContent() { |
1469 return Polymer.dom(this.$.side).textContent; | 1644 return this.$.side.textContent; |
1470 }, | 1645 }, |
1471 | 1646 |
1472 set tapCallback(callback) { | 1647 set tapCallback(callback) { |
1473 this.style.cursor = 'pointer'; | 1648 this.style.cursor = 'pointer'; |
1474 this.tapCallback_ = callback; | 1649 this.tapCallback_ = callback; |
1475 }, | 1650 }, |
1476 | 1651 |
1477 get tapCallback() { | 1652 get tapCallback() { |
1478 return this.tapCallback_; | 1653 return this.tapCallback_; |
1479 }, | 1654 }, |
1480 | 1655 |
1481 onTap_: function() { | 1656 onTap_: function() { |
1482 if (this.tapCallback_) | 1657 if (this.tapCallback_) |
1483 this.tapCallback_(); | 1658 this.tapCallback_(); |
1484 } | 1659 } |
1485 }); | 1660 }); |
1486 </script> | 1661 </script> |
OLD | NEW |