yanah/laravel-kwik-crud

A Laravel package designed to streamline and accelerate development. Let's make it 'kwik' (quick)

v1.5.0-beta.2 2025-01-24 06:46 UTC

README

Description

This package is built to ease the work developers do by streamlining the process of scaffolding CRUD operations. It integrates seamlessly with Laravel, Inertia, and Vue.js 3, reducing boilerplate and simplifying the creation of CRUD functionality.

Discord

To collaborate, please join here: https://discord.gg/UksAt4HqF9

Overview

  • Stack Used
  • Installation & Configurations
  • Run the application
  • Package Command/s
  • CRUD Implementation

I. CRUD (Create)
II. CRUD (LIST)
III. CRUD (EDIT/UPDATE)
IV. CRUD (SHOW)

  • Customize Pages (CRUD)

Insert Components before / after CRUD Pages (List, Edit, Create)
Customize Form fields (Create/Edit)

  • Additional Security
  • CRUD Lifecycle
  • Overriding CRUD controller method
  • Before & After Create (CRUD)
  • Before & After Update (CRUD)

Stack Used

Installation & Configurations

To install the package, follow these steps:

$ composer require yanah/laravel-kwik-crud
$ php artisan kwik:install
  • The default scafold is vuejs. We'll use reactjs soon.
  • kwik:install --client=reactjs, Install for reactjs (SOON)

Add KwikServiceProvider to the providers array in your config/app.php:

'providers' => [
    // Other providers...
    Yanah\LaravelKwik\KwikServiceProvider::class,
]

In app/Http/Kernel.php, ensure that the HandleInertiaRequests middleware is added under web middleware groups.

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\HandleInertiaRequests::class,
        // other middlewares...
    ],
];

Publish kwik configurations

$ php artisan vendor:publish --tag=kwikconfig

After this, new files will be added in config

Install Front-end dependencies:

$ npm install vue@latest @fortawesome/fontawesome-free primevue @primevue/themes primeicons @primevue/forms

In vite.config.js alias, add @kwik

resolve: {
    alias: {
        '@kwik': path.resolve(__dirname, 'vendor/yanah/laravel-kwik-crud/src/client/vuejs'), // use reactjs for react
    },
},

In tailwind.config.js alias, add this line.

content: [
    // More contents here
    'vendor/yanah/laravel-kwik-crud/src/client/vuejs/**/*.vue',
],

Run the application

Check tailwind.config.js configuration

Make sure to implement or use primevue in app.ts Frontend Configurations

$ npm run dev

$ php artisan serve

Package Commands

Execute CRUD automatically

$ php artisan kwik:crud {name} {--only=}

Check the flags below:

name - refers to your model name. It should be capitalized & in singular form.

--only=:

  • crudfiles - Only the ModelList.php, ModelCreate.php, ModelEdit.php will be generated
  • controller - Only the {Model}Controller will be generated
  • model - Only the Model will be generate

Example:

$ php artisan kwik:crud Post --only=crudfiles

I. CRUD (Create)

Populate form with fields inside prepareCreateForm():

Example:

$this->formgroup->addGroup('users', [
    'tab' => true,
    'label' => 'Users',
]);

$this->formgroup->addField('first_name', [
    'label' => 'Samuel',
    'type' => 'text'
]);
In creating of a form, configure `Crud\{Model}Create.php`

First, in prepareCreateForm(), Add group

Use the following syntax to add a group:

$this->formgroup->addGroup('GROUP_NAME_UNIQUE', [
    'tab' => boolean,
    'label' => string,
]);

Second, Add field. Here is the syntax:

$this->formgroup->addField('FIELD_NAME', $attributes);

Note: The type attribute serves as the key to determine what input type we'll implement.

$attributes (Properties)

Types:

  • text
  • input Group
  • textarea
  • switch
  • checkbox
  • radio
  • select
  • select_group
  • calendar: date & time
  • autocomplete
  • custom_file
  • custom_html
**Text** $attributes example:
[
    'label' => 'Post Title',
    'type' => 'text'
]

**Input Group** $attributes example:
[
    'type' => 'input_group',
    'label' => 'Address',
    'placeholder' => 'Type your address',
    'group_icon' => 'pi pi-address-book' 
]

- group_icon is referred here: https://primevue.org/icons/

