diff --git a/src/bootstrap-table.js b/src/bootstrap-table.js
index 89c90e90a3..73c8c3600f 100644
--- a/src/bootstrap-table.js
+++ b/src/bootstrap-table.js
@@ -1109,6 +1109,10 @@ class BootstrapTable {
if (column && column.searchFormatter) {
value = Utils.calculateObjectValue(column,
this.header.formatters[j], [value, item, i, column.field], value)
+ if (this.header.formatters[j] && typeof value !== 'number') {
+ // search innerText
+ value = $('
').html(value).text()
+ }
}
if (typeof value === 'string' || typeof value === 'number') {
@@ -1510,33 +1514,13 @@ class BootstrapTable {
// eslint-disable-next-line no-unused-vars
initRow (item, i, data, trFragments) {
- const html = []
- let style = {}
- const csses = []
- let data_ = ''
- let attributes = {}
- const htmlAttributes = []
-
if (Utils.findIndex(this.hiddenRows, item) > -1) {
return
}
-
- style = Utils.calculateObjectValue(this.options, this.options.rowStyle, [item, i], style)
-
- if (style && style.css) {
- for (const [key, value] of Object.entries(style.css)) {
- csses.push(`${key}: ${value}`)
- }
- }
-
- attributes = Utils.calculateObjectValue(this.options,
- this.options.rowAttributes, [item, i], attributes)
-
- if (attributes) {
- for (const [key, value] of Object.entries(attributes)) {
- htmlAttributes.push(`${key}="${Utils.escapeHTML(value)}"`)
- }
- }
+ const style = Utils.calculateObjectValue(this.options, this.options.rowStyle, [item, i], {})
+ const attributes = Utils.calculateObjectValue(this.options,
+ this.options.rowAttributes, [item, i], {})
+ const data_ = {}
if (item._data && !Utils.isEmptyObject(item._data)) {
for (const [k, v] of Object.entries(item._data)) {
@@ -1544,61 +1528,46 @@ class BootstrapTable {
if (k === 'index') {
return
}
- data_ += ` data-${k}='${typeof v === 'object' ? JSON.stringify(v) : v}'`
+ data_[`data-${k}`] = typeof v === 'object' ? JSON.stringify(v) : v
}
}
-
- html.push('
'
- )
-
- if (this.options.cardView) {
- html.push(``)
- }
-
+ const tr = Utils.h('tr', {
+ ...attributes,
+ id: Array.isArray(item) ? undefined : item._id,
+ class: style && style.classes || (Array.isArray(item) ? undefined : item._class),
+ style: style && style.css || (Array.isArray(item) ? undefined : item._style),
+ 'data-index': i,
+ 'data-uniqueid': Utils.getItemField(item, this.options.uniqueId, false),
+ 'data-has-detail-view': this.options.detailView &&
+ Utils.calculateObjectValue(null, this.options.detailFilter, [i, item]) ? 'true' : undefined,
+ ...data_
+ })
+ const trChildren = []
let detailViewTemplate = ''
if (Utils.hasDetailViewIcon(this.options)) {
- detailViewTemplate = ' '
+ detailViewTemplate = Utils.h('td')
if (Utils.calculateObjectValue(null, this.options.detailFilter, [i, item])) {
- detailViewTemplate += `
-
- ${Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, this.options.icons.detailOpen)}
-
- `
+ detailViewTemplate.append(Utils.h('a', {
+ class: 'detail-icon',
+ href: '#',
+ html: Utils.sprintf(this.constants.html.icon, this.options.iconsPrefix, this.options.icons.detailOpen)
+ }))
}
-
- detailViewTemplate += ' | '
}
if (detailViewTemplate && this.options.detailViewAlign !== 'right') {
- html.push(detailViewTemplate)
+ trChildren.push(detailViewTemplate)
}
- this.header.fields.forEach((field, j) => {
+ const tds = this.header.fields.map((field, j) => {
const column = this.columns[j]
- let text = ''
const value_ = Utils.getItemField(item, field, this.options.escape, column.escape)
let value = ''
- let type = ''
- let cellStyle = {}
- let id_ = ''
- let class_ = this.header.classes[j]
- let style_ = ''
- let styleToAdd_ = ''
- let data_ = ''
- let rowspan_ = ''
- let colspan_ = ''
- let title_ = ''
+ const attrs = {
+ style: []
+ }
if ((this.fromHtml || this.autoMergeCells) && typeof value_ === 'undefined') {
if (!column.checkbox && !column.radio) {
@@ -1614,47 +1583,21 @@ class BootstrapTable {
return
}
- // Style concat
- if (csses.concat([this.header.styles[j]]).length) {
- styleToAdd_ += `${csses.concat([this.header.styles[j]]).join('; ')}`
- }
- if (item[`_${field}_style`]) {
- styleToAdd_ += `${item[`_${field}_style`]}`
+ // handle id and class of td
+ for (const item of ['id', 'class', 'rowspan', 'colspan', 'title']) {
+ attrs[item] = item[`_${field}_${item}`] || undefined
}
- if (styleToAdd_) {
- style_ = ` style="${styleToAdd_}"`
- }
- // Style concat
+ attrs.style.push(this.header.styles[j], item[`_${field}_style`])
+ const cellStyle = Utils.calculateObjectValue(this.header,
+ this.header.cellStyles[j], [value_, item, i, field], {})
- // handle id and class of td
- if (item[`_${field}_id`]) {
- id_ = Utils.sprintf(' id="%s"', item[`_${field}_id`])
- }
- if (item[`_${field}_class`]) {
- class_ = Utils.sprintf(' class="%s"', item[`_${field}_class`])
- }
- if (item[`_${field}_rowspan`]) {
- rowspan_ = Utils.sprintf(' rowspan="%s"', item[`_${field}_rowspan`])
- }
- if (item[`_${field}_colspan`]) {
- colspan_ = Utils.sprintf(' colspan="%s"', item[`_${field}_colspan`])
- }
- if (item[`_${field}_title`]) {
- title_ = Utils.sprintf(' title="%s"', item[`_${field}_title`])
- }
- cellStyle = Utils.calculateObjectValue(this.header,
- this.header.cellStyles[j], [value_, item, i, field], cellStyle)
if (cellStyle.classes) {
- class_ = ` class="${cellStyle.classes}"`
+ attrs.class = attrs.class || []
+ attrs.class.push(cellStyle.classes)
}
if (cellStyle.css) {
- const csses_ = []
-
- for (const [key, value] of Object.entries(cellStyle.css)) {
- csses_.push(`${key}: ${value}`)
- }
- style_ = ` style="${csses_.concat(this.header.styles[j]).join('; ')}"`
+ attrs.style.push(cellStyle.css)
}
value = Utils.calculateObjectValue(column,
@@ -1673,7 +1616,7 @@ class BootstrapTable {
) {
let searchText = this.searchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
- if (this.options.searchAccentNeutralise) {
+ if (this.options.searchAccentNeutralise && typeof value === 'string') {
const indexRegex = new RegExp(`${Utils.normalizeAccent(searchText)}`, 'gmi')
const match = indexRegex.exec(Utils.normalizeAccent(value))
@@ -1694,64 +1637,80 @@ class BootstrapTable {
if (k === 'index') {
return
}
- data_ += ` data-${k}="${v}"`
+ attrs[`data-${k}`] = v
}
}
if (column.checkbox || column.radio) {
- type = column.checkbox ? 'checkbox' : type
- type = column.radio ? 'radio' : type
-
- const c = column['class'] || ''
+ const type = column.checkbox ? 'checkbox' : 'radio'
const isChecked = Utils.isObject(value) && value.hasOwnProperty('checked') ?
value.checked : (value === true || value_) && value !== false
const isDisabled = !column.checkboxEnabled || value && value.disabled
-
- text = [
- this.options.cardView ?
- ` ` :
- ` `,
- ``,
- this.header.formatters[j] && typeof value === 'string' ? value : '',
- this.options.cardView ? '' : ' | '
- ].join('')
+ const valueNodes = this.header.formatters[j] && (
+ typeof value === 'string' || value instanceof Node || value instanceof $) ? Utils.htmlToNodes(value) : []
item[this.header.stateField] = value === true || (!!value_ || value && value.checked)
- } else if (this.options.cardView) {
- const cardTitle = this.options.showHeader ?
- ` ${Utils.getFieldTitle(this.columns, field)}` : ''
- text = ` ${cardTitle}${value} `
+ return Utils.h(this.options.cardView ? 'div' : 'td', {
+ class: [this.options.cardView ? 'card-view' : 'bs-checkbox', column.class],
+ style: this.options.cardView ? undefined : attrs.style
+ }, [
+ Utils.h('label', {}, [
+ Utils.h('input', {
+ 'data-index': i,
+ name: this.options.selectItemName,
+ type,
+ value: item[this.options.idField],
+ checked: isChecked ? 'checked' : undefined,
+ disabled: isDisabled ? 'disabled' : undefined
+ }),
+ Utils.h('span')
+ ]),
+ ...valueNodes
+ ])
+ }
+ if (this.options.cardView) {
if (this.options.smartDisplay && value === '') {
- text = ' '
+ return Utils.h('div', { class: 'card-view' })
}
- } else {
- text = ` ${value} | `
+
+ const cardTitle = this.options.showHeader ?
+ Utils.h('span', {
+ class: ['card-view-title', cellStyle.classes],
+ style: attrs.style,
+ html: Utils.getFieldTitle(this.columns, field)
+ }) : ''
+
+ return Utils.h('div', { class: 'card-view' }, [
+ cardTitle,
+ Utils.h('span', {
+ class: ['card-view-value', cellStyle.classes],
+ style: attrs.style
+ }, [...Utils.htmlToNodes(value)])
+ ])
}
- html.push(text)
- })
+ return Utils.h('td', attrs, [...Utils.htmlToNodes(value)])
+ }).filter(x => x)
+
+ trChildren.push(...tds)
if (detailViewTemplate && this.options.detailViewAlign === 'right') {
- html.push(detailViewTemplate)
+ trChildren.push(detailViewTemplate)
}
if (this.options.cardView) {
- html.push(' | ')
+ tr.append(Utils.h('td', {
+ colspan: this.header.fields.length
+ }, [
+ Utils.h('div', { class: 'card-views' }, trChildren)
+ ]))
+ } else {
+ tr.append(...trChildren)
}
- html.push('
')
- return html.join('')
+ return tr
}
initBody (fixedScroll, updatedUid) {
@@ -1779,12 +1738,13 @@ class BootstrapTable {
for (let i = this.pageFrom - 1; i < this.pageTo; i++) {
const item = data[i]
- let tr = this.initRow(item, i, data, trFragments)
+ const tr = this.initRow(item, i, data, trFragments)
hasTr = hasTr || !!tr
- if (tr && typeof tr === 'string') {
+ if (tr && tr instanceof Node) {
const uniqueId = this.options.uniqueId
+ const toAppend = [tr]
if (uniqueId && item.hasOwnProperty(uniqueId)) {
const itemUniqueId = item[uniqueId]
@@ -1797,15 +1757,15 @@ class BootstrapTable {
toExpand.push(i)
if (!updatedUid || itemUniqueId !== updatedUid) {
- tr += oldTrNext[0].outerHTML
+ toAppend.push(oldTrNext[0])
}
}
}
if (!this.options.virtualScroll) {
- trFragments.append(tr)
+ trFragments.append(toAppend)
} else {
- rows.push(tr)
+ rows.push($('
').html(toAppend).html())
}
}
}
@@ -2291,7 +2251,10 @@ class BootstrapTable {
let detailTemplate = ''
if (Utils.hasDetailViewIcon(this.options)) {
- detailTemplate = '
| '
+ detailTemplate = Utils.h('th', { class: 'detail' }, [
+ Utils.h('div', { class: 'th-inner' }),
+ Utils.h('div', { class: 'fht-cell' })
+ ])
}
if (detailTemplate && this.options.detailViewAlign !== 'right') {
@@ -2299,15 +2262,11 @@ class BootstrapTable {
}
for (const column of this.columns) {
- let falign = ''
- let valign = ''
- const csses = []
- let style = {}
- let class_ = Utils.sprintf(' class="%s"', column['class'])
+ const hasData = this.footerData && this.footerData.length > 0
if (
!column.visible ||
- this.footerData && this.footerData.length > 0 && !(column.field in this.footerData[0])
+ hasData && !(column.field in this.footerData[0])
) {
continue
}
@@ -2316,46 +2275,28 @@ class BootstrapTable {
return
}
- falign = Utils.sprintf('text-align: %s; ', column.falign ? column.falign : column.align)
- valign = Utils.sprintf('vertical-align: %s; ', column.valign)
+ const style = Utils.calculateObjectValue(null, column.footerStyle || this.options.footerStyle, [column])
+ const csses = style && style.css || {}
+ const colspan = hasData && this.footerData[0][`_${column.field}_colspan`] || 0
+ let value = hasData && this.footerData[0][column.field] || ''
- style = Utils.calculateObjectValue(null, column.footerStyle || this.options.footerStyle, [column])
-
- if (style && style.css) {
- for (const [key, value] of Object.entries(style.css)) {
- csses.push(`${key}: ${value}`)
- }
- }
- if (style && style.classes) {
- class_ = Utils.sprintf(' class="%s"', column['class'] ?
- [column['class'], style.classes].join(' ') : style.classes)
- }
-
- html.push('
0) {
- colspan = this.footerData[0][`_${column.field}_colspan`] || 0
- }
- if (colspan) {
- html.push(` colspan="${colspan}" `)
- }
-
- html.push('>')
- html.push(' ')
-
- let value = ''
-
- if (this.footerData && this.footerData.length > 0) {
- value = this.footerData[0][column.field] || ''
- }
- html.push(Utils.calculateObjectValue(column, column.footerFormatter,
- [data, value], value))
+ value = Utils.calculateObjectValue(column, column.footerFormatter,
+ [data, value], value)
- html.push(' ')
- html.push('')
- html.push('')
- html.push(' | ')
+ html.push(Utils.h('th', {
+ class: [column['class'], style && style.classes],
+ style: {
+ 'text-align': column.falign ? column.falign : column.align,
+ 'vertical-align': column.valign,
+ ...csses
+ },
+ colspan: colspan || undefined
+ }, [
+ Utils.h('div', {
+ class: 'th-inner'
+ }, [...Utils.htmlToNodes(value)]),
+ Utils.h('div', { class: 'fht-cell' })
+ ]))
}
if (detailTemplate && this.options.detailViewAlign === 'right') {
@@ -2371,7 +2312,7 @@ class BootstrapTable {
this.$tableFooter.html('
')
}
- this.$tableFooter.find('tr').html(html.join(''))
+ this.$tableFooter.find('tr').html(html)
this.trigger('post-footer', this.$tableFooter)
}
diff --git a/src/extensions/print/bootstrap-table-print.js b/src/extensions/print/bootstrap-table-print.js
index ea7c73ae63..646d908b90 100644
--- a/src/extensions/print/bootstrap-table-print.js
+++ b/src/extensions/print/bootstrap-table-print.js
@@ -148,7 +148,7 @@ $.BootstrapTable = class extends $.BootstrapTable {
[value_, row, i], value_)
return typeof value === 'undefined' || value === null ?
- this.options.undefinedText : value
+ this.options.undefinedText : $('
').html(value).html()
}
const buildTable = (data, columnsArray) => {
diff --git a/src/extensions/toolbar/bootstrap-table-toolbar.js b/src/extensions/toolbar/bootstrap-table-toolbar.js
index 37c78735ab..a5fcd0bfd7 100644
--- a/src/extensions/toolbar/bootstrap-table-toolbar.js
+++ b/src/extensions/toolbar/bootstrap-table-toolbar.js
@@ -350,6 +350,10 @@ $.BootstrapTable = class extends $.BootstrapTable {
value = Utils.calculateObjectValue(this.header,
this.header.formatters[index], [value, item, i], value)
+ if (this.header.formatters[index]) {
+ // search innerText
+ value = $('
').html(value).text()
+ }
if (
!(index !== -1 &&
diff --git a/src/utils/index.js b/src/utils/index.js
index c3b4727590..018bfa26f4 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -668,24 +668,161 @@ export default {
},
replaceSearchMark (html, searchText) {
- const node = document.createElement('div')
- const replaceMark = (node, searchText) => {
- const regExp = new RegExp(searchText, 'gim')
+ const isDom = html instanceof Element
+ const node = isDom ? html : document.createElement('div')
+ const regExp = new RegExp(searchText, 'gim')
+ const replaceTextWithDom = (text, regExp) => {
+ const result = []
+ let match
+ let lastIndex = 0
+
+ while ((match = regExp.exec(text)) !== null) {
+ if (lastIndex !== match.index) {
+ result.push(document.createTextNode(text.substring(lastIndex, match.index)))
+ }
+ const mark = document.createElement('mark')
+
+ mark.innerText = match[0]
+ result.push(mark)
+ lastIndex = match.index + match[0].length
+ }
+ if (!result.length) {
+ // no match
+ return
+ }
+ if (lastIndex !== text.length) {
+ result.push(document.createTextNode(text.substring(lastIndex)))
+ }
+ return result
+ }
+ const replaceMark = node => {
+ for (let i = 0; i < node.childNodes.length; i++) {
+ const child = node.childNodes[i]
- for (const child of node.childNodes) {
if (child.nodeType === document.TEXT_NODE) {
- child.data = child.data.replace(regExp, match => `___${match}___`)
+ const elements = replaceTextWithDom(child.data, regExp)
+
+ if (elements) {
+ for (const el of elements) {
+ node.insertBefore(el, child)
+ }
+ node.removeChild(child)
+ i += elements.length - 1
+ }
}
if (child.nodeType === document.ELEMENT_NODE) {
- replaceMark(child, searchText)
+ replaceMark(child)
+ }
+ }
+ }
+
+ if (!isDom) {
+ node.innerHTML = html
+ }
+ replaceMark(node)
+ return isDom ? node : node.innerHTML
+ },
+
+ classToString (class_) {
+ if (typeof class_ === 'string') {
+ return class_
+ }
+ if (Array.isArray(class_)) {
+ return class_.map(x => this.classToString(x)).filter(x => x).join(' ')
+ }
+ if (class_ && typeof class_ === 'object') {
+ return Object.entries(class_).map(([k, v]) => v ? k : '').filter(x => x).join(' ')
+ }
+ return ''
+ },
+
+ parseStyle (dom, style) {
+ if (!style) {
+ return dom
+ }
+ if (typeof style === 'string') {
+ style.split(';').forEach(i => {
+ const index = i.indexOf(':')
+
+ if (index > 0) {
+ const k = i.substring(0, index).trim()
+ const v = i.substring(index + 1).trim()
+
+ dom.style.setProperty(k, v)
+ }
+ })
+ } else if (Array.isArray(style)) {
+ for (const item of style) {
+ this.parseStyle(item)
+ }
+ } else if (typeof style === 'object') {
+ for (const [k, v] of Object.entries(style)) {
+ dom.style.setProperty(k, v)
+ }
+ }
+ return dom
+ },
+
+ h (element, attrs, children) {
+ const el = element instanceof HTMLElement ? element : document.createElement(element)
+ const _attrs = attrs || {}
+ const _children = children || []
+
+ // default attributes
+ if (el.tagName === 'A') {
+ el.href = 'javascript:'
+ }
+
+ for (const [k, v] of Object.entries(_attrs)) {
+ if (v === undefined) {
+ continue
+ }
+ if (['text', 'innerText'].includes(k)) {
+ el.innerText = v
+ } else if (['html', 'innerHTML'].includes(k)) {
+ el.innerHTML = v
+ } else if (k === 'children') {
+ _children.push(...v)
+ } else if (k === 'class') {
+ el.setAttribute('class', this.classToString(v))
+ } else if (k === 'style') {
+ if (typeof v === 'string') {
+ el.setAttribute('style', v)
+ } else {
+ this.parseStyle(el, v)
}
+ } else if (k.startsWith('@') || k.startsWith('on')) {
+ // event handlers
+ const event = k.startsWith('@') ? k.substring(1) : k.substring(2).toLowerCase()
+ const args = Array.isArray(v) ? v : [v]
+
+ el.addEventListener(event, ...args)
+ } else if (k.startsWith('.')) {
+ // set property
+ el[k.substring(1)] = v
+ } else {
+ el.setAttribute(k, v)
}
}
+ if (_children.length) {
+ el.append(..._children)
+ }
+ return el
+ },
- node.innerHTML = html
- replaceMark(node, searchText)
+ htmlToNodes (html) {
+ if (html instanceof $) {
+ return html.get()
+ }
+ if (html instanceof Node) {
+ return [html]
+ }
+ if (typeof html !== 'string') {
+ html = new String(html).toString()
+ }
+ const d = document.createElement('div')
- return node.innerHTML.replace(new RegExp(`___${searchText}___`, 'gim'),
- match => `${match.slice(3, -3)}`)
+ d.innerHTML = html
+ return d.childNodes
}
}