├── dist ├── css │ └── tool.css ├── mix-manifest.json └── js │ └── tool.js ├── webpack.mix.js ├── .gitignore ├── src ├── NovaDynamicViews.php ├── Http │ ├── Middleware │ │ └── Authorize.php │ └── Controllers │ │ └── NovaDynamicViewsController.php ├── ToolServiceProvider.php └── CustomComponents.php ├── routes └── api.php ├── composer.json ├── package.json ├── resources └── js │ └── tool.js └── readme.md /dist/css/tool.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/tool.js": "/js/tool.js" 3 | } -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix') 2 | 3 | mix 4 | .setPublicPath('dist') 5 | .js('resources/js/tool.js', 'js') 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/NovaDynamicViews.php: -------------------------------------------------------------------------------- 1 | =7.1.0" 11 | }, 12 | "autoload": { 13 | "psr-4": { 14 | "Bernhardh\\NovaDynamicViews\\": "src/" 15 | } 16 | }, 17 | "extra": { 18 | "laravel": { 19 | "providers": [ 20 | "Bernhardh\\NovaDynamicViews\\ToolServiceProvider" 21 | ] 22 | } 23 | }, 24 | "config": { 25 | "sort-packages": true 26 | }, 27 | "minimum-stability": "dev", 28 | "prefer-stable": true 29 | } 30 | -------------------------------------------------------------------------------- /src/Http/Middleware/Authorize.php: -------------------------------------------------------------------------------- 1 | first([$this, 'matchesTool']); 20 | 21 | return optional($tool)->authorize($request) ? $next($request) : abort(403); 22 | } 23 | 24 | /** 25 | * Determine whether this tool belongs to the package. 26 | * 27 | * @param \Laravel\Nova\Tool $tool 28 | * @return bool 29 | */ 30 | public function matchesTool($tool) 31 | { 32 | return $tool instanceof NovaDynamicViews; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/Http/Controllers/NovaDynamicViewsController.php: -------------------------------------------------------------------------------- 1 | resource(); 22 | $model = $request->model(); 23 | $method = Str::camel('custom-' . $method . '-components'); 24 | $resource = new $resourceClass($model); 25 | 26 | if(method_exists($resource, $method)) { 27 | $data = $resource->$method(); 28 | if($data) { 29 | return $data; 30 | } 31 | } 32 | 33 | return []; 34 | } 35 | } -------------------------------------------------------------------------------- /src/ToolServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->booted(function () { 21 | $this->routes(); 22 | }); 23 | } 24 | 25 | /** 26 | * Register the tool's routes. 27 | * 28 | * @return void 29 | */ 30 | protected function routes() 31 | { 32 | if ($this->app->routesAreCached()) { 33 | return; 34 | } 35 | 36 | Route::middleware(['nova', Authorize::class]) 37 | ->prefix('nova-vendor/nova-dynamic-views') 38 | ->group(__DIR__.'/../routes/api.php'); 39 | } 40 | 41 | /** 42 | * Register any application services. 43 | * 44 | * @return void 45 | */ 46 | public function register() 47 | { 48 | // 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /dist/js/tool.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function o(n){if(t[n])return t[n].exports;var a=t[n]={i:n,l:!1,exports:{}};return e[n].call(a.exports,a,a.exports,o),a.l=!0,a.exports}o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=0)}([function(e,t,o){e.exports=o(1)},function(e,t){Nova.booting(function(e){["attach-header","create-header","detail-header","detail-toolbar","index-header","index-toolbar","lens-header","update-attach-header","update-header"].forEach(function(t){e.component("custom-"+t,{props:["resourceName"],template:'
',data:function(){return{customComponents:[],compClass:"",compName:t}},mounted:function(){var e=this,t="/nova-vendor/nova-dynamic-views/"+this.resourceName+"/"+this.compName;this.$route.params&&this.$route.params.resourceId&&(t+="?id="+this.$route.params.resourceId),Nova.request().get(t).then(function(t){var o=t.data.items||[];if(o)for(var n in o)for(var a in e.$props)o[n].meta||(o[n].meta={}),o[n].meta[a]=e[a];e.customComponents=o,e.compClass=t.data.class})}})})})}]); -------------------------------------------------------------------------------- /src/CustomComponents.php: -------------------------------------------------------------------------------- 1 | class = $class; 26 | 27 | return $obj; 28 | } 29 | 30 | 31 | /** 32 | * @param string $name 33 | * @param array $meta 34 | * 35 | * @return $this 36 | */ 37 | public function addItem($name, $meta = []) 38 | { 39 | $item = ['name'=> $name]; 40 | if($meta) { 41 | $item['meta'] = $meta; 42 | } 43 | 44 | $this->items[] = $item; 45 | 46 | return $this; 47 | } 48 | 49 | 50 | /** 51 | * @param string $class 52 | * 53 | * @return CustomComponents 54 | */ 55 | public function setClass(string $class) { 56 | $this->class = $class; 57 | 58 | return $this; 59 | } 60 | 61 | 62 | /** 63 | * @return array|mixed 64 | */ 65 | public function jsonSerialize() 66 | { 67 | return array_filter([ 68 | 'class' => $this->class, 69 | 'items' => $this->items 70 | ]); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /resources/js/tool.js: -------------------------------------------------------------------------------- 1 | Nova.booting((Vue) => { 2 | const components = [ 3 | 'attach-header', 4 | 'create-header', 5 | 'detail-header', 6 | 'detail-toolbar', 7 | 'index-header', 8 | 'index-toolbar', 9 | 'lens-header', 10 | 'update-attach-header', 11 | 'update-header' 12 | ]; 13 | 14 | components.forEach((comp) => { 15 | Vue.component('custom-' + comp, { 16 | props: ['resourceName'], 17 | 18 | template: '
' + 19 | '' + 20 | '
', 21 | 22 | data() { 23 | return { 24 | customComponents: [], 25 | compClass: '', 26 | compName: comp 27 | } 28 | }, 29 | 30 | mounted() { 31 | let url = '/nova-vendor/nova-dynamic-views/' + this.resourceName + '/' + this.compName; 32 | if(this.$route.params && this.$route.params.resourceId) { 33 | url+= '?id=' + this.$route.params.resourceId 34 | } 35 | 36 | Nova.request().get(url) 37 | .then(res => { 38 | let items = res.data.items || [] 39 | if(items) { 40 | for(let i in items) { 41 | for(let j in this.$props) { 42 | if(!items[i].meta) items[i].meta = {} 43 | items[i].meta[j] = this[j] 44 | } 45 | } 46 | } 47 | this.customComponents = items 48 | this.compClass = res.data.class 49 | }) 50 | } 51 | }) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Warning!!!! 2 | 3 | This repository is archived and no longer supported. It doesn't work with Nova 4. Just have a look at the form from https://github.com/shuvroroy/nova-dynamic-views 4 | 5 | 6 | --- 7 | 8 | 9 | # Nova dynamic views 10 | 11 | This package is meant to be used **INSTEAD** of overwriting the `custom-index-header`, `custom-index-toolbar`, `custom-detail-header`, `custom-detail-toolbar`, etc. by yourself. It provides a much easier API for it and it allows you to use these "placeholder" components multiple times without overwriting each other. 12 | 13 | ![2020-10-14_16-13](https://user-images.githubusercontent.com/642292/96001510-6592a980-0e38-11eb-9aea-54ebbf6126d1.png) 14 | 15 | ## Installation 16 | 17 | Require the package with composer 18 | 19 | ``` 20 | composer require bernhardh/nova-dynamic-views 21 | ``` 22 | 23 | Register the tool in the `tools` method in your `\App\Providers\NovaServiceProvider`: 24 | 25 | ```php 26 | use Bernhardh\NovaDynamicViews\NovaDynamicViews; 27 | 28 | ... 29 | 30 | public function tools() { 31 | return [ 32 | new NovaDynamicViews() 33 | ]; 34 | } 35 | ``` 36 | 37 | ## Usage 38 | 39 | Let's say you want to add a custom button to the `toolbar` of all `index` views. Just create a vue component for it, as you would do if you use the `custom-index-header` (see section "Create custom component" if you don't know how to). Let's call it `my-index-toolbar-btn`. Now the only thing you have to do is register it to your `\App\Ņova\Resource` class, within a new method called `customIndexToolbarComponents`, which returns a `\Bernhardh\NovaDynamicViews\CustomComponents` object: 40 | 41 | ```php 42 | public function customIndexToolbarComponents() 43 | { 44 | return CustomComponents::make() 45 | ->addItem('my-index-toolbar-btn'); 46 | } 47 | ``` 48 | 49 | Thats it. Now you should see the content of your component in the toolbar. 50 | 51 | ### Provide extra data 52 | 53 | If you want to add extra data (for example a label) to your component (without extra request), just add it to the `addItem` method as second parameter (as array): 54 | 55 | ```php 56 | public function customIndexToolbarComponents() 57 | { 58 | return CustomComponents::make() 59 | ->addItem('my-index-toolbar-btn', [ 60 | 'label' => 'My label' 61 | ]); 62 | } 63 | ``` 64 | 65 | ### Access resource data 66 | 67 | You have access to the ressource class in all methods by using `$this`. On `detail` and `edit` components, you have access to the ID of the current model with `request('id')`. So if you need the model itself in your `customDetailhHeaderComponents`, `customDetailToolbarComponents` or your `customUpdateHeaderComponents`, you can query for it like so: 68 | 69 | ```php 70 | public function customDetailToolbarComponents() 71 | { 72 | $model = $this->model()->query()->where('id', request('id'))->first(); 73 | 74 | //... 75 | } 76 | ``` 77 | 78 | ### Add (tailwind) class to the container 79 | 80 | If you want to add additional CSS classes to the container div of a section (for example add `flex w-full justify-end items-center mx-3` to the `customIndexToolbarComponents` section), add the `class` in the `make` function (or use the `setClass` method): 81 | 82 | ```php 83 | public function customIndexToolbarComponents() 84 | { 85 | return CustomComponents::make('flex w-full justify-end items-center mx-3') 86 | ->addItem('my-index-toolbar-btn'); 87 | } 88 | ``` 89 | 90 | ### Full usage example 91 | 92 | ```php 93 | class Resource extends \Laravel\Nova\Resource { 94 | ... 95 | 96 | /** 97 | * Using the `custom-index-toolbar` placeholder component 98 | * 99 | * @return array[] 100 | */ 101 | public function customIndexToolbarComponents() 102 | { 103 | return CustomComponents::make('flex w-full justify-end items-center mx-3') 104 | ->addItem('my-index-toolbar-btn', [ 105 | 'title' => 'My first btn' 106 | ]) 107 | ->addItem('my-index-toolbar-btn', [ 108 | 'title' => 'My second btn' 109 | ]); 110 | } 111 | 112 | /** 113 | * Using the `custom-detail-header` placeholder component 114 | * 115 | * @return array[] 116 | */ 117 | public function customDetailHeaderComponents() 118 | { 119 | $model = $this->model()->query()->where('id', request('id'))->first(); 120 | 121 | return CustomComponents::make() 122 | ->addItem('my-other-component', [ 123 | 'id' => $model->id, 124 | 'name' => $model->name 125 | ]); 126 | } 127 | } 128 | ``` 129 | 130 | 131 | ### Use only on specific resources 132 | 133 | If you want to show this button only on a specific resource, for example only for Users, just add this method to the `\App\Nova\User` class. 134 | 135 | ## Available methods and areas 136 | 137 | All `custom-*-*` nova placeholders (except `custom-dashboard-header`) are available as camel case methods postfixed with `Components`: 138 | 139 | - `customAttachHeaderComponents` 140 | - `customCreateHeaderComponents` 141 | - `customDetailhHeaderComponents` 142 | - `customDetailToolbarComponents` 143 | - `customIndexHeaderComponents` 144 | - `customIndexToolbarComponents` 145 | - `customLensHeaderComponents` 146 | - `customUpdateAttachHeaderComponents` 147 | - `customUpdateHeaderComponents` 148 | 149 | ## Create custom component 150 | 151 | This is just a kick start documentation for this. For more info, see https://nova.laravel.com/docs/3.0/customization/resource-tools.html 152 | 153 | Create a new resource tool with artisan: 154 | 155 | ```bash 156 | php artisan nova:resource-tool acme/my-index-toolbar-btn 157 | ``` 158 | 159 | and say yes to all questions of the prompt. Now you can use this component (located ad `nova-components/my-index-toolbar-btn`) inside your `customXXXComponents` (f.e. `customIndexToolbarComponents`) 160 | 161 | 162 | --------------------------------------------------------------------------------