**Textarea** $attributes example:
[
    'label' => 'Post Body',
    'type' => 'textarea',
    'rows' => 4
]
**Switch** $attributes example:
[
    'label' => 'Billing details',
    'type' => 'switch'
]

**Checkbox**  $attributes example:
[
    'type' => 'checkbox',
    'is_boolean' => true, // if you need to return the checkbox as boolean, else string.
    'value' => old('CHECKBOX_ITEM', false),
    'label' => 'Your label',
    'class_item' => 'mb-5'
]

**Radio** $attributes example:
[
    'label' => 'Business Options',
    'type' => 'radio',
    'options' => [
        ['label' => 'Option 1', 'value' => 'option1'],
        ['label' => 'Option 2', 'value' => 'option2'],
        ['label' => 'Option 3', 'value' => 'option3'],
    ]
]


**Select** $attributes example:
[
    'label' => 'Business category',
    'type' => 'select',
    'options' => [
        ['label' => 'Option 1', 'optionValue' => 'option1'], // note: optionValue should be string
        ['label' => 'Option 2', 'optionValue' => 'option2'],
        ['label' => 'Option 3', 'optionValue' => 'option3'],
    ]
]

**Select Group** $attributes example:
[
    'type' => 'select_group',
    'label' => 'Service Group',
    'placeholder' => 'List of Services',
    'required' => true,
    'options' => [
        [
            'label' => 'First group',
            'items' => [
                [ 'label' => 'First1', 'optionValue' => 'first1'],  // note: optionValue should be string
                [ 'label' => 'First2', 'optionValue' => 'first2'],
                [ 'label' => 'First3', 'optionValue' => 'first3'],
            ],
        ],
    ]
];

**Calendar** $attributes example:
[
    'label' => 'business Calendar',
    'type' => 'calendar'
]

To set time only, configure inputProps:
[
    'type' => 'calendar',
    'label' => 'Time only',
    'inputProps' => [
        'timeOnly' => true,
        'hourFormat' => '12'
    ]
]

See more attributes here: https://primevue.org/datepicker/#time

**Custom html**
- If you wan to add a custom html:
[
    'type' => 'custom_html',
    'value' => function() {
        return '<div class="text-3xl border-t pt-4 mb-5">Read here.</div>';
    }
]

Autocomplete input

Example:

[
    'type'    => 'autocomplete',
    'required' => true,
    'label'   => 'Search me',
    'default_query_results' => [
        ['label' => 'test', 'value' => 'this'],
        ['label' => 'test2', 'value' => 'this2'],
    ],
    'api_endpoint' => '/post/search'
]
Property Type Description
default_query_results array Automatically populates values to be searched if an API fetch is not required.
api_endpoint string | null Defines the API endpoint. You should have a /post/search route to handle the request and then the response must match the data structure of default_query_results.

Creating custom vue file

Add custom vue file into the field.

[
    'type'  => 'custom_file', 
    'source' => '@/Components/CustomVueFile.vue' // This is relative to resources/js/Components directory
]
  • source - All fileds should be saved inside Components folder.

Props:

attributes - All of the array values above will serve as attributes.

Emit

@updateFieldValue - This will update the value and be passed as payload.

Example:

<template>
    <label>Input Something</label>
    <input
        @input="updateInput"
        :class="`border-gray-300 shadow-sm focus:ring-primary-default focus:border-primary-default p-2 w-full`"
    />
</template>
<script setup>
const props = defineProps({
    attributes: Object,
    fieldName: String
})
const emit = defineEmits(['updateFieldValue']);
function updateInput(event) {
    emit('updateFieldValue', props.fieldName, event.target.value);
}
</script>

More Attributes:

[
    'helper_text' => 'Sample text',
    
    'value' => 'ANY', // we attached this for every field for edit page purposes.

    'wrapperProps' => [
        // We may add bind to the wrapper of label & input
        // example: 'class' => 'bg-danger flex-row'
    ],

    'labelProps' => [
        // Cutomize or add styles on label
        // example: 'class' => 'text-sm'
    ],

    'inputProps' => [
        // Cutomize or add styles on Input Fields
        // example: 'class' => 'bg-danger w-full'
    ],

    'tooltip_label' => 'You may want to add tooltip for label. Icon is question mark.'
]

