or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

component-management.mddata-display-components.mdfeedback-components.mdform-components.mdindex.mdlayout-components.mdnavigation-components.mdvisual-effects.md

data-display-components.mddocs/

0

# Data Display Components

1

2

Components for displaying structured data including data tables with sorting and selection capabilities. These components enhance standard HTML tables with Material Design styling and interactive functionality.

3

4

## Capabilities

5

6

### Material Data Table

7

8

Enhanced data table with selection capabilities, Material Design styling, and automatic row selection management.

9

10

```javascript { .api }

11

/**

12

* Material Design data table component

13

* CSS Class: mdl-js-data-table

14

* Widget: false

15

*/

16

interface MaterialDataTable {

17

// No public methods - behavior is entirely declarative via HTML/CSS

18

// Automatic functionality includes:

19

// - Row selection with checkboxes

20

// - Master checkbox for select all/none

21

// - Visual feedback for selected rows

22

// - Keyboard navigation support

23

}

24

```

25

26

**HTML Structure:**

27

28

```html

29

<!-- Basic data table -->

30

<table class="mdl-data-table mdl-js-data-table">

31

<thead>

32

<tr>

33

<th class="mdl-data-table__cell--non-numeric">Material</th>

34

<th>Quantity</th>

35

<th>Unit price</th>

36

</tr>

37

</thead>

38

<tbody>

39

<tr>

40

<td class="mdl-data-table__cell--non-numeric">Acrylic (Transparent)</td>

41

<td>25</td>

42

<td>$2.90</td>

43

</tr>

44

<tr>

45

<td class="mdl-data-table__cell--non-numeric">Plywood (Birch)</td>

46

<td>50</td>

47

<td>$1.25</td>

48

</tr>

49

<tr>

50

<td class="mdl-data-table__cell--non-numeric">Laminate (Gold on Blue)</td>

51

<td>10</td>

52

<td>$2.35</td>

53

</tr>

54

</tbody>

55

</table>

56

57

<!-- Selectable data table -->

58

<table class="mdl-data-table mdl-js-data-table mdl-data-table--selectable">

59

<thead>

60

<tr>

61

<th class="mdl-data-table__cell--non-numeric">Material</th>

62

<th>Quantity</th>

63

<th>Unit price</th>

64

</tr>

65

</thead>

66

<tbody>

67

<tr>

68

<td class="mdl-data-table__cell--non-numeric">Acrylic (Transparent)</td>

69

<td>25</td>

70

<td>$2.90</td>

71

</tr>

72

<tr>

73

<td class="mdl-data-table__cell--non-numeric">Plywood (Birch)</td>

74

<td>50</td>

75

<td>$1.25</td>

76

</tr>

77

<tr>

78

<td class="mdl-data-table__cell--non-numeric">Laminate (Gold on Blue)</td>

79

<td>10</td>

80

<td>$2.35</td>

81

</tr>

82

</tbody>

83

</table>

84

```

85

86

**Usage Examples:**

87

88

Since the Material Data Table has no programmatic API, interaction is handled through DOM events:

89

90

