Login / Status
developer.Resource
Home . Documentation . Document Library . Extension Manuals
Sponsors
hosted by punkt.deTYPO3 and Open Source Magazine

Chapter 1. EXT: Dynamic FlexForms

Extension Key: dynaflex

Copyright 2005 - 2007, Thomas Hempel, <thomas@work.de>

This document is published under the Open Content License

available from http://www.opencontent.org/opl.shtml

The content of this document is related to TYPO3

- a GNU/GPL CMS/Framework available from www.typo3.com

1.1. Introduction

What does it do?

Dynaflex gives you the possibility to change an array defined by a configuration. That doesn't sounds very cool but it can handle XML data structures inside of such an array. That makes it possible to change the TCA on the fly. The result is, that you can change the TCA before it's displayed in the backend. This modifications are configurable and can depend on entries in database or formfields inside a FlexForm. For example, you can realize that a tab divider is only visible if at least one fe_user exists.

You can insert fields into a FlexForm data structure depending on the number of entries in a specific table. For example, create an input field for every entry in the table be_users that are not hidden.

Actually you can do anything you want, if a functionality is not implemented in the dynaflex extension, you can call a userfunction, that returns a TCA configuration for example.

Basics

Here I will explain some basic things that are used in the context of this extension manual. You should read this carefully to understand what I'm talking about in the rest of this document.

The dynaflex extension is not for end users but for developers!

DCA

DCA stands for Dynaflex Configuration Array. It's very similar to the TCA. If you understood the scheme of the TCA, understanding the DCA shouldn't be a problem. :-)

Paths

When I'm talking about paths, I always mean a path inside of an array. In most cases this will be inside the TCA, but it also can be inside a FlexForm data structure (which is also an array after all).

If you specify a path, you do it the same way as you'll do it on *NIX systems. Every key inside the array is like a folder in a file system. There is only one difference to a file system, the path doesn't start with a slash! For example, if you have the following array:

TCA

element1

subelement1

sub1sub1field1

subsub1value1

sub1sub1field2

subsub1value2

subsub1value1

sub1sub1field2

subsub1value2

sub1sub1field2

subsub1value2

subsub1value2

subelement2

subsub2field1

subsub2value1

sub1sub1field1

subsub1value1

sub1sub1field2

subsub1value2

subsub1value1

sub1sub1field2

subsub1value2

sub1sub1field2

subsub1value2

subsub1value2

subelement2

subsub2field1

subsub2value1

subelement2

subsub2field1

subsub2value1

subsub2field1

subsub2value1

subsub2value1

element2

sub2field1

sub2value1

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2value1

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2field3

sub2value3

sub2field4

sub2value4

sub2value3

sub2field4

sub2value4

sub2field4

sub2value4

sub2value4

subelement1

sub1sub1field1

subsub1value1

sub1sub1field2

subsub1value2

subsub1value1

sub1sub1field2

subsub1value2

sub1sub1field2

subsub1value2

subsub1value2

subelement2

subsub2field1

subsub2value1

sub1sub1field1

subsub1value1

sub1sub1field2

subsub1value2

subsub1value1

sub1sub1field2

subsub1value2

sub1sub1field2

subsub1value2

subsub1value2

subelement2

subsub2field1

subsub2value1

subelement2

subsub2field1

subsub2value1

subsub2field1

subsub2value1

subsub2value1

element2

sub2field1

sub2value1

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2value1

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2field3

sub2value3

sub2field4

sub2value4

sub2value3

sub2field4

sub2value4

sub2field4

sub2value4

sub2value4

element2

sub2field1

sub2value1

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2value1

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2field3

sub2value3

sub2field4

sub2value4

sub2value3

sub2field4

sub2value4

sub2field4

sub2value4

sub2value4

sub2field1

sub2value1

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2value1

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2field2

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2value2

sub2field3

sub2value3

sub2field4

sub2value4

sub2field3

sub2value3

sub2field4

sub2value4

sub2value3

sub2field4

sub2value4

sub2field4

sub2value4

sub2value4

 

If you want to edit the field “subsub1value1” the path to this field will be:

TCA/element1/subelement1/sub1sub1field1

Where to instantiate

Before the dynaflex extension will do anything, you have to configure it (section “Configuration”) and than you have to instantiate and call it at the right position of the TYPO3 TCA processing.

Since version 1.7.0 this has be improved dramatically!

Since that version, DynaFlex implements all needed hooks by itself. This has various advantages. On the one hand, you don't have to register the hooks by your own. And the second one is, that through the way of implementation, you don't have to think about how DynaFlex has to be called. And last but not least, with version 1.7.0 I introduced hooks for config loading. That means, that you can alter the DCA of another extension by using a hook that is called while the configuration is loaded.

The old way of calling will work as usual of course, but it is highly recommend to use the new way of calling DynaFlex!!!

The recommend way

Since version 1.7.2

