├── .gitignore ├── LICENSE.md ├── composer.json ├── composer.lock ├── config └── crud-forms.php ├── phpunit.xml ├── readme.md ├── src ├── CrudForms.php ├── CrudFormsServiceProvider.php └── views │ ├── _errors.blade.php │ ├── create.blade.php │ ├── displays │ ├── checkbox.blade.php │ ├── checkbox_multiple.blade.php │ ├── date.blade.php │ ├── email.blade.php │ ├── label.blade.php │ ├── password.blade.php │ ├── radio.blade.php │ ├── select.blade.php │ ├── select_multiple.blade.php │ ├── text.blade.php │ ├── textarea.blade.php │ └── url.blade.php │ ├── edit.blade.php │ ├── form.blade.php │ ├── index.blade.php │ ├── inputs │ ├── checkbox.blade.php │ ├── checkbox_multiple.blade.php │ ├── date.blade.php │ ├── email.blade.php │ ├── label.blade.php │ ├── password.blade.php │ ├── radio.blade.php │ ├── select.blade.php │ ├── select_multiple.blade.php │ ├── text.blade.php │ ├── textarea.blade.php │ └── url.blade.php │ ├── layout.blade.php │ └── show.blade.php └── tests ├── Controllers └── PostController.php ├── CrudFormsTest.php ├── Models ├── Category.php ├── Post.php └── Tag.php ├── Providers └── RouteServiceProvider.php └── TestCase.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .php_cs* 3 | .idea/ 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Achilles Panagiotou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "achillesp/laravel-crud-forms", 3 | "description": "Create CRUD Forms for Laravel Models.", 4 | "license": "MIT", 5 | "keywords": ["laravel", "crud", "forms", "eloquent"], 6 | "authors": [ 7 | { 8 | "name": "Achilles Panagiotou", 9 | "email": "achilles.p@gmail.com", 10 | "role": "Developer" 11 | } 12 | ], 13 | "require": { 14 | "php" : "^8.1", 15 | "illuminate/config": "~10.0", 16 | "illuminate/database": "~10.0", 17 | "illuminate/http": "~10.0" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "^9.5.8", 21 | "mockery/mockery": "^1.5.1", 22 | "orchestra/testbench": "^8.0" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Achillesp\\CrudForms\\": "src" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Achillesp\\CrudForms\\Test\\": "tests" 32 | } 33 | }, 34 | "extra": { 35 | "laravel": { 36 | "providers": [ 37 | "Achillesp\\CrudForms\\CrudFormsServiceProvider" 38 | ] 39 | } 40 | }, 41 | "minimum-stability": "dev", 42 | "prefer-stable": true 43 | } 44 | -------------------------------------------------------------------------------- /config/crud-forms.php: -------------------------------------------------------------------------------- 1 | 'crud-forms::layout', 10 | 11 | /* 12 | * The default section in the base blade layout, that the CRUD views will use. 13 | */ 14 | 'blade_section' => 'content', 15 | 16 | /* 17 | * Whether the action buttons (show, edit, delete) of the index view should use font awesome icons. 18 | * If true, you will have to load the font-awesome css in the base layout. 19 | * (eg https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css). 20 | */ 21 | 'button_icons' => true 22 | 23 | ]; 24 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests 15 | 16 | 17 | 18 | 19 | ./src 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Laravel CRUD Forms 2 | 3 | This is a Laravel >=5.5 package to help easily create CRUD (Create, Read, Update, Delete) forms for eloquent models (as well as an index page). 4 | It aims to be used as a quick tool which does not interfere with the other parts of the application that it's used in. 5 | 6 | The package provides: 7 | - A trait to use in resource controllers and 8 | - A series of views for displaying the forms 9 | 10 | The views are built using bootstrap (v3), but the styling can easily be overriden. 11 | 12 | ## Installation 13 | 14 | ### Composer 15 | 16 | From the command line, run: 17 | 18 | ``` 19 | composer require achillesp/laravel-crud-forms 20 | ``` 21 | 22 | ### Configuration 23 | 24 | This package uses a config file which you can override by publishing it to your app's config dir. 25 | 26 | ``` 27 | php artisan vendor:publish --provider=Achillesp\CrudForms\CrudFormsServiceProvider --tag=config 28 | ``` 29 | 30 | ## Usage 31 | 32 | To use the package, you need to use the trait `Achillesp\CrudForms\CrudForms` in your model's controller and define your routes. 33 | The trait provides all the required methods for a Resource Controller, as well as a restore method in case of soft-deleted models. 34 | 35 | ### Routes 36 | 37 | If for example you have a `Post` model, you would define the routes: 38 | 39 | ```php 40 | Route::resource('/posts', 'PostController'); 41 | ``` 42 | 43 | ### Controller 44 | 45 | Then in your `PostController`, you will need to use the trait and also define a constructor where you give the needed details of the model. 46 | 47 | ```php 48 | use App\Post; 49 | use Achillesp\CrudForms\CrudForms; 50 | 51 | class PostController extends Controller 52 | { 53 | use CrudForms; 54 | 55 | public function __construct(Post $post) 56 | { 57 | $this->model = $post; 58 | } 59 | } 60 | ``` 61 | 62 | In the controller's constructor you can define the properties which are handled by the controller. 63 | The available properties that can be defined are as follows. 64 | 65 | ### The model 66 | 67 | This is the model, which should be passed in the constructor through Dependency Injection. 68 | 69 | ### The formFields array 70 | 71 | This is an array of all the fields you need in the forms. Each field is declared as an array that has: 72 | 1. `name`: This is the model's attribute name, as it is in the database. 73 | 2. `label`: This is the field's label in the forms. 74 | 3. `type`: The type of the form input field that will be used. Accepted types are: 75 | - text 76 | - textarea 77 | - email 78 | - url 79 | - password 80 | - date 81 | - select 82 | - select_multiple 83 | - checkbox 84 | - checkbox_multiple 85 | - radio 86 | 4. `relationship`: This is needed in case of a select, select_multiple, radio or checkbox_multiple buttons. 87 | You can state here the name of the relationship as it is defined in the model. 88 | In the example bellow, the `Post` model has a `belongsTo` relationship to `category` and a `belongsToMany` relationship to `tags`. 89 | For `belongsTo` relationships you can use a select or a radio(group of radios) input. 90 | For `belongsToMany` relationships you can use a select_multiple or checkbox_multiple inputs. 91 | 5. `relFieldName`: This is optional. It is used only in case we have a relationship, to set the name of the attribute of the related model that is displayed (ie. in a select's options). 92 | If not defined, the default attribute to be used is `name`. 93 | 94 | ```php 95 | $this->formFields = [ 96 | ['name' => 'title', 'label' => 'Title', 'type' => 'text'], 97 | ['name' => 'slug', 'label' => 'Slug', 'type' => 'text'], 98 | ['name' => 'body', 'label' => 'Enter your content here', 'type' => 'textarea'], 99 | ['name' => 'publish_on', 'label' => 'Publish Date', 'type' => 'date'], 100 | ['name' => 'published', 'label' => 'Published', 'type' => 'checkbox'], 101 | ['name' => 'category_id', 'label' => 'Category', 'type' => 'select', 'relationship' => 'category'], 102 | ['name' => 'tags', 'label' => 'Tags', 'type' => 'select_multiple', 'relationship' => 'tags'], 103 | ]; 104 | ``` 105 | 106 | ### The `indexFields` array 107 | 108 | These are the model's attributes that are displayed in the index page. 109 | 110 | ```php 111 | $this->indexFields = ['title', 'category_id', 'published']; 112 | ``` 113 | 114 | If not defined, then the first of the `formFields` is shown. 115 | 116 | ### The `formTitle` (optional) 117 | 118 | You can optionally, define the name of the model as we want it to appear in the views. 119 | If not defined, the name of the model will be used. 120 | 121 | ### The `bladeLayout` (optional) 122 | 123 | This is used to define the blade layout file that will be extended by the views for the crud forms and index page. 124 | 125 | ### The option to display deleted models (`withTrashed`) 126 | 127 | Setting this to true, will also display deleted models and offer an option to restore them. 128 | 129 | ```php 130 | $this->withTrashed = true; 131 | ``` 132 | 133 | In order to be able to restore the models, you need to define an additional route: 134 | 135 | ```php 136 | Route::put('/posts/{post}/restore', ['as' => 'posts.restore', 'uses' => 'PostController@restore']); 137 | ``` 138 | 139 | ### The `validationRules` array (optional) 140 | 141 | These are the rules we want to use to validate data before saving the model. 142 | 143 | ```php 144 | $this->validationRules = [ 145 | 'title' => 'required|max:255', 146 | 'slug' => 'required|max:100', 147 | 'body' => 'required', 148 | 'publish_on' => 'date', 149 | 'published' => 'boolean', 150 | 'category_id' => 'required|int', 151 | ]; 152 | ``` 153 | 154 | ### The `validationMessages` array (optional) 155 | 156 | Use this to define custom messages for validation errors. For example: 157 | 158 | ```php 159 | $this->validationMessages = [ 160 | 'body.required' => "You need to fill in the post content." 161 | ]; 162 | ``` 163 | 164 | ### The `validationAttributes` array (optional) 165 | 166 | Use this to change the way an attribute's name should appear in validation error messages. 167 | 168 | ```php 169 | $this->validationAttributes = [ 170 | 'title' => 'Post title' 171 | ]; 172 | ``` 173 | 174 | ## Views 175 | 176 | The views are built with bootstrap v.3 and also have css classes to support some common JavaScript libraries. 177 | - select2 class is used in select inputs 178 | - datepicker class is used in date inputs 179 | - data-table class is used in the index view table 180 | 181 | It is also possible to publish the views, so you can change them anyway you need. 182 | To publish them, use the following artisan command: 183 | 184 | ``` 185 | php artisan vendor:publish --provider=Achillesp\CrudForms\CrudFormsServiceProvider --tag=views 186 | ``` 187 | 188 | ## License 189 | 190 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 191 | -------------------------------------------------------------------------------- /src/CrudForms.php: -------------------------------------------------------------------------------- 1 | withTrashed) { 114 | $entities = $this->model->withTrashed()->get(); 115 | } else { 116 | $entities = $this->model->all(); 117 | } 118 | 119 | $this->loadModelRelationships($entities); 120 | 121 | $fields = $this->getIndexFields(); 122 | $title = $this->getFormTitle(); 123 | $route = $this->getRoute(); 124 | $withTrashed = $this->withTrashed; 125 | $bladeLayout = $this->bladeLayout; 126 | 127 | return view('crud-forms::index', 128 | compact( 129 | 'entities', 130 | 'fields', 131 | 'title', 132 | 'route', 133 | 'withTrashed', 134 | 'bladeLayout' 135 | ) 136 | ); 137 | } 138 | 139 | /** 140 | * Show the form for creating a new resource. 141 | * 142 | * @return \Illuminate\Http\Response 143 | */ 144 | public function create() 145 | { 146 | $entity = $this->model; 147 | 148 | $relationshipOptions = $this->getModelRelationshipData(); 149 | 150 | $fields = $this->getFormFields(); 151 | $title = $this->getFormTitle(); 152 | $route = $this->getRoute(); 153 | $bladeLayout = $this->bladeLayout; 154 | 155 | return view('crud-forms::create', 156 | compact( 157 | 'entity', 158 | 'fields', 159 | 'title', 160 | 'route', 161 | 'bladeLayout', 162 | 'relationshipOptions' 163 | ) 164 | ); 165 | } 166 | 167 | /** 168 | * Store a newly created resource in storage. 169 | * 170 | * @param Request $request 171 | * 172 | * @return \Illuminate\Http\RedirectResponse 173 | */ 174 | public function store(Request $request) 175 | { 176 | Validator::make($request->all(), 177 | $this->getValidationRules(), 178 | $this->getValidationMessages(), 179 | $this->getValidationAttributes() 180 | )->validate(); 181 | 182 | $entity = $this->model->create($request->all()); 183 | 184 | $this->syncModelRelationships($entity, $request); 185 | 186 | $request->session()->flash('status', 'Data saved successfully!'); 187 | 188 | return redirect(route($this->getRoute().'.index')); 189 | } 190 | 191 | /** 192 | * Display the specified resource. 193 | * 194 | * @param int $id 195 | * 196 | * @return \Illuminate\Http\Response 197 | */ 198 | public function show($id) 199 | { 200 | $entity = $this->model->findOrFail($id); 201 | 202 | $this->loadModelRelationships($entity); 203 | 204 | $fields = $this->getFormFields(); 205 | $title = $this->getFormTitle(); 206 | $route = $this->getRoute(); 207 | $bladeLayout = $this->bladeLayout; 208 | 209 | return view('crud-forms::show', 210 | compact( 211 | 'entity', 212 | 'fields', 213 | 'title', 214 | 'route', 215 | 'bladeLayout' 216 | ) 217 | ); 218 | } 219 | 220 | /** 221 | * Show the form for editing the specified resource. 222 | * 223 | * @param int $id 224 | * 225 | * @return \Illuminate\View\View 226 | */ 227 | public function edit($id) 228 | { 229 | $entity = $this->model->findOrFail($id); 230 | 231 | $this->loadModelRelationships($entity); 232 | 233 | $relationshipOptions = $this->getModelRelationshipData(); 234 | 235 | $fields = $this->getFormFields(); 236 | $title = $this->getFormTitle(); 237 | $route = $this->getRoute(); 238 | $bladeLayout = $this->bladeLayout; 239 | 240 | return view('crud-forms::edit', 241 | compact( 242 | 'entity', 243 | 'fields', 244 | 'title', 245 | 'route', 246 | 'bladeLayout', 247 | 'relationshipOptions' 248 | ) 249 | ); 250 | } 251 | 252 | /** 253 | * Update the specified resource in storage. 254 | * 255 | * @param Request $request 256 | * @param $id 257 | * 258 | * @return \Illuminate\Http\RedirectResponse 259 | */ 260 | public function update(Request $request, $id) 261 | { 262 | Validator::make($request->all(), 263 | $this->getValidationRules(), 264 | $this->getValidationMessages(), 265 | $this->getValidationAttributes() 266 | )->validate(); 267 | 268 | $entity = $this->model->findOrFail($id); 269 | 270 | // Handle checkboxes 271 | foreach ($this->getFormFields() as $field) { 272 | if ('checkbox' == $field['type']) { 273 | $request["{$field['name']}"] = ($request["{$field['name']}"]) ? true : false; 274 | } 275 | } 276 | 277 | $entity->update($request->all()); 278 | 279 | $this->syncModelRelationships($entity, $request); 280 | 281 | $request->session()->flash('status', 'Data saved.'); 282 | 283 | return redirect(route($this->getRoute().'.show', $id)); 284 | } 285 | 286 | /** 287 | * Remove the specified resource from storage. 288 | * 289 | * @param int $id 290 | * 291 | * @return \Illuminate\Http\Response 292 | */ 293 | public function destroy($id) 294 | { 295 | $entity = $this->model->findOrFail($id); 296 | 297 | $entity->delete(); 298 | 299 | request()->session()->flash('status', 'Data deleted.'); 300 | 301 | return redirect(route($this->getRoute().'.index')); 302 | } 303 | 304 | /** 305 | * Restore the specified softdeleted resource. 306 | * 307 | * @param int $id 308 | * 309 | * @return \Illuminate\Http\Response 310 | */ 311 | public function restore($id) 312 | { 313 | $this->model->withTrashed()->where('id', $id)->restore(); 314 | 315 | request()->session()->flash('status', 'Data restored.'); 316 | 317 | return redirect(Str::before(request()->path(), "/$id/restore")); 318 | } 319 | 320 | /** 321 | * Get the array of fields that we need to present in the forms. 322 | * 323 | * @return array 324 | */ 325 | public function getFormFields() 326 | { 327 | // No fields declared. We have a table with only a name field. 328 | if (0 == count($this->formFields)) { 329 | array_push($this->formFields, ['name' => 'name', 'label' => 'Name', 'type' => 'text']); 330 | 331 | return $this->formFields; 332 | } 333 | 334 | foreach ($this->formFields as $key => $field) { 335 | if (Arr::has($field, 'relationship') && !Arr::has($field, 'relFieldName')) { 336 | // set default name of related table main field 337 | $this->formFields[$key]['relFieldName'] = 'name'; 338 | } 339 | } 340 | 341 | return $this->formFields; 342 | } 343 | 344 | /** 345 | * Get an array of all the model's relationships needed in the crud forms. 346 | * 347 | * @return array 348 | */ 349 | public function getRelationships() 350 | { 351 | foreach ($this->getFormFields() as $field) { 352 | if ( 353 | Arr::has($field, 'relationship') && 354 | !Arr::has($this->relationships, $field['relationship']) && 355 | method_exists($this->model, $field['relationship']) 356 | ) { 357 | $this->relationships[] = $field['relationship']; 358 | } 359 | } 360 | 361 | return $this->relationships; 362 | } 363 | 364 | // -------------------------------- 365 | // Getters & Setters 366 | // -------------------------------- 367 | 368 | /** 369 | * Get an array of the defined validation rules. 370 | * 371 | * @return array 372 | */ 373 | protected function getValidationRules() 374 | { 375 | return $this->validationRules ?: []; 376 | } 377 | 378 | /** 379 | * Get an array of the defined validation custom messages. 380 | * 381 | * @return array 382 | */ 383 | protected function getValidationMessages() 384 | { 385 | return $this->validationMessages ?: []; 386 | } 387 | 388 | /** 389 | * Get an array of the defined validation attributes nice names. 390 | * These are used in the default Laravel validation messages. 391 | * 392 | * @return array 393 | */ 394 | protected function getValidationAttributes() 395 | { 396 | $attributes = []; 397 | 398 | foreach ($this->getFormFields() as $field) { 399 | $attributes[$field['name']] = $field['label']; 400 | } 401 | 402 | return array_merge($attributes, $this->validationAttributes); 403 | } 404 | 405 | /** 406 | * Get the base route of the resource. 407 | * 408 | * @return string 409 | */ 410 | protected function getRoute() 411 | { 412 | if ($this->route) { 413 | return $this->route; 414 | } 415 | 416 | // No route defined. 417 | // We find the full route from the request and get the base from there. 418 | $routeName = request()->route()->getName(); 419 | 420 | return substr($routeName, 0, strrpos($routeName, '.')); 421 | } 422 | 423 | /** 424 | * Get the title of the resource to use in form headers. 425 | * 426 | * @return string 427 | */ 428 | protected function getFormTitle() 429 | { 430 | if ($this->formTitle) { 431 | return $this->formTitle; 432 | } 433 | // No title defined. We return the model name. 434 | return Str::title(class_basename($this->model)); 435 | } 436 | 437 | /** 438 | * Get the array of fields that we need to present in the resource index. 439 | * 440 | * @return array 441 | */ 442 | protected function getIndexFields() 443 | { 444 | // If none declared, use the first of the formFields. 445 | if (0 == count($this->indexFields)) { 446 | $this->indexFields = [$this->formFields[0]['name']]; 447 | 448 | return array_slice($this->getFormFields(), 0, 1); 449 | } 450 | 451 | return Arr::where($this->getFormFields(), function ($value) { 452 | return in_array($value['name'], $this->indexFields, true); 453 | }); 454 | } 455 | 456 | /** 457 | * Get an array of collections of related data. 458 | * 459 | * @return array array of collections 460 | */ 461 | protected function getModelRelationshipData() 462 | { 463 | $formFields = $this->getFormFields(); 464 | 465 | $relationships = $this->getRelationships(); 466 | 467 | foreach ($relationships as $relationship) { 468 | // We need to find the relationship's field 469 | $field = Arr::first(array_filter($formFields, function ($var) use ($relationship) { 470 | if (Arr::has($var, 'relationship') && ($relationship == $var['relationship'])) { 471 | return $var; 472 | } 473 | })); 474 | 475 | if (in_array(get_class($this->model->$relationship()), $this->relationTypesToLoad, true)) { 476 | $relationshipData["$relationship"] = $this->model->$relationship()->getRelated()->all()->pluck($field['relFieldName'], 'id'); 477 | } 478 | } 479 | 480 | return isset($relationshipData) ? $relationshipData : []; 481 | } 482 | 483 | // -------------------------------- 484 | // Helper methods 485 | // -------------------------------- 486 | 487 | /** 488 | * Sync any BelongsToMany Relationships. 489 | * 490 | * @param Model $model 491 | * @param Request $request 492 | */ 493 | protected function syncModelRelationships(Model $model, Request $request) 494 | { 495 | $relationships = $this->getRelationships(); 496 | 497 | foreach ($relationships as $relationship) { 498 | if ('Illuminate\Database\Eloquent\Relations\BelongsToMany' == get_class($model->$relationship())) { 499 | $model->$relationship()->sync($request->input($relationship, [])); 500 | } 501 | } 502 | } 503 | 504 | /** 505 | * Eager load all the BelongsTo and BelongsToMany relationships. 506 | * 507 | * @param mixed $entities 508 | */ 509 | protected function loadModelRelationships($entities) 510 | { 511 | $relationships = $this->getRelationships(); 512 | 513 | foreach ($relationships as $relationship) { 514 | if (in_array(get_class($this->model->$relationship()), $this->relationTypesToLoad, true)) { 515 | $entities->load($relationship); 516 | } 517 | } 518 | } 519 | 520 | /** 521 | * Check if a form field of a given name is defined. 522 | * 523 | * @param string $fieldName 524 | * 525 | * @return bool 526 | */ 527 | protected function hasField($fieldName) 528 | { 529 | return in_array($fieldName, array_column($this->formFields, 'name'), true); 530 | } 531 | } 532 | -------------------------------------------------------------------------------- /src/CrudFormsServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 15 | __DIR__.'/../config/crud-forms.php' => config_path('crud-forms.php'), 16 | ], 'config'); 17 | 18 | $this->mergeConfigFrom(__DIR__.'/../config/crud-forms.php', 'crud-forms'); 19 | 20 | $this->loadViewsFrom(__DIR__.'/../src/views', 'crud-forms'); 21 | 22 | $this->publishes([ 23 | __DIR__.'/../src/views' => resource_path('views/vendor/crud-forms'), 24 | ], 'views'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/views/_errors.blade.php: -------------------------------------------------------------------------------- 1 | @if (count($errors) > 0) 2 |
3 |
4 |
5 |

