index.js 15 KB

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