Vidi stands for "versatile and interactive display" and is the code name of a list component designed for listing all kind of records along with advanced filtering capabilities. By default the extension ships two examples for FE Users / Groups but is configurable for any kind of data type.
Veni, vidi, vici!
Home page of the project: https://github.com/fabarea/vidi
Stable version: https://typo3.org/extensions/repository/view/vidi
Development version from Git:
cd typ3conf/ext git clone https://github.com/fabarea/vidi.git
Flash news about latest development are also announced on https://twitter.com/fudriot
Install the extension as normal in the Extension Manager or download it via composer.
`
composer require fab/vidi
`
Configuration is mainly provided in the Extension Manager and is pretty much self-explanatory. Check possible options there.
A pid (page id) is necessary to be defined when creating a new record for the need of TCEmain. This is not the case for all records as some of them can be on the root level and consequently have a pid 0. However most require a pid value greater than 0. In a first place, a global pid can be configured in the Extension Manager which is taken as fallback value. Besides, User TSconfig can also be set which will configure a custom pid for each data type enabling to be fine grained. We can also define additional constraints which can be used to filter by default a Vidi module:
# Short syntax for data type "tx_domain_model_foo": tx_vidi.dataType.tx_domain_model_foo.storagePid = 33 # Extended syntax for data type "tx_domain_model_foo": tx_vidi { dataType { tx_domain_model_foo { # Define the pid for a new record storagePid = 33 # Default criterion to be used as default filter constraints { # Records are found on pid "33" # Possible operators >= > < <= = like 0 = pid = 33 # Records belongs to category 1 OR 2 1 = categories.uid in 1,2 } } } }
To fetch content, the content.find()
View Helper can be used to retrieve data in a Fluid template. Underneath it will
interact with a Vidi Content Repository.
As example, let display a list of all Frontend Users belonging to User Group "1" and also display the User Groups they belong:
<strong>Number of people: {v:content.count(matches: {usergroup: 3}, type: 'fe_users')}</strong> <f:for each="{v:content.find(matches: {usergroup: 3}, orderings: {uid: 'ASC'}, type: 'fe_users')}" as="person"> <div> {person.uid}:{person.username} <!-- !!! Notice how you can fetch relation through their properties! --> <f:if condition="{person.usergroup}}"> <ul> <f:for each="{person.usergroup}" as="group"> <li>{group.title}</li> </f:for> </ul> </f:if> </div> </f:for> {namespace m=Fab\Vidi\ViewHelpers}
We can do the same to retrieve only one record but this time with ViewHelper findOne
:
<v:content.findOne type="fe_users" identifier="123"> <div> <strong>{object.username}</strong> </div> </v:content.findOne>
Or given matches
where multiple criteria can be cumulated. Value foo
corresponds to a field name and bar to a value:
<v:content.findOne type="fe_users" matches="{foo: bar}"> <div> <strong>{object.username}</strong> </div> </v:content.findOne>
An object can be retrieved dynamically given an argument in the URL. We assume, the URL looks as follows https://domain.tld/detail/?tx_vidifrontend_pi1[uid]=164
<v:content.findOne type="fe_users" argumentName="object""> <div> <strong>{object.username}</strong> </div> </v:content.findOne>
If wanted, we can define a custom argument name with attribute argumentName
and also give the object an alias with attribute as
.
The URL would look like: https://domain.tld/detail/?user=164
<v:content.findOne type="fe_users" argumentName="object" as="user"> <div> <strong>{user.username}</strong> </div> </v:content.findOne>
Each Content type (e.g. fe_users, fe_groups) has its own Content repository instance which is managed internally by the Repository Factory. For getting the adequate instance, the repository can be fetched by this code.
// Fetch the adequate repository for a known data type. $dataType = 'fe_users'; $contentRepository = \Fab\Vidi\Domain\Repository\ContentRepositoryFactory::getInstance($dataType); // From there, you can query the repository as you are used to in Flow / Extbase. // Fetch all users having name "foo". $contentRepository->findByName('foo'); // Fetch one user with username "foo" $contentRepository->findOneByUsername('foo'); // Fetch all users belonging to User Group "1". Usergroup must be written that sort following the TCA of fe_users, column "usergroup". $contentRepository->findByUsergroup(1);
For complex query, a matcher object can be instantiated where to add many criteria. The matching criteria will then be interpreted by the Content Repository. Here is an example for retrieving a set of files:
// Initialize a Matcher object. /** @var \Fab\Vidi\Persistence\Matcher $matcher */ $matcher = GeneralUtility::makeInstance('Fab\Vidi\Persistence\Matcher'); // Add some criteria. $matcher->equals('storage', '1'); // "metadata" is required and corresponds to a field path making the join between the "sys_file_metadata" and "sys_file". $matcher->equals('metadata.categories', '1'); // Add criteria with "like" $matcher->like('metadata.title', 'foo'); // Fetch the objects. $files = $contentRepository->findBy($matcher);
Notice: The example would work in the Frontend as well. However, not everything is in place such as localization. Having that on my todo list.
Facets are visible in the Visual Search and enable the search by criteria. Facets are generally mapped to a field but it is not mandatory ; it can be arbitrary values. To provide a custom Facet, the interface FabVidiFacetFacetInterface must be implemented. Best is to take inspiration of the FabVidiFacetStandardFacet.
``` # Example for table fe_users $GLOBALS['TCA']['fe_users']['grid']['facets'][FabVidiFacetCompositeFacet::class] =
- [
'name' => 'uid', 'label' => 'LLL:EXT:my_example/Resources/Private/Language/locallang.xlf:foo', 'configuration' => [
'disable' => 0, 'email' => '*', 'pid' => 123,]
],
For each Vidi module, it is possible to register some tools to do whatever maintenance, utility, processing operations for a content type.
The landing page of the Tools can be accessed by clicking the upper right icon within the BE module. The icon is only displayed if some Tools is available for the User.
To take example, there is a Tool which is shown for admin User that will check the relations used in the Grid.
To register your own Tool, add the following lines into in ext_tables.php
:
if (TYPO3 == 'BE') { // Register a Tool for a FE User content type only. \Fab\Vidi\Tool\ToolRegistry::getInstance()->register('*', 'Fab\Vidi\Tool\RelationAnalyserTool'); // Register some Tools for all Vidi modules. \Fab\Vidi\Tool\ToolRegistry::getInstance()->register('fe_users', 'Fab\Vidi\Tool\RelationAnalyserTool'); }
Permissions whether the Tool is accessible or not is defined in the Tool class itself. We can control through the method isShown what BE groups (admin, editors, ...) is allowed to access the tool. However, the rules can be overridden programmatically via the API by adding configuration in ext_tables. Assuming we want to allow the access to every BE Users for the "find duplicates" tool provided in Media, we would do something:
\Fab\Vidi\Tool\ToolRegistry::getInstance() ->overridePermission('sys_file', 'Fab\Media\Tool\DuplicateFilesFinderTool', function() { return true; }); # where: # * sys_file: the scope of the, can be possibly '*' for every data type. # * DuplicateFilesFinderTool: the name of the tool. # * function(): the closure to override the default permissions.
The Grid is the heart of the List component in Vidi. The TCA was extended to describe how a grid and its columns should be rendered. Take inspiration of this example below for your own data type:
'grid' => [ 'columns' => [ '__checkbox' => [ 'renderer' => \Fab\Vidi\Grid\CheckBoxRenderer::class, ], 'uid' => [ 'visible' => false, 'label' => 'Id', 'width' => '5px', ], 'username' => [ 'visible' => true, 'label' => 'LLL:EXT:vidi/Resources/Private/Language/fe_users.xlf:username', ], 'usergroup' => [ 'visible' => true, 'label' => 'LLL:EXT:vidi/Resources/Private/Language/fe_users.xlf:usergroup', ], '__buttons' => [ 'renderer' => \Fab\Vidi\Grid\ButtonGroupRenderer::class, ], ], ],
'grid' => [ 'excluded_fields' => 'foo, bar', 'export' => [], 'facets' => [], 'columns' => [] ],
- Key
- included_fields
- Description
- Strictly include this CSV list of fields
- Key
- excluded_fields
- Description
- Whenever some fields should be excluded from the Grid
- Key
- export
- Description
- Configuration for data export, CSV, XML, ...
- Key
- facets
- Description
- Configuration for the facets
- Key
- columns
- Description
- Configuration for the columns in the Grid
Configuration of $GLOBALS['TCA']['tx_foo']['grid']['columns']['field_name']
as example:
'grid' => array( 'columns' => array( 'username' => array( 'visible' => true, 'label' => 'LLL:EXT:vidi/Resources/Private/Language/fe_users.xlf:username', ), ), ),
Possible key and values that can be assigned for a field name:
- Key
- sortable
- Datatype
- boolean
- Description
Whether the column is sortable or not. This value is not respected if the table has a "sortby" value as:
['ctrl']['sorby'] => 'sorting'
- Default
- true
- Key
- visible
- Datatype
- boolean
- Description
- Whether the column is visible by default or hidden. If the column is not visible by default it can be displayed with the column picker (upper right button in the BE module)
- Default
- true
- Key
- renderer
- Datatype
- string
- Description
- A class name implementing Grid Renderer Interface
- Default
- NULL
- Key
- format
- Datatype
- string
- Description
- A full qualified class name implementing
\Fab\Vidi\Formatter\FormatterInterface
- Default
- NULL
- Key
- label
- Datatype
- string
- Description
- An optional label overriding the default label of the field - i.e. the label from TCA['tableName']['columns']['fieldName']['label']
- Default
- NULL
- Key
- editable
- Datatype
- string
- Description
- Whether the field is editable or not.
- Default
- NULL
- Key
- dataType
- Datatype
- string
- Description
- The table name where the field belong. Only defines this option if the field comes from another table. A Grid Render will be necessary to render the content.
- Default
- NULL
- Key
- class
- Datatype
- string
- Description
- Will display the class name to every cell.
- Default
- NULL
- Key
- wrap
- Datatype
- string
- Description
- A possible wrapping of the content. Useful in case the content of the cell should be styled in a special manner.
- Default
- NULL
- Key
- width
- Datatype
- int
- Description
- A possible width of the column
- Default
- NULL
- Key
- canBeHidden
- Datatype
- boolean
- Description
- Whether the column can be hidden or not.
- Default
- true
- Key
- localized
- Datatype
- boolean
- Description
- If a field is configured to be localized in the TCA, there is the chance to force not to be localized in the Grid.
- Default
- true
Configuration of $GLOBALS['TCA']['tx_foo']['grid']['facets']
as example:
'grid' => array( 'facets' => array( 'uid', 'username', .... ), ),
List of fields considered as facets.
Configuration of $GLOBALS['TCA']['tx_foo']['grid']['export']
as example:
'grid' => array( 'export' => array( 'include_files' => false, ), ),
Possible key and values that can be assigned for key export
- Key
- include_files
- Description
- Whether to zip files along with the CSV, XML, ... file
- Default
- true
- Key
- show_wizard (not implemented)
- Description
- Display a pop up windows where it is possible to select what fields are being exported.
Special key for Vidi configuration if needed.
Configuration of $GLOBALS['TCA']['tx_foo']['vidi']
as example:
'vidi' => array( 'mappings' => array( // field_name => propertyName 'TSconfig' => 'tsConfig', 'felogin_redirectPid' => 'feLoginRedirectPid', 'felogin_forgotHash' => 'feLoginForgotHash', ), ),
Possible key and values that can be assigned for key vidi
- Key
- mappings
- Description
Mapping rules when the field name does not follow the underscore name conventions filed_name -> propertyName Vidi needs a bit of help to find the equivalence.
Example:
"WeirdField_Name" => 'weirdFieldName'
By default the value of the column is displayed without further processing except the HTML entities conversion. In some cases, it is wanted to customize the output for instance whenever displaying relations. A Grid Renderer can be configured for the column as example. You can write your custom Grid Renderer, they just have to implement Grid Renderer Interface.
Basic Grid Renderer:
# "foo" is the name of a field and is assumed to have a complex rendering 'foo' => array( 'label' => 'LLL:EXT:lang/locallang_tca.xlf:tx_bar_domain_model.foo', // Label is required 'renderer' => \Fab\Vidi\Grid\RelationRenderer::class, ),
Grid Renderer with options:
# "foo" is the name of a field and is assumed to have a complex rendering 'foo' => array( 'label' => 'LLL:EXT:lang/locallang_tca.xlf:tx_bar_domain_model.foo', // Label is required 'renderer' => \Fab\Vidi\Grid\GenericColumn:class, 'rendererConfiguration' => [ 'foo' => 'bar' ] ),
Multiple Grid Renderers with options:
'foo' => array( 'label' => 'LLL:EXT:lang/locallang_tca.xlf:tx_bar_domain_model.foo', // Label is required 'renderers' => [ \Fab\Vidi\Grid\RelationRenderer:class, ... // more possible renderers ], ),
You can format the value of a column by using one of the built-in formatter of vidi or a custom formatter.
There are two built-in formatters:
\Fab\Vidi\Formatter\Date
- formats a timestamp with d.m.Y\Fab\Vidi\Formatter\Datetime
- formats a timestamp with d.m.Y - H:i
If you want to provide a custom formatter, it has to implement \Fab\Vidi\Formatter\FormatterInterface
Example, using a built-in formatter:
'starttime' => array( 'label' => ... 'format' => 'Fab\Vidi\Formatter\Date', ),
Example, using the custom FancyDate formatter from the Acme Package:
'starttime' => array( 'label' => ... 'format' => 'Acme\Package\Vidi\Formatter\FancyDate', ),
This API enables to fetch info related to TCA in a programmatic way. Since TCA covers a very large set of data, the service is divided in types. There are are four parts being addressed: table, field, grid and form. The "grid" TCA is not official and is extending the TCA for the needs of Vidi.
- table: deals with the "ctrl" part of the TCA. Typical info is what is the label of the table name, what is the default sorting, etc...
- field: deals with the "columns" part of the TCA. Typical info is what configuration, label, ... has a field name.
- grid: deals with the "grid" part of the TCA.
- form: deals with the "types" (and possible "palette") part of the TCA. Get what field compose a record type.
The API is meant to be generic and can be re-use for every data type within TYPO3. Some code examples.
use Fab\Vidi\Tca\Tca; # Return the field type Tca::table($tableName)->field($fieldName)->getType(); # Return the translated label for a field Tca::table($tableName)->field($fieldName)->getLabel(); # Get all field configured for a table name Tca::table($tableName)->getFields(); ...
Internally, Vidi makes an automatic conversion of a field name (in the database) to a property name (of the object)
following a camel-case convention. Example field_name
will be converted to propertyName
.
However, there could be special cases where the field name does not follow the conventions for legacy reason. Vidi needs a bit of help to find the equivalence fieldName <-> propertyName. This can be addressed by configuration:
# Context: $GLOBALS['fe_users']['vidi'] # Example used for "fe_users" 'vidi' => array( 'mappings' => array( 'lockToDomain' => 'lockToDomain', 'TSconfig' => 'tsConfig', 'felogin_redirectPid' => 'feLoginRedirectPid', 'felogin_forgotHash' => 'feLoginForgotHash', ), ),
For actions such as "update", "remove", "copy", "move", the DataHandler of the Core is configured to be used by default. It will work fine in most cases. However, there is the chance to call your own Data Handler if there are special needs (@see FileDataHandler in EXT:media) Another reasons, would be for speed. You will notice a performance impact when mass editing data and relying on the Core DataHandler at the same time. While it will disconnect you from TCEmain (which handles for your hooks, cache Handling, etc... ), using your own DataHandler will make the mass processing much faster.
# Context: $GLOBALS['sys_file']['vidi'] # Example used for "sys_file" 'vidi' => array( 'data_handler' => array( // For all actions '*' => 'TYPO3\CMS\Media\DataHandler\FileDataHandler' // Or for individual action ProcessAction::UPDATE => 'MyVendor\MyExt\DataHandler\FooDataHandler' ), ),
If you need to hook into a module and add some custom behaviour for a new button or replacing a Component, you can configure the Module through the Vidi Module Loader. As a quick example:
$moduleLoader = GeneralUtility::makeInstance('Fab\Vidi\Module\ModuleLoader', 'sys_file'); $moduleLoader->addJavaScriptFiles(...)
For more insight, consider the example of ext_tables.php
in extension Media.
Notice also for each Vidi module, you can add any kind of utility tools in a dedicated module (c.f. Add tools in a Vidi module).
- What about performance?
We don't have figures. However, Vidi is quite close to the database and if the index are well configured, Vidi modules behave quite well when dealing with large amount of data. In general, Vidi is capable to fetch just the exact number of records required. Furthermore, Vidi is capable of internally caching data in memory such as relations once they have been fetched.
If you experiment a slow Grid, consider reducing the number of column visible among other the "relational" columns which are the most expensive to render. If a column is hidden in the Grid, the content will not be computed for performance sake.
- How to get started with a new custom Vidi module?
As of 0.4.0 Vidi comes with a new experimentation in the form of a "list2". The idea is to bring all the power of Vidi to every record types without further configuration.
It can be activated in the settings of the Extension Manager. If you need further configure the Grid, take example how it is done for fe_users in file EXT:vidi/Configuration/Overrides/fe_users.php
.