diff --git a/app.ts b/app.ts new file mode 100644 index 00000000..89ba051a --- /dev/null +++ b/app.ts @@ -0,0 +1,15 @@ +window.onload = async () => { + let state = new State(); + + // Get data from server and load into table + await state.getData().then(resp => { + if (resp == true) { + state.loadIntoTable(true); + } + }) + + // Set needed navigation controls + state.setSearchButton(); + state.setPageButtons(); + state.setResizeEvent(); +}; diff --git a/index.html b/index.html index add5e736..7e6b2aed 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,39 @@ JS Onboard Project + + + -

Hello

+
+
+
+
+
+
+

IMQS Onboarding Project

+
+ +
+ + + +
+
+
+ + + + +
+

+
+
- - diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..9adaa2d7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,29 @@ +{ + "name": "onboard-javascript", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/jquery": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.13.tgz", + "integrity": "sha512-ZxJrup8nz/ZxcU0vantG+TPdboMhB24jad2uSap50zE7Q9rUeYlCF25kFMSmHR33qoeOgqcdHEp3roaookC0Sg==", + "requires": { + "@types/sizzle": "*" + } + }, + "@types/jquery.browser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@types/jquery.browser/-/jquery.browser-0.1.1.tgz", + "integrity": "sha512-1y/iQmNaVkQfVhCc39q3KYOqS/oZscurcbiOcYasiSoX8v8XMjkAfiBmgXMgMLRFzDDoNT6Jmr1Krqh3XDMDWQ==", + "requires": { + "@types/jquery": "*" + } + }, + "@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..24487ab9 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "onboard-javascript", + "version": "1.0.0", + "description": "onboard", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc --build" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/CelesteNaude/onboard-javascript.git" + }, + "author": "celeste", + "license": "ISC", + "bugs": { + "url": "https://github.com/CelesteNaude/onboard-javascript/issues" + }, + "homepage": "https://github.com/CelesteNaude/onboard-javascript#readme", + "dependencies": { + "@types/jquery.browser": "^0.1.1" + } +} diff --git a/state.js b/state.js new file mode 100644 index 00000000..2cf32de6 --- /dev/null +++ b/state.js @@ -0,0 +1,384 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +// Code is only triggered once per user input +var debounce = function (fn, ms) { + var timeoutId; + return function () { + var _this = this; + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + clearTimeout(timeoutId); + timeoutId = setTimeout(function () { return fn.apply(_this, args); }, ms); + }; +}; +var State = /** @class */ (function () { + function State() { + // Starting values for when page is first loaded + this.records = this.calculateRecords(); + this.trimStart = 0; + this.trimEnd = this.records - 1; + // Default values for server data variables + this.RECORDCOUNT = 350; + this.HEADERS = ["ID", "City", "Population"]; + this.data = [[0, "Cape Town", 3500000], [1, "New York", 8500000], [2, "Johannesburg", 4500000]]; + this.contentTable = document.getElementById('content-table'); + this.tableBody = document.querySelector('tbody'); + this.tableHead = document.getElementById('content-thead'); + this.pageInfo = document.getElementById('page-info'); + this.searchBtn = document.getElementById('id-search-btn'); + this.firstBtn = document.getElementById('first'); + this.prevBtn = document.getElementById('prev'); + this.nextBtn = document.getElementById('next'); + this.lastBtn = document.getElementById('last'); + this.inputBox = document.getElementById('id-search'); + } + // Record count and headers should not be changed outside the class + // hence there are only get methods for them + State.prototype.getRecordCount = function () { + return this.RECORDCOUNT; + }; + State.prototype.getHeaders = function () { + return this.HEADERS; + }; + State.prototype.calculateRecords = function () { + // Estimate of available table space + // The calculation is an estimate of how many space there is for rows + // 160 is estimate space for header and footer of website and 40 is estimated space of a single row + return Math.floor((window.innerHeight - 160) / 40); + }; + // The setPageButtons, setSearchButton, and setResizeEvent are seperate functions + // so that only the needed functions are used + State.prototype.setPageButtons = function () { + var _this = this; + // Event listener for button that goes to first page + this.firstBtn.addEventListener("click", debounce(function () { + _this.goToFirst(); + }, 250)); + // Event listener for button that goes to previous page + this.prevBtn.addEventListener("click", debounce(function () { + _this.goToPrev(); + }, 250)); + // Event listener for button that goes to next page + this.nextBtn.addEventListener("click", debounce(function () { + _this.goToNext(); + }, 250)); + // Event listener for button that goes to last page + this.lastBtn.addEventListener("click", debounce(function () { + _this.goToLast(); + }, 250)); + }; + State.prototype.setSearchButton = function () { + var _this = this; + // // Event listener for button that searches entered ID + this.searchBtn.addEventListener("click", debounce(function () { + _this.searchId(); + }, 250)); + }; + State.prototype.setResizeEvent = function () { + var _this = this; + // Event listener for when user resizes the page + window.addEventListener("resize", debounce(function () { + _this.resize(); + }, 150)); // Log window dimensions at most every 150ms + }; + // Fetch headers and record count + State.prototype.getData = function () { + return __awaiter(this, void 0, void 0, function () { + var _this = this; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + // API calls for record count and headers + return [4 /*yield*/, fetch('/recordCount') + .then(function (resp) { + if (resp.ok) { + return resp.json(); + } + throw new Error('Could not retrieve data'); + }) + .then(function (count) { + _this.RECORDCOUNT = count; + }) + .catch(function (error) { + console.error(error); + })]; + case 1: + // API calls for record count and headers + _a.sent(); + return [4 /*yield*/, fetch('/columns') + .then(function (resp) { + if (resp.ok) { + return resp.json(); + } + throw new Error('Could not retrieve data'); + }) + .then(function (count) { + _this.HEADERS = count; + }) + .catch(function (error) { + console.error(error); + })]; + case 2: + _a.sent(); + return [2 /*return*/, true]; + } + }); + }); + }; + // Add rows to table + State.prototype.addRows = function (start, end) { + return __awaiter(this, void 0, void 0, function () { + var table, newTableBody, oldTableBody, link, _i, _a, row, rowElement, _b, row_1, cellText, cellElement; + var _this = this; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + table = document.getElementById("content-table"); + newTableBody = document.createElement("tbody"); + oldTableBody = table.querySelector("tbody"); + link = "/records?from=" + start + "&to=" + end; + return [4 /*yield*/, fetch(link) + .then(function (resp) { + if (resp.ok) { + return resp.json(); + } + throw new Error('Could not retrieve data'); + }) + .then(function (count) { + _this.data = count; + }) + .catch(function (error) { + console.error(error); + })]; + case 1: + _c.sent(); + for (_i = 0, _a = this.data; _i < _a.length; _i++) { + row = _a[_i]; + rowElement = document.createElement("tr"); + for (_b = 0, row_1 = row; _b < row_1.length; _b++) { + cellText = row_1[_b]; + cellElement = document.createElement("td"); + cellElement.textContent = cellText; + rowElement.appendChild(cellElement); // Append cells to row + } + newTableBody.appendChild(rowElement); // Append rows to tbody + } + // If table exists, replace the new tbody with the old one + if (!table) { + console.error("Table is undefined"); + return [2 /*return*/]; + } + else { + table.replaceChild(newTableBody, oldTableBody); + } + return [2 /*return*/]; + } + }); + }); + }; + // Delete rows from table + State.prototype.deleteRows = function (newHeight, diff) { + var tableBod = this.contentTable.querySelector('tbody'); + var num = newHeight - 1; + for (var i = num; i > (num + diff); i--) { + tableBod.deleteRow(i); + } + }; + // Load json data into table function + State.prototype.loadIntoTable = function (clearHeader) { + var _a, _b, _c, _d, _e, _f, _g, _h; + // Display loader + $(".content").fadeOut(230); + $(".loader").fadeIn(230); + // UI "Aesthetic": update buttons + (_a = this.firstBtn) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled"); + (_b = this.prevBtn) === null || _b === void 0 ? void 0 : _b.removeAttribute("disabled"); + (_c = this.nextBtn) === null || _c === void 0 ? void 0 : _c.removeAttribute("disabled"); + (_d = this.lastBtn) === null || _d === void 0 ? void 0 : _d.removeAttribute("disabled"); + if (this.trimEnd == this.getRecordCount() - 1) { + (_e = this.lastBtn) === null || _e === void 0 ? void 0 : _e.setAttribute("disabled", "disabled"); + (_f = this.nextBtn) === null || _f === void 0 ? void 0 : _f.setAttribute("disabled", "disabled"); + } + if (this.trimStart == 0) { + (_g = this.firstBtn) === null || _g === void 0 ? void 0 : _g.setAttribute("disabled", "disabled"); + (_h = this.prevBtn) === null || _h === void 0 ? void 0 : _h.setAttribute("disabled", "disabled"); + } + this.pageInfo.innerHTML = "

