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