JS - Geordende tabellen
- We maken het mogelijk om tabellen de ordenen op de inhoud van één bepaalde kolom.
- Je kan per kolom aangeven als de ordening dalend of stijgend moet zijn.
- Die functionaliteit moet aan meer dan één tabel kunnen toegevoegd worden door middel van slechts één instructie.
- Die functionaliteit moet zowel via het toetsenbord als via de muis toegankelijk zijn.
- De code vind je terug op Bitbucket.
Video
Constructor functie
Om de code te kunnen toepassen op om het even welke tabel moeten we een constructor-functie schrijven (zie JS - Object Constructor). Van deze functie maken voor elke tabel, die we sorteerbaar willen maken, een instantie:
function TableSort(id, continentalNotation = true) {
    // When calling an object constructor or any of its methods,
    // this’ refers to the instance of the object
    // much like any class-based language
    this.continentalNotation = continentalNotation;
    this.tableElement = document.getElementById(id);
    if (this.tableElement && this.tableElement.nodeName == "TABLE") {
        this.prepare();
    }
}
De code hierboven maakt een constructor-functie met de naam TableSort. Met erop dat de naam van deze functie in pascal-notatie geschreven en niet in camelcase zoals dat de gewoonte is voor gewone functies.
Wanneer je de functie oproept geef je de id-selector naam mee van de tabel. De functie gebruikt de getElementId DOM methode om een referentie naar de tabel op te halen. Die referentie wordt opgeslagen in this.tableElement. Het sleutelwoord this verwijst hier naar instantie van de constructor-functie die met het new sleutelwoord gemaakt zal worden. De code test als de opgegeven id-selector overeenkomt met een table element en indien dat zo is wordt de prepare methode uitgevoerd. Deze methode doet het werk om de tabel sorteerbaar te maken. Het sorteren zelf gebeurt pas als de gebruiker op één van de koppen van de tabel klikt.
Met de tweede parameter geef je aan in welke notatie de getallen geschreven zijn. In de Engelse notatie worden kommagetallen gescheiden door een punt en duizenden door een komma. In de continentale schrijfwijze is het precies omgekeerd. Onze sort methode moet dus rekening kunnen houden met de manier waarop de getallen geschreven worden. We declareren daarvoor een eigenschap this.continentalNotation en stellen die standaar in op true.
De TableSort roepen we pas op als de HTML volledig in de browser geladen is. Dat doen we bijvoorbeeld wanneer de browser het onload event afvuurt:
window.onload = function() {
    var jommeke = new TableSort("jommeke");
    var fruit = new TableSort("fruit");
}
Kolomkoppen toegankelijk maken
- preparemethode- De preparemethode voegen we aan onze functie toe met behulp van deprototypeeigenschap ervan (zie hiervoor JS - Objecten - Prototype). De prototype eigenschap is een object dat het toevoegen van een eigenschap of een methode aan alle instanties van een constructor-functie eenvoudig maakt:TableSort.prototype.prepare = function() { ... }
- In de preparemethode beginnen we met aan te geven dat de sorteervolgorde standaard stijgend is. Daarvoor voegen we aan elke kolomkop een pijltje naar boven toe. We gaan ervan uit dat als de gebruiker voor de eerste keer op een kolomkop klikt de kolom in stijgende volgorde gesorteerd zal worden.
 Om te onthouden in welke volgorde de kolom gesorteerd zal worden wanneer de gebruiker op de kolomkop stellen we de klassennaam van hetthelement, die het sort event zal afvuren, in op asc. Om dit te realiseren:- maken we twee CSS klassen en gebruiken de ::afterselector om een pijltje achter de koptekst van de kolom toe te voegen. De klasseascvoor een pijltje naar boven endescvoor een pijltje naar beneden:/* css entities: https://www.w3schools.com/cssref/css_entities.asp */ .asc::after { content: "\0020\0020\0020\2191"; } .desc::after { content: "\0020\0020\0020\2193"; }