Form submit failed. Errors found:

6 |
    7 | @foreach ($errors->all() as $error) 8 |
  • {{ $error }}
  • 9 | @endforeach 10 |
11 |
12 |
13 |
14 |
15 | @endif 16 | -------------------------------------------------------------------------------- /src/views/create.blade.php: -------------------------------------------------------------------------------- 1 | @extends( $bladeLayout ?: config('crud-forms.blade_layout')) 2 | 3 | @section(config('crud-forms.blade_section')) 4 |
5 |
6 |
7 |
8 |

Add New {{ $title }}

9 |
10 |
11 | @include('crud-forms::_errors') 12 |
id) }}" method="POST"> 13 | {{ csrf_field() }} 14 |
15 |
16 | @include('crud-forms::form') 17 |
18 |
19 |
20 |
21 | {{-- Back to resource index --}} 22 | 27 | {{-- Submit --}} 28 |
29 | 32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 |
40 | @endsection 41 | -------------------------------------------------------------------------------- /src/views/displays/checkbox.blade.php: -------------------------------------------------------------------------------- 1 | @if ($entity->{$field['name']} == true) 2 | YES 3 | @else 4 | NO 5 | @endif 6 | -------------------------------------------------------------------------------- /src/views/displays/checkbox_multiple.blade.php: -------------------------------------------------------------------------------- 1 | {{-- Collection, so this is a belongsToMany, so we display a list of items --}} 2 | @if (!empty($entity->{$field['name']})) 3 | 8 | @endif -------------------------------------------------------------------------------- /src/views/displays/date.blade.php: -------------------------------------------------------------------------------- 1 | {{ $entity->{$field['name']} }} -------------------------------------------------------------------------------- /src/views/displays/email.blade.php: -------------------------------------------------------------------------------- 1 | {{ $entity->{$field['name']} }} -------------------------------------------------------------------------------- /src/views/displays/label.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/displays/password.blade.php: -------------------------------------------------------------------------------- 1 | *********** -------------------------------------------------------------------------------- /src/views/displays/radio.blade.php: -------------------------------------------------------------------------------- 1 | {{-- BelongsTo, so single item --}} 2 | @if (!empty($entity->{$field['name']})) 3 | {{ $entity->{$field['relationship']}->{$field['relFieldName']} }} 4 | @endif -------------------------------------------------------------------------------- /src/views/displays/select.blade.php: -------------------------------------------------------------------------------- 1 | {{-- BelongsTo, so single item --}} 2 | @if (!empty($entity->{$field['name']})) 3 | {{ $entity->{$field['relationship']}->{$field['relFieldName']} }} 4 | @endif -------------------------------------------------------------------------------- /src/views/displays/select_multiple.blade.php: -------------------------------------------------------------------------------- 1 | {{-- Collection, so this is a belongsToMany, so we display a list of items --}} 2 | @if (!empty($entity->{$field['name']})) 3 | 8 | @endif -------------------------------------------------------------------------------- /src/views/displays/text.blade.php: -------------------------------------------------------------------------------- 1 | {{ $entity->{$field['name']} }} -------------------------------------------------------------------------------- /src/views/displays/textarea.blade.php: -------------------------------------------------------------------------------- 1 | {{ $entity->{$field['name']} }} -------------------------------------------------------------------------------- /src/views/displays/url.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{ $entity->{$field['name']} }} 3 | -------------------------------------------------------------------------------- /src/views/edit.blade.php: -------------------------------------------------------------------------------- 1 | @extends( $bladeLayout ?: config('crud-forms.blade_layout')) 2 | 3 | @section(config('crud-forms.blade_section')) 4 |
5 |
6 |
7 |
8 |

