├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── dist ├── js │ └── dynamic-action-fields.js └── mix-manifest.json ├── package.json ├── resources └── js │ ├── components │ └── DynamicActionFieldModal.vue │ └── dynamic-action-fields.js ├── routes └── api.php ├── screenshot.png ├── src ├── DynamicFieldAction.php ├── DynamicFieldsController.php └── DynamicFieldsServiceProvider.php └── webpack.mix.js /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Tunezilla Software Ltd 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Action Fields for Laravel Nova 2 | 3 | This package allows you to change the fields of a Laravel Nova Action depending on which resources are selected. 4 | 5 | **This is experimental, proceed at your own risk** 6 | 7 | ## Screenshot 8 | 9 | ![Screenshot of Dynamic Action Fields](./screenshot.png) 10 | 11 | ## Installation 12 | 13 | ``` 14 | composer require tunezilla/dynamic-action-fields 15 | ``` 16 | 17 | ## Usage 18 | 19 | 1. Add the `DynamicFieldAction` trait to your resource 20 | 21 | 2. Change your `public function fields()` to `public function fieldsForModels(Collection $models): array` 22 | 23 | Example: 24 | 25 | ```php 26 | isEmpty()) { 52 | return []; 53 | } 54 | 55 | /** @var User $user */ 56 | $user = $models->first(); 57 | 58 | return [ 59 | Text::make('Email') 60 | ->help('The new email for ' . $user->name), 61 | $user->hasVerifiedEmail() 62 | ? Boolean::make('Require Verification') 63 | ->help('Send a verification email that they must accept') 64 | : Hidden::make('Require Verification'), 65 | ]; 66 | } 67 | 68 | public function handle(ActionFields $fields, Collection $models) 69 | { 70 | /** @var User $user */ 71 | $user = $models->first(); 72 | $user->email = $fields->get('email'); 73 | 74 | if ($fields->get('require_verification')) { 75 | $user->email_verified_at = null; 76 | } 77 | 78 | $user->save(); 79 | 80 | if (!$user->hasVerifiedEmail()) { 81 | $user->sendEmailVerificationNotification(); 82 | } 83 | 84 | return Action::message(implode(' ', [ 85 | 'Changed email to ' . $fields->get('email'), 86 | $fields->get('require_verification') 87 | ? 'and sent a verification email' 88 | : 'but did not send a verification email' 89 | ])); 90 | } 91 | } 92 | ``` 93 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tunezilla/dynamic-action-fields", 3 | "description": "Dynamic Action Fields for Laravel Nova", 4 | "keywords": [ 5 | "laravel", 6 | "nova" 7 | ], 8 | "license": "MIT", 9 | "require": { 10 | "php": ">=7.1.0" 11 | }, 12 | "autoload": { 13 | "psr-4": { 14 | "TuneZilla\\DynamicActionFields\\": "src/" 15 | } 16 | }, 17 | "extra": { 18 | "laravel": { 19 | "providers": [ 20 | "TuneZilla\\DynamicActionFields\\DynamicFieldsServiceProvider" 21 | ] 22 | } 23 | }, 24 | "config": { 25 | "sort-packages": true 26 | }, 27 | "minimum-stability": "dev", 28 | "prefer-stable": true 29 | } 30 | -------------------------------------------------------------------------------- /dist/js/dynamic-action-fields.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:o})},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=0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){Nova.booting(function(e,t,o){e.component("dynamic-action-field-modal",n(2))})},function(e,t,n){var o=n(3)(n(4),n(5),!1,null,null,null);e.exports=o.exports},function(e,t){e.exports=function(e,t,n,o,r,i){var c,s=e=e||{},a=typeof e.default;"object"!==a&&"function"!==a||(c=e,s=e.default);var u,d="function"==typeof s?s.options:s;if(t&&(d.render=t.render,d.staticRenderFns=t.staticRenderFns,d._compiled=!0),n&&(d.functional=!0),r&&(d._scopeId=r),i?(u=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__),o&&o.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(i)},d._ssrRegister=u):o&&(u=o),u){var l=d.functional,f=l?d.render:d.beforeCreate;l?(d._injectStyles=u,d.render=function(e,t){return u.call(t),f(e,t)}):d.beforeCreate=f?[].concat(f,u):[u]}return{esModule:c,exports:s,options:d}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={name:"dynamic-action-field-modal",props:{working:Boolean,resourceName:{type:String,required:!0},action:{type:Object,required:!0},selectedResources:{type:[Array,String],required:!0},errors:{type:Object,required:!0}},data:function(){return{dynamicAction:null}},methods:{loadAction:function(){var e=this;this.dynamicAction=null,window.axios.get("/nova-vendor/dynamic-action-fields/actions/"+this.resourceName+"/dynamic-fields",{params:{resources:Array.isArray(this.selectedResources)?this.selectedResources.join(","):this.selectedResources,action:this.action.uriKey}}).then(function(t){e.dynamicAction=t.data,e.action.fields=e.dynamicAction.fields})}},mounted:function(){this.loadAction()},watch:{action:function(){this.loadAction()},resourceName:function(){this.loadAction()},selectedResources:function(){this.loadAction()}}}},function(e,t){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return e.dynamicAction?n("confirm-action-modal",e._b({attrs:{action:e.dynamicAction},on:{confirm:function(t){return e.$emit("confirm")},close:function(t){return e.$emit("close")}}},"confirm-action-modal",{working:e.working,resourceName:e.resourceName,selectedResources:e.selectedResources,errors:e.errors},!1)):n("modal",{attrs:{tabindex:"-1",role:"dialog"},nativeOn:{click:function(t){return e.$emit("close")}}},[n("div",{staticClass:"flex items-center justify-center z-50 p-6",staticStyle:{"min-height":"150px"}},[n("loader",{staticClass:"text-60"})],1)])},staticRenderFns:[]}}]); -------------------------------------------------------------------------------- /dist/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/dynamic-action-fields.js": "/js/dynamic-action-fields.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 | }, 16 | "dependencies": { 17 | "vue": "^2.5.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /resources/js/components/DynamicActionFieldModal.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 74 | -------------------------------------------------------------------------------- /resources/js/dynamic-action-fields.js: -------------------------------------------------------------------------------- 1 | Nova.booting((Vue, router, store) => { 2 | Vue.component('dynamic-action-field-modal', require('./components/DynamicActionFieldModal.vue')) 3 | }) 4 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | actionRequest ?? app(ActionRequest::class); 22 | 23 | $models = []; 24 | 25 | if ($actionRequest->has('resources')) { 26 | $actionRequest->chunks(100, function ($chunkedModels) use (&$models) { 27 | /** @var \Illuminate\Database\Eloquent\Collection $chunkedModels */ 28 | $models = array_merge($models, $chunkedModels->all()); 29 | }); 30 | } 31 | 32 | return $this->fieldsForModels(collect($models)); 33 | } 34 | 35 | abstract public function fieldsForModels(Collection $models): array; 36 | } 37 | -------------------------------------------------------------------------------- /src/DynamicFieldsController.php: -------------------------------------------------------------------------------- 1 | json($request->action()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/DynamicFieldsServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->booted(function () { 20 | $this->routes(); 21 | }); 22 | 23 | Nova::serving(function (ServingNova $event) { 24 | Nova::script('dynamic-action-fields', __DIR__.'/../dist/js/dynamic-action-fields.js'); 25 | }); 26 | } 27 | 28 | /** 29 | * Register the card's routes. 30 | * 31 | * @return void 32 | */ 33 | protected function routes() 34 | { 35 | if ($this->app->routesAreCached()) { 36 | return; 37 | } 38 | 39 | Route::middleware(['nova']) 40 | ->prefix('nova-vendor/dynamic-action-fields') 41 | ->group(__DIR__.'/../routes/api.php'); 42 | } 43 | 44 | /** 45 | * Register any application services. 46 | * 47 | * @return void 48 | */ 49 | public function register() 50 | { 51 | // 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix') 2 | 3 | mix 4 | .setPublicPath('dist') 5 | .js('resources/js/dynamic-action-fields.js', 'js') 6 | --------------------------------------------------------------------------------