Contao filter bundle
This bundle offers a generic filter module to use with arbitrary contao entities containing standard filter with initial filters and filter form types including symfony form type representations.
Features
codefog/tags-bundle
integrationheimrichhannot/contao-categories-bundle
integration- Form handling using symfony form component
- Form rendering by using symfony form templates (currently available: bootstrap 3, bootstrap 4, foundation, div, table)
- Numerous symfony form types supported (see: http://symfony.com/doc/3.4/reference/forms/types.html)
- Highly customizable and detached from tl_module table
- Label/Message handling using symfony translations
- Render form always empty (without user selection)
- Merge data over multiple filter forms with same form name
- Default Values (can be overwritten by user)
- Initial Values (can`t be overwritten by user)
- Stores filter data in session (no GET parameter URL remnant)
- Content element "Filter-Preselect" with optional redirect functionality to preselect filter on given page
- Content element "Filter-Hyperlink" with filter preselect feature
Usage
Install
-
Install with composer or contao manager
composer require heimrichhannot/contao-filter-bundle
-
Update database
We recommend to use this bundle toghether with List Bundle and Reader Bundle.
Setup
- Create a filter configuration within System -> Filter & sort configuration
- Add filter elements to the filter config.
- If you want to show the filter somewhere (for example to filter a list), create a filter/sort frontend module.
Wrapper elements (DateRange, ProximitySearch, ...)
The Wrapper element has to be places before the fields associated with them. For example the date_range wrapper element needs to be placed before the two associated date fields.
Preselect
Filter Bundle Forms are not typical GET-Forms, so it is not possible to simple copy the filter urls to share or bookmark a filtered list. To overcome this limitation, preselect urls can be generated. Preselect urls for the current filter can be found within template variabled, you can create a preselect content element or get the url programmatically from the FilterConfig.
Template variables
If a filter is set, the variable preselectUrl
contains the preselection url for the current filter. It's available in the filter templates and the frontend module template.
You can for example create a copy preselect url button:
{% if preselectUrl is defined and preselectUrl is not empty %}
<div class="col-xd-12 col-md-3">
<a class="btn btn-primary" onclick="navigator.clipboard.writeText('{{ preselectUrl }}');alert('Copied preselect link!');return false;">Filtervorauswahllink kopieren</a>
</div>
{% endif %}
Content element
You can use one of the following content elements:
- "Filter-Preselect" with optional redirect functionality to preselect filter on given page
- "Filter-Hyperlink" with filter preselect feature
FilterConfig
You can generate the preselect link from the FilterConfig instance
<?php
use HeimrichHannot\FilterBundle\Manager\FilterManager;
class CustomController {
private FilterManager $filterManager;
public function invoke(): string
{
$filterConfig = $this->filterManager->findById($this->objModel->filter);
return !empty($filterConfig->getData()) ? $filterConfig->getPreselectAction($filterConfig->getData(), true) : ''
}
}
Inserttags
Insert tag | Arguments | Description |
---|---|---|
{{filter_reset_url::*::*}} |
filter ID :: page ID or alias | This tag will be replaced with a reset filter link to an internal page with (replace 1st * with the filter ID, replace 2nd * with the page ID or alias) |
Templates (filter)
There are two ways to define your templates.
1. By Prefix
The first one is to simply deploy twig templates inside any templates
or bundles views
directory with the following prefixes:
** filter template prefixes**
filter_
More prefixes can be defined, see 2nd way.
2. By config.yml
The second on is to extend the config.yml
and define a strict template:
Plugin.php
<?php
class Plugin implements BundlePluginInterface, ExtensionPluginInterface
{
/**
* {@inheritdoc}
*/
public function getBundles(ParserInterface $parser)
{
…
}
/**
* {@inheritdoc}
*/
public function getExtensionConfig($extensionName, array $extensionConfigs, ContainerBuilder $container)
{
return ContainerUtil::mergeConfigFile(
'huh_filter',
$extensionName,
$extensionConfigs,
__DIR__ .'/../Resources/config/config.yml'
);
}
}
config.yml
huh:
filter:
templates:
- {name: form_div_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_div_layout.html.twig'}
- {name: form_table_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_table_layout.html.twig'}
- {name: bootstrap_3_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_bootstrap_3_layout.html.twig'}
- {name: bootstrap_3_horizontal_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_bootstrap_3_horizontal_layout.html.twig'}
- {name: bootstrap_4_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_bootstrap_4_layout.html.twig'}
- {name: bootstrap_4_horizontal_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_bootstrap_4_horizontal_layout.html.twig'}
- {name: foundation_5_layout, template: '@HeimrichHannotContaoFilter/filter/filter_form_foundation_5_layout.html.twig'}
template_prefixes:
- filter_
Developers
Events
Event | Event ID | Description |
---|---|---|
Adjust filter options | huh.filter.event.adjust_filter_options_event |
|
Adjust filter value | huh.filter.event.adjust_filter_value_event |
|
FilterConfigInitEvent | FilterConfigInitEvent::class | Modify config on FilterConfig initialization. |
FilterBeforeRenderFilterFormEvent | FilterBeforeRenderFilterFormEvent:class | Modify the filter form template context before rendering |
FilterFormAdjustOptionsEvent | FilterFormAdjustOptionsEvent::class | Modify form options before building the form. |
FilterQueryBuilderComposeEvent | FilterQueryBuilderComposeEvent::class | Description provided below. |
ModifyJsonResponseEvent | huh.filter.event.modify_json_response_event |
Modify the JSON response of async form submits. |
FilterQueryBuilderComposeEvent
In this event you can modify the data before the query for the current element is created and added to the QueryBuilder. It is also possible to add a query within in the event and skip the subsequent query creating.
function __invoke(FilterQueryBuilderComposeEvent $event): void
{
// modify values before creating the query
if ($event->getName() === 'my_field') {
if ("special_value" === $event->getValue()) {
$event->setOperator(DatabaseUtil::OPERATOR_NOT_IN);
$event->setValue([3,5]);
return;
}
}
// create a custom query and skip the default query creation pars.
if ($event->getName() === 'totally_custom_field') {
// do some magic
$event->getQueryBuilder()->andWhere('custom_table_field REGEXP '.$magicValue);
$event->setContinue(false);
}
}
Bootstrap 4 form snippets
The following bootstrap 4 form theme snippets can be used to generate uncommon, but existing bootstrap 4 form widgets within your custom filter_form_bootstrap4*.html.twig
template.
Radio buttons
Replace categories
with the name of your custom field. Remove onchange
handler if not required. Select fallback can be used on small devices, if too many options, display/hide, using @media
breakpoints.
{% if(form.categories is defined) %}
<div class="disable-hidden-inputs {{ ('form-group' ~ ' ' ~ form.categories.vars.id ~ ' ' ~ form.categories.vars.name ~ ' ' ~ form.categories.vars.attr.class)|trim }}">
{{ form_label(form.categories) }}
{% do form.categories.setRendered %}
<div class="select-fallback">
<label class="form-control-label" for="{{ form.categories.vars.id }}-select">{{ form.categories.vars.label|trans }}</label>
<select id="{{ form.categories.vars.id }}-select" onchange="this.form.submit();" name="{{ form.categories.vars.full_name }}" class="form-control">
{% for key,choice in form.categories.vars.choices %}
<option data-icon="category-{{ choice.value }}" value="{{ choice.value }}"{{ (choice.value == form.categories.vars.value ? ' selected' : '') }}>{{ choice.label }}</option>
{% endfor %}
</select>
</div>
<div class="btn-group btn-group-toggle" data-toggle="buttons">
{% for key,choice in form.categories.vars.choices %}
<label class="{{ ('btn btn-link' ~ (choice.value == form.categories.vars.value ? ' active' : ''))|trim }}">
<input type="radio" id="{{ form.categories.vars.id }}_{{ key }}" {% if choice.value == form.categories.vars.value %}checked{% endif %}
autocomplete="off" onchange="this.form.submit();" name="{{ form.categories.vars.full_name }}" value="{{ choice.value }}">
{{ choice.label }}
</label>
{% endfor %}
</div>
</div>
{% endif %}
You must also set the non visible inputs to disabled
in order to prevent them from being submitted and overwrite selected value due to same input name. The following script can be used to achieve this behavior.
(function($) {
function disableHiddenInputs () {
var $selector = $('.disable-hidden-inputs');
$selector.find(':hidden').children(':input').prop('disabled', true);
$selector.find(':not(:hidden)').children(':input').prop('disabled', false);
};
$(function() {
disableHiddenInputs();
});
$(window).resize(function() {
disableHiddenInputs();
});
})(jQuery);