jdw5/vanguard

Extend filters, actions and table data for your Laravel applications.

v0.3.5 2024-04-25 23:33 UTC

This package is auto-updated.

Last update: 2024-11-09 22:23:01 UTC


README

Vanguard is a fullstack datatable and search query builder package for Laravel, with a companion Vue frontend composable using the InertiaJS HTTP protocol specification. It provides an elegant API to define tables, columns, actions and refiners for your data.

Table of Contents

Installation

Install the package via composer.

composer require jdw5/vanguard

There are no configuration files needed. You may want to publish the stub and command to customise the default table generated.

php artisan vendor:publish --tag=vanguard-stubs

Frontend Companion

There is a Vue-Inertia client library available via npm that provides a composable around the refiners and table data. View the documentation for that here.

npm install vanguard-client

And use as

import { useTable } from 'vanguard-client'

defineProps({
    propName: Object
})

const table = useTable('propName')

It comes with in-built query string parsing and data refreshing, bulk selection of table items and generating functions in Javascript for the table.

See the repository for more information, and API documentation.

Anatomy

The core functionality is the ability to define your tables and perform filtering automatically. The refinements and actions can also be used separately, without the table, but the anatomy will focus on the Table class.

When using the make:table command, the following boilerplate will be generated:


You must define the model, table attributes or complete the defineQuery method to tell the table what data to fetch. You can also exclude this, and pass in a Builder instance to the UserTable::make(Model::query()) method.

The table is expected a unique column, or identifier for each element. This is particularly useful for generating modals: the based/momentum-modal is recommended for providing modal endpoints. The key can be defined as an attribute on the model, or you can chain asKey() onto a column when defining.

The table will default to performing a get() when retrieving records if not collection method is provided. These can be provided when creating the class as such:

$table = UserTable::make()->paginate(10);

Or, the recommended method is to complete the public function paginate() method. This can return either a single integer or an array of integers. If an array is provided, you have opted in for dynamic pagination and a show property will be generated on the table to the frontend. This allows for users to change the number of records per page.

defineColumns returns an array where you can specify the columns to display on the frontend. The selected data from the query will be reduced such that only the column properties here are passed to the client. It provides functionality to define the visibility, breakpoints, transform data and more on each column.

defineRefinements returns an array of Filter and Sort child classes to define the refinements available to the user. The documentation contains a complete specification of the provided APIs for this.

defineActions returns an array where you can specify the actions to perform on the data. There are three actions types available PageAction, InlineAction and BulkAction to use to separate them.

Core

We provide a complete API documentation for the classes and traits provided by Vanguard, including the relevant namespacing. Abstract classes are not included in the documentation, but are available in the source code.

Table

Tables can be generated from the command line using php artisan make:table.

Defining the Query

The table requires a query to fetch data from. This can be supplied in a number of ways, listed below in order of precendence:

  • Passed as the only argument to Table::make($argument)
  • Overriding the method protected function defineQuery() on your table class, returing a Builder|QueryBuilder instance
  • Setting the property protected $model to be the model class to fetch data from
  • Setting the property protected $getModelClassesUsing to be a function to resolve the model class
  • It will attempt to resolve the model class from the table name if none of the above are provided

DEPRECATED To modify the query between the query you provide and the data being fetched, you can add another method protected function beforeFetch(Builder|QueryBuilder $query) to apply any additional constraints or modifications, most notably for changing sort orders.

You should also define a unique key for each row in the table. This can be done by setting the property protected $key to the column name, or by chaining the method asKey() onto a column when defining it.

Defining columns

Columns define what data is passed to the frontend, and can be used to mutate the data before it is passed among many other things. Columns are defined using the protected function defineColumns(): array method, which should return an array of Column instances. See the Column documentation for the API.

There is an additional property protected $applyColumns which can be set to false to disable the application of columns to the query. This is useful when you don't want to reduce the data, or are happy with the default columns. This, by default, is set to true.

Defining refiners

Vanguard provides a macro on both the Query\Builder and Eloquent\Builder's to apply the Refinement class to a given query. This is used in the pipeline for generating the table, allowing you to fluently define refinements for the table.

To add refinements to your table, you can define the protected function defineRefinements(): array method, which should return an array of Refinement instances. See the Refinement documentation for the API.

Defining pagination and meta

Vanguard provides a nearly identical API to the default fetching methods provided by Laravel's query builder. You can define the fetching method when making the table, defining the attribute or defining the method. The supported methods are: get, paginate, cursorPaginate and dynamicPaginate. In order of precendence:

  • Setting the method when making Table::make()->paginate()
  • Setting protected $paginateType to get, paginate, cursor or dynamic
  • Overriding the method protected function paginate(): int|array to return the number of records per page

The chaining method provides an identical API to Laravel's in-built paginate mechanisms. If you don't do this at the make level, you can override the relevant attributes on the table. These are protected $pageName, protected $page, protected $columns, and then the protected $paginateType.

There is a dynamicPaginate option available. This allows for users to change the number of records per page. This is done safely, and they cannot arbitrarily change the number of records per page - it must be one of the provided options. To enable this, you must return an array of integers from the protected function definePaginate() method, where each integer is a valid number of records per page. Alternatively, you can manually set the perPage attribute as an array and change the paginateType to dynamic - but this is not recommended.

