UNPKG

15.9 kBJavaScriptView Raw
1export default {
2 getBootstrapVersion () {
3 let bootstrapVersion = 5
4
5 try {
6 const rawVersion = $.fn.dropdown.Constructor.VERSION
7
8 // Only try to parse VERSION if it is defined.
9 // It is undefined in older versions of Bootstrap (tested with 3.1.1).
10 if (rawVersion !== undefined) {
11 bootstrapVersion = parseInt(rawVersion, 10)
12 }
13 } catch (e) {
14 // ignore
15 }
16
17 try {
18 // eslint-disable-next-line no-undef
19 const rawVersion = bootstrap.Tooltip.VERSION
20
21 if (rawVersion !== undefined) {
22 bootstrapVersion = parseInt(rawVersion, 10)
23 }
24 } catch (e) {
25 // ignore
26 }
27
28 return bootstrapVersion
29 },
30
31 getIconsPrefix (theme) {
32 return {
33 bootstrap3: 'glyphicon',
34 bootstrap4: 'fa',
35 bootstrap5: 'bi',
36 'bootstrap-table': 'icon',
37 bulma: 'fa',
38 foundation: 'fa',
39 materialize: 'material-icons',
40 semantic: 'fa'
41 }[theme] || 'fa'
42 },
43
44 getIcons (prefix) {
45 return {
46 glyphicon: {
47 paginationSwitchDown: 'glyphicon-collapse-down icon-chevron-down',
48 paginationSwitchUp: 'glyphicon-collapse-up icon-chevron-up',
49 refresh: 'glyphicon-refresh icon-refresh',
50 toggleOff: 'glyphicon-list-alt icon-list-alt',
51 toggleOn: 'glyphicon-list-alt icon-list-alt',
52 columns: 'glyphicon-th icon-th',
53 detailOpen: 'glyphicon-plus icon-plus',
54 detailClose: 'glyphicon-minus icon-minus',
55 fullscreen: 'glyphicon-fullscreen',
56 search: 'glyphicon-search',
57 clearSearch: 'glyphicon-trash'
58 },
59 fa: {
60 paginationSwitchDown: 'fa-caret-square-down',
61 paginationSwitchUp: 'fa-caret-square-up',
62 refresh: 'fa-sync',
63 toggleOff: 'fa-toggle-off',
64 toggleOn: 'fa-toggle-on',
65 columns: 'fa-th-list',
66 detailOpen: 'fa-plus',
67 detailClose: 'fa-minus',
68 fullscreen: 'fa-arrows-alt',
69 search: 'fa-search',
70 clearSearch: 'fa-trash'
71 },
72 bi: {
73 paginationSwitchDown: 'bi-caret-down-square',
74 paginationSwitchUp: 'bi-caret-up-square',
75 refresh: 'bi-arrow-clockwise',
76 toggleOff: 'bi-toggle-off',
77 toggleOn: 'bi-toggle-on',
78 columns: 'bi-list-ul',
79 detailOpen: 'bi-plus',
80 detailClose: 'bi-dash',
81 fullscreen: 'bi-arrows-move',
82 search: 'bi-search',
83 clearSearch: 'bi-trash'
84 },
85 icon: {
86 paginationSwitchDown: 'icon-arrow-up-circle',
87 paginationSwitchUp: 'icon-arrow-down-circle',
88 refresh: 'icon-refresh-cw',
89 toggleOff: 'icon-toggle-right',
90 toggleOn: 'icon-toggle-right',
91 columns: 'icon-list',
92 detailOpen: 'icon-plus',
93 detailClose: 'icon-minus',
94 fullscreen: 'icon-maximize',
95 search: 'icon-search',
96 clearSearch: 'icon-trash-2'
97 },
98 'material-icons': {
99 paginationSwitchDown: 'grid_on',
100 paginationSwitchUp: 'grid_off',
101 refresh: 'refresh',
102 toggleOff: 'tablet',
103 toggleOn: 'tablet_android',
104 columns: 'view_list',
105 detailOpen: 'add',
106 detailClose: 'remove',
107 fullscreen: 'fullscreen',
108 sort: 'sort',
109 search: 'search',
110 clearSearch: 'delete'
111 }
112 }[prefix]
113 },
114
115 getSearchInput (that) {
116 if (typeof that.options.searchSelector === 'string') {
117 return $(that.options.searchSelector)
118 }
119 return that.$toolbar.find('.search input')
120 },
121
122 // $.extend: https://github.com/jquery/jquery/blob/3.6.2/src/core.js#L132
123 extend (...args) {
124 let target = args[0] || {}
125 let i = 1
126 let deep = false
127 let clone
128
129 // Handle a deep copy situation
130 if (typeof target === 'boolean') {
131 deep = target
132
133 // Skip the boolean and the target
134 target = args[i] || {}
135 i++
136 }
137
138 // Handle case when target is a string or something (possible in deep copy)
139 if (typeof target !== 'object' && typeof target !== 'function') {
140 target = {}
141 }
142
143 for (; i < args.length; i++) {
144 const options = args[i]
145
146 // Ignore undefined/null values
147 if (typeof options === 'undefined' || options === null) {
148 continue
149 }
150
151 // Extend the base object
152 // eslint-disable-next-line guard-for-in
153 for (const name in options) {
154 const copy = options[name]
155
156 // Prevent Object.prototype pollution
157 // Prevent never-ending loop
158 if (name === '__proto__' || target === copy) {
159 continue
160 }
161
162 const copyIsArray = Array.isArray(copy)
163
164 // Recurse if we're merging plain objects or arrays
165 if (deep && copy && (this.isObject(copy) || copyIsArray)) {
166 const src = target[name]
167
168 if (copyIsArray && Array.isArray(src)) {
169 if (src.every(it => !this.isObject(it) && !Array.isArray(it))) {
170 target[name] = copy
171 continue
172 }
173 }
174
175 if (copyIsArray && !Array.isArray(src)) {
176 clone = []
177 } else if (!copyIsArray && !this.isObject(src)) {
178 clone = {}
179 } else {
180 clone = src
181 }
182
183 // Never move original objects, clone them
184 target[name] = this.extend(deep, clone, copy)
185
186 // Don't bring in undefined values
187 } else if (copy !== undefined) {
188 target[name] = copy
189 }
190 }
191 }
192
193 return target
194 },
195
196 // it only does '%s', and return '' when arguments are undefined
197 sprintf (_str, ...args) {
198 let flag = true
199 let i = 0
200
201 const str = _str.replace(/%s/g, () => {
202 const arg = args[i++]
203
204 if (typeof arg === 'undefined') {
205 flag = false
206 return ''
207 }
208 return arg
209 })
210
211 return flag ? str : ''
212 },
213
214 isObject (obj) {
215 return typeof obj === 'object' && obj !== null && !Array.isArray(obj)
216 },
217
218 isEmptyObject (obj = {}) {
219 return Object.entries(obj).length === 0 && obj.constructor === Object
220 },
221
222 isNumeric (n) {
223 return !isNaN(parseFloat(n)) && isFinite(n)
224 },
225
226 getFieldTitle (list, value) {
227 for (const item of list) {
228 if (item.field === value) {
229 return item.title
230 }
231 }
232 return ''
233 },
234
235 setFieldIndex (columns) {
236 let totalCol = 0
237 const flag = []
238
239 for (const column of columns[0]) {
240 totalCol += column.colspan || 1
241 }
242
243 for (let i = 0; i < columns.length; i++) {
244 flag[i] = []
245 for (let j = 0; j < totalCol; j++) {
246 flag[i][j] = false
247 }
248 }
249
250 for (let i = 0; i < columns.length; i++) {
251 for (const r of columns[i]) {
252 const rowspan = r.rowspan || 1
253 const colspan = r.colspan || 1
254 const index = flag[i].indexOf(false)
255
256 r.colspanIndex = index
257
258 if (colspan === 1) {
259 r.fieldIndex = index
260 // when field is undefined, use index instead
261 if (typeof r.field === 'undefined') {
262 r.field = index
263 }
264 } else {
265 r.colspanGroup = r.colspan
266 }
267
268 for (let j = 0; j < rowspan; j++) {
269 for (let k = 0; k < colspan; k++) {
270 flag[i + j][index + k] = true
271 }
272 }
273 }
274 }
275 },
276
277 normalizeAccent (value) {
278 if (typeof value !== 'string') {
279 return value
280 }
281 return value.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
282 },
283
284 updateFieldGroup (columns, fieldColumns) {
285 const allColumns = [].concat(...columns)
286
287 for (const c of columns) {
288 for (const r of c) {
289 if (r.colspanGroup > 1) {
290 let colspan = 0
291
292 for (let i = r.colspanIndex; i < r.colspanIndex + r.colspanGroup; i++) {
293 const column = allColumns.find(col => col.fieldIndex === i)
294
295 if (column.visible) {
296 colspan++
297 }
298 }
299 r.colspan = colspan
300 r.visible = colspan > 0
301 }
302 }
303 }
304
305 if (columns.length < 2) {
306 return
307 }
308
309 for (const column of fieldColumns) {
310 const sameColumns = allColumns.filter(col => col.fieldIndex === column.fieldIndex)
311
312 if (sameColumns.length > 1) {
313 for (const c of sameColumns) {
314 c.visible = column.visible
315 }
316 }
317 }
318 },
319
320 getScrollBarWidth () {
321 if (this.cachedWidth === undefined) {
322 const $inner = $('<div/>').addClass('fixed-table-scroll-inner')
323 const $outer = $('<div/>').addClass('fixed-table-scroll-outer')
324
325 $outer.append($inner)
326 $('body').append($outer)
327
328 const w1 = $inner[0].offsetWidth
329
330 $outer.css('overflow', 'scroll')
331 let w2 = $inner[0].offsetWidth
332
333 if (w1 === w2) {
334 w2 = $outer[0].clientWidth
335 }
336
337 $outer.remove()
338 this.cachedWidth = w1 - w2
339 }
340 return this.cachedWidth
341 },
342
343 calculateObjectValue (self, name, args, defaultValue) {
344 let func = name
345
346 if (typeof name === 'string') {
347 // support obj.func1.func2
348 const names = name.split('.')
349
350 if (names.length > 1) {
351 func = window
352 for (const f of names) {
353 func = func[f]
354 }
355 } else {
356 func = window[name]
357 }
358 }
359
360 if (func !== null && typeof func === 'object') {
361 return func
362 }
363
364 if (typeof func === 'function') {
365 return func.apply(self, args || [])
366 }
367
368 if (
369 !func &&
370 typeof name === 'string' &&
371 args &&
372 this.sprintf(name, ...args)
373 ) {
374 return this.sprintf(name, ...args)
375 }
376
377 return defaultValue
378 },
379
380 compareObjects (objectA, objectB, compareLength) {
381 const aKeys = Object.keys(objectA)
382 const bKeys = Object.keys(objectB)
383
384 if (compareLength && aKeys.length !== bKeys.length) {
385 return false
386 }
387
388 for (const key of aKeys) {
389 if (bKeys.includes(key) && objectA[key] !== objectB[key]) {
390 return false
391 }
392 }
393
394 return true
395 },
396
397 regexCompare (value, search) {
398 try {
399 const regexpParts = search.match(/^\/(.*?)\/([gim]*)$/)
400
401 if (value.toString().search(regexpParts ? new RegExp(regexpParts[1], regexpParts[2]) : new RegExp(search, 'gim')) !== -1) {
402 return true
403 }
404 } catch (e) {
405 return false
406 }
407 return false
408 },
409
410 escapeApostrophe (value) {
411 return value.toString()
412 .replace(/'/g, '&#39;')
413 },
414
415 escapeHTML (text) {
416 if (!text) {
417 return text
418 }
419 return text.toString()
420 .replace(/&/g, '&amp;')
421 .replace(/</g, '&lt;')
422 .replace(/>/g, '&gt;')
423 .replace(/"/g, '&quot;')
424 .replace(/'/g, '&#39;')
425 },
426
427 unescapeHTML (text) {
428 if (typeof text !== 'string' || !text) {
429 return text
430 }
431 return text.toString()
432 .replace(/&amp;/g, '&')
433 .replace(/&lt;/g, '<')
434 .replace(/&gt;/g, '>')
435 .replace(/&quot;/g, '"')
436 .replace(/&#39;/g, '\'')
437 },
438
439 removeHTML (text) {
440 if (!text) {
441 return text
442 }
443 return text.toString()
444 .replace(/(<([^>]+)>)/ig, '')
445 .replace(/&[#A-Za-z0-9]+;/gi, '')
446 .trim()
447 },
448
449 getRealDataAttr (dataAttr) {
450 for (const [attr, value] of Object.entries(dataAttr)) {
451 const auxAttr = attr.split(/(?=[A-Z])/).join('-').toLowerCase()
452
453 if (auxAttr !== attr) {
454 dataAttr[auxAttr] = value
455 delete dataAttr[attr]
456 }
457 }
458 return dataAttr
459 },
460
461 getItemField (item, field, escape, columnEscape = undefined) {
462 let value = item
463
464 // use column escape if it is defined
465 if (typeof columnEscape !== 'undefined') {
466 escape = columnEscape
467 }
468
469 if (typeof field !== 'string' || item.hasOwnProperty(field)) {
470 return escape ? this.escapeHTML(item[field]) : item[field]
471 }
472
473 const props = field.split('.')
474
475 for (const p of props) {
476 value = value && value[p]
477 }
478 return escape ? this.escapeHTML(value) : value
479 },
480
481 isIEBrowser () {
482 return navigator.userAgent.includes('MSIE ') ||
483 /Trident.*rv:11\./.test(navigator.userAgent)
484 },
485
486 findIndex (items, item) {
487 for (const it of items) {
488 if (JSON.stringify(it) === JSON.stringify(item)) {
489 return items.indexOf(it)
490 }
491 }
492 return -1
493 },
494
495 trToData (columns, $els) {
496 const data = []
497 const m = []
498
499 $els.each((y, el) => {
500 const $el = $(el)
501 const row = {}
502
503 // save tr's id, class and data-* attributes
504 row._id = $el.attr('id')
505 row._class = $el.attr('class')
506 row._data = this.getRealDataAttr($el.data())
507 row._style = $el.attr('style')
508
509 $el.find('>td,>th').each((_x, el) => {
510 const $el = $(el)
511 const cspan = +$el.attr('colspan') || 1
512 const rspan = +$el.attr('rowspan') || 1
513 let x = _x
514
515 // skip already occupied cells in current row
516 for (; m[y] && m[y][x]; x++) {
517 // ignore
518 }
519
520 // mark matrix elements occupied by current cell with true
521 for (let tx = x; tx < x + cspan; tx++) {
522 for (let ty = y; ty < y + rspan; ty++) {
523 if (!m[ty]) { // fill missing rows
524 m[ty] = []
525 }
526 m[ty][tx] = true
527 }
528 }
529
530 const field = columns[x].field
531
532 row[field] = this.escapeApostrophe($el.html().trim())
533 // save td's id, class and data-* attributes
534 row[`_${field}_id`] = $el.attr('id')
535 row[`_${field}_class`] = $el.attr('class')
536 row[`_${field}_rowspan`] = $el.attr('rowspan')
537 row[`_${field}_colspan`] = $el.attr('colspan')
538 row[`_${field}_title`] = $el.attr('title')
539 row[`_${field}_data`] = this.getRealDataAttr($el.data())
540 row[`_${field}_style`] = $el.attr('style')
541 })
542 data.push(row)
543 })
544 return data
545 },
546
547 sort (a, b, order, options, aPosition, bPosition) {
548 if (a === undefined || a === null) {
549 a = ''
550 }
551 if (b === undefined || b === null) {
552 b = ''
553 }
554
555 if (options.sortStable && a === b) {
556 a = aPosition
557 b = bPosition
558 }
559
560 // If both values are numeric, do a numeric comparison
561 if (this.isNumeric(a) && this.isNumeric(b)) {
562 // Convert numerical values form string to float.
563 a = parseFloat(a)
564 b = parseFloat(b)
565 if (a < b) {
566 return order * -1
567 }
568 if (a > b) {
569 return order
570 }
571 return 0
572 }
573
574 if (options.sortEmptyLast) {
575 if (a === '') {
576 return 1
577 }
578
579 if (b === '') {
580 return -1
581 }
582 }
583
584 if (a === b) {
585 return 0
586 }
587
588 // If value is not a string, convert to string
589 if (typeof a !== 'string') {
590 a = a.toString()
591 }
592
593 if (a.localeCompare(b) === -1) {
594 return order * -1
595 }
596
597 return order
598 },
599
600 getEventName (eventPrefix, id = '') {
601 id = id || `${+new Date()}${~~(Math.random() * 1000000)}`
602 return `${eventPrefix}-${id}`
603 },
604
605 hasDetailViewIcon (options) {
606 return options.detailView && options.detailViewIcon && !options.cardView
607 },
608
609 getDetailViewIndexOffset (options) {
610 return this.hasDetailViewIcon(options) && options.detailViewAlign !== 'right' ? 1 : 0
611 },
612
613 checkAutoMergeCells (data) {
614 for (const row of data) {
615 for (const key of Object.keys(row)) {
616 if (key.startsWith('_') && (key.endsWith('_rowspan') || key.endsWith('_colspan'))) {
617 return true
618 }
619 }
620 }
621 return false
622 },
623
624 deepCopy (arg) {
625 if (arg === undefined) {
626 return arg
627 }
628 return this.extend(true, Array.isArray(arg) ? [] : {}, arg)
629 },
630
631 debounce (func, wait, immediate) {
632 let timeout
633
634 return function executedFunction () {
635 const context = this
636 const args = arguments
637
638 const later = function () {
639 timeout = null
640 if (!immediate) func.apply(context, args)
641 }
642
643 const callNow = immediate && !timeout
644
645 clearTimeout(timeout)
646
647 timeout = setTimeout(later, wait)
648
649 if (callNow) func.apply(context, args)
650 }
651 }
652}