```javascript

91

// Listen for row selection changes

92

document.addEventListener('change', (event) => {

93

if (event.target.matches('.mdl-data-table__select')) {

94

const row = event.target.closest('tr');

95

const isSelected = event.target.checked;

96

97

console.log('Row selection changed:', {

98

row: row,

99

selected: isSelected,

100

data: getRowData(row)

101

});

102

103

updateSelectionActions();

104

}

105

});

106

107

// Get data from a table row

108

function getRowData(row) {

109

const cells = row.querySelectorAll('td:not(.mdl-data-table__cell--checkbox)');

110

return Array.from(cells).map(cell => cell.textContent.trim());

111

}

112

113

// Handle master checkbox (select all/none)

114

document.addEventListener('change', (event) => {

115

if (event.target.matches('thead .mdl-data-table__select')) {

116

const table = event.target.closest('table');

117

const allRowCheckboxes = table.querySelectorAll('tbody .mdl-data-table__select');

118

const isChecked = event.target.checked;

119

120

allRowCheckboxes.forEach(checkbox => {

121

checkbox.checked = isChecked;

122

// Trigger change event for each checkbox

123

checkbox.dispatchEvent(new Event('change', { bubbles: true }));

124

});

125

}

126

});

127

128

// Get all selected rows

129

function getSelectedRows(table) {

130

const selectedRows = [];

131

const checkboxes = table.querySelectorAll('tbody .mdl-data-table__select:checked');

132

133

checkboxes.forEach(checkbox => {

134

const row = checkbox.closest('tr');

135

selectedRows.push({

136

row: row,

137

data: getRowData(row),

138

index: Array.from(row.parentNode.children).indexOf(row)

139

});

140

});

141

142

return selectedRows;

143

}

144

145

// Update UI based on selection

146

function updateSelectionActions() {

147

const tables = document.querySelectorAll('.mdl-data-table--selectable');

148

149

tables.forEach(table => {

150

const selectedRows = getSelectedRows(table);

151

const actionBar = document.querySelector(`[data-table="${table.id}"] .selection-actions`);

152

153

if (actionBar) {

154

if (selectedRows.length > 0) {

155

actionBar.style.display = 'block';

156

actionBar.querySelector('.selection-count').textContent = selectedRows.length;

157

} else {

158

actionBar.style.display = 'none';

159

}

160

}

161

});

162

}

163

```

164

165

### Dynamic Data Table Management

166

167

```javascript

168

// Create data table dynamically

169

function createDataTable(containerId, data, options = {}) {

170

const container = document.getElementById(containerId);

171

if (!container) return null;

172

173

const table = document.createElement('table');

174

table.className = 'mdl-data-table mdl-js-data-table';

175

176

if (options.selectable) {

177

table.classList.add('mdl-data-table--selectable');

178

}

179

180

// Create header

181

if (data.length > 0) {

182

const thead = document.createElement('thead');

183

const headerRow = document.createElement('tr');

184

185

Object.keys(data[0]).forEach(key => {

186

const th = document.createElement('th');

187

188

// Determine if column is numeric

189

const isNumeric = data.every(row =>

190

typeof row[key] === 'number' ||

191

!isNaN(parseFloat(row[key]))

192

);

193

194

if (!isNumeric) {

195

th.classList.add('mdl-data-table__cell--non-numeric');

196

}

197

198

th.textContent = formatHeaderText(key);

199

headerRow.appendChild(th);

200

});

201

202

thead.appendChild(headerRow);

203

table.appendChild(thead);

204

}

205

206

// Create body

207

const tbody = document.createElement('tbody');

208

209

data.forEach(rowData => {

210

const row = document.createElement('tr');

211

212

Object.values(rowData).forEach((value, index) => {

213

const td = document.createElement('td');

214

215

// Apply non-numeric class if needed

216

const isNumeric = typeof value === 'number' || !isNaN(parseFloat(value));

217

if (!isNumeric) {

218

td.classList.add('mdl-data-table__cell--non-numeric');

219

}

220

221

td.textContent = value;

222

row.appendChild(td);

223

});

224

225

tbody.appendChild(row);

226

});

227

228

table.appendChild(tbody);

229

container.appendChild(table);

230

231

// Upgrade table

232

componentHandler.upgradeElement(table);

233

234

return table;

235

}

236

237

function formatHeaderText(key) {

238

return key.replace(/([A-Z])/g, ' $1')

239

.replace(/^./, str => str.toUpperCase())

240

.trim();

241

}

242

243

// Usage

244

const sampleData = [

245

{ material: 'Acrylic (Transparent)', quantity: 25, unitPrice: 2.90 },

246

{ material: 'Plywood (Birch)', quantity: 50, unitPrice: 1.25 },

247

{ material: 'Laminate (Gold on Blue)', quantity: 10, unitPrice: 2.35 }

248

];

249

250

createDataTable('table-container', sampleData, { selectable: true });

251

```

252

253

### Table Sorting Implementation

254

255

Since MDL data tables don't include built-in sorting, here's how to add it:

256

257