You have to create a new class for your configuration. Just create file that can be called like class.tx_dynaflextut_dfconfig.php and place a class in it that fits the usual naming conventions. Inside of this class you just have to define to variables.

  1. $rowChecks – Defines some rules when the DCA should be executed. (since version 1.9.0!!!)

  2. $DCA – Contains the DCA for your table.

  3. $cleanUpField – This defines the table for which the flexform is defined. This field is cleaned up at the end of the modifying process.

  4. $hooks – May contain hooks for altering the DCA for a table

Such a file can look like this:

class tx_dynaflextut_dfconfig{

var $rowChecks = array (

// add your checks here

);

var $DCA = array (

0 => array (

'path' => 'tx_dynaflextut_test/columns/flexdata/config/ds/default',

'modifications' => array (

--- snip ---

)

)

);

var $cleanUpField = 'flexdata';

var $hooks = array(

'EXT:dynaflex_tut/class.tx_dynaflextut_dcahooks.php:tx_dynaflextut_dcahooks'

);

}

You now may ask, where the table is defined that DynaFlex knows when it should call itself. This is defined in the registration. Open the ext_localconf.php of your extension and register your config class like this:

$GLOBALS['T3_VAR']['ext']['dynaflex']['tx_dynaflextut_test'][] = 'EXT:dynaflex_tut/class.tx_dynaflextut_dfconfig.php:tx_dynaflextut_dfconfig';

As you can see, in the registration path you have to define for which table you want to register the configuration. In this case we're registering it for the table “tx_dynaflextut_test”.

If you don't submit a path to a class but the simple string “TS”, DynaFlex will load the DCA not from a class but from the Tsconfig of the page. See section configuration.

The old fashioned way

I recommend to use DynaFlex with TYPO3 version 3.8.0 and higher. There is one hook available where the flexform processing should be started. This hook can be found in the befunc class and is called “getFlexFormDS_postProcessDS”. This is processed whenever a flexform field in the backend is processed. It's highly recommend to use this hook because only with this hook it's possible to create file upload fields (type “group”). I will not try to explain why it is so but it takes me a few hours to find it out. ;-)

Anyway, at first you have to register your hook method. This is normally done in the ext_localconf.php of your extension. The code should look similar to this snippet:

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['getFlexFormDSClass'][] = 'EXT:dynaflex_tut/class.tx_dynaflextut_befunc_hooks.php:tx_dynaflextut_befunc_hooks';

This code comes from the dynaflex_tut extension that is availble from the TER. The hook code looks like this:

class tx_dynaflextut_befunc_hooks{
function getFlexFormDS_postProcessDS(&$dataStructArray, $conf, $row, $table, $fieldName){
global $counter;
$counter++;
if ($table == 'tx_dynaflextut_test'&&
t3lib_extMgm::isLoaded('dynaflex') &&
$counter <= 1
){
// Load the configuration from a file
require_once(t3lib_extMgm::extPath('dynaflex_tut') .'df_config.php');
// And start the dynaflex processing
require_once(t3lib_extMgm::extPath('dynaflex') .'class.dynaflex.php');
$dynaflex = t3lib_div::makeInstance('dynaflex');
// debug($GLOBALS['TCA']['tx_dynaflextut_test']);
$dynaflex->init($GLOBALS['TCA'], $GLOBALS['T3_VAR']['dynaflex']['dca']);
// process DCA and read dataStructArray from index 0
$GLOBALS['TCA'] = $dynaflex->getDynamicTCA();
$dataStructArray = $dynaflex->dataStructArray[0];
// at last cleanup the XML structure in the database
$dynaflex->doCleanup('flexdata');
}
}
}

This code works only with version 1.5.0 and higher!!!

Most things should be clear in this code. Maybe you wonder about the $counter. This workaround is needed to avoid that dynaflex processes the config more than one time. This is needed, because the code is called every time a single flexform field is processed. So we count the number fields that where already processed and call dynaflex only if we are in the first cycle. That is enough to build up the dynamic flexform structure.

But to be honest, there is one bid disadvantage of this call. If you use a fieldvalue a source for one or more modifications, the changes take effect after second save and not after the first click on “save”. To avoid this, you can use another hook in the tceforms class. This hook is called “getMainFields_preProcess”. The registration and call of this hook is likely the same than the befunc hook. But keep in mind that if you use this tceforms hook that dynaflex is processing the modifications twice. I will work on a proper solution for this odd behavior.

Register tceform hook:

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms.php']['getMainFieldsClass'][] = 'EXT:dynaflex_tut/class.tx_dynaflextut_tceforms_hooks.php:tx_dynaflextut_tceforms_hooks';

Tceforms hook:

