├── .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 |
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 |
4 | @foreach ($entity->{$field['name']} as $related)
5 | - {{ $related->{$field['relFieldName']} }}
6 | @endforeach
7 |
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 |
4 | @foreach ($entity->{$field['name']} as $related)
5 | - {{ $related->{$field['relFieldName']} }}
6 | @endforeach
7 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------