Skip to content

Commit f75fd13

Browse files
authored
Merge pull request tetranz#84 from n3o77/master
Allow to add the values from dependent fields
2 parents 2a9ebd8 + 84663c0 commit f75fd13

File tree

6 files changed

+205
-8
lines changed

6 files changed

+205
-8
lines changed

Form/Type/Select2EntityType.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Symfony\Component\Routing\RouterInterface;
1414
use Tetranz\Select2EntityBundle\Form\DataTransformer\EntitiesToPropertyTransformer;
1515
use Tetranz\Select2EntityBundle\Form\DataTransformer\EntityToPropertyTransformer;
16+
use Symfony\Component\PropertyAccess\PropertyAccess;
1617

1718
/**
1819
*
@@ -94,6 +95,17 @@ public function finishView(FormView $view, FormInterface $form, array $options)
9495
$view->vars[$varName] = $options[$varName];
9596
}
9697

98+
if (isset($options['req_params']) && is_array($options['req_params']) && count($options['req_params']) > 0) {
99+
$accessor = PropertyAccess::createPropertyAccessor();
100+
101+
$reqParams = [];
102+
foreach ($options['req_params'] as $key => $reqParam) {
103+
$reqParams[$key] = $accessor->getValue($view, $reqParam . '.vars[full_name]');
104+
}
105+
106+
$view->vars['attr']['data-req_params'] = json_encode($reqParams);
107+
}
108+
97109
//tags options
98110
$varNames = array_keys($this->config['allow_add']);
99111
foreach ($varNames as $varName) {
@@ -154,6 +166,9 @@ public function configureOptions(OptionsResolver $resolver)
154166
'transformer' => null,
155167
'autostart' => true,
156168
'width' => isset($this->config['width']) ? $this->config['width'] : null,
169+
'req_params' => array(),
170+
'property' => null,
171+
'callback' => null,
157172
)
158173
);
159174
}

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,88 @@ $builder
241241
]);
242242
```
243243

244+
### Including other field values in request
245+
246+
If you need to include other field values because the selection depends on it you can add the `req_params` option.
247+
The key is the name of the parameter in the query string, the value is the path in the FormView (if you don't know the path you can do something like `{{ dump(form) }}` in your template.)
248+
249+
The `property` option refers to your entity field which is used for the label as well as for the search term.
250+
251+
In the callback you get the QueryBuilder to modify the result query and the data object as parameter (data can be a simple Request object or a plain array. See AutocompleteService.php for more details).
252+
253+
```php
254+
$builder
255+
->add('firstName', TextType::class)
256+
->add('lastName', TextType::class)
257+
->add('state', EntityType::class, array('class' => State::class))
258+
->add('county', Select2EntityType::class, [
259+
'required' => true,
260+
'multiple' => false,
261+
'remote_route' => 'ajax_autocomplete',
262+
'class' => County::class,
263+
'minimum_input_length' => 0,
264+
'page_limit' => 10,
265+
'scroll' => true,
266+
'allow_clear' => false,
267+
'req_params' => ['state' => 'parent.children[state]'],
268+
'property' => 'name',
269+
'callback' => function (QueryBuilder $qb, $data) {
270+
$qb->andWhere('e.state = :state');
271+
272+
if ($data instanceof Request) {
273+
$qb->setParameter('state', $data->get('state'));
274+
} else {
275+
$qb->setParameter('state', $data['state']);
276+
}
277+
278+
},
279+
])
280+
->add('city', Select2EntityType::class, [
281+
'required' => true,
282+
'multiple' => false,
283+
'remote_route' => 'ajax_autocomplete',
284+
'class' => City::class,
285+
'minimum_input_length' => 0,
286+
'page_limit' => 10,
287+
'scroll' => true,
288+
'allow_clear' => false,
289+
'req_params' => ['county' => 'parent.children[county]'],
290+
'property' => 'name',
291+
'callback' => function (QueryBuilder $qb, $data) {
292+
$qb->andWhere('e.county = :county');
293+
294+
if ($data instanceof Request) {
295+
$qb->setParameter('county', $data->get('county'));
296+
} else {
297+
$qb->setParameter('county', $data['county']);
298+
}
299+
300+
},
301+
]);
302+
```
303+
304+
Because the handling of requests is usually very similar you can use a service which helps you with your results. To use it just add a route in one of your controllers:
305+
306+
```php
307+
/**
308+
* @param Request $request
309+
*
310+
* @Route("/autocomplete", name="ajax_autocomplete")
311+
*
312+
* @return Response
313+
*/
314+
public function autocompleteAction(Request $request)
315+
{
316+
// Check security etc. if needed
317+
318+
$as = $this->get('tetranz_select2entity.autocomplete_service');
319+
320+
$result = $as->getAutocompleteResults($request, YourFormType::class);
321+
322+
return new JsonResponse($result);
323+
}
324+
```
325+
244326

245327
### Templating
246328

Resources/config/services.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
<argument type="service" id="router" />
1212
<argument>%tetranz_select2_entity.config%</argument>
1313
</service>
14+
<service id="tetranz_select2entity.autocomplete_service" class="Tetranz\Select2EntityBundle\Service\AutocompleteService">
15+
<argument type="service" id="service_container" />
16+
</service>
1417
</services>
1518

1619
</container>

Resources/public/js/select2entity.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@
1010
scroll = $s2.data('scroll'),
1111
prefix = Date.now(),
1212
cache = [];
13+
14+
var reqParams = $s2.data('req_params');
15+
if (reqParams) {
16+
$.each(reqParams, function (key, value) {
17+
$('*[name="'+value+'"]').on('change', function () {
18+
$s2.val(null);
19+
$s2.trigger('change');
20+
});
21+
});
22+
}
23+
1324
// Deep-merge the options
1425
$s2.select2($.extend(true, {
1526
// Tags support
@@ -50,17 +61,24 @@
5061
}
5162
},
5263
data: function (params) {
64+
var ret = {
65+
'q': params.term,
66+
'field_name': $s2.data('name')
67+
};
68+
69+
var reqParams = $s2.data('req_params');
70+
if (reqParams) {
71+
$.each(reqParams, function (key, value) {
72+
ret[key] = $('*[name="'+value+'"]').val()
73+
});
74+
}
75+
5376
// only send the 'page' parameter if scrolling is enabled
5477
if (scroll) {
55-
return {
56-
q: params.term,
57-
page: params.page || 1
58-
};
78+
ret['page'] = params.page || 1;
5979
}
6080

61-
return {
62-
q: params.term
63-
};
81+
return ret;
6482
},
6583
processResults: function (data, params) {
6684
var results, more = false, response = {};

Resources/views/Form/fields.html.twig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
'data-page-limit':page_limit,
1212
'data-scroll':scroll ? 'true' : 'false',
1313
'data-autostart':autostart ? 'true' : 'false',
14-
'class' : (attr.class|default('') ~ ' select2entity form-control')|trim
14+
'class' : (attr.class|default('') ~ ' select2entity form-control')|trim,
15+
'data-name' : name|e('html_attr')
1516
}) %}
1617

1718
{% if allow_add.enabled %}

Service/AutocompleteService.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace Tetranz\Select2EntityBundle\Service;
4+
5+
use Doctrine\ORM\EntityRepository;
6+
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
7+
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
8+
use Symfony\Component\DependencyInjection\ContainerInterface;
9+
use Symfony\Component\Form\FormTypeInterface;
10+
use Symfony\Component\HttpFoundation\Request;
11+
use Symfony\Component\PropertyAccess\PropertyAccess;
12+
13+
class AutocompleteService implements ContainerAwareInterface
14+
{
15+
use ContainerAwareTrait;
16+
17+
18+
public function __construct(ContainerInterface $container)
19+
{
20+
$this->setContainer($container);
21+
}
22+
23+
/**
24+
* @param Request $request
25+
* @param string|FormTypeInterface $type
26+
*
27+
* @return array
28+
*/
29+
public function getAutocompleteResults(Request $request, $type)
30+
{
31+
$form = $this->container->get('form.factory')->create($type);
32+
$fieldOptions = $form->get($request->get('field_name'))->getConfig()->getOptions();
33+
34+
/** @var EntityRepository $repo */
35+
$repo = $this->container->get('doctrine')->getRepository($fieldOptions['class']);
36+
37+
$term = $request->get('q');
38+
39+
$countQB = $repo->createQueryBuilder('e');
40+
$countQB
41+
->select($countQB->expr()->count('e'))
42+
->where('e.'.$fieldOptions['property'].' LIKE :term')
43+
->setParameter('term', '%' . $term . '%')
44+
;
45+
46+
$maxResults = $fieldOptions['page_limit'];
47+
$offset = ($request->get('page', 1) - 1) * $maxResults;
48+
49+
$resultQb = $repo->createQueryBuilder('e');
50+
$resultQb
51+
->where('e.'.$fieldOptions['property'].' LIKE :term')
52+
->setParameter('term', '%' . $term . '%')
53+
->setMaxResults($maxResults)
54+
->setFirstResult($offset)
55+
;
56+
57+
58+
if (array_key_exists('callback', $fieldOptions)) {
59+
$cb = $fieldOptions['callback'];
60+
61+
$cb($countQB, $request);
62+
$cb($resultQb, $request);
63+
}
64+
65+
$count = $countQB->getQuery()->getSingleScalarResult();
66+
$paginationResults = $resultQb->getQuery()->getResult();
67+
68+
$result = ['results' => null, 'more' => $count > ($offset + $maxResults)];
69+
70+
$accessor = PropertyAccess::createPropertyAccessor();
71+
72+
$result['results'] = array_map(function ($item) use ($accessor, $fieldOptions) {
73+
return ['id' => $accessor->getValue($item, $fieldOptions['primary_key']), 'text' => $accessor->getValue($item, $fieldOptions['property'])];
74+
}, $paginationResults);
75+
76+
return $result;
77+
}
78+
}

0 commit comments

Comments
 (0)