Skip to content

Commit 9a81cd4

Browse files
tmorehousejacobmllr95
authored andcommitted
fix(b-table, b-table-lite): handle edge case with row events when table is removed from dom. instantiate row event handlers only when listeners are registered (fixes #4384) (#4388)
* fix(b-table, b-table-lite): handle edge case with row events when table is removed from dom (fixes #4384) * Update mixin-tbody.js * Update mixin-tbody-row.js * Update table-tbody-row-events.spec.js * Update table-tbody-row-events.spec.js * Update mixin-tbody-row.js * only emit events if listeners are registered * Update table-tbody-row-events.spec.js * Update mixin-tbody-row.js * Update mixin-tbody.js * Update mixin-tbody.js * Update mixin-tbody-row.js * Update mixin-tbody.js
1 parent 136a72b commit 9a81cd4

File tree

3 files changed

+104
-23
lines changed

3 files changed

+104
-23
lines changed

Diff for: src/components/table/helpers/mixin-tbody-row.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,19 @@ export default {
6969
rowHovered(evt) {
7070
// `mouseenter` handler (non-bubbling)
7171
// `this.tbodyRowEvtStopped` from tbody mixin
72-
if (!this.tbodyRowEvtStopped(evt)) {
72+
const type = 'row-hovered'
73+
if (this.$listeners[type] && !this.tbodyRowEvtStopped(evt)) {
7374
// `this.emitTbodyRowEvent` from tbody mixin
74-
this.emitTbodyRowEvent('row-hovered', evt)
75+
this.emitTbodyRowEvent(type, evt)
7576
}
7677
},
7778
rowUnhovered(evt) {
7879
// `mouseleave` handler (non-bubbling)
7980
// `this.tbodyRowEvtStopped` from tbody mixin
80-
if (!this.tbodyRowEvtStopped(evt)) {
81+
const type = 'row-unhovered'
82+
if (this.$listeners[type] && !this.tbodyRowEvtStopped(evt)) {
8183
// `this.emitTbodyRowEvent` from tbody mixin
82-
this.emitTbodyRowEvent('row-unhovered', evt)
84+
this.emitTbodyRowEvent(type, evt)
8385
}
8486
},
8587
// Render helpers

Diff for: src/components/table/helpers/mixin-tbody.js

+24-19
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,12 @@ export default {
2424
// `this.$refs.itemRows` is an array of item TR components/elements
2525
// Rows should all be B-TR components, but we map to TR elements
2626
// Also note that `this.$refs.itemRows` may not always be in document order
27-
const tbody = this.$refs.tbody.$el || this.$refs.tbody
28-
const trs = (this.$refs.itemRows || []).map(tr => tr.$el || tr)
29-
// TODO: This may take time for tables many rows, so we may want to cache
30-
// the result of this during each render cycle on a non-reactive
31-
// property. We clear out the cache as each render starts, and
32-
// populate it on first access of this method if null
33-
return arrayFrom(tbody.children).filter(tr => arrayIncludes(trs, tr))
27+
const refs = this.$refs || {}
28+
const tbody = refs.tbody ? refs.tbody.$el || refs.tbody : null
29+
const trs = (refs.itemRows || []).map(tr => tr.$el || tr)
30+
return tbody && tbody.children && tbody.children.length > 0 && trs && trs.length > 0
31+
? arrayFrom(tbody.children).filter(tr => arrayIncludes(trs, tr))
32+
: []
3433
},
3534
getTbodyTrIndex(el) {
3635
// Returns index of a particular TBODY item TR
@@ -102,6 +101,7 @@ export default {
102101
}
103102
},
104103
onTBodyRowClicked(evt) {
104+
// Row-clicked handler is only added when needed
105105
if (this.tbodyRowEvtStopped(evt)) {
106106
// If table is busy, then don't propagate
107107
return
@@ -113,18 +113,21 @@ export default {
113113
this.emitTbodyRowEvent('row-clicked', evt)
114114
},
115115
onTbodyRowMiddleMouseRowClicked(evt) {
116-
if (!this.tbodyRowEvtStopped(evt) && evt.which === 2) {
117-
this.emitTbodyRowEvent('row-middle-clicked', evt)
116+
const type = 'row-middle-clicked'
117+
if (this.$listeners[type] && !this.tbodyRowEvtStopped(evt) && evt.which === 2) {
118+
this.emitTbodyRowEvent(type, evt)
118119
}
119120
},
120121
onTbodyRowContextmenu(evt) {
121-
if (!this.tbodyRowEvtStopped(evt)) {
122-
this.emitTbodyRowEvent('row-contextmenu', evt)
122+
const type = 'row-contextmenu'
123+
if (this.$listeners[type] && !this.tbodyRowEvtStopped(evt)) {
124+
this.emitTbodyRowEvent(type, evt)
123125
}
124126
},
125127
onTbodyRowDblClicked(evt) {
126-
if (!this.tbodyRowEvtStopped(evt) && !filterEvent(evt)) {
127-
this.emitTbodyRowEvent('row-dblclicked', evt)
128+
const type = 'row-dblclicked'
129+
if (this.$listeners[type] && !this.tbodyRowEvtStopped(evt) && !filterEvent(evt)) {
130+
this.emitTbodyRowEvent(type, evt)
128131
}
129132
},
130133
// Note: Row hover handlers are handled by the tbody-row mixin
@@ -187,22 +190,24 @@ export default {
187190
$rows.push(this.renderBottomRow ? this.renderBottomRow() : h())
188191
}
189192

193+
// Note: these events will only emit if a listener is registered
190194
const handlers = {
191-
// TODO: We may want to to only instantiate these handlers
192-
// if there is an event listener registered
193195
auxclick: this.onTbodyRowMiddleMouseRowClicked,
194-
// TODO: Perhaps we do want to automatically prevent the
195-
// default context menu from showing if there is
196-
// a `row-contextmenu` listener registered.
196+
// TODO:
197+
// Perhaps we do want to automatically prevent the
198+
// default context menu from showing if there is a
199+
// `row-contextmenu` listener registered
197200
contextmenu: this.onTbodyRowContextmenu,
198201
// The following event(s) is not considered A11Y friendly
199202
dblclick: this.onTbodyRowDblClicked
200-
// hover events (mouseenter/mouseleave) ad handled by tbody-row mixin
203+
// Hover events (`mouseenter`/`mouseleave`) are handled by `tbody-row` mixin
201204
}
205+
// Add in click/keydown listeners if needed
202206
if (hasRowClickHandler) {
203207
handlers.click = this.onTBodyRowClicked
204208
handlers.keydown = this.onTbodyRowKeydown
205209
}
210+
206211
// Assemble rows into the tbody
207212
const $tbody = h(
208213
BTbody,

Diff for: src/components/table/table-tbody-row-events.spec.js

+74
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ describe('table > tbody row events', () => {
8282
propsData: {
8383
fields: testFields,
8484
items: testItems
85+
},
86+
listeners: {
87+
// Row-dblclicked will only occur if there is a registered listener
88+
'row-dblclicked': () => {}
8589
}
8690
})
8791
expect(wrapper).toBeDefined()
@@ -104,6 +108,10 @@ describe('table > tbody row events', () => {
104108
fields: testFields,
105109
items: testItems,
106110
busy: true
111+
},
112+
listeners: {
113+
// Row-dblclicked will only occur if there is a registered listener
114+
'row-dblclicked': () => {}
107115
}
108116
})
109117
expect(wrapper).toBeDefined()
@@ -121,6 +129,10 @@ describe('table > tbody row events', () => {
121129
propsData: {
122130
fields: testFields,
123131
items: testItems
132+
},
133+
listeners: {
134+
// Row-middle-clicked will only occur if there is a registered listener
135+
'row-middle-clicked': () => {}
124136
}
125137
})
126138
expect(wrapper).toBeDefined()
@@ -143,6 +155,10 @@ describe('table > tbody row events', () => {
143155
fields: testFields,
144156
items: testItems,
145157
busy: true
158+
},
159+
listeners: {
160+
// Row-middle-clicked will only occur if there is a registered listener
161+
'row-middle-clicked': () => {}
146162
}
147163
})
148164
expect(wrapper).toBeDefined()
@@ -160,6 +176,10 @@ describe('table > tbody row events', () => {
160176
propsData: {
161177
fields: testFields,
162178
items: testItems
179+
},
180+
listeners: {
181+
// Row-contextmenu will only occur if there is a registered listener
182+
'row-contextmenu': () => {}
163183
}
164184
})
165185
expect(wrapper).toBeDefined()
@@ -182,6 +202,10 @@ describe('table > tbody row events', () => {
182202
fields: testFields,
183203
items: testItems,
184204
busy: true
205+
},
206+
listeners: {
207+
// Row-contextmenu will only occur if there is a registered listener
208+
'row-contextmenu': () => {}
185209
}
186210
})
187211
expect(wrapper).toBeDefined()
@@ -199,6 +223,10 @@ describe('table > tbody row events', () => {
199223
propsData: {
200224
fields: testFields,
201225
items: testItems
226+
},
227+
listeners: {
228+
// Row-hovered will only occur if there is a registered listener
229+
'row-hovered': () => {}
202230
}
203231
})
204232
expect(wrapper).toBeDefined()
@@ -215,12 +243,33 @@ describe('table > tbody row events', () => {
215243
wrapper.destroy()
216244
})
217245

246+
it('should not emit row-hovered event when a row is hovered and no listener', async () => {
247+
const wrapper = mount(BTable, {
248+
propsData: {
249+
fields: testFields,
250+
items: testItems
251+
}
252+
})
253+
expect(wrapper).toBeDefined()
254+
const $rows = wrapper.findAll('tbody > tr')
255+
expect($rows.length).toBe(3)
256+
expect(wrapper.emitted('row-hovered')).not.toBeDefined()
257+
$rows.at(1).trigger('mouseenter')
258+
expect(wrapper.emitted('row-hovered')).not.toBeDefined()
259+
260+
wrapper.destroy()
261+
})
262+
218263
it('should not emit row-hovered event when a row is hovered and table busy', async () => {
219264
const wrapper = mount(BTable, {
220265
propsData: {
221266
fields: testFields,
222267
items: testItems,
223268
busy: true
269+
},
270+
listeners: {
271+
// Row-hovered will only occur if there is a registered listener
272+
'row-hovered': () => {}
224273
}
225274
})
226275
expect(wrapper).toBeDefined()
@@ -238,6 +287,10 @@ describe('table > tbody row events', () => {
238287
propsData: {
239288
fields: testFields,
240289
items: testItems
290+
},
291+
listeners: {
292+
// Row-unhovered will only occur if there is a registered listener
293+
'row-unhovered': () => {}
241294
}
242295
})
243296
expect(wrapper).toBeDefined()
@@ -254,12 +307,33 @@ describe('table > tbody row events', () => {
254307
wrapper.destroy()
255308
})
256309

310+
it('should not emit row-nhovered event when a row is hovered and no listener', async () => {
311+
const wrapper = mount(BTable, {
312+
propsData: {
313+
fields: testFields,
314+
items: testItems
315+
}
316+
})
317+
expect(wrapper).toBeDefined()
318+
const $rows = wrapper.findAll('tbody > tr')
319+
expect($rows.length).toBe(3)
320+
expect(wrapper.emitted('row-unhovered')).not.toBeDefined()
321+
$rows.at(1).trigger('mouseleave')
322+
expect(wrapper.emitted('row-unhovered')).not.toBeDefined()
323+
324+
wrapper.destroy()
325+
})
326+
257327
it('should not emit row-unhovered event when a row is unhovered and table busy', async () => {
258328
const wrapper = mount(BTable, {
259329
propsData: {
260330
fields: testFields,
261331
items: testItems,
262332
busy: true
333+
},
334+
listeners: {
335+
// Row-unhovered will only occur if there is a registered listener
336+
'row-unhovered': () => {}
263337
}
264338
})
265339
expect(wrapper).toBeDefined()

0 commit comments

Comments
 (0)