Coder Social home page Coder Social logo

coryrose1 / livewire-tables Goto Github PK

View Code? Open in Web Editor NEW
87.0 2.0 19.0 67 KB

An extension for Laravel Livewire that allows you to effortlessly scaffold datatables with optional pagination, search, and sort.

License: Other

PHP 100.00%
laravel livewire datatables tables livewire-tables

livewire-tables's Introduction

Livewire-Tables

Latest Version on Packagist Total Downloads Build Status StyleCI

An extension for Livewire that allows you to effortlessly scaffold datatables with optional pagination, search, and sort.

Live demo website will be available soon.

Installation

Via Composer

$ composer require coryrose/livewire-tables

The package will automatically register its service provider.

To publish the configuration file to config/livewire-tables.php run:

php artisan vendor:publish --provider="Coryrose\LivewireTables\LivewireTablesServiceProvider"

Usage

Livewire tables are created in three simple steps:

  1. Create a table component class
  2. Configure the table class using the available options
  3. Scaffold the table view (as needed when component class changes)

Create a table component class

Run the make command to generate a table class:

php artisan livewire-tables:make UsersTable

App/Http/Livewire/Tables/UsersTable.php

...

class UsersTable extends LivewireModelTable
{
    use WithPagination;
    
        public $paginate = true;
        public $pagination = 10;
        public $hasSearch = true;
    
        public $fields = [
            [
                'title' => 'ID',
                'name' => 'id',
                'header_class' => '',
                'cell_class' => '',
                'sortable' => true,
            ],
            [
                'title' => 'Name',
                'name' => 'name',
                'header_class' => '',
                'cell_class' => '',
                'sortable' => true,
                'searchable' => true,
            ],
            [
                'title' => 'City',
                'name' => 'address.city',
                'header_class' => 'bolded',
                'cell_class' => 'bolded bg-green',
                'sortable' => true,
                'searchable' => true,
            ],
            [
                'title' => 'Post',
                'name' => 'post.content',
                'header_class' => '',
                'cell_class' => '',
                'sortable' => true,
                'searchable' => true,
            ],
        ];
    
        public function render()
        {
            return view('livewire.tables.users-table', [
                'rowData' => $this->query(),
            ]);
        }
    
        public function model()
        {
            return User::class;
        }
    
        public function with()
        {
            return ['address', 'post'];
        }
}

Configure the component options

First, set your base model in the model() method in the following format:

public function model()
{
    return User::class;
}

To eager load relationships, use the with() and return an array of relation(s):

public function with()
{
    return ['address', 'post'];
}

The following are editable public properties for the table class:

key description value default
$paginate Controls whether the data query & results are paginated. If true, the class must use WithPagination; bool true
$pagination The number value to paginate with integer 10
$hasSearch Controls global appearance of search bar bool true
$fields The fields configuration for your table array null
$css Per-table CSS settings array null

$fields

Controls the field configuration for your table

key description value
title Set the displayed column title string
name Should represent the database field name. Use '.' notation for related columns, such as user.address string
header_class Set a class for the <th> tag for this field string or null
cell_class Set a class for the <td> tag for this field string or null
sortable Control whether or not the column is sortable bool or null
searchable Control whether or not the column is searchable bool or null

$css

Used to generate CSS classes when scaffolding the table.

These can be set globally in the configuration file, or on a per-table basis in the component class.

Note: CSS classes set in the component will override those from the configuration file where both exist.

key description value
wrapper CSS class for <div> surrounding table string or null
table CSS class for <table> string or null
thead CSS class for <thead> string or null
th CSS class for <th> string or null
tbody CSS class for <tbody> string or null
tr CSS class for <tr> string or null
td CSS class for <td> string or null
search_wrapper CSS class for <div> surrounding search string or null
search_input CSS class for search <input> string or null
pagination_wrapper CSS class for <div> surrounding pagination buttons string or null

Scaffold the table view

When ready, scaffold the table view using the scaffold command:

php artisan livewire-tables:scaffold UsersTable

resources/views/livewire/tables/users-table.blade.php

