├── composer.json
├── readme.md
├── resources
├── css
│ └── crudify.css
├── js
│ └── crudify.js
├── stubs
│ ├── generate
│ │ ├── DummyClass.stub
│ │ ├── DummyClassController.stub
│ │ ├── DummyClassDatatable.stub
│ │ ├── DummyClassFactory.stub
│ │ ├── DummyClassRequest.stub
│ │ ├── DummyClassSeeder.stub
│ │ ├── DummyMigration.stub
│ │ ├── navbar-link.stub
│ │ ├── routes.stub
│ │ └── views
│ │ │ ├── actions.stub
│ │ │ ├── create.stub
│ │ │ ├── edit.stub
│ │ │ ├── fields.stub
│ │ │ ├── index.stub
│ │ │ └── show.stub
│ └── install
│ │ ├── browser-sync.stub
│ │ └── datatables-script.stub
└── views
│ ├── checkbox.blade.php
│ ├── checkboxes.blade.php
│ ├── file.blade.php
│ ├── input.blade.php
│ ├── radios.blade.php
│ ├── select.blade.php
│ └── textarea.blade.php
└── src
├── Commands
├── GeneratesCrud.php
└── InstallsCrudify.php
├── Components
├── Checkbox.php
├── Checkboxes.php
├── File.php
├── Input.php
├── Radios.php
├── Select.php
└── Textarea.php
├── Http
└── Datatable.php
├── Providers
└── CrudifyServiceProvider.php
├── Seeders
└── AdminUserSeeder.php
└── Traits
├── FillsColumns.php
├── FormatsOptions.php
├── HasInputAttributes.php
└── SerializesDates.php
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kejojedi/crudify",
3 | "description": "Laravel 7 CRUD app scaffolding & generator.",
4 | "homepage": "https://github.com/kejojedi/crudify",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Kevin Dion",
9 | "email": "kejojedi@gmail.com",
10 | "role": "Developer"
11 | }
12 | ],
13 | "require": {
14 | "barryvdh/laravel-ide-helper": "^2.6",
15 | "laravel/framework": "^7.0",
16 | "laravel/ui": "^2.0",
17 | "yajra/laravel-datatables-html": "^4.23",
18 | "yajra/laravel-datatables-oracle": "^9.9"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "Kejojedi\\Crudify\\": "src"
23 | }
24 | },
25 | "extra": {
26 | "laravel": {
27 | "providers": [
28 | "Kejojedi\\Crudify\\Providers\\CrudifyServiceProvider"
29 | ]
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Crudify
4 |
5 | Crudify is a Laravel 7 package which includes sensible CRUD app scaffolding and a generator to make your life easier. It automates initial CRUD app setup with the `crudify:install` command, and generates CRUD resource files for you with the `crudify:generate` command. It also includes form components to make creating forms a breeze.
6 |
7 | It is configured to work well with PHPStorm, Valet, and Laragon, among others. **This package requires Node.js to be installed in order to run `npm` commands.**
8 |
9 | Useful links:
10 |
11 | - Support: [GitHub Issues](https://github.com/kejojedi/crudify/issues)
12 | - Contribute: [GitHub Pulls](https://github.com/kejojedi/crudify/pulls)
13 | - Donate: [PayPal](https://www.paypal.com/paypalme2/kjjdion)
14 |
15 | ## Installation
16 |
17 | Install Laravel:
18 |
19 | laravel new app
20 |
21 | Configure `.env` file:
22 |
23 | APP_NAME=App
24 | APP_URL=http://app.test
25 | DB_DATABASE=app
26 | MAIL_USERNAME=mailtrap_username
27 | MAIL_PASSWORD=mailtrap_password
28 | MAIL_FROM_ADDRESS=info@app.test
29 |
30 | Require Crudify:
31 |
32 | composer require kejojedi/crudify
33 |
34 | Install Crudify:
35 |
36 | php artisan crudify:install
37 |
38 | Visit your app URL and login using:
39 |
40 | Email: admin@example.com
41 | Password: password
42 |
43 | The `AdminUserSeeder` call can be removed from your `DatabaseSeeder` any time.
44 |
45 | ## Generating CRUD
46 |
47 | Run `crudify:generate` for a new model:
48 |
49 | php artisan crudify:generate Model
50 |
51 | This will generate:
52 |
53 | - Controller
54 | - Datatable
55 | - Form Request
56 | - Model
57 | - Factory
58 | - Migration
59 | - Seeder
60 | - View Files
61 | - Navbar Link
62 | - Routes
63 |
64 | Don't forget to migrate after updating the new migration file.
65 |
66 | **Tip: use the `--force` in order to replace existing generated files e.g. `php artisan crudify:generate Model --force`**
67 |
68 | ## Datatables
69 |
70 | Crudify includes a wrapper for [yajra/laravel-datatables-html](https://github.com/yajra/laravel-datatables-html) to make building datatables nice and declarative. Generated model datatable classes are located in `app\Http\Datatables`.
71 |
72 | Declaring [columns](https://yajrabox.com/docs/laravel-datatables/master/html-builder-column-builder):
73 |
74 | protected function columns()
75 | {
76 | return [
77 | Column::make('id'),
78 | Column::make('name'),
79 | Column::make('created_at'),
80 | Column::make('updated_at'),
81 | ];
82 | }
83 |
84 | Different ways of defining default sort order:
85 |
86 | protected $order_by = 'id'; // sorts by id, ascending
87 | protected $order_by = ['created_at', 'desc']; // sorts by created_at, descending
88 |
89 | protected function orderBy()
90 | {
91 | return 'id'; // sorts by id, ascending
92 | }
93 |
94 | protected function orderBy()
95 | {
96 | return ['created_at', 'desc']; // sorts by created_at, descending
97 | }
98 |
99 | **Note: a users per-page entries & sorting preferences are saved per-table in their browser indefinitely, so this will only set the initial default order.**
100 |
101 | Example of adding methods to the datatables html builder:
102 |
103 | protected function htmlMethods(Builder &$html)
104 | {
105 | $html->ajax([
106 | 'url' => route('users.index'),
107 | 'type' => 'GET',
108 | 'data' => 'function(d) { d.key = "value"; }',
109 | ]);
110 | }
111 |
112 | Example of adding methods to the datatables json builder:
113 |
114 | protected function jsonMethods(DataTableAbstract &$datatables)
115 | {
116 | $datatables->editColumn('name', function(User $user) {
117 | return 'Hi ' . $user->name . '!';
118 | });
119 | }
120 |
121 | **Tip: If you don't want a datatable to have an actions column, simply remove the `actions()` method entirely.**
122 |
123 | ## Form Components
124 |
125 | Crudify offers simple form components to make building forms fast & easy. See below for minimal and complete examples of each component.
126 |
127 | Input:
128 |
129 |
130 |
131 |
132 | Textarea:
133 |
134 |
135 |
136 |
137 | Select:
138 |
139 |
140 |
141 |
142 | **Note: if the options are an associative array, the keys are used as the labels and the values as the values. For sequential arrays, the values are used for both the labels and values.**
143 |
144 | File:
145 |
146 |
147 |
148 |
149 | Checkbox:
150 |
151 |
152 |
153 |
154 | **Note: checkbox attributes should have `boolean` migration columns.**
155 |
156 | Checkboxes:
157 |
158 |
159 |
160 |
161 | **Note: checkboxes attributes should be cast to `array` with `text` migration columns.**
162 |
163 | Radios:
164 |
165 |
166 |
167 |
168 | **Tip: you can determine if the fields are showing on the `create` or `edit` page by checking `isset($model)` (e.g. `isset($car)`). If a `$model` is set, it means the user is on the edit page.**
169 |
170 | ## Packages Used
171 |
172 | Composer packages:
173 |
174 | - [barryvdh/laravel-ide-helper](https://github.com/barryvdh/laravel-ide-helper)
175 | - [laravel/ui](https://github.com/laravel/ui)
176 | - [yajra/laravel-datatables-html](https://github.com/yajra/laravel-datatables-html)
177 | - [yajra/laravel-datatables-oracle](https://github.com/yajra/laravel-datatables)
178 |
179 | NPM packages:
180 |
181 | - [@fortawesome/fontawesome-free](https://www.npmjs.com/package/@fortawesome/fontawesome-free)
182 | - [browser-sync](https://www.npmjs.com/package/browser-sync)
183 | - [datatables.net-bs4](https://www.npmjs.com/package/datatables.net-bs4)
184 | - [datatables.net-responsive-bs4](https://www.npmjs.com/package/datatables.net-responsive-bs4)
185 |
--------------------------------------------------------------------------------
/resources/css/crudify.css:
--------------------------------------------------------------------------------
1 | .card-body table.dataTable {
2 | margin-left: -1.25rem;
3 | margin-right: -1.25rem;
4 | width: calc(100% + 2.5rem);
5 | }
6 |
7 | table.dataTable tr th {
8 | white-space: nowrap;
9 | }
10 |
11 | div.dataTables_paginate {
12 | overflow-x: auto
13 | }
14 | div.dataTables_paginate .pagination {
15 | display: inline-table
16 | }
17 | div.dataTables_paginate .pagination li {
18 | display: table-cell
19 | }
20 |
21 | @media (min-width: 768px) {
22 | .card-body table.dataTable tr th:first-child, .card-body table.dataTable tr td:first-child {
23 | padding-left: 1.25rem;
24 | }
25 | .card-body table.dataTable tr th:last-child, .card-body table.dataTable tr td:last-child {
26 | padding-right: 1.25rem;
27 | }
28 | table.dataTable tr th, table.dataTable tr td {
29 | vertical-align: middle;
30 | }
31 | }
32 |
33 | @media (max-width: 767.98px) {
34 | table.dataTable > tbody > tr.child span.dtr-title:empty {
35 | display: none;
36 | }
37 |
38 | .col-form-label {
39 | padding-top: 0 !important;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/resources/js/crudify.js:
--------------------------------------------------------------------------------
1 | $.extend(true, $.fn.dataTable.defaults, {
2 | autoWidth: false,
3 | responsive: true,
4 | stateDuration: 0,
5 | stateSave: true,
6 | stateSaveParams: function (settings, data) {
7 | data.search.search = '';
8 | data.start = 0;
9 | },
10 | stateLoadCallback: function (settings, callback) {
11 | return JSON.parse(localStorage.getItem($(this).attr('id')));
12 | },
13 | stateSaveCallback: function (settings, data) {
14 | localStorage.setItem($(this).attr('id'), JSON.stringify(data));
15 | }
16 | });
17 |
18 | $(document).on('input', '.custom-file-input', function () {
19 | let files = [];
20 |
21 | for (let i = 0; i < $(this)[0].files.length; i++) {
22 | files.push($(this)[0].files[i].name);
23 | }
24 |
25 | $(this).next('.custom-file-label').html(files.join(', '));
26 | });
27 |
--------------------------------------------------------------------------------
/resources/stubs/generate/DummyClass.stub:
--------------------------------------------------------------------------------
1 | middleware('auth');
15 | }
16 |
17 | public function index(Request $request)
18 | {
19 | $query = DummyClass::query();
20 | $datatables = DummyClassDatatable::make($query);
21 |
22 | return $request->ajax()
23 | ? $datatables->json()
24 | : view('DummyVars.index', $datatables->html());
25 | }
26 |
27 | public function create()
28 | {
29 | return view('DummyVars.create');
30 | }
31 |
32 | public function store(DummyClassRequest $request)
33 | {
34 | DummyClass::create($request->all());
35 |
36 | return $request->input('submit') == 'reload'
37 | ? redirect()->route('DummyVars.create')
38 | : redirect()->route('DummyVars.index');
39 | }
40 |
41 | public function show(DummyClass $DummyVar)
42 | {
43 | return view('DummyVars.show', compact('DummyVar'));
44 | }
45 |
46 | public function edit(DummyClass $DummyVar)
47 | {
48 | return view('DummyVars.edit', compact('DummyVar'));
49 | }
50 |
51 | public function update(DummyClassRequest $request, DummyClass $DummyVar)
52 | {
53 | $DummyVar->update($request->all());
54 |
55 | return $request->input('submit') == 'reload'
56 | ? redirect()->route('DummyVars.edit', $DummyVar->id)
57 | : redirect()->route('DummyVars.index');
58 | }
59 |
60 | /** @noinspection PhpUnhandledExceptionInspection */
61 | public function destroy(DummyClass $DummyVar)
62 | {
63 | $DummyVar->delete();
64 |
65 | return redirect()->route('DummyVars.index');
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/resources/stubs/generate/DummyClassDatatable.stub:
--------------------------------------------------------------------------------
1 | define(DummyClass::class, function (Faker $faker) {
9 | return [
10 | 'name' => $faker->firstNameFemale,
11 | ];
12 | });
13 |
--------------------------------------------------------------------------------
/resources/stubs/generate/DummyClassRequest.stub:
--------------------------------------------------------------------------------
1 | ['required', Rule::unique('DummyVars')->ignore($this->route('DummyVar'))],
14 | ];
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/resources/stubs/generate/DummyClassSeeder.stub:
--------------------------------------------------------------------------------
1 | create();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/resources/stubs/generate/DummyMigration.stub:
--------------------------------------------------------------------------------
1 | id();
13 | $table->string('name');
14 | $table->timestamps();
15 | });
16 | }
17 |
18 | public function down()
19 | {
20 | Schema::dropIfExists('DummyVars');
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/resources/stubs/generate/navbar-link.stub:
--------------------------------------------------------------------------------
1 |
2 | {{ __('DummyTitles') }}
3 |
4 |
--------------------------------------------------------------------------------
/resources/stubs/generate/routes.stub:
--------------------------------------------------------------------------------
1 | Route::resource('DummyVars', 'DummyClassController');
2 |
--------------------------------------------------------------------------------
/resources/stubs/generate/views/actions.stub:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
12 |
13 |
14 |
15 |
19 |
20 |
--------------------------------------------------------------------------------
/resources/stubs/generate/views/create.stub:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('title', __('Create DummyTitle'))
4 | @section('content')
5 |
6 |
@yield('title')
7 |
8 |
20 |
21 | @endsection
22 |
--------------------------------------------------------------------------------
/resources/stubs/generate/views/edit.stub:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('title', __('Edit DummyTitle'))
4 | @section('content')
5 |
6 |
7 |
8 |
@yield('title')
9 |
10 |
11 | @include('DummyVars.actions')
12 |
13 |
14 |
15 |
28 |
29 | @endsection
30 |
--------------------------------------------------------------------------------
/resources/stubs/generate/views/fields.stub:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/resources/stubs/generate/views/index.stub:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('title', __('DummyTitles'))
4 | @section('content')
5 |
6 |
7 |
8 |
@yield('title')
9 |
10 |
13 |
14 |
15 |
16 |
17 | {!! $html->table() !!}
18 | {!! $html->scripts() !!}
19 |
20 |
21 |
22 | @endsection
23 |
--------------------------------------------------------------------------------
/resources/stubs/generate/views/show.stub:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('title', __('DummyTitle'))
4 | @section('content')
5 |
6 |
7 |
8 |
@yield('title')
9 |
10 |
11 | @include('DummyVars.actions')
12 |
13 |
14 |
15 |
16 |
17 | @foreach($DummyVar->toArray() as $attribute => $value)
18 |
19 |
20 |
21 | {{ Str::title(str_replace('_', ' ', $attribute)) }}
22 |
23 |
24 | @if(is_array($value))
25 |
{{ json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) }}
26 | @else
27 | {{ $value ?? __('N/A') }}
28 | @endif
29 |
30 |
31 |
32 | @endforeach
33 |
34 |
35 |
36 | @endsection
37 |
--------------------------------------------------------------------------------
/resources/stubs/install/browser-sync.stub:
--------------------------------------------------------------------------------
1 | mix.browserSync({
2 | proxy: 'DummyDomain',
3 | snippetOptions: {
4 | whitelist: ['*'],
5 | }
6 | });
7 |
--------------------------------------------------------------------------------
/resources/stubs/install/datatables-script.stub:
--------------------------------------------------------------------------------
1 | window.addEventListener('DOMContentLoaded', function() {
2 | (function(window,$){window.LaravelDataTables=window.LaravelDataTables||{};window.LaravelDataTables["%1$s"]=$("#%1$s").DataTable(%2$s);})(window,jQuery);
3 | });
4 |
--------------------------------------------------------------------------------
/resources/views/checkbox.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | @if($label)
4 |
5 | @endif
6 |
7 |
8 |
9 |
16 |
17 |
18 |
19 | @error($name)
{{ $message }} @enderror
20 | @if($hint)
{{ $hint }} @endif
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/resources/views/checkboxes.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | @foreach($options as $option_label => $option_value)
8 |
9 |
16 |
17 |
18 | @endforeach
19 |
20 | @error($name)
{{ $message }} @enderror
21 | @if($hint)
{{ $hint }} @endif
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/resources/views/file.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 | @error($name) {{ $message }} @enderror
13 | @if($hint) {{ $hint }} @endif
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/resources/views/input.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 | @error($name) {{ $message }} @enderror
13 | @if($hint) {{ $hint }} @endif
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/resources/views/radios.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | @foreach($options as $option_label => $option_value)
8 |
9 |
16 |
17 |
18 | @endforeach
19 |
20 | @error($name)
{{ $message }} @enderror
21 | @if($hint)
{{ $hint }} @endif
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/resources/views/select.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 | @error($name) {{ $message }} @enderror
17 | @if($hint) {{ $hint }} @endif
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/resources/views/textarea.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 | @error($name) {{ $message }} @enderror
12 | @if($hint) {{ $hint }} @endif
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Commands/GeneratesCrud.php:
--------------------------------------------------------------------------------
1 | setReplaces();
19 | $this->createPhpFiles();
20 | $this->createViewFiles();
21 | $this->insertNavLink();
22 | $this->insertRoutes();
23 |
24 | Artisan::call('ide-helper:generate', [], $this->getOutput());
25 |
26 | $this->info('CRUD generation complete for: ' . $this->argument('model'));
27 | $this->warn("Don't forget to migrate after updating the new migration file.");
28 | }
29 |
30 | private function setReplaces()
31 | {
32 | $title = trim(preg_replace('/[A-Z]([A-Z](?![a-z]))*/', ' $0', $this->argument('model')));
33 |
34 | $this->replaces = [
35 | 'DummyTitles' => $titles = Str::plural($title),
36 | 'DummyTitle' => $title,
37 | 'DummyClasses' => str_replace(' ', '', $titles),
38 | 'DummyClass' => $this->argument('model'),
39 | 'DummyVars' => $vars = Str::snake($titles),
40 | 'DummyVar' => Str::snake($title),
41 | 'DummyMigration' => date('Y_m_d_') . '000000_create_' . $vars . '_table',
42 | ];
43 | }
44 |
45 | private function replace($contents)
46 | {
47 | foreach ($this->replaces as $search => $replace) {
48 | $contents = str_replace($search, $replace, $contents);
49 | }
50 |
51 | return $contents;
52 | }
53 |
54 | private function createPhpFiles()
55 | {
56 | File::ensureDirectoryExists(app_path('Http/Datatables'));
57 | File::ensureDirectoryExists(app_path('Http/Requests'));
58 |
59 | $files = [
60 | 'DummyClass' => app_path(),
61 | 'DummyClassController' => app_path('Http/Controllers'),
62 | 'DummyClassDatatable' => app_path('Http/Datatables'),
63 | 'DummyClassRequest' => app_path('Http/Requests'),
64 | 'DummyClassFactory' => database_path('factories'),
65 | 'DummyClassSeeder' => database_path('seeds'),
66 | 'DummyMigration' => database_path('migrations'),
67 | ];
68 |
69 | foreach ($files as $stub => $path) {
70 | $stub_contents = file_get_contents(__DIR__ . '/../../resources/stubs/generate/' . $stub . '.stub');
71 | $new_file = $path . '/' . $this->replace($stub) . '.php';
72 |
73 | $this->createFile($new_file, $stub_contents);
74 | }
75 | }
76 |
77 | private function createViewFiles()
78 | {
79 | $view_path = resource_path('views/' . $this->replaces['DummyVars']);
80 | File::ensureDirectoryExists($view_path);
81 |
82 | foreach (File::allFiles(__DIR__ . '/../../resources/stubs/generate/views') as $stub) {
83 | $stub_contents = $this->replace($stub->getContents());
84 | $new_file = $view_path . '/' . str_replace('.stub', '.blade.php', $stub->getBasename());
85 |
86 | $this->createFile($new_file, $stub_contents);
87 | }
88 | }
89 |
90 | private function createFile($new_file, $stub_contents)
91 | {
92 | if (!file_exists($new_file) || $this->option('force')) {
93 | file_put_contents($new_file, $this->replace($stub_contents));
94 |
95 | $this->line('Created new file: ' . $new_file);
96 | }
97 | else {
98 | $this->info('File already exists: ' . $new_file);
99 | }
100 | }
101 |
102 | private function insertNavLink()
103 | {
104 | $stub_contents = $this->replace(rtrim(file_get_contents(__DIR__ . '/../../resources/stubs/generate/navbar-link.stub')));
105 | $nav_file = resource_path('views/layouts/app.blade.php');
106 |
107 | if (file_exists($nav_file)) {
108 | $nav_contents = file_get_contents($nav_file);
109 | $nav_hook = ' ';
110 |
111 | if (Str::contains($nav_contents, $nav_hook) && !Str::contains($nav_contents, $stub_contents)) {
112 | $nav_contents = Str::replaceLast($nav_hook, $stub_contents . PHP_EOL . PHP_EOL . $nav_hook, $nav_contents);
113 |
114 | file_put_contents($nav_file, $nav_contents);
115 |
116 | $this->line('Nav link inserted in: ' . $nav_file);
117 | }
118 | }
119 | }
120 |
121 | private function insertRoutes()
122 | {
123 | $stub_contents = $this->replace(rtrim(file_get_contents(__DIR__ . '/../../resources/stubs/generate/routes.stub')));
124 | $routes_file = base_path('routes/web.php');
125 | $routes_contents = file_get_contents($routes_file);
126 |
127 | if (!Str::contains($routes_contents, $stub_contents)) {
128 | file_put_contents($routes_file, rtrim($routes_contents) . PHP_EOL . PHP_EOL . $stub_contents);
129 |
130 | $this->line('Routes inserted in: ' . $routes_file);
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Commands/InstallsCrudify.php:
--------------------------------------------------------------------------------
1 | updateDatabaseSeeder();
18 | Artisan::call('migrate:fresh --seed', [], $this->getOutput());
19 |
20 | // configure & generate ide helper file
21 | Artisan::call('vendor:publish --tag=config', [], $this->getOutput());
22 | $this->updateIdeHelperConfig();
23 | Artisan::call('ide-helper:generate', [], $this->getOutput());
24 |
25 | // configure datatables
26 | Artisan::call('vendor:publish --tag=datatables-html', [], $this->getOutput());
27 | $this->updateDatatablesConfig();
28 | $this->replaceDatatablesScript();
29 |
30 | // scaffold frontend
31 | Artisan::call('ui bootstrap --auth', [], $this->getOutput());
32 | $this->insertBrowserSyncMix();
33 | $this->insertJsResources();
34 | $this->insertSassResources();
35 | $this->insertNpmPackages();
36 | $this->executeNpmCommands();
37 |
38 | $this->info('Crudify installation complete.');
39 | }
40 |
41 | private function updateDatabaseSeeder()
42 | {
43 | $seeder_path = database_path('seeds/DatabaseSeeder.php');
44 | $seeder_contents = file_get_contents($seeder_path);
45 | $updated_contents = str_replace('// $this->call(UserSeeder::class);', '$this->call(\Kejojedi\Crudify\Seeders\AdminUserSeeder::class);', $seeder_contents);
46 |
47 | file_put_contents($seeder_path, $updated_contents);
48 |
49 | $this->line('Updated database seeder.');
50 | }
51 |
52 | private function updateIdeHelperConfig()
53 | {
54 | $config_path = config_path('ide-helper.php');
55 | $config_contents = file_get_contents($config_path);
56 | $updated_contents = str_replace("'write_eloquent_model_mixins' => false", "'write_eloquent_model_mixins' => true", $config_contents);
57 |
58 | file_put_contents($config_path, $updated_contents);
59 |
60 | $this->line('Updated IDE helper config.');
61 | }
62 |
63 | private function updateDatatablesConfig()
64 | {
65 | $config_path = config_path('datatables-html.php');
66 | $config_contents = file_get_contents($config_path);
67 | $updated_contents = str_replace("'class' => 'table'", "'class' => 'table table-hover'", $config_contents);
68 |
69 | file_put_contents($config_path, $updated_contents);
70 |
71 | $this->line('Updated datatables config.');
72 | }
73 |
74 | private function replaceDatatablesScript()
75 | {
76 | $stub_path = __DIR__ . '/../../resources/stubs/install/datatables-script.stub';
77 | $stub_contents = file_get_contents($stub_path);
78 | $script_path = resource_path('views/vendor/datatables/script.blade.php');
79 |
80 | file_put_contents($script_path, $stub_contents);
81 |
82 | $this->line('Replaced datatables script.');
83 | }
84 |
85 | private function insertBrowserSyncMix()
86 | {
87 | $mix_path = base_path('webpack.mix.js');
88 | $mix_contents = file_get_contents($mix_path);
89 | $mix_domain = str_replace(['http://', 'https://'], '', config('app.url'));
90 | $stub_contents = str_replace('DummyDomain', $mix_domain, rtrim(file_get_contents(__DIR__ . '/../../resources/stubs/install/browser-sync.stub')));
91 |
92 | if (!Str::contains($mix_contents, $stub_contents)) {
93 | $mix_contents = rtrim($mix_contents) . PHP_EOL . PHP_EOL . $stub_contents;
94 | }
95 |
96 | file_put_contents($mix_path, $mix_contents);
97 |
98 | $this->line('Inserted browser sync mix.');
99 | }
100 |
101 | private function insertJsResources()
102 | {
103 | $js_path = resource_path('js/app.js');
104 | $js_contents = file_get_contents($js_path);
105 | $js_resources = [
106 | 'datatables.net-bs4',
107 | 'datatables.net-responsive-bs4',
108 | '../../vendor/kejojedi/crudify/resources/js/crudify',
109 | ];
110 |
111 | foreach ($js_resources as $js_resource) {
112 | if (!Str::contains($js_contents, $js_resource)) {
113 | $js_require = "require('$js_resource');";
114 | $js_contents = trim($js_contents) . PHP_EOL . PHP_EOL . $js_require;
115 | }
116 | }
117 |
118 | file_put_contents($js_path, $js_contents);
119 |
120 | $this->line('Inserted JS resources.');
121 | }
122 |
123 | private function insertSassResources()
124 | {
125 | $sass_path = resource_path('sass/app.scss');
126 | $sass_contents = file_get_contents($sass_path);
127 | $sass_resources = [
128 | '~@fortawesome/fontawesome-free/css/all.css',
129 | '~datatables.net-bs4/css/dataTables.bootstrap4.css',
130 | '~datatables.net-responsive-bs4/css/responsive.bootstrap4.css',
131 | '../../vendor/kejojedi/crudify/resources/css/crudify.css',
132 | ];
133 |
134 | foreach ($sass_resources as $sass_resource) {
135 | if (!Str::contains($sass_contents, $sass_resource)) {
136 | $sass_import = "@import '$sass_resource';";
137 | $sass_contents = trim($sass_contents) . PHP_EOL . PHP_EOL . $sass_import;
138 | }
139 | }
140 |
141 | file_put_contents($sass_path, $sass_contents);
142 |
143 | $this->line('Inserted SASS resources.');
144 | }
145 |
146 | private function insertNpmPackages()
147 | {
148 | $package_path = base_path('package.json');
149 | $package_contents = file_get_contents($package_path);
150 | $package_array = json_decode($package_contents, true);
151 | $packages_to_insert = [
152 | '@fortawesome/fontawesome-free' => '^5.13.0',
153 | 'datatables.net-bs4' => '^1.10.20',
154 | 'datatables.net-responsive-bs4' => '^2.2.3',
155 | ];
156 |
157 | foreach ($packages_to_insert as $name => $version) {
158 | if (!isset($package_array['devDependencies'][$name])) {
159 | $package_array['devDependencies'][$name] = $version;
160 | }
161 | }
162 |
163 | file_put_contents($package_path, json_encode($package_array, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
164 |
165 | $this->line('Inserted NPM packages.');
166 | }
167 |
168 | private function executeNpmCommands()
169 | {
170 | exec('npm install && npm run dev');
171 | exec('npm run dev');
172 |
173 | $this->line('Executed NPM commands.');
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/Components/Checkbox.php:
--------------------------------------------------------------------------------
1 | name = $name;
19 | $this->label = $label;
20 | $this->checkbox_label = $checkboxLabel ?? Str::title(str_replace('_', ' ', $name));
21 | $this->id = $id ?? $name;
22 | $this->value = $value;
23 | $this->hint = $hint;
24 | $this->disabled = $disabled;
25 | }
26 |
27 | public function render()
28 | {
29 | return view('crudify::checkbox');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Components/Checkboxes.php:
--------------------------------------------------------------------------------
1 | name = $name;
20 | $this->options = $this->formatOptions($options);
21 | $this->label = $label ?? Str::title(str_replace('_', ' ', $name));
22 | $this->id = $id ?? $name;
23 | $this->value = $value;
24 | $this->hint = $hint;
25 | $this->disabled = $disabled;
26 | }
27 |
28 | public function render()
29 | {
30 | return view('crudify::checkboxes');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Components/File.php:
--------------------------------------------------------------------------------
1 | name = $name;
19 | $this->label = $label ?? Str::title(str_replace('_', ' ', $name));
20 | $this->file_label = $fileLabel;
21 | $this->id = $id ?? $name;
22 | $this->multiple = $multiple;
23 | $this->hint = $hint;
24 | $this->disabled = $disabled;
25 | }
26 |
27 | public function render()
28 | {
29 | return view('crudify::file');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Components/Input.php:
--------------------------------------------------------------------------------
1 | name = $name;
20 | $this->type = $type;
21 | $this->label = $label ?? Str::title(str_replace('_', ' ', $name));
22 | $this->id = $id ?? $name;
23 | $this->value = $value;
24 | $this->hint = $hint;
25 | $this->disabled = $disabled;
26 | $this->readonly = $readonly;
27 | }
28 |
29 | public function render()
30 | {
31 | return view('crudify::input');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Components/Radios.php:
--------------------------------------------------------------------------------
1 | name = $name;
20 | $this->options = $this->formatOptions($options);
21 | $this->label = $label ?? Str::title(str_replace('_', ' ', $name));
22 | $this->id = $id ?? $name;
23 | $this->value = $value;
24 | $this->hint = $hint;
25 | $this->disabled = $disabled;
26 | }
27 |
28 | public function render()
29 | {
30 | return view('crudify::radios');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Components/Select.php:
--------------------------------------------------------------------------------
1 | name = $name;
21 | $this->options = $this->formatOptions($options);
22 | $this->empty = $empty;
23 | $this->label = $label ?? Str::title(str_replace('_', ' ', $name));
24 | $this->id = $id ?? $name;
25 | $this->value = $value;
26 | $this->hint = $hint;
27 | $this->disabled = $disabled;
28 | }
29 |
30 | public function render()
31 | {
32 | return view('crudify::select');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Components/Textarea.php:
--------------------------------------------------------------------------------
1 | name = $name;
20 | $this->rows = $rows;
21 | $this->label = $label ?? Str::title(str_replace('_', ' ', $name));
22 | $this->id = $id ?? $name;
23 | $this->value = $value;
24 | $this->hint = $hint;
25 | $this->disabled = $disabled;
26 | $this->readonly = $readonly;
27 | }
28 |
29 | public function render()
30 | {
31 | return view('crudify::textarea');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Http/Datatable.php:
--------------------------------------------------------------------------------
1 | data = $data;
18 | }
19 |
20 | public static function make($data)
21 | {
22 | return new static($data);
23 | }
24 |
25 | protected function columns()
26 | {
27 | return [
28 | Column::make('id'),
29 | Column::make('name'),
30 | Column::make('created_at'),
31 | Column::make('updated_at'),
32 | ];
33 | }
34 |
35 | private function columnsArray()
36 | {
37 | $array = [];
38 |
39 | foreach ($this->columns() as $column) {
40 | $array[] = !is_array($column) ? $column->toArray() : $column;
41 | }
42 |
43 | return $array;
44 | }
45 |
46 | /** @noinspection PhpUnhandledExceptionInspection */
47 | public function html()
48 | {
49 | $html = DataTables::getHtmlBuilder();
50 | $html->setTableId(class_basename($this));
51 | $html->columns($this->columnsArray());
52 | $html->orderBy($this->orderByKey(), $this->orderBy()[1] ?? 'asc');
53 |
54 | if ($this->actions(null)) {
55 | $html->addAction(['title' => '']);
56 | }
57 |
58 | $this->htmlMethods($html);
59 |
60 | return compact('html');
61 | }
62 |
63 | protected function htmlMethods(Builder &$html)
64 | {
65 | //
66 | }
67 |
68 | protected function orderBy()
69 | {
70 | return $this->order_by;
71 | }
72 |
73 | private function orderByKey()
74 | {
75 | $order_by = $this->orderBy();
76 |
77 | foreach ($this->columnsArray() as $key => $column) {
78 | if ($column['data'] == $order_by[0] ?? $order_by) {
79 | return $key;
80 | }
81 | }
82 |
83 | return 0;
84 | }
85 |
86 | /** @noinspection PhpUnhandledExceptionInspection */
87 | public function json()
88 | {
89 | $datatables = DataTables::of($this->data);
90 |
91 | if ($this->actions(null)) {
92 | $datatables->editColumn('action', function ($model) {
93 | return $this->actions($model);
94 | });
95 | }
96 |
97 | $this->jsonMethods($datatables);
98 |
99 | return $datatables->toJson();
100 | }
101 |
102 | protected function jsonMethods(DataTableAbstract &$datatables)
103 | {
104 | //
105 | }
106 |
107 | protected function actions($model)
108 | {
109 | return null;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Providers/CrudifyServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->runningInConsole()) {
24 | $this->commands([
25 | InstallsCrudify::class,
26 | GeneratesCrud::class,
27 | ]);
28 | }
29 |
30 | $this->loadViewsFrom(__DIR__ . '/../../resources/views', 'crudify');
31 | $this->publishes([__DIR__ . '/../../resources/views' => resource_path('views/vendor/crudify')], 'views');
32 |
33 | $this->loadViewComponentsAs('crudify', [
34 | Checkbox::class,
35 | Checkboxes::class,
36 | File::class,
37 | Input::class,
38 | Radios::class,
39 | Select::class,
40 | Textarea::class,
41 | ]);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Seeders/AdminUserSeeder.php:
--------------------------------------------------------------------------------
1 | create([
13 | 'name' => 'Admin',
14 | 'email' => 'admin@example.com',
15 | ]);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Traits/FillsColumns.php:
--------------------------------------------------------------------------------
1 | getTable());
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Traits/FormatsOptions.php:
--------------------------------------------------------------------------------
1 | format('Y-m-d H:i:s');
12 | }
13 | }
14 |
--------------------------------------------------------------------------------