```javascript

258

// Add sorting functionality to data tables

259

function makeTableSortable(table) {

260

const headers = table.querySelectorAll('th');

261

262

headers.forEach((header, columnIndex) => {

263

// Skip checkbox column

264

if (header.classList.contains('mdl-data-table__cell--checkbox')) {

265

return;

266

}

267

268

header.style.cursor = 'pointer';

269

header.style.userSelect = 'none';

270

271

// Add sort indicator

272

const sortIcon = document.createElement('i');

273

sortIcon.className = 'material-icons sort-icon';

274

sortIcon.textContent = 'unfold_more';

275

sortIcon.style.fontSize = '16px';

276

sortIcon.style.marginLeft = '4px';

277

sortIcon.style.color = '#999';

278

header.appendChild(sortIcon);

279

280

header.addEventListener('click', () => {

281

sortTable(table, columnIndex, header);

282

});

283

});

284

}

285

286

function sortTable(table, columnIndex, header) {

287

const tbody = table.querySelector('tbody');

288

const rows = Array.from(tbody.querySelectorAll('tr'));

289

290

// Determine sort direction

291

const currentDirection = header.dataset.sortDirection || 'asc';

292

const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';

293

294

// Clear all sort indicators

295

table.querySelectorAll('th .sort-icon').forEach(icon => {

296

icon.textContent = 'unfold_more';

297

icon.style.color = '#999';

298

});

299

300

// Update current header

301

const sortIcon = header.querySelector('.sort-icon');

302

sortIcon.textContent = newDirection === 'asc' ? 'keyboard_arrow_up' : 'keyboard_arrow_down';

303

sortIcon.style.color = '#333';

304

header.dataset.sortDirection = newDirection;

305

306

// Sort rows

307

rows.sort((a, b) => {

308

const aCell = a.cells[columnIndex];

309

const bCell = b.cells[columnIndex];

310

311

// Skip checkbox cells

312

const aText = aCell.classList.contains('mdl-data-table__cell--checkbox') ?

313

'' : aCell.textContent.trim();

314

const bText = bCell.classList.contains('mdl-data-table__cell--checkbox') ?

315

'' : bCell.textContent.trim();

316

317

// Try numeric comparison first

318

const aNum = parseFloat(aText.replace(/[^0-9.-]/g, ''));

319

const bNum = parseFloat(bText.replace(/[^0-9.-]/g, ''));

320

321

let comparison = 0;

322

323

if (!isNaN(aNum) && !isNaN(bNum)) {

324

comparison = aNum - bNum;

325

} else {

326

comparison = aText.localeCompare(bText);

327

}

328

329

return newDirection === 'asc' ? comparison : -comparison;

330

});

331

332

// Re-append sorted rows

333

rows.forEach(row => tbody.appendChild(row));

334

335

// Update selection state if needed

336

updateSelectionActions();

337

}

338

339

// Make all data tables sortable

340

document.addEventListener('DOMContentLoaded', () => {

341

document.querySelectorAll('.mdl-data-table').forEach(makeTableSortable);

342

});

343

```

344

345

### Table Filtering and Search

346

347