<div>
    @if ($hasSearch)
    <div>
        <div style="position: relative; display: inline-block;">
        <input type="text" wire:model="search" />
            @if ($search)
                <button wire:click="clearSearch" style="position: absolute; right: 5px;">&#10005;</button>
            @endif
        </div>
    </div>
    @endif
    <table class="table-wrapper">
        <thead>
        <tr>
            <th class="header" wire:click="$emit('sortColumn', 0)">ID</th>
            <th class="header" wire:click="$emit('sortColumn', 1)">Name</th>
            <th class="header bolded" wire:click="$emit('sortColumn', 2)">City</th>
            <th class="header" wire:click="$emit('sortColumn', 3)">Post</th>
        </tr>
        </thead>
        <tbody>
        @foreach ($rowData as $row)
            <tr>
                <td class="table-cell">{{ $row->id }}</td>
                <td class="table-cell">{{ $row->name }}</td>
                <td class="table-cell bolded bg-green">{{ $row->address->city }}</td>
                <td class="table-cell">{{ $row->post->content }}</td>
            </tr>
        @endforeach
        </tbody>
    </table>
    @if ($paginate)
    <div>
        {{ $rowData->links() }}
    </div>
    @endif
</div>

You can use the scaffold command continuously as you make changes to the parent component class.

Since the rendered template is simple HTML, there’s no need for table “slots” for customization - customize the template as you see fit!

Todo

  • Further support for more advanced queries than a model

Change log

Please see the changelog for more information on what has changed recently.

Contributing

Please see contributing.md for details and a todolist.

Credits

License

MIT. Please see the license file for more information.

livewire-tables's People

Contributors

coryrose1 avatar crosenwald avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

livewire-tables's Issues

Issue/Conflict with Livewire\HasPagination trait

Something in the table component is overriding the WithPagination trait provided by Livewire.

The pagination view being loaded is actually the default Laravel / Bootstrap view. This results in full-page reloads / going to ?page=[number] in the URL. It also ceases to work after any Livewire method is run (i.e. sort or search then try pagination and it will result in an error).

There is an initializeWithPagination() method in the trait that does the following:

public function initializeWithPagination()
    {
        Paginator::currentPageResolver(function () {
            return $this->paginator['page'];
        });

        Paginator::defaultView('livewire::pagination-links');
    }

Pagination::defaultView() is not taking effect unless placed directly into AppServiceProvider.

However even when set, the Livewire pagination is not functioning correctly.

Can anyone identify why the pagination trait should be affected, by either LivewireModelTable component or the user-generated table component? If anyone can help me dig into this, I'd appreciate it.

Detecting sort in the column header

Currently the <th> header cells do not detect when / if they are sorted.

I think I'll end up having to extract them into a component where we can pass different states, such as a base class (passing in from config / table $css array), and ascending class, and a descending class.

$fields could be modified to support a ascending_class and descending_class strings.

It's going to get pretty dirty..

@if ($sortField == 'id')
  @if ($sortDir == 'asc')
    <th class="base-class ascending-class" wire:click="$emit('sortColumn', 0)">ID</th>
  @elseif ($sortDir == 'desc')
    <th class="base-class descending-class" wire:click="$emit('sortColumn', 0)">ID</th>
  @endif
@else
  <th class="base-class" wire:click="$emit('sortColumn', 0)">ID</th>
@endif

Class not found on composer install

First time using.

run the command: composer require coryrose/livewire-tables

And all looks good until I get the following error:


Package operations: 1 install, 0 updates, 3 removals
  Removing predis/predis (v1.1.1)
  Removing laravel/horizon (v3.2.7)
  Removing cakephp/chronos (1.2.8)
  Installing coryrose/livewire-tables (0.3.0): Downloading (100%)
Package moontoast/math is abandoned, you should avoid using it. Use brick/math instead.
Writing lock file
Generating optimized autoload files


> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
PHP Fatal error:  Class 'Livewire\Commands\FileManipulationCommand' not found in /Users/user/Documents/Laravel/domain/vendor/coryrose/livewire-tables/src/Commands/MakeLivewireTableCommand.php on line 11

Any ideas? Thanks

Suggestions/recommendations

Here are some suggestions/recommendations.

1. Use a baseQuery instead

Right now you specify a model method, but it makes more sense to use a base query. This would eliminate the need for the with method as well.

For example, instead of this:

public function model()
{
    return User::class;
}

public function with()
{
    return ['address', 'post'];
}

Have something like this:

public function baseQuery()
{
    return User::with(['address', 'post'])->withCount('posts');
}

Notice how I've added withCount to this. Doing it this way makes withCount possible without having to implement a bunch of other methods. Then we would be able to use posts_count in the $fields name.

2. Make field declaration expressive

Right now, you dump the fields into an array. Sure, this works, but it is confusing and makes future updates a nightmare. I propose you do field declaration similar to Laravel Nova. Then developers know exactly what they can do with fields, and makes it nicer to work with.

For example, instead of this:

public $fields = [
    [
        'title' => 'ID',
        'name' => 'id',
        'header_class' => '',
        'cell_class' => '',
        'sortable' => true,
    ],
    [
        'title' => 'City',
        'name' => 'address.city',
        'header_class' => 'bolded',
        'cell_class' => 'bolded bg-green',
        'sortable' => true,
        'searchable' => true,
    ],
];

Have something like this:

public function fields()
{
    return [
        Field::make('ID', 'id')->sortable();
        Field::make('City', 'address.city')->headerClass('bolded')->cellClass('bolded bg-green')->sortable()->searchable();
        Field::make('Posts Count', 'posts_count')->cellClass('text-muted');
    ];
}

Things like searchable/sortable can default to false, and using this method would convert said values to true.

3. Use a single view for all tables

You can use a single view for ALL tables. This would eliminate the need for devs to scaffold their own view files, which is tedious. Since the package dynamically declares stuff anyways, there shouldn't be a need for this. The package can simply publish 1 view file to the views/vendor folder, and the stub used by livewire-tables:make can contain this view by default. Then developers can easily make changes to that one view file if they want, and it would apply to all tables.

In this view, just use @foreach loops to iterate the fields and output the headings, etc..

For example:

@if($hasSearch)
    {{-- show search input etc. --}}
@endif

<table class="table-wrapper">
    <thead>
    <tr>
        @foreach($fields as $field)
            <th class="header {{ $field['headerClass'] }}" wire:click="$emit('sortColumn', {{ $loop->index() }})">{{ $field['title'] }}</th>
        @endforeach
    </tr>
    </thead>
    <tbody>
    @foreach ($rowData as $row)
        <tr>
            @foreach($fields as $field)
                <td class="table-cell {{ $field['cellClass'] }}">
                    @if($field['view'])
                        {{-- field uses a custom view file for the TD :) --}}
                        @include($field['view'])
                    @elseif(strpos($field['name'], '.') !== false)
                        {{-- explode by period or w/e for relationship logic --}}
                        ...
                    @else
                        {{ $row->{$field['name']} }}
                    @endif
                </td>
            @endforeach
        </tr>
    @endforeach
    </tbody>
</table>

@if($paginate)
    {{ $rowData->links() }}
@endif

Notice how I've included a conditional for each TD. If we added a new view() method to the field delcaration, devs could use their own custom view file for the TD, and it would automatically be passed the $row var.

For relationships, we can just check if the name has a period. If it does, explode it into the actual value or w/e. See this: https://stackoverflow.com/questions/33645301/how-do-i-traverse-through-model-relationships-in-laravel-with-dot-syntax

Anyways, these are just a few ideas. Would love to hear your thoughts on these.

hasMany relationship, count/sum/max/min fields

Currently only belongsTo and hasOne work out of the box.

The query builder can be modified to support hasMany relationships and return a count, sum, min, or max column/field.

The challenge is building this into the existing query builder.

Here is an example of how the end query might be built:

$query->select('users.id', 'name', 'addresses.city', DB::raw('COUNT(posts.id) as posts_count'))
            ->leftJoin('addresses', 'users.id', '=', 'addresses.user_id')
            ->leftJoin('posts', 'users.id', '=', 'posts.user_id')
            ->groupBy('users.id', 'addresses.city')
            ->paginate(15);

Effectively, the DB::raw call must be built into the select, and the groupBy() clause must be added.

One unfortunate thing I ran into is MySQL is in STRICT mode by default and thus every other "left join" relationship field must be included into the groupBy clause.

We might allow someone to specify these fields like so:

 public $fields = [
        [
            'title' => 'ID',
            'name' => 'id',
            'header_class' => '',
            'cell_class' => '',
            'sortable' => true,
            'searchable' => true,
        ],
        ...
        [
            'title' => 'Post Count',
            'name' => 'posts.count',
            'header_class' => '',
            'cell_class' => '',
            'sortable' => true,
            'searchable' => true,
        ],
    ];

where .count, .sum, .max, .min etc are reserved keywords that the query builder will parse and understand how to build the query.

Always supplying primary key to table view

Should the primary key of the base model always be passed in $rowData?

What if someone wants to remove the ID column, but still needs the ID for use in links or otherwise?

Similarly, relationships primary key / id. In our demo app, the User hasOne Address. For the purposes of the table, we only pull the 'address.city' field. What if we want to link to that address by primary key, but don't want the primary key of the address as a table cell?

Nested table support

hey,

i think this would be much better is we could have nested tables / row grouping options that would enable the user to present data in different way. e.g if the model has relationships i would want to pass the relationships into a nested table under each raw.

Nested tables are always great way to present huge chunk of related data.

Is this something that could be pulled in!

Thanks

Cannot install by composer

