Skip to content

Commit a3f62ad

Browse files
author
Jeff
committed
Merge branch 'feature/custom-filter'
# Conflicts: # dev.html # yarn.lock
2 parents f72d460 + f4441ae commit a3f62ad

File tree

8 files changed

+282
-120
lines changed

8 files changed

+282
-120
lines changed

.travis.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
language: node_js
22
node_js:
3-
- "5"
4-
- "5.1"
3+
- node
4+
- 8
5+
- 6
56
after_success:
67
- codeclimate-test-reporter < ./test/unit/coverage/lcov.info

dev.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<div id="app">
3232
<v-select placeholder="default" :options="options"></v-select>
3333
<v-select placeholder="default, RTL" :options="options" dir="rtl"></v-select>
34+
<v-select placeholder="default, options=[1,5,10]" :options="[1,5,10]"></v-select>
3435
<v-select placeholder="multiple" multiple :options="options"></v-select>
3536
<v-select placeholder="multiple, taggable" multiple taggable :options="options" no-drop></v-select>
3637
<v-select placeholder="multiple, taggable, push-tags" multiple push-tags taggable :options="[{label: 'Foo', value: 'foo'}]"></v-select>
@@ -47,8 +48,14 @@
4748
</template>
4849
</v-select>
4950
<v-select placeholder="disabled" disabled value="disabled"></v-select>
50-
<v-select placeholder="disabled multiple" disabled multiple :value="['disabled', 'multiple']"></v-select>
51+
<v-select placeholder="disabled multiple" disabled multiple :value="['disabled', 'multiple']"></v-select>
5152
<v-select placeholder="filterable=false, @search=searchPeople" label="first_name" :filterable="false" @search="searchPeople" :options="people"></v-select>
53+
<v-select placeholder="filtering with fuse.js" label="title" :options="fuseSearchOptions" :filter="fuseSearch">
54+
<template slot="option" scope="option">
55+
<strong>{{ option.title }}</strong><br>
56+
<em>{{ `${option.author.firstName} ${option.author.lastName}` }}</em>
57+
</template>
58+
</v-select>
5259
</div>
5360
</body>
5461

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"extract-text-webpack-plugin": "^1.0.1",
4141
"file-loader": "^0.8.4",
4242
"function-bind": "^1.0.2",
43+
"fuse.js": "^3.2.0",
4344
"gh-pages": "^0.11.0",
4445
"gitbook-plugin-codepen": "^0.1.2",
4546
"gitbook-plugin-edit-link": "^2.0.2",