Edit {{ $title }}

9 |
10 | 11 |
12 | @include('crud-forms::_errors') 13 |
id) }}" method="POST"> 14 | {{ method_field('PATCH') }} 15 | {{ csrf_field() }} 16 |
17 |
18 | @include('crud-forms::form') 19 |
20 |
21 |
22 |
23 | {{-- Back to resource index --}} 24 | 29 | 30 | {{-- Cancel and go back to resource show --}} 31 | 36 | 37 | {{-- Submit --}} 38 |
39 | 42 |
43 | 44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 | @endsection 52 | -------------------------------------------------------------------------------- /src/views/form.blade.php: -------------------------------------------------------------------------------- 1 | @foreach ($fields as $field) 2 | 3 |
4 | @include( "crud-forms::inputs.{$field['type']}") 5 |
6 | 7 | @endforeach -------------------------------------------------------------------------------- /src/views/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends( $bladeLayout ?: config('crud-forms.blade_layout')) 2 | 3 | @section(config('crud-forms.blade_section')) 4 | 5 |
6 |
7 |
8 |
9 | 14 |

{{ Illuminate\Support\Str::plural($title) }} Index

15 |
16 |
17 | 18 | 19 | 20 | @foreach ($fields as $field) 21 | 22 | @endforeach 23 | @if ($withTrashed) 24 | 25 | @endif 26 | 27 | 28 | 29 | 30 | @foreach ($entities as $entity) 31 | 32 | @foreach ($fields as $field) 33 | 34 | @endforeach 35 | 36 | @if ($withTrashed) 37 | 38 | @endif 39 | 40 | 86 | 87 | @endforeach 88 | 89 |
{{$field['label']}}Deleted OnActions
@include( "crud-forms::displays.{$field['type']}"){{ !empty($entity->deleted_at) ? $entity->deleted_at : '' }} 41 | @if (empty($entity->deleted_at)) 42 | {{-- Show --}} 43 | id ) }}" class="btn btn-info"> 44 | @if (config('crud-forms.button_icons')) 45 | 46 | @else 47 | show 48 | @endif 49 | 50 | 51 | {{-- Update --}} 52 | id ) }}" class="btn btn-warning"> 53 | @if (config('crud-forms.button_icons')) 54 | 55 | @else 56 | edit 57 | @endif 58 | 59 | 60 | {{-- Delete --}} 61 |
id) }}" method="POST" style="display: inline-block;"> 62 | {{ method_field('DELETE') }} 63 | {{ csrf_field() }} 64 | 71 |
72 | @elseif ($withTrashed) 73 | {{-- Restore SoftDeleted --}} 74 |
75 | {{ method_field('PUT') }} 76 | {{ csrf_field() }} 77 | 83 |
84 | @endif 85 |
90 |
91 |
92 |
93 |
94 | 95 | @endsection 96 | -------------------------------------------------------------------------------- /src/views/inputs/checkbox.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 13 |
14 | -------------------------------------------------------------------------------- /src/views/inputs/checkbox_multiple.blade.php: -------------------------------------------------------------------------------- 1 | {$field['name']}->pluck('id')->toArray(); 7 | } 8 | 9 | ?> 10 | 11 | @include('crud-forms::inputs.label') 12 | 13 | @foreach($relationshipOptions["{$field['relationship']}"] as $key=>$val) 14 |
15 | 25 |
26 | @endforeach 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/views/inputs/date.blade.php: -------------------------------------------------------------------------------- 1 | @include('crud-forms::inputs.label') 2 | 3 |
4 | @if (config('crud-forms.button_icons')) 5 |
6 | 7 |
8 | @endif 9 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /src/views/inputs/email.blade.php: -------------------------------------------------------------------------------- 1 | @include('crud-forms::inputs.label') 2 | 3 | -------------------------------------------------------------------------------- /src/views/inputs/label.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/inputs/password.blade.php: -------------------------------------------------------------------------------- 1 | @include('crud-forms::inputs.label') 2 | 3 | -------------------------------------------------------------------------------- /src/views/inputs/radio.blade.php: -------------------------------------------------------------------------------- 1 | {$field['name']}; 7 | } 8 | 9 | ?> 10 | 11 | @include('crud-forms::inputs.label') 12 | 13 | @foreach($relationshipOptions["{$field['relationship']}"] as $key=>$val) 14 |
15 | 25 |
26 | @endforeach -------------------------------------------------------------------------------- /src/views/inputs/select.blade.php: -------------------------------------------------------------------------------- 1 | {$field['name']}; 7 | } 8 | 9 | ?> 10 | 11 | @include('crud-forms::inputs.label') 12 | 13 | 22 | -------------------------------------------------------------------------------- /src/views/inputs/select_multiple.blade.php: -------------------------------------------------------------------------------- 1 | {$field['name']}->pluck('id')->toArray(); 7 | } 8 | 9 | ?> 10 | 11 | @include('crud-forms::inputs.label') 12 | 13 | 27 | 28 | -------------------------------------------------------------------------------- /src/views/inputs/text.blade.php: -------------------------------------------------------------------------------- 1 | @include('crud-forms::inputs.label') 2 | 3 | -------------------------------------------------------------------------------- /src/views/inputs/textarea.blade.php: -------------------------------------------------------------------------------- 1 | @include('crud-forms::inputs.label') 2 | 3 | -------------------------------------------------------------------------------- /src/views/inputs/url.blade.php: -------------------------------------------------------------------------------- 1 | @include('crud-forms::inputs.label') 2 | 3 | -------------------------------------------------------------------------------- /src/views/layout.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ config('app.name', 'Laravel') }} 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | @yield('content') 21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/views/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends( $bladeLayout ?: config('crud-forms.blade_layout')) 2 | 3 | @section(config('crud-forms.blade_section')) 4 |
5 |
6 |
7 |
8 |

