jQuery DataTables sind ein wirklich mächtiges Werkzeug. Besonders die Möglichkeit des Server-side Processing ist ein äußerst nettes Feature im Zusammenspiel mit TYPO3 bzw. einer Extbase Extension.
Im nachfolgenden Beispiel werden die Daten für den DataTable von einem JsonProductController über eine JsonProductView (JsonView anstelle TemplateView) bereit gestellt.
In der Konfiguration der JsonView wird unter anderem definiert, welche Properties sowie Child-Objekte des Models in der Rückgabe enthalten sein sollen (die Syntax ist gewöhnungsbedürftig und hat es besonders bei verschachtelten Objekten in sich).
Hier im Table genutzte Features (alles läuft Server-side ab und wird entsprechend vom ProductRepository bereit gestellt!): Globale Suche, spaltenbasierte Suche, spaltenbasierte Sortierung, Pagebrowser, Anzahl Datensätze.
JsonProductView
class JsonProductView extends \TYPO3\CMS\Extbase\Mvc\View\JsonView {
/**
* @var array
*/
protected $configuration = [
'response' => [
'data' => [
'_descendAll' => [
'_descend' => [
'maturity' => [],
'currency' => [
'_only' => ['title'],
],
'updated' => [],
'customer' => [
'_only' => ['title', 'title_short', 'titleShort'],
],
],
],
],
],
];
/**
* Transform ObjectStorages to Arrays
*
* @param mixed $value
* @param array $configuration
* @return array
*/
protected function transformValue($value, array $configuration) {
if ($value instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage) {
$value = $value->toArray();
}
return parent::transformValue($value, $configuration);
}
}
JsonProductController
class JsonProductController extends BaseJsonController {
/**
* productRepository
*
* @var \Vendor\Extension\Domain\Repository\ProductRepository
* @inject
*/
protected $productRepository = null;
/**
* initialize action list
*
* @return void
*/
public function initializeListAction(){
$this->defaultViewObjectName = \Vendor\Extension\View\JsonProductView::class;
}
/**
* action list
*
* @return void
*/
public function listAction() {
$draw = (int) ($this->request->hasArgument('draw') && $this->request->getArgument('draw') > 0 ? $this->request->getArgument('draw') : 0);
$start = (int) ($this->request->hasArgument('start') && $this->request->getArgument('start') > 0 ? $this->request->getArgument('start') : 0);
$length = (int) ($this->request->hasArgument('length') && $this->request->getArgument('length') > 0 ? $this->request->getArgument('length') : 10);
$order = ($this->request->hasArgument('order') && count($this->request->getArgument('order')) > 0 ? $this->request->getArgument('order') : []);
$columns = ($this->request->hasArgument('columns') && count($this->request->getArgument('columns')) > 0 ? $this->request->getArgument('columns') : []);
$search = ($this->request->hasArgument('search') && count($this->request->getArgument('search')) > 0 ? $this->request->getArgument('search') : []);
// Ordering
foreach ($order as $value) {
if ($columns[$value['column']]['orderable'] == true) {
if ($value['dir'] == 'asc') {
$orderings[$columns[$value['column']]['data']] = \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING;
} else {
$orderings[$columns[$value['column']]['data']] = \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING;
}
}
}
// Search - global
if ($search && $search['value'] != '') {
foreach ($columns as $column) {
if ($column['searchable'] == true) {
$searchingsGlobal[$column['data']] = $search['value'];
}
}
}
// Search - columned
foreach ($columns as $column) {
if ($column['searchable'] == true && $column['search']['value'] != '') {
$searchingsColumned[$column['data']] = $column['search']['value'];
}
}
$allProducts = $this->productRepository->findAllByFilter(null, null, $orderings, $searchingsGlobal, $searchingsColumned);
$filteredProducts = $this->productRepository->findAllByFilter($start, $length, $orderings, $searchingsGlobal, $searchingsColumned);
$response = [
'draw' => $draw,
'start' => $start,
'length' => $length,
'recordsTotal' => $allProducts->count(),
'recordsFiltered' => $allProducts->count(),
'order' => $order,
'search' => $search,
'columns' => $columns,
'data' => $filteredProducts
];
$this->view->setVariablesToRender(['response']);
$this->view->assign('response', $response);
}
}
ProductRepository
class ProductRepository extends \TYPO3\CMS\Extbase\Persistence\Repository {
protected $defaultOrderings = array(
'maturity' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
'title' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING
);
/**
* findAllByFilter
*
* @param integer $start
* @param integer $length
* @param mixed $order
* @param mixed $searchGlobal
* @param mixed $searchColumned
*
* @return QueryResultInterface|array
*/
public function findAllByFilter($start=0, $length=0, $order=[], $searchGlobal=[], $searchColumned=[]) {
$query=$this->createQuery();
$constraints = [];
// Start
if($start > 0) {
$query->setOffset($start);
}
// Length
if($length > 0) {
$query->setLimit($length);
}
// Orderings
if(count($order) > 0) {
$query->setOrderings($order);
}
// Search - global
if(count($searchGlobal) > 0) {
foreach($searchGlobal as $key => $value) {
$constraintsSearchGlobal[] = $query->like($key, '%' . $value . '%');
}
$constraints[] = $query->logicalOr($constraintsSearchGlobal);
}
// Search - columned
if(count($searchColumned) > 0) {
foreach($searchColumned as $key => $value) {
$constraintsSearchColumned[] = $query->like($key, '%' . $value . '%');
}
$constraints[] = $query->logicalAnd($constraintsSearchColumned);
}
// Combine constraints
if(count($constraints) > 0) {
$query->matching($query->logicalAnd($constraints));
}
return $query->execute();
}
}
TypoScript (Bootstrap Json Plugin)
page_json = PAGE
page_json {
typeNum = 9876
10 = USER
10 {
userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
extensionName = Extension
pluginName = JsonData
vendorName = Vendor
}
config {
disableAllHeaderCode = 1
additionalHeaders = Content-type:application/json
xhtml_cleaning = 0
admPanel = 0
debug = 0
no_cache = 1
contentObjectExceptionHandler = 0
}
}
Template (DataTable)
<table
id="datatable-products-scroller"
rel="{f:uri.action(pluginName: 'JsonData', controller: 'JsonProduct', action: 'list', pageType: 9876, noCacheHash: 1, absolute: 1)}"
>
<thead>
<tr>
<th>Currency</th>
<th>Maturity</th>
<th>Ticker</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<th class="search_column">
<input type="text" class="form-control input-sm input-search" placeholder="Search Currency" />
</th>
<th class="search_column">
<input type="text" class="form-control input-sm input-search" placeholder="Search Maturity" />
</th>
<th class="search_column">
<input type="text" class="form-control input-sm input-search" placeholder="Search Name" />
</th>
</tr>
</tfoot>
</table>
JavaScript (DataTable Initialisierung)
// DataTable
tableProducts = $('#datatable-products-scroller').DataTable({
deferRender: true,
scrollY: 280,
scrollCollapse: false,
scroller: false,
paging: true,
order: [[1, 'asc'], [2, 'asc']],
lengthChange: true,
info: false,
searching: true,
iDisplayLength: 100,
select: true,
rowId: 'uid',
processing: true,
serverSide: true,
ajax: {
url: $('#datatable-products-scroller').attr('rel'),
data: function (data) {
return {tx_extension_jsondata: data};
},
error: function (hxr, error, thrown) {
console.log('Uups...');
}
},
columns: [
{data: 'currency.title'},
{
data: 'maturity',
render: function (data) {
var date = new Date(data);
var month = date.getMonth() + 1;
return (date.getDate() < 10 ? '0' : '') + date.getDate() + '.' + (date.getMonth() < 10 ? '0' : '') + date.getMonth() + '.' + date.getFullYear();
}
},
{data: 'title'}
]
});
// Search - columned
tableProducts.columns().every(function () {
var that = this;
$('input', this.footer()).on('keyup change', function () {
if (that.search() !== this.value) {
that
.search(this.value)
.draw();
}
});
});
// Clicked row - attach anything
$('#datatable-products-scroller tbody').on('click', 'tr', function () {
var data = tableProducts.row(this).data();
productId = data['uid'];
doSomething(productId);
});
Kommentare