Skip to content

Commit

Permalink
add new prop: valueFormat
Browse files Browse the repository at this point in the history
  • Loading branch information
riophae committed Mar 25, 2018
1 parent b99ae0b commit 31cdc4c
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 44 deletions.
89 changes: 56 additions & 33 deletions src/mixins/treeselectMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import debounce from 'lodash/debounce'

import {
warning,
quickCompare, onlyOnLeftClick,
quickDiff, onlyOnLeftClick,
hasOwn, last, find, findIndex, removeFromArray,
} from '../utils'

Expand Down Expand Up @@ -512,17 +512,24 @@ export default {
* @type {?Array}
*/
value: null,

/**
* Format of `value` prop
* Acceptable values:
* - "id"
* - "object"
* @default "id"
* @type {string}
*/
valueFormat: {
type: String,
default: 'id',
},
},

data() {
return {
internalValue: this.multiple
? Array.isArray(this.value)
? this.value.slice()
: []
: this.value != null
? [ this.value ]
: [],
internalValue: this.extractNodeIdsFromValue(),
isFocused: false, // whether the control has been focused
isOpen: false, // whether the menu is open
nodeCheckedStateMap: Object.create(null), // used for multi-select mode
Expand Down Expand Up @@ -657,16 +664,12 @@ export default {
this.$emit('input', this.getValue(), this.id)
},

value(newValue) {
const _newValue = (!newValue && newValue !== 0)
? []
: this.multiple
? newValue.slice()
: [ newValue ]
const hasChanged = !quickCompare(_newValue, this.internalValue)
value() {
const newInternalValue = this.extractNodeIdsFromValue()
const hasChanged = quickDiff(newInternalValue, this.internalValue)

if (hasChanged) {
this.internalValue = _newValue
this.internalValue = newInternalValue
this.buildSelectedNodeMap()
this.buildNodeCheckedStateMap()
}
Expand Down Expand Up @@ -704,9 +707,14 @@ export default {
},

getValue() {
return this.multiple
? this.internalValue.slice()
: this.internalValue[0]
if (this.valueFormat === 'id') {
return this.multiple
? this.internalValue.slice()
: this.internalValue[0]
}

const rawNodes = this.internalValue.map(this.getNode).map(node => node.raw)
return this.multiple ? rawNodes : rawNodes[0]
},

getNode(nodeId) {
Expand All @@ -727,19 +735,8 @@ export default {
// we create a fallback node to keep the component working
// when the real data is loaded, we'll override this fake node

const matchNode = node => node && node.id === id

let label
// try extracting the label from `value`
if (this.multiple) {
// TODO: `label` can be other customized key name
label = Array.isArray(this.value) && (find(this.value, matchNode) || {}).label
} else {
label = (find(this.value, matchNode) || {}).label
}
// can not get the label, create one by the node id
label = label || `${id} (unknown)`
const fallback = this.nodeMap[id] = {
const label = this.extractLabelFromValue(id) || `${id} (unknown)`
const fallbackNode = this.nodeMap[id] = {
id,
label,
ancestors: [],
Expand All @@ -756,7 +753,33 @@ export default {
},
}

return fallback
return fallbackNode
},

extractNodeIdsFromValue() {
if (this.value == null) return []

if (this.valueFormat === 'id') {
return this.multiple
? this.value.slice()
: [ this.value ]
}

return this.multiple
? this.value.map(node => node.id)
: [ this.value.id ] // TODO
},

extractLabelFromValue(nodeId) {
if (this.valueFormat === 'id') return undefined

const valueArray = this.multiple
? Array.isArray(this.value) ? this.value : []
: this.value ? [ this.value ] : []
const matched = find(valueArray, node => node && node.id === nodeId)

// TODO
return matched ? matched.label : undefined
},

isSelected(node) {
Expand Down
8 changes: 4 additions & 4 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ export function removeFromArray(arr, elem) {
if (idx !== -1) arr.splice(idx, 1)
}

export function quickCompare(arrA, arrB) {
if (arrA.length !== arrB.length) return false
export function quickDiff(arrA, arrB) {
if (arrA.length !== arrB.length) return true

for (let i = 0; i < arrA.length; i++) {
if (arrA[i] !== arrB[i]) return false
if (arrA[i] !== arrB[i]) return true
}

return true
return false
}
203 changes: 202 additions & 1 deletion test/unit/specs/Treeselect.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -588,9 +588,47 @@ describe('Basic', () => {
},
})
})

describe('label', () => {
it('extract label from value object', () => {
const wrapper = mount(Treeselect, {
propsData: {
value: {
id: 'id',
label: 'label',
},
options: [],
valueFormat: 'object',
},
})
const { vm } = wrapper

expect(vm.nodeMap.id).toEqual(jasmine.objectContaining({
id: 'id',
label: 'label',
isFallbackNode: true,
}))
})

it('default label', () => {
const wrapper = mount(Treeselect, {
propsData: {
value: 'a',
options: [],
},
})
const { vm } = wrapper

expect(vm.nodeMap.a).toEqual(jasmine.objectContaining({
id: 'a',
label: 'a (unknown)',
isFallbackNode: true,
}))
})
})
})

it('should accept undefined/null', () => {
it('should accept undefined/null as value', () => {
[ true, false ].forEach(multiple => {
[ undefined, null ].forEach(value => {
const wrapper = mount(Treeselect, {
Expand Down Expand Up @@ -3906,6 +3944,169 @@ describe('Props', () => {
expect($input.getAttribute('tabindex')).toBe('1')
})
})

describe('valueFormat', () => {
describe('when valueFormat=id', () => {
it('single-select', async done => {
const vm = new Vue({
components: { Treeselect },
data: {
value: 'a',
options: [ {
id: 'a',
label: 'a',
}, {
id: 'b',
label: 'b',
} ],
},
template: `
<div>
<treeselect
v-model="value"
:options="options"
value-format="id"
/>
</div>
`,
}).$mount()
const comp = vm.$children[0]

expect(comp.internalValue).toEqual([ 'a' ])

comp.select(comp.nodeMap.b)
await comp.$nextTick()
expect(comp.internalValue).toEqual([ 'b' ])
expect(vm.value).toEqual('b')

done()
})

it('multi-select', async done => {
const vm = new Vue({
components: { Treeselect },
data: {
value: [ 'a' ],
options: [ {
id: 'a',
label: 'a',
}, {
id: 'b',
label: 'b',
} ],
},
template: `
<div>
<treeselect
v-model="value"
:options="options"
:multiple="true"
value-format="id"
/>
</div>
`,
}).$mount()
const comp = vm.$children[0]

expect(comp.internalValue).toEqual([ 'a' ])

comp.select(comp.nodeMap.b)
await comp.$nextTick()
expect(comp.internalValue).toEqual([ 'a', 'b' ])
expect(vm.value).toEqual([ 'a', 'b' ])

done()
})
})

describe('when valueFormat=object', () => {
it('single-select', async done => {
const vm = new Vue({
components: { Treeselect },
data: {
value: {
id: 'a',
label: 'a',
},
options: [ {
id: 'a',
label: 'a',
}, {
id: 'b',
label: 'b',
} ],
},
template: `
<div>
<treeselect
v-model="value"
:options="options"
value-format="object"
/>
</div>
`,
}).$mount()
const comp = vm.$children[0]

expect(comp.internalValue).toEqual([ 'a' ])

comp.select(comp.nodeMap.b)
await comp.$nextTick()
expect(comp.internalValue).toEqual([ 'b' ])
expect(vm.value).toEqual({
id: 'b',
label: 'b',
})

done()
})

it('multi-select', async done => {
const vm = new Vue({
components: { Treeselect },
data: {
value: [ {
id: 'a',
label: 'a',
} ],
options: [ {
id: 'a',
label: 'a',
}, {
id: 'b',
label: 'b',
} ],
},
template: `
<div>
<treeselect
v-model="value"
:options="options"
:multiple="true"
value-format="object"
/>
</div>
`,
}).$mount()
const comp = vm.$children[0]

expect(comp.internalValue).toEqual([ 'a' ])

comp.select(comp.nodeMap.b)
await comp.$nextTick()
expect(comp.internalValue).toEqual([ 'a', 'b' ])
expect(vm.value).toEqual([ {
id: 'a',
label: 'a',
}, {
id: 'b',
label: 'b',
} ])

done()
})
})
})
})

describe('Methods', () => {
Expand Down
12 changes: 6 additions & 6 deletions test/unit/specs/utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ describe('Utils', () => {
expect(arr).toEqual([ 1, 3 ])
})

it('quickCompare', () => {
const { quickCompare } = utils
it('quickDiff', () => {
const { quickDiff } = utils
const obj = {}
expect(quickCompare([], [])).toBe(true)
expect(quickCompare([ 1 ], [])).toBe(false)
expect(quickCompare([ {} ], [ {} ])).toBe(false)
expect(quickCompare([ obj ], [ obj ])).toBe(true)
expect(quickDiff([], [])).toBe(false)
expect(quickDiff([ 1 ], [])).toBe(true)
expect(quickDiff([ {} ], [ {} ])).toBe(true)
expect(quickDiff([ obj ], [ obj ])).toBe(false)
})
})

0 comments on commit 31cdc4c

Please sign in to comment.