@@ -21,12 +21,19 @@ export default {
2121 filterIncludedFields : {
2222 type : Array
2323 // default: undefined
24+ } ,
25+ filterDebounce : {
26+ type : [ Number , String ] ,
27+ default : 0 ,
28+ validator : val => / ^ \d + / . test ( String ( val ) )
2429 }
2530 } ,
2631 data ( ) {
2732 return {
2833 // Flag for displaying which empty slot to show and some event triggering
29- isFiltered : false
34+ isFiltered : false ,
35+ // Where we store the copy of the filter citeria after debouncing
36+ localFilter : null
3037 }
3138 } ,
3239 computed : {
@@ -36,6 +43,9 @@ export default {
3643 computedFilterIncluded ( ) {
3744 return this . filterIncludedFields ? concat ( this . filterIncludedFields ) . filter ( Boolean ) : null
3845 } ,
46+ computedFilterDebounce ( ) {
47+ return parseInt ( this . filterDebounce , 10 ) || 0
48+ } ,
3949 localFiltering ( ) {
4050 return this . hasProvider ? ! ! this . noProviderFiltering : true
4151 } ,
@@ -47,22 +57,6 @@ export default {
4757 localFilter : this . localFilter
4858 }
4959 } ,
50- // Sanitized/normalized version of filter prop
51- localFilter ( ) {
52- // Using internal filter function, which only accepts string or RegExp
53- if (
54- this . localFiltering &&
55- ! isFunction ( this . filterFunction ) &&
56- ! ( isString ( this . filter ) || isRegExp ( this . filter ) )
57- ) {
58- return ''
59- }
60-
61- // Could be a string, object or array, as needed by external filter function
62- // We use `cloneDeep` to ensure we have a new copy of an object or array
63- // without Vue reactive observers
64- return cloneDeep ( this . filter )
65- } ,
6660 // Sanitized/normalize filter-function prop
6761 localFilterFn ( ) {
6862 // Return `null` to signal to use internal filter function
@@ -72,13 +66,14 @@ export default {
7266 // Returns the original `localItems` array if not sorting
7367 filteredItems ( ) {
7468 const items = this . localItems || [ ]
69+ // Note the criteria is debounced
70+ const criteria = this . filterSanitize ( this . localFilter )
7571
7672 // Resolve the filtering function, when requested
7773 // We prefer the provided filtering function and fallback to the internal one
7874 // When no filtering criteria is specified the filtering factories will return `null`
7975 let filterFn = null
8076 if ( this . localFiltering ) {
81- const criteria = this . localFilter
8277 filterFn =
8378 this . filterFnFactory ( this . localFilterFn , criteria ) ||
8479 this . defaultFilterFnFactory ( criteria )
@@ -94,6 +89,32 @@ export default {
9489 }
9590 } ,
9691 watch : {
92+ // Watch for debounce being set to 0
93+ computedFilterDebounce ( newVal , oldVal ) {
94+ if ( ! newVal && this . filterTimer ) {
95+ clearTimeout ( this . filterTimer )
96+ this . filterTimer = null
97+ this . localFilter = this . filter
98+ }
99+ } ,
100+ // Watch for changes to the filter criteria, and debounce if necessary
101+ filter ( newFilter , oldFilter ) {
102+ const timeout = this . computedFilterDebounce
103+ if ( this . filterTimer ) {
104+ clearTimeout ( this . filterTimer )
105+ this . filterTimer = null
106+ }
107+ if ( timeout ) {
108+ // If we have a debounce time, delay the update of this.localFilter
109+ this . filterTimer = setTimeout ( ( ) => {
110+ this . filterTimer = null
111+ this . localFilter = this . filterSanitize ( this . filter )
112+ } , timeout )
113+ } else {
114+ // Otherwise, immediately update this.localFilter
115+ this . localFilter = this . filterSanitize ( this . filter )
116+ }
117+ } ,
97118 // Watch for changes to the filter criteria and filtered items vs localItems).
98119 // And set visual state and emit events as required
99120 filteredCheck ( { filteredItems, localItems, localFilter } ) {
@@ -123,13 +144,42 @@ export default {
123144 }
124145 } ,
125146 created ( ) {
147+ // Create non-reactive prop where we store the debounce timer id
148+ this . filterTimer = null
149+ // If filter is "pre-set", set the criteria
150+ // This will trigger any watchers/dependants
151+ this . localFilter = this . filterSanitize ( this . filter )
126152 // Set the initial filtered state.
127153 // In a nextTick so that we trigger a filtered event if needed
128154 this . $nextTick ( ( ) => {
129155 this . isFiltered = Boolean ( this . localFilter )
130156 } )
131157 } ,
158+ beforeDestroy ( ) {
159+ /* istanbul ignore next */
160+ if ( this . filterTimer ) {
161+ clearTimeout ( this . filterTimer )
162+ this . filterTimer = null
163+ }
164+ } ,
132165 methods : {
166+ filterSanitize ( criteria ) {
167+ // Sanitizes filter criteria based on internal or external filtering
168+ if (
169+ this . localFiltering &&
170+ ! isFunction ( this . filterFunction ) &&
171+ ! ( isString ( criteria ) || isRegExp ( criteria ) )
172+ ) {
173+ // If using internal filter function, which only accepts string or RegExp
174+ // return null to signify no filter
175+ return null
176+ }
177+
178+ // Could be a string, object or array, as needed by external filter function
179+ // We use `cloneDeep` to ensure we have a new copy of an object or array
180+ // without Vue's reactive observers
181+ return cloneDeep ( criteria )
182+ } ,
133183 // Filter Function factories
134184 filterFnFactory ( filterFn , criteria ) {
135185 // Wrapper factory for external filter functions
0 commit comments