(To customize fields proceed to the bottom.)

*** Validations ***

Validations are defined in $validationRules.

protected $validationRules = [
    // Add validations here.
];

II. CRUD (LIST)

CRUD List is configured in Crud\{Model}List.php

A. Two options how we display the table

First, Through Pagination.

Use BodyPaginatorInterface as interface, then add the responseBodyPaginator().
It should look like this:

class {Model}List implements ControlCrudInterface, BodyPaginatorInterface
{
    public function responseBodyPaginator(Builder $query) : LengthAwarePaginator
    {
        // you may customize the query here.
        return $query->paginate($this->perPage);
    }  
}

Second, We may want to display all response data.

Use BodyCollectionInterface as interface, then add the responseBodyCollection()
It should look like this:

class {Model}List implements ControlCrudInterface, BodyCollectionInterface
{
    public function responseBodyCollection(Builder $query) : Collection
    {
        return $query->get()->map(function($item) {
            $item->primary = $item->title;  // customized main text
            $item->secondary = $item->body; // description
            return $item;
        });
    }   
}

or, you may customize the row using html codes by adding rawHtml.

public function responseBodyCollection(Builder $query) : Collection
{
    return $query->get()->map(function($item) {
        $item->rawHtml = '<div class="text-xl">Custom html here</div>'; // Add this property
        return $item;
    });
}

B. Define View

We have two options of how our list should look like:

ListTemplateViewEnum::TABLELIST or ListTemplateViewEnum::LISTITEM

  • First, TABLELIST view contains pagination which requires BodyPaginatorInterface interface.
  • Second, LISTITEM view displays all list it is attached to BodyCollectionInterface.

C. Toggle Visibility

In your \App\CrudKwik\DIR\{Model}List.php file, insert toggleVisibility method

use Yanah\LaravelKwik\Crud\CrudListControl;

public function toggleVisibility(CrudListControl $control) : array
{
    $control->set('showSearch', true); // add more below

    return $control->get()->toArray();
}

To toggle action buttons:

$control->set('showSearchBar', false);
$control->updateAction('edit', true);
$control->updateAction('delete', true);

APIs:

  • showSearchBar: boolean - wrapper of add button, search and summary
  • showSearch: boolean - toggle display search input
  • showPrintPdf: boolean - toggle pdf print button (WIP)
  • showAddButton: boolean - toggle Add button
  • showListSummary: boolean - This shows the summary list of records in table list
  • actions: array - toggle action buttons (preview, edit, delet).

Available toggle controls:

'showSearchBar' => true,
'showSearch'    => true,
'showPrintPdf'  => false,
'showAddButton' => true,
'showListSummary' => true,
'actions' => [
    'preview' => true,
    'edit' => true,
    'delete' => true,
]

D. Handle Search functionality

Search functionality is visible only on pagination list and $control->set('showSearch', true);

public function search(Builder $query, string $q) : Builder
{
    return $query->where('FIELD', $q);
}

III. CRUD (EDIT/UPDATE)

We'll reuse the fields we defined in prepareCreateForm().

We update those in prepareEditForm().

$this->formgroup->editField('GROUP_NAME', 'FIELD_NAME', $attributes); // pattern

For $attributes, refer to $attributes (Properties) above.


Example:
$this->formgroup->editField('details', 'post_title', [
    'label' => 'Post Title (Edited)',
    'value' => old('post_title', $post->title)
]);

Or the details:

$this->formgroup->editDetails(string $groupName, array $details);

(To customize fields proceed to the bottom.)

*** Validations ***

Check method getValidationRules to modify validations added in {Model}Create.php

public function getValidationRules() {}

IV. CRUD (SHOW)

From your controller, customize the display based on the data.

Expected return:

[
    'data' => $data, // This will be shown on the table
    'except' => [] // list down the fields you don't want to display.
];

Customization:

You may add vue files before or after the table:

public function getShowItem(Builder $query, $fields = ['*'], $id)
{
    $this->setPageWrapperItems([
        'prepend' => '@/Components/SampleLorem.vue', // Before the table
        'append'  => '@/Components/SampleLorem.vue', // This will be shown after the table
    ]);
    // More codes below
}
<script setup>
const emit = defineEmits(['toggleShowTable']); // add this
</script>

