@matdata/yasr 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/build/ts/src/bindingsToCsv.d.ts +2 -0
  3. package/build/ts/src/defaults.d.ts +2 -0
  4. package/build/ts/src/helpers/addCSS.d.ts +1 -0
  5. package/build/ts/src/helpers/addScript.d.ts +1 -0
  6. package/build/ts/src/helpers/index.d.ts +3 -0
  7. package/build/ts/src/helpers/sanitize.d.ts +2 -0
  8. package/build/ts/src/imgs.d.ts +4 -0
  9. package/build/ts/src/index.d.ts +130 -0
  10. package/build/ts/src/parsers/csv.d.ts +2 -0
  11. package/build/ts/src/parsers/index.d.ts +68 -0
  12. package/build/ts/src/parsers/json.d.ts +2 -0
  13. package/build/ts/src/parsers/tsv.d.ts +2 -0
  14. package/build/ts/src/parsers/turtleFamily.d.ts +4 -0
  15. package/build/ts/src/parsers/xml.d.ts +2 -0
  16. package/build/ts/src/plugins/boolean/index.d.ts +13 -0
  17. package/build/ts/src/plugins/error/index.d.ts +13 -0
  18. package/build/ts/src/plugins/index.d.ts +19 -0
  19. package/build/ts/src/plugins/response/index.d.ts +28 -0
  20. package/build/ts/src/plugins/table/index.d.ts +49 -0
  21. package/build/yasr.html +32 -0
  22. package/build/yasr.min.css +2 -0
  23. package/build/yasr.min.css.map +1 -0
  24. package/build/yasr.min.js +3 -0
  25. package/build/yasr.min.js.LICENSE.txt +34 -0
  26. package/build/yasr.min.js.map +1 -0
  27. package/package.json +56 -0
  28. package/src/bin/takeScreenshot.js +373 -0
  29. package/src/bindingsToCsv.ts +18 -0
  30. package/src/defaults.ts +28 -0
  31. package/src/helpers/addCSS.ts +7 -0
  32. package/src/helpers/addScript.ts +12 -0
  33. package/src/helpers/index.ts +3 -0
  34. package/src/helpers/sanitize.ts +11 -0
  35. package/src/imgs.ts +7 -0
  36. package/src/index.ts +688 -0
  37. package/src/jquery/extendJquery.js +1 -0
  38. package/src/jquery/tableToCsv.js +88 -0
  39. package/src/main.scss +229 -0
  40. package/src/parsers/csv.ts +30 -0
  41. package/src/parsers/index.ts +311 -0
  42. package/src/parsers/json.ts +22 -0
  43. package/src/parsers/tsv.ts +43 -0
  44. package/src/parsers/turtleFamily.ts +60 -0
  45. package/src/parsers/xml.ts +79 -0
  46. package/src/plugins/boolean/index.scss +11 -0
  47. package/src/plugins/boolean/index.ts +42 -0
  48. package/src/plugins/error/index.scss +57 -0
  49. package/src/plugins/error/index.ts +124 -0
  50. package/src/plugins/index.ts +24 -0
  51. package/src/plugins/response/index.scss +63 -0
  52. package/src/plugins/response/index.ts +170 -0
  53. package/src/plugins/table/index.scss +154 -0
  54. package/src/plugins/table/index.ts +437 -0
  55. package/src/scss/global.scss +10 -0
  56. package/src/scss/variables.scss +2 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ Delimiters
