spanish-quizzer

An app to quiz you on Spanish vocabulary and verb conjugations
git clone https://git.ashermorgan.net/spanish-quizzer/
Log | Files | Refs | README

commit 1c2b346d97271735b994f8fe504600e40baa227b
parent 5e24bc5c5de6855d927328e6f76c01f6bd99d93d
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Fri,  2 Jul 2021 11:54:30 -0700

Refactor reference tables to use data-table

Diffstat:
Mcss/reference.css | 17+----------------
Mindex.html | 2++
Mjs/reference.js | 113++++++++++++++++++++++++++-----------------------------------------------------
Mservice-worker.js | 4+++-
Mtests/test.reference.js | 115++-----------------------------------------------------------------------------
Avendor/data-table.min.css | 6++++++
Avendor/data-table.min.js | 6++++++
7 files changed, 57 insertions(+), 206 deletions(-)

diff --git a/css/reference.css b/css/reference.css @@ -38,26 +38,11 @@ } /* Table */ -.referenceTable { - text-align: left; - overflow-x: auto; -} .referenceTable table { + width: auto; margin: auto; } -.referenceTable th { - background-color: var(--hover-color); - border: 1px solid var(--border-color); - position: sticky; - position: -webkit-sticky; - top: 0; -} -.referenceTable th div { - display: flex; - align-items: center; -} .referenceTable td { - border: 1px solid var(--border-color); cursor: pointer; } diff --git a/index.html b/index.html @@ -9,6 +9,7 @@ <link rel="manifest" href="manifest.json"> <link rel="icon" type="image/png" href="images/favicon-32.png"> <link rel="apple-touch-icon" sizes="180x180" href="images/favicon-180.png"> + <link rel="stylesheet" href="vendor/data-table.min.css"> <link rel="stylesheet" href="css/global.css"> <link rel="stylesheet" href="css/app.css"> <link rel="stylesheet" href="css/filtersPage.css"> @@ -16,6 +17,7 @@ <link rel="stylesheet" href="css/quizzer.css"> <link rel="stylesheet" href="css/reference.css"> <script src="vendor/diff.js"></script> + <script src="vendor/data-table.min.js"></script> <script src="vendor/papaparse.js"></script> <script src="vendor/vue.js"></script> <script src="vendor/vue-router.js"></script> diff --git a/js/reference.js b/js/reference.js @@ -9,17 +9,30 @@ const referenceTables = Vue.component("referenceTables", { data: function() { return { category: "Choose a category", - query: "", conjugationColors: true, - sortIndex: 0, - sortAccending: true, + tableTheme: null, + table: null, } }, watch: { - category: function() { - // Reset sortIndex and sortAccending - this.sortIndex = 0; - this.sortAccending = true; + category: function(value) { + // Update table + this.table.setData({ + headers: this.tableData[value][0], + body: this.tableData[value].slice(1), + bodyClasses: value === "verbs" && this.conjugationColors ? this.conjugationColorClasses : null, + }); + this.table.sort(0, true); + }, + tableTheme: function(value) { + // Update table theme + this.table.theme = value; + }, + conjugationColors: function(value) { + // Update table body classes + this.table.setData({ + bodyClasses: this.category === "verbs" && value ? this.conjugationColorClasses : null, + }); } }, computed: { @@ -58,7 +71,7 @@ const referenceTables = Vue.component("referenceTables", { } } } - return result; + return result.slice(1); }, /** * The data used by the table @@ -75,55 +88,7 @@ const referenceTables = Vue.component("referenceTables", { search: function(args) { if (args) args.target.blur(); this.$refs.search.blur(); - this.query = this.$refs.search.value; - }, - - /** - * Sort the table by values in a column - * @param {Number} index The index of the column to sort by - * @param {Boolean} accending Whether to sort accending or descending - */ - sortColumn: function(index, accending) { - // Get sort direction - let direction; - if (accending !== undefined) { - direction = accending; - } - else if (this.sortIndex === index) { - direction = !this.sortAccending; - } - else { - direction = true; - } - - // Remove headers - let headers = this.data[this.category][0]; - this.data[this.category] = this.data[this.category].slice(1); - - // Sort data - if (this.sortIndex === index && this.sortAccending === direction) { - // Data is sorted by correct column AND in correct direction - } - else if (this.sortIndex === index && this.sortAccending !== direction) { - // Data is sorted by correct column but in wrong direction - this.data[this.category].reverse(); - } - else { - // Data is sorted by incorrect column AND in incorrect direction - this.data[this.category].sort((a, b) => { - if (a[index] === b[index]) return 0; - else if (a[index] < b[index]) return -1; - else return 1; - }); - if (!direction) this.data[this.category].reverse(); - } - - // Reinsert headers - this.data[this.category].unshift(headers); - - // Set sortStatus - this.sortIndex = index; - this.sortAccending = direction; + this.table.search(this.$refs.search.value); }, /** @@ -178,6 +143,17 @@ const referenceTables = Vue.component("referenceTables", { }, }, mounted: function() { + // Generate table + this.table = new DataTable(".referenceTable", { + sortable: true, + unsortable: false, + bodyEventHandlers: { + click: (row, column, args) => { + this.Read(this.tableData[this.category][row + 1][column], this.tableData[this.category][0][column]); + } + } + }); + // Set table height this.setTableHeight(); @@ -195,8 +171,10 @@ const referenceTables = Vue.component("referenceTables", { window.removeEventListener("keyup", this.keyup); }, activated: function() { - // Update conjugationColors setting - this.conjugationColors = getSettings().conjugationColors; + // Update settings + let settings = getSettings(); + this.conjugationColors = settings.conjugationColors; + this.tableTheme = settings.darkTheme ? "basic-dark" : "basic-light"; }, template: ` <div> @@ -213,23 +191,6 @@ const referenceTables = Vue.component("referenceTables", { </div> <div class="referenceTable" ref="referenceTable"> - <table> - <tr v-for="(row, rowIndex) in data[category]" v-show="rowIndex === 0 || row.join(',').toLowerCase().includes(query.toLowerCase())"> - <th v-if="rowIndex === 0" v-for="(column, columnIndex) in row" @click="sortColumn(columnIndex)"> - <div> - <span>{{ column }}</span> - <button class="icon"> - <img v-if="sortIndex === columnIndex && sortAccending" src="images/chevron-up.svg"> - </button> - <button class="icon"> - <img v-if="sortIndex === columnIndex && !sortAccending" src="images/chevron-down.svg"> - </button> - </div> - </th> - <td v-if="rowIndex !== 0" v-for="(column, columnIndex) in row" @click="Read(column, data[category][0][columnIndex])" - :lang="getLang(data[category][0][columnIndex])" :class="(conjugationColors && category === 'verbs') ? conjugationColorClasses[rowIndex][columnIndex] : 'normal'">{{ column }}</td> - </tr> - </table> </div> </div> ` diff --git a/service-worker.js b/service-worker.js @@ -1,5 +1,5 @@ // Initialize constants -const version = "spanish-quizzer-6"; +const version = "spanish-quizzer-7"; const resources = [ "./css/app.css", "./css/filtersPage.css", @@ -29,6 +29,8 @@ const resources = [ "./js/reference.js", "./js/settingsPage.js", "./vendor/diff.js", + "./vendor/data-table.min.css", + "./vendor/data-table.min.js", "./vendor/papaparse.js", "./vendor/vue-router.js", "./vendor/vue.js", diff --git a/tests/test.reference.js b/tests/test.reference.js @@ -10,36 +10,12 @@ describe("ReferenceTables", function() { expect(ReferenceTables.category).to.equal("Choose a category"); }); - it("Query should be empty", function() { - expect(ReferenceTables.query).to.equal(""); - }); - it("ConjugationColors should be true", function() { expect(ReferenceTables.conjugationColors).to.be.true; }); - it("SortIndex should be 0", function() { - expect(ReferenceTables.sortIndex).to.equal(0); - }); - - it("SortAccending should be true", function() { - expect(ReferenceTables.sortAccending).to.be.true; - }); - }); - - describe("Category watch", function() { - it("Should reset sortIndex and sortAccending", async function() { - // Set sortIndex and sortAccending - ReferenceTables.sortIndex = 1; - ReferenceTables.sortAccending = false; - - // Set category - ReferenceTables.category = "new category"; - await ReferenceTables.$nextTick(); - - // Assert sortIndex and sortAccending are reset - expect(ReferenceTables.sortIndex).to.equal(0); - expect(ReferenceTables.sortAccending).to.be.true; + it("tableTheme should be null", function() { + expect(ReferenceTables.tableTheme).to.be.null; }); }); @@ -65,9 +41,6 @@ describe("ReferenceTables", function() { // Assert conjugationColorClasses is correct expect(ReferenceTables.conjugationColorClasses).to.deep.equal([ - // Test table headers - ["normal","normal","normal","normal","normal","normal","normal","normal","normal","normal","normal","normal","normal","normal"], - // Test regular conjugations and orthographic conjugations ["normal","normal","regular","regular","regular","regular","regular","regular","nonregular","nonregular","nonregular","nonregular","nonregular","nonregular"], @@ -108,88 +81,4 @@ describe("ReferenceTables", function() { expect(ReferenceTables.tableData).to.deep.equal({...{"Choose a category":[]}, ...data}); }); }); - - describe("SortColumn method", function() { - it("Should correctly sort table", function() { - // Set table data and category - ReferenceTables.data = {"category1":[ - ["English", "Spanish"], - ["Red", "Rojo"], - ["Green", "Verde"], - ["Blue", "Azul"], - ]}; - ReferenceTables.category = "category1"; - - // Sort table - ReferenceTables.sortColumn(1, false); - - // Assert table data is correct - expect(ReferenceTables.data).to.deep.equal({"category1":[ - ["English", "Spanish"], - ["Green", "Verde"], - ["Red", "Rojo"], - ["Blue", "Azul"], - ]}); - - // Assert sortIndex and sortAccending are correct - expect(ReferenceTables.sortIndex).to.equal(1); - expect(ReferenceTables.sortAccending).to.be.false; - }); - - it("Should correctly choose sort direction if column is already sorted", function() { - // Set variables - ReferenceTables.data = {"category1":[ - ["English", "Spanish"], - ["Blue", "Azul"], - ["Red", "Rojo"], - ["Green", "Verde"], - ]}; - ReferenceTables.category = "category1"; - ReferenceTables.sortIndex = 1; - ReferenceTables.sortAccending = true; - - // Sort table - ReferenceTables.sortColumn(1); - - // Assert table data is correct - expect(ReferenceTables.data).to.deep.equal({"category1":[ - ["English", "Spanish"], - ["Green", "Verde"], - ["Red", "Rojo"], - ["Blue", "Azul"], - ]}); - - // Assert sortIndex and sortAccending are correct - expect(ReferenceTables.sortIndex).to.equal(1); - expect(ReferenceTables.sortAccending).to.be.false; - }); - - it("Should correctly choose sort direction if column is not already sorted", function() { - // Set variables - ReferenceTables.data = {"category1":[ - ["English", "Spanish"], - ["Blue", "Azul"], - ["Green", "Verde"], - ["Red", "Rojo"], - ]}; - ReferenceTables.category = "category1"; - ReferenceTables.sortIndex = 0; - ReferenceTables.sortAccending = true; - - // Sort table - ReferenceTables.sortColumn(1); - - // Assert table data is correct - expect(ReferenceTables.data.category1).to.deep.equal([ - ["English", "Spanish"], - ["Blue", "Azul"], - ["Red", "Rojo"], - ["Green", "Verde"], - ]); - - // Assert sortIndex and sortAccending are correct - expect(ReferenceTables.sortIndex).to.equal(1); - expect(ReferenceTables.sortAccending).to.be.true; - }); - }); }); diff --git a/vendor/data-table.min.css b/vendor/data-table.min.css @@ -0,0 +1,6 @@ +/* + * data-table 1.0.0 + * (c) 2021 Asher Morgan (MIT) + * https://github.com/ashermorgan/data-table + */ +.data-table{overflow-x:auto;overflow-y:auto}.data-table table{width:100%;border-spacing:0;text-align:left}.data-table table th{position:sticky;position:-webkit-sticky;top:0}.data-table table th button{margin:0;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle}.data-table table th button svg{height:24px;width:24px}.data-table table.basic-light{border:1px solid #000;border-collapse:separate}.data-table table.basic-light td{border:1px solid #000;color:#000;background-color:#fff}.data-table table.basic-light th{border:1px solid #000;color:#000;background-color:#e0e0e0}.data-table table.basic-light svg{color:#000}.data-table table.basic-dark{border:1px solid grey;border-collapse:separate}.data-table table.basic-dark td{border:1px solid grey;color:#e0e0e0;background-color:#202020}.data-table table.basic-dark th{border:1px solid grey;color:#e0e0e0;background-color:#404040}.data-table table.basic-dark svg{color:#e0e0e0} diff --git a/vendor/data-table.min.js b/vendor/data-table.min.js @@ -0,0 +1,6 @@ +/* + * data-table 1.0.0 + * (c) 2021 Asher Morgan (MIT) + * https://github.com/ashermorgan/data-table + */ +let DataTable=function(e,t){"use strict";let o=[];Object.defineProperty(this,"body",{get:function(){return o}});let r=null;Object.defineProperty(this,"bodyClasses",{get:function(){return r}});let l={};Object.defineProperty(this,"bodyEventHandlers",{get:function(){return l}});let i=!1;Object.defineProperty(this,"dataIsHTML",{get:function(){return i},set:function(e){i=e,this.render()}});let n=[];Object.defineProperty(this,"headers",{get:function(){return n}});let s=null;Object.defineProperty(this,"headerClasses",{get:function(){return s}});let d={};Object.defineProperty(this,"headerEventHandlers",{get:function(){return d}});let a={up:'<svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="data-table-up"><polyline points="18 16 12 8 6 16 18 16"></polyline></svg>',down:'<svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="data-table-down"><polyline points="18 8 12 16 6 8 18 8"></polyline></svg>',updown:'<svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="data-table-updown"><polyline points="15 10 12 6 9 10 15 10"></polyline><polyline points="15 14 12 18 9 14 15 14"></polyline></svg>'};Object.defineProperty(this,"downIcon",{get:function(){return a.down},set:function(e){a.down=e,this.render()}}),Object.defineProperty(this,"upIcon",{get:function(){return a.up},set:function(e){a.up=e,this.render()}}),Object.defineProperty(this,"updownIcon",{get:function(){return a.updown},set:function(e){a.updown=e,this.render()}});let u="";Object.defineProperty(this,"searchQuery",{get:function(){return u}});let c=null;Object.defineProperty(this,"selector",{get:function(){return c}});let h=!1;Object.defineProperty(this,"sortable",{get:function(){return h},set:function(e){h=e,this.render()}});let f=null;Object.defineProperty(this,"sortAscending",{get:function(){return f}});let b=null;Object.defineProperty(this,"sortIndex",{get:function(){return b}});let p={headers:[],body:[]},y="basic-light";Object.defineProperty(this,"theme",{get:function(){return y},set:function(e){y=e,this.render()}});let v=!0;Object.defineProperty(this,"unsortable",{get:function(){return v},set:function(e){v=e,this.render()}});var g,w;let j=function(){p.headers=[];for(let e=0;e<n.length;e++)p.headers.push({value:n[e],class:s?s[e]:""});p.body=[];for(let n=0;n<o.length;n++){let t={visible:!0,columns:[],row:n};for(let e=0;e<o[n].length;e++)t.columns.push({value:o[n][e],class:r?r[n][e]:""});p.body.push(t)}C(u);var e=b,t=f;b=null,f=null,O(e,t)};this.render=function(){let e=document.querySelector(this.selector);e.classList.add("data-table");let t=`<table class="${y?y.toLowerCase():""}">`;if(0<p.headers.length){t+="<thead><tr>";for(let e=0;e<p.headers.length;e++)t+=`<th class="${p.headers[e].class}">${i?p.headers[e].value:m(p.headers[e].value)}`,h&&(b!==e?t+=`<button>${a.updown}</button>`:b===e&&!0===f?t+=`<button>${a.up}</button>`:b===e&&!1===f&&(t+=`<button>${a.down}</button>`)),t+="</th>";t+="</thead></tr>"}if(0<p.body.length){t+="<tbody>";for(var n of p.body){t+=`<tr${n.visible?"":' hidden=""'}>`;for(var o of n.columns)t+=`<td class="${o.class}">${i?o.value:m(o.value)}</td>`;t+="</tr>"}t+="</tbody>"}t+="</table>",e.innerHTML=t;let r=document.querySelectorAll(`${this.selector} thead th`),s=document.querySelectorAll(`${this.selector} tbody td`);if(h)for(let e=0;e<r.length;e++)r[e].addEventListener("click",()=>{b!==e?this.sort(e,!0):!0===f?this.sort(e,!1):v?this.sort(e,null):this.sort(e,!0)});for(let o in l){let e=-1;for(let n=0;n<p.body.length;n++)for(let t=0;t<p.body[n].columns.length;t++)e++,s[e].addEventListener(o,e=>{l[o](p.body[n].row,t,e)})}for(let n in d)for(let t=0;t<p.headers.length;t++)r[t].addEventListener(n,e=>{d[n](t,e)})};let m=function(e){return e=(e=(e=(e=(e=e.replace(/&/g,"&amp;")).replace(/</g,"&lt;")).replace(/>/g,"&gt;")).replace(/\n/g,"<br>")).replace(/\t/g,"&emsp;")},C=function(e){for(var t of p.body){t.visible=!1;for(var n of t.columns)if(n.value.toLowerCase().includes(e.toLowerCase())){t.visible=!0;break}}u=e};this.search=function(e){C(e),this.render()},this.setData=function(e){e&&(void 0!==e.body&&(o=e.body),void 0!==e.bodyClasses&&(r=e.bodyClasses),void 0!==e.headers&&(n=e.headers),void 0!==e.headerClasses&&(s=e.headerClasses)),j(),this.render()};let O=function(n,e){b===n&&f===e||(n<0||n>=p.headers.length||null===e?(p.body.sort((e,t)=>e.row<t.row?-1:1),e=n=null):b===n&&f!==e?p.body.reverse():(p.body.sort((e,t)=>e.columns[n].value===t.columns[n].value?0:e.columns[n].value<t.columns[n].value?-1:1),e||p.body.reverse())),b=n,f=e};this.sort=function(e,t){O(e,t),this.render()},g=this,w=t,c=e,w&&(void 0!==w.body&&(o=w.body),void 0!==w.bodyClasses&&(r=w.bodyClasses),void 0!==w.bodyEventHandlers&&(l=w.bodyEventHandlers),void 0!==w.dataIsHTML&&(i=w.dataIsHTML),void 0!==w.downIcon&&(a.down=w.downIcon),void 0!==w.headers&&(n=w.headers),void 0!==w.headerClasses&&(s=w.headerClasses),void 0!==w.headerEventHandlers&&(d=w.headerEventHandlers),void 0!==w.sortable&&(h=w.sortable),void 0!==w.searchQuery&&(u=w.searchQuery),void 0!==w.sortAscending&&(f=w.sortAscending),void 0!==w.sortIndex&&(b=w.sortIndex),void 0!==w.theme&&(y=w.theme),void 0!==w.unsortable&&(v=w.unsortable),void 0!==w.upIcon&&(a.up=w.upIcon),void 0!==w.updownIcon&&(a.updown=w.updownIcon)),j(),g.render()};Object.defineProperty(DataTable,"version",{get:function(){return"1.0.0"}}),"function"==typeof define&&define.amd?define(function(){return root.DataTable=DataTable,root.DataTable}):"object"==typeof exports&&"undefined"!=typeof module?module.exports=DataTable:this.DataTable=DataTable;