{{ $title }} Details

9 |
10 | 11 |
12 |
    13 | @foreach ($fields as $field) 14 |
  • 15 | {{ $field['label'] }}: 16 | @include( "crud-forms::displays.{$field['type']}") 17 |
  • 18 | 19 | @endforeach 20 |
21 |
22 | 49 |
50 |
51 |
52 | @endsection 53 | -------------------------------------------------------------------------------- /tests/Controllers/PostController.php: -------------------------------------------------------------------------------- 1 | indexFields = ['title', 'category_id', 'published']; 20 | 21 | $this->formFields = [ 22 | ['name' => 'title', 'label' => 'Title', 'type' => 'text'], 23 | ['name' => 'slug', 'label' => 'Slug', 'type' => 'text'], 24 | ['name' => 'body', 'label' => 'Enter your content here', 'type' => 'textarea'], 25 | ['name' => 'publish_on', 'label' => 'Publish Date', 'type' => 'date'], 26 | ['name' => 'published', 'label' => 'Published', 'type' => 'checkbox'], 27 | ['name' => 'category_id', 'label' => 'Category', 'type' => 'select', 'relationship' => 'category'], 28 | ['name' => 'tags', 'label' => 'Tags', 'type' => 'select_multiple', 'relationship' => 'tags'], 29 | ]; 30 | 31 | $this->withTrashed = true; 32 | 33 | $this->validationRules = [ 34 | 'title' => 'string|required|max:255', 35 | 'slug' => 'string|required|max:100', 36 | 'body' => 'required', 37 | 'publish_on' => 'date', 38 | 'published' => 'boolean', 39 | 'category_id' => 'int|required', 40 | ]; 41 | 42 | $this->validationMessages = [ 43 | 'body.required' => 'You need to fill in the post content.', 44 | ]; 45 | 46 | $this->validationAttributes = [ 47 | 'title' => 'Post title', 48 | ]; 49 | 50 | $this->model = $post; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/CrudFormsTest.php: -------------------------------------------------------------------------------- 1 | load(['category', 'tags']); 18 | $response = $this->get('/post'); 19 | 20 | $response->assertViewHas('entities', $posts) 21 | ->assertSee($posts[0]->title); 22 | } 23 | 24 | /** @test */ 25 | public function it_shows_a_single_resource() 26 | { 27 | $post = Post::find(1)->load(['category', 'tags']); 28 | $response = $this->get('/post/1'); 29 | 30 | $response->assertViewHas('entity', $post) 31 | ->assertSee($post->title); 32 | } 33 | 34 | /** @test */ 35 | public function it_loads_the_form_for_editing_resource() 36 | { 37 | $post = Post::find(1)->load(['category', 'tags']); 38 | $response = $this->get('/post/1/edit'); 39 | 40 | $response->assertViewHas('entity', $post) 41 | ->assertSee('Submit Form') 42 | ->assertSee($post->title); 43 | } 44 | 45 | /** @test */ 46 | public function it_loads_the_form_for_creating_a_resource() 47 | { 48 | $response = $this->get('/post/create'); 49 | 50 | $response->assertSee('Submit Form'); 51 | } 52 | 53 | /** @test */ 54 | public function it_can_create_a_new_resource() 55 | { 56 | $this->post('/post', [ 57 | 'title' => 'post X', 58 | 'slug' => 'post-x', 59 | 'body' => 'post X body', 60 | 'publish_on' => now(), 61 | 'published' => 1, 62 | 'category_id'=> 1, 63 | ]); 64 | 65 | $this->assertCount(1, Post::where('title', '=', 'post X')->get()); 66 | } 67 | 68 | /** @test */ 69 | public function it_can_update_a_resource() 70 | { 71 | $this->assertCount(0, Post::where('title', '=', 'post X')->get()); 72 | 73 | $this->put('/post/1', [ 74 | 'title' => 'post X', 75 | 'slug' => 'post-x', 76 | 'body' => 'post X body', 77 | 'publish_on' => now(), 78 | 'published' => 1, 79 | 'category_id'=> 1, 80 | ]); 81 | 82 | $this->assertCount(1, Post::where('title', '=', 'post X')->get()); 83 | } 84 | 85 | /** @test */ 86 | public function it_can_delete_a_resource() 87 | { 88 | $postCount = Post::all()->count(); 89 | $this->delete('/post/1'); 90 | 91 | $this->assertSame($postCount - 1, Post::all()->count()); 92 | } 93 | 94 | /** @test */ 95 | public function it_can_restore_a_soft_deleted_resource() 96 | { 97 | $postCount = Post::all()->count(); 98 | $this->delete('/post/1'); 99 | 100 | $this->assertSame($postCount - 1, Post::all()->count()); 101 | 102 | $this->get('/post')->assertSee('Restore'); 103 | 104 | $this->put('/post/1/restore'); 105 | 106 | $this->assertSame($postCount, Post::all()->count()); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/Models/Category.php: -------------------------------------------------------------------------------- 1 | hasMany(Post::class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Models/Post.php: -------------------------------------------------------------------------------- 1 | belongsTo(Category::class); 24 | } 25 | 26 | public function tags() 27 | { 28 | return $this->belongsToMany(Tag::class); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Models/Tag.php: -------------------------------------------------------------------------------- 1 | belongsToMany(Post::class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | group(['namespace' => $this->namespace], function ($router) { 28 | $router->get('/', function () { 29 | return 'home'; 30 | }); 31 | 32 | $router->group(['middleware' => 'web'], function ($router) { 33 | $router->resource('/post', 'PostController'); 34 | $router->put('/post/{post}/restore','PostController@restore'); 35 | }); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | setUpDatabase(); 26 | $this->migrateTables(); 27 | $this->seedTables(); 28 | } 29 | 30 | /** 31 | * Get package providers. 32 | * 33 | * @param \Illuminate\Foundation\Application $app 34 | * @return array 35 | */ 36 | protected function getPackageProviders($app) 37 | { 38 | return [ 39 | CrudFormsServiceProvider::class, 40 | RouteServiceProvider::class, 41 | ]; 42 | } 43 | 44 | /** 45 | * Define environment setup. 46 | * 47 | * @param \Illuminate\Foundation\Application $app 48 | * @return void 49 | */ 50 | protected function getEnvironmentSetUp($app) 51 | { 52 | $this->app = $app; 53 | $app['config']->set('app.url', 'http://localhost'); 54 | $app['config']->set('app.key', 'base64:WpZ7D2IUkBA+99f8HABIVujw2HqzR6kLGsTpDdV5nao='); 55 | } 56 | 57 | /** 58 | * Setup the test database. 59 | * 60 | * @return void 61 | */ 62 | protected function setUpDatabase() 63 | { 64 | $database = new DB(); 65 | 66 | $database->addConnection(['driver' => 'sqlite', 'database' => ':memory:']); 67 | $database->bootEloquent(); 68 | $database->setAsGlobal(); 69 | } 70 | 71 | /** 72 | * Run migrations on test database. 73 | * 74 | * @return void 75 | */ 76 | protected function migrateTables() 77 | { 78 | DB::schema()->create('categories', function ($table) { 79 | $table->increments('id'); 80 | $table->string('name'); 81 | $table->string('slug', 100); 82 | $table->timestamps(); 83 | $table->softDeletes(); 84 | }); 85 | 86 | DB::schema()->create('tags', function ($table) { 87 | $table->increments('id'); 88 | $table->string('name'); 89 | $table->string('slug', 100); 90 | $table->timestamps(); 91 | }); 92 | 93 | DB::schema()->create('posts', function ($table) { 94 | $table->increments('id'); 95 | $table->string('title'); 96 | $table->string('slug', 100); 97 | $table->text('body'); 98 | $table->date('publish_on')->nullable(); 99 | $table->boolean('published')->default(false); 100 | $table->unsignedInteger('category_id'); 101 | $table->timestamps(); 102 | $table->softDeletes(); 103 | 104 | $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade'); 105 | }); 106 | 107 | DB::schema()->create('post_tag', function ($table) { 108 | $table->unsignedInteger('post_id'); 109 | $table->unsignedInteger('tag_id'); 110 | 111 | $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade'); 112 | $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); 113 | 114 | $table->primary(['post_id', 'tag_id']); 115 | }); 116 | } 117 | 118 | /** 119 | * Seed test tables with dummy data. 120 | * 121 | * @return void 122 | */ 123 | protected function seedTables() 124 | { 125 | for ($i = 1; $i <= 11; ++$i) { 126 | Category::create([ 127 | 'name' => "category $i", 128 | 'slug' => "category-$i", 129 | ]); 130 | 131 | Tag::create([ 132 | 'name' => "tag $i", 133 | 'slug' => "tag-$i", 134 | ]); 135 | } 136 | 137 | for ($i = 1; $i <= 11; ++$i) { 138 | $post = Post::create([ 139 | 'title' => "post $i", 140 | 'slug' => "post-$i", 141 | 'body' => "post $i body", 142 | 'publish_on' => date_sub(Carbon::now(), date_interval_create_from_date_string("$i days")), 143 | 'published' => rand(0, 1), 144 | 'category_id'=> $i, 145 | ]); 146 | 147 | $post->tags()->attach(Tag::where('id', '<=', rand(0, 10))->get()); 148 | } 149 | } 150 | } 151 | --------------------------------------------------------------------------------