src/components/Select.vue

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -525,21 +525,6 @@
525525
}
526526
},
527527
528-
/**
529-
* Callback to filter the search result the label text.
530-
* @type {Function}
531-
* @param {Object || String} option
532-
* @param {String} label
533-
* @param {String} search
534-
* @return {Boolean}
535-
*/
536-
filterFunction: {
537-
type: Function,
538-
default(option, label, search) {
539-
return (label || '').toLowerCase().indexOf(search.toLowerCase()) > -1
540-
}
541-
},
542-
543528
/**
544529
* An optional callback function that is called each time the selected
545530
* value(s) change. When integrating with Vuex, use this callback to trigger
@@ -593,6 +578,47 @@
593578
default: true
594579
},
595580
581+
/**
582+
* Callback to determine if the provided option should
583+
* match the current search text. Used to determine
584+
* if the option should be displayed.
585+
* @type {Function}
586+
* @param {Object || String} option
587+
* @param {String} label
588+
* @param {String} search
589+
* @return {Boolean}
590+
*/
591+
filterBy: {
592+
type: Function,
593+
default(option, label, search) {
594+
return (label || '').toLowerCase().indexOf(search.toLowerCase()) > -1
595+
}
596+
},
597+
598+
/**
599+
* Callback to filter results when search text
600+
* is provided. Default implementation loops
601+
* each option, and returns the result of
602+
* this.filterBy.
603+
* @type {Function}
604+
* @param {Array} list of options
605+
* @param {String} search text
606+
* @param {Object} vSelect instance
607+
* @return {Boolean}
608+
*/
609+
filter: {
610+
"type": Function,
611+
default(options, search) {
612+
return options.filter((option) => {
613+
let label = this.getOptionLabel(option)
614+
if (typeof label === 'number') {
615+
label = label.toString()
616+
}
617+
return this.filterBy(option, label, search)
618+
});
619+
}
620+
},
621+
596622
/**
597623
* User defined function for adding Options
598624
* @type {Function}
@@ -988,13 +1014,7 @@
9881014
if (!this.filterable && !this.taggable) {
9891015
return this.mutableOptions.slice()
9901016
}
991-
let options = this.mutableOptions.filter((option) => {
992-
let label = this.getOptionLabel(option)
993-
if (typeof label === 'number') {
994-
label = label.toString()
995-
}
996-
return this.filterFunction(option, label, this.search)
997-
})
1017+
let options = this.search.length ? this.filter(this.mutableOptions, this.search, this) : this.mutableOptions;
9981018
if (this.taggable && this.search.length && !this.optionExists(this.search)) {
9991019
options.unshift(this.search)
10001020
}

src/dev.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import Vue from 'vue'
2-
import vSelect from './components/Select.vue'
3-
import countries from 'docs/data/advanced.js'
2+
import Fuse from 'fuse.js'
43
import debounce from 'lodash/debounce'
54
import resource from 'vue-resource'
5+
import vSelect from './components/Select.vue'
6+
import countries from 'docs/data/advanced.js'
7+
import fuseSearchOptions from './fuseSearchOptions'
68

79
Vue.use(resource)
810

@@ -18,11 +20,12 @@ new Vue({
1820
value: null,
1921
options: countries,
2022
ajaxRes: [],
21-
people: []
23+
people: [],
24+
fuseSearchOptions
2225
},
2326
methods: {
2427
search(search, loading) {
25-
loading(true)
28+
loading(true);
2629
this.getRepositories(search, loading, this)
2730
},
2831
searchPeople(search, loading) {
@@ -37,9 +40,14 @@ new Vue({
3740
}, 250),
3841
getRepositories: debounce((search, loading, vm) => {
3942
vm.$http.get(`https://api.github.com/search/repositories?q=${search}`).then(res => {
40-
vm.ajaxRes = res.data.items
43+
vm.ajaxRes = res.data.items;
4144
loading(false)
4245
})
43-
}, 250)
46+
}, 250),
47+
fuseSearch(options, search) {
48+
return new Fuse(options, {
49+
keys: ['title', 'author.firstName', 'author.lastName'],
50+
}).search(search);
51+
}
4452
}
45-
})
53+
});

src/fuseSearchOptions.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
export default [
2+
{
3+
title: "Old Man's War",
4+
author: {
5+
firstName: "John",
6+
lastName: "Scalzi"
7+
}
8+
},
9+
{
10+
title: "The Lock Artist",
11+
author: {
12+
firstName: "Steve",
13+
lastName: "Hamilton"
14+
}
15+
},
16+
{
17+
title: "HTML5",
18+
author: {
19+
firstName: "Remy",
20+
lastName: "Sharp"
21+
}
22+
},
23+
{
24+
title: "Right Ho Jeeves",
25+
author: {
26+
firstName: "P.D",
27+
lastName: "Woodhouse"
28+
}
29+
},
30+
{
31+
title: "The Code of the Wooster",
32+
author: {
33+
firstName: "P.D",
34+
lastName: "Woodhouse"
35+
}
36+
},
37+
{
38+
title: "Thank You Jeeves",
39+
author: {
40+
firstName: "P.D",
41+
lastName: "Woodhouse"
42+
}
43+
},
44+
{
45+
title: "The DaVinci Code",
46+
author: {
47+
firstName: "Dan",
48+
lastName: "Brown"
49+
}
50+
},
51+
{
52+
title: "Angels & Demons",
53+
author: {
54+
firstName: "Dan",
55+
lastName: "Brown"
56+
}
57+
},
58+
{
59+
title: "The Silmarillion",
60+
author: {
61+
firstName: "J.R.R",
62+
lastName: "Tolkien"
63+
}
64+
},
65+
{
66+
title: "Syrup",
67+
author: {
68+
firstName: "Max",
69+
lastName: "Barry"
70+
}
71+
},
72+
{
73+
title: "The Lost Symbol",
74+
author: {
75+
firstName: "Dan",
76+
lastName: "Brown"
77+
}
78+
},
79+
{
80+
title: "The Book of Lies",
81+
author: {
82+
firstName: "Brad",
83+
lastName: "Meltzer"
84+
}
85+
},
86+
{
87+
title: "Lamb",
88+
author: {
89+
firstName: "Christopher",
90+
lastName: "Moore"
91+
}
92+
},
93+
{
94+
title: "Fool",
95+
author: {
96+
firstName: "Christopher",
97+
lastName: "Moore"
98+
}
99+
},
100+
{
101+
title: "Incompetence",
102+
author: {
103+
firstName: "Rob",
104+
lastName: "Grant"
105+
}
106+
},
107+
{
108+
title: "Fat",
109+
author: {
110+
firstName: "Rob",
111+
lastName: "Grant"
112+
}
113+
},
114+
{
115+
title: "Colony",
116+
author: {
117+
firstName: "Rob",
118+
lastName: "Grant"
119+
}
120+
},
121+
{
122+
title: "Backwards, Red Dwarf",
123+
author: {
124+
firstName: "Rob",
125+
lastName: "Grant"
126+
}
127+
},
128+
{
129+
title: "The Grand Design",
130+
author: {
131+
firstName: "Stephen",
132+
lastName: "Hawking"
133+
}
134+
},
135+
{
136+
title: "The Book of Samson",
137+
author: {
138+
firstName: "David",
139+
lastName: "Maine"
140+
}
141+
},
142+
{
143+
title: "The Preservationist",
144+
author: {
145+
firstName: "David",
146+
lastName: "Maine"
147+
}
148+
},
149+
{
150+
title: "Fallen",
151+
author: {
152+
firstName: "David",
153+
lastName: "Maine"
154+
}
155+
},
156+
{
157+
title: "Monster 1959",
158+
author: {
159+
firstName: "David",
160+
lastName: "Maine"
161+
}
162+
}
163+
]

test/unit/specs/Select.spec.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,9 +322,9 @@ describe('Select.vue', () => {
322322
expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify([{label: 'Bar', value: 'bar'}, {label: 'Baz', value: 'baz'}]))
323323
})
324324

325-
it('can use a custom filterFunction passed via props', ()=>{
325+
it('can determine if a given option should match the current search text', () => {
326326
const vm = new Vue({
327-
template: `<div><v-select ref="select" :filterFunction="customFn" :options="[{label: 'Aoo', value: 'foo'}, {label: 'Bar', value: 'bar'}, {label: 'Baz', value: 'baz'}]" v-model="value"></v-select></div>`,
327+
template: `<div><v-select ref="select" :filter-by="customFn" :options="[{label: 'Aoo', value: 'foo'}, {label: 'Bar', value: 'bar'}, {label: 'Baz', value: 'baz'}]" v-model="value"></v-select></div>`,
328328
data: {value: 'foo'},
329329
methods:{
330330
customFn: (option, label, search) => label.match(new RegExp('^' + search, 'i'))
@@ -333,6 +333,35 @@ describe('Select.vue', () => {
333333
vm.$refs.select.search = 'a'
334334
expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify([{label: 'Aoo', value: 'foo'}]))
335335
})
336+
337+
it('can use a custom filtering method', () => {
338+
const vm = new Vue({
339+
template: `<div><v-select ref="select" :filter="customFn" :options="options" v-model="value"></v-select></div>`,
340+
data: {
341+
options: ['foo','bar','baz'],
342+
value: 'foo'
343+
},
344+
methods:{
345+
customFn(options, search, vm) {
346+
return options.filter(option => option.indexOf(search) > 0)
347+
}
348+
}
349+
}).$mount()
350+
vm.$refs.select.search = 'a'
351+
expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify(['bar','baz']))
352+
})
353+
354+
it('can filter arrays of numbers', () => {
355+
const vm = new Vue({
356+
template: `<div><v-select ref="select" :options="options"></v-select></div>`,
357+
data: {
358+
options: [1,5,10],
359+
value: 'foo'
360+
},
361+
}).$mount()
362+
vm.$refs.select.search = '5'
363+
expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify([5]))
364+
})
336365
})
337366

338367
describe('Toggling Dropdown', () => {
@@ -780,6 +809,7 @@ describe('Select.vue', () => {
780809
const vm = new Vue({
781810
template: '<div><v-select :options="[{}]"></v-select></div>',
782811
}).$mount()
812+
vm.$children[0].open = true
783813
Vue.nextTick(() => {
784814
expect(console.warn).toHaveBeenCalledWith(
785815
'[vue-select warn]: Label key "option.label" does not exist in options object {}.' +

0 commit comments

Comments
 (0)