terminal42 / contao-tablelookupwizard
Contao widget to select records from a foreign table. Useful if you have a large set of records.
Package info
github.com/terminal42/contao-tablelookupwizard
Type:contao-bundle
pkg:composer/terminal42/contao-tablelookupwizard
Requires
- php: ^8.3
- codefog/contao-haste: ^5.2
- contao/core-bundle: ^5.7
- symfony/asset: ^7.4
- symfony/config: ^7.4
- symfony/dependency-injection: ^7.4
- symfony/http-foundation: ^7.4
- symfony/http-kernel: ^7.4
Requires (Dev)
- contao/manager-plugin: ^2.0
- terminal42/contao-build-tools: dev-main
Conflicts
- contao/manager-plugin: <2.0 || >=3.0
README
A backend widget for Contao that allows editors to search and select records from a foreign database table without loading the entire dataset.
It is specifically designed for large datasets where a traditional select, checkboxWizard, or static select field would be inefficient or unusable.
Installation
Install via Composer:
composer require terminal42/contao-tablelookupwizard
Version Compatibility
Please choose the appropriate major version based on your Contao version:
- 4.x → for Contao 5.6 and below
- 5.x → for Contao 5.7 and above
Make sure to select the correct major version to ensure compatibility with your Contao installation.
Supporting both versions simultaneously
If your project needs to support both Contao 5.6 and 5.7+, you can allow both major
versions using a version union constraint in your composer.json:
"require": { "terminal42/contao-tablelookupwizard": "^4.0 || ^5.0" }
Composer will then automatically install:
4.xwhen the project uses Contao ≤ 5.65.xwhen the project uses Contao ≥ 5.7
No code changes are needed on your end — the correct version will be resolved
based on the Contao version constraint in your own composer.json.
If you want to support both 4.x and 5.x simultaneously with a custom template,
you need to set customTpl dynamically at runtime rather than statically in the DCA,
since the template name changed between versions. For example, you can use an attributes callback
to detect the installed version and return the correct template name:
<?php use Composer\InstalledVersions; use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; #[AsCallback('tl_my_table', 'fields.my_field.attributes')] class MyFieldAttributesListener { public function __invoke(array $attributes): array { $isNewVersion = version_compare(InstalledVersions::getVersion('terminal42/contao-tablelookupwizard'), '5.0', '>='); if ($newNewVersion) { $attributes['eval']['customTpl'] = 'backend/widget/custom_tablelookupwizard'; $attributes['eval']['customRecordsTpl'] = 'backend/widget/custom_tablelookupwizard_records'; } else { $attributes['eval']['customTpl'] = 'be_widget_tablelookupwizard_content_custom'; $attributes['eval']['customContentTpl'] = 'be_widget_tablelookupwizard_content_custom'; } return $attributes; } }
Migrating from 4.x to 5.x
Several DCA configuration properties were renamed or replaced in 5.x. The table below lists all breaking changes.
Configuration
| Version 4.x | Version 5.x | Notes |
|---|---|---|
fieldType |
multiple |
checkbox → multiple: true, radio → multiple: false |
customLabels |
headerFields |
- |
matchAllKeywords |
searchMatchAll |
- |
enableSorting |
isSortable |
Also requires multiple: true to be effective |
joins |
sqlJoins |
Inner keys also renamed, see below |
joins.[table].jkey |
sqlJoins.[table].joinKey |
- |
joins.[table].fkey |
sqlJoins.[table].foreignKey |
- |
customContentTpl |
customRecordsTpl |
- |
Templates
The default template names have also been renamed. If you have overridden any of the templates in your project, update them accordingly:
| Version 4.x (.html5) | Version 5.x (.html.twig) |
|---|---|
be_widget_tablelookupwizard |
backend/widget/tablelookupwizard |
be_widget_tablelookupwizard_content |
backend/widget/tablelookupwizard_records |
Basic Usage (DCA Example)
'myField' => [ 'inputType' => 'tableLookup', 'eval' => [ 'foreignTable' => 'tl_news', 'listFields' => ['headline'], ], 'sql' => ['type' => \Doctrine\DBAL\Types\Types::INTEGER, 'unsigned' => true, 'default' => 0], ],
Configuration Reference
Required Options
| Option | Type | Default | Description |
|---|---|---|---|
foreignTable |
string |
- | Base database table used for querying records. |
listFields |
string[] |
- | Fields displayed in the result table. Use fully qualified names when using joins. |
Selection Options
| Option | Type | Default | Description |
|---|---|---|---|
multiple |
bool |
false |
Allows selecting multiple records. |
isSortable |
bool |
false |
Enables drag & drop sorting. Only effective when multiple = true. |
headerFields |
string[] |
[] |
Custom column header labels. Must match listFields length. |
Search Options
| Option | Type | Default | Description |
|---|---|---|---|
searchFields |
string[] |
[] |
Fields used for keyword searching. Defaults to listFields. |
searchMatchAll |
bool |
false |
If true, all keywords must match (AND). Otherwise OR logic is used. |
searchLabel |
string |
"Search" |
Label for the search button. |
Template Options
| Option | Type | Default | Description |
|---|---|---|---|
customTpl |
string |
backend/widget/tablelookupwizard |
Custom backend widget template. |
customRecordsTpl |
string |
backend/widget/tablelookupwizard_records |
Template for rendering selected records. |
SQL Options
| Option | Type | Default | Description |
|---|---|---|---|
sqlWhere |
string |
'' |
Additional SQL condition appended to WHERE (without the WHERE keyword). |
sqlOrderBy |
string |
'' |
Adds ORDER BY clause to search results. |
sqlGroupBy |
string |
'' |
Adds GROUP BY clause. |
sqlLimit |
int |
30 |
Maximum number of result rows displayed. |
sqlJoins |
array |
[] |
Defines SQL joins to make related tables available. |
Join Parameters
| Parameter | Type | Description |
|---|---|---|
type |
string |
SQL join type (INNER JOIN, LEFT JOIN, RIGHT JOIN). |
joinKey |
string |
Column on the base table (foreignTable). |
foreignKey |
string |
Column on the joined table. |
After joining, fields can be referenced as:
tl_news_archive.title
Full Configuration Example
'myField' => [ 'inputType' => 'tableLookup', 'eval' => [ // Required 'foreignTable' => 'tl_news', 'listFields' => [ 'tl_news.headline', 'tl_news.date', 'tl_news_archive.title', ], // Selection behavior 'multiple' => true, 'isSortable' => true, 'headerFields' => [ 'Headline', 'Date published', 'News archive', ], // Search behavior 'searchFields' => [ 'tl_news.headline', 'tl_news_archive.title', ], 'searchMatchAll' => true, 'searchLabel' => 'Search records', // Template overrides 'customTpl' => 'backend/widget/custom_tablelookupwizard', 'customRecordsTpl' => 'backend/widget/custom_tablelookupwizard_records', // SQL customization 'sqlWhere' => 'tl_news.protected=1', 'sqlOrderBy' => 'tl_news.date DESC', 'sqlGroupBy' => 'tl_news.pid', 'sqlLimit' => 100, 'sqlJoins' => [ 'tl_news_archive' => [ 'type' => 'INNER JOIN', 'joinKey' => 'pid', 'foreignKey' => 'id', ], ], ], 'sql' => ['type' => \Doctrine\DBAL\Types\Types::BLOB, 'notnull' => false], ],
Data Storage
Storage behavior:
multiple => false→ single ID storedmultiple => true→ serialized array of IDs stored
The DCA field should use:
// multiple => false 'sql' => ['type' => \Doctrine\DBAL\Types\Types::INTEGER, 'unsigned' => true, 'default' => 0], // multiple => true 'sql' => ['type' => \Doctrine\DBAL\Types\Types::BLOB, 'notnull' => false],
Adjust if your implementation differs.
Best Practices
- Use fully qualified column names when joins are present.
- Ensure
headerFieldscount matcheslistFields. - Avoid unsanitized input inside
sqlWhere. - Index searchable fields for performance.