Table Editor

https://confluence.ontrq.com/display/KB/Medium+level+-+native+JS/#Mediumlevel-nativeJS-#3:TableEditor

by fullyslick

HTML

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="style.css">
    <title>Table Editor</title>
</head>
<body>
<div class="container">
    <h1>Table editor</h1>
    <form action="#" class="filter-form">
        <label for="name-filter">Filter By Name</label>
        <input type="text" name="name-filter" id="name-filter">
    </form>
    <table>
        <thead>
        <tr id="tableHeadersShell">
            <th class="sort-header" data-sort="id">Id</th>
            <th class="sort-header" data-sort="name">Name</th>
            <th class="sort-header" data-sort="quantity">Qty</th>
            <th>Availability</th>
            <th>Delete</th>
        </tr>
        </thead>
        <tbody id="rowList">
        </tbody>
    </table>

    <div class="operations">
        <button type="button" id="addRowBtn" class="operation-button white">Add new row</button>
        <button type="button" id="deleteRowBtn" class="operation-button red">Delete row</button>
        <button type="button" id="demoDataBtn" class="operation-button green">Demo data</button>
        <button type="button" id="clearBtn" class="operation-button blue">Clear table</button>
        <button type="button" id="exportTableBtn" class="operation-button yellow">Export table</button>
    </div>
    <form action="#" id="addRowForm" name="addrowform" class="add-row-form">
        <h2>Add new row</h2>
        <div class="form-group">
            <label for="rowName">Name</label>
            <input type="text" name="rowName" id="rowName" required>
        </div>
        <div class="form-group">
            <label for="rowQty">Qty</label>
            <select name="rowQty" id="rowQty">
                <option value="1">1</option>
                <option...

CSS

body {
    font-family: Arial, sans-serif;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
h1,
h2 {
    text-align: center;
}
.container {
    max-width: 960px;
    margin: 0 auto;
}
table {
    width: 100%;
    padding: 5px;
    border-collapse: collapse;
}
table, th, td {
    border: solid 1px grey;
}
thead {
    background: lightgrey;
}
td {
    text-align: center;
    vertical-align: middle;
}
td:hover {
    cursor: grab;
}
tbody tr {
    border-top: grey 1px solid;
    display: none;
}
tbody tr:nth-child(even) {
    background-color: #f3f3f3;
}
tbody tr:nth-child(odd) {
    background-color: #fff;
}
textarea {
    display: block;
    margin: 10px 0;
}
.active {
    display: table-row;
}
.filter-form {
    margin: 10px 0;
}
.add-row-form {
    border: solid 1px grey;
}
.form-group {
    padding: 10px;
    border-bottom: solid 1px grey;
}
.form-submit {
    margin: 10px;
}
label {
    display: block;
    margin: 0 0 10px 0;
}
.operations {
    margin: 20px;
}
.operation-button {
    line-height: 2;
    color: white;
    border: solid grey 1px;
    border-radius: 5px;
}
.white {
    color: black;
    background: white;
}
.red {
    background: red;
}
.green {
    background: green;
}
.blue {
    background: blue;
}
.yellow {
    background: darkorange;
}
.hidden {
    display: none;
}
.pagination > ul {
    list-style: none;
    display: flex;
    flex-flow: row wrap;
}
.pagination-btn {
    display: block;
    background: none;
    color: dodgerblue;
    border: solid 1px darkgrey;
    text-decoration: none;
    padding: 3px 10px;
}
.pagination-btn:hover {
    cursor: pointer;
}
.sort-header:hover {
    cursor: pointer;
}
.sort-header.ascending::after {
    content: ' ↑';
}
.sort-header.descending::after {
    content: ' ↓';
}

JavaScript

// https://confluence.ontrq.com/display/KB/Medium+level+-+native+JS/#Mediumlevel-nativeJS-#3:TableEditor
// #3: Table Editor Module 3 and Module 8 Sorting

(function() {
    'use strict';

    var model = {
        id: 0,
        rows: {},
        filteredRows: {},
        sortedRows: {},
        addRow: function(row) {

            var rowId = this.id + 1;

            if (row.availability) {
                row.availability = 'yes';
            } else {
                row.availability = 'no';
            }

            // Insert the newly created row to the model.rows object
            this.rows[this.id] = [rowId, row.name, row.qty, row.availability];

            // Increment id every time row is added to make it unique
            this.id++;
        },
        deleteRow: function(rowId) {

            rowId = Number(rowId);

            // Find the requested for deletion row and delete it
            for (var rowKey in this.rows) {
                if (this.rows.hasOwnProperty(rowKey)) {
                    if (this.rows[rowKey][0] === rowId) {
                        delete this.rows[rowKey];
                    }
                }
            }
        },
        deleteAllRows: function() {
            this.rows = {};
        },
        setDemoData: function() {
            var rowAmount = this.generateRandomInt(1, 10),
                row = {};

            for (var i = 0; i < rowAmount; i++) {
                row = this.generateRandomRow();

                this.addRow(row);
            }
        },
        generateRandomRow: function() {
            return {
                name: this.generateRandomName(),
                qty: this.generateRandomInt(0, 1000),
                availability: this.generateRandomInt(0, 1)
            };
        },
        generateRandomInt: function(min, max) {
            min = Math.ceil(min);
            max = Math.floor(max);
            return Math.floor(Math.random() * (max - min + 1)) + min;
        },
       ...