3
+ Delimiters used by us
4
+ - CamelCase
5
+ Delimiters used by datatables
6
+ - dashes
7
+ - underscores
8
+ */
9
+
10
+ .yasr {
11
+ .tableControls {
12
+ display: flex;
13
+ align-items: center;
14
+ padding: 0px;
15
+ padding-right: 5px;
16
+ flex-wrap: wrap;
17
+
18
+ .tableFilter {
19
+ margin-right: 10px;
20
+ height: 100%;
21
+ }
22
+
23
+ .tableSizer {
24
+ height: 100%;
25
+ }
26
+ .switch {
27
+ display: flex;
28
+ align-items: center;
29
+ margin-right: 10px;
30
+ }
31
+ }
32
+ .dataTable.ellipseTable {
33
+ white-space: nowrap;
34
+ div:not(.expanded) {
35
+ overflow: hidden;
36
+ text-overflow: ellipsis;
37
+ }
38
+ }
39
+ .dataTable:not(.ellipseTable) {
40
+ div:not(.expanded) {
41
+ word-break: break-all;
42
+ }
43
+ }
44
+ .expanded {
45
+ white-space: normal;
46
+ word-break: break-all;
47
+ }
48
+ .expandable:not(.expanded) {
49
+ cursor: pointer;
50
+ a {
51
+ pointer-events: none;
52
+ }
53
+ }
54
+
55
+ .dataTables_wrapper {
56
+ font-size: 0.9em;
57
+ min-width: 100%;
58
+ .grip-container {
59
+ max-width: 100%;
60
+ }
61
+ .grip-padding > tbody > tr > td,
62
+ .grip-padding > tbody > tr > th {
63
+ padding-left: 7px !important;
64
+ padding-right: 5px !important;
65
+ }
66
+ .dataTable {
67
+ min-width: 100%;
68
+ box-sizing: border-box;
69
+ // Override border-bottom datatables styling
70
+ &.no-footer {
71
+ border-bottom: none;
72
+ }
73
+
74
+ tbody tr:hover {
75
+ background-color: #f9f9f9;
76
+ }
77
+
78
+ thead tr th {
79
+ //cannot select text anyway (as headers are buttons as well)
80
+ //By setting it to none explicitly, we won't have issues when resizing columns _and_ selecting text at the meantime
81
+ user-select: none;
82
+ font-weight: bold;
83
+ text-align: start;
84
+ overflow: hidden;
85
+ text-overflow: ellipsis;
86
+ border: none;
87
+ padding: 5px;
88
+ padding-left: 7px;
89
+ min-width: 28px;
90
+ &.sorting {
91
+ min-width: 10px;
92
+ padding-right: 18px; //space for sort icon
93
+ }
94
+ &:hover {
95
+ background-color: #f9f9f9;
96
+ }
97
+ }
98
+
99
+ td {
100
+ padding: 5px;
101
+ & > div {
102
+ hyphens: auto;
103
+ &.rowNumber {
104
+ word-break: keep-all;
105
+ overflow: visible;
106
+ }
107
+ }
108
+ .tableEllipse {
109
+ cursor: pointer;
110
+ font-weight: bold;
111
+ padding: 0 2px;
112
+ background-color: #428bca33;
113
+ border-radius: 2px;
114
+ margin: 0 3px;
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ Selector for pagination element
121
+ */
122
+ div.dataTables_paginate.paging_simple_numbers {
123
+ a.paginate_button {
124
+ border: none;
125
+ background: transparent;
126
+
127
+ // When the buttons are disabled show the default YASR disabled color
128
+ .disabled {
129
+ color: #505050;
130
+ }
131
+
132
+ &.current {
133
+ border: none;
134
+ background: transparent;
135
+ text-decoration: underline !important; // dataTables made the text-decoration important
136
+ }
137
+
138
+ &:hover {
139
+ border: none;
140
+ background: transparent;
141
+
142
+ // Don't override the disabled grayed out style
143
+ &:not(.disabled) {
144
+ color: black !important; // dataTables made the color important
145
+ }
146
+ }
147
+
148
+ &:active {
149
+ box-shadow: none;
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Make sure not to include any deps from our main index file. That way, we can easily publish the plugin as standalone build
3
+ */
4
+ require("./index.scss");
5
+ require("datatables.net-dt/css/dataTables.dataTables.min.css");
6
+ require("datatables.net");
7
+ //@ts-ignore (jquery _does_ expose a default. In es6, it's the one we should use)
8
+ import $ from "jquery";
9
+ import Parser from "../../parsers";
10
+ import { escape } from "lodash-es";
11
+ import { Plugin, DownloadInfo } from "../";
12
+ import Yasr from "../../";
13
+ import { drawSvgStringAsElement, drawFontAwesomeIconAsSvg, addClass, removeClass } from "@matdata/yasgui-utils";
14
+ import * as faTableIcon from "@fortawesome/free-solid-svg-icons/faTable";
15
+ import { DeepReadonly } from "ts-essentials";
16
+ import { cloneDeep } from "lodash-es";
17
+ import sanitize from "../../helpers/sanitize";
18
+ import type { Api, ConfigColumns, CellMetaSettings, Config } from "datatables.net";
19
+
20
+ const ColumnResizer = require("column-resizer");
21
+ const DEFAULT_PAGE_SIZE = 50;
22
+
23
+ export interface PluginConfig {
24
+ openIriInNewWindow: boolean;
25
+ tableConfig: Config;
26
+ }
27
+
28
+ export interface PersistentConfig {
29
+ pageSize?: number;
30
+ compact?: boolean;
31
+ isEllipsed?: boolean;
32
+ }
33
+
34
+ type DataRow = [number, ...(Parser.BindingValue | "")[]];
35
+
36
+ function expand(this: HTMLDivElement, event: MouseEvent) {
37
+ addClass(this, "expanded");
38
+ event.preventDefault();
39
+ }
40
+
41
+ export default class Table implements Plugin<PluginConfig> {
42
+ private config: DeepReadonly<PluginConfig>;
43
+ private persistentConfig: PersistentConfig = {};
44
+ private yasr: Yasr;
45
+ private tableControls: Element | undefined;
46
+ private tableEl: HTMLTableElement | undefined;
47
+ private dataTable: Api | undefined;
48
+ private tableFilterField: HTMLInputElement | undefined;
49
+ private tableSizeField: HTMLSelectElement | undefined;
50
+ private tableCompactSwitch: HTMLInputElement | undefined;
51
+ private tableEllipseSwitch: HTMLInputElement | undefined;
52
+ private tableResizer:
53
+ | {
54
+ reset: (options: {
55
+ disable: boolean;
56
+ onResize?: () => void;
57
+ partialRefresh?: boolean;
58
+ headerOnly?: boolean;
59
+ }) => void;
60
+ onResize: () => {};
61
+ }
62
+ | undefined;
63
+ public helpReference = "https://docs.triply.cc/yasgui/#table";
64
+ public label = "Table";
65
+ public priority = 10;
66
+ public getIcon() {
67
+ return drawSvgStringAsElement(drawFontAwesomeIconAsSvg(faTableIcon));
68
+ }
69
+ constructor(yasr: Yasr) {
70
+ this.yasr = yasr;
71
+ //TODO read options from constructor
72
+ this.config = Table.defaults;
73
+ }
74
+ public static defaults: PluginConfig = {
75
+ openIriInNewWindow: true,
76
+ tableConfig: {
77
+ layout: {
78
+ // @ts-ignore
79
+ top: null, // @TODO: remove ignore once https://github.com/DataTables/DataTablesSrc/issues/271 is released
80
+ // @ts-ignore
81
+ topStart: null, // @TODO: remove ignore once https://github.com/DataTables/DataTablesSrc/issues/271 is released
82
+ // @ts-ignore
83
+ topEnd: null, // @TODO: remove ignore once https://github.com/DataTables/DataTablesSrc/issues/271 is released
84
+ },
85
+ pageLength: DEFAULT_PAGE_SIZE, //default page length
86
+ lengthChange: true, //allow changing page length
87
+ data: [],
88
+ columns: [],
89
+ order: [],
90
+ deferRender: true,
91
+ orderClasses: false,
92
+ language: {
93
+ paginate: {
94
+ first: "&lt;&lt;", // Have to specify these two due to TS defs, <<
95
+ last: "&gt;&gt;", // Have to specify these two due to TS defs, >>
96
+ next: "&gt;", // >
97
+ previous: "&lt;", // <
98
+ },
99
+ },
100
+ },
101
+ };
102
+ private getRows(): DataRow[] {
103
+ if (!this.yasr.results) return [];
104
+ const bindings = this.yasr.results.getBindings();
105
+ if (!bindings) return [];
106
+ // Vars decide the columns
107
+ const vars = this.yasr.results.getVariables();
108
+ // Use "" as the empty value, undefined will throw runtime errors
109
+ return bindings.map((binding, rowId) => [rowId + 1, ...vars.map((variable) => binding[variable] ?? "")]);
110
+ }
111
+
112
+ private getUriLinkFromBinding(binding: Parser.BindingValue, prefixes?: { [key: string]: string }) {
113
+ const href = sanitize(binding.value);
114
+ let visibleString = href;
115
+ let prefixed = false;
116
+ if (prefixes) {
117
+ for (const prefixLabel in prefixes) {
118
+ if (visibleString.indexOf(prefixes[prefixLabel]) == 0) {
119
+ visibleString = prefixLabel + ":" + href.substring(prefixes[prefixLabel].length);
120
+ prefixed = true;
121
+ break;
122
+ }
123
+ }
124
+ }
125
+ // Hide brackets when prefixed or compact
126
+ const hideBrackets = prefixed || this.persistentConfig.compact;
127
+ const uri = `${hideBrackets ? "" : "&lt;"}<a class='iri' target='${
128
+ this.config.openIriInNewWindow ? "_blank" : "_self"
129
+ }'${this.config.openIriInNewWindow ? " ref='noopener noreferrer'" : ""} href='${href}'>${sanitize(
130
+ visibleString,
131
+ )}</a>${hideBrackets ? "" : "&gt;"}`;
132
+ return sanitize(uri);
133
+ }
134
+ private getCellContent(binding: Parser.BindingValue, prefixes?: { [label: string]: string }): string {
135
+ let content: string;
136
+ if (binding.type == "uri") {
137
+ content = `<span>${this.getUriLinkFromBinding(binding, prefixes)}</span>`;
138
+ } else {
139
+ content = `<span class='nonIri'>${this.formatLiteral(binding, prefixes)}</span>`;
140
+ }
141
+
142
+ return `<div>${content}</div>`;
143
+ }
144
+ private formatLiteral(literalBinding: Parser.BindingValue, prefixes?: { [key: string]: string }) {
145
+ let stringRepresentation = sanitize(escape(literalBinding.value));
146
+ // Return now when in compact mode.
147
+ if (this.persistentConfig.compact) return stringRepresentation;
148
+
149
+ if (literalBinding["xml:lang"]) {
150
+ stringRepresentation = `"${stringRepresentation}"<sup>@${literalBinding["xml:lang"]}</sup>`;
151
+ } else if (literalBinding.datatype) {
152
+ const dataType = this.getUriLinkFromBinding({ type: "uri", value: literalBinding.datatype }, prefixes);
153
+ stringRepresentation = `"${stringRepresentation}"<sup>^^${dataType}</sup>`;
154
+ }
155
+ return stringRepresentation;
156
+ }
157
+
158
+ private getColumns(): ConfigColumns[] {
159
+ if (!this.yasr.results) return [];
160
+ const prefixes = this.yasr.getPrefixes();
161
+
162
+ return [
163
+ {
164
+ name: "",
165
+ searchable: false,
166
+ width: `${this.getSizeFirstColumn()}px`,
167
+ type: "num",
168
+ orderable: false,
169
+ visible: this.persistentConfig.compact !== true,
170
+ render: (data: number, type: any) =>
171
+ type === "filter" || type === "sort" || !type ? data : `<div class="rowNumber">${data}</div>`,
172
+ }, //prepend with row numbers column
173
+ ...this.yasr.results?.getVariables().map((name) => {
174
+ return <ConfigColumns>{
175
+ name,
176
+ title: sanitize(name),
177
+ render: (data: Parser.BindingValue | "", type: any, _row: any, _meta: CellMetaSettings) => {
178
+ // Handle empty rows
179
+ if (data === "") return data;
180
+ if (type === "filter" || type === "sort" || !type) return sanitize(data.value);
181
+ return this.getCellContent(data, prefixes);
182
+ },
183
+ };
184
+ }),
185
+ ];
186
+ }
187
+ private getSizeFirstColumn() {
188
+ const numResults = this.yasr.results?.getBindings()?.length || 0;
189
+ return numResults.toString().length * 8;
190
+ }
191
+
192
+ public draw(persistentConfig: PersistentConfig) {
193
+ this.persistentConfig = { ...this.persistentConfig, ...persistentConfig };
194
+ this.tableEl = document.createElement("table");
195
+ const rows = this.getRows();
196
+ const columns = this.getColumns();
197
+
198
+ if (rows.length <= (persistentConfig?.pageSize || DEFAULT_PAGE_SIZE)) {
199
+ this.yasr.pluginControls;
200
+ addClass(this.yasr.rootEl, "isSinglePage");
201
+ } else {
202
+ removeClass(this.yasr.rootEl, "isSinglePage");
203
+ }
204
+
205
+ if (this.dataTable) {
206
+ this.destroyResizer();
207
+
208
+ this.dataTable.destroy(true);
209
+ this.dataTable = undefined;
210
+ }
211
+ this.yasr.resultsEl.appendChild(this.tableEl);
212
+ // reset some default config properties as they couldn't be initialized beforehand
213
+ const dtConfig: Config = {
214
+ ...(cloneDeep(this.config.tableConfig) as unknown as Config),
215
+ pageLength: persistentConfig?.pageSize ? persistentConfig.pageSize : DEFAULT_PAGE_SIZE,
216
+ data: rows,
217
+ columns: columns,
218
+ };
219
+ this.dataTable = $(this.tableEl).DataTable(dtConfig);
220
+ this.tableEl.style.removeProperty("width");
221
+ this.tableEl.style.width = this.tableEl.clientWidth + "px";
222
+ const widths = Array.from(this.tableEl.querySelectorAll("th")).map((h) => h.offsetWidth - 26);
223
+ this.tableResizer = new ColumnResizer.default(this.tableEl, {
224
+ widths: this.persistentConfig.compact === true ? widths : [this.getSizeFirstColumn(), ...widths.slice(1)],
225
+ partialRefresh: true,
226
+ onResize: this.persistentConfig.isEllipsed !== false && this.setEllipsisHandlers,
227
+ headerOnly: true,
228
+ });
229
+ // DataTables uses the rendered style to decide the widths of columns.
230
+ // Before a draw remove the ellipseTable styling
231
+ if (this.persistentConfig.isEllipsed !== false) {
232
+ this.dataTable?.on("preDraw", () => {
233
+ this.tableResizer?.reset({ disable: true });
234
+ removeClass(this.tableEl, "ellipseTable");
235
+ this.tableEl?.style.removeProperty("width");
236
+ this.tableEl?.style.setProperty("width", this.tableEl.clientWidth + "px");
237
+ return true; // Indicate it should re-render
238
+ });
239
+ // After a draw
240
+ this.dataTable?.on("draw", () => {
241
+ if (!this.tableEl) return;
242
+ // Width of table after render, removing width will make it fall back to 100%
243
+ let targetSize = this.tableEl.clientWidth;
244
+ this.tableEl.style.removeProperty("width");
245
+ // Let's make sure the new size is not bigger
246
+ if (targetSize > this.tableEl.clientWidth) targetSize = this.tableEl.clientWidth;
247
+ this.tableEl?.style.setProperty("width", `${targetSize}px`);
248
+ // Enable the re-sizer
249
+ this.tableResizer?.reset({
250
+ disable: false,
251
+ partialRefresh: true,
252
+ onResize: this.setEllipsisHandlers,
253
+ headerOnly: true,
254
+ });
255
+ // Re-add the ellipsis
256
+ addClass(this.tableEl, "ellipseTable");
257
+ // Check if cells need the ellipsisHandlers
258
+ this.setEllipsisHandlers();
259
+ });
260
+ }
261
+
262
+ this.drawControls();
263
+ // Draw again but with the events
264
+ if (this.persistentConfig.isEllipsed !== false) {
265
+ addClass(this.tableEl, "ellipseTable");
266
+ this.setEllipsisHandlers();
267
+ }
268
+ // if (this.tableEl.clientWidth > width) this.tableEl.parentElement?.style.setProperty("overflow", "hidden");
269
+ }
270
+
271
+ private setEllipsisHandlers = () => {
272
+ this.dataTable?.cells({ page: "current" }).every((rowIdx, colIdx) => {
273
+ const cell = this.dataTable?.cell(rowIdx, colIdx);
274
+ if (cell?.data() === "") return;
275
+ const cellNode = cell?.node() as HTMLTableCellElement;
276
+ if (cellNode) {
277
+ const content = cellNode.firstChild as HTMLDivElement;
278
+ if ((content.firstElementChild?.getBoundingClientRect().width || 0) > content.getBoundingClientRect().width) {
279
+ if (!content.classList.contains("expandable")) {
280
+ addClass(content, "expandable");
281
+ content.addEventListener("click", expand, { once: true });
282
+ }
283
+ } else {
284
+ if (content.classList.contains("expandable")) {
285
+ removeClass(content, "expandable");
286
+ content.removeEventListener("click", expand);
287
+ }
288
+ }
289
+ }
290
+ });
291
+ };
292
+ private handleTableSearch = (event: KeyboardEvent) => {
293
+ this.dataTable?.search((event.target as HTMLInputElement).value).draw("page");
294
+ };
295
+ private handleTableSizeSelect = (event: Event) => {
296
+ const pageLength = parseInt((event.target as HTMLSelectElement).value);
297
+ // Set page length
298
+ this.dataTable?.page.len(pageLength).draw("page");
299
+ // Store in persistentConfig
300
+ this.persistentConfig.pageSize = pageLength;
301
+ this.yasr.storePluginConfig("table", this.persistentConfig);
302
+ };
303
+ private handleSetCompactToggle = (event: Event) => {
304
+ // Store in persistentConfig
305
+ this.persistentConfig.compact = (event.target as HTMLInputElement).checked;
306
+ // Update the table
307
+ this.draw(this.persistentConfig);
308
+ this.yasr.storePluginConfig("table", this.persistentConfig);
309
+ };
310
+ private handleSetEllipsisToggle = (event: Event) => {
311
+ // Store in persistentConfig
312
+ this.persistentConfig.isEllipsed = (event.target as HTMLInputElement).checked;
313
+ // Update the table
314
+ this.draw(this.persistentConfig);
315
+ this.yasr.storePluginConfig("table", this.persistentConfig);
316
+ };
317
+ /**
318
+ * Draws controls on each update
319
+ */
320
+ drawControls() {
321
+ // Remove old header
322
+ this.removeControls();
323
+ this.tableControls = document.createElement("div");
324
+ this.tableControls.className = "tableControls";
325
+
326
+ // Compact switch
327
+ const toggleWrapper = document.createElement("div");
328
+ const switchComponent = document.createElement("label");
329
+ const textComponent = document.createElement("span");
330
+ textComponent.innerText = "Simple view";
331
+ addClass(textComponent, "label");
332
+ switchComponent.appendChild(textComponent);
333
+ addClass(switchComponent, "switch");
334
+ toggleWrapper.appendChild(switchComponent);
335
+ this.tableCompactSwitch = document.createElement("input");
336
+ switchComponent.addEventListener("change", this.handleSetCompactToggle);
337
+ this.tableCompactSwitch.type = "checkbox";
338
+ switchComponent.appendChild(this.tableCompactSwitch);
339
+ this.tableCompactSwitch.defaultChecked = !!this.persistentConfig.compact;
340
+ this.tableControls.appendChild(toggleWrapper);
341
+
342
+ // Ellipsis switch
343
+ const ellipseToggleWrapper = document.createElement("div");
344
+ const ellipseSwitchComponent = document.createElement("label");
345
+ const ellipseTextComponent = document.createElement("span");
346
+ ellipseTextComponent.innerText = "Ellipse";
347
+ addClass(ellipseTextComponent, "label");
348
+ ellipseSwitchComponent.appendChild(ellipseTextComponent);
349
+ addClass(ellipseSwitchComponent, "switch");
350
+ ellipseToggleWrapper.appendChild(ellipseSwitchComponent);
351
+ this.tableEllipseSwitch = document.createElement("input");
352
+ ellipseSwitchComponent.addEventListener("change", this.handleSetEllipsisToggle);
353
+ this.tableEllipseSwitch.type = "checkbox";
354
+ ellipseSwitchComponent.appendChild(this.tableEllipseSwitch);
355
+ this.tableEllipseSwitch.defaultChecked = this.persistentConfig.isEllipsed !== false;
356
+ this.tableControls.appendChild(ellipseToggleWrapper);
357
+
358
+ // Create table filter
359
+ this.tableFilterField = document.createElement("input");
360
+ this.tableFilterField.className = "tableFilter";
361
+ this.tableFilterField.placeholder = "Filter query results";
362
+ this.tableFilterField.setAttribute("aria-label", "Filter query results");
363
+ this.tableControls.appendChild(this.tableFilterField);
364
+ this.tableFilterField.addEventListener("keyup", this.handleTableSearch);
365
+
366
+ // Create page wrapper
367
+ const pageSizerWrapper = document.createElement("div");
368
+ pageSizerWrapper.className = "pageSizeWrapper";
369
+
370
+ // Create label for page size element
371
+ const pageSizerLabel = document.createElement("span");
372
+ pageSizerLabel.textContent = "Page size: ";
373
+ pageSizerLabel.className = "pageSizerLabel";
374
+ pageSizerWrapper.appendChild(pageSizerLabel);
375
+
376
+ // Create page size element
377
+ this.tableSizeField = document.createElement("select");
378
+ this.tableSizeField.className = "tableSizer";
379
+
380
+ // Create options for page sizer
381
+ const options = [10, 50, 100, 1000, -1];
382
+ for (const option of options) {
383
+ const element = document.createElement("option");
384
+ element.value = option + "";
385
+ // -1 selects everything so we should call it All
386
+ element.innerText = option > 0 ? option + "" : "All";
387
+ // Set initial one as selected
388
+ if (this.dataTable?.page.len() === option) element.selected = true;
389
+ this.tableSizeField.appendChild(element);
390
+ }
391
+ pageSizerWrapper.appendChild(this.tableSizeField);
392
+ this.tableSizeField.addEventListener("change", this.handleTableSizeSelect);
393
+ this.tableControls.appendChild(pageSizerWrapper);
394
+ this.yasr.pluginControls.appendChild(this.tableControls);
395
+ }
396
+ download(filename?: string) {
397
+ return {
398
+ getData: () => this.yasr.results?.asCsv() || "",
399
+ contentType: "text/csv",
400
+ title: "Download result",
401
+ filename: `${filename || "queryResults"}.csv`,
402
+ } as DownloadInfo;
403
+ }
404
+
405
+ public canHandleResults() {
406
+ return !!this.yasr.results && this.yasr.results.getVariables() && this.yasr.results.getVariables().length > 0;
407
+ }
408
+ private removeControls() {
409
+ // Unregister listeners and remove references to old fields
410
+ this.tableFilterField?.removeEventListener("keyup", this.handleTableSearch);
411
+ this.tableFilterField = undefined;
412
+ this.tableSizeField?.removeEventListener("change", this.handleTableSizeSelect);
413
+ this.tableSizeField = undefined;
414
+ this.tableCompactSwitch?.removeEventListener("change", this.handleSetCompactToggle);
415
+ this.tableCompactSwitch = undefined;
416
+ this.tableEllipseSwitch?.removeEventListener("change", this.handleSetEllipsisToggle);
417
+ this.tableEllipseSwitch = undefined;
418
+ // Empty controls
419
+ while (this.tableControls?.firstChild) this.tableControls.firstChild.remove();
420
+ this.tableControls?.remove();
421
+ }
422
+ private destroyResizer() {
423
+ if (this.tableResizer) {
424
+ this.tableResizer.reset({ disable: true });
425
+ window.removeEventListener("resize", this.tableResizer.onResize);
426
+ this.tableResizer = undefined;
427
+ }
428
+ }
429
+ destroy() {
430
+ this.removeControls();
431
+ this.destroyResizer();
432
+ // According to datatables docs, destroy(true) will also remove all events
433
+ this.dataTable?.destroy(true);
434
+ this.dataTable = undefined;
435
+ removeClass(this.yasr.rootEl, "isSinglePage");
436
+ }
437
+ }
@@ -0,0 +1,10 @@
1
+ @use "variables.scss";
2
+ /**need to reset this: the modal-dialog class conflicts with bootstrap**/
3
+ .modal-dialog.google-visualization-charteditor-dialog {
4
+ z-index: variables.$zIndex-global;
5
+ width: auto;
6
+ margin: inherit;
7
+ .charts-flat-menu-button {
8
+ box-sizing: content-box;
9
+ }
10
+ }
@@ -0,0 +1,2 @@
1
+ $zIndex-fullscreen: 10;
2
+ $zIndex-global: $zIndex-fullscreen + 1;