"; + // Add header only if 'clearHeader' is true + if (clearHeader) { + this.tableHead.innerHTML = ""; + var headerRow = document.createElement("tr"); + // Populate the headers + for (var _i = 0, _j = this.getHeaders(); _i < _j.length; _i++) { + var headerText = _j[_i]; + var headerElement = document.createElement("th"); + headerElement.textContent = headerText; + headerRow.appendChild(headerElement); + } + this.tableHead.appendChild(headerRow); + } + // Clear the table + this.tableBody.innerHTML = ""; + // Add only records that must be displayed on table + this.addRows(this.trimStart, this.trimEnd); + // Display content + $(".loader").fadeOut(230); + $(".content").fadeIn(230); + }; + // Search entered ID + State.prototype.searchId = function () { + var id = parseInt(this.inputBox.value); + var numRecords = this.getRecordCount() - 1; + if (id < 0 || id > numRecords || isNaN(id)) { + // User info: Display error message + this.pageInfo.innerHTML = "

No records to display

"; + } + else { + // Use entered ID to calculate what records should display + if ((this.getRecordCount() - 1) - id >= this.records) { + this.trimStart = id; + this.trimEnd = this.trimStart + (this.records - 1); + } + else { + this.trimEnd = this.getRecordCount() - 1; + this.trimStart = this.trimEnd - this.records + 1; + } + this.loadIntoTable(false); + } + document.getElementById('id-search').value = 'Enter ID number'; + }; + // Set trim to start of data + State.prototype.goToFirst = function () { + this.trimStart = 0; + this.trimEnd = this.trimStart + this.records - 1; + this.loadIntoTable(false); + }; + // Set trim to previous data + State.prototype.goToPrev = function () { + // If previous page is end of data && there are not enough records to fill window + if ((this.trimStart - 1) - (this.records - 1) < 0) { + this.trimStart = 0; + this.trimEnd = this.trimStart + this.records - 1; + } + else { + this.trimEnd = this.trimStart - 1; + this.trimStart = this.trimEnd - this.records + 1; + } + this.loadIntoTable(false); + }; + // Set trim to next data + State.prototype.goToNext = function () { + // If next page is end of data && there are not enough records to fill window + if ((this.getRecordCount() - 1) - (this.trimEnd + 1) < this.records) { + this.trimEnd = this.getRecordCount() - 1; + this.trimStart = this.trimEnd - this.records + 1; + } + else { + this.trimStart = this.trimEnd + 1; + this.trimEnd = this.trimStart + this.records - 1; + } + this.loadIntoTable(false); + }; + // Set trim to end of data + State.prototype.goToLast = function () { + this.trimEnd = this.getRecordCount() - 1; + this.trimStart = this.trimEnd - this.records + 1; + this.loadIntoTable(false); + }; + // Add/remove rows from table based on resize event of the window + State.prototype.resize = function () { + var _a, _b, _c, _d, _e, _f, _g, _h; + return __awaiter(this, void 0, void 0, function () { + var newHeight, diff, start, end; + return __generator(this, function (_j) { + switch (_j.label) { + case 0: + newHeight = this.calculateRecords(); + diff = newHeight - this.records; + start = this.trimStart; + end = this.trimEnd + diff; + if (!(diff < 0)) return [3 /*break*/, 1]; + // If screen is made smaller, call delete rows function with this.records (amount of rows that were on the screen) + // and diff (how many rows should be deleted) + this.deleteRows(this.records, diff); + this.trimEnd = this.trimEnd + diff; + return [3 /*break*/, 4]; + case 1: + if (!(diff > 0 && end >= this.getRecordCount())) return [3 /*break*/, 3]; + // Prepend rows as last page gets bigger + end = this.getRecordCount() - 1; + start = end - (newHeight - 1); + console.log("End reached, displaying record ", start, " to ", end); + return [4 /*yield*/, this.addRows(start, end)]; + case 2: + _j.sent(); + this.trimStart = this.trimStart - diff; + this.trimEnd = this.getRecordCount() - 1; + return [3 /*break*/, 4]; + case 3: + if (diff > 0 && start <= this.getRecordCount() - 1) { + // Add rows if end of data is not yet reached + this.addRows(start, end); + this.trimEnd = this.trimEnd + diff; + } + _j.label = 4; + case 4: + this.records = newHeight; + // UI "Aesthetic": update buttons + (_a = this.firstBtn) === null || _a === void 0 ? void 0 : _a.removeAttribute("disabled"); + (_b = this.prevBtn) === null || _b === void 0 ? void 0 : _b.removeAttribute("disabled"); + (_c = this.nextBtn) === null || _c === void 0 ? void 0 : _c.removeAttribute("disabled"); + (_d = this.lastBtn) === null || _d === void 0 ? void 0 : _d.removeAttribute("disabled"); + if (this.trimEnd == this.getRecordCount() - 1) { + (_e = this.lastBtn) === null || _e === void 0 ? void 0 : _e.setAttribute("disabled", "disabled"); + (_f = this.nextBtn) === null || _f === void 0 ? void 0 : _f.setAttribute("disabled", "disabled"); + } + if (this.trimStart == 0) { + (_g = this.firstBtn) === null || _g === void 0 ? void 0 : _g.setAttribute("disabled", "disabled"); + (_h = this.prevBtn) === null || _h === void 0 ? void 0 : _h.setAttribute("disabled", "disabled"); + } + return [2 /*return*/]; + } + }); + }); + }; + return State; +}()); +//# sourceMappingURL=state.js.map \ No newline at end of file diff --git a/state.js.map b/state.js.map new file mode 100644 index 00000000..d436bc07 --- /dev/null +++ b/state.js.map @@ -0,0 +1 @@ +{"version":3,"file":"state.js","sourceRoot":"","sources":["state.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/state.ts b/state.ts new file mode 100644 index 00000000..0f9aeb37 --- /dev/null +++ b/state.ts @@ -0,0 +1,360 @@ +// Code is only triggered once per user input +const debounce = (fn: Function, ms: number) => { + let timeoutId: ReturnType; + return function (this: any, ...args: any[]) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => fn.apply(this, args), ms); + }; +}; + +class State { + // Variables to determine how many records there should be on the screen (records) + // and where these records should start and end (trimStart and trimEnd) > (display record 15 to 25) + records: number; + trimStart: number; + trimEnd: number; + + // Variables for server data + private RECORDCOUNT: number; + private HEADERS: string[]; + data: any[]; + + // Variables for DOM elements + contentTable: HTMLElement | null; + tableBody: HTMLTableSectionElement | null; + tableHead: HTMLElement | null; + pageInfo: HTMLElement | null; + searchBtn: HTMLElement | null; + firstBtn: HTMLElement | null; + prevBtn: HTMLElement | null; + nextBtn: HTMLElement | null; + lastBtn: HTMLElement | null; + inputBox: HTMLElement | null; + + constructor() { + // Starting values for when page is first loaded + this.records = this.calculateRecords(); + this.trimStart = 0; + this.trimEnd = this.records - 1; + + // Default values for server data variables + this.RECORDCOUNT = 350; + this.HEADERS = ["ID", "City", "Population"]; + this.data = [[0, "Cape Town", 3500000], [1, "New York", 8500000], [2, "Johannesburg", 4500000]]; + + this.contentTable = document.getElementById('content-table'); + this.tableBody = document.querySelector('tbody'); + this.tableHead = document.getElementById('content-thead'); + this.pageInfo = document.getElementById('page-info'); + this.searchBtn = document.getElementById('id-search-btn'); + this.firstBtn = document.getElementById('first'); + this.prevBtn = document.getElementById('prev'); + this.nextBtn = document.getElementById('next'); + this.lastBtn = document.getElementById('last'); + this.inputBox = document.getElementById('id-search'); + } + + // Record count and headers should not be changed outside the class + // hence there are only get methods for them + getRecordCount() { + return this.RECORDCOUNT; + } + + getHeaders() { + return this.HEADERS; + } + + calculateRecords() { + // Estimate of available table space + // The calculation is an estimate of how many space there is for rows + // 160 is estimate space for header and footer of website and 40 is estimated space of a single row + return Math.floor((window.innerHeight - 160) / 40); + } + + // The setPageButtons, setSearchButton, and setResizeEvent are seperate functions + // so that only the needed functions are used + setPageButtons() { + // Event listener for button that goes to first page + this.firstBtn!.addEventListener("click", debounce(() => { + this.goToFirst(); + }, 250)); + + // Event listener for button that goes to previous page + this.prevBtn!.addEventListener("click", debounce(() => { + this.goToPrev(); + }, 250)); + + // Event listener for button that goes to next page + this.nextBtn!.addEventListener("click", debounce(() => { + this.goToNext(); + }, 250)); + + // Event listener for button that goes to last page + this.lastBtn!.addEventListener("click", debounce(() => { + this.goToLast(); + }, 250)); + } + + setSearchButton() { + // // Event listener for button that searches entered ID + this.searchBtn!.addEventListener("click", debounce(() => { + this.searchId(); + }, 250)); + } + + setResizeEvent() { + // Event listener for when user resizes the page + window.addEventListener("resize", debounce(() => { + this.resize(); + }, 150)); // Log window dimensions at most every 150ms + } + + // Fetch headers and record count + async getData(): Promise { + // API calls for record count and headers + await fetch('/recordCount') + .then(resp => { + if (resp.ok) { + return resp.json(); + } + throw new Error('Could not retrieve data'); + }) + .then(count => { + this.RECORDCOUNT = count; + }) + .catch((error) => { + console.error(error); + }); + + await fetch('/columns') + .then(resp => { + if (resp.ok) { + return resp.json(); + } + throw new Error('Could not retrieve data'); + }) + .then(count => { + this.HEADERS = count; + }) + .catch((error) => { + console.error(error); + }); + + return true; + } + + // Add rows to table + async addRows(start: number, end: number) { + let table = document.getElementById("content-table"); + let newTableBody = document.createElement("tbody"); + let oldTableBody = table!.querySelector("tbody"); + let link = "/records?from=" + start + "&to=" + end; + + await fetch(link) + .then(resp => { + if (resp.ok) { + return resp.json(); + } + throw new Error('Could not retrieve data'); + }) + .then(count => { + this.data = count; + }) + .catch((error) => { + console.error(error); + }); + + for (let row of this.data) { + let rowElement = document.createElement("tr"); + + for (let cellText of row) { + let cellElement = document.createElement("td"); + + cellElement.textContent = cellText; + rowElement.appendChild(cellElement); // Append cells to row + } + newTableBody.appendChild(rowElement);// Append rows to tbody + } + + // If table exists, replace the new tbody with the old one + if (!table) { + console.error("Table is undefined"); + return; + } else { + table.replaceChild(newTableBody, oldTableBody!); + } + } + + // Delete rows from table + deleteRows(newHeight: number, diff: number) { + let tableBod = this.contentTable!.querySelector('tbody'); + let num = newHeight - 1; + + for (let i = num; i > (num + diff); i--) { + tableBod!.deleteRow(i); + } + } + + // Load json data into table function + loadIntoTable(clearHeader: boolean) { + + // Display loader + $(".content").fadeOut(230); + $(".loader").fadeIn(230); + + // UI "Aesthetic": update buttons + this.firstBtn?.removeAttribute("disabled"); + this.prevBtn?.removeAttribute("disabled"); + this.nextBtn?.removeAttribute("disabled"); + this.lastBtn?.removeAttribute("disabled"); + + if (this.trimEnd == this.getRecordCount() - 1) { + this.lastBtn?.setAttribute("disabled", "disabled"); + this.nextBtn?.setAttribute("disabled", "disabled"); + } + + if (this.trimStart == 0) { + this.firstBtn?.setAttribute("disabled", "disabled"); + this.prevBtn?.setAttribute("disabled", "disabled"); + } + + this.pageInfo!.innerHTML = `

