From 1cba9458a24d88c61bbf9549d37212351c5c873e Mon Sep 17 00:00:00 2001 From: cassmbautista <35530271+cassmbautista@users.noreply.github.com> Date: Thu, 20 Feb 2020 16:48:17 -0500 Subject: [PATCH] Feature/add multiselect filter (#587) * Added multiselect to VGT * update docs * add dependancy to package-lock * allow multiselect to use array of objects --- dev/App.vue | 41 +++++++++++++++++++ package-lock.json | 5 +++ package.json | 3 +- src/components/Table.vue | 33 +++++++++++++++ src/components/VgtFilterRow.vue | 26 +++++++++++- src/index.js | 2 + src/styles/_input.scss | 21 +++++++++- src/styles/black-rhino/black-rhino.scss | 10 +++++ src/styles/nocturnal/nocturnal.scss | 10 +++++ src/styles/style.scss | 3 ++ .../configuration/column-filter-options.md | 20 +++++++++ 11 files changed, 171 insertions(+), 3 deletions(-) diff --git a/dev/App.vue b/dev/App.vue index 3c5ed714..101de71b 100644 --- a/dev/App.vue +++ b/dev/App.vue @@ -135,6 +135,30 @@ export default { ], }, }, + { + label: 'Multiselect', + field: 'multiselect', + filterOptions: { + enabled: true, + filterMultiselectDropdownItems: [ + { id: 1, label: 'hello'}, + 'oh no' + ], + }, + }, + { + label: 'Average age', + field: 'average', + type: 'number', + filterOptions: { + enabled: true, + filterMultiselectDropdownItems: [ + 1, + 1.5, + 2 + ], + }, + }, ], rows: [ // { id:1, name:"John", age: 20, createdAt: '2018-02-18T00:00:43-05:00',score: 0.03343 }, @@ -146,6 +170,8 @@ export default { score: 0.03343, bool: true, exact: 'match', + multiselect: 'hello', + average: 1 }, { id: 3, @@ -155,6 +181,8 @@ export default { score: 0.03343, bool: true, exact: 'match', + multiselect: 'oh no', + average: null }, { id: 4, @@ -164,6 +192,7 @@ export default { score: 0.03343, bool: false, exact: null, + multiselect: null }, { id: 5, @@ -173,6 +202,8 @@ export default { score: 0.03343, bool: null, exact: 'rematch', + multiselect: 'hello world', + average: 2 }, { id: 5, @@ -182,6 +213,8 @@ export default { score: 0.03343, bool: null, exact: 'rematch', + multiselect: 'hello', + average: 3 }, { id: 5, @@ -191,6 +224,8 @@ export default { score: 0.03343, bool: null, exact: null, + multiselect: 'hello', + average: 2 }, { id: 6, @@ -200,6 +235,8 @@ export default { score: 0.03343, bool: true, exact: 'match', + multiselect: 'hello', + average: 1.5 }, { id: 7, @@ -209,6 +246,8 @@ export default { score: null, bool: 'false', exact: null, + multiselect: 'hello', + average: 1 }, { id: 8, @@ -218,6 +257,8 @@ export default { score: 0.03343, bool: true, exact: 'rematch', + multiselect: 'hello', + average: 1 }, ], }; diff --git a/package-lock.json b/package-lock.json index 58d11d25..a7cea7bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13066,6 +13066,11 @@ "integrity": "sha512-yodqdAWt/QrUkb51jN2DS4dtF4vQWg5YejYdBAcHIOi6kBoGLRVEDz5NYGdh5IhzLrElgi+eKX1DQJmj3bCuJw==", "dev": true }, + "vue-select": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/vue-select/-/vue-select-3.4.0.tgz", + "integrity": "sha512-UHd0fiUjPgRmHIGhI6yUKtnJsHOdvzD00QUGUtD+FaxWWZRWF2AAb7KPZRj0j/egVfZQvey6M6woHn78GbTogA==" + }, "vue-server-renderer": { "version": "2.6.10", "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.10.tgz", diff --git a/package.json b/package.json index 3518eb7a..d61f49fc 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,8 @@ "lodash.clonedeep": "^4.5.0", "lodash.filter": "^4.6.0", "lodash.foreach": "^4.5.0", - "lodash.isequal": "^4.5.0" + "lodash.isequal": "^4.5.0", + "vue-select": "^3.1.0" }, "devDependencies": { "@vuepress/plugin-google-analytics": "^1.0.0-rc.1", diff --git a/src/components/Table.vue b/src/components/Table.vue index bf1e8780..49dd4205 100644 --- a/src/components/Table.vue +++ b/src/components/Table.vue @@ -1175,6 +1175,33 @@ export default { return classes; }, + filterMultiselectItems(column, row) { + const columnFieldName = column.field; + const columnFilters = this.columnFilters[columnFieldName]; + if (column.filterOptions && column.filterOptions.filterMultiselectDropdownItems) { + if (columnFilters.length === 0) { + return true; + } + + // Otherwise Use default filters + const typeDef = column.typeDef; + for (let filter of columnFilters) { + let filterLabel = filter; + if (typeof filter === 'object') { + filterLabel = filter.label; + } + if (typeDef.filterPredicate( + this.collect(row, columnFieldName), + filterLabel + )) { + return true; + } + } + return false; + } + return undefined; + }, + // method to filter rows filterRows(columnFilters, fromFilter = true) { // if (!this.rows.length) return; @@ -1228,6 +1255,12 @@ export default { this.columnFilters[col.field] ); } + + const filterMultiselect = this.filterMultiselectItems(col, row); + if (filterMultiselect !== undefined) { + return filterMultiselect; + } + // Otherwise Use default filters const { typeDef } = col; return typeDef.filterPredicate( diff --git a/src/components/VgtFilterRow.vue b/src/components/VgtFilterRow.vue index 2a827520..27f4160b 100644 --- a/src/components/VgtFilterRow.vue +++ b/src/components/VgtFilterRow.vue @@ -8,7 +8,7 @@
- {{ option.text }} + + +
@@ -93,6 +103,12 @@ export default { methods: { reset(emitEvent = false) { this.columnFilters = {}; + + // Clear the selection in the multiselect + this.$refs['vgt-multiselect'].forEach((ref) => { + ref.clearSelection(); + }); + if (emitEvent) { this.$emit('filter-changed', this.columnFilters); } @@ -119,6 +135,14 @@ export default { && typeof column.filterOptions.filterDropdownItems[0] !== 'object'; }, + isMultiselectDropdown(column) { + return ( + this.isFilterable(column) && + column.filterOptions && + column.filterOptions.filterMultiselectDropdownItems + ); + }, + // get column's defined placeholder or default one getPlaceholder(column) { const placeholder = (this.isFilterable(column) && column.filterOptions.placeholder) || `Filter ${column.label}`; diff --git a/src/index.js b/src/index.js index 73bb27d5..b06940d7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,9 @@ import VueGoodTable from './components/Table.vue'; +import vSelect from 'vue-select'; const VueGoodTablePlugin = { install(Vue, options) { + Vue.component('v-select', vSelect); Vue.component(VueGoodTable.name, VueGoodTable); }, }; diff --git a/src/styles/_input.scss b/src/styles/_input.scss index 6f3ee0de..125256cd 100644 --- a/src/styles/_input.scss +++ b/src/styles/_input.scss @@ -22,4 +22,23 @@ outline: none; border-color: $link-color; } -} \ No newline at end of file +} + +.v-select { + border-radius: 4px; + color: $text-color; + &::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ + color: $text-color; + opacity: 0.3; /* Firefox */ + } + &:focus { + outline: none; + border-color: $link-color; + } + input { + color: $text-color + } + .vs__open-indicator { + fill: $text-color + } +} diff --git a/src/styles/black-rhino/black-rhino.scss b/src/styles/black-rhino/black-rhino.scss index ffc8a882..9756c89e 100644 --- a/src/styles/black-rhino/black-rhino.scss +++ b/src/styles/black-rhino/black-rhino.scss @@ -68,6 +68,16 @@ opacity: 0.3; /* Firefox */ } } + + .v-select { + background-color: $input-bg; + input { + color: $text-color; + } + .vs__open-indicator { + fill: $text-color + } + } } .vgt-wrap.black-rhino{ diff --git a/src/styles/nocturnal/nocturnal.scss b/src/styles/nocturnal/nocturnal.scss index c3b03fd5..e1edc9fe 100644 --- a/src/styles/nocturnal/nocturnal.scss +++ b/src/styles/nocturnal/nocturnal.scss @@ -64,6 +64,16 @@ opacity: 0.3; /* Firefox */ } } + + .v-select { + background-color: darken($thead-bg-color-2, 5%); + input { + color: $text-color; + } + .vs__open-indicator { + fill: $text-color + } + } } .vgt-wrap.nocturnal{ diff --git a/src/styles/style.scss b/src/styles/style.scss index d5ba6d1f..ac842312 100644 --- a/src/styles/style.scss +++ b/src/styles/style.scss @@ -1,3 +1,6 @@ +// V-select plugin +@import "../../node_modules/vue-select/dist/vue-select"; + // base table styles @import './variables'; @import './utils'; diff --git a/vp-docs/guide/configuration/column-filter-options.md b/vp-docs/guide/configuration/column-filter-options.md index d694e84f..cd147a77 100644 --- a/vp-docs/guide/configuration/column-filter-options.md +++ b/vp-docs/guide/configuration/column-filter-options.md @@ -16,6 +16,7 @@ columns: [ placeholder: 'Filter This Thing', // placeholder for filter input filterValue: 'Jane', // initial populated value for this filter filterDropdownItems: [], // dropdown (with selected values) instead of text input + filterMultiselectDropdownItems: [], // dropdown (with multiple selected values) instead of text input filterFn: this.columnFilterFn, //custom filter function that trigger: 'enter', //only trigger on enter not on keyup }, @@ -61,6 +62,25 @@ filterDropdownItems: [ ], ``` +## filterMultiselectDropdownItems + +type `Array of strings` or `Array of objects` with labels + +allows creating a dropdown for filtering multiple items as opposed to an input + +```javascript +//array of strings +filterMultiselectDropdownItems: ['Blue', 'Red', 'Yellow'] +``` +```javascript +//array of objects +filterMultiselectDropdownItems: [ + { id: 1, label: 'Blue' }, + { id: 2, label: 'Red' }, + { id: 3, label: 'Yellow' } +] +``` + ## filterFn type `Function`