The default number of records is set to 10, but can be overriden by changing the attribute protected $defaultPerPage. It is recommended that the array provided as page number options contains the default number of records you set. You can also override the query parameter term by changing the attribute protected $showKey. By default, the term is show.

Defining actions

Actions are defined using the protected defineActions(): array method, which should return an array of Action instances. See the Action documentation for the API. These are then grouped by their type for access on your frontend.

Defining preferences

Preferences are a way to dynamically change the data that is sent to the frontend based on a user changing the selection. This behaviour is not enabled by default. It will add a paging_options property to the table data object, containing columns to be used as preferences.

To enable preferences, you must define the the key to be used for the preferences in the search query. This can be done by setting the property protected $preferencesKey to the name of your choice (cols is a common name), or by overriding the method protected definePreferenceKey(): string to return the key.

The columns you have defined can then have the preference() method chained onto them to enable them as preferences. If you have columns applied, this will then prevent any data not in those preferences from being sent to the frontend. However, the query must select the necessary columns for all possible preferences - as the preferencing is done at the server level, not database.

Additionally, Vanguard provides functionality to store a user's preferences for a given table through a cookie. To enable this, the cookie name must be defined in the table class. This can be done by setting the property protected $preferenceCookie to the name of your choice, or by overriding the method protected definePreferenceCookie(): string to return the name. It is critical that this key is unique amongst all your tables, and cookies, to prevent conflicts.

Actions

Actions allow for you to define a group of actions, not part of a table. To define an Actions class, you can create it inline with the Actions::make(...BaseAction $actions) method. The arguments must be InlineAction, PageAction or BulkAction instances. The actions are then grouped by their type for access on your frontend.

Refiners

Refiners are a way to define search parameters, and apply them to a query using a fluent API. Refiners are then grouped by their type for access on your frontend. You can group them, not part of a table, using the Refiners class. Generate it in-line using the Refiners::make(...Refinement $refiners) method. The arguments must be Refinement instances.

Component Documentation

Columns

Columns are used exclusively in the Table class to define the data that is passed to the frontend. They can be used to mutate the data before it is passed, define the visibility, breakpoints and more. Columns are generated using Column::make($name). The $name parameter should be the name of the column in the database if you are applying columns. The remaining properties are autogenerated, or require chaining using the following methods:

Actions

Classes which extend BaseAction are used to define simple methods users can perform on a table. To generate an action, InlineAction::make(string $name). The name is used as the key for the action in it's group or table. The remaining properties are autogenerated, or require chaining using the following methods:

Inline Action

Inline action has an additional property for default, often used to denote the default action to be performed when a row is clicked.

Page Action

Currently, only page actions have access to the endpoint API as these pages do not generally require data to be passed and can be built with only server data.

Refinements

Refinements are a way to define search parameters, and apply them to a query using a fluent API. The constructor for all refinements is slightly more complex:

Refinement::make(string $property, ?string $name)

The property field is required, and denotes the specific database column. The name field is optional, and is used to display the refinement on the frontend. If no name is provided, the property is used as the name. The constructor also generates a label, which fallbacks to the name then property but is often overriden. The remaining API for the refinement is provided below:

There are some specific refiners created for you, which should cover almost every use case in reality. These are formed from bases:

Sorts

The BaseSort API adds a direction to the refinement to accomodate for the ordering of the data. The standard Sort option extends this base sort with no additional parameters.

The ToggleSort API is not available for extension, and so not provided. There are no chaining methods, as it is all handled internally.

Filters

Filters provide a way to filter the data based on a specific property. The BaseFilter API adds a value to the refinement to accomodate for the filtering of the data.

Filter

Filter extends the base filter, and allows for a mode and operator to be defined.

SelectFilter

Select filter allows for whereIn clauses to be applied, that is it allows for an array to be checked. It only has the operator methods available, see above.

QueryFilter

QueryFilter allows for a custom query to be applied to the filter. This is useful for complex queries, or queries that cannot be expressed using the standard operators.

Options

Options are available values a query term can take on. They can be generated in a number of ways because of this, detailed underneath the chain methods available. The standard way to create an option is to use the Option::make(string $value, ?string $label) method.

As mentioned, the standard way to create an option is to use the Option::make(string $value, ?string $label) method where the value of the option is a required parameter, and a label can be optionally passed. There are instances where you may want to create options from a source, these are also covered.

From a collection, such as performing a database query, Option::collection(Collection $collection, string\|callable $asValue, string\|callable $asLabel) can be used. The collection is the collection of data to be used, and the two callables are used to extract the value and label from the collection. This provides a way to map the data and create a value, label pair. The callable functions should accept an element of the collection and return the value or label respectively. The method defaults to indexing the associative array with value and label to retrieve the data.

From an array, such as a list of options, Option::array(array $array, string\|callable $asValue, string\|callable $asLabel) can be used. This uses the same API as the collection method, but for an array.

Finally, enums can be used to generate options using Option::enum(string $enum, string\|callable $asLabel). This will check for a BackedEnum at the given enum string, the value is taken as the enum value. The label is then generated from a function, with an instance of BackedEnum sent to it or using a method defined on the enum if a string is passed.

Testing

Before testing ensure that your environment or container has the PHP Sqlite adapter, on Linux:

  • sudo apt-get install php-sqlite3

Perform a composer install to retrieve the required packages. Tests can then be executed as:

vendor/bin/phpunit

Specify a testsuite as such:

vendor/bin/phpunit --testsuite=Feature