function getMainFields_preProcess($table, $row, $pObj){
if ($table == 'tx_dynaflextut_test'&&
t3lib_extMgm::isLoaded('dynaflex')
){
// Load the configuration from a file
require_once(t3lib_extMgm::extPath('dynaflex_tut') .'df_config.php');
// And start the dynaflex processing
require_once(t3lib_extMgm::extPath('dynaflex') .'class.dynaflex.php');
$dynaflex = t3lib_div::makeInstance('dynaflex');
$dynaflex->init($GLOBALS['TCA'], $GLOBALS['T3_VAR']['dynaflex']['dca']);
$GLOBALS['TCA'] = $dynaflex->getDynamicTCA();
$dynaflex->doCleanup('flexdata');
}
}

How configuration is processed

The configuration of dynaflex is processed in the same order as it's keys! That means that you have to keep track of the keys you give your configurations. This is good to know, because you can reference to field you have dynamically created in a modfication step before. I know that sounds a little bit coated but believe me, it can be useful. :-)

Regulate execution

In some cases you might register DynaFlex for general table like tt_content. This is the case if you want to modify the flexform of a frontend plugin. The problem is, that DynaFlex will execute the modification every time tt_content is loaded. This means for every content element!

Since version 1.9 you can define some restriction to make sure that DynaFlex will only start if the dataset we're working on accomplishes some rules. These rules or conditions can be defined in the configuration class in a variable called $rowChecks.

$rowChecks is an array of key value pairs where the keys are the fieldnames in the table we are working on and the values are the values the tablefield should have. If any of the tablefields does not have the value that is set in the $rowChecks array, the complete DCA won't be executed!

Here is an example. This checks if the CType of the tt_content element is 'list' (Insert Plugin) and that the selected plugin is 'dynaflex_tut_pi1':

var $rowChecks = array (
    'list_type' => 'dynaflex_tut_pi1',
    'CType' => 'list'

);

Cleanup the data

Since version 1.4.0 you can force a cleanup of your data in the database. The problem is, that the values of a flexform are stored in a XML structure which holds each field you have ever had in your flexform. That is not a problem if the flexforms are static. If you use DynaFlex, fields can disappear from a form and so the data in the database is holding data for a field that doesn't exist.

Since version 4.0 of TYPO there is a class called “flexformtools” which provides a method that removes all flexform fields from the datastructure that are not present in the TCA. In DynaFlex this method is used to provide a method that cleans up the database. This method also works for pre 4.0 version of TYPO3. DynaFlex ships with a copy of this flexformtools class.

All you have to do is, to call the method “doCleanup” after you have written back the TCA. The following code snippet shows this:

   1: $dynaflex->init($GLOBALS['TCA'], $dca);
   2: 
   3:     // write back the modified TCA
   4: $GLOBALS['TCA'] = $dynaflex->getDynamicTCA();
   5: 

   6: $dynaflex->doCleanup('flexdata');

The argument you pass to this method is the field of the content element that is used to store the data XML. In this case the field is called “flexdata”. This is also the field for which the flexform structure is defined in the TCA.

This is obsolete with version 1.7.0! The cleanup is done automatically by DynaFlex if you use the recommend way of calling it.

Using hooks to alter configurations

Since the new way of calling DynaFlex was introduced it's possible to register hooks that can alter the DCA of other extensions. The only condition is, that the author of the orginal extension has used the new way. If he has not, the hooks are normally not available.

The way of registering a hook is the same as you might know it from TYPO3 itself. Only the location is a different one. If you take a look at the section before (Where to instanciate – the recommend way) you'll find an example config class for DynaFlex. There you can see that the hooks are registered inside that config classes in the variable $hooks. Just enter the path to your hook class in such a config class as new array element and register the class for the targeted table.

Now everytime time, DynaFlex is processing this table, it will load the regsitered DCA and will look for all hooks that might be registered for it.

Keep in mind that it makes in which order the extensions are installed. The extension that provides the orginal DCA has to be installed before any extension that provides altering hooks!!!

Inside the hook class you have to define a methd called alterDCA_onLoad. This method will receive two arguments. The first one is the DCA with all modifications at the time the hook is called. This variable is called by reference! So you your method don't have to return anything. Just change the DCA.

The second parameter is the name of the table we are working on. This is not really needed because you should know for which table you have regsitered your hook but it makes it easier to change the tablename later on if you use it in the alter method. An example hook could look like this:

function alterDCA_onLoad($currentDCA, $table){
// store the last modification (the move)
$lastIndex = count($currentDCA[0]['modifications']) -1;
$lastModification = $currentDCA[0]['modifications'][$lastIndex];
// write the new one
$currentDCA[0]['modifications'][$lastIndex] = array (
'method' => 'add',
'path' => 'ROOT/el',
'type' => 'field',
'field_config' => array (
'name' => 'df_field_fromHook',
'label' => 'A field from a hook',
'config' => array (
'type' => 'input'
)
)
);
// and append the former last one at the end again
$currentDCA[0]['modifications'][] = $lastModification;

}