- doorlopen we de headingshtmlcollectie met eenforlus en stellen de we css klasse van alleth's in opasc:TableSort.prototype.prepare = function () { // add arrow up // default is ascending order let headings = this.tableElement.tHead.rows[0].cells; // headings is een htmlcollection for (let i = 0; i < headings.length; i++) { headings[i].className = 'asc'; } }
- Tenslotte voegen we wat CSS toe om de intentie van UI duidelijker te maken. Als de gebruiker over de kolomkop zweeft met de muis wordt die in reverse getoond:
			th:hover { cursor: hand; cursor: pointer; color: white; background-color: #630; }
 
- maken we twee CSS klassen en gebruiken de 
- Nadat we het pijltje met behulp van de ascklasse aan de header hebben toegevoegd, moeten we nu een eventhandelaar toekennen die zal worden uitgevoerd als de gebruiker op een kolomkop van een tabel klikt. We koppelen de eventafhandelaar aan hetclickevent van de tabel.
 Ons eerste idee is om de eventafhandelaar met de volgende code toe te voegenTableSort.prototype.prepare = function () { // add arrow up // default is ascending order let headings = this.tableElement.tHead.rows[0].cells; // headings is een htmlcollection for (let i = 0; i < headings.length; i++) { headings[i].className = 'asc'; } this.tableElement.addEventListener("click", this.eventHandler, false); }Maar als je goed nadenkt zie je dat hier een probleem is. Je zou denken dat dethisvóór de eventHandler verwijst naar de instantie vanTableSortomdat je inTableSortbezig bent te coderen. Maar vergeet niet dat een eventafhandelaar eigenlijk een callback functie is. De codethis.eventHandlerwordt niet afgevuurd op het moment dat de EventListener wordt toegevoegd maar op het moment dat het event wordt afgevuurd. Op dat moment verwijstthisnaar de kopkolom waarop geklikt werd en zalthisdus verwijzen naar die kopkolom. En het kopkolom object beschikt natuurlijk niet over de methodesortColumn. Dus op het moment dat we de EventListener toevoegen moeten we een verwijzing naar dethisvan hetSortTableobject onthouden. Dat doen we met behulp van een closure (zie Closures en Closures in de praktijk):TableSort.prototype.prepare = function () { // add arrow up // default is ascending order let headings = this.tableElement.tHead.rows[0].cells; // headings is een htmlcollection for (let i = 0; i < headings.length; i++) { headings[i].className = 'asc'; } this.tableElement.addEventListener("click", function (that) { return function (event) { that.eventHandler(event); } }(this), false); }We hebben hier wel een mooi voorbeeld van het gevolg dat functies in JavaScript eersteklasrangburgers zijn:- anonieme functie: in de eventHandler geven we een anonieme functie mee die een anonieme functie retourneert
- closure: de buitenste anonieme functie maakt een closure die ervoor zorgt dat de referentie naar het object TableSort bij het afvuren van het klik event bekend is;
- een IIFE of Immediately-Invoked Function Expression: bij het toekennen van de anonieme functie aan de eventhandler wordt die anonieme functie onmiddelijk uitgevoerd, dat gebeurt dus tijdens het toekennen van de functie aan de eventhandler en niet op het moment dat gebruikt op de kolomkop klikt; dat doen we omdat we de referentie naar de this van TableSort met behulp van een closure willen meegeven aan de eventhandler die slechts zal worden uitgevoerd op het moment dat erop de kolomkop wordt geklikt;
 
 
- De 
- eventHandlermethode
 De gebruiker moet kunnen aangeven dat de tabel geordend moet worden met behulp van het toetsenbord en van de muis. Toegankelijk voor de gebruiker wil zeggen dat we door de gebruiker afgevuurde events moeten kunnen ondervangen. We gebruiken hiervoor:- addEventListenervan de DOM;
- 
		de opgaande stroom van de events (zie hiervoor JS - Eventstroom): we gaan niet aan elke kolomkop afzonderlijk een eventafhandelaar toekennen. We laten de afgevuurde events opborrelen tot aan het table element en pas aan het tableelement kennen we eenEventListenertoe die zal nagaan welk element in de tabel het event heeft afgevuurd. Op die manier houden we alle logica die te maken heeft met de interactie met de gebruiker in één overzichtelijke methode.Als het event werd afgevuurd door op een thelement te klikken wordt de sorteer methode uitgevoerd. Om te verifiëren als erop een th is geklikt schrijven we het volgende statement:if (event.target.tagName === 'TH') { // alert('kolomkop'); this.sortColumn(event.target); ... }Maar als er een nog een ander HTML element in het thelement staat, in de kopteksten van de suiker tabel staat bijvoorbeeld<th><strong></strong>Product</strong></th>, gaat de sort mehode niet uitgevoerd worden. Want er wordt niet op eenthelement geklikt maar op eenstrongelement. Daarom gaan we zoeken naar het eerst bovenliggendethelement van het element waarop geklikt werd, in ons voorbeeld eenstrongelement. We gebruiken daarvoor deevent.target.closest('TH')methode. Meer info hierover vind je op Detecting clicks inside an element with vanilla JavaScript.
 - TableSort.prototype.eventHandler = function (event) { // zoek het eerst bovenliggende TH element en sla het op in een variabele let elemTarget = event.target.closest('TH'); if (elemTarget.tagName === 'TH') { // alert('kolomkop'); // sorteer eerst in de richting van de pijl this.sortColumn(elemTarget); // draai de pijl om om de richting van de volgende // sort te bepalen als de gebruiker weer klikt op de kolomkop if (elemTarget.className === "asc") { elemTarget.className = 'desc'; } else { elemTarget.className = 'asc'; } } }
Het sorteer algoritme
Op het moment dat de gebruiker op een kolomkop klikt moet de tabel geordend worden op deze kolom. De eventHandler ondervangt dit klik-event en voert de sortColomn methode uit.
- Onthouden in welke volgorde de kolom gesorteerd is. Daarvoor hallen we de klassennaam op van het thelement waarop geklikt werd.
- Initialiseer enkele variabelen:
	- rows: deze variabele gebruiken als een shortcut die verwijst naar de tabelrijen, op die manier moeten we niet altijd de volledige- this.tableElement.rowsintypen
- alpha,- numeric: in deze arrays slaan we alfanumerieke en numerieke inhoud van de cellen in de tabel op en de rijindex van de cel in een literal array van de vorm:- { value: 'de inhoud van cel' row: index van de rij waarin de cel staat }
- aIndex,- nIndex: deze variabelen gebruiken we als indices, telkens als we een element aan een van beide array's toevoegen wordt die met 1 vermeerderd
- cellIndex: in deze variabele zetten we- cellIndexwaarde van de kopkolom waarop geklikt werd.- cellIndexis een eigenschap van het- colDOM element en retourneert de plaats van de kolom in de rij waarin die zich bevindt.
 TableSort.prototype.sortColumn = function(headerCell) { // onthouden in welke volgorde de kolom nu geordend is const order = headerCell.className; // Get cell data for column that is to be sorted from HTML table let rows = this.tableElement.rows; let alpha = [], numeric = []; let alphaIndex = 0, numericIndex = 0; let cellIndex = headerCell.cellIndex; . . . }
- Vervolgens doorlopen we elke rij van de tabel en bewerken de cel die onder de kolomkop valt waarop de gebruiker geklikt heeft. We willen alleen de tekst uit de cel halen en de eventuele HTML erin negeren. Daarvoor gebruiken we de textContentofinnerTexteigenschap. Firefox ondersteunt alleentextContenten IE alleeninnerText. Andere browsers ondersteunen beiden. We gebruiken daarom de ternaire operator die de een of de andere eigenschap gebruikt afhankelijk van welke eigenschap in de browser voorhanden is.TableSort.prototype.sortColumn = function(headerCell) { ... for (let i = 1; rows[i]; i++) { let cell = rows[i].cells[cellIndex]; let content = cell.textContent ? cell.textContent : cell.innerText; alert(content) } }
- We moeten kunnen bepalen of de cel een numerieke waarde bevat of niet:
	- Daarvoor moeten we eerst alle karakters verwijderen die ervoor kunnen zorgen dat de celinhoud geïnterpreteerd wordt als alfanumeriek zoals het euroteken of dollarteken enz. We verwijderen alle alfakaracters en wijzigen Europese notatie in Engelse.
 Eventuele verbetering: aan de constructor meegeven of Engelse of contintentale notatie gebruikt wordt.
 Dat doen we met een regulieren expressie (zie meer daarover op reguliere expressies).
- Met de parseFloatfunctie gaan we na of de uitgezuiverde waarde een getal is of niet.
- Indien het een getal is slaan we uitgezuiverde waarde en de referentie naar de rij waarin de cel staat, als een literal array, in de numeric array (in de laatste wijziging beschouwen we de tekst in de cel als getal als er cijfers in staan, de alpha tekst wordt verwijderd)
- anders in de alpha array.
- de literal array, de uitgezuiverde waarde en de referentie naar de rij waarin de betreffende kolom staat, hebben we later nodig om de geordende tabel weer op te bouwen;
 TableSort.prototype.sortColumn = function(headerCell) { // onthoudt in welke volgorde de kolom nu geordend is const order = headerCell.className; // Get cell data for column that is to be sorted from HTML table let rows = this.tableElement.rows; let alpha = [], numeric = []; let alphaIndex = 0, numericIndex = 0; let cellIndex = headerCell.cellIndex; for (var i = 1; rows[i]; i++) { let cell = rows[i].cells[cellIndex]; let content = cell.textContent ? cell.textContent : cell.innerText; // let numericValue = content.replace(/(\€\$|\,|\s)/g, ""); // als er getallen instaan, verwijder alle karakters die geen getallen, // punt of komma zijn let numericValue; if (this.continentalNotation) { // vervang vervolgens komma door punt en punt door komma numericValue = content.replace(/[^0-9,.]*/g, "").replace(/[,.]/g, function (m) { // m is the match found in the string // If `,` is matched return `.`, if `.` matched return `,` return m === ',' ? '.' : ','; }); } else { numericValue = content.replace(/[^0-9,.]*/g, ""); } // alert(numericValue); if (parseFloat(numericValue) == numericValue) { numeric[numericIndex++] = { value: Number(numericValue), row: rows[i] } } else { alpha[alphaIndex++] = { value: content, row: rows[i] } } } }
- Daarvoor moeten we eerst alle karakters verwijderen die ervoor kunnen zorgen dat de celinhoud geïnterpreteerd wordt als alfanumeriek zoals het euroteken of dollarteken enz. We verwijderen alle alfakaracters en wijzigen Europese notatie in Engelse.
- De sort implementeren
	We gaan niet zelf een sorteeralgoritme schrijven maar de sortmethode van hetArrayobject gebruiken (zie JS - array methoden - muterend). We gebruiken deordervariabele om te kijken als we in dalende of stijgende volgorde moeten sorteren.
 Voeg de volgende code toe aan het einde van deSortColumnmethode van hierboven:TableSort.prototype.sortColumn = function (headerCell) { // onthoudt in welke volgorde de kolom nu geordend is const order = headerCell.className; // alert(order); // Get cell data for column that is to be sorted from HTML table let rows = this.tableElement.rows; let alpha = [], numeric = []; let alphaIndex = 0, numericIndex = 0; let cellIndex = headerCell.cellIndex; for (let i = 1; rows[i]; i++) { let cell = rows[i].cells[cellIndex]; let content = cell.textContent ? cell.textContent : cell.innerText; // let numericValue = content.replace(/(\€\$|\,|\s)/g, ""); // als er getallen instaan, verwijder alle karakters die geen getallen, // punt of komma zijn let numericValue; if (this.continentalNotation) { // vervang vervolgens komma door punt en punt door komma numericValue = content.replace(/[^0-9,.]*/g, "").replace(/[,.]/g, function (m) { // m is the match found in the string // If `,` is matched return `.`, if `.` matched return `,` return m === ',' ? '.' : ','; }); } else { numericValue = content.replace(/[^0-9,.]*/g, ""); } // alert(numericValue); if (parseFloat(numericValue) == numericValue) { numeric[numericIndex++] = { value: Number(numericValue), row: rows[i] } } else { alpha[alphaIndex++] = { value: content, row: rows[i] } } } numeric.sort(function (a, b) { if (order === 'asc') { return a.value - b.value; } else { return b.value - a.value; } }); alpha.sort(function (a, b) { let aName = a.value.toLowerCase(); let bName = b.value.toLowerCase(); if (aName < bName) { if (order === 'asc') { return -1; } else { return 1 } } else if (aName > bName) { if (order === 'asc') { return 1; } else { return -1 } } else { return 0; } }); }
- De geordende tabel opnieuw genereren
	Tensolotte moeten we de tabel opnieuw opbouwen maar nu gesorteerd op de inhoud van de geselecteerde kolom. Voeg daarvoor de volgende code toe aan het einde van de SortColumnmethode van hierboven:TableSort.prototype.sortColumn = function (headerCell) { // onthoudt in welke volgorde de kolom nu geordend is const order = headerCell.className; // alert(order); // Get cell data for column that is to be sorted from HTML table let rows = this.tableElement.rows; let alpha = [], numeric = []; let alphaIndex = 0, numericIndex = 0; let cellIndex = headerCell.cellIndex; for (let i = 1; rows[i]; i++) { let cell = rows[i].cells[cellIndex]; let content = cell.textContent ? cell.textContent : cell.innerText; // let numericValue = content.replace(/(\€\$|\,|\s)/g, ""); // als er getallen instaan, verwijder alle karakters die geen getallen, // punt of komma zijn let numericValue; if (this.continentalNotation) { // vervang vervolgens komma door punt en punt door komma numericValue = content.replace(/[^0-9,.]*/g, "").replace(/[,.]/g, function (m) { // m is the match found in the string // If `,` is matched return `.`, if `.` matched return `,` return m === ',' ? '.' : ','; }); } else { numericValue = content.replace(/[^0-9,.]*/g, ""); } // alert(numericValue); if (parseFloat(numericValue) == numericValue) { numeric[numericIndex++] = { value: Number(numericValue), row: rows[i] } } else { alpha[alphaIndex++] = { value: content, row: rows[i] } } } numeric.sort(function (a, b) { if (order === 'asc') { return a.value - b.value; } else { return b.value - a.value; } }); alpha.sort(function (a, b) { let aName = a.value.toLowerCase(); let bName = b.value.toLowerCase(); if (aName < bName) { if (order === 'asc') { return -1; } else { return 1 } } else if (aName > bName) { if (order === 'asc') { return 1; } else { return -1 } } else { return 0; } }); let orderdedColumns = []; orderdedColumns = numeric.concat(alpha); let tBody = this.tableElement.tBodies[0]; for (let i = 0; orderdedColumns[i]; i++) { tBody.appendChild(orderdedColumns[i].row); } }En hiermee is de sortColumnmethode volledig afgewerkt.
Een JS library bestand maken
Tenslote halen we de JavaScript code voor het ordenen van een tabel uit de HTML en plaatsen het in een apart bestand met de naam tablesort.js in de map js.
De HTML
Als voorbeeld nemen we enkele Jommeke's albums en een tabel die aangeeft hoeveel calorieën er in 100 g fruit zit. We gebruiken twee tabellen omdat onze sorteerfunctie op meer dan één tabel op dezelfde pagina toegepast moet kunnen worden:
<table id="jommeke" class="spreadsheet">
    <caption>Jommeke albums</caption>
    <col />
    <col />
    <col />
    <col />
    <col />
    <col />
    <thead>
        <tr>
            <th scope="col">Nummer</th>
            <th scope="col">Titel</th>
            <th scope="col">Kaft</th>
            <th scope="col">€</th>
            <th scope="col">¥</th>
            <th scope="col">£</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th scope="row">1</th>
            <td>Jacht op een voetbal</td>
            <td>Softcover</td>
            <td>5,22</td>
            <td>34</td>
            <td>3,76</td>
        </tr>
        <tr>
            <th scope="row">2</th>
            <td>De zingende aap</td>
            <td>Softcover</td>
            <td>5,22</td>
            <td>34</td>
            <td>3,76</td>
        </tr>
        <tr>
            <th scope="row">3</th>
            <td>De Koningin van Onderland</td>
            <td>Hardcover</td>
            <td>8,22</td>
            <td>54,1</td>
            <td>5,91</td>
        </tr>
        <tr>
            <th scope="row">4</th>
            <td>Purperen pillen</td>
            <td>Softcover</td>
            <td>5,22</td>
            <td>34</td>
            <td>3,76</td>
        </tr>
        <tr>
            <th scope="row">5</th>
            <td>De Muzikale Bella</td>
            <td>Hardcover</td>
            <td>8,22</td>
            <td>54,1</td>
            <td>5,91</td>
        </tr>
    </tbody>
</table>
De calorieëntabel voor fruit:
<table id="fruit" class="spreadsheet">
    <thead>
        <tr>
            <th>Fruitsoort</th>
            <th>hoeveelheid</th>
            <th>calorieën</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>abrikoos met schil</td>
            <td>3 st. (100 gr)</td>
            <td align="right">48</td>
        </tr>
        <tr>
            <td>ananas</td>
            <td>100 gr</td>
            <td align="right">60</td>
        </tr>
        <tr>
            <td>appel met schil</td>
            <td>1 st. (100 gr)</td>
            <td align="right">52</td>
        </tr>
        <tr>
            <td>banaan</td>
            <td>1 st .</td>
            <td align="right">94</td>
        </tr>
        <tr>
            <td>blauwe bessen</td>
            <td>100 g</td>
            <td align="right">75</td>
        </tr>
        <tr>
            <td>citroen</td>
            <td>1 st.</td>
            <td align="right">17</td>
        </tr>
        <tr>
            <td>druiven</td>
            <td>100 g</td>
            <td align="right">54</td>
        </tr>
        <tr>
            <td>frambozen</td>
            <td>100 g</td>
            <td align="right">68</td>
        </tr>
        <tr>
            <td>grapefruit</td>
            <td>1 st.</td>
            <td align="right">82</td>
        </tr>
        <tr>
            <td>kersen</td>
            <td>100 g</td>
            <td align="right">64</td>
        </tr>
        <tr>
            <td>kiwi</td>
            <td>1 st</td>
            <td align="right">46</td>
        </tr>
        <tr>
            <td>nectarine</td>
            <td>1 st.</td>
            <td align="right">67</td>
        </tr>
        <tr>
            <td>peer</td>
            <td>1 st.</td>
            <td align="right">98</td>
        </tr>
        <tr>
            <td>perzik</td>
            <td>1 st.</td>
            <td align="right">42</td>
        </tr>
        <tr>
            <td>pruim</td>
            <td>1 st.</td>
            <td align="right">36</td>
        </tr>
        <tr>
            <td>sinaasappel</td>
            <td>1 st.</td>
            <td align="right">86</td>
        </tr>
        <tr>
            <td>watermeloen</td>
            <td>100 g</td>
            <td align="right">37</td>
        </tr>
    </tbody>
</table>
De suiker tabel
<table id="fruit-sugar">
    <caption>Suikerlijst fruit</caption>
    <thead>
        <tr>
            <th><strong>Product</strong></th>
            <th><strong>Per portie</strong></th>
            <th><strong>Suiker</strong></th>
            <th><strong>Energie</strong></th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Aardbeien</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>5,1 g</td>
            <td>29 kcal</td>
        </tr>
        <tr>
            <td>Abrikozen, gedroogd</td>
            <td>1 stuks (= 9 g)</td>
            <td>5 g</td>
            <td>26 kcal</td>
        </tr>
        <tr>
            <td>Abrikozen, vers</td>
            <td>1 stuk (= 20 g)</td>
            <td>1,6 g</td>
            <td>9 kcal</td>
        </tr>
        <tr>
            <td>Ananas</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>11,6 g</td>
            <td>57 kcal</td>
        </tr>
        <tr>
            <td>Appel</td>
            <td>1 stuk (= 135 g)</td>
            <td>14 g</td>
            <td>81 kcal</td>
        </tr>
        <tr>
            <td>Banaan</td>
            <td>1 middel (= 130 g)</td>
            <td>20,1 g</td>
            <td>124 kcal</td>
        </tr>
        <tr>
            <td>Blauwe bessen</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>10 g</td>
            <td>52 kcal</td>
        </tr>
        <tr>
            <td>Blauwe druiven</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>16,8 g</td>
            <td>75 kcal</td>
        </tr>
        <tr>
            <td>Cranberries, gedroogd</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>64,6 g</td>
            <td>335 kcal</td>
        </tr>
        <tr>
            <td>Cranberries, vers</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>3,4 g</td>
            <td>24 kcal</td>
        </tr>
        <tr>
            <td>Dadels, gedroogd, geconfijt</td>
            <td>1 stuk (= 6 g)</td>
            <td>4,2 g</td>
            <td>19 kcal</td>
        </tr>
        <tr>
            <td>Dadels, vers</td>
            <td>1 stuk (= 6 g)</td>
            <td>1,9 g</td>
            <td>8 kcal</td>
        </tr>
        <tr>
            <td>Frambozen</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>4,5 g</td>
            <td>35 kcal</td>
        </tr>
        <tr>
            <td>Grapefruit</td>
            <td>1 stuk (= 150 g)</td>
            <td>10 g</td>
            <td>28 kcal</td>
        </tr>
        <tr>
            <td>Kaki</td>
            <td>1 stuk (= 150 g)</td>
            <td>27,9 g</td>
            <td>116 kcal</td>
        </tr>
        <tr>
            <td>Kersen</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>11,5 g</td>
            <td>54 kcal</td>
        </tr>
        <tr>
            <td>Kiwi</td>
            <td>1 stuks (= 75 g)</td>
            <td>7,7 g</td>
            <td>51 kcal</td>
        </tr>
        <tr>
            <td>Kumquat</td>
            <td>1 stuk (= 10 g)</td>
            <td>0,9 g</td>
            <td>5 kcal</td>
        </tr>
        <tr>
            <td>Lychee</td>
            <td>1 stuk (= 10 g)</td>
            <td>1,6 g</td>
            <td>7 kcal</td>
        </tr>
        <tr>
            <td>Mandarijn</td>
            <td>1 stuk (= 55 g)</td>
            <td>4,5 g</td>
            <td>25 kcal</td>
        </tr>
        <tr>
            <td>Mango</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>13,9 g</td>
            <td>66 kcal</td>
        </tr>
        <tr>
            <td>Nectarine</td>
            <td>1 stuk (= 90 g)</td>
            <td>5,9 g</td>
            <td>32 kcal</td>
        </tr>
        <tr>
            <td>Papaja</td>
            <td>1 stuk (= 100 g)</td>
            <td>7,8 g</td>
            <td>39 kcal</td>
        </tr>
        <tr>
            <td>Passievrucht</td>
            <td>1 stuk (= 15 g)</td>
            <td>0,9 g</td>
            <td>8 kcal</td>
        </tr>
        <tr>
            <td>Peer (met schil)</td>
            <td>1 stuk (= 150 g)</td>
            <td>14,2 g</td>
            <td>82 kcal</td>
        </tr>
        <tr>
            <td>Perzik</td>
            <td>1 stuk (= 110 g)</td>
            <td>8,7 g</td>
            <td>45 kcal</td>
        </tr>
        <tr>
            <td>Pruim, gedroogd</td>
            <td>1 stuk (= 8 g)</td>
            <td>3,6 g</td>
            <td>20 kcal</td>
        </tr>
        <tr>
            <td>Pruim, vers</td>
            <td>1 stuk (= 40g)</td>
            <td>2,9 g</td>
            <td>18 kcal</td>
        </tr>
        <tr>
            <td>Rozijnen, gedroogd</td>
            <td>1 handje (= 35 g)</td>
            <td>25,5 g</td>
            <td>127 kcal</td>
        </tr>
        <tr>
            <td>Sinaasappel</td>
            <td>1 stuk (= 120 g)</td>
            <td>9,2 g</td>
            <td>61 kcal</td>
        </tr>
        <tr>
            <td>Vijgen, gedroogd</td>
            <td>1 stuk (= 20 g)</td>
            <td>9,6 g</td>
            <td>52 kcal</td>
        </tr>
        <tr>
            <td>Vijgen, vers</td>
            <td>1 stuk (= 50 g)</td>
            <td>9,5 g</td>
            <td>42 kcal</td>
        </tr>
        <tr>
            <td>Watermeloen</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>8 g</td>
            <td>36 kcal</td>
        </tr>
        <tr>
            <td>Witte druiven</td>
            <td>1 schaaltje (= 100 g)</td>
            <td>15,6 g</td>
            <td>76 kcal</td>
        </tr>
    </tbody>
</table>
En de CSS om de tabel een beetje uitzicht te geven (css/tablesort.css):
table {
    border-collapse: collapse;
    border: 0.05em solid black;
    border-radius: 5em / 5em;
    background-color: #b6ff00;
}
table caption {
    font-weight: bold;
    font-size: 125%;
    text-transform: uppercase;
}
.footnote {
    font-size: 75%;
    color: #666;
}
table caption,
table th,
table td,
.footnote {
    font-family: Arial, Helvetica, sans-serif;
    padding: .5em;
}
table td {
    border: solid 1px #C35E4D;
}
table tbody th {
    color: #630;
    border-bottom: solid 1px #C35E4D;
}
table thead th {
    background: #ffe4cd ;
    border-bottom: solid 3px #E75D49;
}
/* css entities: https://www.w3schools.com/cssref/css_entities.asp */
.asc::after {
    content: "\0020\0020\0020\2191";
}
.desc::after {
    content: "\0020\0020\0020\2193";
}
table thead th:hover {
    cursor: hand;
    cursor: pointer;
    color: white;
    background-color: #630;
}