├── .gitignore ├── README.md ├── composer.json ├── dist ├── js │ └── filter.js └── mix-manifest.json ├── package.json ├── resources └── js │ ├── components │ ├── Filter.vue │ └── SelectMultiple.vue │ └── filter.js ├── screenshot.png ├── src ├── FilterServiceProvider.php └── MultiselectFilter.php ├── webpack.mix.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /node_modules 4 | package-lock.json 5 | composer.phar 6 | composer.lock 7 | phpunit.xml 8 | .phpunit.result.cache 9 | .DS_Store 10 | Thumbs.db 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nova Multiselect Filter 2 | 3 | Provides capability of selecting multiple values with Nova Resource filter. 4 | 5 | ![Nova Multiselect Field](./screenshot.png) 6 | 7 | ## Installation 8 | 9 | You can install the package in to a Laravel app that uses [Nova](https://nova.laravel.com) via composer: 10 | 11 | ```bash 12 | composer require rcknr/nova-multiselect-filter 13 | ``` 14 | 15 | ## Usage 16 | 17 | Use `MultiselectFilter` class instead of `Filter`: 18 | 19 | ```php 20 | use rcknr\Nova\Filters\MultiselectFilter; 21 | 22 | class UserType extends MultiselectFilter 23 | { 24 | public function apply(Request $request, $query, $value) 25 | { 26 | return $query->whereIn('user_role', $value); 27 | } 28 | 29 | public function options(Request $request) 30 | { 31 | return [ 32 | 'Administrator' => 'admin', 33 | 'Editor' => 'editor', 34 | ]; 35 | } 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rcknr/nova-multiselect-filter", 3 | "description": "A Laravel Nova filter that allows multiple selection.", 4 | "keywords": [ 5 | "laravel", 6 | "nova" 7 | ], 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Sergii Kauk", 12 | "email": "sergii@kauk.at", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=7.1.0" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "rcknr\\Nova\\Filters\\": "src/" 22 | } 23 | }, 24 | "extra": { 25 | "laravel": { 26 | "providers": [ 27 | "rcknr\\Nova\\Filters\\FilterServiceProvider" 28 | ] 29 | } 30 | }, 31 | "config": { 32 | "sort-packages": true 33 | }, 34 | "minimum-stability": "dev", 35 | "prefer-stable": true 36 | } 37 | -------------------------------------------------------------------------------- /dist/js/filter.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var s=t[r]={i:r,l:!1,exports:{}};return e[r].call(s.exports,s,s.exports,n),s.l=!0,s.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=1)}([function(e,t){e.exports=function(e,t,n,r,s,o){var i,l=e=e||{},a=typeof e.default;"object"!==a&&"function"!==a||(i=e,l=e.default);var c,u="function"==typeof l?l.options:l;if(t&&(u.render=t.render,u.staticRenderFns=t.staticRenderFns,u._compiled=!0),n&&(u.functional=!0),s&&(u._scopeId=s),o?(c=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),r&&r.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(o)},u._ssrRegister=c):r&&(c=r),c){var d=u.functional,f=d?u.render:u.beforeCreate;d?(u._injectStyles=c,u.render=function(e,t){return c.call(t),f(e,t)}):u.beforeCreate=f?[].concat(f,c):[c]}return{esModule:i,exports:l,options:u}}},function(e,t,n){e.exports=n(2)},function(e,t,n){Nova.booting(function(e){e.component("multiselect-filter",n(3))})},function(e,t,n){var r=n(0)(n(4),n(8),!1,null,null,null);e.exports=r.exports},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(5),s=n.n(r);t.default={components:{SelectMultiple:s.a},props:{resourceName:{type:String,required:!0},filterKey:{type:String,required:!0},lens:String},methods:{handleChange:function(e){this.$store.commit(this.resourceName+"/updateFilterState",{filterClass:this.filterKey,value:e}),this.$emit("change")}},computed:{filter:function(){return this.$store.getters[this.resourceName+"/getFilter"](this.filterKey)},value:function(){return this.filter.currentValue}}}},function(e,t,n){var r=n(0)(n(6),n(7),!1,null,null,null);e.exports=r.exports},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={props:{options:{type:Array,default:[]},value:{default:""}},data:function(){return{showDropdown:!1,selected:[]}},computed:{availableOptions:function(){var e=this;return this.options.filter(function(t){return!e.selected.includes(t)})}},methods:{select:function(e){this.showDropdown=!1,this.selected.push(e)},remove:function(e){this.selected.splice(e,1)},toggle:function(e){[this.$el,this.$refs.selected].includes(e.target)&&this.availableOptions.length>0?this.showDropdown=!this.showDropdown:this.showDropdown=!1}},mounted:function(){var e=this;document.addEventListener("click",this.toggle),this.selected=this.options.filter(function(t){return e.value.includes(t.value)}),console.log(this)},watch:{selected:function(){this.$emit("change",this.selected.map(function(e){return e.value}))}}}},function(e,t){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"flex h-auto",attrs:{tabindex:"-1"}},[0===e.selected.length?n("div",{staticClass:"h-8 pt-1 leading-normal"},[e._v("—")]):n("ul",{ref:"selected",staticClass:"list-reset flex flex-wrap text-sm -ml-2 pb-1"},e._l(e.selected,function(t,r){return n("li",{staticClass:"bg-primary text-white rounded -ml-0 mt-1 mr-1 px-2 py-1 hover:bg-primary-dark",class:t.value,on:{click:function(t){return e.remove(r)}}},[e._v("\n "+e._s(t.name)+"\n ")])}),0),e._v(" "),e.showDropdown&&e.availableOptions.length?n("ul",{staticClass:"list-reset absolute top-auto w-5/6 -ml-6 py-1 border border-60 rounded-lg bg-30"},e._l(e.availableOptions,function(t){return n("li",{staticClass:"px-3 py-1 hover:text-white hover:bg-primary-dark",on:{click:function(n){return e.select(t)}}},[e._v("\n "+e._s(t.name)+"\n ")])}),0):e._e()])},staticRenderFns:[]}},function(e,t){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",[n("h3",{staticClass:"text-sm uppercase tracking-wide text-80 bg-30 p-3"},[e._v("\n "+e._s(e.filter.name)+"\n ")]),e._v(" "),n("div",{staticClass:"p-2"},[n("select-multiple",{staticClass:"block w-full form-control-sm form-select",attrs:{dusk:e.filter.name+"-filter-select",options:e.filter.options,value:e.value},on:{change:e.handleChange}})],1)])},staticRenderFns:[]}}]); -------------------------------------------------------------------------------- /dist/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/filter.js": "/js/filter.js" 3 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "cross-env": "^5.0.0", 14 | "laravel-mix": "^1.0", 15 | "laravel-nova": "^1.0" 16 | }, 17 | "dependencies": { 18 | "vue": "^2.5.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /resources/js/components/Filter.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 62 | 63 | -------------------------------------------------------------------------------- /resources/js/components/SelectMultiple.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 70 | -------------------------------------------------------------------------------- /resources/js/filter.js: -------------------------------------------------------------------------------- 1 | Nova.booting(Vue => { 2 | Vue.component('multiselect-filter', require('./components/Filter')); 3 | }) 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcknr/nova-multiselect-filter/ac43fe6e9f1339deab00270ad78ffe45fd1807dc/screenshot.png -------------------------------------------------------------------------------- /src/FilterServiceProvider.php: -------------------------------------------------------------------------------- 1 |