`; + + // Add header only if 'clearHeader' is true + if (clearHeader) { + this.tableHead!.innerHTML = ""; + let headerRow = document.createElement("tr"); + + // Populate the headers + for (let headerText of this.getHeaders()) { + let headerElement = document.createElement("th"); + + headerElement.textContent = headerText; + headerRow.appendChild(headerElement); + } + this.tableHead!.appendChild(headerRow); + } + + // Clear the table + this.tableBody!.innerHTML = ""; + + // Add only records that must be displayed on table + this.addRows(this.trimStart, this.trimEnd); + + // Display content + $(".loader").fadeOut(230); + $(".content").fadeIn(230); + } + + // Search entered ID + searchId() { + let id = parseInt((this.inputBox).value); + let numRecords = this.getRecordCount() - 1; + + if (id < 0 || id > numRecords || isNaN(id)) { + // User info: Display error message + this.pageInfo!.innerHTML = `

No records to display

`; + } else { + // Use entered ID to calculate what records should display + if ((this.getRecordCount() - 1) - id >= this.records) { + this.trimStart = id; + this.trimEnd = this.trimStart + (this.records - 1); + } else { + this.trimEnd = this.getRecordCount() - 1; + this.trimStart = this.trimEnd - this.records + 1; + } + this.loadIntoTable(false); + } + (document.getElementById('id-search')).value = 'Enter ID number'; + } + + // Set trim to start of data + goToFirst() { + this.trimStart = 0; + this.trimEnd = this.trimStart + this.records - 1; + this.loadIntoTable(false); + } + + // Set trim to previous data + goToPrev() { + // If previous page is end of data && there are not enough records to fill window + if ((this.trimStart - 1) - (this.records - 1) < 0) { + this.trimStart = 0; + this.trimEnd = this.trimStart + this.records - 1; + } else { + this.trimEnd = this.trimStart - 1; + this.trimStart = this.trimEnd - this.records + 1; + } + this.loadIntoTable(false); + } + + // Set trim to next data + goToNext() { + // If next page is end of data && there are not enough records to fill window + if ((this.getRecordCount() - 1) - (this.trimEnd + 1) < this.records) { + this.trimEnd = this.getRecordCount() - 1; + this.trimStart = this.trimEnd - this.records + 1; + } else { + this.trimStart = this.trimEnd + 1; + this.trimEnd = this.trimStart + this.records - 1; + } + this.loadIntoTable(false); + } + + // Set trim to end of data + goToLast() { + this.trimEnd = this.getRecordCount() - 1; + this.trimStart = this.trimEnd - this.records + 1; + this.loadIntoTable(false); + } + + // Add/remove rows from table based on resize event of the window + async resize() { + // Calculate number rows to be added/deleted + let newHeight = this.calculateRecords(); + let diff = newHeight - this.records; + + let start = this.trimStart; + let end = this.trimEnd + diff; + + if (diff < 0) { + // If screen is made smaller, call delete rows function with this.records (amount of rows that were on the screen) + // and diff (how many rows should be deleted) + this.deleteRows(this.records, diff); + + this.trimEnd = this.trimEnd + diff; + } else if (diff > 0 && end >= this.getRecordCount()) { + // Prepend rows as last page gets bigger + end = this.getRecordCount() - 1; + start = end - (newHeight - 1); + console.log("End reached, displaying record ", start, " to ", end); + + await this.addRows(start, end); + + this.trimStart = this.trimStart - diff; + this.trimEnd = this.getRecordCount() - 1; + } else if (diff > 0 && start <= this.getRecordCount() - 1) { + // Add rows if end of data is not yet reached + this.addRows(start, end); + + this.trimEnd = this.trimEnd + diff; + } + this.records = newHeight; + + // UI "Aesthetic": update buttons + this.firstBtn?.removeAttribute("disabled"); + this.prevBtn?.removeAttribute("disabled"); + this.nextBtn?.removeAttribute("disabled"); + this.lastBtn?.removeAttribute("disabled"); + + if (this.trimEnd == this.getRecordCount() - 1) { + this.lastBtn?.setAttribute("disabled", "disabled"); + this.nextBtn?.setAttribute("disabled", "disabled"); + } + + if (this.trimStart == 0) { + this.firstBtn?.setAttribute("disabled", "disabled"); + this.prevBtn?.setAttribute("disabled", "disabled"); + } + } +} diff --git a/style.css b/style.css new file mode 100644 index 00000000..388295a6 --- /dev/null +++ b/style.css @@ -0,0 +1,193 @@ +html { + font-size: 62.5%; +} + +body { + font-size: 16px; + font-size: 1.6rem; + min-height: 100%; + overflow: hidden; /* Hide scrollbars */ + background-color: #16191e; + color: #fdfdfd; + min-width: 300px; + min-width: 3.0; +} + +/* Controls above table*/ +.controls { + display: flex; + text-overflow: clip; + white-space: nowrap; +} + +.constrols-heading { + width: 30%; +} + +h1 { + font-size: 32px; + font-size: 3.2rem; +} + +.constrols-search { + width: 70%; + display: flex; + justify-content: center; + padding: 2rem; +} + +.search-btn { + color: #fdfdfd; + padding: 0.5rem; + text-decoration: none; + border: solid 1px #45474b; + background-color: #45474b; +} + +.search-btn:hover:not(.selected) { + color: #1d1f20; + background-color: #cae00d; + border: solid 1px #cae00d; +} + +/* Table */ +.table { + box-shadow: 0 0 10px rgba(94, 102, 80, 0.918); + border-collapse: collapse; + /* overflow: hidden; */ + font-weight: bold; + text-align: center; + width: 100%; + table-layout:fixed; +} + +.table thead th { + background: #cae00d; + color: #16191e; + opacity: 0.9; +} + +.table td, +.table th { + padding: 10px 20px; + height: 10px; + overflow: hidden; + text-overflow: clip; + white-space: nowrap; +} + +.table tbody tr:nth-of-type(even) { + background: #45474b; +} + +.table tbody tr:last-of-type { + border-bottom: #cae00d; +} + +/* Controls underneath table */ +.container { + padding: 1rem; + margin: 1rem auto; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + bottom: -1rem; + text-overflow: clip; + white-space: nowrap; + background-color: #16191e; +} + +/* Page info underneath table */ +.page-info { + color: #cae00d; + font-size: 2.4rem; + margin: auto; + width: 30%; + text-align: center; +} + +/* Pagination buttons */ +.pagination-wrapper { + width: 70%; + overflow-x: auto; + margin-left: 1rem; +} + +.btn { + color: #fdfdfd; + padding: 1rem; + text-decoration: none; + border: solid 1px #45474b; + background-color: #45474b; +} + +.btn:hover:not(.btn:disabled) { + color: #1d1f20; + background-color: #cae00d; + border: solid 1px #cae00d; + cursor: pointer; +} + +.btn:disabled, +.btn[disabled]{ + border: 1px solid #999999; + background-color: #cccccc; + color: #666666; +} + +/* width */ +::-webkit-scrollbar { + height: 10px; +} + +/* Track */ +::-webkit-scrollbar-track { + background: #16191e; +} + +/* Handle */ +::-webkit-scrollbar-thumb { + background: #888; +} + +/* Handle on hover */ +::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* Loader */ +.content { + display: none; +} + +.loader { + height: 100vh; + width: 100vw; + overflow: hidden; + background-color: #16191e; + /* position: absolute; */ + align-items: center; +} + +.loader>div{ + height: 6rem; + width: 6rem; + border: 15px solid #45474b; + border-top-color: #cae00d; + position: absolute; + margin: auto; + top: 0; + bottom: 0; + left: 0; + right: 0; + border-radius: 50%; + animation: spin 1.5s infinite linear; +} + +@keyframes spin { + 100%{ + transform: rotate(360deg); + } +}