Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add customTrackBy prop #1784

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion documentation/v2/index.html

Large diffs are not rendered by default.

40 changes: 32 additions & 8 deletions src/multiselectMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ export default {
type: Boolean,
default: true
},
/**
* Function to get the trackBy value
*
* @default false
* @type {Function}
*/
customTrackBy: {
type: Function
},
/**
* Function to interpolate the custom label
* @default false
Expand Down Expand Up @@ -368,8 +377,8 @@ export default {
return options.slice(0, this.optionsLimit)
},
valueKeys () {
if (this.trackBy) {
return this.internalValue.map((element) => element[this.trackBy])
if (this.hasTrackBy) {
return this.internalValue.map((element) => this.getOptionTrackBy(element))
} else {
return this.internalValue
}
Expand All @@ -384,6 +393,12 @@ export default {
: this.internalValue.length
? this.getOptionLabel(this.internalValue[0])
: this.searchable ? '' : this.placeholder
},
/**
* Returns true if a trackBy key/function is set. This is used in a few places to avoid copying arrays.
*/
hasTrackBy () {
return this.trackBy != null || this.customTrackBy != null
}
},
watch: {
Expand Down Expand Up @@ -461,9 +476,7 @@ export default {
* @returns {Boolean} returns true if element is selected
*/
isSelected (option) {
const opt = this.trackBy
? option[this.trackBy]
: option
const opt = this.getOptionTrackBy(option)
return this.valueKeys.indexOf(opt) > -1
},
/**
Expand Down Expand Up @@ -494,6 +507,17 @@ export default {
if (isEmpty(label)) return ''
return label
},
/**
* Gets the trackBy value for the provided option
*
* @param {Object||String||Integer} Passed option
* @returns {Object||String}
*/
getOptionTrackBy (option) {
if (this.customTrackBy != null) return this.customTrackBy(option, this.trackBy)
if (this.trackBy != null) return option[this.trackBy]
return option
},
/**
* Add the given option to the list of selected options
* or sets the option as the selected option.
Expand Down Expand Up @@ -559,9 +583,9 @@ export default {
if (this.wholeGroupSelected(group)) {
this.$emit('remove', group[this.groupValues], this.id)

const groupValues = this.trackBy ? group[this.groupValues].map(val => val[this.trackBy]) : group[this.groupValues]
const groupValues = this.hasTrackBy ? group[this.groupValues].map(val => this.getOptionTrackBy(val)) : group[this.groupValues]
const newValue = this.internalValue.filter(
option => groupValues.indexOf(this.trackBy ? option[this.trackBy] : option) === -1
option => groupValues.indexOf(this.hasTrackBy ? this.getOptionTrackBy(option) : option) === -1
)

this.$emit('update:modelValue', newValue)
Expand Down Expand Up @@ -621,7 +645,7 @@ export default {
}

const index = typeof option === 'object'
? this.valueKeys.indexOf(option[this.trackBy])
? this.valueKeys.indexOf(this.getOptionTrackBy(option))
: this.valueKeys.indexOf(option)

if (this.multiple) {
Expand Down
111 changes: 99 additions & 12 deletions tests/unit/Multiselect.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,7 @@ describe('Multiselect.vue', () => {
expect(wrapper.vm.getOptionLabel(option)).toBe('2')
})

test('should return option.label for passed option', () => {
test('should return option’s label when custom label is set', () => {
const wrapper = shallowMount(Multiselect, {
props: {
modelValue: [],
Expand All @@ -1066,11 +1066,69 @@ describe('Multiselect.vue', () => {
multiple: true
}
})
const option = wrapper.vm.options[2]
expect(wrapper.vm.getOptionLabel(option)).toBe('3')
})

test('should return customLabel’s interpolation if set for objects options', () => {
const wrapper = shallowMount(Multiselect, {
props: {
label: 'id',
trackBy: 'id',
multiple: true,
customLabel ({ id }) {
return `${id}+${id}`
},
modelValue: [],
options: [{ id: '1' }, { id: '2' }, { id: '3' }]
}
})
const option = wrapper.vm.options[2]
expect(wrapper.vm.getOptionLabel(option)).toBe('3+3')
})

test('should return customLabel’s interpolation if set for primitive options', () => {
const wrapper = shallowMount(Multiselect, {
props: {
multiple: true,
customLabel (option) {
return `${option}+${option}`
},
modelValue: [],
options: [1, 2, 3]
}
})
const option = wrapper.vm.options[2]
expect(wrapper.vm.getOptionLabel(option)).toBe('3+3')
})
})

describe('#getOptionTrackBy()', () => {
test('should return the passed primitive option when no trackBy is set', () => {
const wrapper = shallowMount(Multiselect, {
props: {
multiple: true,
modelValue: [],
options: ['1', '2', '3']
}
})
const option = wrapper.vm.options[1]
expect(wrapper.vm.getOptionLabel(option)).toBe('2')
expect(wrapper.vm.getOptionTrackBy(option)).toBe('2')
})

test('should return option’s label when custom label is set', () => {
test('should return the passed object option when no trackBy is set', () => {
const wrapper = shallowMount(Multiselect, {
props: {
multiple: true,
modelValue: [],
options: ['1', '2', '3']
}
})
const option = wrapper.vm.options[1]
expect(wrapper.vm.getOptionTrackBy(option)).toBe(option)
})

test('should return option[trackBy] when specified', () => {
const wrapper = shallowMount(Multiselect, {
props: {
modelValue: [],
Expand All @@ -1080,40 +1138,40 @@ describe('Multiselect.vue', () => {
multiple: true
}
})
const option = wrapper.vm.options[2]
expect(wrapper.vm.getOptionLabel(option)).toBe('3')
const option = wrapper.vm.options[1]
expect(wrapper.vm.getOptionTrackBy(option)).toBe('2')
})

test('should return customLabel’s interpolation if set for objects options', () => {
test('should return customTrackBy’s interpolation if set for objects options', () => {
const wrapper = shallowMount(Multiselect, {
props: {
label: 'id',
trackBy: 'id',
multiple: true,
customLabel ({ id }) {
return `${id}+${id}`
customTrackBy (option, trackBy) {
return `${trackBy}=${option.id}`
},
modelValue: [],
options: [{ id: '1' }, { id: '2' }, { id: '3' }]
}
})
const option = wrapper.vm.options[2]
expect(wrapper.vm.getOptionLabel(option)).toBe('3+3')
expect(wrapper.vm.getOptionTrackBy(option)).toBe('id=3')
})

test('should return customLabel’s interpolation if set for primitive options', () => {
test('should return customTrackBy’s interpolation if set for primitive options', () => {
const wrapper = shallowMount(Multiselect, {
props: {
multiple: true,
customLabel (option) {
customTrackBy (option) {
return `${option}+${option}`
},
modelValue: [],
options: [1, 2, 3]
}
})
const option = wrapper.vm.options[2]
expect(wrapper.vm.getOptionLabel(option)).toBe('3+3')
expect(wrapper.vm.getOptionTrackBy(option)).toBe('3+3')
})
})

Expand Down Expand Up @@ -1157,6 +1215,35 @@ describe('Multiselect.vue', () => {
const comp = wrapper.vm
expect(comp.valueKeys).toEqual(['2'])
})

test('should return an Array mapped from customTrackBy when multiple is TRUE', () => {
const wrapper = shallowMount(Multiselect, {
props: {
modelValue: [{ id: '1' }, { id: '2' }],
options: [{ id: '1' }, { id: '2' }, { id: '3' }],
label: 'id',
customTrackBy: (option) => '~' + option.id,
searchable: true,
multiple: true
}
})
expect(wrapper.vm.valueKeys).toEqual(['~1', '~2'])
})

test('should return customTrackBy(option) value when multiple is FALSE', () => {
const wrapper = shallowMount(Multiselect, {
props: {
label: 'id',
customTrackBy: (option) => '~' + option.id,
searchable: true,
multiple: false,
modelValue: { id: '2' },
options: [{ id: '1' }, { id: '2' }, { id: '3' }]
}
})
const comp = wrapper.vm
expect(comp.valueKeys).toEqual(['~2'])
})
})

describe('optionKeys', () => {
Expand Down