diff --git a/README.md b/README.md index 12ef56f..e4d96a2 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,14 @@ From there you can use as normal. Here's an [example on JSBin](http://jsbin.com/ default: 'label' }, +/** + * An optional callback function that is called each time the selected + * value(s) change. When integrating with Vuex, use this callback to trigger + * an action, rather than using :value.sync to retreive the selected value. + * @type {Function} + * @default {null} + */ +onChange: Function, /** * Enable/disable creating options from searchInput. diff --git a/old_docs/components/Params.vue b/old_docs/components/Params.vue index c7ef210..0c240c9 100644 --- a/old_docs/components/Params.vue +++ b/old_docs/components/Params.vue @@ -78,6 +78,16 @@ label: { type: String, default: 'label' + }, + + /** + * An optional callback function that is called each time the selected + * value(s) change. When integrating with Vuex, use this callback to trigger + * an action, rather than using :value.sync to retreive the selected value. + * @type {Function} + * @default {null} + */ + onChange: Function } } diff --git a/src/components/Select.vue b/src/components/Select.vue index a684016..81174b0 100644 --- a/src/components/Select.vue +++ b/src/components/Select.vue @@ -342,6 +342,16 @@ } }, + /** + * An optional callback function that is called each time the selected + * value(s) change. When integrating with Vuex, use this callback to trigger + * an action, rather than using :value.sync to retreive the selected value. + * @type {Function} + * @default {null} + */ + onChange: Function, + + /** * Enable/disable creating options from searchInput. * @type {Boolean} @@ -368,7 +378,7 @@ createOption: { type: Function, default: function (newOption) { - if (typeof this.actualOptions[0] === 'object') { + if (typeof this.currentOptions[0] === 'object') { return {[this.label]: newOption} } return newOption @@ -389,27 +399,38 @@ return { search: '', open: false, - actualValue: null, - actualOptions: [], + currentSelection: null, + currentOptions: [], showLoading: false } }, watch: { - actualValue(val, old) { + value(val, old) { + this.currentSelection = val + }, + currentSelection(val, old) { if (this.multiple) { + this.onChange ? this.onChange(val) : null this.$emit('change', val) } else { if(val !== old) { + this.onChange? this.onChange(val) : null this.$emit('change', val) } } }, - actualOptions() { + options(val) { + this.currentOptions = val + }, + currentOptions() { if (!this.taggable && this.resetOnOptionsChange) { - this.actualValue = this.multiple ? [] : null + this.currentSelection = this.multiple ? [] : null } - } + }, + multiple(val) { + this.currentSelection = val ? [] : null + } }, methods: { @@ -427,18 +448,19 @@ option = this.createOption(option) if (this.pushTags) { - this.actualOptions.push(option) + console.log("adding " + option +" to "+ this.currentOptions) + this.currentOptions.push(option) } } if (this.multiple) { - if (!this.actualValue) { - this.actualValue = [option] + if (!this.currentSelection) { + this.currentSelection = [option] } else { - this.actualValue.push(option) + this.currentSelection.push(option) } } else { - this.actualValue = option + this.currentSelection = option } } @@ -453,15 +475,15 @@ deselect(option) { if (this.multiple) { let ref = -1 - this.actualValue.forEach((val) => { + this.currentSelection.forEach((val) => { if (val === option || typeof val === 'object' && val[this.label] === option[this.label]) { ref = val } }) - var index = this.actualValue.indexOf(ref) - this.actualValue.splice(index, 1) + var index = this.currentSelection.indexOf(ref) + this.currentSelection.splice(index, 1) } else { - this.actualValue = null + this.currentSelection = null } }, @@ -503,9 +525,9 @@ * @return {Boolean} True when selected || False otherwise */ isOptionSelected(option) { - if (this.multiple && this.actualValue) { + if (this.multiple && this.currentSelection) { let selected = false - this.actualValue.forEach(opt => { + this.currentSelection.forEach(opt => { if (typeof opt === 'object' && opt[this.label] === option[this.label]) { selected = true } else if (opt === option) { @@ -515,7 +537,7 @@ return selected } - return this.actualValue === option + return this.currentSelection === option }, /** @@ -537,14 +559,14 @@ * @return {this.value} */ maybeDeleteValue() { - if (!this.$refs.search.value.length && this.actualValue) { - return this.multiple ? this.actualValue.pop() : this.actualValue = null + if (!this.$refs.search.value.length && this.currentSelection) { + return this.multiple ? this.currentSelection.pop() : this.currentSelection = null } }, /** * Determine if an option exists - * within this.actualOptions array. + * within this.currentOptions array. * * @param {Object || String} option * @return {boolean} @@ -552,7 +574,7 @@ optionExists(option) { let exists = false - this.actualOptions.forEach(opt => { + this.currentOptions.forEach(opt => { if (typeof opt === 'object' && opt[this.label] === option) { exists = true } else if (opt === option) { @@ -598,7 +620,7 @@ * @return {array} */ filteredOptions() { - let options = this.$options.filters.filterBy?this.$options.filters.filterBy(this.actualOptions, this.search):this.actualOptions + let options = this.$options.filters.filterBy?this.$options.filters.filterBy(this.currentOptions, this.search):this.currentOptions if (this.taggable && this.search.length && !this.optionExists(this.search)) { options.unshift(this.search) } @@ -610,11 +632,11 @@ * @return {Boolean} */ isValueEmpty() { - if (this.actualValue) { - if (typeof this.actualValue === 'object') { - return !Object.keys(this.actualValue).length + if (this.currentSelection) { + if (typeof this.currentSelection === 'object') { + return !Object.keys(this.currentSelection).length } - return !this.actualValue.length + return !this.currentSelection.length } return true; @@ -626,17 +648,17 @@ */ valueAsArray() { if (this.multiple) { - return this.actualValue - } else if (this.actualValue) { - return [this.actualValue] + return this.currentSelection + } else if (this.currentSelection) { + return [this.currentSelection] } return [] } }, created: function() { - this.actualValue = this.value - this.actualOptions = this.options.slice(0) + this.currentSelection = this.value + this.currentOptions = this.options.slice(0) this.showLoading = this.loading } diff --git a/test/unit/specs/Select.spec.js b/test/unit/specs/Select.spec.js index e960ed8..81ea301 100644 --- a/test/unit/specs/Select.spec.js +++ b/test/unit/specs/Select.spec.js @@ -68,7 +68,7 @@ describe('Select.vue', () => { options: ['one', 'two', 'three'] } }).$mount() - expect(vm.$children[0].actualValue).toEqual(vm.value) + expect(vm.$children[0].currentSelection).toEqual(vm.value) }) it('can accept an array of objects and pre-selected value (single)', () => { @@ -80,7 +80,7 @@ describe('Select.vue', () => { options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}] } }).$mount() - expect(vm.$children[0].actualValue).toEqual(vm.value) + expect(vm.$children[0].currentSelection).toEqual(vm.value) }) it('can accept an array of objects and pre-selected values (multiple)', () => { @@ -92,7 +92,7 @@ describe('Select.vue', () => { options: [{label: 'This is Foo', value: 'foo'}, {label: 'This is Bar', value: 'bar'}] } }).$mount() - expect(vm.$children[0].actualValue).toEqual(vm.value) + expect(vm.$children[0].currentSelection).toEqual(vm.value) }) it('can deselect a pre-selected object', () => { @@ -104,7 +104,7 @@ describe('Select.vue', () => { } }).$mount() vm.$children[0].select({label: 'This is Foo', value: 'foo'}) - expect(vm.$children[0].actualValue.length).toEqual(1) + expect(vm.$children[0].currentSelection.length).toEqual(1) }) it('can deselect a pre-selected string', () => { @@ -116,7 +116,7 @@ describe('Select.vue', () => { } }).$mount() vm.$children[0].select('foo') - expect(vm.$children[0].actualValue.length).toEqual(1) + expect(vm.$children[0].currentSelection.length).toEqual(1) }) it('can determine if the value prop is empty', () => { @@ -164,10 +164,10 @@ describe('Select.vue', () => { vm.multiple = false Vue.nextTick(() => { - expect(vm.$children[0].actualValue).toEqual(null) + expect(vm.$children[0].currentSelection).toEqual(null) vm.multiple = true Vue.nextTick(() => { - expect(vm.$children[0].actualValue).toEqual([]) + expect(vm.$children[0].currentSelection).toEqual([]) done() }) }) @@ -183,7 +183,7 @@ describe('Select.vue', () => { } }).$mount() vm.$children[0].options = ['one', 'five', 'six'] - expect(vm.$children[0].actualValue).toEqual(['one']) + expect(vm.$children[0].currentSelection).toEqual(['one']) }) it('can determine if an object is already selected', () => { @@ -228,7 +228,7 @@ describe('Select.vue', () => { it('should run change when multiple is true and the value changes', (done) => { const vm = new Vue({ - template: `
`, + template: `
`, methods: { cb(val) { } @@ -256,7 +256,7 @@ describe('Select.vue', () => { describe('Toggling Dropdown', () => { it('should open the dropdown when the el is clicked', (done) => { const vm = new Vue({ - template: '
', + template: '
', components: {vSelect}, data: { value: [{label: 'one'}], @@ -292,7 +292,7 @@ describe('Select.vue', () => { it('should close the dropdown on search blur', () => { const vm = new Vue({ - template: '
', + template: '
', components: {vSelect}, data: { value: [{label: 'one'}], @@ -324,7 +324,7 @@ describe('Select.vue', () => { it('should remove existing search text on escape keyup', () => { const vm = new Vue({ - template: '
', + template: '
', components: {vSelect}, data: { value: [{label: 'one'}], @@ -534,7 +534,7 @@ describe('Select.vue', () => { }).$mount() vm.$children[0].$refs.toggle.querySelector('.close').click() Vue.nextTick(() => { - expect(vm.$children[0].actualValue).toEqual([]) + expect(vm.$children[0].currentSelection).toEqual([]) done() }) }) @@ -551,7 +551,7 @@ describe('Select.vue', () => { }).$mount() vm.$children[0].maybeDeleteValue() Vue.nextTick(() => { - expect(vm.$children[0].actualValue).toEqual(['one']) + expect(vm.$children[0].currentSelection).toEqual(['one']) }) }) @@ -566,7 +566,7 @@ describe('Select.vue', () => { }).$mount() vm.$children[0].maybeDeleteValue() Vue.nextTick(() => { - expect(vm.$children[0].actualValue).toEqual(null) + expect(vm.$children[0].currentSelection).toEqual(null) }) }) }) @@ -594,7 +594,7 @@ describe('Select.vue', () => { }).$mount() expect(vm.$children[0].searchPlaceholder).toEqual('foo') - vm.$children[0].actualValue = {label: 'one'} + vm.$children[0].currentSelection = {label: 'one'} Vue.nextTick(() => { expect(vm.$children[0].searchPlaceholder).not.toBeDefined() done() @@ -670,7 +670,7 @@ describe('Select.vue', () => { searchSubmit(vm, 'three') Vue.nextTick(() => { - expect(vm.$children[0].actualValue).toEqual(['one', 'three']) + expect(vm.$children[0].currentSelection).toEqual(['one', 'three']) done() }) }) @@ -687,7 +687,7 @@ describe('Select.vue', () => { searchSubmit(vm, 'two') Vue.nextTick(() => { - expect(vm.$children[0].actualValue).toEqual([{label: 'one'}, {label: 'two'}]) + expect(vm.$children[0].currentSelection).toEqual([{label: 'one'}, {label: 'two'}]) done() }) }) @@ -703,7 +703,7 @@ describe('Select.vue', () => { }).$mount() searchSubmit(vm, 'three') - expect(vm.$children[0].actualOptions).toEqual(['one', 'two', 'three']) + expect(vm.$children[0].currentOptions).toEqual(['one', 'two', 'three']) }) it('wont add a freshly created option/tag to the options list when pushTags is false', () => { @@ -717,7 +717,7 @@ describe('Select.vue', () => { }).$mount() searchSubmit(vm, 'three') - expect(vm.$children[0].actualOptions).toEqual(['one', 'two']) + expect(vm.$children[0].currentOptions).toEqual(['one', 'two']) }) it('should select an existing option if the search string matches a string from options', (done) => { @@ -735,7 +735,7 @@ describe('Select.vue', () => { searchSubmit(vm) Vue.nextTick(() => { - expect(vm.$children[0].actualValue[0]).toBe(two) + expect(vm.$children[0].currentSelection[0]).toBe(two) done() }) }) @@ -757,7 +757,7 @@ describe('Select.vue', () => { // This needs to be wrapped in nextTick() twice so that filteredOptions can // calculate after setting the search text, and move the typeAheadPointer index to 0. Vue.nextTick(() => { - expect(vm.$children[0].actualValue.label).toBe(two.label) + expect(vm.$children[0].currentSelection.label).toBe(two.label) done() }) }) @@ -773,7 +773,7 @@ describe('Select.vue', () => { }).$mount() vm.$children[0].options = [{label: 'two'}] Vue.nextTick(() => { - expect(vm.$children[0].actualValue).toEqual([{label: 'one'}]) + expect(vm.$children[0].currentSelection).toEqual([{label: 'one'}]) done() }) }) @@ -876,7 +876,7 @@ describe('Select.vue', () => { }).$mount() vm.$children[0].options = ['four', 'five', 'six'] Vue.nextTick(() => { - expect(vm.$children[0].actualValue).toEqual('one') + expect(vm.$children[0].currentSelection).toEqual('one') done() }) }) @@ -892,7 +892,7 @@ describe('Select.vue', () => { }).$mount() vm.$children[0].options = ['four', 'five', 'six'] Vue.nextTick(() => { - expect(vm.$children[0].actualValue).toEqual(null) + expect(vm.$children[0].currentSelection).toEqual(null) done() }) })