```javascript

348

// Add search/filter functionality

349

function addTableSearch(table, searchInputId) {

350

const searchInput = document.getElementById(searchInputId);

351

if (!searchInput) return;

352

353

const tbody = table.querySelector('tbody');

354

const rows = Array.from(tbody.querySelectorAll('tr'));

355

356

searchInput.addEventListener('input', (event) => {

357

const searchTerm = event.target.value.toLowerCase();

358

359

rows.forEach(row => {

360

const cells = Array.from(row.cells);

361

const rowText = cells

362

.filter(cell => !cell.classList.contains('mdl-data-table__cell--checkbox'))

363

.map(cell => cell.textContent.toLowerCase())

364

.join(' ');

365

366

const matches = rowText.includes(searchTerm);

367

row.style.display = matches ? '' : 'none';

368

});

369

370

updateRowCount(table);

371

});

372

}

373

374

function updateRowCount(table) {

375

const tbody = table.querySelector('tbody');

376

const visibleRows = tbody.querySelectorAll('tr:not([style*="display: none"])');

377

const totalRows = tbody.querySelectorAll('tr');

378

379

// Update row count display if it exists

380

const countDisplay = document.querySelector(`[data-table="${table.id}"] .row-count`);

381

if (countDisplay) {

382

countDisplay.textContent = `Showing ${visibleRows.length} of ${totalRows.length} rows`;

383

}

384

}

385

386

// Column-specific filtering

387

function addColumnFilter(table, columnIndex, filterId) {

388

const filterSelect = document.getElementById(filterId);

389

if (!filterSelect) return;

390

391

const tbody = table.querySelector('tbody');

392

const rows = Array.from(tbody.querySelectorAll('tr'));

393

394

// Populate filter options

395

const uniqueValues = new Set();

396

rows.forEach(row => {

397

const cell = row.cells[columnIndex];

398

if (cell && !cell.classList.contains('mdl-data-table__cell--checkbox')) {

399

uniqueValues.add(cell.textContent.trim());

400

}

401

});

402

403

// Add "All" option

404

const allOption = document.createElement('option');

405

allOption.value = '';

406

allOption.textContent = 'All';

407

filterSelect.appendChild(allOption);

408

409

// Add unique values as options

410

Array.from(uniqueValues).sort().forEach(value => {

411

const option = document.createElement('option');

412

option.value = value;

413

option.textContent = value;

414

filterSelect.appendChild(option);

415

});

416

417

// Handle filter changes

418

filterSelect.addEventListener('change', (event) => {

419

const filterValue = event.target.value;

420

421

rows.forEach(row => {

422

const cell = row.cells[columnIndex];

423

if (cell && !cell.classList.contains('mdl-data-table__cell--checkbox')) {

424

const cellValue = cell.textContent.trim();

425

const matches = !filterValue || cellValue === filterValue;

426

row.style.display = matches ? '' : 'none';

427

}

428

});

429

430

updateRowCount(table);

431

});

432

}

433

```

434

435

### Bulk Actions

436

437

```javascript

438

// Implement bulk actions for selected rows

439

function setupBulkActions(table, actionsConfig) {

440

const actionBar = document.createElement('div');

441

actionBar.className = 'bulk-actions';

442

actionBar.style.display = 'none';

443

actionBar.innerHTML = `

444

<span class="selection-count">0</span> selected

445

<div class="action-buttons"></div>

446

`;

447

448

const buttonContainer = actionBar.querySelector('.action-buttons');

449

450

// Create action buttons

451

actionsConfig.forEach(action => {

452

const button = document.createElement('button');

453

button.className = 'mdl-button mdl-js-button mdl-button--raised';

454

button.textContent = action.label;

455

button.addEventListener('click', () => {

456

const selectedRows = getSelectedRows(table);

457

action.handler(selectedRows, table);

458

});

459

buttonContainer.appendChild(button);

460

461

// Upgrade button

462

componentHandler.upgradeElement(button);

463

});

464

465

// Insert action bar before table

466

table.parentNode.insertBefore(actionBar, table);

467

468

// Update action bar visibility based on selection

469

const originalUpdateFunction = window.updateSelectionActions || (() => {});

470

window.updateSelectionActions = function() {

471

originalUpdateFunction();

472

473

const selectedRows = getSelectedRows(table);

474

const selectionCount = actionBar.querySelector('.selection-count');

475

476

if (selectedRows.length > 0) {

477

actionBar.style.display = 'block';

478

selectionCount.textContent = selectedRows.length;

479

} else {

480

actionBar.style.display = 'none';

481

}

482

};

483

}

484

485

// Usage example

486

setupBulkActions(document.querySelector('#my-table'), [

487

{

488

label: 'Delete',

489

handler: (selectedRows, table) => {

490

if (confirm(`Delete ${selectedRows.length} items?`)) {

491

selectedRows.forEach(({ row }) => {

492

row.remove();

493

});

494

updateSelectionActions();

495

}

496

}

497

},

498

{

499

label: 'Export',

500

handler: (selectedRows) => {

501

const data = selectedRows.map(({ data }) => data);

502

exportToCSV(data);

503

}

504

}

505

]);

506

507

function exportToCSV(data) {

508

if (data.length === 0) return;

509

510

const csvContent = data.map(row =>

511

row.map(cell => `"${cell}"`).join(',')

512

).join('\n');

513

514

const blob = new Blob([csvContent], { type: 'text/csv' });

515

const url = URL.createObjectURL(blob);

516

517

const a = document.createElement('a');

518

a.href = url;

519

a.download = 'export.csv';

520

a.click();

521

522

URL.revokeObjectURL(url);

523

}

524

```

