bootstrap-table-group-by.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /**
  2. * @author: Yura Knoxville
  3. * @version: v1.1.0
  4. */
  5. const Utils = $.fn.bootstrapTable.utils
  6. let initBodyCaller
  7. const groupBy = (array, f) => {
  8. const tmpGroups = {}
  9. array.forEach(o => {
  10. const groups = f(o)
  11. tmpGroups[groups] = tmpGroups[groups] || []
  12. tmpGroups[groups].push(o)
  13. })
  14. return tmpGroups
  15. }
  16. Object.assign($.fn.bootstrapTable.defaults.icons, {
  17. collapseGroup: {
  18. bootstrap3: 'glyphicon-chevron-up',
  19. bootstrap5: 'bi-chevron-up',
  20. materialize: 'arrow_drop_down'
  21. }[$.fn.bootstrapTable.theme] || 'fa-angle-up',
  22. expandGroup: {
  23. bootstrap3: 'glyphicon-chevron-down',
  24. bootstrap5: 'bi-chevron-down',
  25. materialize: 'arrow_drop_up'
  26. }[$.fn.bootstrapTable.theme] || 'fa-angle-down'
  27. })
  28. Object.assign($.fn.bootstrapTable.defaults, {
  29. groupBy: false,
  30. groupByField: '',
  31. groupByFormatter: undefined,
  32. groupByToggle: false,
  33. groupByShowToggleIcon: false,
  34. groupByCollapsedGroups: []
  35. })
  36. const BootstrapTable = $.fn.bootstrapTable.Constructor
  37. const _initSort = BootstrapTable.prototype.initSort
  38. const _initBody = BootstrapTable.prototype.initBody
  39. const _updateSelected = BootstrapTable.prototype.updateSelected
  40. BootstrapTable.prototype.initSort = function (...args) {
  41. _initSort.apply(this, Array.prototype.slice.apply(args))
  42. const that = this
  43. this.tableGroups = []
  44. if (this.options.groupBy && this.options.groupByField !== '') {
  45. if (this.options.sortName !== this.options.groupByField) {
  46. if (this.options.customSort) {
  47. Utils.calculateObjectValue(this.options, this.options.customSort, [
  48. this.options.sortName,
  49. this.options.sortOrder,
  50. this.data
  51. ])
  52. } else {
  53. this.options.data.sort((a, b) => {
  54. const groupByFields = this.getGroupByFields()
  55. const fieldValuesA = []
  56. const fieldValuesB = []
  57. $.each(groupByFields, (i, field) => {
  58. fieldValuesA.push(a[field])
  59. fieldValuesB.push(b[field])
  60. })
  61. a = fieldValuesA.join()
  62. b = fieldValuesB.join()
  63. return a.localeCompare(b, undefined, { numeric: true })
  64. })
  65. }
  66. }
  67. const groups = groupBy(that.data, item => {
  68. const groupByFields = this.getGroupByFields()
  69. const groupValues = []
  70. $.each(groupByFields, (i, field) => {
  71. const value_ = Utils.getItemField(item, field, that.options.escape, item.escape)
  72. groupValues.push(value_)
  73. })
  74. return groupValues.join(', ')
  75. })
  76. let index = 0
  77. $.each(groups, (key, value) => {
  78. this.tableGroups.push({
  79. id: index,
  80. name: key,
  81. data: value
  82. })
  83. value.forEach(item => {
  84. if (!item._data) {
  85. item._data = {}
  86. }
  87. if (this.isCollapsed(key, value)) {
  88. item._class += ' hidden'
  89. }
  90. item._data['parent-index'] = index
  91. })
  92. index++
  93. })
  94. }
  95. }
  96. BootstrapTable.prototype.initBody = function (...args) {
  97. initBodyCaller = true
  98. _initBody.apply(this, Array.prototype.slice.apply(args))
  99. if (this.options.groupBy && this.options.groupByField !== '') {
  100. const that = this
  101. let checkBox = false
  102. let visibleColumns = 0
  103. this.columns.forEach(column => {
  104. if (column.checkbox) {
  105. checkBox = true
  106. } else if (column.visible) {
  107. visibleColumns += 1
  108. }
  109. })
  110. if (this.options.detailView && !this.options.cardView) {
  111. visibleColumns += 1
  112. }
  113. this.tableGroups.forEach(item => {
  114. const html = []
  115. html.push(Utils.sprintf('<tr class="info group-by %s" data-group-index="%s">', this.options.groupByToggle ? 'expanded' : '', item.id))
  116. if (that.options.detailView && !that.options.cardView) {
  117. html.push('<td class="detail"></td>')
  118. }
  119. if (checkBox) {
  120. html.push('<td class="bs-checkbox">',
  121. '<input name="btSelectGroup" type="checkbox" />',
  122. '</td>'
  123. )
  124. }
  125. let formattedValue = item.name
  126. if (that.options.groupByFormatter !== undefined) {
  127. formattedValue = Utils.calculateObjectValue(that.options, that.options.groupByFormatter, [item.name, item.id, item.data])
  128. }
  129. html.push('<td',
  130. Utils.sprintf(' colspan="%s"', visibleColumns),
  131. '>', formattedValue
  132. )
  133. let icon = this.options.icons.collapseGroup
  134. if (this.isCollapsed(item.name, item.data)) {
  135. icon = this.options.icons.expandGroup
  136. }
  137. if (this.options.groupByToggle && this.options.groupByShowToggleIcon) {
  138. html.push(`<span class="float-right ${this.options.iconsPrefix} ${icon}"></span>`)
  139. }
  140. html.push('</td></tr>')
  141. that.$body.find(`tr[data-parent-index=${item.id}]:first`).before($(html.join('')))
  142. })
  143. this.$selectGroup = []
  144. this.$body.find('[name="btSelectGroup"]').each(function () {
  145. const self = $(this)
  146. that.$selectGroup.push({
  147. group: self,
  148. item: that.$selectItem.filter(function () {
  149. return $(this).closest('tr').data('parent-index') ===
  150. self.closest('tr').data('group-index')
  151. })
  152. })
  153. })
  154. if (this.options.groupByToggle) {
  155. this.$container.off('click', '.group-by')
  156. .on('click', '.group-by', function () {
  157. const $this = $(this)
  158. const groupIndex = $this.closest('tr').data('group-index')
  159. const $groupRows = that.$body.find(`tr[data-parent-index=${groupIndex}]`)
  160. $this.toggleClass('expanded collapsed')
  161. $this.find('span').toggleClass(`${that.options.icons.collapseGroup} ${that.options.icons.expandGroup}`)
  162. $groupRows.toggleClass('hidden')
  163. $groupRows.each((i, element) => that.collapseRow($(element).data('index')))
  164. })
  165. }
  166. this.$container.off('click', '[name="btSelectGroup"]')
  167. .on('click', '[name="btSelectGroup"]', function (event) {
  168. event.stopImmediatePropagation()
  169. const self = $(this)
  170. const checked = self.prop('checked')
  171. that[checked ? 'checkGroup' : 'uncheckGroup']($(this).closest('tr').data('group-index'))
  172. })
  173. }
  174. initBodyCaller = false
  175. this.updateSelected()
  176. }
  177. BootstrapTable.prototype.updateSelected = function (...args) {
  178. if (!initBodyCaller) {
  179. _updateSelected.apply(this, Array.prototype.slice.apply(args))
  180. if (this.options.groupBy && this.options.groupByField !== '') {
  181. this.$selectGroup.forEach(item => {
  182. const checkGroup = item.item.filter(':enabled').length ===
  183. item.item.filter(':enabled').filter(':checked').length
  184. item.group.prop('checked', checkGroup)
  185. })
  186. }
  187. }
  188. }
  189. BootstrapTable.prototype.checkGroup = function (index) {
  190. this.checkGroup_(index, true)
  191. }
  192. BootstrapTable.prototype.uncheckGroup = function (index) {
  193. this.checkGroup_(index, false)
  194. }
  195. BootstrapTable.prototype.isCollapsed = function (groupKey, items) {
  196. if (this.options.groupByCollapsedGroups) {
  197. const collapsedGroups = Utils.calculateObjectValue(this, this.options.groupByCollapsedGroups, [groupKey, items], true)
  198. if ($.inArray(groupKey, collapsedGroups) > -1) {
  199. return true
  200. }
  201. }
  202. return false
  203. }
  204. BootstrapTable.prototype.checkGroup_ = function (index, checked) {
  205. const rowsBefore = this.getSelections()
  206. const filter = function () {
  207. return $(this).closest('tr').data('parent-index') === index
  208. }
  209. this.$selectItem.filter(filter).prop('checked', checked)
  210. this.updateRows()
  211. this.updateSelected()
  212. const rowsAfter = this.getSelections()
  213. if (checked) {
  214. this.trigger('check-all', rowsAfter, rowsBefore)
  215. return
  216. }
  217. this.trigger('uncheck-all', rowsAfter, rowsBefore)
  218. }
  219. BootstrapTable.prototype.getGroupByFields = function () {
  220. let groupByFields = this.options.groupByField
  221. if (!$.isArray(this.options.groupByField)) {
  222. groupByFields = [this.options.groupByField]
  223. }
  224. return groupByFields
  225. }
  226. $.BootstrapTable = class extends $.BootstrapTable {
  227. scrollTo (params) {
  228. if (this.options.groupBy) {
  229. let options = { unit: 'px', value: 0 }
  230. if (typeof params === 'object') {
  231. options = Object.assign(options, params)
  232. }
  233. if (options.unit === 'rows') {
  234. let scrollTo = 0
  235. this.$body.find(`> tr:not(.group-by):lt(${options.value})`).each((i, el) => {
  236. scrollTo += $(el).outerHeight(true)
  237. })
  238. const $targetColumn = this.$body.find(`> tr:not(.group-by):eq(${options.value})`)
  239. $targetColumn.prevAll('.group-by').each((i, el) => {
  240. scrollTo += $(el).outerHeight(true)
  241. })
  242. this.$tableBody.scrollTop(scrollTo)
  243. return
  244. }
  245. }
  246. super.scrollTo(params)
  247. }
  248. }