I got this error when installing with composer

Using version ^0.3.0 for coryrose/livewire-tables
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for coryrose/livewire-tables ^0.3.0 -> satisfiable by coryrose/livewire-tables[0.3.0].
    - Conclusion: remove laravel/framework v7.4.0
    - Conclusion: don't install laravel/framework v7.4.0
    - coryrose/livewire-tables 0.3.0 requires illuminate/support ~5|~6 -> satisfiable by illuminate/support[5.0.x-dev, 5.1.x-dev, 5.2.x-dev, 5.3.x-dev, 5.4.x-dev, 5.5.x-dev, 5.6.x-dev, 5.7.17, 5.7.18, 5.7.19, 5.7.x-dev, 5.8.x-dev, 6.x-dev, v5.0.0, v5.0.22, v5.0.25, v5.0.26, v5.0.28, v5.0.33, v5.0.4, v5.1.1, v5.1.13, v5.1.16, v5.1.2, v5.1.20, v5.1.22, v5.1.25, v5.1.28, v5.1.30, v5.1.31, v5.1.41, v5.1.6, v5.1.8, 
v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7, v5.3.0, v5.3.16, v5.3.23, v5.3.4, v5.4.0, v5.4.13, v5.4.17, v5.4.19, v5.4.27, v5.4.36, v5.4.9, v5.5.0, v5.5.16, v5.5.17, v5.5.2, v5.5.28, v5.5.33, v5.5.34, v5.5.35, v5.5.36, v5.5.37, v5.5.39, v5.5.40, v5.5.41, v5.5.43, v5.5.44, v5.6.0, v5.6.1, v5.6.10, v5.6.11, v5.6.12, v5.6.13, v5.6.14, v5.6.15, v5.6.16, v5.6.17, v5.6.19, v5.6.2, v5.6.20, v5.6.21, v5.6.22, v5.6.23, v5.6.24, v5.6.25, v5.6.26, v5.6.27, v5.6.28, v5.6.29, v5.6.3, v5.6.30, v5.6.31, v5.6.32, v5.6.33, v5.6.34, v5.6.35, v5.6.36, v5.6.37, v5.6.38, v5.6.39, v5.6.4, v5.6.5, v5.6.6, v5.6.7, v5.6.8, v5.6.9, v5.7.0, v5.7.1, v5.7.10, v5.7.11, v5.7.15, v5.7.2, v5.7.20, v5.7.21, v5.7.22, v5.7.23, v5.7.26, v5.7.27, v5.7.28, v5.7.3, v5.7.4, v5.7.5, v5.7.6, v5.7.7, v5.7.8, v5.7.9, v5.8.0, v5.8.11, v5.8.12, v5.8.14, v5.8.15, v5.8.17, v5.8.18, v5.8.19, v5.8.2, v5.8.20, v5.8.22, v5.8.24, v5.8.27, v5.8.28, v5.8.29, v5.8.3, v5.8.30, v5.8.31, v5.8.32, v5.8.33, v5.8.34, v5.8.35, v5.8.36, v5.8.4, v5.8.8, v5.8.9, v6.0.0, v6.0.1, v6.0.2, v6.0.3, v6.0.4, v6.1.0, v6.10.0, v6.11.0, v6.12.0, v6.13.0, v6.13.1, v6.14.0, v6.15.0, v6.15.1, v6.16.0, v6.17.0, v6.17.1, v6.18.0, v6.18.1, v6.18.2, v6.18.3, v6.2.0, v6.3.0, v6.4.1, v6.5.0, v6.5.1, v6.5.2, v6.6.0, v6.6.1, v6.6.2, v6.7.0, v6.8.0].

Please help me, i need this amazing package, thanks in advanced

Feature Idea: Filters

  • Say you have a column with colors
  • You want to have a dropdown with available colors and filter on the selected
  • Or you have a created_at column and want to filter on timeframe (this year, this month, today)
  • These filters should work in combination with search, so you could find all records that are blue and from today

Action links and buttons

Starting a discussion on handling action links and buttons within the table.

There are essentially two ways to handle this... by letting the user modify the HTML and add a column header / button themselves, or by allowing the button/link to be built via the scaffold command.

There are many challenges for building this into the scaffolding command.

1. Do we extend $fields, or add an $actions array?

Will "actions" ever need to modify the base query?

Do we need to comb through $fields for actions vs data cells, or if we use an $actions array, how do we determine the order of columns for the overall table?

2. How is the action link href built

Assuming this will comprise of some primary key, or field value that needs to already be present in $rowData. What happens if there are variables (even multiple) in the link?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.