Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(b-table): addheader-contextmenu event (closes #5841)#6488

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
dflock wants to merge3 commits intobootstrap-vue:dev
base:dev
Choose a base branch
Loading
fromphemisystems:table-header-context-click
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 34 additions & 29 deletionssrc/components/table/helpers/mixin-thead.js
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { Vue } from '../../../vue'
import { EVENT_NAME_HEAD_CLICKED } from '../../../constants/events'
import { EVENT_NAME_HEAD_CLICKED, EVENT_NAME_HEAD_CONTEXTMENU } from '../../../constants/events'
import { CODE_ENTER, CODE_SPACE } from '../../../constants/key-codes'
import { PROP_TYPE_ARRAY_OBJECT_STRING, PROP_TYPE_STRING } from '../../../constants/props'
import { SLOT_NAME_THEAD_TOP } from '../../../constants/slots'
Expand DownExpand Up@@ -59,6 +59,21 @@ export const theadMixin = Vue.extend({
stopEvent(event)
this.$emit(EVENT_NAME_HEAD_CLICKED, field.key, field, event, isFoot)
},
headContextMenu(event, field, isFoot) {
if (this.stopIfBusy && this.stopIfBusy(event)) {
// If table is busy (via provider) then don't propagate
return
} else if (filterEvent(event)) {
// Clicked on a non-disabled control so ignore
return
} else if (textSelectionActive(this.$el)) {
// User is selecting text, so ignore
/* istanbul ignore next: JSDOM doesn't support getSelection() */
return
}
stopEvent(event)
this.$emit(EVENT_NAME_HEAD_CONTEXTMENU, field.key, field, event, isFoot)
},
renderThead(isFoot = false) {
const {
computedFields: fields,
Expand All@@ -77,7 +92,12 @@ export const theadMixin = Vue.extend({
return h()
}

const hasHeadClickListener = isSortable || this.hasListener(EVENT_NAME_HEAD_CLICKED)
const hasHeadClickListener =
isSortable ||
this.hasListener(EVENT_NAME_HEAD_CLICKED) ||
this.hasListener(EVENT_NAME_HEAD_CONTEXTMENU)

const hasHeadContextMenuListener = this.hasListener(EVENT_NAME_HEAD_CONTEXTMENU)

// Reference to `selectAllRows` and `clearSelected()`, if table is selectable
const selectAllRows = isSelectable ? this.selectAllRows : noop
Expand DownExpand Up@@ -108,6 +128,12 @@ export const theadMixin = Vue.extend({
}
}

if (hasHeadContextMenuListener) {
on.contextmenu = event => {
this.headContextMenu(event, field, isFoot)
}
}

const sortAttrs = isSortable ? this.sortTheadThAttrs(key, field, isFoot) : {}
const sortClass = isSortable ? this.sortTheadThClasses(key, field, isFoot) : null
const sortLabel = isSortable ? this.sortTheadThLabel(key, field, isFoot) : null
Expand DownExpand Up@@ -159,19 +185,13 @@ export const theadMixin = Vue.extend({
]
}

const scope = {
label,
column: key,
field,
isFoot,
// Add in row select methods
selectAllRows,
clearSelected
}
const scope = { label, column: key, field, isFoot, selectAllRows, clearSelected } // Add in row select methods

const $content =
this.normalizeSlot(slotNames, scope) ||
h('div', { domProps: htmlOrText(labelHtml, label) })
h('div', {
domProps: htmlOrText(labelHtml, label)
})

const $srLabel = sortLabel ? h('span', { staticClass: 'sr-only' }, ` (${sortLabel})`) : null

Expand DownExpand Up@@ -200,25 +220,10 @@ export const theadMixin = Vue.extend({
)
)
} else {
const scope = {
columns: fields.length,
fields,
// Add in row select methods
selectAllRows,
clearSelected
}
const scope = { columns: fields.length, fields, selectAllRows, clearSelected } // Add in row select methods
$trs.push(this.normalizeSlot(SLOT_NAME_THEAD_TOP, scope) || h())

$trs.push(
h(
BTr,
{
class: this.theadTrClass,
props: { variant: headRowVariant }
},
$cells
)
)
$trs.push(h(BTr, { class: this.theadTrClass, props: { variant: headRowVariant } }, $cells))
}

return h(
Expand Down
58 changes: 58 additions & 0 deletionssrc/components/table/package.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -359,6 +359,35 @@
}
]
},
{
"event": "head-contextmenu",
"description": "Emitted when a header or footer cell is context/right clicked. Not applicable for 'custom-foot' slot",
"args": [
{
"arg": "key",
"type": "String",
"description": "Column key clicked (field name)"
},
{
"arg": "field",
"type": "Object",
"description": "Field definition object"
},
{
"arg": "event",
"type": [
"MouseEvent",
"KeyboardEvent"
],
"description": "Native event object"
},
{
"arg": "isFooter",
"type": "Boolean",
"description": "'True' if this event originated from clicking on the footer cell"
}
]
},
{
"event": "refreshed",
"description": "Emitted when the items provider function has returned data"
Expand DownExpand Up@@ -1183,6 +1212,35 @@
}
]
},
{
"event": "head-contextmenu",
"description": "Emitted when a header or footer cell is context/right clicked. Not applicable for 'custom-foot' slot",
"args": [
{
"arg": "key",
"type": "String",
"description": "Column key clicked (field name)"
},
{
"arg": "field",
"type": "Object",
"description": "Field definition object"
},
{
"arg": "event",
"type": [
"MouseEvent",
"KeyboardEvent"
],
"description": "Native event object"
},
{
"arg": "isFooter",
"type": "Boolean",
"description": "'True' if this event originated from clicking on the footer cell"
}
]
},
{
"event": "row-clicked",
"description": "Emitted when a row is clicked",
Expand Down
141 changes: 141 additions & 0 deletionssrc/components/table/table-thead-events.spec.js
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,28 @@ describe('table > thead events', () => {
expect(wrapper.emitted('head-clicked')).toBeUndefined()
})

it('should not emit head-contextmenu event when a head cell is clicked and no head-contextmenu listener', async () => {
const wrapper = mount(BTable, {
propsData: {
fields: testFields,
items: testItems
},
listeners: {}
})
expect(wrapper).toBeDefined()
const $rows = wrapper.findAll('thead > tr')
expect($rows.length).toBe(1)
const $ths = wrapper.findAll('thead > tr > th')
expect($ths.length).toBe(testFields.length)
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()
await $ths.at(0).trigger('contextmenu')
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()
await $ths.at(1).trigger('contextmenu')
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()
await $ths.at(2).trigger('contextmenu')
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()
})

it('should emit head-clicked event when a head cell is clicked', async () => {
const wrapper = mount(BTable, {
propsData: {
Expand DownExpand Up@@ -62,6 +84,41 @@ describe('table > thead events', () => {
wrapper.destroy()
})

it('should emit head-contextmenu event when a head cell is context clicked', async () => {
const wrapper = mount(BTable, {
propsData: {
fields: testFields,
items: testItems
},
listeners: {
// Head-contextmenu will only be emitted if there is a registered listener
'head-contextmenu': () => {}
}
})
expect(wrapper).toBeDefined()
const $rows = wrapper.findAll('thead > tr')
expect($rows.length).toBe(1)
const $ths = wrapper.findAll('thead > tr > th')
expect($ths.length).toBe(testFields.length)
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()
await $ths.at(0).trigger('contextmenu')
expect(wrapper.emitted('head-contextmenu')).toBeDefined()
expect(wrapper.emitted('head-contextmenu').length).toBe(1)
expect(wrapper.emitted('head-contextmenu')[0][0]).toEqual(testFields[0].key) // Field key
expect(wrapper.emitted('head-contextmenu')[0][1]).toEqual(testFields[0]) // Field definition
expect(wrapper.emitted('head-contextmenu')[0][2]).toBeInstanceOf(MouseEvent) // Event
expect(wrapper.emitted('head-contextmenu')[0][3]).toBe(false) // Is footer

await $ths.at(2).trigger('contextmenu')
expect(wrapper.emitted('head-contextmenu').length).toBe(2)
expect(wrapper.emitted('head-contextmenu')[1][0]).toEqual(testFields[2].key) // Field key
expect(wrapper.emitted('head-contextmenu')[1][1]).toEqual(testFields[2]) // Field definition
expect(wrapper.emitted('head-contextmenu')[1][2]).toBeInstanceOf(MouseEvent) // Event
expect(wrapper.emitted('head-contextmenu')[1][3]).toBe(false) // Is footer

wrapper.destroy()
})

it('should not emit head-clicked event when prop busy is set', async () => {
const wrapper = mount(BTable, {
propsData: {
Expand All@@ -84,6 +141,28 @@ describe('table > thead events', () => {
wrapper.destroy()
})

it('should not emit head-contextmenu event when prop busy is set', async () => {
const wrapper = mount(BTable, {
propsData: {
fields: testFields,
items: testItems,
busy: true
},
listeners: {
// Head-contextmenu will only be emitted if there is a registered listener
'head-contextmenu': () => {}
}
})
expect(wrapper).toBeDefined()
const $ths = wrapper.findAll('thead > tr > th')
expect($ths.length).toBe(testFields.length)
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()
await $ths.at(0).trigger('contextmenu')
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()

wrapper.destroy()
})

it('should not emit head-clicked event when vm.localBusy is true', async () => {
const wrapper = mount(BTable, {
propsData: {
Expand All@@ -108,6 +187,28 @@ describe('table > thead events', () => {
wrapper.destroy()
})

it('should not emit head-contextmenu event when vm.localBusy is true', async () => {
const wrapper = mount(BTable, {
propsData: {
fields: testFields,
items: testItems
},
listeners: {
// Head-contextmenu will only be emitted if there is a registered listener
'head-contextmenu': () => {}
}
})
await wrapper.setData({ localBusy: true })
expect(wrapper).toBeDefined()
const $ths = wrapper.findAll('thead > tr > th')
expect($ths.length).toBe(testFields.length)
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()
await $ths.at(0).trigger('contextmenu')
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()

wrapper.destroy()
})

it('should not emit head-clicked event when clicking on a button or other interactive element', async () => {
const wrapper = mount(BTable, {
propsData: {
Expand DownExpand Up@@ -147,4 +248,44 @@ describe('table > thead events', () => {

wrapper.destroy()
})

it('should not emit head-contextmenu event when clicking on a button or other interactive element', async () => {
const wrapper = mount(BTable, {
propsData: {
fields: testFields,
items: testItems
},
listeners: {
// Head-contextmenu will only be emitted if there is a registered listener
'head-contextmenu': () => {}
},
slots: {
// In Vue 2.6x, slots get translated into scopedSlots
'head(a)': '<button id="a">button</button>',
'head(b)': '<input id="b">',
'head(c)': '<a href="#" id="c">link</a>'
}
})
expect(wrapper).toBeDefined()
const $ths = wrapper.findAll('thead > tr > th')
expect($ths.length).toBe(testFields.length)
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()

const $btn = wrapper.find('button[id="a"]')
expect($btn.exists()).toBe(true)
await $btn.trigger('contextmenu')
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()

const $input = wrapper.find('input[id="b"]')
expect($input.exists()).toBe(true)
await $input.trigger('contextmenu')
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()

const $link = wrapper.find('a[id="c"]')
expect($link.exists()).toBe(true)
await $link.trigger('contextmenu')
expect(wrapper.emitted('head-contextmenu')).toBeUndefined()

wrapper.destroy()
})
})
1 change: 1 addition & 0 deletionssrc/constants/events.js
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ export const EVENT_NAME_FOCUS = 'focus'
export const EVENT_NAME_FOCUSIN = 'focusin'
export const EVENT_NAME_FOCUSOUT = 'focusout'
export const EVENT_NAME_HEAD_CLICKED = 'head-clicked'
export const EVENT_NAME_HEAD_CONTEXTMENU = 'head-contextmenu'
export const EVENT_NAME_HIDDEN = 'hidden'
export const EVENT_NAME_HIDE = 'hide'
export const EVENT_NAME_IMG_ERROR = 'img-error'
Expand Down

[8]ページ先頭

©2009-2025 Movatter.jp