Customize Pages (CRUD)

Insert Components before / after CRUD Pages (List, Edit, Create)

Implement PageAffixInterface in Crud\{Model}Create.php, Crud\{Model}Edit.php, Crud\{Model}List.php and define the components to be inserted (prepend / append).

use Yanah\LaravelKwik\App\Contracts\PageAffixInterface;

class {CrudClass} extends KwikForm implements PageAffixInterface
{
    public function defineAttributes(): array
    {
        return [
            'prepend' => '@/Components/YOUR_FILE_HERE.vue',
            'append'  => '@/Components/YOUR_FILE_HERE.vue'
        ];
    }
}

prepend & append have available api:

import { usePage } from '@inertiajs/vue3';
const { props: pageProps } = usePage();
console.log(pageProps.pageWrapper);

Example:

//@/Components/YOUR_FILE_HERE.vue
const emit = defineEmits(['updateCrudList']); // Make sure to define updateCrudList.

const ChangeListItems = () => {
  router.visit(`URL`, {
    method: 'get',
    preserveState: true, 
    replace: true, 
    onSuccess: (response) => {
      emit('updateCrudList', response.props.crud)
    },
  });
}

Customize Form fields (Create/Edit)

You may want to wrap fields.

Example:

$this->formgroup->beginWrap('INDEX_KEY', $atributes, $headings);

// Add Fields here

$this->formgroup->endWrap();

See $attributes below:

[
    'class' => 'gap-4 xs:grid-cols-1 sm:grid-cols-1', // cuztomize the cols number as desired
    'style' => 'background:red;'
]

See $headings (optional) below:

[
    'heading' => string,
    'paragraph' => string,
]

Additional Security

Notice that in show & edit pages, routes are accessible via id.

You may want to append uuid instead.

First, make sure to add uuid field in your model and migration.

Next, use the UuidRestrictionTrait trait in your controller.
Example:

use Yanah\LaravelKwik\Traits\UuidRestrictionTrait;

class {Your}Controller extends KwikController implements PageControlInterface
{
    use UuidRestrictionTrait; // Attach this
}

CRUD Persist Lifecycle

Store

  1. PageControl - Serves as middleware.
  2. beforeStore - prepare method before storing.
  3. Validations - handle validations.
  4. Insert Model - updateOrCreate or create
  5. afterStore - Handle . We may trigger an event after store.

Update

  1. PageControl - Serves as middleware.
  2. beforeUpdate - prepare method before storing.
  3. Validations - handle validations.
  4. Update model
  5. afterUpdate - Handle . We may trigger an event after store.

Overriding CRUD controller method

You may want to override Crud Controller methods and use Laravel CRUD methods:

class YourController extends KwikController  implements PageControlInterface
{
    public function create()
    {
        // do something here.

        return parent::create();
    }
}

Before & After Create (CRUD)

In some cases, you may want to execute something or trigger an event before and after creating of new record. To do this, you have to override beforeStore() and afterStore() methods.

In your \App\CrudKwik\DIR\{Model}Create.php file, insert these lines:

use Yanah\LaravelKwik\Services\CrudService;

public function beforeStore(CrudService $crudService) : void
{
    // Change to false if you want to insert validated payloads only.
    $crudService->setShouldIncludeFillable(true);

    $crudService->setIndexOfUpdateCreate([
        // You may want to use updateCreate.
        // Add here the index, example:
        'user_id' => 1 // this will updateCreate based on user_id
    ]); 
}

public function afterStore($response)
{
    // Add stuffs

    // response
    return response()->json(['success' => true], 201);
}

Before & After Update (CRUD)

In your \App\CrudKwik\DIR\{Model}Edit.php file, insert these lines:

use Yanah\LaravelKwik\Services\CrudService;

public function beforeUpdate(CrudService $crudService) : void
{
    $crudService->setShouldIncludeFillable(true);
    $crudService->setIndexOfUpdateCreate([
        // You may want to use updateCreate.
        // Add here the index
    ]); 
}

public function afterUpdate($id)
{
    // trigger event after update

    return response()->json(['success' => true], 201);
}

Todo list:

  • Add Test
  • Responsiveness
  • Validate frontend

That's all. Please feel free to send PR when you found a bug.

Hope this package will help you "kwik"en your development. Appreciated!