beanbagdb-components 0.1.1 → 0.2.0
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.
- package/README.md +3 -0
- package/dist/app.js +74 -0
- package/dist/data.html +457 -0
- package/dist/main.js +18663 -6118
- package/dist/main.js.map +1 -0
- package/dist/main.umd.cjs +30 -15
- package/dist/main.umd.cjs.map +1 -0
- package/dist/settings.html +39 -0
- package/dist/test.html +34 -0
- package/package.json +5 -3
package/README.md
CHANGED
package/dist/app.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const initPage = async (params) => {
|
|
2
|
+
// 1. Check if "db" param exists, throw error if missing
|
|
3
|
+
const urlParams = new URLSearchParams(params || window.location.search);
|
|
4
|
+
const dbParam = urlParams.get('db');
|
|
5
|
+
|
|
6
|
+
if (!dbParam) {
|
|
7
|
+
throw new Error('Required "db" parameter is missing from URL');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 2. Get the bbdb-db-list array from localStorage
|
|
11
|
+
const dbListJson = localStorage.getItem('bbdb-db-list');
|
|
12
|
+
if (!dbListJson) {
|
|
13
|
+
throw new Error('No databases found in localStorage "bbdb-db-list"');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const dbList = JSON.parse(dbListJson); // [{name: ""}, {}]
|
|
17
|
+
|
|
18
|
+
// 3. Find matching db object by name
|
|
19
|
+
const dbObj = dbList.find(db => db.name === dbParam);
|
|
20
|
+
|
|
21
|
+
if (!dbObj) {
|
|
22
|
+
throw new Error(`Database "${dbParam}" not found in bbdb-db-list`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return dbObj;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const showMessage = (type, message) => {
|
|
29
|
+
let messageEl = document.querySelector('#show-message');
|
|
30
|
+
|
|
31
|
+
// Color mapping based on type
|
|
32
|
+
const colors = {
|
|
33
|
+
'error': '#dc3545',
|
|
34
|
+
'success': '#28a745',
|
|
35
|
+
'warning': '#ffc107',
|
|
36
|
+
'info': '#17a2b8'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const color = colors[type] || '#6c757d';
|
|
40
|
+
|
|
41
|
+
if (messageEl) {
|
|
42
|
+
// Replace existing content
|
|
43
|
+
messageEl.innerHTML = `${message} <button onclick="this.parentElement.remove()">X</button>`;
|
|
44
|
+
} else {
|
|
45
|
+
// Create new div on top of body
|
|
46
|
+
messageEl = document.createElement('div');
|
|
47
|
+
messageEl.id = 'show-message';
|
|
48
|
+
messageEl.innerHTML = `${message} <button onclick="this.parentElement.remove()">X</button>`;
|
|
49
|
+
document.body.prepend(messageEl); // Adds dynamically on top of body
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Apply styling
|
|
53
|
+
Object.assign(messageEl.style, {
|
|
54
|
+
color: color,
|
|
55
|
+
padding: '15px',
|
|
56
|
+
margin: '1',
|
|
57
|
+
boxShadow: `2px 10px 9px -5px rgba(0,0,0,0.2)`,
|
|
58
|
+
background: '#f8f9fa',
|
|
59
|
+
borderBottom: `4px solid ${color}`,
|
|
60
|
+
//boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
|
|
61
|
+
position: 'relative'
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Style the close button
|
|
65
|
+
const closeBtn = messageEl.querySelector('button');
|
|
66
|
+
Object.assign(closeBtn.style, {
|
|
67
|
+
float: 'right',
|
|
68
|
+
border: 'none',
|
|
69
|
+
background: 'none',
|
|
70
|
+
fontSize: '20px',
|
|
71
|
+
cursor: 'pointer',
|
|
72
|
+
fontWeight: 'bold'
|
|
73
|
+
});
|
|
74
|
+
};
|
package/dist/data.html
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<!-- <script type="module" src="../dist/main.js"></script> -->
|
|
7
|
+
|
|
8
|
+
<title>BBDB-Database</title>
|
|
9
|
+
<link
|
|
10
|
+
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css"
|
|
11
|
+
rel="stylesheet"
|
|
12
|
+
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB"
|
|
13
|
+
crossorigin="anonymous"
|
|
14
|
+
/>
|
|
15
|
+
<link
|
|
16
|
+
href="https://unpkg.com/tabulator-tables@6.3.1/dist/css/tabulator.min.css"
|
|
17
|
+
rel="stylesheet"
|
|
18
|
+
/>
|
|
19
|
+
<script
|
|
20
|
+
type="text/javascript"
|
|
21
|
+
src="https://unpkg.com/tabulator-tables@6.3.1/dist/js/tabulator.min.js"
|
|
22
|
+
></script>
|
|
23
|
+
|
|
24
|
+
<script src="app.js"></script>
|
|
25
|
+
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
|
26
|
+
<script type="module">
|
|
27
|
+
import { BBDB } from "../dist/main.js";
|
|
28
|
+
const { createApp, ref, onMounted, nextTick } = Vue;
|
|
29
|
+
let DB;
|
|
30
|
+
let dataTable;
|
|
31
|
+
|
|
32
|
+
function linkEditor(cell, onRendered, success, cancel, editorParams) {
|
|
33
|
+
const editor = document.createElement("input");
|
|
34
|
+
editor.type = "text";
|
|
35
|
+
editor.value = cell.getValue() || "";
|
|
36
|
+
editor.style.padding = "3px";
|
|
37
|
+
editor.style.width = "100%";
|
|
38
|
+
editor.style.boxSizing = "border-box";
|
|
39
|
+
|
|
40
|
+
const oldValue = cell.getOldValue();
|
|
41
|
+
const rowData = cell.getRow().getData();
|
|
42
|
+
const rowId = rowData._id;
|
|
43
|
+
|
|
44
|
+
console.log("Row ID:", rowId); // Debug: verify ID is correct
|
|
45
|
+
|
|
46
|
+
onRendered(() => {
|
|
47
|
+
editor.focus();
|
|
48
|
+
editor.select();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function attemptCommit() {
|
|
52
|
+
const newValue = editor.value.trim();
|
|
53
|
+
|
|
54
|
+
if (newValue === oldValue) {
|
|
55
|
+
success(newValue);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!/^[a-zA-Z0-9]+$/.test(newValue)) {
|
|
60
|
+
cancel();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
editor.disabled = true;
|
|
65
|
+
editor.style.opacity = "0.6";
|
|
66
|
+
|
|
67
|
+
DB.update({
|
|
68
|
+
criteria: { _id: rowId },
|
|
69
|
+
updates: {
|
|
70
|
+
meta: {
|
|
71
|
+
link: newValue,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
.then((response) => {
|
|
76
|
+
console.log(response);
|
|
77
|
+
success(newValue);
|
|
78
|
+
// if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
79
|
+
// return response.json();
|
|
80
|
+
})
|
|
81
|
+
// .then((data) => {
|
|
82
|
+
// if (data.success) {
|
|
83
|
+
// success(newValue);
|
|
84
|
+
// } else {
|
|
85
|
+
// throw new Error(data.error || "API rejected");
|
|
86
|
+
// }
|
|
87
|
+
// })
|
|
88
|
+
.catch((error) => {
|
|
89
|
+
console.error("Update failed:", error);
|
|
90
|
+
//cell.restoreOldValue();
|
|
91
|
+
cancel();
|
|
92
|
+
showMessage("error", error.message);
|
|
93
|
+
})
|
|
94
|
+
.finally(() => {
|
|
95
|
+
editor.disabled = false;
|
|
96
|
+
editor.style.opacity = "1";
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
editor.addEventListener("change", attemptCommit);
|
|
101
|
+
editor.addEventListener("blur", attemptCommit);
|
|
102
|
+
editor.addEventListener("keydown", (e) => {
|
|
103
|
+
if (e.key === "Enter") attemptCommit();
|
|
104
|
+
if (e.key === "Escape") cancel();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return editor;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function titleEditor(cell, onRendered, success, cancel, editorParams) {
|
|
111
|
+
const editor = document.createElement("input");
|
|
112
|
+
editor.type = "text";
|
|
113
|
+
editor.value = cell.getValue() || "";
|
|
114
|
+
editor.style.padding = "3px";
|
|
115
|
+
editor.style.width = "100%";
|
|
116
|
+
editor.style.boxSizing = "border-box";
|
|
117
|
+
|
|
118
|
+
const oldValue = cell.getOldValue();
|
|
119
|
+
const rowData = cell.getRow().getData();
|
|
120
|
+
const rowId = rowData._id;
|
|
121
|
+
|
|
122
|
+
console.log("Row ID:", rowId); // Debug: verify ID is correct
|
|
123
|
+
|
|
124
|
+
onRendered(() => {
|
|
125
|
+
editor.focus();
|
|
126
|
+
editor.select();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
function attemptCommit() {
|
|
130
|
+
const newValue = editor.value.trim();
|
|
131
|
+
|
|
132
|
+
if (newValue === oldValue) {
|
|
133
|
+
success(newValue);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Title validation: alphanumeric + spaces, dashes, underscores (1-100 chars)
|
|
138
|
+
if (!/^[a-zA-Z0-9\s\-_]{1,100}$/.test(newValue)) {
|
|
139
|
+
cancel();
|
|
140
|
+
showMessage(
|
|
141
|
+
"error",
|
|
142
|
+
"Title must be 1-100 chars (letters, numbers, spaces, -, _)"
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
editor.disabled = true;
|
|
148
|
+
editor.style.opacity = "0.6";
|
|
149
|
+
|
|
150
|
+
DB.update({
|
|
151
|
+
criteria: { _id: rowId },
|
|
152
|
+
updates: {
|
|
153
|
+
meta: {
|
|
154
|
+
title: newValue,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
.then((response) => {
|
|
159
|
+
console.log(response);
|
|
160
|
+
success(newValue);
|
|
161
|
+
})
|
|
162
|
+
.catch((error) => {
|
|
163
|
+
console.error("Update failed:", error);
|
|
164
|
+
cancel();
|
|
165
|
+
showMessage("error", error.message);
|
|
166
|
+
})
|
|
167
|
+
.finally(() => {
|
|
168
|
+
editor.disabled = false;
|
|
169
|
+
editor.style.opacity = "1";
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
editor.addEventListener("change", attemptCommit);
|
|
174
|
+
editor.addEventListener("blur", attemptCommit);
|
|
175
|
+
editor.addEventListener("keydown", (e) => {
|
|
176
|
+
if (e.key === "Enter") attemptCommit();
|
|
177
|
+
if (e.key === "Escape") cancel();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return editor;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function tagsEditor(cell, onRendered, success, cancel, editorParams) {
|
|
184
|
+
const editor = document.createElement("input");
|
|
185
|
+
editor.type = "text";
|
|
186
|
+
editor.value = (cell.getValue() || []).join(", ");
|
|
187
|
+
editor.style.padding = "3px";
|
|
188
|
+
editor.style.width = "100%";
|
|
189
|
+
editor.style.boxSizing = "border-box";
|
|
190
|
+
editor.placeholder = "Enter tags separated by commas";
|
|
191
|
+
|
|
192
|
+
const oldValue = cell.getOldValue() || [];
|
|
193
|
+
const rowData = cell.getRow().getData();
|
|
194
|
+
const rowId = rowData._id;
|
|
195
|
+
|
|
196
|
+
console.log("Row ID:", rowId);
|
|
197
|
+
|
|
198
|
+
onRendered(() => {
|
|
199
|
+
editor.focus();
|
|
200
|
+
editor.select();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
function attemptCommit() {
|
|
204
|
+
const inputValue = editor.value.trim();
|
|
205
|
+
|
|
206
|
+
if (
|
|
207
|
+
inputValue ===
|
|
208
|
+
(Array.isArray(oldValue) ? oldValue.join(", ") : oldValue)
|
|
209
|
+
) {
|
|
210
|
+
success(oldValue);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const newTags = inputValue
|
|
215
|
+
? inputValue
|
|
216
|
+
.split(",")
|
|
217
|
+
.map((tag) => tag.trim())
|
|
218
|
+
.filter((tag) => tag.length > 0)
|
|
219
|
+
: [];
|
|
220
|
+
|
|
221
|
+
if (newTags.length === 0 && inputValue !== "") {
|
|
222
|
+
cancel();
|
|
223
|
+
showMessage("error", "At least one tag required or clear all tags");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const invalidTag = newTags.find(
|
|
228
|
+
(tag) => !/^[a-zA-Z0-9\-_\s]{1,50}$/.test(tag)
|
|
229
|
+
);
|
|
230
|
+
if (invalidTag) {
|
|
231
|
+
cancel();
|
|
232
|
+
showMessage(
|
|
233
|
+
"error",
|
|
234
|
+
`Invalid tag: "${invalidTag}". Use letters, numbers, -, _, spaces`
|
|
235
|
+
);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
editor.disabled = true;
|
|
240
|
+
editor.style.opacity = "0.6";
|
|
241
|
+
|
|
242
|
+
DB.update({
|
|
243
|
+
criteria: { _id: rowId },
|
|
244
|
+
updates: {
|
|
245
|
+
meta: {
|
|
246
|
+
tags: newTags,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
})
|
|
250
|
+
.then((response) => {
|
|
251
|
+
console.log("Tags updated:", newTags, response);
|
|
252
|
+
success(newTags);
|
|
253
|
+
})
|
|
254
|
+
.catch((error) => {
|
|
255
|
+
console.error("Update failed:", error);
|
|
256
|
+
cell.restoreOldValue();
|
|
257
|
+
cancel();
|
|
258
|
+
showMessage("error", error.message);
|
|
259
|
+
})
|
|
260
|
+
.finally(() => {
|
|
261
|
+
editor.disabled = false;
|
|
262
|
+
editor.style.opacity = "1";
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
editor.addEventListener("change", attemptCommit);
|
|
267
|
+
editor.addEventListener("blur", attemptCommit);
|
|
268
|
+
editor.addEventListener("keydown", (e) => {
|
|
269
|
+
if (e.key === "Enter") attemptCommit();
|
|
270
|
+
if (e.key === "Escape") cancel();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
return editor;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
document.addEventListener("DOMContentLoaded", async () => {
|
|
277
|
+
try {
|
|
278
|
+
const db_details = await initPage();
|
|
279
|
+
DB = await BBDB(db_details);
|
|
280
|
+
await DB.ready();
|
|
281
|
+
//console.log(DB);
|
|
282
|
+
//console.log(db_details);
|
|
283
|
+
showMessage("success", `Loaded database: ${db_details.name}`);
|
|
284
|
+
|
|
285
|
+
createApp({
|
|
286
|
+
setup() {
|
|
287
|
+
const tableRef = ref(null);
|
|
288
|
+
const table = ref(null);
|
|
289
|
+
const loading = ref(false);
|
|
290
|
+
const recordCount = ref(0);
|
|
291
|
+
|
|
292
|
+
// Transform records for table
|
|
293
|
+
const transformRecords = (docs) => {
|
|
294
|
+
return docs.map((doc) => {
|
|
295
|
+
const row = {
|
|
296
|
+
schema: doc.schema || "unknown",
|
|
297
|
+
_id: doc._id,
|
|
298
|
+
_rev: doc._rev,
|
|
299
|
+
};
|
|
300
|
+
Object.keys(doc.meta).map((ky) => {
|
|
301
|
+
row[`meta_${ky}`] = doc.meta[ky];
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// if (doc.data) {
|
|
305
|
+
// Object.assign(row, doc.data);
|
|
306
|
+
// }
|
|
307
|
+
return row;
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const generateColumns = () => {
|
|
312
|
+
let columns = [
|
|
313
|
+
{
|
|
314
|
+
title: "Title",
|
|
315
|
+
field: "meta_title",
|
|
316
|
+
sorter: "string",
|
|
317
|
+
editor: titleEditor,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
title: "Schema",
|
|
321
|
+
field: "schema",
|
|
322
|
+
sorter: "string",
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
title: "Link",
|
|
326
|
+
field: "meta_link",
|
|
327
|
+
sorter: "string",
|
|
328
|
+
editor: linkEditor,
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
title: "Document ID",
|
|
332
|
+
field: "_id",
|
|
333
|
+
sorter: "string",
|
|
334
|
+
},
|
|
335
|
+
// {
|
|
336
|
+
// title: "Revision ID",
|
|
337
|
+
// field: "_rev",
|
|
338
|
+
// sorter: "string",
|
|
339
|
+
// },
|
|
340
|
+
|
|
341
|
+
{
|
|
342
|
+
title: "Created On",
|
|
343
|
+
field: "meta_created_on",
|
|
344
|
+
formatter: function (cell) {
|
|
345
|
+
const ts = Number(cell.getValue());
|
|
346
|
+
if (!ts || isNaN(ts)) return "Invalid Date";
|
|
347
|
+
|
|
348
|
+
// Adjust multiplier based on your epoch unit:
|
|
349
|
+
// - Use *1000 if seconds → ms (common for Unix)
|
|
350
|
+
// - Use *1 if already ms (JavaScript Date expects ms)
|
|
351
|
+
const date = new Date(ts * 1000);
|
|
352
|
+
|
|
353
|
+
// Format as YYYY-MM-DD HH:mm:ss (customize as needed)
|
|
354
|
+
const yyyy = date.getFullYear();
|
|
355
|
+
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
|
356
|
+
const dd = String(date.getDate()).padStart(2, "0");
|
|
357
|
+
const hh = String(date.getHours()).padStart(2, "0");
|
|
358
|
+
const min = String(date.getMinutes()).padStart(2, "0");
|
|
359
|
+
const ss = String(date.getSeconds()).padStart(2, "0");
|
|
360
|
+
|
|
361
|
+
return `${yyyy}-${mm}-${dd} ${hh}:${min}`;
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
title: "Tags",
|
|
366
|
+
field: "meta_tags",
|
|
367
|
+
//sorter: "array",
|
|
368
|
+
editor: tagsEditor,
|
|
369
|
+
formatter: function (cell) {
|
|
370
|
+
const tags = cell.getValue() || [];
|
|
371
|
+
return tags.length ? tags.join(", ") : "";
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
];
|
|
375
|
+
return columns;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const loadData = async (criteria = {}) => {
|
|
379
|
+
try {
|
|
380
|
+
loading.value = true;
|
|
381
|
+
const result = await DB.search({ selector: criteria });
|
|
382
|
+
recordCount.value = result.docs.length;
|
|
383
|
+
|
|
384
|
+
//const allFields = getAllFields(result.docs);
|
|
385
|
+
//console.log(allFields)
|
|
386
|
+
const tableData = transformRecords(result.docs);
|
|
387
|
+
//console.log(tableData)
|
|
388
|
+
const columns = generateColumns();
|
|
389
|
+
|
|
390
|
+
if (table.value) {
|
|
391
|
+
table.value.destroy();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
await nextTick();
|
|
395
|
+
|
|
396
|
+
table.value = new Tabulator(tableRef.value, {
|
|
397
|
+
data: tableData,
|
|
398
|
+
//autoColumns:true,
|
|
399
|
+
columns: columns,
|
|
400
|
+
layout: "fitColumns",
|
|
401
|
+
responsiveLayout: "hide",
|
|
402
|
+
pagination: true,
|
|
403
|
+
paginationSize: 25,
|
|
404
|
+
paginationSizeSelector: [10, 25, 50, 100],
|
|
405
|
+
groupBy: "schema",
|
|
406
|
+
groupStartOpen: true,
|
|
407
|
+
download: true,
|
|
408
|
+
downloadConfig: {
|
|
409
|
+
csv: { downloadButton: true },
|
|
410
|
+
excel: { downloadButton: true },
|
|
411
|
+
},
|
|
412
|
+
placeholder: "No data - click Search to load",
|
|
413
|
+
});
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error("Load failed:", error);
|
|
416
|
+
} finally {
|
|
417
|
+
loading.value = false;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
onMounted(() => {
|
|
422
|
+
loadData({});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
tableRef,
|
|
427
|
+
table,
|
|
428
|
+
loading,
|
|
429
|
+
recordCount,
|
|
430
|
+
loadData,
|
|
431
|
+
};
|
|
432
|
+
},
|
|
433
|
+
}).mount("#app");
|
|
434
|
+
} catch (error) {
|
|
435
|
+
showMessage("error", error.message);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
</script>
|
|
439
|
+
</head>
|
|
440
|
+
<body>
|
|
441
|
+
<div id="app" class="container-fluid py-4">
|
|
442
|
+
<div class="row mb-4">
|
|
443
|
+
<div class="col">
|
|
444
|
+
<button class="btn btn-primary" @click="loadData">
|
|
445
|
+
<span
|
|
446
|
+
v-if="loading"
|
|
447
|
+
class="spinner-border spinner-border-sm me-2"
|
|
448
|
+
></span>
|
|
449
|
+
{{ loading ? 'Loading...' : 'Search' }}
|
|
450
|
+
</button>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
<div ref="tableRef" class="tabulator"></div>
|
|
455
|
+
</div>
|
|
456
|
+
</body>
|
|
457
|
+
</html>
|