525

526

## Data Table Constants

527

528

```javascript { .api }

529

/**

530

* Material Data Table CSS classes and selectors

531

*/

532

interface DataTableConstants {

533

/** Main data table class */

534

DATA_TABLE: 'mdl-data-table';

535

536

/** Selectable data table modifier */

537

SELECTABLE: 'mdl-data-table--selectable';

538

539

/** Checkbox cell class */

540

SELECT_ELEMENT: 'mdl-data-table__select';

541

542

/** Non-numeric cell class */

543

NON_NUMERIC: 'mdl-data-table__cell--non-numeric';

544

545

/** Selected row state class */

546

IS_SELECTED: 'is-selected';

547

548

/** Upgraded component state class */

549

IS_UPGRADED: 'is-upgraded';

550

}

551

```

552

553

## Accessibility Features

554

555

```javascript

556

// Enhance table accessibility

557

function enhanceTableAccessibility(table) {

558

// Add ARIA labels

559

table.setAttribute('role', 'table');

560

561

const thead = table.querySelector('thead');

562

if (thead) {

563

thead.setAttribute('role', 'rowgroup');

564

565

thead.querySelectorAll('tr').forEach(row => {

566

row.setAttribute('role', 'row');

567

row.querySelectorAll('th').forEach(header => {

568

header.setAttribute('role', 'columnheader');

569

});

570

});

571

}

572

573

const tbody = table.querySelector('tbody');

574

if (tbody) {

575

tbody.setAttribute('role', 'rowgroup');

576

577

tbody.querySelectorAll('tr').forEach((row, index) => {

578

row.setAttribute('role', 'row');

579

row.setAttribute('aria-rowindex', index + 1);

580

581

row.querySelectorAll('td').forEach((cell, cellIndex) => {

582

cell.setAttribute('role', 'cell');

583

cell.setAttribute('aria-colindex', cellIndex + 1);

584

});

585

});

586

}

587

588

// Add keyboard navigation

589

table.addEventListener('keydown', (event) => {

590

if (event.target.matches('.mdl-data-table__select')) {

591

handleCheckboxKeyboard(event);

592

}

593

});

594

}

595

596

function handleCheckboxKeyboard(event) {

597

const checkbox = event.target;

598

const row = checkbox.closest('tr');

599

600

switch (event.key) {

601

case 'ArrowUp':

602

event.preventDefault();

603

focusPreviousCheckbox(row);

604

break;

605

606

case 'ArrowDown':

607

event.preventDefault();

608

focusNextCheckbox(row);

609

break;

610

611

case ' ':

612

event.preventDefault();

613

checkbox.click();

614

break;

615

}

616

}

617

618

function focusPreviousCheckbox(currentRow) {

619

const tbody = currentRow.parentNode;

620

const rows = Array.from(tbody.querySelectorAll('tr'));

621

const currentIndex = rows.indexOf(currentRow);

622

623

if (currentIndex > 0) {

624

const prevCheckbox = rows[currentIndex - 1].querySelector('.mdl-data-table__select');

625

if (prevCheckbox) prevCheckbox.focus();

626

}

627

}

628

629

function focusNextCheckbox(currentRow) {

630

const tbody = currentRow.parentNode;

631

const rows = Array.from(tbody.querySelectorAll('tr'));

632

const currentIndex = rows.indexOf(currentRow);

633

634

if (currentIndex < rows.length - 1) {

635

const nextCheckbox = rows[currentIndex + 1].querySelector('.mdl-data-table__select');

636

if (nextCheckbox) nextCheckbox.focus();

637

}

638

}

639

```