Skip to content

Commit

Permalink
Add HTML table: filter and sort (metafacture-core#369)
Browse files Browse the repository at this point in the history
The flux-commands-table.html is a HTML view of flux-commands.md with
filter and sort functionality of the signature of the commands.

Proof of concept - WIP
  • Loading branch information
dr0i committed Oct 29, 2024
1 parent ceacddb commit 70e9743
Show file tree
Hide file tree
Showing 3 changed files with 391 additions and 0 deletions.
101 changes: 101 additions & 0 deletions assets/css/sortTable.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
.sr-only {
position: absolute;
top: -30em;
}

table.sortable td,
table.sortable th {
padding: 0.125em 0.25em;
width: 22em;
}

table.sortable th {
font-weight: bold;
border-bottom: thin solid #888;
position: relative;
}

table.sortable th.no-sort {
padding-top: 0.35em;
}

table.sortable th:nth-child(5) {
width: 10em;
}

table.sortable th button {
padding: 4px;
margin: 1px;
font-size: 100%;
font-weight: bold;
background: transparent;
border: none;
display: inline;
right: 0;
left: 0;
top: 0;
bottom: 0;
width: 100%;
text-align: left;
outline: none;
cursor: pointer;
}

table.sortable th button span {
position: absolute;
right: 4px;
}

table.sortable th[aria-sort="descending"] span::after {
content: "▼";
color: currentcolor;
font-size: 100%;
top: 0;
}

table.sortable th[aria-sort="ascending"] span::after {
content: "▲";
color: currentcolor;
font-size: 100%;
top: 0;
}

table.show-unsorted-icon th:not([aria-sort]) button span::after {
content: "♢";
color: currentcolor;
font-size: 100%;
position: relative;
top: -3px;
left: -4px;
}

table.sortable td.num {
text-align: right;
}

table.sortable tbody tr:nth-child(odd) {
background-color: #ddd;
}

/* Focus and hover styling */

table.sortable th button:focus,
table.sortable th button:hover {
padding: 2px;
border: 2px solid currentcolor;
background-color: #e5f4ff;
}

table.sortable th button:focus span,
table.sortable th button:hover span {
right: 2px;
}

table.sortable th:not([aria-sort]) button:focus span::after,
table.sortable th:not([aria-sort]) button:hover span::after {
content: "▼";
color: currentcolor;
font-size: 100%;
top: 0;
}

168 changes: 168 additions & 0 deletions assets/js/sortable-table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* This content is licensed according to the W3C Software License at
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*
* File: sortable-table.js
*
* Desc: Adds sorting to a HTML data table that implements ARIA Authoring Practices
*/

'use strict';

class SortableTable {
constructor(tableNode) {
this.tableNode = tableNode;

this.columnHeaders = tableNode.querySelectorAll('thead th');

this.sortColumns = [];

for (var i = 0; i < this.columnHeaders.length; i++) {
var ch = this.columnHeaders[i];
var buttonNode = ch.querySelector('button');
if (buttonNode) {
this.sortColumns.push(i);
buttonNode.setAttribute('data-column-index', i);
buttonNode.addEventListener('click', this.handleClick.bind(this));
}
}

this.optionCheckbox = document.querySelector(
'input[type="checkbox"][value="show-unsorted-icon"]'
);

if (this.optionCheckbox) {
this.optionCheckbox.addEventListener(
'change',
this.handleOptionChange.bind(this)
);
if (this.optionCheckbox.checked) {
this.tableNode.classList.add('show-unsorted-icon');
}
}
}

setColumnHeaderSort(columnIndex) {
if (typeof columnIndex === 'string') {
columnIndex = parseInt(columnIndex);
}

for (var i = 0; i < this.columnHeaders.length; i++) {
var ch = this.columnHeaders[i];
var buttonNode = ch.querySelector('button');
if (i === columnIndex) {
var value = ch.getAttribute('aria-sort');
if (value === 'descending') {
ch.setAttribute('aria-sort', 'ascending');
this.sortColumn(
columnIndex,
'ascending',
ch.classList.contains('num')
);
} else {
ch.setAttribute('aria-sort', 'descending');
this.sortColumn(
columnIndex,
'descending',
ch.classList.contains('num')
);
}
} else {
if (ch.hasAttribute('aria-sort') && buttonNode) {
ch.removeAttribute('aria-sort');
}
}
}
}

sortColumn(columnIndex, sortValue, isNumber) {
function compareValues(a, b) {
if (sortValue === 'ascending') {
if (a.value === b.value) {
return 0;
} else {
if (isNumber) {
return a.value - b.value;
} else {
return a.value < b.value ? -1 : 1;
}
}
} else {
if (a.value === b.value) {
return 0;
} else {
if (isNumber) {
return b.value - a.value;
} else {
return a.value > b.value ? -1 : 1;
}
}
}
}

if (typeof isNumber !== 'boolean') {
isNumber = false;
}

var tbodyNode = this.tableNode.querySelector('tbody');
var rowNodes = [];
var dataCells = [];

var rowNode = tbodyNode.firstElementChild;

var index = 0;
while (rowNode) {
rowNodes.push(rowNode);
var rowCells = rowNode.querySelectorAll('th, td');
var dataCell = rowCells[columnIndex];

var data = {};
data.index = index;
data.value = dataCell.textContent.toLowerCase().trim();
if (isNumber) {
data.value = parseFloat(data.value);
}
dataCells.push(data);
rowNode = rowNode.nextElementSibling;
index += 1;
}

dataCells.sort(compareValues);

// remove rows
while (tbodyNode.firstChild) {
tbodyNode.removeChild(tbodyNode.lastChild);
}

// add sorted rows
for (var i = 0; i < dataCells.length; i += 1) {
tbodyNode.appendChild(rowNodes[dataCells[i].index]);
}
}

/* EVENT HANDLERS */

handleClick(event) {
var tgt = event.currentTarget;
this.setColumnHeaderSort(tgt.getAttribute('data-column-index'));
}

handleOptionChange(event) {
var tgt = event.currentTarget;

if (tgt.checked) {
this.tableNode.classList.add('show-unsorted-icon');
} else {
this.tableNode.classList.remove('show-unsorted-icon');
}
}
}

// Initialize sortable table buttons
window.addEventListener('load', function () {
var sortableTables = document.querySelectorAll('table.sortable');
for (var i = 0; i < sortableTables.length; i++) {
new SortableTable(sortableTables[i]);
}
});

122 changes: 122 additions & 0 deletions docs/flux/flux-commands-table.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../../assets/css/sortTable.css">
<script src="../../assets/js/sortable-table.js"></script>
<script>
function getValue(param) {
// Declare variables
var filter, table, tr, td, i, txtValue, column;

input3 = document.getElementById("3");
filter3 = input3.querySelector('.name').value.toUpperCase();

input4 = document.getElementById("4");
filter4 = input4.querySelector('.name').value.toUpperCase();

table = document.getElementById("myTable");
tr = table.getElementsByTagName("tr");

// Loop through all table rows, and hide those who don't match the search query
for (i = 0; i < tr.length; i++) {
td3 = tr[i].getElementsByTagName("td")[3];
td4 = tr[i].getElementsByTagName("td")[4];
if (td3 && td4) {
txtValue3 = td3.textContent || td3.innerText;
txtValue4 = td4.textContent || td4.innerText;
if ((txtValue3.toUpperCase().indexOf(filter3) > -1 || filter3==="*") && (txtValue4.toUpperCase().indexOf(filter4) > -1 || filter4==="*" )) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
</script>
</head>
<body>

<div class="table-wrap">
<table class="sortable" id="myTable">
<caption>
Flux commands
<span class="sr-only"> (column headers with buttons are sortable).</span>
</caption>
<thead>
<tr>
<th>
<button>
Name
<span aria-hidden="true"></span>
</button>
</th>
<th class="no-sort">description</th>
<th class="no-sort">options</th>
<th id="3">
<label for="filterColumn3">Filter IN Signature :</label>
<select id="3" onchange="getValue();" class="name">
<option value="*">*</option>
<option value="StreamReceiver">StreamReceiver</option>
<option value="String">String</option>
<option value="Object">Object</option>
<option value="Void">Void</option>
</select>
</th>
<th id="4">
<label for="filterColumn4">Filter OUT Signature:</label>
<select id="4" onchange="getValue();" class="name">
<option value="*">*</option>
<option value="StreamReceiver">StreamReceiver</option>
<option value="String">String</option>
<option value="Object">Object</option>
<option value="Void">Void</option>
</select>
</th>
<th class="no-sort">example in Playground</th>
<th class="no-sort">java class</th>
</tr>
</thead>
<tbody>
<tr>
<td>add-oreaggregation</td>
<td>Adds ore:Aggregation to an Europeana Data Model stream. The aggregation id is set by emitting literal('aggregation_id', id)</td>
<td>-</td>
<td>StreamReceiver</td>
<td>StreamReceiver</td>
<td>-</td>
<td><a href="https://github.com/metafacture/metafacture-core/blob/master/metafacture-linkeddata/src/main/java/org/metafacture/linkeddata/OreAggregationAdder.java">org.metafacture.linkeddata.OreAggregationAdder</a></td>
</tr>
<tr>
<td>add-preamble-epilogue</td>
<td>Adds a String preamle and/or epilogue to the stream</td>
<td>preamble (String), epilogue (String)</td>
<td>String</td>
<td>String</td>
<td><a href="https://github.com/metafacture/metafacture-core/blob/master/metafacture-formatting/src/main/java/org/metafacture/formatting/PreambleEpilogueAdder.java">example in Playground</a></td>
<td><a href="">org.metafacture.formatting.PreambleEpilogueAdder</a></td>
</tr>
<tr>
<td>badd-preamble-epilogue</td>
<td>bAdds a String preamle and/or epilogue to the stream</td>
<td>preamble (String), epilogue (String)</td>
<td>String</td>
<td>StreamReceiver</td>
<td><a href="https://github.com/metafacture/metafacture-core/blob/master/metafacture-formatting/src/main/java/org/metafacture/formatting/PreambleEpilogueAdder.java">example in Playground</a></td>
<td><a href="">org.metafacture.formatting.PreambleEpilogueAdder</a></td>
</tr>

<tr>
<td>badd-preamble-epilogue</td>
<td>bAdds a String preamle and/or epilogue to the stream</td>
<td>preamble (String), epilogue (String)</td>
<td>StreamReceiver</td>
<td>String</td>
<td><a href="https://github.com/metafacture/metafacture-core/blob/master/metafacture-formatting/src/main/java/org/metafacture/formatting/PreambleEpilogueAdder.java">example in Playground</a></td>
<td><a href="">org.metafacture.formatting.PreambleEpilogueAdder</a></td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

0 comments on commit 70e9743

Please sign in to comment.