Introduction
Cartalyst's Data Grid package makes it easy for you to filter data sources. Data Grid shifts the focus from pagination to data filtration. Pass any data source through a data handler and the package will take care of the rest so you can use the filtered result set to create your applications.
We emphasize filtering first, sorting second, and lastly, paginating those results.
Features
- Easily filter large data sources
- Create JSON responses to use in your API
- Build paginated result sets
Installation
The best and easiest way to install the Data Grid package is with Composer.
Preparation
Open your composer.json
file and add the following to the require
array:
"cartalyst/data-grid": "1.0.*"
Add the following lines after the require
array on your composer.json
file:
"repositories": [
{
"type": "composer",
"url": "https://packages.cartalyst.com"
}
]
Note: Make sure your
composer.json
file is in a valid JSON format after applying the required changes.
You can use the JSONLint online tool to validate yourcomposer.json
file.
Install the dependencies
Run Composer to install or update the new requirement.
php composer install
or
php composer update
Now you are able to require the vendor/autoload.php
file to autoload the package.
Integration
Laravel 4
The Data Grid package has optional support for Laravel 4 and it comes bundled with a Service Provider and a Facade for easy integration.
After installing the package, open your Laravel config file located at app/config/app.php
and add the following lines.
In the $providers
array add the following service provider for this package.
'Cartalyst\DataGrid\DataGridServiceProvider',
In the $aliases
array add the following facade for this package.
'DataGrid' => 'Cartalyst\DataGrid\Facades\DataGrid',
Configuration
After installing, you can publish the package configuration file into your application by running the following command on your terminal:
php artisan config:publish cartalyst/data-grid
This will publish the config file to app/config/packages/cartalyst/data-grid/config.php
where you can modify the package configuration.
Collection Handlers
By default, the package will register two built-in data handlers with Laravel, the Cartalyst\DataGrid\DataHandlers\CollectionHandler
and the Cartalyst\DataGrid\DataHandlers\DatabaseHandler
.
With these two data handlers you can use the follow types of data.
CollectionHandler
- Illuminate Collection objects
- Arrays with data objects which could be:
- An array
- An object which is an instance of or extends the
stdClass
object - An object which implements the
Illuminate\Support\ArrayableInterface
interface
DatabaseHandler
- Queries
- Query results
- Eloquent Models & Relationships
Note: You can register more data handlers by publishing and editing the config file. Read more about publishing the config file here.
Creating a Data Grid object
Creating a Data Grid object in Laravel 4 can be done in the same way as you do in native PHP except that you make use of the DataGrid
alias.
$dataGrid = DataGrid::make($data, $columns);
Working With The Query Builder
Thanks to the default built-in database data handler, Cartalyst's Data Grid package can work with instances of many different database objects. One of them is Illuminate\Database\Query\Builder
. You can pass along an instance of this class as a data source for your Data Grid and the data handler will extract the data from the query and prepares it as a result set.
For instance, if you'd like to use the data from the users
table as a data source for your Data Grid.
$query = DB::table('users')->where('age', '>', 20);
$dataGrid = DataGrid::make($query, array(
'name',
'email',
'address',
));
This will create a Data Grid object with all of the users and the selected columns in the result set.
You can also pass along a query result set.
$users = DB::table('users')->get();
$dataGrid = DataGrid::make($users, array(
'name',
'email',
'address',
));
Working With Eloquent
The built-in database data handler also enables you to pass along Eloquent objects as a data source.
Eloquent Models
You can pass along an Eloquent model as a data source.
$user = new User;
$dataGrid = DataGrid::make($user, array(
'name',
'email',
'address',
));
This would retrieve all of the users and create a result set with them in the DataGrid object.
Eloquent Query Builder
You can also pass along an instance of the Eloquent query builder:
$query = with(new User)->newQuery();
$dataGrid = DataGrid::make($query, array(
'name',
'email',
'address',
));
Eloquent Results
Besides models and the query builder, the DatabaseHandler
data handler also accepts Eloquent results.
$users = User::where('age', '>', 20)->get();
$dataGrid = DataGrid::make($users, array(
'name',
'email',
'address',
));
Eloquent Relationships
Eloquent relationships are also supported. Don't forget to call the relationship method instead of the property.
$roles = User::find(1)->roles();
$dataGrid = DataGrid::make($roles, array(
'title',
'level',
'created_at',
));
Joining Tables
Because we can pass along database query objects, we can also join tables together and get a combined result set from multiple tables. If you have duplicate column names after joining the tables you can create aliases for them in the columns array.
$query = DB::query('cars')
->join('manufacturers', 'cars.manufacturer_id', '=', 'manufacturers.id')
->select('cars.*', 'manufacturers.name');
$dataGrid = DataGrid::make($query, array(
'manufacturers.name' => 'manufacturer_name',
'name' => 'car_name',
'year',
'price',
));
Using Data Grid With Routes
Because the Data Grid object will render the result set as a JSON response, you can use it to make API routes in your application.
Route::get('users', function()
{
$query = DB::table('users');
return DataGrid::make($query, array(
'name',
'email',
'address',
));
});
You can see at how a Data Grid result set looks like here.
Using Data Grid In Views
Besides outputting the Data Grid object as JSON responses to work with APIs, you can also use them to build tabular data views for your application. Let's look at an extensive example.
First we'll register the route.
Route::get('posts', function()
{
// Get all the posts from the database.
$posts = Post::all();
// Create a data grid object to list all posts
// with their id, title and creation date.
$dataGrid = DataGrid::make($posts, array('id', 'title', 'created_at'));
// Get the data handler.
$dataHandler = $dataGrid->getDataHandler();
// If there are results, let's build the tabular data view.
if ($results = $dataHandler->getResults())
{
// Get the amount of pages.
$pagesCount = $dataHandler->getPagesCount();
// Calculate the per page.
$perPage = floor(count($posts) / $pagesCount);
// Manually create pagination.
$paginator = Paginator::make($results, count($posts), $perPage);
// Build and output the view.
return View::make('posts', compact('results', 'paginator'));
}
return 'No posts found.';
});
Now let's create the posts
view.
<table>
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Created at</th>
</tr>
</thead>
<tbody>
@foreach ($results as $result)
<tr>
@foreach ($result as $value)
<td>{{ $value }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
{{ $paginator->links() }}
This will build you a nice overview table with your tabular data. Notice that we've manually created an instance of the Paginator
class with the data from our data handler. If you change the pages on the paginator, the table should page through the list of records automatically because your request provider will catch the page
request parameter.
Using With The Javascript Plugin
Before you can use the Javascript plugin you have to publish the package's assets first.
php artisan asset:publish cartalyst/data-grid
This will publish Data Grid's assets into public/packages/cartalyst/data-grid
so you can link to them in your views.
You can read more about installing and using the Javascript plugin here.
Native
Coming Soon.
Usage
Cartalyst's Data Grid package provides a couple of ways to interact with. The most basic way is to instantiate a new environment with the Cartalyst\DataGrid\Environment
class and use Cartalyst's default built-in Cartalyst\DataGrid\DataHandlers\CollectionHandler
for data handling.
After creating a Data Grid object you can use the registered data handler to interact with your result set.
Loading An Environment
Before you can use the Data Grid package you need to load a new environment first. This environment will determine which request provider it needs to instantiate for you to interact with. Natively it will load an instance of Cartalyst\DataGrid\RequestProviders\NativeProvider
.
$environment = new Cartalyst\DataGrid\Environment;
From here on out you can start working with the Data Grid package.
You can register your custom request provider by sending it along when instantiating a new environment.
$provider = new CustomProvider;
$environment = new Cartalyst\DataGrid\Environment($provider);
Note: Make sure that your request provider implements
Cartalyst\DataGrid\RequestProviders\ProviderInterface
.
Registering Data Handlers
Data handlers are essentially drivers which manipulate a data source and return the required data. You can register data handlers with your environment by using the addDataHandlerMapping
function.
$environment->addDataHandlerMapping('FooDataHandler', function($data)
{
return ($data instanceof FooData);
});
Now whenever you pass along data which is an instance of FooData
when instantiating the Data Grid, the package will know to use the FooDataHandler
to handle the data.
Alternatively you can register your data handlers when loading an environment.
$handlers => array(
'FooDataHandler' => function($data)
{
return ($data instanceof FooData);
},
'BarDataHandler' => function($data)
{
return ($data instanceof BarData);
},
);
$environment = new Cartalyst\DataGrid\Environment(null, $handlers);
Default Data Handlers
Cartalyst's Data Grid package provides two data handlers by default. One of them is the Cartalyst\DataGrid\DataHandlers\CollectionHandler
which provides support for arrays and Illuminate\Support\Collection
objects.
If you'd like to use the CollectionHandler
data handler you need to register it to your Data Grid environment.
$environment->addDataHandlerMapping('Cartalyst\DataGrid\DataHandlers\CollectionHandler', function($data)
{
return(
$data instanceof Illuminate\Support\Collection or
is_array($data)
);
});
Now whenever you pass along an array of data or an Illuminate\Support\Collection
object when instantiating the Data Grid, the package will know to use the CollectionHandler
to handle the data.
Note: When we're using examples in the documentation for Data Grid, we're going to assume you have registered the
CollectionHandler
data handler.
Creating Custom Data Handlers
In addition to register the default data handlers provided by the package, you can create your own custom data handlers as well. All data handlers need to extend the abstract Cartalyst\DataGrid\DataHandlers\BaseHandler
class.
use Cartalyst\DataGrid\DataHandlers\BaseHandler;
use Cartalyst\DataGrid\DataHandlers\HandlerInterface;
class CustomHandler extends BaseHandler implements HandlerInterface {
}
Specific handlers can be created to handle specific sets of data like framework specific result sets or a certain service's API result responses.
Creating a Data-Grid Object
Creating a Data Grid object can be done by calling the make
function on the Data Grid environment.
$dataGrid = $environment->make($data, $columns);
Calling the make
function will send back an instance of Cartalyst\DataGrid\DataGrid
. The $data
variable must contain all of the data you want to filter. This can be any sort of data type as long as it can be handled by your data handlers. The $columns
variable must contain an array of all the columns for each data object to include in the result set.
The data provided can hold data objects of the following types:
- An array
- An object which is an instance of or extends the
stdClass
object - An object which implements the
Illuminate\Support\ArrayableInterface
interface
A basic example of creating a Data Grid object could be:
$object = new StdClass;
$object->title = 'foo';
$object->age = 20;
$data = array(
array(
'title' => 'bar',
'age' => 34,
),
$object,
);
$dataGrid = $environment->make($data, array(
'title',
'age',
));
Because we send in the data wrapped in an array, the Data Grid object will handle the data with the registered CollectionHandler
Data Handler.
Note: If a data object in the
$data
set doesn't has a column set in the$columns
array, it will returnnull
in the result set for that column.
You can also rename columns by defining them as a key/value pair with the originial name being the key and the new name being the value.
$dataGrid = $environment->make($data, array(
'title' => 'new_title_column_name',
'age' => 'new_age_column_name',
));
Catching Unsupported Data Types
When the Data Grid package can't find a Data Handler for the provided data, it will throw a RuntimeException
[^1]. You can catch it by doing the following:
try
{
$dataGrid = $environment->make($data, $columns);
}
catch (\RuntimeException $exception)
{
echo $exception->getMessage();
}
[^1]: PHP manual on the RuntimeException class
Data Handler
Introduction
The data handler is the class that handles and filters the data you passed along. In the examples below we'll go over the basic functionality for a data handler.
Accessing the registered data handler can be done by calling the getDataHandler
method on the Data Grid object.
$handler = $dataGrid->getDataHandler();
Retrieving Results
You can retrieve the result set by calling the getResults
method on the data handler.
$results = $handler->getResults();
This will return an array with the result set after all the request parameters have been applied.
Other Methods
Get the total amount of results.
$totalCount = $handler->getTotalCount();
Get the total amount of filtered results.
$filteredCount = $handler->getFilteredCount();
Get the current page.
$page = $handler->getPage();
Get the number of pages.
$pagesCount = $handler->getPagesCount();
Get the previous page.
$previousPage = $handler->getPreviousPage();
Get the next page.
$nextPage = $handler->getNextPage();
Get the number of results per page.
$perPage = $handler->getPerPage();
Javascript Plugin
Introduction
One of our goals with Data Grid was to leave the front end HTML up to you, and avoid what most plugins do by keeping you within a container. We built a Javascript plugin (data-grid.js
) that works together with the Tempo rendering engine to allow you to easily built flexible data grids.
You can see a working demo of the plugin at the demo page.
Requirements
Using data-grid.js
requires the following:
Installing
Add jQuery, Tempo and data-grid.js
to the <head>
section of your page.
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="//raw.github.com/twigkit/tempo/master/tempo.min.js"></script>
<script src="/vendor/cartalyst{url}/src/public/js/data-grid.js"></script>
The HTML
Data Grid requires three elements for instantiation: a results container, pagination container and finally an applied filters container. Each of these containers will contain either one or many Tempo templates.
Results Container
<table class="results" data-grid="main" data-source="http://example.com/api/v1/foo">
<thead>
<tr>
<td>City</td>
<td>Population</td>
</tr>
</thead>
<tbody>
<tr data-template>
<td>[[ city ]]</td>
<td>[[ population ]]</td>
</tr>
</tbody>
</table>
The required data-grid
attribute will allow you to create multiple Data Grids on a single page and the results
class will mark it as the results container. The data-source
attribute contains the API endpoint URI.
You might notice that the [[ ... ]]
is different from the default Tempo brace syntax. This is needed to allow the use of [? ... ?]
. You can always change this behaviour by changing the plugin's options.
Pagination Container
<ul class="pagination" data-grid="main">
<li data-template data-if-infinite data-page="[[ page ]]">Load More</li>
<li data-template data-if-throttle data-throttle>[[ label ]]</li>
<li data-template data-page="[[ page ]]">[[ pageStart ]] - [[ pageLimit ]]</li>
</ul>
Because we're setting the same data-grid
attribute, the plugin will know to group it with your results container. We use the pagination
class to indicate it as the pagination container.
There are three templates that are used inside the pagination container. The data-if-infinite
template will render only when the pagination type is set to infinite
. The data-if-throttle
template will render if a throttle is set and reached. The last template is the template used for the multiple
pagination type.
As for the other attributes, the data-page
attribute is where we store the current page. The data-throttle
attribute is our selector for events to increase the throttle. By default we will use pageStart
and pageLimit
in our pagination template to indicate which results are displayed on each page. This would output to 1 - 10, 11 - 20, ...
Filters Container
<ul class="applied" data-grid="main">
<li data-template>
[? if column == undefined ?]
[[ valueLabel ]]
[? else ?]
[[ valueLabel ]] in [[ columnLabel ]]
[? endif ?]
</li>
</ul>
We use our custom itteration of Tempo's brace syntax to build if else statements. We check for columns so you can display your filters in a readable manner. If your filter isn't filtering within a column it will just display the filter. If your filtering within a column we show both the filter and column.
The Javascript
Now that you have all of your templates setup, let's instantiate Data Grid. The first argument within the instantiation is the grid, this is the value of the data-grid
attribute. This allows us you to have a flexible layout, and multiple Data Grids on a page. Next is the results container for the response, followed by the pagination container and finally the applied filters container.
<script>
$(function()
{
$.datagrid('main', '.results', '.pagination', '.applied');
});
</script>
Should you have placed the data-source
attribute on the results container, the plugin will automatically know which URI to make it API calls to. You can also set the URI through the plugin's options.
After applying the plugin, you should get a nicely filled table with all of your data and pagination.
Applying Filters & Sorting
Lets go over how to set filters and sorts now that you have your Data Grid returning results. We use two attributes for setting filters and sorts, data-filter
and data-sort
. Both are done in key/value pairs separated by ,
. Note the space after the comma, this is needed. Filters and Sorts can be set on any element within site.
Some filter examples:
<a href="#" data-filter="all:USA" data-grid="main">Filter USA</a>
This will filter the Data Grid of main
, for anything matching USA.
<a href="#" data-filter="state:Washington" data-grid="main">Filter Washington</a>
This will filter the Data Grid of main
, for anything with a state of Washington
<a href="#" data-filter="all:USA, state:Washington" data-grid="main">Filter USA by Washington State</a>
This will filter the Data Grid of main
, for anything within the USA, and with a state of Washington.
Some sort examples:
<a href="#" data-sort="city:asc" data-grid="main">Sort By City ASC</a>
This will sort the Data Grid of 'main', by City ASC.
<a href="#" data-sort="city:desc" data-grid="main">Sort By City DESC</a>
This will sort the Data Grid of 'main', by City DESC.
From time to time, when using filters and sorts you will run into issues when your column names have underscores or something of that nature. Because of this we've created the data-label attribute to help you rewrite this for the user. The data-label
works just like filters and sorts as in its a key/value pair, separated by ,
. Again, note the whitespace after the comma.
Heres a filter with a label fix.
<a href="#" data-filter="country_code:USA" data-label="country_code:Country Code">Filter by Country Code</a>
Adding Search
Data Grid ships with a few other things to help developers get off the ground faster. If you are looking to search within your data set, all you need to do is create a form and set the data-grid
attribute and set data-search
. Make sure the input name is set to filter.
<form method="post" action="" accept-charset="utf-8" data-search data-grid="main">
<input name="filter" type="text" placeholder="Filter All">
<button>Add Filter</button>
</form>
Now if you are looking for to let users filter within defined columns all you need to do is add a select menu within the form. The select name should be set to 'column' and the value of options set to the column name.
<form method="post" action="" accept-charset="utf-8" data-search data-grid="main">
<select name="column" class="input-medium">
<option value="all">All</option>
<option value="city">city</option>
<option value="population">Population</option>
</select>
<input name="filter" type="text" placeholder="Filter All">
<button>Add Filter</button>
</form>
Resetting
If you want to give your users a simple way to reset Data Grid, just create a button, set the attributes data-grid
and data-reset
and your done.
<button data-reset data-grid="main">Reset</button>
Options
You can add plugin options by adding a fifth object parameter to your plugin instantiation.
<script>
$(function()
{
$.datagrid('main', '.results', '.pagination', '.applied',
{
source: 'http://example.com/api/v1{url}',
sort: {
column: 'city',
direction: 'desc'
},
...
});
});
</script>
Below is a list with all of the available options.
Option | Type | Description |
---|---|---|
source | string | The API endpoint URI. |
sort | object | Set a default column and direction for sorting. |
threshold | integer | Minimum amount of results before pagination is applied. |
dividend | integer | The maximum amount of pages you wish to have. |
throttle | integer | The maxmim amount of results on a single page. Overrides dividend. |
type | string | The type of pagination to use. Options are: "single", "multiple" and "infinite". |
tempoOptions | object | Changes the surrounding braces. Data Grid's default is set to [[ ... ]]. |
loader | string | class of id of a loading element to be shown while the ajax request is made. |
callback | function | This parameter you can pass a function that will run every time a filter is added, or a sort is applied. This function recives one argument, and gives you access to anyting set within the plugin. |
Result Sets
Introduction
After instantiating a Data Grid object you have a couple of options to work with the data result sets. Depending on the request provider which you registered with your environment, the implementation of these methods can differ. In the examples below we're going to assume you're using the default Cartalyst\DataGrid\RequestProviders\NativeProvider
request provider.
You can filter results by sending specific request parameters along with your HTTP request. Your request provider will catch these so your data handler can filter the data based on these request parameters.
Generating Results
You can convert the result set from the Data Grid object to an array or JSON response by calling the toArray
or toJson
functions.
$environment = new Cartalyst\DataGrid\Environment;
$dataGrid = $environment->make($data, $columns);
// Retrieve the result set as an array.
$array = $dataGrid->toArray();
// Retrieve the result set as a JSON response.
$json = $dataGrid->toJson();
The returned response would look something like this:
{
"total_count": 3,
"filtered_count": 3,
"page": 1,
"pages_count": 1,
"previous_page": null,
"next_page": null,
"per_page": 3,
"results": [
{
"name": "John Doe",
"age": 22,
"location": "New York"
},
...
]
}
The response contains some useful information like the total number of results, the amount of filtered results, the current, previous and next page and a list with all of the results for the current page.
Note: When sending the Data Grid object to the browser, it will be automatically converted to a JSON response. This is very useful, for example, when building APIs.
Sorting Results
You can sort the result set by sending a request parameter with the sort
key.
http://example.com/search?sort=name
This will sort the results by name.
Reverting the sorted results can be done by sending a request parameter with the direction
key.
http://example.com/search?sort=name&direction=desc
Now the results will be sorted descended by name.
Switching Pages
Changing the page in a result set can be done by sending a request parameter with the page
key.
http://example.com/search?page=2
This would show the results on the second page of the result set.
Filter Results
You can filter results by sending a request parameter with the filters
key. The filters request parameter must provide filters based as a key (column) and value (column value) array.
For example:
http://testing.loc/search?filters[0][name]=foo&filters[1][age]=24
This would show only results which have a foo name and an age of 24.
Paginating Results
There are three request parameters you can use to paginate a result set: threshold
, dividend
and throttle
.
threshold
is the number of results before pagination is applied to the result set. For example: if you set this to 5 then only when there are more than 5 results, pagination would be applied. The default for this parameter is 100.
dividend
is the ideal number of pages you want to have for your result set. If you set this, for example, to 5 and the number of results is greater then the threshold
the data handler will try to create a paginated result set with a maximum of 5 pages. The amount of results / page is calculated by dividing the total results with the dividend
. the default value for this parameter is 10.
throttle
is the maximum amount of results you wish to display on a single page. Should the amount of results for each page be greater than this number, a new amount of results / page will be calculated by diving the threshold
with the dividend
. This means that the amount of pages can be greater than set by dividend
. The default value for this parameter is 100.
An example of these request parameters can be:
http://example.com/search?threshold=5÷nd=10&throttle=100
In this example, the data handler will start paginating the results when there are more than 5 results. When there are less than 100 results, pagination will be created with 10 pages containing a number of results for each page based on dividing the total number of results by 10 (dividend
). Should there be more than 100 results, the amount of results would be limited by 100 and the amount of results for each page would be calculated by dividing the throttle
with the dividend
.