├── .gitignore
├── .travis.yml
├── DependencyInjection
├── Configuration.php
└── WaldoDatatableExtension.php
├── LICENSE
├── Listener
└── KernelTerminateListener.php
├── README.md
├── Resources
├── config
│ └── services.xml
├── doc
│ ├── images
│ │ └── sample_01.png
│ └── test.md
├── translations
│ ├── messages.ar.yml
│ ├── messages.cn.yml
│ ├── messages.de.yml
│ ├── messages.en.yml
│ ├── messages.es.yml
│ ├── messages.fr.yml
│ ├── messages.it.yml
│ ├── messages.pl.yml
│ ├── messages.ru.yml
│ ├── messages.tr.yml
│ └── messages.ua.yml
└── views
│ ├── Main
│ ├── datatableHtml.html.twig
│ ├── datatableJs.html.twig
│ └── index.html.twig
│ ├── Renderers
│ └── _default.html.twig
│ └── Snippet
│ ├── individualSearchField.js.twig
│ └── multipleRaw.js.twig
├── Tests
├── BaseClient.php
├── Functional
│ ├── Datatable
│ │ ├── DatatableStaticTest.php
│ │ ├── DatatableTest.php
│ │ └── DoctrineBuilderTest.php
│ └── Entity
│ │ ├── Feature.php
│ │ └── Product.php
├── Unit
│ ├── DependencyInjection
│ │ └── WaldoDatatableExtensionTest.php
│ ├── Listner
│ │ └── KernelTerminateListenerTest.php
│ └── Twig
│ │ └── DatatableTwigExtensionTest.php
├── app
│ ├── AppKernel.php
│ ├── Resources
│ │ └── views
│ │ │ └── Renderers
│ │ │ └── _actions.html.twig
│ └── config
│ │ ├── config_test.yml
│ │ └── routing.yml
└── bootstrap.php
├── Twig
└── Extension
│ └── DatatableExtension.php
├── Util
├── ArrayMerge.php
├── Datatable.php
├── Factory
│ └── Query
│ │ ├── DoctrineBuilder.php
│ │ └── QueryInterface.php
└── Formatter
│ └── Renderer.php
├── WaldoDatatableBundle.php
├── composer.json
└── phpunit.xml.dist
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | !/vendor/vendors.php
3 | composer.lock
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.4
5 | - 5.5
6 | - 5.6
7 | - 7.0
8 |
9 |
10 | env:
11 | - SYMFONY_VERSION=v2.6
12 | - SYMFONY_VERSION=v2.7
13 | - SYMFONY_VERSION=v2.8
14 | - SYMFONY_VERSION=v3.0
15 | - SYMFONY_VERSION=v3.1
16 |
17 | install: composer --prefer-source install
18 |
19 |
--------------------------------------------------------------------------------
/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | root('waldo_datatable');
22 |
23 | $rootNode
24 | ->children()
25 | ->arrayNode('all')
26 | ->addDefaultsIfNotSet()
27 | ->children()
28 | ->scalarNode('action')->defaultTrue()->end()
29 | ->scalarNode('search')->defaultFalse()->end()
30 | ->end()
31 | ->end()
32 | ->arrayNode('js')
33 | ->useAttributeAsKey('name')
34 | ->prototype('variable')
35 | ->end()
36 | ->end()
37 | ->end()
38 | ;
39 |
40 | return $treeBuilder;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/DependencyInjection/WaldoDatatableExtension.php:
--------------------------------------------------------------------------------
1 | processConfiguration($configuration, $configs);
25 |
26 | $config = $this->applyDefaultConfig($config);
27 |
28 |
29 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
30 | $loader->load('services.xml');
31 |
32 | $container->setParameter('datatable', $config);
33 | }
34 |
35 | /**
36 | * Datatable options
37 | *
38 | * @see https://datatables.net/reference/option/
39 | * @param type $config
40 | */
41 | private function applyDefaultConfig($config)
42 | {
43 | $defaultJsConfig = array(
44 | "jQueryUI" => true,
45 | "pagingType" => "full_numbers",
46 | "lengthMenu" => [[10, 25, 50, -1], [10, 25, 50, "All"]],
47 | "pageLength" => 10,
48 | "serverSide" => true,
49 | "processing" => true,
50 | "paging" => true,
51 | "lengthChange" => true,
52 | "ordering" => true,
53 | "searching" => true,
54 | "autoWidth" => false,
55 | "order" => array()
56 | );
57 |
58 | $config['js'] = array_merge($defaultJsConfig, $config['js']);
59 |
60 | return $config;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2014 Ali Hichem
2 | Copyright (c) 2015 Valérian Girard
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is furnished
9 | to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Listener/KernelTerminateListener.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class KernelTerminateListener
11 | {
12 | public function onKernelTerminate()
13 | {
14 | Datatable::clearInstance();
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | DatatableBundle
2 | ===============
3 |
4 | Fork of [AliHichem/DatatableBundle](https://github.com/AliHichem/DatatableBundle), this bundle will add some great features
5 | and evolve in a different way than it source.
6 |
7 | [](https://travis-ci.org/waldo2188/DatatableBundle)
8 | [](https://insight.sensiolabs.com/projects/bb7b64f6-4203-45ca-b99a-2d15c4d272ec)
9 |
10 | > **Warning**: The [jQuery Datatable plugin](http://datatables.net/) has evolved (version 1.10) with a all new API and option.
11 | > You **MUST** use the version **2** of DatatableBundle with the jQuery Datatable plugin version lower than 1.10.
12 | > You **MUST** use the version **3** of DatatableBundle with the jQuery Datatable plugin version equal or greater than 1.10.
13 |
14 | The Datatable bundle for symfony2 allow for easily integration of the [jQuery Datatable plugin](http://datatables.net/) with
15 | the doctrine2 entities.
16 | This bundle provides a way to make a projection of a doctrine2 entity to a powerful jquery datagrid. It mainly includes:
17 |
18 | * datatable service container: to manage the datatable as a service.
19 | * twig extension: for view integration.
20 | * dynamic pager handler : no need to set your pager.
21 | * support doctrine2 association.
22 | * support of Doctrine Query Builder.
23 | * support of doctrine subquery.
24 | * support of column search.
25 | * support of custom twig/phpClosure renderers.
26 |
27 |

28 |
29 | -------------------------------------
30 | ## Summary
31 | - [Installation](#installation)
32 | 1. [Download DatatableBundle using Composer](#Download-DatatableBundle-using-Composer)
33 | 2. [Enable the Bundle](#Enable-the-Bundle)
34 | 3. [Configure the Bundle](#Configure-the-Bundle)
35 | - [How to use DatatableBundle ?](#how-to-use-datatablebundle-)
36 | - [Rendering inside Twig](#rendering-inside-twig)
37 | - [Advanced Use of DatatableBundle](#advanced-use-of-DatatableBundle)
38 | - [Use of search filters](#use-of-search-filters)
39 | - [Activate search globally](#activate-search-globally)
40 | - [Set search fields](#set-search-fields)
41 | - [Multiple actions, how to had checkbox for each row ?](#Multiple-actions-how-to-had-checkbox-for-each-row-)
42 | - [Custom renderer for cells](#custom-renderer-for-cells)
43 | - [Datatable Callbacks options](#datatable-callbacks-options)
44 | - [Translation](#translation)
45 | - [Doctrine Query Builder](#doctrine-query-builder)
46 | - [Multiple datatable in the same view](#multiple-datatable-in-the-same-view)
47 | - [Use specific jQuery Datatable options](#Use-specific-jQuery-Datatable-options)
48 | - [Launch the test suite](/Resources/doc/test.md)
49 |
50 | ---------------------------------------
51 |
52 | ## Installation
53 |
54 | Installation is a quick (I promise!) 3 step process:
55 | 1. [Download DatatableBundle using Composer](#Download-DatatableBundle-using-Composer)
56 | 2. [Enable the Bundle](#Enable-the-Bundle)
57 | 3. [Configure the Bundle](#Configure-the-Bundle)
58 |
59 | ### Download DatatableBundle using Composer
60 |
61 | #### Using Composer
62 |
63 | Install with this command : `composer require waldo/datatable-bundle`
64 |
65 | Generate the assets symlinks :
66 | ```bash
67 | php app/console assets:install --symlink web
68 | ```
69 |
70 | ### Enable the Bundle
71 |
72 | Add the bundle to the `AppKernel.php`
73 | ```php
74 | $bundles = array(
75 | \\...
76 | new Waldo\DatatableBundle\WaldoDatatableBundle(),
77 | )
78 | ```
79 |
80 | ### Configure the Bundle
81 |
82 | In this section you can put the global config that you want to set for all the instance of DataTable in your project.
83 |
84 | #### To keep it to default
85 |
86 | ```
87 | # app/config/config.yml
88 | waldo_datatable:
89 | all: ~
90 | js: ~
91 | ```
92 |
93 | The `js` config will be applied to DataTable exactly like you do with `$().datatable({ your config });` in a javascript part.
94 | > Note: all your js config have to be string typed, make sure to use (") as delimiters.
95 |
96 | #### Config sample
97 |
98 | ```
99 | waldo_datatable:
100 | all:
101 | search: false
102 | js:
103 | pageLength: "10"
104 | lengthMenu: [[5,10, 25, 50, -1], [5,10, 25, 50, 'All']]
105 | dom: '<"clearfix"lf>rtip'
106 | jQueryUI: "false"
107 | ```
108 |
109 | ## How to use DatatableBundle ?
110 |
111 | Assuming for example that you need a grid in your "index" action, create in your controller method as below :
112 |
113 | **Warning alias `as` is case-sensitive, always write it in lower case**
114 |
115 | ```php
116 | /**
117 | * set datatable configs
118 | * @return \Waldo\DatatableBundle\Util\Datatable
119 | */
120 | private function datatable() {
121 | return $this->get('datatable')
122 | ->setEntity("XXXMyBundle:Entity", "x") // replace "XXXMyBundle:Entity" by your entity
123 | ->setFields(
124 | array(
125 | "Name" => 'x.name', // Declaration for fields:
126 | "Address" => 'x.address', // "label" => "alias.field_attribute_for_dql"
127 | "Total" => 'COUNT(x.people) as total', // Use SQL commands, you must always define an alias
128 | "Sub" => '(SELECT i FROM ... ) as sub', // you can set sub DQL request, you MUST ALWAYS define an alias
129 | "_identifier_" => 'x.id') // you have to put the identifier field without label. Do not replace the "_identifier_"
130 | )
131 | ->setWhere( // set your dql where statement
132 | 'x.address = :address',
133 | array('address' => 'Paris')
134 | )
135 | ->setOrder("x.created", "desc"); // it's also possible to set the default order
136 | }
137 |
138 |
139 | /**
140 | * Grid action
141 | * @Route("/", name="datatable")
142 | * @return Response
143 | */
144 | public function gridAction()
145 | {
146 | return $this->datatable()->execute(); // call the "execute" method in your grid action
147 | }
148 |
149 | /**
150 | * Lists all entities.
151 | * @Route("/list", name="datatable_list")
152 | * @return Response
153 | */
154 | public function indexAction()
155 | {
156 | $this->datatable(); // call the datatable config initializer
157 | return $this->render('XXXMyBundle:Module:index.html.twig'); // replace "XXXMyBundle:Module:index.html.twig" by yours
158 | }
159 | ```
160 |
161 | ## Rendering inside Twig
162 |
163 | You have the choice, you can render the HTML table part and Javascript part in just one time with the Twig function `datatable`,
164 | like below.
165 |
166 | ```twig
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | {{ datatable({
175 | 'js' : {
176 | 'ajax' : path('route_for_your_datatable_action')
177 | }
178 | })
179 | }}
180 | ```
181 |
182 | Or, render each part separatly.
183 |
184 | `datatable_html` is the Twig function for the HTML part.
185 | `datatable_js` is the Twig function for the Javascript part.
186 |
187 | ```twig
188 | {% block body %}
189 |
190 | {{ datatable_html({
191 | 'id' : 'dta-offres'
192 | })
193 | }}
194 |
195 | {% endblock %}
196 |
197 | {% block javascripts %}
198 |
199 |
200 | {{ datatable_js({
201 | 'id' : 'dta-offres',
202 | 'js' : {
203 | 'dom': '<"clearfix"lf>rtip',
204 | 'ajax': path('route_for_your_datatable_action'),
205 | }
206 | })
207 | }}
208 | {% endblock javascripts %}
209 | ```
210 |
211 | ## Advanced Use of DatatableBundle
212 |
213 | ### Advanced php config
214 |
215 | Assuming the example above, you can add your joins and where statements.
216 |
217 | ```php
218 | /**
219 | * set datatable configs
220 | *
221 | * @return \Waldo\DatatableBundle\Util\Datatable
222 | */
223 | private function datatable()
224 | {
225 | return $this->get('datatable')
226 | ->setEntity("XXXMyBundle:Entity", "x") // replace "XXXMyBundle:Entity" by your entity
227 | ->setFields(
228 | array(
229 | "Name" => 'x.name', // Declaration for fields:
230 | "Address" => 'x.address', // "label" => "alias.field_attribute_for_dql"
231 | "Group" => 'g.name',
232 | "Team" => 't.name',
233 | "_identifier_" => 'x.id') // you have to put the identifier field without label. Do not replace the "_identifier_"
234 | )
235 | ->addJoin('x.group', 'g', \Doctrine\ORM\Query\Expr\Join::INNER_JOIN)
236 | ->addJoin('x.team', 't', \Doctrine\ORM\Query\Expr\Join::LEFT_JOIN)
237 | ->addJoin('x.something', 's', \Doctrine\ORM\Query\Expr\Join::LEFT_JOIN, \Doctrine\ORM\Query\Expr\Join::WITH, 's.id = :someId')
238 | ->setWhere( // set your dql where statement
239 | 'x.address = :address',
240 | array('address' => 'Paris')
241 | )
242 | ->setOrder("x.created", "desc") // it's also possible to set the default order.
243 | ->setParameter('someId', 12)
244 | ;
245 | }
246 | ```
247 |
248 | ### Use of search filter
249 | * [Activate search globally](#activate-search-globally)
250 | * [Set search fields](#set-search-fields)
251 |
252 | #### Activate search globally
253 |
254 | The searching functionality that is very useful for quickly search through the information from the database.
255 | This bundle provide two way of searching, who can be used together : **global search** and **individual column search**.
256 |
257 | By default the filtering functionality is disabled, to get it working you just need to activate it from your configuration method like this :
258 |
259 | ```php
260 | private function datatable()
261 | {
262 | return $this->get('datatable')
263 | //...
264 | ->setSearch(true); // for individual column search
265 | // or
266 | ->setGlobalSearch(true);
267 | }
268 | ```
269 | #### Set search fields
270 |
271 | You can set fields where you want to enable your search.
272 | Let say you want search to be active only for "field 1" and "field3 ", you just need to activate search for the approriate column key
273 | and your datatable config should be :
274 |
275 | ```php
276 | /**
277 | * set datatable configs
278 | *
279 | * @return \Waldo\DatatableBundle\Util\Datatable
280 | */
281 | private function datatable()
282 | {
283 | $datatable = $this->get('datatable');
284 | return $datatable->setEntity("XXXMyBundle:Entity", "x")
285 | ->setFields(
286 | array(
287 | "label of field 1" => 'x.field1', // column key 0
288 | "label of field 2" => 'x.field2', // column key 1
289 | "label of field 3" => 'x.field3', // column key 2
290 | "_identifier_" => 'x.id') // column key 3
291 | )
292 | ->setSearch(true)
293 | ->setSearchFields(array(0,2))
294 | ;
295 | }
296 | ```
297 |
298 | ### Multiple actions, how to had checkbox for each row ?
299 |
300 | Sometimes, it's good to be able to do the same action on multiple records like deleting, activating, moving ...
301 | Well this is very easy to add to your datatable: all what you need is to declare your multiple action as follow.
302 |
303 | ```php
304 | /**
305 | * set datatable configs
306 | * @return \Waldo\DatatableBundle\Util\Datatable
307 | */
308 | private function datatable()
309 | {
310 | $datatable = $this->get('datatable');
311 | return $datatable->setEntity("XXXMyBundle:Entity", "x")
312 | ->setFields(
313 | array(
314 | "label of field1" => 'x.field1', // column key 0
315 | "label of field2" => 'x.field2', // column key 1
316 | "_identifier_" => 'x.id') // column key 2
317 | )
318 | ->setMultiple(
319 | array(
320 | 'delete' => array(
321 | 'title' => 'Delete',
322 | 'route' => 'multiple_delete_route' // path to multiple delete route action
323 | ),
324 | 'move' => array(
325 | 'title' => 'Move',
326 | 'route' => 'multiple_move_route' // path to multiple move route action
327 | ),
328 | )
329 | )
330 | ;
331 | }
332 | ```
333 |
334 | Then all what you have to do is to add the necessary logic in your "multiple_delete_route" (or whatever your route is for).
335 | In that action, you can get the selected ids by :
336 |
337 | ```php
338 | $data = $this->getRequest()->get('dataTables');
339 | $ids = $data['actions'];
340 | ```
341 |
342 | ### Custom renderer for cells
343 |
344 | #### Twig renderers
345 |
346 | To set your own column structure, you can use a custom twig renderer as below :
347 | In this example you can find how to set the use of the default twig renderer for action fields which you can override as
348 | your own needs.
349 |
350 | ```php
351 | /**
352 | * set datatable configs
353 | * @return \Waldo\DatatableBundle\Util\Datatable
354 | */
355 | private function datatable()
356 | {
357 | $datatable = $this->get('datatable');
358 | return $datatable->setEntity("XXXMyBundle:Entity", "x")
359 | ->setFields(
360 | array(
361 | "label of field1" => 'x.field1',
362 | "label of field2" => 'x.field2',
363 | "_identifier_" => 'x.id')
364 | )
365 | ->setRenderers(
366 | array(
367 | 2 => array(
368 | 'view' => 'XXXMyBundle:Renderers:_actions.html.twig', // Path to the template
369 | 'params' => array( // All the parameters you need (same as a twig template)
370 | 'edit_route' => 'route_edit',
371 | 'delete_route' => 'route_delete'
372 | ),
373 | ),
374 | )
375 | );
376 | }
377 | ```
378 |
379 | In a twig renderer you can have access the the field value using `dt_item` variable,
380 | ```
381 | // XXXMyBundle:Renderers:_actions.html.twig
382 | {{ dt_item }}
383 | ```
384 | or access the entire entity object using `dt_obj` variable.
385 | ```
386 | // XXXMyBundle:Renderers:_actions.html.twig
387 | {{ dt_obj.username }}
388 | ```
389 |
390 | > NOTE: be careful of Doctrine's LAZY LOADING when using dt_obj !
391 |
392 | #### PHP Closures
393 |
394 | Assuming the example above, you can set your custom fields renderer using [PHP Closures](http://php.net/manual/en/class.closure.php).
395 |
396 | ```php
397 | /**
398 | * set datatable configs
399 | * @return \Waldo\DatatableBundle\Util\Datatable
400 | */
401 | private function datatable() {
402 |
403 | $controller_instance = $this;
404 | return $this->get('datatable')
405 | ->setEntity("XXXMyBundle:Entity", "x") // replace "XXXMyBundle:Entity" by your entity
406 | ->setFields(
407 | array(
408 | "Name" => 'x.name', // Declaration for fields:
409 | "Address" => 'x.address', // "label" => "alias.field_attribute_for_dql"
410 | "_identifier_" => 'x.id') // you have to put the identifier field without label. Do not replace the "_identifier_"
411 | )
412 | ->setRenderer(
413 | function(&$data) use ($controller_instance) {
414 | foreach ($data as $key => $value) {
415 | if ($key == 1) { // 1 => address field
416 | $data[$key] = $controller_instance
417 | ->get('templating')
418 | ->render(
419 | 'XXXMyBundle:Module:_grid_entity.html.twig',
420 | array('data' => $value)
421 | );
422 | }
423 | }
424 | }
425 | );
426 | }
427 | ```
428 |
429 | ### DataTable Callbacks options
430 | If you need to put some Javascript Callbacks like [`drawCallback`](http://datatables.net/reference/option/drawCallback), you can
431 | do it localy with the Datatable `js` option. See the two examples below:
432 |
433 | ```twig
434 | // XXXMyBundle:Welcome:list.html.twig
435 | {{ datatable_js({
436 | 'id' : 'datable-id',
437 | 'js' : {
438 | 'ajax': "/some/path",
439 | 'createdRow': 'function(){console.log("Do something useful here");}'
440 | }
441 | })
442 | }}
443 |
444 | {# or #}
445 |
446 | {{ datatable_js({
447 | 'id' : 'datable-id',
448 | 'js' : {
449 | 'ajax': "/some/path",
450 | 'createdRow': 'myUsefullThing'
451 | }
452 | })
453 | }}
454 |
459 | ```
460 |
461 | You can also define a Callback globally by setting it up in the `config.yml` like below :
462 |
463 | ```yaml
464 | waldo_datatable:
465 | js:
466 | createdRow: |
467 | function(){console.log("Do something useful here");}
468 |
469 | ```
470 |
471 |
472 | ### Translation
473 |
474 | You can set your own translated labels by adding in your translation catalog entries as define in `Resources/translations/messages.en.yml`
475 |
476 | You can also get the translated labels from *official DataTable translation* repository, by configuring the bundle like below :
477 | ```yaml
478 | waldo_datatable:
479 | all: ~
480 | js:
481 | language:
482 | url: "//cdn.datatables.net/plug-ins/1.10.9/i18n/Chinese.json"
483 | ```
484 |
485 | This bundle includes nine translation catalogs: Arabic, Chinese, Dutch, English, Spanish, French, Italian, Polish, Russian and Turkish
486 | To get more translated entries, you can follow the [official DataTable translation](https://datatables.net/manual/i18n)
487 |
488 |
489 | ### Doctrine Query Builder
490 |
491 | To use your own query object to supply to the datatable object, you can perform this action using your own
492 | "Doctrine Query object": DatatableBundle allow to manipulate the query object provider which is now a Doctrine Query Builder object,
493 | you can use it to update the query in all its components except of course in the selected field part.
494 |
495 | This is a classic config before using the Doctrine Query Builder:
496 |
497 | ```php
498 | private function datatable()
499 | {
500 | $datatable = $this->get('datatable')
501 | ->setEntity("XXXBundle:Entity", "e")
502 | ->setFields(
503 | array(
504 | "column1 label" => 'e.column1',
505 | "_identifier_" => 'e.id')
506 | )
507 | ->setWhere(
508 | 'e.column1 = :column1',
509 | array('column1' => '1' )
510 | )
511 | ->setOrder("e.created", "desc");
512 |
513 | $qb = $datatable->getQueryBuilder()->getDoctrineQueryBuilder();
514 | // This is the Doctrine Query Builder object, you can
515 | // retrieve it and include your own change
516 |
517 | return $datatable;
518 | }
519 | ```
520 |
521 | This is a config that uses a Doctrine Query object a query builder :
522 |
523 | ```php
524 | private function datatable()
525 | {
526 | $qb = $this->getDoctrine()->getEntityManager()->createQueryBuilder();
527 | $qb->from("XXXBundle:Entity", "e")
528 | ->where('e.column1 = :column1')
529 | ->setParameters(array('column1' = 0))
530 | ->orderBy("e.created", "desc");
531 |
532 | $datatable = $this->get('datatable')
533 | ->setFields(
534 | array(
535 | "Column 1 label" => 'e.column1',
536 | "_identifier_" => 'e.id')
537 | );
538 |
539 | $datatable->getQueryBuilder()->setDoctrineQueryBuilder($qb);
540 |
541 | return $datatable;
542 | }
543 | ```
544 |
545 | ### Multiple DataTable in the same view
546 |
547 | To declare multiple DataTables in the same view, you have to set the datatable identifier in you controller with `setDatatableId` :
548 | Each of your DataTable config methods ( datatable() , datatable_1() .. datatable_n() ) needs to set the same identifier used in your view:
549 |
550 | #### In the controller
551 |
552 | ```php
553 | protected function datatable()
554 | {
555 | // ...
556 | return $this->get('datatable')
557 | ->setDatatableId('dta-unique-id_1')
558 | ->setEntity("XXXMyBundle:Entity", "x")
559 | // ...
560 | }
561 |
562 | protected function datatableSecond()
563 | {
564 | // ...
565 | return $this->get('datatable')
566 | ->setDatatableId('dta-unique-id_2')
567 | ->setEntity("YYYMyBundle:Entity", "y")
568 | // ...
569 | }
570 | ```
571 |
572 | #### In the view
573 |
574 | ```js
575 | {{
576 | datatable({
577 | 'id' : 'dta-unique-id_1',
578 | ...
579 | 'js' : {
580 | 'ajax' : path('route_for_your_datatable_action_1')
581 | }
582 | })
583 | }}
584 |
585 | {{
586 | datatable({
587 | 'id' : 'dta-unique-id_2',
588 | ...
589 | 'js' : {
590 | 'ajax' : path('route_for_your_datatable_action_2')
591 | }
592 | })
593 | }}
594 | ```
595 |
596 | ## Use specific jQuery DataTable options
597 |
598 | Some time we need to apply some specific options to a grid, like a specific width for the second column.
599 | DataTable comes with a [lot a feature](http://datatables.net/reference/option/) that you can always use, even with this bundle.
600 |
601 | In the code below, we use the `columnDefs` option to fix the width of the second column.
602 | ```js
603 | {{
604 | datatable({
605 | 'id' : 'dta-id',
606 | 'js' : {
607 | 'ajax' : path('route_for_your_datatable_action'),
608 | 'columnDefs': [
609 | { "width": "15%", "targets": 1 }
610 | ]
611 | }
612 | })
613 | }}
614 | ```
615 |
616 | You really can play with all the DataTable's options.
617 |
618 |
--------------------------------------------------------------------------------
/Resources/config/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | %datatable%
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Resources/doc/images/sample_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/waldo2188/DatatableBundle/7ed6f754441a561caf1265d102144b15284a9e6a/Resources/doc/images/sample_01.png
--------------------------------------------------------------------------------
/Resources/doc/test.md:
--------------------------------------------------------------------------------
1 | Tests
2 | =====
3 |
4 | For launch the test suite follow the command below :
5 |
6 | ## Install the required vendors
7 |
8 | ```bash
9 | composer update
10 | ```
11 |
12 | ## Launch the test suite
13 |
14 | ```bash
15 | ./vendor/phpunit/phpunit/phpunit -c phpunit.xml.dist
16 | ```
17 |
--------------------------------------------------------------------------------
/Resources/translations/messages.ar.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: 'هل أنت متأكد ؟'
4 | you_need_to_select_at_least_one_element: 'تحتاج إلى تحديد عنصر واحد على الأقل.'
5 | search: 'بحث'
6 | execute: تنفيذ
7 | ok: حسنا
8 | datatable:
9 | searchPlaceholder: ""
10 | processing: "جاري التحميل..."
11 | lengthMenu: "أظهر مُدخلات _MENU_"
12 | zeroRecords: "لم يُعثر على أية سجلات"
13 | info: "إظهار _START_ إلى _END_ من أصل _TOTAL_ مُدخل"
14 | infoEmpty: "يعرض 0 إلى 0 من أصل 0 سجلّ"
15 | infoFiltered: "(منتقاة من مجموع _MAX_ مُدخل)"
16 | infoPostFix: ""
17 | search: "ابحث:"
18 | paginate:
19 | first: "الأول"
20 | previous: "السابق"
21 | next: "التالي"
22 | last: "الأخير"
23 | aria:
24 | sortAscending: "تفعيل لفرز العمود تصاعدي"
25 | sortDescending: "تفعيل لفرز العمود الهابطة"
26 |
--------------------------------------------------------------------------------
/Resources/translations/messages.cn.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: "你确定吗 ?"
4 | you_need_to_select_at_least_one_element: "您需要至少选择一个元素"
5 | search: "搜索"
6 | execute: "执"
7 | ok: "OK"
8 | datatable:
9 | searchPlaceholder: ""
10 | emptyTable: "表中数据为空"
11 | info: "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项"
12 | infoEmpty: "显示第 0 至 0 项结果,共 0 项"
13 | infoFiltered: "(由 _MAX_ 项结果过滤)"
14 | infoPostFix: ""
15 | infoThousands: ","
16 | lengthMenu: "显示 _MENU_ 项结果"
17 | loadingRecords: "载入中..."
18 | processing: "处理中..."
19 | search: "搜索:"
20 | zeroRecords: "没有匹配结果"
21 | paginate:
22 | first: "首页"
23 | last: "末页"
24 | next: "下页"
25 | previous: "上页"
26 | aria:
27 | sortAscending: ": 以升序排列此列"
28 | sortDescending: ": 以降序排列此列"
29 |
--------------------------------------------------------------------------------
/Resources/translations/messages.de.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: "Bist du sicher ?"
4 | you_need_to_select_at_least_one_element: "Sie müssen mindestens ein Element auswählen."
5 | search: Zoeken
6 | execute: "Ausführen"
7 | ok: "OK"
8 | datatable:
9 | searchPlaceholder: ""
10 | emptyTable: "Geen resultaten aanwezig in de tabel"
11 | info: "_START_ tot _END_ van _TOTAL_ resultaten"
12 | infoEmpty: "Geen resultaten om weer te geven"
13 | infoFiltered: " (gefilterd uit _MAX_ resultaten)"
14 | infoPostFix: ""
15 | infoThousands: "."
16 | lengthMenu: "_MENU_ resultaten weergeven"
17 | loadingRecords: "Een moment geduld aub - bezig met laden..."
18 | processing: "Bezig..."
19 | search: "Zoeken:"
20 | zeroRecords: "Geen resultaten gevonden"
21 | paginate:
22 | first: "Eerste"
23 | last: "Laatste"
24 | next: "Volgende"
25 | previous: "Vorige"
26 | aria:
27 | sortAscending: ": zu aktivieren, um Spalte aufsteigend zu sortieren"
28 | sortDescending: ": zu aktivieren, um Spalte absteigend sortieren"
29 |
--------------------------------------------------------------------------------
/Resources/translations/messages.en.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: Are you sure ?
4 | you_need_to_select_at_least_one_element: You need to select at least one element.
5 | search: Search
6 | execute: Execute
7 | ok: OK
8 | datatable:
9 | searchPlaceholder: ""
10 | emptyTable: "No data available in table"
11 | info: "Showing _START_ to _END_ of _TOTAL_ entries"
12 | infoEmpty: "Showing 0 to 0 of 0 entries"
13 | infoFiltered: "(filtered from _MAX_ total entries)"
14 | infoPostFix: ""
15 | infoThousands: ","
16 | lengthMenu: "Show _MENU_ entries"
17 | loadingRecords: "Loading..."
18 | processing: "Processing..."
19 | search: "Search:"
20 | zeroRecords: "No matching records found"
21 | paginate:
22 | first: "First"
23 | last: "Last"
24 | next: "Next"
25 | previous: "Previous"
26 | aria:
27 | sortAscending: ": activate to sort column ascending"
28 | sortDescending: ": activate to sort column descending"
29 |
30 |
--------------------------------------------------------------------------------
/Resources/translations/messages.es.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: Estas seguro ?
4 | you_need_to_select_at_least_one_element: Debe seleccionar al menos un elemento.
5 | search: Buscar
6 | execute: ""
7 | ok: "OK"
8 | datatable:
9 | searchPlaceholder: ""
10 | emptyTable: "Ningún dato disponible en esta tabla"
11 | info: "Mostrando registros del _START_ al _END_ de un total de _TOTAL_ registros"
12 | infoEmpty: "Mostrando registros del 0 al 0 de un total de 0 registros"
13 | infoFiltered: "(filtrado de un total de _MAX_ registros)"
14 | infoPostFix: ""
15 | infoThousands: ","
16 | lengthMenu: "Mostrar _MENU_ registros"
17 | loadingRecords: "Cargando..."
18 | processing: "Procesando..."
19 | search: "Buscar:"
20 | zeroRecords: "No se encontraron resultados"
21 | paginate:
22 | first: "Primero"
23 | last: "Último"
24 | next: "Siguiente"
25 | previous: "Anterior"
26 | aria:
27 | sortAscending: ": Activar para ordenar la columna de manera ascendente"
28 | sortDescending: ": Activar para ordenar la columna de manera descendente"
29 |
--------------------------------------------------------------------------------
/Resources/translations/messages.fr.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: Êtes-vous sûr ?
4 | you_need_to_select_at_least_one_element: Vous devez sélectionner au moins un élément.
5 | search: Rechercher
6 | execute: Exécuter
7 | ok: OK
8 | datatable:
9 | searchPlaceholder: ""
10 | processing: "Traitement en cours..."
11 | search: "Rechercher :"
12 | lengthMenu: "Afficher _MENU_ éléments"
13 | info: "Affichage de l'élément _START_ à _END_ sur _TOTAL_ éléments"
14 | infoEmpty: "Affichage de l'élément 0 à 0 sur 0 éléments"
15 | infoFiltered: "(filtré de _MAX_ éléments au total)"
16 | infoPostFix: ""
17 | infoThousands: "."
18 | loadingRecords: "Chargement en cours..."
19 | zeroRecords: "Aucun élément à afficher"
20 | emptyTable: "Aucune donnée disponible dans le tableau"
21 | paginate:
22 | first: "Premier"
23 | previous: "Précédent"
24 | next: "Suivant"
25 | last: "Dernier"
26 | aria:
27 | sortAscending: ": activer pour trier la colonne par ordre croissant"
28 | sortDescending: ": activer pour trier la colonne par ordre décroissant"
29 |
--------------------------------------------------------------------------------
/Resources/translations/messages.it.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: Sei sicuro ?
4 | you_need_to_select_at_least_one_element: È necessario selezionare almeno un elemento.
5 | search: Cerca
6 | execute: "Eseguire"
7 | ok: "OK"
8 | datatable:
9 | searchPlaceholder: ""
10 | emptyTable: "Nessun dato presente nella tabella"
11 | info: "Vista da _START_ a _END_ di _TOTAL_ elementi"
12 | infoEmpty: "Vista da 0 a 0 di 0 elementi"
13 | infoFiltered: "(filtrati da _MAX_ elementi totali)"
14 | infoPostFix: ""
15 | infoThousands: ","
16 | lengthMenu: "Visualizza _MENU_ elementi"
17 | loadingRecords: "Caricamento..."
18 | processing: "Elaborazione..."
19 | search: "Cerca:"
20 | zeroRecords: "La ricerca non ha portato alcun risultato."
21 | paginate:
22 | first: "Inizio"
23 | last: "Fine"
24 | next: "Successivo"
25 | previous: "Precedente"
26 | aria:
27 | sortAscending: ": attiva per ordinare la colonna in ordine crescente"
28 | sortDescending: ": attiva per ordinare la colonna in ordine decrescente"
29 |
--------------------------------------------------------------------------------
/Resources/translations/messages.pl.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: Jesteś pewny ?
4 | you_need_to_select_at_least_one_element: Musisz wybrać co najmniej jeden element.
5 | search: Szukaj
6 | execute: "Wykonać"
7 | ok: "OK"
8 | datatable:
9 | searchPlaceholder: ""
10 | emptyTable: "Brak danych"
11 | info: "Pozycje od _START_ do _END_ z _TOTAL_ łącznie"
12 | infoEmpty: "Pozycji 0 z 0 dostępnych"
13 | infoFiltered: "(filtrowanie spośród _MAX_ dostępnych pozycji)"
14 | infoPostFix: ""
15 | infoThousands: " "
16 | lengthMenu: "Pokaż _MENU_ pozycji"
17 | loadingRecords: "Wczytywanie..."
18 | processing: "Przetwarzanie..."
19 | search: "Szukaj:"
20 | zeroRecords: "Nie znaleziono pasujących pozycji"
21 | paginate:
22 | first: "Pierwsza"
23 | last: "Ostatnia"
24 | next: "Następna"
25 | previous: "Poprzednia"
26 | aria:
27 | sortAscending: ": aktywuj, by posortować kolumnę rosnąco"
28 | sortDescending: ": aktywuj, by posortować kolumnę malejąco"
29 |
--------------------------------------------------------------------------------
/Resources/translations/messages.ru.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: Вы уверены ?
4 | you_need_to_select_at_least_one_element: Вы должны выбрать хотя бы один элемент.
5 | search: Поиск
6 | execute: "выполнять"
7 | ok: "OK"
8 | datatable:
9 | searchPlaceholder: ""
10 | emptyTable: "В таблице отсутствуют данные"
11 | info: "Записи с _START_ до _END_ из _TOTAL_ записей"
12 | infoEmpty: "Записи с 0 до 0 из 0 записей"
13 | infoFiltered: "(отфильтровано из _MAX_ записей)"
14 | infoPostFix: ""
15 | infoThousands: ","
16 | lengthMenu: "Показать _MENU_ записей"
17 | loadingRecords: "Загрузка записей..."
18 | processing: "Подождите..."
19 | search: "Поиск:"
20 | zeroRecords: "Записи отсутствуют."
21 | paginate:
22 | first: "Первая"
23 | last: "Последняя"
24 | next: "Следующая"
25 | previous: "Предыдущая"
26 | aria:
27 | sortAscending: ": активировать для сортировки столбца по возрастанию"
28 | sortDescending: ": активировать для сортировки столбца по убыванию"
29 |
--------------------------------------------------------------------------------
/Resources/translations/messages.tr.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: Emin misiniz
4 | you_need_to_select_at_least_one_element: En az bir eleman seçmeniz gerekir.
5 | search: Bul
6 | execute: "Yürütme"
7 | ok: "OK"
8 | datatable:
9 | searchPlaceholder: ""
10 | emptyTable: ""
11 | info: " _TOTAL_ Kayıttan _START_ - _END_ Arası Kayıtlar"
12 | infoEmpty: "Kayıt Yok"
13 | infoFiltered: "( _MAX_ Kayıt İçerisinden Bulunan)"
14 | infoPostFix: ""
15 | infoThousands: ","
16 | lengthMenu: "Sayfada _MENU_ Kayıt Göster"
17 | loadingRecords: ""
18 | processing: "İşleniyor..."
19 | search: "Bul:"
20 | zeroRecords: "Eşleşen Kayıt Bulunmadı"
21 | paginate:
22 | first: "İlk"
23 | last: "Son"
24 | next: "Sonraki"
25 | previous: "Önceki"
26 | aria:
27 | sortAscending: ": activate to sort column ascending"
28 | sortDescending: ": activate to sort column descending"
29 |
30 |
--------------------------------------------------------------------------------
/Resources/translations/messages.ua.yml:
--------------------------------------------------------------------------------
1 | datatable:
2 | common:
3 | are_you_sure: Ти впевнений ?
4 | you_need_to_select_at_least_one_element: Ви повинні вибрати хоча б один елемент.
5 | search: Пошук
6 | execute: "виконувати"
7 | ok: "OK"
8 | datatable:
9 | searchPlaceholder: ""
10 | emptyTable: ""
11 | info: "Записи з _START_ по _END_ із _TOTAL_ записів"
12 | infoEmpty: "Записи з 0 по 0 із 0 записів"
13 | infoFiltered: "(відфільтровано з _MAX_ записів)"
14 | infoPostFix: ""
15 | infoThousands: ","
16 | lengthMenu: "Показати _MENU_ записів"
17 | loadingRecords: ""
18 | processing: "Зачекайте..."
19 | search: "Пошук:"
20 | zeroRecords: "Записи відсутні."
21 | paginate:
22 | first: "Перша"
23 | last: "Остання"
24 | next: "Наступна"
25 | previous: "Попередня"
26 | aria:
27 | sortAscending: ": активувати для сортування стовпців за зростанням"
28 | sortDescending: ": активувати для сортування стовпців за спаданням"
29 |
--------------------------------------------------------------------------------
/Resources/views/Main/datatableHtml.html.twig:
--------------------------------------------------------------------------------
1 | {% block main %}
2 | {% if multiple %}{% endif %}
40 | {% endblock %}
41 |
--------------------------------------------------------------------------------
/Resources/views/Main/datatableJs.html.twig:
--------------------------------------------------------------------------------
1 | {% block main %}
2 |
3 | {% spaceless %}
4 |
15 | {% endspaceless %}
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/Resources/views/Main/index.html.twig:
--------------------------------------------------------------------------------
1 | {% block main %}
2 | {% include "WaldoDatatableBundle:Main:datatableHtml.html.twig" %}
3 | {% include "WaldoDatatableBundle:Main:datatableJs.html.twig" %}
4 | {% endblock %}
5 |
--------------------------------------------------------------------------------
/Resources/views/Renderers/_default.html.twig:
--------------------------------------------------------------------------------
1 | {{dt_item}}
2 |
--------------------------------------------------------------------------------
/Resources/views/Snippet/individualSearchField.js.twig:
--------------------------------------------------------------------------------
1 | $('#{{ id }}').one('draw.dt', function() {
2 |
3 | var delay = (function(){
4 | var timer = 0;
5 | return function(callback, ms){
6 | clearTimeout (timer);
7 | timer = setTimeout(callback, ms);
8 | };
9 | })();
10 |
11 | $('#{{ id }}').DataTable().columns().every(function() {
12 |
13 | $(this.footer()).find('input[type="text"][searchable="true"]').each(function() {
14 | var me = this;
15 | $(this).on('keyup change', function() {
16 |
17 | column = $('#{{ id }}').DataTable().columns(me.getAttribute('index'));
18 |
19 | if ( column.search() !== me.value ) {
20 | delay(function(){
21 | column.search( me.value ).draw();
22 | }, 400);
23 | }
24 | });
25 | });
26 | });
27 |
28 | });
29 |
--------------------------------------------------------------------------------
/Resources/views/Snippet/multipleRaw.js.twig:
--------------------------------------------------------------------------------
1 | $('#{{ id }}').one('draw.dt', function() {
2 | var multiple_rawhtml = '';
3 | if((el = $('#{{ id }}_wrapper div[id^="{{ id }}"]')).length > 0 ) {
4 | $(el[0]).prepend(multiple_rawhtml);
5 | }
6 |
7 | {% set chbox %}input:checkbox[name="dataTables[actions][]"]{% endset %}
8 | {% set chboxAll %}input:checkbox[name="datatable_action_all"]{% endset %}
9 |
10 | $('#{{ id }}_wrapper .btn-datatable-multiple:not(.search_init)').on('click', function(e) {
11 |
12 | if($('input:focus', $('#{{ id }}_wrapper')).length > 0){
13 | return false;
14 | }
15 |
16 | e.preventDefault();
17 |
18 | if($('{{ chbox }}:checked').length > 0){
19 | if (!confirm('{% trans %}datatable.common.are_you_sure{% endtrans %}')) {
20 | return false;
21 | }
22 |
23 | var form = $(this).parents('form:eq(0)');
24 | var action = $('select[name="dataTables[select]"]',$('#{{ id }}_wrapper')).val();
25 |
26 | $.ajax({
27 | type: "POST",
28 | url: action,
29 | data: form.serialize(),
30 | success: function(msg) {
31 | $('#{{ id }}').trigger('dt.draw');
32 | }
33 | });
34 | } else {
35 | alert('{% trans %}datatable.common.you_need_to_select_at_least_one_element{% endtrans %}');
36 | }
37 | });
38 |
39 | $('#{{ id }}_wrapper').on('click', '{{ chboxAll }}', function() {
40 | if($(this).is(':checked')) {
41 | $('{{ chbox }}', $('#{{ id }}_wrapper')).attr("checked",false).click();
42 | } else {
43 | $('{{ chbox }}', $('#{{ id }}_wrapper')).attr("checked",true).click();
44 | }
45 | });
46 |
47 | $('#{{ id }}_wrapper {{ chboxAll }}').attr('checked', false);
48 | });
49 |
--------------------------------------------------------------------------------
/Tests/BaseClient.php:
--------------------------------------------------------------------------------
1 | getContainer()->get('doctrine.orm.default_entity_manager');
15 |
16 | $schemaTool = new \Doctrine\ORM\Tools\SchemaTool($em);
17 | $classes = array(
18 | $em->getClassMetadata("\Waldo\DatatableBundle\Tests\Functional\Entity\Product"),
19 | $em->getClassMetadata("\Waldo\DatatableBundle\Tests\Functional\Entity\Feature"),
20 | );
21 | $schemaTool->dropSchema($classes);
22 | $schemaTool->createSchema($classes);
23 |
24 | $this->insertData($em);
25 | }
26 |
27 | protected function insertData($em)
28 | {
29 | $p = new Product;
30 | $p->setName('Laptop')
31 | ->setPrice(1000)
32 | ->setDescription('New laptop');
33 | $em->persist($p);
34 |
35 | $p = new Product;
36 | $p->setName('Desktop')
37 | ->setPrice(5000)
38 | ->setDescription('New Desktop');
39 | $em->persist($p);
40 |
41 |
42 | $f = new Feature;
43 | $f->setName('CPU I7 Generation')
44 | ->setProduct($p);
45 |
46 | $f1 = new Feature;
47 | $f1->setName('SolidState drive')
48 | ->setProduct($p);
49 |
50 | $f2 = new Feature;
51 | $f2->setName('SLI graphic card ')
52 | ->setProduct($p);
53 |
54 |
55 | $em->persist($f);
56 | $em->persist($f1);
57 | $em->persist($f2);
58 |
59 | $em->flush();
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/Tests/Functional/Datatable/DatatableStaticTest.php:
--------------------------------------------------------------------------------
1 | initDatatable();
35 | }
36 |
37 | private function initDatatable($query = array())
38 | {
39 | if(count($query) > 0) {
40 | unset($this->client);
41 | unset($this->datatable);
42 | }
43 |
44 | $this->client = static::createClient();
45 | $this->buildDatabase($this->client);
46 |
47 | // Inject a fake request
48 | $requestStack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack');
49 |
50 | $requestStack
51 | ->expects($this->any())
52 | ->method('getCurrentRequest')
53 | ->willReturn(new Request($query));
54 |
55 | $this->client->getContainer()->set("request_stack", $requestStack);
56 |
57 | $this->em = $this->client->getContainer()->get('doctrine.orm.default_entity_manager');
58 |
59 | Datatable::clearInstance();
60 |
61 | $this->datatable = $this->client->getContainer()->get('datatable');
62 | }
63 |
64 | public function test_chainingClassBehavior()
65 | {
66 | $this->assertInstanceOf(
67 | '\Waldo\DatatableBundle\Util\Datatable',
68 | $this->datatable->setEntity('$entity_name', '$entity_alias')
69 | );
70 |
71 | $this->assertInstanceOf('\Waldo\DatatableBundle\Util\Datatable', $this->datatable->setFields(array()));
72 | $this->assertInstanceOf('\Waldo\DatatableBundle\Util\Datatable', $this->datatable->setFixedData('$data'));
73 | $this->assertInstanceOf('\Waldo\DatatableBundle\Util\Datatable', $this->datatable->setOrder('$order_field', '$order_type'));
74 |
75 | $this->assertInstanceOf('\Waldo\DatatableBundle\Util\Datatable',
76 | $this->datatable->setRenderer(function($value, $key) {
77 | return true;
78 | }));
79 | }
80 |
81 | public function test_addJoin()
82 | {
83 | $this->datatable
84 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
85 | ->addJoin('p.features', 'f');
86 |
87 | /* @var $qb \Doctrine\ORM\QueryBuilder */
88 | $qb = $this->datatable->getQueryBuilder()->getDoctrineQueryBuilder();
89 | $parts = $qb->getDQLParts();
90 | $this->assertNotEmpty($parts['join']);
91 | $this->assertTrue(array_key_exists('p', $parts['join']));
92 | }
93 |
94 | public function test_addJoinWithCondition()
95 | {
96 | $this->datatable
97 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
98 | ->addJoin('p.features', 'f', \Doctrine\ORM\Query\Expr\Join::INNER_JOIN, 'p.id = 1');
99 |
100 | /* @var $qb \Doctrine\ORM\QueryBuilder */
101 | $qb = $this->datatable->getQueryBuilder()->getDoctrineQueryBuilder();
102 | $parts = $qb->getDQLParts();
103 | $this->assertNotEmpty($parts['join']);
104 | $this->assertTrue(array_key_exists('p', $parts['join']));
105 | }
106 |
107 |
108 | public function test_execute()
109 | {
110 | $r = $this->datatable
111 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
112 | ->setFields(
113 | array(
114 | "title" => 'p.name',
115 | "_identifier_" => 'p.id')
116 | )
117 | ->execute();
118 |
119 | $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $r);
120 | }
121 |
122 | public function test_executeWithGroupBy()
123 | {
124 | $r = $this->datatable
125 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
126 | ->setFields(
127 | array(
128 | "title" => 'p.name',
129 | "_identifier_" => 'p.id')
130 | )
131 | ->setGroupBy("p.id")
132 | ->execute();
133 |
134 | $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $r);
135 | }
136 |
137 | public function test_executeWithFixedData()
138 | {
139 | $r = $this->datatable
140 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
141 | ->setFields(
142 | array(
143 | "title" => 'p.name',
144 | "_identifier_" => 'p.id')
145 | )
146 | ->setFixedData(array("plop"))
147 | ->execute();
148 |
149 | $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $r);
150 | }
151 |
152 | public function test_executeWithMultiple()
153 | {
154 | $r = $this->datatable
155 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
156 | ->setFields(
157 | array(
158 | "title" => 'p.name',
159 | "_identifier_" => 'p.id')
160 | )
161 | ->setMultiple(array('delete' => array ('title' => "Delete", 'route' => 'route_to_delete')))
162 | ->execute();
163 |
164 | $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $r);
165 | }
166 |
167 | public function test_getInstance()
168 | {
169 | $this->datatable
170 | ->setDatatableId('test')
171 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
172 | ->setFields(
173 | array(
174 | "title" => 'p.name',
175 | "_identifier_" => 'p.id')
176 | );
177 | $i = $this->datatable->getInstance('test');
178 | $this->assertInstanceOf('\Waldo\DatatableBundle\Util\Datatable', $i);
179 | $this->assertEquals('p', $i->getEntityAlias());
180 | }
181 |
182 | public function test_getInstanceWithFaketId()
183 | {
184 | $this->datatable
185 |
186 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
187 | ->setFields(
188 | array(
189 | "title" => 'p.name',
190 | "_identifier_" => 'p.id')
191 | );
192 |
193 | $i = $this->datatable->getInstance("fake");
194 |
195 | $this->assertInstanceOf('\Waldo\DatatableBundle\Util\Datatable', $i);
196 | $this->assertEquals('p', $i->getEntityAlias());
197 | }
198 |
199 | public function test_clearInstance()
200 | {
201 | $this->datatable->setDatatableId('fake1')
202 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
203 | ->setFields(
204 | array(
205 | "title" => 'p.name',
206 | "_identifier_" => 'p.id')
207 | );
208 |
209 | Datatable::clearInstance();
210 | }
211 |
212 | /**
213 | * @expectedException Exception
214 | * @expectedExceptionMessage Identifer already exists
215 | */
216 | public function test_setDatatableIdException()
217 | {
218 | $this->datatable->setDatatableId('fake1');
219 | $this->datatable->setDatatableId('fake1');
220 | }
221 |
222 | public function test_queryBuilder()
223 | {
224 | $qb = $this->getMockBuilder("Waldo\DatatableBundle\Util\Factory\Query\QueryInterface")->getMock();
225 |
226 |
227 | $this->datatable->setQueryBuilder($qb);
228 |
229 | $qbGet = $this->datatable->getQueryBuilder();
230 |
231 | $this->assertEquals($qb, $qbGet);
232 | }
233 |
234 | public function test_getEntityName()
235 | {
236 | $this->datatable
237 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
238 | ->setFields(
239 | array(
240 | "title" => 'p.name',
241 | "_identifier_" => 'p.id')
242 | );
243 | $this->assertEquals('Waldo\DatatableBundle\Tests\Functional\Entity\Product', $this->datatable->getEntityName());
244 | }
245 |
246 | public function test_getEntityAlias()
247 | {
248 | $this->datatable
249 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
250 | ->setFields(
251 | array(
252 | "title" => 'p.name',
253 | "_identifier_" => 'p.id')
254 | );
255 | $this->assertEquals('p', $this->datatable->getEntityAlias());
256 | }
257 |
258 | public function test_getFields()
259 | {
260 | $this->datatable
261 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
262 | ->setFields(
263 | array(
264 | "title" => 'p.name',
265 | "_identifier_" => 'p.id')
266 | );
267 | $this->assertInternalType('array', $this->datatable->getFields());
268 | }
269 |
270 | public function test_getOrderField()
271 | {
272 | $this->datatable
273 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
274 | ->setFields(
275 | array(
276 | "title" => 'p.name',
277 | "_identifier_" => 'p.id'))
278 | ->setOrder('p.id', 'asc')
279 | ;
280 | $this->assertInternalType('string', $this->datatable->getOrderField());
281 | }
282 |
283 | public function test_getOrderFieldWithAlias()
284 | {
285 | $this->initDatatable(array("iSortCol_0" => 0));
286 |
287 |
288 | $data = $this->datatable
289 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
290 | ->setFields(
291 | array(
292 | "title" => "(SELECT Product.name
293 | FROM \Waldo\DatatableBundle\Tests\Functional\Entity\Product as Product
294 | WHERE Product.id = 1) as someAliasName",
295 | "_identifier_" => 'p.id'))
296 | ->getQueryBuilder()->getData(null);
297 |
298 | $this->assertEquals("Laptop", $data[0][0][0]);
299 | }
300 |
301 | public function test_getOrderType()
302 | {
303 | $this->datatable
304 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
305 | ->setFields(
306 | array(
307 | "title" => 'p.name',
308 | "_identifier_" => 'p.id'))
309 | ->setOrder('p.id', 'asc')
310 | ;
311 | $this->assertInternalType('string', $this->datatable->getOrderType());
312 | }
313 |
314 | public function test_getQueryBuilder()
315 | {
316 | $this->datatable
317 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
318 | ->setFields(
319 | array(
320 | "title" => 'p.name',
321 | "_identifier_" => 'p.id'))
322 | ->setOrder('p.id', 'asc')
323 | ;
324 | $this->assertInstanceOf('Waldo\DatatableBundle\Util\Factory\Query\DoctrineBuilder', $this->datatable->getQueryBuilder());
325 | }
326 |
327 | public function test_alias()
328 | {
329 | $r = $this->datatable
330 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
331 | ->setFields(
332 | array(
333 | "title" => 'p.name as someAliasName',
334 | "_identifier_" => 'p.id')
335 | )->getQueryBuilder()->getData(null);
336 |
337 |
338 | $this->assertArrayHasKey("someAliasName", $r[1][0]);
339 | }
340 |
341 | public function test_multipleAlias()
342 | {
343 | $r = $this->datatable
344 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
345 | ->setFields(
346 | array(
347 | "title" => "(SELECT Product.name
348 | FROM \Waldo\DatatableBundle\Tests\Functional\Entity\Product as Product
349 | WHERE Product.id = 1) as someAliasName",
350 | "_identifier_" => 'p.id')
351 | )->getQueryBuilder()->getData(null);
352 |
353 |
354 | $this->assertArrayHasKey("someAliasName", $r[1][0]);
355 | }
356 |
357 | public function test_setWhere()
358 | {
359 | $r = $this->datatable
360 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
361 | ->setFields(
362 | array(
363 | "title" => "(SELECT Product.name
364 | FROM \Waldo\DatatableBundle\Tests\Functional\Entity\Product as Product
365 | WHERE Product.id = 1) as someAliasName",
366 | "_identifier_" => 'p.id')
367 | )
368 | ->setWhere("thisIsAWhere")
369 | ->getQueryBuilder()->getDoctrineQueryBuilder()->getDQL();
370 |
371 | $this->assertContains("thisIsAWhere", $r);
372 | }
373 |
374 | public function test_setGroupBy()
375 | {
376 | $r = $this->datatable
377 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
378 | ->setFields(
379 | array(
380 | "title" => "(SELECT Product.name
381 | FROM \Waldo\DatatableBundle\Tests\Functional\Entity\Product as Product
382 | WHERE Product.id = 1) as someAliasName",
383 | "_identifier_" => 'p.id')
384 | )
385 | ->setGroupBy("thisIsAGroupBy")
386 | ->getQueryBuilder()->getDoctrineQueryBuilder()->getDQL();
387 |
388 | $this->assertContains("thisIsAGroupBy", $r);
389 | }
390 |
391 | public function test_multiple()
392 | {
393 | $expectedArray = array(
394 | 'add' => array('title' => "Add", 'route' => 'route_to_add'),
395 | 'delete' => array('title' => "Delete", 'route' => 'route_to_delete'),
396 | );
397 |
398 | $this->datatable
399 | ->setMultiple($expectedArray);
400 |
401 |
402 | $this->assertEquals($expectedArray, $this->datatable->getMultiple());
403 | }
404 |
405 | public function test_getConfig()
406 | {
407 | $c = $this->datatable->getConfiguration();
408 |
409 | $this->assertTrue(is_array($c));
410 | }
411 |
412 | public function test_searchFields()
413 | {
414 | $expectedArray = array("field 1", "field 2");
415 |
416 | $this->datatable->setSearchFields($expectedArray);
417 |
418 | $this->assertEquals($expectedArray, $this->datatable->getSearchFields());
419 | }
420 |
421 | public function test_notFilterableFields()
422 | {
423 | $expectedArray = array("field 1", "field 2");
424 |
425 | $this->datatable->setNotFilterableFields($expectedArray);
426 |
427 | $this->assertEquals($expectedArray, $this->datatable->getNotFilterableFields());
428 | }
429 |
430 | public function test_notSortableFields()
431 | {
432 | $expectedArray = array("field 1", "field 2");
433 |
434 | $this->datatable->setNotSortableFields($expectedArray);
435 |
436 | $this->assertEquals($expectedArray, $this->datatable->getNotSortableFields());
437 | }
438 |
439 | public function test_hiddenFields()
440 | {
441 | $expectedArray = array("field 1", "field 2");
442 |
443 | $this->datatable->setHiddenFields($expectedArray);
444 |
445 | $this->assertEquals($expectedArray, $this->datatable->getHiddenFields());
446 | }
447 |
448 | public function test_filteringType()
449 | {
450 | $this->initDatatable(array(
451 | "search" => array("regex" => "false", "value" => "desktop"),
452 | "columns" => array(
453 | 0 => array(
454 | "searchable" => "true",
455 | "search" => array("regex" => "false", "value" => "")
456 | ),
457 | 1 => array(
458 | "searchable" => "true",
459 | "search" => array("regex" => "false", "value" => "")
460 | ),
461 | 2 => array(
462 | "searchable" => "true",
463 | "search" => array("regex" => "false", "value" => "")
464 | ),
465 | 3 => array(
466 | "searchable" => "true",
467 | "search" => array("regex" => "false", "value" => "")
468 | )
469 | )
470 | ));
471 |
472 | /* @var $res \Symfony\Component\HttpFoundation\JsonResponse */
473 | $res = $this->datatable
474 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
475 | ->setFields(array(
476 | "name" => "p.name",
477 | "price" => "p.price",
478 | "description" => "p.description",
479 | "_identifier_" => "p.id"
480 | ))
481 | ->setFilteringType(array(
482 | 0 => "s",
483 | 1 => "f",
484 | 2 => "b",
485 | ))
486 | ->setSearch(true)
487 | ->execute();
488 |
489 | $res = json_decode($res->getContent());
490 |
491 | $this->assertEquals(1, $res->recordsFiltered);
492 | $this->assertEquals(2, $res->recordsTotal);
493 | $this->assertEquals("Desktop", $res->data[0][0]);
494 | }
495 |
496 | public function test_SQLCommandInFields()
497 | {
498 | $datatable = $this->datatable
499 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
500 | ->setFields(
501 | array(
502 | "total" => 'COUNT(p.id) as total',
503 | "_identifier_" => 'p.id')
504 | );
505 |
506 | /* @var $qb \Doctrine\ORM\QueryBuilder */
507 | $qb = $datatable->getQueryBuilder()->getDoctrineQueryBuilder();
508 | $qb->groupBy('p.id');
509 |
510 | $r = $datatable->getQueryBuilder()->getData(null);
511 |
512 | $this->assertArrayHasKey("total", $r[1][0]);
513 | $this->assertEquals(1, $r[0][0][1]);
514 | $this->assertEquals(1, $r[1][0]['total']);
515 | }
516 |
517 | public function test_getSearch()
518 | {
519 | $this->datatable
520 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
521 | ->setFields(
522 | array(
523 | "title" => 'p.name',
524 | "_identifier_" => 'p.id'))
525 | ->setOrder('p.id', 'asc')
526 | ;
527 |
528 | $this->assertInternalType('boolean', $this->datatable->getSearch());
529 | }
530 |
531 | public function test_getSearchWithSubQuery()
532 | {
533 | $this->initDatatable(array(
534 | "search" => array("regex" => "false", "value" => "Laptop"),
535 | "columns" => array(
536 | 0 => array(
537 | "searchable" => "true",
538 | "search" => array("regex" => "false", "value" => "")
539 | ),
540 | 1 => array(
541 | "searchable" => "true",
542 | "search" => array("regex" => "false", "value" => "")
543 | ),
544 | 2 => array(
545 | "searchable" => "true",
546 | "search" => array("regex" => "false", "value" => "")
547 | ),
548 | 3 => array(
549 | "searchable" => "true",
550 | "search" => array("regex" => "false", "value" => "")
551 | )
552 | )
553 | ));
554 |
555 | $this->datatable
556 | ->setEntity('Waldo\DatatableBundle\Tests\Functional\Entity\Product', 'p')
557 | ->setFields(
558 | array(
559 | "title" => "(SELECT Product.name
560 | FROM Waldo\DatatableBundle\Tests\Functional\Entity\Product as Product
561 | WHERE Product.id = p.id) as someAliasName",
562 | "id" => 'p.id',
563 | "_identifier_" => 'p.id')
564 | )
565 | ->setSearch(true)
566 | ;
567 |
568 | $data = $this->datatable->execute();
569 |
570 | $this->assertEquals('{"draw":0,"recordsTotal":"2","recordsFiltered":"1","data":[["Laptop",1,1]]}', $data->getContent());
571 | }
572 |
573 | public function test_setRenderders()
574 | {
575 | $tpl = new \SplFileInfo(__DIR__ . '/../../app/Resources/views/Renderers/_actions.html.twig');
576 |
577 | $out = $this->datatable
578 | ->setEntity('\Waldo\DatatableBundle\Tests\Functional\Entity\Feature', 'f')
579 | ->setFields(
580 | array(
581 | "title" => 'f.name',
582 | "_identifier_" => 'f.id')
583 | )
584 | ->setRenderers(
585 | array(
586 | 1 => array(
587 | 'view' => $tpl->getRealPath(),
588 | 'params' => array(
589 | 'edit_route' => '_edit',
590 | 'delete_route' => '_delete'
591 | ),
592 | ),
593 | )
594 | )
595 | ->execute()
596 | ;
597 | $json = (array) json_decode($out->getContent());
598 | $this->assertContains('form', $json['data'][0][1]);
599 | }
600 |
601 | public function test_setRenderer()
602 | {
603 | $datatable = $this->datatable;
604 |
605 | $templating = $this->client->getContainer()->get('templating');
606 | $out = $datatable
607 | ->setEntity('\Waldo\DatatableBundle\Tests\Functional\Entity\Feature', 'f')
608 | ->setFields(
609 | array(
610 | "title" => 'f.name',
611 | "_identifier_" => 'f.id')
612 | )
613 | ->setRenderer(
614 | function(&$data) use ($templating, $datatable) {
615 |
616 | $tpl = new \SplFileInfo(__DIR__ . '/../../app/Resources/views/Renderers/_actions.html.twig');
617 |
618 | foreach ($data as $key => $value)
619 | {
620 | if ($key == 1) // 1 => adress field
621 | {
622 | $data[$key] = $templating
623 | ->render($tpl->getRealPath(), array(
624 | 'edit_route' => '_edit',
625 | 'delete_route' => '_delete'
626 | )
627 | );
628 | }
629 | }
630 | }
631 | )
632 | ->execute()
633 | ;
634 | $json = (array) json_decode($out->getContent());
635 | $this->assertContains('form', $json['data'][0][1]);
636 | }
637 |
638 | public function test_globalSearch()
639 | {
640 | $this->datatable->setGlobalSearch(true);
641 | $this->assertTrue($this->datatable->getGlobalSearch());
642 | }
643 |
644 | }
645 |
--------------------------------------------------------------------------------
/Tests/Functional/Datatable/DoctrineBuilderTest.php:
--------------------------------------------------------------------------------
1 | initDatatable();
36 | }
37 |
38 | private function initDatatable($query = array())
39 | {
40 | if (count($query) > 0) {
41 | unset($this->client);
42 | unset($this->datatable);
43 | }
44 |
45 | $this->client = static::createClient();
46 | $this->buildDatabase($this->client);
47 |
48 | // Inject a fake request
49 | $requestStack = $this->getMock("Symfony\Component\HttpFoundation\RequestStack");
50 |
51 | $requestStack
52 | ->expects($this->any())
53 | ->method('getCurrentRequest')
54 | ->willReturn(new Request($query));
55 |
56 | $this->client->getContainer()->set("request_stack", $requestStack);
57 |
58 | $this->em = $this->client->getContainer()->get('doctrine.orm.default_entity_manager');
59 |
60 | Datatable::clearInstance();
61 |
62 | $this->datatable = $this->client->getContainer()->get('datatable');
63 | }
64 |
65 | public function providerAddSearch()
66 | {
67 | return array(
68 | array("s", "Laptop"),
69 | array("f", "pto"),
70 | array("b", "ptop"),
71 | array("e", "Lap"),
72 | array(null, "Lap"),
73 | );
74 | }
75 |
76 | /**
77 | * @dataProvider providerAddSearch
78 | */
79 | public function test_addSearch($searchType, $searchString)
80 | {
81 | $query = array(
82 | "search" => array("regex" => "false", "value" => $searchString),
83 | "columns" => array(
84 | 0 => array(
85 | "searchable" => "true",
86 | "search" => array("regex" => "false", "value" => "")
87 | ),
88 | 1 => array(
89 | "searchable" => "true",
90 | "search" => array("regex" => "false", "value" => "")
91 | ),
92 | 2 => array(
93 | "searchable" => "true",
94 | "search" => array("regex" => "false", "value" => "")
95 | ),
96 | )
97 | );
98 |
99 | $this->initDatatable($query);
100 |
101 | $requestStack = $this->getRequestStackMock();
102 | $requestStack->expects($this->any())
103 | ->method("getCurrentRequest")
104 | ->willReturn(new Request($query));
105 |
106 | $doctrineBuilder = new DoctrineBuilder($this->em, $requestStack);
107 |
108 | $doctrineBuilder->setFields(array(
109 | "name" => "p.name",
110 | "description" => "p.description",
111 | "price" => "p.price",
112 | "_identifier_" => "p.id"
113 | ))
114 | ->setEntity("Waldo\DatatableBundle\Tests\Functional\Entity\Product", "p")
115 | ->setSearch(true)
116 | ;
117 |
118 | if($searchType !== null) {
119 | $doctrineBuilder->setFilteringType(array(
120 | 0 => $searchType
121 | ));
122 | }
123 |
124 | $res = $doctrineBuilder->getData();
125 |
126 | $this->assertCount(1, $res[0]);
127 | $this->assertEquals("Laptop", $res[0][0][0]);
128 | }
129 |
130 | /**
131 | * @dataProvider providerAddSearch
132 | */
133 | public function test_addSearchWithoutColumns($searchType, $searchString)
134 | {
135 | $query = array(
136 | "search" => array("regex" => "false", "value" => $searchString)
137 | );
138 |
139 | $this->initDatatable($query);
140 |
141 | $requestStack = $this->getRequestStackMock();
142 | $requestStack->expects($this->any())
143 | ->method("getCurrentRequest")
144 | ->willReturn(new Request($query));
145 |
146 | $doctrineBuilder = new DoctrineBuilder($this->em, $requestStack);
147 |
148 | $doctrineBuilder->setFields(array(
149 | "name" => "p.name",
150 | "description" => "p.description",
151 | "price" => "p.price",
152 | "_identifier_" => "p.id"
153 | ))
154 | ->setEntity("Waldo\DatatableBundle\Tests\Functional\Entity\Product", "p")
155 | ->setSearch(true)
156 | ;
157 |
158 | if($searchType !== null) {
159 | $doctrineBuilder->setFilteringType(array(
160 | 0 => $searchType
161 | ));
162 | }
163 |
164 | $res = $doctrineBuilder->getData();
165 |
166 | $this->assertGreaterThan(1, $res[0]);
167 | }
168 |
169 | public function test_setDoctrineQueryBuilder()
170 | {
171 | $this->initDatatable();
172 |
173 | $requestStack = $this->getRequestStackMock();
174 |
175 | $doctrineBuilder = new DoctrineBuilder($this->em, $requestStack);
176 |
177 | $qbMock = $this->getMockBuilder("Doctrine\ORM\QueryBuilder")
178 | ->disableOriginalConstructor()
179 | ->getMock();
180 |
181 | $res = $doctrineBuilder->setDoctrineQueryBuilder($qbMock);
182 |
183 | $this->assertEquals($qbMock, $res->getDoctrineQueryBuilder());
184 | }
185 |
186 | private function getRequestStackMock()
187 | {
188 | return $this->getMockBuilder("Symfony\Component\HttpFoundation\RequestStack")
189 | ->disableOriginalConstructor()
190 | ->getMock();
191 | }
192 |
193 | }
194 |
--------------------------------------------------------------------------------
/Tests/Functional/Entity/Feature.php:
--------------------------------------------------------------------------------
1 | id;
35 | }
36 |
37 | public function setId($id)
38 | {
39 | $this->id = $id;
40 | return $this;
41 | }
42 |
43 | public function getName()
44 | {
45 | return $this->name;
46 | }
47 |
48 | public function setName($name)
49 | {
50 | $this->name = $name;
51 | return $this;
52 | }
53 |
54 | public function getProduct()
55 | {
56 | return $this->product;
57 | }
58 |
59 | public function setProduct($product)
60 | {
61 | $this->product = $product;
62 | return $this;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Tests/Functional/Entity/Product.php:
--------------------------------------------------------------------------------
1 | features = new \Doctrine\Common\Collections\ArrayCollection();
44 | }
45 |
46 | public function getId()
47 | {
48 | return $this->id;
49 | }
50 |
51 | public function setId($id)
52 | {
53 | $this->id = $id;
54 | return $this;
55 | }
56 |
57 | public function getName()
58 | {
59 | return $this->name;
60 | }
61 |
62 | public function setName($name)
63 | {
64 | $this->name = $name;
65 | return $this;
66 | }
67 |
68 | public function getPrice()
69 | {
70 | return $this->price;
71 | }
72 |
73 | public function setPrice($price)
74 | {
75 | $this->price = $price;
76 | return $this;
77 | }
78 |
79 | public function getDescription()
80 | {
81 | return $this->description;
82 | }
83 |
84 | public function setDescription($description)
85 | {
86 | $this->description = $description;
87 | return $this;
88 | }
89 |
90 | public function getFeatures()
91 | {
92 | return $this->features;
93 | }
94 |
95 | public function setFeatures($features)
96 | {
97 | $this->features = $features;
98 | return $this;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Tests/Unit/DependencyInjection/WaldoDatatableExtensionTest.php:
--------------------------------------------------------------------------------
1 | extension = new WaldoDatatableExtension();
26 |
27 | $this->container = new ContainerBuilder();
28 | $this->container->registerExtension($this->extension);
29 | }
30 |
31 | public function testWithoutConfiguration()
32 | {
33 | $this->container->loadFromExtension($this->extension->getAlias());
34 | $this->extension->load(array(), $this->container);
35 |
36 | $this->assertTrue($this->container->hasDefinition("datatable"));
37 | $this->assertTrue($this->container->hasDefinition("datatable.renderer"));
38 | $this->assertTrue($this->container->hasDefinition("datatable.twig.extension"));
39 | $this->assertTrue($this->container->hasDefinition("datatable.kernel.listener.terminate"));
40 | $this->assertArrayHasKey("datatable", $this->container->getParameterBag()->all());
41 | }
42 |
43 | public function testWithYamlConfig()
44 | {
45 | $configYaml = <<parse($configYaml);
63 |
64 | $this->container->loadFromExtension($this->extension->getAlias());
65 | $this->extension->load($config, $this->container);
66 |
67 | $this->assertArrayHasKey("datatable", $this->container->getParameterBag()->all());
68 |
69 | $parseConfig = $this->container->getParameterBag()->get("datatable");
70 |
71 | $this->assertEquals("[[5,10, 25, 50, -1], [5,10, 25, 50, 'All']]", $parseConfig['js']['aLengthMenu']);
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/Tests/Unit/Listner/KernelTerminateListenerTest.php:
--------------------------------------------------------------------------------
1 | getMockBuilder("Doctrine\ORM\EntityManager")->disableOriginalConstructor()->getMock(),
18 | $this->getMockBuilder("Symfony\Component\HttpFoundation\RequestStack")->disableOriginalConstructor()->getMock(),
19 | $this->getMockBuilder("Waldo\DatatableBundle\Util\Factory\Query\DoctrineBuilder")->disableOriginalConstructor()->getMock(),
20 | $this->getMockBuilder("Waldo\DatatableBundle\Util\Formatter\Renderer")->disableOriginalConstructor()->getMock(),
21 | array("js" => array())
22 | );
23 |
24 | $dt->setDatatableId("testOnKernelTerminate");
25 |
26 | $listner = new KernelTerminateListener();
27 |
28 | $listner->onKernelTerminate();
29 |
30 | $this->assertFalse($dt->hasInstanceId("testOnKernelTerminate"));
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/Tests/Unit/Twig/DatatableTwigExtensionTest.php:
--------------------------------------------------------------------------------
1 | translator = $this->getMockBuilder("Symfony\Component\Translation\DataCollectorTranslator")
25 | ->disableOriginalConstructor()
26 | ->getMock();
27 |
28 |
29 |
30 | $this->extentsion = new DatatableExtension($this->translator);
31 | }
32 |
33 | public function testGetName()
34 | {
35 | $this->assertEquals("DatatableBundle", $this->extentsion->getName());
36 | }
37 |
38 |
39 | public function testGetFunctions()
40 | {
41 | /* @var $functions array<\Twig_SimpleFunction> */
42 | $functions = $this->extentsion->getFunctions();
43 |
44 | $this->assertEquals("datatable", $functions[0]->getName());
45 | $this->assertEquals("datatable_html", $functions[1]->getName());
46 | $this->assertEquals("datatable_js", $functions[2]->getName());
47 | }
48 |
49 | public function testDatatable()
50 | {
51 | $dbMock = $this->getMockBuilder("Waldo\DatatableBundle\Util\Factory\Query\DoctrineBuilder")
52 | ->disableOriginalConstructor()
53 | ->getMock();
54 |
55 | $dbMock->expects($this->any())
56 | ->method("getOrderField")
57 | ->willReturn("oHYes");
58 |
59 | $dbMock->expects($this->any())
60 | ->method("getFields")
61 | ->willReturn(array());
62 |
63 | $dt = $this->getDatatable();
64 | $dt->setQueryBuilder($dbMock);
65 | $dt->setDatatableId("testDatatable");
66 |
67 | $twig = $this->getMock("\Twig_Environment");
68 | $twig->expects($this->once())
69 | ->method("render")
70 | ->with($this->equalTo("WaldoDatatableBundle:Main:index.html.twig"))
71 | ->willReturn("OK");
72 |
73 | $res = $this->extentsion->datatable($twig, array(
74 | "id" => "testDatatable",
75 | "js" => array(),
76 | "action" => "",
77 | "action_twig" => "",
78 | "fields" => "",
79 | "delete_form" => "",
80 | "search" => "",
81 | "global_search" => "",
82 | "searchFields" => "",
83 | "multiple" => "",
84 | "sort" => ""
85 | ));
86 |
87 | $this->assertEquals("OK", $res);
88 | }
89 |
90 | public function testDatatableJs()
91 | {
92 | $dt = $this->getDatatable();
93 | $dt->setDatatableId("testDatatableJs");
94 |
95 | $twig = $this->getMock("\Twig_Environment");
96 | $twig->expects($this->any())
97 | ->method("render")
98 | ->with($this->equalTo("WaldoDatatableBundle:Main:datatableJs.html.twig"))
99 | ->willReturnArgument(1);
100 |
101 | $configRow = array(
102 | "js" => array(
103 | 'dom'=> "<'row'<'span6'fr>>t<'row'<'span7'il><'span5 align-right'p>>",
104 | 'ajax'=> "urlDatatable"
105 | ),
106 | "action" => "",
107 | "action_twig" => "",
108 | "fields" => "",
109 | "delete_form" => "",
110 | "search" => "",
111 | "global_search" => "",
112 | "searchFields" => "",
113 | "multiple" => "",
114 | "sort" => ""
115 | );
116 |
117 | $res = $this->extentsion->datatableJs($twig, $configRow);
118 |
119 |
120 | $this->assertEquals("<'row'<'span6'fr>>t<'row'<'span7'il><'span5 align-right'p>>", $res['js']['dom']);
121 | $this->assertEquals("urlDatatable", $res['js']['ajax']['url']);
122 | $this->assertTrue($res['js']['paging']);
123 |
124 | $configRow["js"]['paging'] = false;
125 |
126 | $res = $this->extentsion->datatableJs($twig, $configRow);
127 |
128 | $this->assertFalse($res['js']['paging']);
129 | }
130 |
131 | public function testDatatableJsTranslation()
132 | {
133 | $dt = $this->getDatatable();
134 | $dt->setDatatableId("testDatatableJsTranslation");
135 |
136 | $twig = $this->getMock("\Twig_Environment");
137 | $twig->expects($this->once())
138 | ->method("render")
139 | ->with(
140 | $this->equalTo("WaldoDatatableBundle:Main:datatableJs.html.twig"),
141 | $this->callback(function($option){
142 |
143 | return $option['js']['language']["searchPlaceholder"] === "find Me" &&
144 | $option['js']['language']["paginate"]["first"] === "coucou" &&
145 | array_key_exists("next", $option['js']['language']["paginate"])
146 | ;
147 |
148 | })
149 | )
150 | ->willReturn("OK");
151 |
152 | $res = $this->extentsion->datatableJs($twig, array(
153 | "js" => array("language" => array(
154 | "searchPlaceholder" => "find Me",
155 | "paginate" => array(
156 | "first" => "coucou"
157 | )
158 | )),
159 | "action" => "",
160 | "action_twig" => "",
161 | "fields" => "",
162 | "delete_form" => "",
163 | "search" => "",
164 | "global_search" => "",
165 | "searchFields" => "",
166 | "multiple" => "",
167 | "sort" => ""
168 | ));
169 |
170 |
171 | $this->assertEquals("OK", $res);
172 | }
173 |
174 | public function testDatatableHtml()
175 | {
176 | $dt = $this->getDatatable();
177 | $dt->setDatatableId("testDatatableHtml");
178 |
179 | $twig = $this->getMock("\Twig_Environment");
180 | $twig->expects($this->once())
181 | ->method("render")
182 | ->with($this->equalTo("myHtmlTemplate"))
183 | ->willReturn("OK");
184 |
185 | $res = $this->extentsion->datatableHtml($twig, array(
186 | "html_template" => "myHtmlTemplate",
187 | "js" => array(),
188 | "action" => "",
189 | "action_twig" => "",
190 | "fields" => "",
191 | "delete_form" => "",
192 | "search" => "",
193 | "global_search" => "",
194 | "searchFields" => "",
195 | "multiple" => "",
196 | "sort" => ""
197 | ));
198 |
199 | $this->assertEquals("OK", $res);
200 | }
201 |
202 | private function getDatatable()
203 | {
204 | return new \Waldo\DatatableBundle\Util\Datatable(
205 | $this->getMockBuilder("Doctrine\ORM\EntityManager")->disableOriginalConstructor()->getMock(),
206 | $this->getMockBuilder("Symfony\Component\HttpFoundation\RequestStack")->disableOriginalConstructor()->getMock(),
207 | $this->getMockBuilder("Waldo\DatatableBundle\Util\Factory\Query\DoctrineBuilder")->disableOriginalConstructor()->getMock(),
208 | $this->getMockBuilder("Waldo\DatatableBundle\Util\Formatter\Renderer")->disableOriginalConstructor()->getMock(),
209 | array("js" => array(
210 | 'paging' => true
211 | ))
212 | );
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/Tests/app/AppKernel.php:
--------------------------------------------------------------------------------
1 | load(__DIR__ . '/config/config_test.yml');
21 | }
22 |
23 | /**
24 | * @return string
25 | */
26 | public function getCacheDir()
27 | {
28 | $cacheDir = sys_get_temp_dir() . '/cache';
29 | if (!is_dir($cacheDir)) {
30 | mkdir($cacheDir, 0777, true);
31 | }
32 |
33 | return $cacheDir;
34 | }
35 |
36 | /**
37 | * @return string
38 | */
39 | public function getLogDir()
40 | {
41 | $logDir = sys_get_temp_dir() . '/logs';
42 | if (!is_dir($logDir)) {
43 | mkdir($logDir, 0777, true);
44 | }
45 |
46 | return $logDir;
47 | }
48 | }
--------------------------------------------------------------------------------
/Tests/app/Resources/views/Renderers/_actions.html.twig:
--------------------------------------------------------------------------------
1 |
5 | edit
--------------------------------------------------------------------------------
/Tests/app/config/config_test.yml:
--------------------------------------------------------------------------------
1 | framework:
2 | secret: secret
3 | test: ~
4 | router: { resource: "%kernel.root_dir%/config/routing.yml" }
5 | form: true
6 | csrf_protection: true
7 | validation: { enable_annotations: true }
8 | templating: { engines: ['twig'] }
9 | session:
10 | storage_id: session.storage.filesystem
11 |
12 | doctrine:
13 | dbal:
14 | driver: pdo_sqlite
15 | host: ~
16 | port: ~
17 | dbname: datatableDB
18 | user: ~
19 | password: ~
20 | memory: true
21 | charset: utf8
22 | orm:
23 | auto_generate_proxy_classes: true
24 | # auto_mapping: true
25 | mappings:
26 | WaldoDatatableBundle:
27 | mapping: true
28 | type: annotation
29 | dir: 'Tests/Functional/Entity'
30 | alias: 'WaldoDatatableBundle'
31 | prefix: 'Waldo\DatatableBundle\Tests'
32 | is_bundle: false
33 |
--------------------------------------------------------------------------------
/Tests/app/config/routing.yml:
--------------------------------------------------------------------------------
1 | _homepage:
2 | path: /
3 | _delete:
4 | path: /
5 | _edit:
6 | path: /
7 |
--------------------------------------------------------------------------------
/Tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | translator = $translator;
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function getFunctions()
51 | {
52 | return array(
53 | new Twig_SimpleFunction('datatable', array($this, 'datatable'),
54 | array("is_safe" => array("html"), 'needs_environment' => true)),
55 | new Twig_SimpleFunction('datatable_html', array($this, 'datatableHtml'),
56 | array("is_safe" => array("html"), 'needs_environment' => true)),
57 | new Twig_SimpleFunction('datatable_js', array($this, 'datatableJs'),
58 | array("is_safe" => array("html"), 'needs_environment' => true))
59 | );
60 | }
61 |
62 | public function getFilters()
63 | {
64 | return array(
65 | new \Twig_SimpleFilter('printDatatableOption', array($this, 'printDatatableOption'),
66 | array("is_safe" => array("html")))
67 | );
68 | }
69 |
70 | /**
71 | * Converts a string to time
72 | *
73 | * @param string $string
74 | * @return int
75 | */
76 | public function datatable(\Twig_Environment $twig, $options)
77 | {
78 | $options = $this->buildDatatableTemplate($options);
79 |
80 | $mainTemplate = array_key_exists('main_template', $options) ? $options['main_template'] : 'WaldoDatatableBundle:Main:index.html.twig';
81 |
82 | return $twig->render($mainTemplate, $options);
83 | }
84 |
85 | /**
86 | * Converts a string to time
87 | *
88 | * @param string $string
89 | * @return int
90 | */
91 | public function datatableJs(\Twig_Environment $twig, $options)
92 | {
93 | $options = $this->buildDatatableTemplate($options, "js");
94 |
95 | $mainTemplate = array_key_exists('main_template', $options) ? $options['js_template'] : 'WaldoDatatableBundle:Main:datatableJs.html.twig';
96 |
97 | return $twig->render($mainTemplate, $options);
98 | }
99 |
100 | /**
101 | * Converts a string to time
102 | *
103 | * @param string $string
104 | * @return int
105 | */
106 | public function datatableHtml(\Twig_Environment $twig, $options)
107 | {
108 | if (!isset($options['id'])) {
109 | $options['id'] = 'ali-dta_' . md5(mt_rand(1, 100));
110 | }
111 | $dt = Datatable::getInstance($options['id']);
112 |
113 | $options['fields'] = $dt->getFields();
114 | $options['search'] = $dt->getSearch();
115 | $options['searchFields'] = $dt->getSearchFields();
116 | $options['multiple'] = $dt->getMultiple();
117 |
118 | $mainTemplate = 'WaldoDatatableBundle:Main:datatableHtml.html.twig';
119 |
120 | if (isset($options['html_template'])) {
121 | $mainTemplate = $options['html_template'];
122 | }
123 |
124 | return $twig->render($mainTemplate, $options);
125 | }
126 |
127 | private function buildDatatableTemplate($options, $type = null)
128 | {
129 | if (!isset($options['id'])) {
130 | $options['id'] = 'ali-dta_' . md5(mt_rand(1, 100));
131 | }
132 |
133 | $dt = Datatable::getInstance($options['id']);
134 |
135 | $config = $dt->getConfiguration();
136 |
137 | $options['js'] = array_merge($config['js'], $options['js']);
138 | $options['fields'] = $dt->getFields();
139 | $options['search'] = $dt->getSearch();
140 | $options['global_search'] = $dt->getGlobalSearch();
141 | $options['multiple'] = $dt->getMultiple();
142 | $options['searchFields'] = $dt->getSearchFields();
143 | $options['sort'] = $dt->getOrderField() === null ? null : array(
144 | array_search($dt->getOrderField(), array_values($dt->getFields())),
145 | $dt->getOrderType()
146 | );
147 |
148 | if ($type == "js") {
149 | $this->buildJs($options, $dt);
150 | }
151 |
152 | return $options;
153 | }
154 |
155 | private function buildJs(&$options, $dt)
156 | {
157 | if(array_key_exists("ajax", $options['js']) && !is_array($options['js']['ajax'])) {
158 | $options['js']['ajax'] = array(
159 | "url" => $options['js']['ajax'],
160 | "type" => "POST"
161 | );
162 | }
163 |
164 | if (count($dt->getHiddenFields()) > 0) {
165 | $options['js']['columnDefs'][] = array(
166 | "visible" => false,
167 | "targets" => $dt->getHiddenFields()
168 | );
169 | }
170 | if (count($dt->getNotSortableFields()) > 0) {
171 | $options['js']['columnDefs'][] = array(
172 | "orderable" => false,
173 | "targets" => $dt->getNotSortableFields()
174 | );
175 | }
176 | if (count($dt->getNotFilterableFields()) > 0) {
177 | $options['js']['columnDefs'][] = array(
178 | "searchable" => false,
179 | "targets" => $dt->getNotFilterableFields()
180 | );
181 | }
182 |
183 | $this->buildTranslation($options);
184 | }
185 |
186 | private function buildTranslation(&$options)
187 | {
188 | if(!array_key_exists("language", $options['js'])) {
189 | $options['js']['language'] = array();
190 | }
191 |
192 | $baseLanguage = array(
193 | "processing" => $this->translator->trans("datatable.datatable.processing"),
194 | "search"=> $this->translator->trans("datatable.datatable.search"),
195 | "lengthMenu"=> $this->translator->trans("datatable.datatable.lengthMenu"),
196 | "info"=> $this->translator->trans("datatable.datatable.info"),
197 | "infoEmpty"=> $this->translator->trans("datatable.datatable.infoEmpty"),
198 | "infoFiltered"=> $this->translator->trans("datatable.datatable.infoFiltered"),
199 | "infoPostFix"=> $this->translator->trans("datatable.datatable.infoPostFix"),
200 | "loadingRecords"=> $this->translator->trans("datatable.datatable.loadingRecords"),
201 | "zeroRecords"=> $this->translator->trans("datatable.datatable.zeroRecords"),
202 | "emptyTable"=> $this->translator->trans("datatable.datatable.emptyTable"),
203 | "searchPlaceholder" => $this->translator->trans("datatable.datatable.searchPlaceholder"),
204 | "paginate"=> array (
205 | "first"=> $this->translator->trans("datatable.datatable.paginate.first"),
206 | "previous"=> $this->translator->trans("datatable.datatable.paginate.previous"),
207 | "next"=> $this->translator->trans("datatable.datatable.paginate.next"),
208 | "last"=> $this->translator->trans("datatable.datatable.paginate.last")
209 | ),
210 | "aria"=> array(
211 | "sortAscending"=> $this->translator->trans("datatable.datatable.aria.sortAscending"),
212 | "sortDescending"=> $this->translator->trans("datatable.datatable.aria.sortDescending")
213 | ));
214 |
215 | $options['js']['language'] = $this->arrayMergeRecursiveDistinct($baseLanguage, $options['js']['language']);
216 | }
217 |
218 | public function printDatatableOption($var, $elementName)
219 | {
220 | if(is_bool($var)) {
221 | return $var === true ? 'true' : 'false';
222 | }
223 |
224 | if(is_array($var)) {
225 | return json_encode($var);
226 | }
227 |
228 | if(in_array($elementName, $this->callbackMethodName)) {
229 | return $var;
230 | }
231 |
232 | return json_encode($var);
233 | }
234 |
235 | /**
236 | * Returns the name of the extension.
237 | *
238 | * @return string The extension name
239 | */
240 | public function getName()
241 | {
242 | return 'DatatableBundle';
243 | }
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/Util/ArrayMerge.php:
--------------------------------------------------------------------------------
1 | 'org value'), array('key' => 'new value'));
17 | * => array('key' => array('org value', 'new value'));
18 | *
19 | * array_merge_recursive_distinct does not change the datatypes of the values in the arrays.
20 | * Matching keys' values in the second array overwrite those in the first array, as is the
21 | * case with array_merge, i.e.:
22 | *
23 | * arrayMergeRecursiveDistinct(array('key' => 'org value'), array('key' => 'new value'));
24 | * => array('key' => array('new value'));
25 | *
26 | * Parameters are passed by reference, though only for performance reasons. They're not
27 | * altered by this function.
28 | *
29 | * @param array $array1
30 | * @param array $array2
31 | * @return array
32 | * @author Daniel
33 | * @author Gabriel Sobrinho
34 | */
35 | public function arrayMergeRecursiveDistinct(array &$array1, array &$array2)
36 | {
37 | $merged = $array1;
38 | foreach ($array2 as $key => &$value) {
39 | if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
40 | $merged[$key] = $this->arrayMergeRecursiveDistinct($merged[$key], $value);
41 | } else {
42 | $merged[$key] = $value;
43 | }
44 | }
45 | return $merged;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Util/Datatable.php:
--------------------------------------------------------------------------------
1 | em = $entityManager;
126 | $this->request = $request;
127 | $this->queryBuilder = $doctrineBuilder;
128 | $this->rendererEngine = $renderer;
129 | $this->config = $config;
130 |
131 | self::$currentInstance = $this;
132 |
133 | $this->applyDefaults();
134 | }
135 |
136 | /**
137 | * apply default value from datatable config
138 | *
139 | * @return void
140 | */
141 | protected function applyDefaults()
142 | {
143 | if (isset($this->config['all'])) {
144 | $this->search = $this->config['all']['search'];
145 | }
146 | }
147 |
148 | /**
149 | * add join
150 | *
151 | * @example:
152 | * ->setJoin(
153 | * 'r.event',
154 | * 'e',
155 | * \Doctrine\ORM\Query\Expr\Join::INNER_JOIN,
156 | * \Doctrine\ORM\Query\Expr\Join::WITH,
157 | * 'e.name like %test%')
158 | *
159 | * @param string $join The relationship to join.
160 | * @param string $alias The alias of the join.
161 | * @param string|Join::INNER_JOIN $type The type of the join Join::INNER_JOIN | Join::LEFT_JOIN
162 | * @param string|null $conditionType The condition type constant. Either ON or WITH.
163 | * @param string|null $condition The condition for the join.
164 | *
165 | * @return Datatable
166 | */
167 | public function addJoin($join, $alias, $type = Join::INNER_JOIN, $conditionType = null, $condition = null)
168 | {
169 | $this->queryBuilder->addJoin($join, $alias, $type, $conditionType, $condition);
170 | return $this;
171 | }
172 |
173 | /**
174 | * execute
175 | *
176 | * @param int $hydrationMode
177 | *
178 | * @return JsonResponse
179 | */
180 | public function execute()
181 | {
182 | $request = $this->request->getCurrentRequest();
183 |
184 | $iTotalRecords = $this->queryBuilder->getTotalRecords();
185 | $iTotalDisplayRecords = $this->queryBuilder->getTotalDisplayRecords();
186 |
187 | list($data, $objects) = $this->queryBuilder->getData($this->multiple);
188 |
189 | $id_index = array_search('_identifier_', array_keys($this->getFields()));
190 | $ids = array();
191 |
192 | array_walk($data, function($val, $key) use ($id_index, &$ids) {
193 | $ids[$key] = $val[$id_index];
194 | });
195 |
196 | if (!is_null($this->fixedData)) {
197 | $this->fixedData = array_reverse($this->fixedData);
198 | foreach ($this->fixedData as $item) {
199 | array_unshift($data, $item);
200 | }
201 | }
202 |
203 | if (!is_null($this->renderer)) {
204 | array_walk($data, $this->renderer);
205 | }
206 |
207 | if (!is_null($this->rendererObj)) {
208 | $this->rendererObj->applyTo($data, $objects);
209 | }
210 |
211 | if (!empty($this->multiple)) {
212 | array_walk($data,
213 | function($val, $key) use(&$data, $ids) {
214 | array_unshift($val, sprintf('', $ids[$key]));
215 | $data[$key] = $val;
216 | });
217 | }
218 |
219 | $output = array(
220 | "draw" => $request->query->getInt('draw'),
221 | "recordsTotal" => $iTotalRecords,
222 | "recordsFiltered" => $iTotalDisplayRecords,
223 | "data" => $data
224 | );
225 |
226 | return new JsonResponse($output);
227 | }
228 |
229 | /**
230 | * get datatable instance by id
231 | * return current instance if null
232 | *
233 | * @param string $id
234 | *
235 | * @return Datatable .
236 | */
237 | public static function getInstance($id)
238 | {
239 | $instance = null;
240 |
241 | if (array_key_exists($id, self::$instances)) {
242 | $instance = self::$instances[$id];
243 | } else {
244 | $instance = self::$currentInstance;
245 | }
246 |
247 | if ($instance === null) {
248 | throw new \Exception('No instance found for datatable, you should set a datatable id in your action with "setDatatableId" using the id from your view');
249 | }
250 |
251 | return $instance;
252 | }
253 |
254 | public static function clearInstance()
255 | {
256 | self::$instances = array();
257 | }
258 |
259 | /**
260 | * get entity name
261 | *
262 | * @return string
263 | */
264 | public function getEntityName()
265 | {
266 | return $this->queryBuilder->getEntityName();
267 | }
268 |
269 | /**
270 | * get entity alias
271 | *
272 | * @return string
273 | */
274 | public function getEntityAlias()
275 | {
276 | return $this->queryBuilder->getEntityAlias();
277 | }
278 |
279 | /**
280 | * get fields
281 | *
282 | * @return array
283 | */
284 | public function getFields()
285 | {
286 | return $this->queryBuilder->getFields();
287 | }
288 |
289 | /**
290 | * get order field
291 | *
292 | * @return string
293 | */
294 | public function getOrderField()
295 | {
296 | return $this->queryBuilder->getOrderField();
297 | }
298 |
299 | /**
300 | * get order type
301 | *
302 | * @return string
303 | */
304 | public function getOrderType()
305 | {
306 | return $this->queryBuilder->getOrderType();
307 | }
308 |
309 | /**
310 | * get query builder
311 | *
312 | * @return QueryInterface
313 | */
314 | public function getQueryBuilder()
315 | {
316 | return $this->queryBuilder;
317 | }
318 |
319 | /**
320 | * get search
321 | *
322 | * @return boolean
323 | */
324 | public function getSearch()
325 | {
326 | return $this->search;
327 | }
328 |
329 | /**
330 | * get global_search
331 | *
332 | * @return boolean
333 | */
334 | public function getGlobalSearch()
335 | {
336 | return $this->globalSearch;
337 | }
338 |
339 | /**
340 | * set entity
341 | *
342 | * @param type $entityName
343 | * @param type $entityAlias
344 | *
345 | * @return Datatable
346 | */
347 | public function setEntity($entityName, $entityAlias)
348 | {
349 | $this->queryBuilder->setEntity($entityName, $entityAlias);
350 | return $this;
351 | }
352 |
353 | /**
354 | * set fields
355 | *
356 | * @param array $fields
357 | *
358 | * @return Datatable
359 | */
360 | public function setFields(array $fields)
361 | {
362 | $this->queryBuilder->setFields($fields);
363 | return $this;
364 | }
365 |
366 | /**
367 | * set order
368 | *
369 | * @param type $orderField
370 | * @param type $orderType
371 | *
372 | * @return Datatable
373 | */
374 | public function setOrder($orderField, $orderType)
375 | {
376 | $this->queryBuilder->setOrder($orderField, $orderType);
377 | return $this;
378 | }
379 |
380 | /**
381 | * set fixed data
382 | *
383 | * @param type $data
384 | *
385 | * @return Datatable
386 | */
387 | public function setFixedData($data)
388 | {
389 | $this->fixedData = $data;
390 | return $this;
391 | }
392 |
393 | /**
394 | * set query builder
395 | *
396 | * @param QueryInterface $queryBuilder
397 | */
398 | public function setQueryBuilder(QueryInterface $queryBuilder)
399 | {
400 | $this->queryBuilder = $queryBuilder;
401 | }
402 |
403 | /**
404 | * set a php closure as renderer
405 | *
406 | * @example:
407 | *
408 | * $controller_instance = $this;
409 | * $datatable = $this->get('datatable')
410 | * ->setEntity("BaseBundle:Entity", "e")
411 | * ->setFields($fields)
412 | * ->setOrder("e.created", "desc")
413 | * ->setRenderer(
414 | * function(&$data) use ($controller_instance)
415 | * {
416 | * foreach ($data as $key => $value)
417 | * {
418 | * if ($key == 1)
419 | * {
420 | * $data[$key] = $controller_instance
421 | * ->get('templating')
422 | * ->render('BaseBundle:Entity:_decorator.html.twig',
423 | * array(
424 | * 'data' => $value
425 | * )
426 | * );
427 | * }
428 | * }
429 | * }
430 | * )
431 | *
432 | * @param \Closure $renderer
433 | *
434 | * @return Datatable
435 | */
436 | public function setRenderer(\Closure $renderer)
437 | {
438 | $this->renderer = $renderer;
439 | return $this;
440 | }
441 |
442 | /**
443 | * set renderers as twig views
444 | *
445 | * @example: To override the actions column
446 | *
447 | * ->setFields(
448 | * array(
449 | * "field label 1" => 'x.field1',
450 | * "field label 2" => 'x.field2',
451 | * "_identifier_" => 'x.id'
452 | * )
453 | * )
454 | * ->setRenderers(
455 | * array(
456 | * 2 => array(
457 | * 'view' => 'WaldoDatatableBundle:Renderers:_actions.html.twig',
458 | * 'params' => array(
459 | * 'edit_route' => 'matche_edit',
460 | * 'delete_route' => 'matche_delete',
461 | * 'delete_form_prototype' => $datatable->getPrototype('delete_form')
462 | * ),
463 | * ),
464 | * )
465 | * )
466 | *
467 | * @param array $renderers
468 | *
469 | * @return Datatable
470 | */
471 | public function setRenderers(array $renderers)
472 | {
473 | $this->renderers = $renderers;
474 |
475 | if (!empty($this->renderers)) {
476 | $this->rendererObj = $this->rendererEngine->build($this->renderers, $this->getFields());
477 | }
478 |
479 | return $this;
480 | }
481 |
482 | /**
483 | * set query where
484 | *
485 | * @param string $where
486 | * @param array $params
487 | *
488 | * @return Datatable
489 | */
490 | public function setWhere($where, array $params = array())
491 | {
492 | $this->queryBuilder->setWhere($where, $params);
493 | return $this;
494 | }
495 |
496 | /**
497 | * set query group
498 | *
499 | * @param string $groupby
500 | *
501 | * @return Datatable
502 | */
503 | public function setGroupBy($groupby)
504 | {
505 | $this->queryBuilder->setGroupBy($groupby);
506 | return $this;
507 | }
508 |
509 | /**
510 | * set search
511 | *
512 | * @param bool $search
513 | *
514 | * @return Datatable
515 | */
516 | public function setSearch($search)
517 | {
518 | $this->search = $search;
519 | $this->queryBuilder->setSearch($search || $this->globalSearch);
520 | return $this;
521 | }
522 |
523 | /**
524 | * set global search
525 | *
526 | * @param bool $globalSearch
527 | *
528 | * @return Datatable
529 | */
530 | public function setGlobalSearch($globalSearch)
531 | {
532 | $this->globalSearch = $globalSearch;
533 | $this->queryBuilder->setSearch($globalSearch || $this->search);
534 | return $this;
535 | }
536 |
537 | /**
538 | * set datatable identifier
539 | *
540 | * @param string $id
541 | *
542 | * @return Datatable
543 | */
544 | public function setDatatableId($id)
545 | {
546 | if (!array_key_exists($id, self::$instances)) {
547 | self::$instances[$id] = $this;
548 | } else {
549 | throw new \Exception('Identifer already exists');
550 | }
551 |
552 | return $this;
553 | }
554 |
555 | /**
556 | * hasInstanceId
557 | *
558 | * @param strin $id
559 | */
560 | public function hasInstanceId($id)
561 | {
562 | return array_key_exists($id, self::$instances);
563 | }
564 |
565 | /**
566 | * get multiple
567 | *
568 | * @return array
569 | */
570 | public function getMultiple()
571 | {
572 | return $this->multiple;
573 | }
574 |
575 | /**
576 | * set multiple
577 | *
578 | * @example
579 | *
580 | * ->setMultiple('delete' => array ('title' => "Delete", 'route' => 'route_to_delete'));
581 | *
582 | * @param array $multiple
583 | *
584 | * @return \Waldo\DatatableBundle\Util\Datatable
585 | */
586 | public function setMultiple(array $multiple)
587 | {
588 | $this->multiple = $multiple;
589 |
590 | if(count($this->multiple) > 0) {
591 | if(!in_array(0, $this->notFilterableFields)) {
592 | $this->notFilterableFields[] = 0;
593 | }
594 | if(!in_array(0, $this->notSortableFields)) {
595 | $this->notSortableFields[] = 0;
596 | }
597 | }
598 |
599 | return $this;
600 | }
601 |
602 | /**
603 | * get global configuration (read it from config.yml under datatable)
604 | *
605 | * @return array
606 | */
607 | public function getConfiguration()
608 | {
609 | return $this->config;
610 | }
611 |
612 | /**
613 | * get search field
614 | *
615 | * @return array
616 | */
617 | public function getSearchFields()
618 | {
619 | return $this->searchFields;
620 | }
621 |
622 | /**
623 | * set search fields
624 | *
625 | * @example
626 | *
627 | * ->setSearchFields(array(0,2,5))
628 | *
629 | * @param array $searchFields
630 | *
631 | * @return \Waldo\DatatableBundle\Util\Datatable
632 | */
633 | public function setSearchFields(array $searchFields)
634 | {
635 | $this->searchFields = $searchFields;
636 | return $this;
637 | }
638 |
639 | /**
640 | * set not filterable fields
641 | *
642 | * @example
643 | *
644 | * ->setNotFilterableFields(array(0,2,5))
645 | *
646 | * @param array $notFilterableFields
647 | *
648 | * @return \Waldo\DatatableBundle\Util\Datatable
649 | */
650 | public function setNotFilterableFields(array $notFilterableFields)
651 | {
652 | $this->notFilterableFields = $notFilterableFields;
653 | return $this;
654 | }
655 |
656 | /**
657 | * get not filterable field
658 | *
659 | * @return array
660 | */
661 | public function getNotFilterableFields()
662 | {
663 | return $this->notFilterableFields;
664 | }
665 |
666 | /**
667 | * set not sortable fields
668 | *
669 | * @example
670 | *
671 | * ->setNotSortableFields(array(0,2,5))
672 | *
673 | * @param array $notSortableFields
674 | *
675 | * @return \Waldo\DatatableBundle\Util\Datatable
676 | */
677 | public function setNotSortableFields(array $notSortableFields)
678 | {
679 | $this->notSortableFields = $notSortableFields;
680 | return $this;
681 | }
682 |
683 | /**
684 | * get not sortable field
685 | *
686 | * @return array
687 | */
688 | public function getNotSortableFields()
689 | {
690 | return $this->notSortableFields;
691 | }
692 |
693 | /**
694 | * set hidden fields
695 | *
696 | * @example
697 | *
698 | * ->setHiddenFields(array(0,2,5))
699 | *
700 | * @param array $hiddenFields
701 | *
702 | * @return \Waldo\DatatableBundle\Util\Datatable
703 | */
704 | public function setHiddenFields(array $hiddenFields)
705 | {
706 | $this->hiddenFields = $hiddenFields;
707 | return $this;
708 | }
709 |
710 | /**
711 | * get hidden field
712 | *
713 | * @return array
714 | */
715 | public function getHiddenFields()
716 | {
717 | return $this->hiddenFields;
718 | }
719 |
720 | /**
721 | * set filtering type
722 | * 's' strict
723 | * 'f' full => LIKE '%' . $value . '%'
724 | * 'b' begin => LIKE '%' . $value
725 | * 'e' end => LIKE $value . '%'
726 | *
727 | * @example
728 | *
729 | * ->setFilteringType(array(0 => 's',2 => 'f',5 => 'b'))
730 | *
731 | * @param array $filteringType
732 | *
733 | * @return \Waldo\DatatableBundle\Util\Datatable
734 | */
735 | public function setFilteringType(array $filteringType)
736 | {
737 | $this->queryBuilder->setFilteringType($filteringType);
738 | return $this;
739 | }
740 | }
741 |
--------------------------------------------------------------------------------
/Util/Factory/Query/DoctrineBuilder.php:
--------------------------------------------------------------------------------
1 | em = $entityManager;
93 | $this->request = $request;
94 |
95 | $this->queryBuilder = $this->em->createQueryBuilder();
96 | }
97 |
98 | /**
99 | * get the search DQL
100 | *
101 | * @return string
102 | */
103 | protected function addSearch(QueryBuilder $queryBuilder)
104 | {
105 | if ($this->search !== true) {
106 | return;
107 | }
108 |
109 | $request = $this->request->getCurrentRequest();
110 |
111 | $columns = $request->get('columns', array());
112 |
113 | $searchFields = array_intersect_key(array_values($this->fields), $columns);
114 |
115 | $globalSearch = $request->get('search');
116 |
117 | $orExpr = $queryBuilder->expr()->orX();
118 |
119 | $filteringType = $this->getFilteringType();
120 |
121 | foreach ($searchFields as $i => $searchField) {
122 |
123 | $searchField = $this->getSearchField($searchField);
124 |
125 | // Global filtering
126 | if ((!empty($globalSearch) || $globalSearch['value'] == '0') && $columns[$i]['searchable'] === "true") {
127 |
128 | $qbParam = "sSearch_global_" . $i;
129 |
130 | if ($this->isStringDQLQuery($searchField)) {
131 |
132 | $orExpr->add(
133 | $queryBuilder->expr()->eq($searchField, ':' . $qbParam)
134 | );
135 | $queryBuilder->setParameter($qbParam, $globalSearch['value']);
136 |
137 | } else {
138 |
139 | $orExpr->add($queryBuilder->expr()->like($searchField, ":" . $qbParam));
140 | $queryBuilder->setParameter($qbParam, "%" . $globalSearch['value'] . "%");
141 |
142 | }
143 | }
144 |
145 | // Individual filtering
146 | $searchName = "sSearch_" . $i;
147 |
148 | if($columns[$i]['searchable'] === "true" && $columns[$i]['search']['value'] != "") {
149 |
150 | $queryBuilder->andWhere($queryBuilder->expr()->like($searchField, ":" . $searchName));
151 |
152 | if (array_key_exists($i, $filteringType)) {
153 | switch ($filteringType[$i]) {
154 | case 's':
155 | $queryBuilder->setParameter($searchName, $columns[$i]['search']['value']);
156 | break;
157 | case 'f':
158 | $queryBuilder->setParameter($searchName, sprintf("%%%s%%", $columns[$i]['search']['value']));
159 | break;
160 | case 'b':
161 | $queryBuilder->setParameter($searchName, sprintf("%%%s", $columns[$i]['search']['value']));
162 | break;
163 | case 'e':
164 | $queryBuilder->setParameter($searchName, sprintf("%s%%", $columns[$i]['search']['value']));
165 | break;
166 | }
167 | } else {
168 | $queryBuilder->setParameter($searchName, sprintf("%%%s%%", $columns[$i]['search']['value']));
169 | }
170 | }
171 | }
172 |
173 | if ((!empty($globalSearch) || $globalSearch == '0') && $orExpr->count() > 0) {
174 | $queryBuilder->andWhere($orExpr);
175 | }
176 | }
177 |
178 | /**
179 | * add join
180 | *
181 | * @example:
182 | * ->setJoin(
183 | * 'r.event',
184 | * 'e',
185 | * \Doctrine\ORM\Query\Expr\Join::INNER_JOIN,
186 | * \Doctrine\ORM\Query\Expr\Join::WITH,
187 | * 'e.name like %test%')
188 | *
189 | * @param string $join The relationship to join.
190 | * @param string $alias The alias of the join.
191 | * @param string|Join::INNER_JOIN $type The type of the join Join::INNER_JOIN | Join::LEFT_JOIN
192 | * @param string|null $conditionType The condition type constant. Either ON or WITH.
193 | * @param string|null $condition The condition for the join.
194 | *
195 | * @return Datatable
196 | */
197 | public function addJoin($join, $alias, $type = Join::INNER_JOIN, $conditionType = null, $condition = null)
198 | {
199 | if($type === Join::INNER_JOIN) {
200 | $this->queryBuilder->innerJoin($join, $alias, $conditionType, $condition);
201 | } elseif($type === Join::LEFT_JOIN) {
202 | $this->queryBuilder->leftJoin($join, $alias, $conditionType, $condition);
203 | }
204 |
205 | $this->joins[] = array($join, $alias, $type, $conditionType, $condition);
206 |
207 | return $this;
208 | }
209 |
210 | /**
211 | * get total records
212 | *
213 | * @return integer
214 | */
215 | public function getTotalRecords()
216 | {
217 | $qb = clone $this->queryBuilder;
218 | $qb->resetDQLPart('orderBy');
219 |
220 | $gb = $qb->getDQLPart('groupBy');
221 |
222 | if (empty($gb) || !in_array($this->fields['_identifier_'], $gb)) {
223 | $qb->select(
224 | $qb->expr()->count($this->fields['_identifier_'])
225 | );
226 |
227 | return $qb->getQuery()->getSingleScalarResult();
228 | } else {
229 | $qb->resetDQLPart('groupBy');
230 | $qb->select(
231 | $qb->expr()->countDistinct($this->fields['_identifier_'])
232 | );
233 |
234 | return $qb->getQuery()->getSingleScalarResult();
235 | }
236 | }
237 |
238 | /**
239 | * get total records after filtering
240 | *
241 | * @return integer
242 | */
243 | public function getTotalDisplayRecords()
244 | {
245 | $qb = clone $this->queryBuilder;
246 |
247 | $this->addSearch($qb);
248 |
249 | $qb->resetDQLPart('orderBy');
250 |
251 | $gb = $qb->getDQLPart('groupBy');
252 |
253 | if (empty($gb) || !in_array($this->fields['_identifier_'], $gb)) {
254 | $qb->select(
255 | $qb->expr()->count($this->fields['_identifier_'])
256 | );
257 |
258 | return $qb->getQuery()->getSingleScalarResult();
259 |
260 | } else {
261 | $qb->resetDQLPart('groupBy');
262 | $qb->select(
263 | $qb->expr()->countDistinct($this->fields['_identifier_'])
264 | );
265 |
266 | return $qb->getQuery()->getSingleScalarResult();
267 | }
268 | }
269 |
270 | /**
271 | * get data
272 | *
273 | * @return array
274 | */
275 | public function getData($multiple)
276 | {
277 | $request = $this->request->getCurrentRequest();
278 | $order = $request->get('order', array());
279 |
280 | $dqlFields = array_values($this->fields);
281 |
282 | $qb = clone $this->queryBuilder;
283 |
284 | // add sorting
285 | if (array_key_exists(0, $order)) {
286 | if ($multiple) $orderColumn = $order[0]['column']-1; else $orderColumn = $order[0]['column'];
287 | $orderField = explode(' as ', $dqlFields[$orderColumn]);
288 | end($orderField);
289 |
290 | $qb->orderBy(current($orderField), $order[0]['dir']);
291 | } elseif($this->orderField === null) {
292 | $qb->resetDQLPart('orderBy');
293 | }
294 |
295 | // extract alias selectors
296 | $select = array($this->entityAlias);
297 | foreach ($this->joins as $join) {
298 | $select[] = $join[1];
299 | }
300 |
301 | foreach ($this->fields as $key => $field) {
302 | if (stripos($field, " as ") !== false || stripos($field, "(") !== false) {
303 | $select[] = $field;
304 | }
305 | }
306 |
307 | $qb->select(implode(',', $select));
308 |
309 | // add search
310 | $this->addSearch($qb);
311 |
312 | // get results and process data formatting
313 | $query = $qb->getQuery();
314 | $length = (int) $request->get('length', 0);
315 |
316 | if ($length > 0) {
317 | $query->setMaxResults($length)
318 | ->setFirstResult((int) $request->get('start'));
319 | }
320 |
321 | $objects = $query->getResult(Query::HYDRATE_OBJECT);
322 | $maps = $query->getResult(Query::HYDRATE_SCALAR);
323 | $data = array();
324 |
325 | $aliasPattern = self::DQL_ALIAS_PATTERN;
326 |
327 | $get_scalar_key = function($field) use($aliasPattern) {
328 |
329 | $has_alias = (bool) preg_match_all($aliasPattern, $field, $matches);
330 | $_f = ( $has_alias === true ) ? $matches[2][0] : $field;
331 | $_f = str_replace('.', '_', $_f);
332 |
333 | return $_f;
334 | };
335 |
336 | $fields = array();
337 |
338 | foreach ($this->fields as $field) {
339 | $fields[] = $get_scalar_key($field);
340 | }
341 |
342 | foreach ($maps as $map) {
343 | $item = array();
344 | foreach ($fields as $_field) {
345 | $item[] = $map[$_field];
346 | }
347 | $data[] = $item;
348 | }
349 |
350 | return array($data, $objects);
351 | }
352 |
353 | /**
354 | * get entity name
355 | *
356 | * @return string
357 | */
358 | public function getEntityName()
359 | {
360 | return $this->entityName;
361 | }
362 |
363 | /**
364 | * get entity alias
365 | *
366 | * @return string
367 | */
368 | public function getEntityAlias()
369 | {
370 | return $this->entityAlias;
371 | }
372 |
373 | /**
374 | * get fields
375 | *
376 | * @return array
377 | */
378 | public function getFields()
379 | {
380 | return $this->fields;
381 | }
382 |
383 | /**
384 | * get order field
385 | *
386 | * @return string
387 | */
388 | public function getOrderField()
389 | {
390 | return $this->orderField;
391 | }
392 |
393 | /**
394 | * get order type
395 | *
396 | * @return string
397 | */
398 | public function getOrderType()
399 | {
400 | return $this->orderType;
401 | }
402 |
403 | /**
404 | * get doctrine query builder
405 | *
406 | * @return \Doctrine\ORM\QueryBuilder
407 | */
408 | public function getDoctrineQueryBuilder()
409 | {
410 | return $this->queryBuilder;
411 | }
412 |
413 | /**
414 | * set entity
415 | *
416 | * @param type $entity_name
417 | * @param type $entity_alias
418 | *
419 | * @return Datatable
420 | */
421 | public function setEntity($entity_name, $entity_alias)
422 | {
423 | $this->entityName = $entity_name;
424 | $this->entityAlias = $entity_alias;
425 | $this->queryBuilder->from($entity_name, $entity_alias);
426 |
427 | return $this;
428 | }
429 |
430 | /**
431 | * set fields
432 | *
433 | * @param array $fields
434 | *
435 | * @return Datatable
436 | */
437 | public function setFields(array $fields)
438 | {
439 | $this->fields = $fields;
440 | $this->queryBuilder->select(implode(', ', $fields));
441 |
442 | return $this;
443 | }
444 |
445 | /**
446 | * set order
447 | *
448 | * @param type $order_field
449 | * @param type $order_type
450 | *
451 | * @return Datatable
452 | */
453 | public function setOrder($order_field, $order_type)
454 | {
455 | $this->orderField = $order_field;
456 | $this->orderType = $order_type;
457 | $this->queryBuilder->orderBy($order_field, $order_type);
458 |
459 | return $this;
460 | }
461 |
462 | /**
463 | * set query where
464 | *
465 | * @param string $where
466 | * @param array $params
467 | *
468 | * @return Datatable
469 | */
470 | public function setWhere($where, array $params = array())
471 | {
472 | $this->queryBuilder->where($where);
473 | $this->queryBuilder->setParameters($params);
474 | return $this;
475 | }
476 |
477 | /**
478 | * set query group
479 | *
480 | * @param string $group
481 | *
482 | * @return Datatable
483 | */
484 | public function setGroupBy($group)
485 | {
486 | $this->queryBuilder->groupBy($group);
487 | return $this;
488 | }
489 |
490 | /**
491 | * set search
492 | *
493 | * @param bool $search
494 | *
495 | * @return Datatable
496 | */
497 | public function setSearch($search)
498 | {
499 | $this->search = $search;
500 | return $this;
501 | }
502 |
503 | /**
504 | * set doctrine query builder
505 | *
506 | * @param \Doctrine\ORM\QueryBuilder $queryBuilder
507 | *
508 | * @return DoctrineBuilder
509 | */
510 | public function setDoctrineQueryBuilder(QueryBuilder $queryBuilder)
511 | {
512 | $this->queryBuilder = $queryBuilder;
513 | return $this;
514 | }
515 |
516 | /**
517 | * set filtering type
518 | * 's' strict
519 | * 'f' full => LIKE '%' . $value . '%'
520 | * 'b' begin => LIKE '%' . $value
521 | * 'e' end => LIKE $value . '%'
522 | *
523 | * @example
524 | *
525 | * ->setFilteringType(array(0 => 's',2 => 'f',5 => 'b'))
526 | *
527 | * @param array $filtering_type
528 | *
529 | * @return DoctrineBuilder
530 | */
531 | public function setFilteringType(array $filtering_type)
532 | {
533 | $this->filteringType = $filtering_type;
534 | return $this;
535 | }
536 |
537 | public function getFilteringType()
538 | {
539 | return $this->filteringType;
540 | }
541 |
542 | /**
543 | * The most of time $search_field is a string that represent the name of a field in data base.
544 | * But some times, $search_field is a DQL subquery
545 | *
546 | * @param string $field
547 | * @return string
548 | */
549 | private function getSearchField($field)
550 | {
551 | if ($this->isStringDQLQuery($field)) {
552 |
553 | $dqlQuery = $field;
554 |
555 | $lexer = new Query\Lexer($field);
556 |
557 | // We have to rename some identifier or the execution will crash
558 | do {
559 | $lexer->moveNext();
560 |
561 | if ($this->isTheIdentifierILookingFor($lexer)) {
562 |
563 | $replacement = sprintf("$1%s_%d$3", $lexer->lookahead['value'], mt_rand());
564 | $pattern = sprintf("/([\(\s])(%s)([\s\.])/", $lexer->lookahead['value']);
565 |
566 | $dqlQuery = preg_replace($pattern, $replacement, $dqlQuery);
567 | }
568 |
569 | } while($lexer->lookahead !== null);
570 |
571 | $dqlQuery = substr($dqlQuery, 0, strripos($dqlQuery, ")") + 1);
572 |
573 | return $dqlQuery;
574 | }
575 |
576 | $field = explode(' ', trim($field));
577 |
578 | return $field[0];
579 | }
580 |
581 | /**
582 | * Check if it's the lexer part is the identifier I looking for
583 | *
584 | * @param \Doctrine\ORM\Query\Lexer $lexer
585 | * @return boolean
586 | */
587 | private function isTheIdentifierILookingFor(Query\Lexer $lexer)
588 | {
589 | if ($lexer->token['type'] === Query\Lexer::T_IDENTIFIER && $lexer->isNextToken(Query\Lexer::T_IDENTIFIER)) {
590 | return true;
591 | }
592 |
593 | if ($lexer->token['type'] === Query\Lexer::T_IDENTIFIER && $lexer->isNextToken(Query\Lexer::T_AS)) {
594 |
595 | $lexer->moveNext();
596 |
597 | if ($lexer->lookahead['type'] === Query\Lexer::T_IDENTIFIER) {
598 | return true;
599 | }
600 | }
601 |
602 | return false;
603 | }
604 |
605 | /**
606 | * Check if a sring is a DQL query
607 | *
608 | * @param string $value
609 | * @return boolean
610 | */
611 | private function isStringDQLQuery($value)
612 | {
613 | $keysWord = array(
614 | "SELECT ",
615 | " FROM ",
616 | " WHERE "
617 | );
618 |
619 | foreach ($keysWord as $keyWord) {
620 | if (stripos($value, $keyWord) !== false) {
621 | return true;
622 | }
623 | }
624 |
625 | return false;
626 | }
627 | }
628 |
--------------------------------------------------------------------------------
/Util/Factory/Query/QueryInterface.php:
--------------------------------------------------------------------------------
1 | setJoin(
114 | * 'r.event',
115 | * 'e',
116 | * \Doctrine\ORM\Query\Expr\Join::INNER_JOIN,
117 | * \Doctrine\ORM\Query\Expr\Join::WITH,
118 | * 'e.name like %test%')
119 | *
120 | * @param string $join The relationship to join.
121 | * @param string $alias The alias of the join.
122 | * @param string|Join::INNER_JOIN $type The type of the join Join::INNER_JOIN | Join::LEFT_JOIN
123 | * @param string|null $conditionType The condition type constant. Either ON or WITH.
124 | * @param string|null $condition The condition for the join.
125 | *
126 | * @return Datatable
127 | */
128 | public function addJoin($join, $alias, $type = Join::INNER_JOIN, $conditionType = null, $condition = null);
129 |
130 | /**
131 | * set filtering type
132 | * 's' strict
133 | * 'f' full => LIKE '%' . $value . '%'
134 | * 'b' begin => LIKE '%' . $value
135 | * 'e' end => LIKE $value . '%'
136 | *
137 | * @example
138 | *
139 | * ->setFilteringType(array(0 => 's',2 => 'f',5 => 'b'))
140 | *
141 | * @param array $filtering_type
142 | *
143 | * @return Datatable
144 | */
145 | function setFilteringType(array $filtering_type);
146 |
147 | /**
148 | * @return \Doctrine\ORM\QueryBuilder;
149 | */
150 | function getDoctrineQueryBuilder();
151 |
152 | function getFilteringType();
153 | }
154 |
--------------------------------------------------------------------------------
/Util/Formatter/Renderer.php:
--------------------------------------------------------------------------------
1 | templating = $templating;
39 | }
40 |
41 | /**
42 | * Build the renderer
43 | *
44 | * @param array $renderers
45 | * @param array $fields
46 | * @return \Waldo\DatatableBundle\Util\Formatter\Renderer
47 | */
48 | public function build(array $renderers, array $fields)
49 | {
50 | $this->renderers = $renderers;
51 | $this->fields = $fields;
52 | $this->prepare();
53 |
54 | return $this;
55 | }
56 |
57 | /**
58 | * return the rendered view using the given content
59 | *
60 | * @param string $view_path
61 | * @param array $params
62 | *
63 | * @return string
64 | */
65 | public function applyView($view_path, array $params)
66 | {
67 | $out = $this->templating
68 | ->render($view_path, $params);
69 |
70 | return html_entity_decode($out);
71 | }
72 |
73 | /**
74 | * prepare the renderer :
75 | * - guess the identifier index
76 | *
77 | * @return void
78 | */
79 | protected function prepare()
80 | {
81 | $this->identifierIndex = array_search("_identifier_", array_keys($this->fields));
82 | }
83 |
84 | /**
85 | * apply foreach given cell content the given (if exists) view
86 | *
87 | * @param array $data
88 | * @param array $objects
89 | *
90 | * @return void
91 | */
92 | public function applyTo(array &$data, array $objects)
93 | {
94 | foreach ($data as $row_index => $row) {
95 | $identifier_raw = $data[$row_index][$this->identifierIndex];
96 | foreach ($row as $column_index => $column) {
97 | $params = array();
98 | if (array_key_exists($column_index, $this->renderers)) {
99 | $view = $this->renderers[$column_index]['view'];
100 | $params = isset($this->renderers[$column_index]['params']) ? $this->renderers[$column_index]['params'] : array();
101 | } else {
102 | $view = 'WaldoDatatableBundle:Renderers:_default.html.twig';
103 | }
104 | $params = array_merge($params,
105 | array(
106 | 'dt_obj' => $objects[$row_index],
107 | 'dt_item' => $data[$row_index][$column_index],
108 | 'dt_id' => $identifier_raw,
109 | 'dt_line' => $data[$row_index]
110 | )
111 | );
112 | $data[$row_index][$column_index] = $this->applyView(
113 | $view, $params
114 | );
115 | }
116 | }
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/WaldoDatatableBundle.php:
--------------------------------------------------------------------------------
1 | =5.4",
26 | "twig/twig": "~1.8",
27 | "doctrine/common": "~2.1"
28 | },
29 | "require-dev": {
30 | "symfony/symfony": ">=2.6.0,<=4.0",
31 | "doctrine/doctrine-bundle": "~1.4",
32 | "doctrine/orm": "^2.4.8",
33 | "phpunit/phpunit": "~4.8"
34 | },
35 | "autoload": {
36 | "psr-4": { "Waldo\\DatatableBundle\\": ""}
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | ./Tests
12 |
13 |
14 |
15 |
16 |
17 | ./
18 |
19 | ./Tests
20 | ./vendor
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------