2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-22 10:30:34 +03:00

tagging 90% complete

This commit is contained in:
Jeff Sagal
2016-05-26 16:13:00 -07:00
parent 9af491be45
commit 655e612aaf
3 changed files with 396 additions and 259 deletions
+1
View File
@@ -1,3 +1,4 @@
.DS_Store .DS_Store
node_modules node_modules
npm-debug.log npm-debug.log
.idea
+49 -21
View File
@@ -153,7 +153,7 @@
</div> </div>
<ul v-show="open" v-el:dropdown-menu :transition="transition" :style="{ 'max-height': maxHeight }" class="dropdown-menu animated"> <ul v-show="open" v-el:dropdown-menu :transition="transition" :style="{ 'max-height': maxHeight }" class="dropdown-menu animated">
<li v-for="option in filteredOptions" :class="{ active: isOptionSelected(option), highlight: $index === typeAheadPointer }" @mouseover="typeAheadPointer = $index"> <li v-for="option in filteredOptions" track-by="$index" :class="{ active: isOptionSelected(option), highlight: $index === typeAheadPointer }" @mouseover="typeAheadPointer = $index">
<a @mousedown.prevent="select(option)"> <a @mousedown.prevent="select(option)">
{{ getOptionLabel(option) }} {{ getOptionLabel(option) }}
</a> </a>
@@ -167,7 +167,7 @@
</template> </template>
<script> <script type="text/babel">
export default { export default {
props: { props: {
/** /**
@@ -278,6 +278,11 @@
default: false default: false
}, },
pushTags: {
type: Boolean,
default: true
},
/** /**
* User defined function for adding Options * User defined function for adding Options
* @type {Function} * @type {Function}
@@ -285,15 +290,10 @@
createOption: { createOption: {
type: Function, type: Function,
default: function (newOption) { default: function (newOption) {
let value = newOption if (typeof this.options[0] === 'object') {
let firstOption = this.options ? this.options[0] : null return {[this.label]: newOption}
if (firstOption && typeof firstOption === 'object' ) {
value = {
value
} }
value[this.label] = newOption return newOption
}
return value
} }
} }
}, },
@@ -332,6 +332,15 @@
*/ */
select(option) { select(option) {
if (!this.isOptionSelected(option)) { if (!this.isOptionSelected(option)) {
if (this.taggable && !this.optionExists(option)) {
newOption = this.createOption(option)
option = typeof newOption === 'undefined' ? option : newOption
if( this.pushTags ) {
this.options.push(option)
}
}
if (this.multiple) { if (this.multiple) {
if (!this.value) { if (!this.value) {
@@ -366,10 +375,6 @@
if( this.clearSearchOnSelect ) { if( this.clearSearchOnSelect ) {
this.search = '' this.search = ''
} }
// if( this.onChange ) {
// this.onChange(this.$get('value'))
// }
}, },
/** /**
@@ -395,7 +400,15 @@
*/ */
isOptionSelected( option ) { isOptionSelected( option ) {
if( this.multiple && this.value ) { if( this.multiple && this.value ) {
return this.value.indexOf(option) !== -1 let selected = false
this.value.forEach(opt => {
if( typeof opt === 'object' && opt[this.label] === option ) {
selected = true
} else if( opt === option ) {
selected = true
}
})
return selected
} }
return this.value === option return this.value === option
@@ -460,11 +473,7 @@
if( this.filteredOptions[ this.typeAheadPointer ] ) { if( this.filteredOptions[ this.typeAheadPointer ] ) {
this.select( this.filteredOptions[ this.typeAheadPointer ] ); this.select( this.filteredOptions[ this.typeAheadPointer ] );
} else if (this.taggable && this.search.length){ } else if (this.taggable && this.search.length){
let option = this.createOption(this.search) this.select(this.search)
this.$set('options', [option, ...this.options])
this.$nextTick(() => {
this.select(option)
})
} }
if( this.clearSearchOnSelect ) { if( this.clearSearchOnSelect ) {
@@ -494,6 +503,21 @@
if( ! this.$els.search.value.length && this.value ) { if( ! this.$els.search.value.length && this.value ) {
return this.multiple ? this.value.pop() : this.$set('value', null) return this.multiple ? this.value.pop() : this.$set('value', null)
} }
},
optionExists(option) {
let exists = false
this.options.forEach(opt => {
if( typeof opt === 'object' && opt[this.label] === option ) {
exists = true
} else if( opt === option ) {
exists = true
}
})
return exists
} }
}, },
@@ -527,7 +551,11 @@
* @return {[type]} [description] * @return {[type]} [description]
*/ */
filteredOptions() { filteredOptions() {
return this.$options.filters.filterBy(this.options, this.search) let options = this.$options.filters.filterBy(this.options, this.search)
if (this.taggable && this.search.length && !this.optionExists(this.search)) {
options.unshift(this.search)
}
return options
}, },
/** /**
+170 -62
View File
@@ -11,8 +11,15 @@ function trigger (target, event, process) {
return e return e
} }
function searchSubmit(vm) {
trigger(vm.$children[0].$els.search, 'keyup', function (e) {
e.keyCode = 13
})
}
describe('Select.vue', () => { describe('Select.vue', () => {
describe('Selecting values', () => {
it('can accept an array with pre-selected values', () => { it('can accept an array with pre-selected values', () => {
const vm = new Vue({ const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>', template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
@@ -55,53 +62,6 @@ describe('Select.vue', () => {
expect(labels).toEqual(['This is Foo', 'This is Bar']) expect(labels).toEqual(['This is Foo', 'This is Bar'])
}) })
it('removes the given tag when its close icon is clicked', (done) => {
const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>',
components: { vSelect },
data: {
value: ['one'],
options: ['one','two','three']
}
}).$mount()
vm.$children[0].$els.toggle.querySelector('.close').click()
Vue.nextTick(() => {
expect(vm.$children[0].$get('value')).toEqual([])
done()
})
})
it('removes the last item in the value array on delete keypress when multiple is true', () => {
const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>',
components: { vSelect },
data: {
value: ['one','two'],
options: ['one','two','three']
}
}).$mount()
vm.$children[0].maybeDeleteValue()
Vue.nextTick(() => {
expect(vm.$children[0].$get('value')).toEqual(['one'])
})
})
it('sets the value to null on delete keypress when multiple is false', () => {
const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
components: { vSelect },
data: {
value: 'one',
options: ['one','two','three']
}
}).$mount()
vm.$children[0].maybeDeleteValue()
Vue.nextTick(() => {
expect(vm.$children[0].$get('value')).toEqual(null)
})
})
it('can determine if the value prop is empty', () => { it('can determine if the value prop is empty', () => {
const vm = new Vue({ const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>', template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
@@ -161,7 +121,58 @@ describe('Select.vue', () => {
vm.$children[0].$set('options', ['one', 'five', 'six']) vm.$children[0].$set('options', ['one', 'five', 'six'])
expect(vm.$children[0].value).toEqual(['one']) expect(vm.$children[0].value).toEqual(['one'])
}) })
})
describe('Removing values', () => {
it('removes the given tag when its close icon is clicked', (done) => {
const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>',
components: {vSelect},
data: {
value: ['one'],
options: ['one', 'two', 'three']
}
}).$mount()
vm.$children[0].$els.toggle.querySelector('.close').click()
Vue.nextTick(() => {
expect(vm.$children[0].$get('value')).toEqual([])
done()
})
})
it('removes the last item in the value array on delete keypress when multiple is true', () => {
const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>',
components: {vSelect},
data: {
value: ['one', 'two'],
options: ['one', 'two', 'three']
}
}).$mount()
vm.$children[0].maybeDeleteValue()
Vue.nextTick(() => {
expect(vm.$children[0].$get('value')).toEqual(['one'])
})
})
it('sets the value to null on delete keypress when multiple is false', () => {
const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value"></v-select></div>',
components: {vSelect},
data: {
value: 'one',
options: ['one', 'two', 'three']
}
}).$mount()
vm.$children[0].maybeDeleteValue()
Vue.nextTick(() => {
expect(vm.$children[0].$get('value')).toEqual(null)
})
})
})
describe('Labels', () => {
it('can generate labels using the default label key', () => { it('can generate labels using the default label key', () => {
const vm = new Vue({ const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>', template: '<div><v-select :options="options" :value.sync="value" :multiple="true"></v-select></div>',
@@ -212,44 +223,141 @@ describe('Select.vue', () => {
}) })
}) })
}) })
})
it('can adding option if taggable enabled and search is not empty', () => { describe('When Tagging Is Enabled', () => {
it('can determine if a given option string already exists', () => {
const vm = new Vue({
template: '<div><v-select v-ref:select :options="options" :taggable="true"></v-select></div>',
components: {vSelect},
data: {
options: ['one', 'two']
}
}).$mount()
expect(vm.$refs.select.optionExists('one')).toEqual(true)
expect(vm.$refs.select.optionExists('three')).toEqual(false)
})
it('can determine if a given option object already exists', () => {
const vm = new Vue({
template: '<div><v-select v-ref:select :options="options" :taggable="true"></v-select></div>',
components: {vSelect},
data: {
options: [{label: 'one'}, {label: 'two'}]
}
}).$mount()
expect(vm.$refs.select.optionExists('one')).toEqual(true)
expect(vm.$refs.select.optionExists('three')).toEqual(false)
})
it('can determine if a given option object already exists when using custom labels', () => {
const vm = new Vue({
template: '<div><v-select v-ref:select :options="options" label="foo" :taggable="true"></v-select></div>',
components: {vSelect},
data: {
options: [{foo: 'one'}, {foo: 'two'}]
}
}).$mount()
expect(vm.$refs.select.optionExists('one')).toEqual(true)
expect(vm.$refs.select.optionExists('three')).toEqual(false)
})
it('can add the current search text as the first item in the options list', () => {
const vm = new Vue({ const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value" :multiple="true" :taggable="true"></v-select></div>', template: '<div><v-select :options="options" :value.sync="value" :multiple="true" :taggable="true"></v-select></div>',
components: {vSelect}, components: {vSelect},
data: { data: {
value: ['one'], value: ['one'],
options: ['one','two','three'] options: ['one', 'two']
} }
}).$mount() }).$mount()
vm.$children[0].search = 'four'
trigger(vm.$children[0].$els.search, 'keyup', function (e) { vm.$children[0].search = 'three'
e.keyCode = 13 expect(vm.$children[0].filteredOptions).toEqual(['three'])
}) })
expect(vm.$children[0].options[0]).toEqual('four') it('can select the current search text', (done) => {
})
it('should select added option if taggable enabled and search is not empty', (done) => {
const vm = new Vue({ const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value" :multiple="true" :taggable="true"></v-select></div>', template: '<div><v-select :options="options" :value.sync="value" :multiple="true" :taggable="true"></v-select></div>',
components: {vSelect}, components: {vSelect},
data: { data: {
value: ['one'], value: ['one'],
options: ['one','two','three'] options: ['one', 'two']
} }
}).$mount() }).$mount()
vm.$children[0].search = 'four'
trigger(vm.$children[0].$els.search, 'keyup', function (e) { vm.$children[0].search = 'three'
e.keyCode = 13
}) searchSubmit(vm)
Vue.nextTick(() => { Vue.nextTick(() => {
expect(vm.$children[0].$get('value')).toEqual(['one', 'four']) expect(vm.$children[0].$get('value')).toEqual(['one', 'three'])
done() done()
}) })
}) })
it('will add a freshly created option/tag to the options list when pushTags is true', () => {
const vm = new Vue({
template: '<div><v-select :options="options" pushTags :value.sync="value" :multiple="true" :taggable="true"></v-select></div>',
components: {vSelect},
data: {
value: ['one'],
options: ['one', 'two']
}
}).$mount()
vm.$children[0].search = 'three'
searchSubmit(vm)
expect(vm.$children[0].options).toEqual(['one', 'two', 'three'])
})
it('will select an existing option if the search string matches a string from options', (done) => {
let two = 'two'
const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value" :multiple="true" :taggable="true"></v-select></div>',
components: {vSelect},
data: {
value: null,
options: ['one', two]
}
}).$mount()
vm.$children[0].search = 'two'
searchSubmit(vm)
Vue.nextTick(() => {
expect(vm.$children[0].$get('value')[0]).toBe(two)
done()
})
})
it('will select an existing option if the search string matches an objects label from options', (done) => {
const vm = new Vue({
template: '<div><v-select :options="options" :value.sync="value" multiple :taggable="true"></v-select></div>',
components: {vSelect},
data: {
value: null,
options: [{label: 'one'}, {label: 'two'}]
}
}).$mount()
vm.$children[0].search = 'two'
searchSubmit(vm)
Vue.nextTick(() => {
console.dir(JSON.stringify(vm.$children[0].options))
console.dir(JSON.stringify(vm.$children[0].value))
expect(vm.$children[0].value).toEqual({label: 'two'})
done()
})
})
})
}) })
// also see example testing a component with mocks at // also see example testing a component with mocks at