-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtableSearch.js
149 lines (138 loc) · 4.68 KB
/
tableSearch.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
class Patch {
constructor(
patchInput = "",
grammar = {
rowDelimiter: "&",
columnDelimiter: "=",
encodedRowDelimiter: "%2E%2E%2E",
encodedColumnDelimiter: "%7E"
}
) {
// The pipeline of encodings. Operations will be run in order for encoding (and reveresed for decoding).
this.encoders = [
{
encode: str => encodeURIComponent(str),
decode: str => decodeURIComponent(str)
},
{
encode: str => this.replaceAll(str, this.grammar.columnDelimiter, this.grammar.encodedColumnDelimiter),
decode: str => this.replaceAll(str, this.grammar.encodedColumnDelimiter, this.grammar.columnDelimiter)
},
{
encode: str => this.replaceAll(str, this.grammar.rowDelimiter, this.grammar.encodedRowDelimiter),
decode: str => this.replaceAll(str, this.grammar.encodedRowDelimiter, this.grammar.rowDelimiter)
},
{
// Turn "%20" into "+" for prettier urls.
encode: str => str.replace(/\%20/g, "+"),
decode: str => str.replace(/\+/g, "%20")
}
]
this.grammar = grammar
if (typeof patchInput === "string") this.uriEncodedString = patchInput
else if (Array.isArray(patchInput)) this.uriEncodedString = this.arrayToEncodedString(patchInput)
else this.uriEncodedString = this.objectToEncodedString(patchInput)
}
replaceAll(str, search, replace) {
return str.split(search).join(replace)
}
objectToEncodedString(obj) {
return Object.keys(obj)
.map(identifierCell => {
const value = obj[identifierCell]
const valueCells = value instanceof Array ? value : [value]
const row = [identifierCell, ...valueCells].map(cell => this.encodeCell(cell))
return row.join(this.grammar.columnDelimiter)
})
.join(this.grammar.rowDelimiter)
}
arrayToEncodedString(arr) {
return arr.map(line => line.map(cell => this.encodeCell(cell)).join(this.grammar.columnDelimiter)).join(this.grammar.rowDelimiter)
}
get array() {
return this.uriEncodedString.split(this.grammar.rowDelimiter).map(line => line.split(this.grammar.columnDelimiter).map(cell => this.decodeCell(cell)))
}
get object() {
const patchObj = {}
if (!this.uriEncodedString) return patchObj
this.array.forEach(cells => {
const identifierCell = cells.shift()
patchObj[identifierCell] = cells.length > 1 ? cells : cells[0] // If a single value, collapse to a simple tuple. todo: sure about this design?
})
return patchObj
}
encodeCell(unencodedCell) {
return this.encoders.reduce((str, encoder) => encoder.encode(str), unencodedCell)
}
decodeCell(encodedCell) {
return this.encoders
.slice()
.reverse()
.reduce((str, encoder) => encoder.decode(str), encodedCell)
}
}
class TableSearchApp {
constructor() {
this.createDatatable()
this.bindToHashChange()
}
get windowHash() {
return window.location.hash.replace(/^#/, "")
}
get objectFromHash() {
return new Patch(this.windowHash).object
}
setObjectOnHash(obj) {
if (obj.order === "0.asc") delete obj.order
Object.keys(obj).forEach(key => {
if (obj[key] === "") delete obj[key]
})
const newHash = new Patch(obj).uriEncodedString
if (this.windowHash !== newHash) window.location.hash = newHash
}
bindToHashChange() {
// Listen for hash changes to update the DataTable search
window.addEventListener("hashchange", () => {
this.dataTables.search(this.searchFromHash).order(this.orderFromHash).draw(false)
})
}
createDatatable() {
this.dataTables = jQuery("table").DataTable({
paging: false,
stateSave: true,
stateSaveCallback: (settings, data) => {
const order = data.order.map(([column, direction]) => `${column}.${direction}`).join(this.columnDelimiter)
const patch = {
q: data.search.search,
order
}
this.setObjectOnHash(patch)
},
// Set the search input to the initial value extracted from the URL
search: { search: this.searchFromHash },
order: this.orderFromHash,
stateLoadCallback: settings => {
return {
search: {
order: this.orderFromHash,
search: this.searchFromHash
}
}
}
})
}
get orderFromHash() {
const order = this.objectFromHash.order
return order
? order.split(this.columnDelimiter).map(o => {
const parts = o.split(".")
return [parseInt(parts[0]), parts[1]]
})
: []
}
columnDelimiter = "~"
get searchFromHash() {
return this.objectFromHash.q || ""
}
}
document.addEventListener("DOMContentLoaded", () => (window.tableSearchApp = new TableSearchApp()))