index.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  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. clearSearch: 'glyphicon-trash',
  41. columns: 'glyphicon-th icon-th',
  42. detailClose: 'glyphicon-minus icon-minus',
  43. detailOpen: 'glyphicon-plus icon-plus',
  44. fullscreen: 'glyphicon-fullscreen',
  45. paginationSwitchDown: 'glyphicon-collapse-down icon-chevron-down',
  46. paginationSwitchUp: 'glyphicon-collapse-up icon-chevron-up',
  47. refresh: 'glyphicon-refresh icon-refresh',
  48. search: 'glyphicon-search',
  49. toggleOff: 'glyphicon-list-alt icon-list-alt',
  50. toggleOn: 'glyphicon-list-alt icon-list-alt'
  51. },
  52. fa: {
  53. clearSearch: 'fa-trash',
  54. columns: 'fa-th-list',
  55. detailClose: 'fa-minus',
  56. detailOpen: 'fa-plus',
  57. fullscreen: 'fa-arrows-alt',
  58. paginationSwitchDown: 'fa-caret-square-down',
  59. paginationSwitchUp: 'fa-caret-square-up',
  60. refresh: 'fa-sync',
  61. search: 'fa-search',
  62. toggleOff: 'fa-toggle-off',
  63. toggleOn: 'fa-toggle-on'
  64. },
  65. bi: {
  66. clearSearch: 'bi-trash',
  67. columns: 'bi-list-ul',
  68. detailClose: 'bi-dash',
  69. detailOpen: 'bi-plus',
  70. fullscreen: 'bi-arrows-move',
  71. paginationSwitchDown: 'bi-caret-down-square',
  72. paginationSwitchUp: 'bi-caret-up-square',
  73. refresh: 'bi-arrow-clockwise',
  74. search: 'bi-search',
  75. toggleOff: 'bi-toggle-off',
  76. toggleOn: 'bi-toggle-on'
  77. },
  78. icon: {
  79. clearSearch: 'icon-trash-2',
  80. columns: 'icon-list',
  81. detailClose: 'icon-minus',
  82. detailOpen: 'icon-plus',
  83. fullscreen: 'icon-maximize',
  84. paginationSwitchDown: 'icon-arrow-up-circle',
  85. paginationSwitchUp: 'icon-arrow-down-circle',
  86. refresh: 'icon-refresh-cw',
  87. search: 'icon-search',
  88. toggleOff: 'icon-toggle-right',
  89. toggleOn: 'icon-toggle-right'
  90. },
  91. 'material-icons': {
  92. clearSearch: 'delete',
  93. columns: 'view_list',
  94. detailClose: 'remove',
  95. detailOpen: 'add',
  96. fullscreen: 'fullscreen',
  97. paginationSwitchDown: 'grid_on',
  98. paginationSwitchUp: 'grid_off',
  99. refresh: 'refresh',
  100. search: 'search',
  101. sort: 'sort',
  102. toggleOff: 'tablet',
  103. toggleOn: 'tablet_android'
  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. if (typeof obj !== 'object' || obj === null) {
  188. return false
  189. }
  190. let proto = obj
  191. while (Object.getPrototypeOf(proto) !== null) {
  192. proto = Object.getPrototypeOf(proto)
  193. }
  194. return Object.getPrototypeOf(obj) === proto
  195. },
  196. isEmptyObject (obj = {}) {
  197. return Object.entries(obj).length === 0 && obj.constructor === Object
  198. },
  199. isNumeric (n) {
  200. return !isNaN(parseFloat(n)) && isFinite(n)
  201. },
  202. getFieldTitle (list, value) {
  203. for (const item of list) {
  204. if (item.field === value) {
  205. return item.title
  206. }
  207. }
  208. return ''
  209. },
  210. setFieldIndex (columns) {
  211. let totalCol = 0
  212. const flag = []
  213. for (const column of columns[0]) {
  214. totalCol += column.colspan || 1
  215. }
  216. for (let i = 0; i < columns.length; i++) {
  217. flag[i] = []
  218. for (let j = 0; j < totalCol; j++) {
  219. flag[i][j] = false
  220. }
  221. }
  222. for (let i = 0; i < columns.length; i++) {
  223. for (const r of columns[i]) {
  224. const rowspan = r.rowspan || 1
  225. const colspan = r.colspan || 1
  226. const index = flag[i].indexOf(false)
  227. r.colspanIndex = index
  228. if (colspan === 1) {
  229. r.fieldIndex = index
  230. // when field is undefined, use index instead
  231. if (typeof r.field === 'undefined') {
  232. r.field = index
  233. }
  234. } else {
  235. r.colspanGroup = r.colspan
  236. }
  237. for (let j = 0; j < rowspan; j++) {
  238. for (let k = 0; k < colspan; k++) {
  239. flag[i + j][index + k] = true
  240. }
  241. }
  242. }
  243. }
  244. },
  245. normalizeAccent (value) {
  246. if (typeof value !== 'string') {
  247. return value
  248. }
  249. return value.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
  250. },
  251. updateFieldGroup (columns, fieldColumns) {
  252. const allColumns = [].concat(...columns)
  253. for (const c of columns) {
  254. for (const r of c) {
  255. if (r.colspanGroup > 1) {
  256. let colspan = 0
  257. for (let i = r.colspanIndex; i < r.colspanIndex + r.colspanGroup; i++) {
  258. const underColumns = allColumns.filter(col => col.fieldIndex === i)
  259. const column = underColumns[underColumns.length - 1]
  260. if (underColumns.length > 1) {
  261. for (let j = 0; j < underColumns.length - 1; j++) {
  262. underColumns[j].visible = column.visible
  263. }
  264. }
  265. if (column.visible) {
  266. colspan++
  267. }
  268. }
  269. r.colspan = colspan
  270. r.visible = colspan > 0
  271. }
  272. }
  273. }
  274. if (columns.length < 2) {
  275. return
  276. }
  277. for (const column of fieldColumns) {
  278. const sameColumns = allColumns.filter(col => col.fieldIndex === column.fieldIndex)
  279. if (sameColumns.length > 1) {
  280. for (const c of sameColumns) {
  281. c.visible = column.visible
  282. }
  283. }
  284. }
  285. },
  286. getScrollBarWidth () {
  287. if (this.cachedWidth === undefined) {
  288. const $inner = $('<div/>').addClass('fixed-table-scroll-inner')
  289. const $outer = $('<div/>').addClass('fixed-table-scroll-outer')
  290. $outer.append($inner)
  291. $('body').append($outer)
  292. const w1 = $inner[0].offsetWidth
  293. $outer.css('overflow', 'scroll')
  294. let w2 = $inner[0].offsetWidth
  295. if (w1 === w2) {
  296. w2 = $outer[0].clientWidth
  297. }
  298. $outer.remove()
  299. this.cachedWidth = w1 - w2
  300. }
  301. return this.cachedWidth
  302. },
  303. calculateObjectValue (self, name, args, defaultValue) {
  304. let func = name
  305. if (typeof name === 'string') {
  306. // support obj.func1.func2
  307. const names = name.split('.')
  308. if (names.length > 1) {
  309. func = window
  310. for (const f of names) {
  311. func = func[f]
  312. }
  313. } else {
  314. func = window[name]
  315. }
  316. }
  317. if (func !== null && typeof func === 'object') {
  318. return func
  319. }
  320. if (typeof func === 'function') {
  321. return func.apply(self, args || [])
  322. }
  323. if (
  324. !func &&
  325. typeof name === 'string' &&
  326. args &&
  327. this.sprintf(name, ...args)
  328. ) {
  329. return this.sprintf(name, ...args)
  330. }
  331. return defaultValue
  332. },
  333. compareObjects (objectA, objectB, compareLength) {
  334. const aKeys = Object.keys(objectA)
  335. const bKeys = Object.keys(objectB)
  336. if (compareLength && aKeys.length !== bKeys.length) {
  337. return false
  338. }
  339. for (const key of aKeys) {
  340. if (bKeys.includes(key) && objectA[key] !== objectB[key]) {
  341. return false
  342. }
  343. }
  344. return true
  345. },
  346. regexCompare (value, search) {
  347. try {
  348. const regexpParts = search.match(/^\/(.*?)\/([gim]*)$/)
  349. if (value.toString().search(regexpParts ? new RegExp(regexpParts[1], regexpParts[2]) : new RegExp(search, 'gim')) !== -1) {
  350. return true
  351. }
  352. } catch (e) {
  353. return false
  354. }
  355. return false
  356. },
  357. escapeApostrophe (value) {
  358. return value.toString()
  359. .replace(/'/g, '&#39;')
  360. },
  361. escapeHTML (text) {
  362. if (!text) {
  363. return text
  364. }
  365. return text.toString()
  366. .replace(/&/g, '&amp;')
  367. .replace(/</g, '&lt;')
  368. .replace(/>/g, '&gt;')
  369. .replace(/"/g, '&quot;')
  370. .replace(/'/g, '&#39;')
  371. },
  372. unescapeHTML (text) {
  373. if (typeof text !== 'string' || !text) {
  374. return text
  375. }
  376. return text.toString()
  377. .replace(/&amp;/g, '&')
  378. .replace(/&lt;/g, '<')
  379. .replace(/&gt;/g, '>')
  380. .replace(/&quot;/g, '"')
  381. .replace(/&#39;/g, '\'')
  382. },
  383. removeHTML (text) {
  384. if (!text) {
  385. return text
  386. }
  387. return text.toString()
  388. .replace(/(<([^>]+)>)/ig, '')
  389. .replace(/&[#A-Za-z0-9]+;/gi, '')
  390. .trim()
  391. },
  392. getRealDataAttr (dataAttr) {
  393. for (const [attr, value] of Object.entries(dataAttr)) {
  394. const auxAttr = attr.split(/(?=[A-Z])/).join('-').toLowerCase()
  395. if (auxAttr !== attr) {
  396. dataAttr[auxAttr] = value
  397. delete dataAttr[attr]
  398. }
  399. }
  400. return dataAttr
  401. },
  402. getItemField (item, field, escape, columnEscape = undefined) {
  403. let value = item
  404. // use column escape if it is defined
  405. if (typeof columnEscape !== 'undefined') {
  406. escape = columnEscape
  407. }
  408. if (typeof field !== 'string' || item.hasOwnProperty(field)) {
  409. return escape ? this.escapeHTML(item[field]) : item[field]
  410. }
  411. const props = field.split('.')
  412. for (const p of props) {
  413. value = value && value[p]
  414. }
  415. return escape ? this.escapeHTML(value) : value
  416. },
  417. isIEBrowser () {
  418. return navigator.userAgent.includes('MSIE ') ||
  419. /Trident.*rv:11\./.test(navigator.userAgent)
  420. },
  421. findIndex (items, item) {
  422. for (const it of items) {
  423. if (JSON.stringify(it) === JSON.stringify(item)) {
  424. return items.indexOf(it)
  425. }
  426. }
  427. return -1
  428. },
  429. trToData (columns, $els) {
  430. const data = []
  431. const m = []
  432. $els.each((y, el) => {
  433. const $el = $(el)
  434. const row = {}
  435. // save tr's id, class and data-* attributes
  436. row._id = $el.attr('id')
  437. row._class = $el.attr('class')
  438. row._data = this.getRealDataAttr($el.data())
  439. row._style = $el.attr('style')
  440. $el.find('>td,>th').each((_x, el) => {
  441. const $el = $(el)
  442. const colspan = +$el.attr('colspan') || 1
  443. const rowspan = +$el.attr('rowspan') || 1
  444. let x = _x
  445. // skip already occupied cells in current row
  446. for (; m[y] && m[y][x]; x++) {
  447. // ignore
  448. }
  449. // mark matrix elements occupied by current cell with true
  450. for (let tx = x; tx < x + colspan; tx++) {
  451. for (let ty = y; ty < y + rowspan; ty++) {
  452. if (!m[ty]) { // fill missing rows
  453. m[ty] = []
  454. }
  455. m[ty][tx] = true
  456. }
  457. }
  458. const field = columns[x].field
  459. row[field] = this.escapeApostrophe($el.html().trim())
  460. // save td's id, class and data-* attributes
  461. row[`_${field}_id`] = $el.attr('id')
  462. row[`_${field}_class`] = $el.attr('class')
  463. row[`_${field}_rowspan`] = $el.attr('rowspan')
  464. row[`_${field}_colspan`] = $el.attr('colspan')
  465. row[`_${field}_title`] = $el.attr('title')
  466. row[`_${field}_data`] = this.getRealDataAttr($el.data())
  467. row[`_${field}_style`] = $el.attr('style')
  468. })
  469. data.push(row)
  470. })
  471. return data
  472. },
  473. sort (a, b, order, options, aPosition, bPosition) {
  474. if (a === undefined || a === null) {
  475. a = ''
  476. }
  477. if (b === undefined || b === null) {
  478. b = ''
  479. }
  480. if (options.sortStable && a === b) {
  481. a = aPosition
  482. b = bPosition
  483. }
  484. // If both values are numeric, do a numeric comparison
  485. if (this.isNumeric(a) && this.isNumeric(b)) {
  486. // Convert numerical values form string to float.
  487. a = parseFloat(a)
  488. b = parseFloat(b)
  489. if (a < b) {
  490. return order * -1
  491. }
  492. if (a > b) {
  493. return order
  494. }
  495. return 0
  496. }
  497. if (options.sortEmptyLast) {
  498. if (a === '') {
  499. return 1
  500. }
  501. if (b === '') {
  502. return -1
  503. }
  504. }
  505. if (a === b) {
  506. return 0
  507. }
  508. // If value is not a string, convert to string
  509. if (typeof a !== 'string') {
  510. a = a.toString()
  511. }
  512. if (a.localeCompare(b) === -1) {
  513. return order * -1
  514. }
  515. return order
  516. },
  517. getEventName (eventPrefix, id = '') {
  518. id = id || `${+new Date()}${~~(Math.random() * 1000000)}`
  519. return `${eventPrefix}-${id}`
  520. },
  521. hasDetailViewIcon (options) {
  522. return options.detailView && options.detailViewIcon && !options.cardView
  523. },
  524. getDetailViewIndexOffset (options) {
  525. return this.hasDetailViewIcon(options) && options.detailViewAlign !== 'right' ? 1 : 0
  526. },
  527. checkAutoMergeCells (data) {
  528. for (const row of data) {
  529. for (const key of Object.keys(row)) {
  530. if (key.startsWith('_') && (key.endsWith('_rowspan') || key.endsWith('_colspan'))) {
  531. return true
  532. }
  533. }
  534. }
  535. return false
  536. },
  537. deepCopy (arg) {
  538. if (arg === undefined) {
  539. return arg
  540. }
  541. return this.extend(true, Array.isArray(arg) ? [] : {}, arg)
  542. },
  543. debounce (func, wait, immediate) {
  544. let timeout
  545. return function executedFunction () {
  546. const context = this
  547. const args = arguments
  548. const later = function () {
  549. timeout = null
  550. if (!immediate) func.apply(context, args)
  551. }
  552. const callNow = immediate && !timeout
  553. clearTimeout(timeout)
  554. timeout = setTimeout(later, wait)
  555. if (callNow) func.apply(context, args)
  556. }
  557. },
  558. replaceSearchMark (html, searchText) {
  559. const isDom = html instanceof Element
  560. const node = isDom ? html : document.createElement('div')
  561. const regExp = new RegExp(searchText, 'gim')
  562. const replaceTextWithDom = (text, regExp) => {
  563. const result = []
  564. let match
  565. let lastIndex = 0
  566. while ((match = regExp.exec(text)) !== null) {
  567. if (lastIndex !== match.index) {
  568. result.push(document.createTextNode(text.substring(lastIndex, match.index)))
  569. }
  570. const mark = document.createElement('mark')
  571. mark.innerText = match[0]
  572. result.push(mark)
  573. lastIndex = match.index + match[0].length
  574. }
  575. if (!result.length) {
  576. // no match
  577. return
  578. }
  579. if (lastIndex !== text.length) {
  580. result.push(document.createTextNode(text.substring(lastIndex)))
  581. }
  582. return result
  583. }
  584. const replaceMark = node => {
  585. for (let i = 0; i < node.childNodes.length; i++) {
  586. const child = node.childNodes[i]
  587. if (child.nodeType === document.TEXT_NODE) {
  588. const elements = replaceTextWithDom(child.data, regExp)
  589. if (elements) {
  590. for (const el of elements) {
  591. node.insertBefore(el, child)
  592. }
  593. node.removeChild(child)
  594. i += elements.length - 1
  595. }
  596. }
  597. if (child.nodeType === document.ELEMENT_NODE) {
  598. replaceMark(child)
  599. }
  600. }
  601. }
  602. if (!isDom) {
  603. node.innerHTML = html
  604. }
  605. replaceMark(node)
  606. return isDom ? node : node.innerHTML
  607. },
  608. classToString (class_) {
  609. if (typeof class_ === 'string') {
  610. return class_
  611. }
  612. if (Array.isArray(class_)) {
  613. return class_.map(x => this.classToString(x)).filter(x => x).join(' ')
  614. }
  615. if (class_ && typeof class_ === 'object') {
  616. return Object.entries(class_).map(([k, v]) => v ? k : '').filter(x => x).join(' ')
  617. }
  618. return ''
  619. },
  620. parseStyle (dom, style) {
  621. if (!style) {
  622. return dom
  623. }
  624. if (typeof style === 'string') {
  625. style.split(';').forEach(i => {
  626. const index = i.indexOf(':')
  627. if (index > 0) {
  628. const k = i.substring(0, index).trim()
  629. const v = i.substring(index + 1).trim()
  630. dom.style.setProperty(k, v)
  631. }
  632. })
  633. } else if (Array.isArray(style)) {
  634. for (const item of style) {
  635. this.parseStyle(item)
  636. }
  637. } else if (typeof style === 'object') {
  638. for (const [k, v] of Object.entries(style)) {
  639. dom.style.setProperty(k, v)
  640. }
  641. }
  642. return dom
  643. },
  644. h (element, attrs, children) {
  645. const el = element instanceof HTMLElement ? element : document.createElement(element)
  646. const _attrs = attrs || {}
  647. const _children = children || []
  648. // default attributes
  649. if (el.tagName === 'A') {
  650. el.href = 'javascript:'
  651. }
  652. for (const [k, v] of Object.entries(_attrs)) {
  653. if (v === undefined) {
  654. continue
  655. }
  656. if (['text', 'innerText'].includes(k)) {
  657. el.innerText = v
  658. } else if (['html', 'innerHTML'].includes(k)) {
  659. el.innerHTML = v
  660. } else if (k === 'children') {
  661. _children.push(...v)
  662. } else if (k === 'class') {
  663. el.setAttribute('class', this.classToString(v))
  664. } else if (k === 'style') {
  665. if (typeof v === 'string') {
  666. el.setAttribute('style', v)
  667. } else {
  668. this.parseStyle(el, v)
  669. }
  670. } else if (k.startsWith('@') || k.startsWith('on')) {
  671. // event handlers
  672. const event = k.startsWith('@') ? k.substring(1) : k.substring(2).toLowerCase()
  673. const args = Array.isArray(v) ? v : [v]
  674. el.addEventListener(event, ...args)
  675. } else if (k.startsWith('.')) {
  676. // set property
  677. el[k.substring(1)] = v
  678. } else {
  679. el.setAttribute(k, v)
  680. }
  681. }
  682. if (_children.length) {
  683. el.append(..._children)
  684. }
  685. return el
  686. },
  687. htmlToNodes (html) {
  688. if (html instanceof $) {
  689. return html.get()
  690. }
  691. if (html instanceof Node) {
  692. return [html]
  693. }
  694. if (typeof html !== 'string') {
  695. html = new String(html).toString()
  696. }
  697. const d = document.createElement('div')
  698. d.innerHTML = html
  699. return d.childNodes
  700. }
  701. }