Block controls

Controls are used to manage dynamic values in blocks.

To learn how to declare and use controls, see block-editor.

The role of a control is to convert a <Control /> tag in an usable input which works in a similar manner in builders supported by blocks.

Currently, supported builders are:

Each new control must be supported by the 3, and must have the same render and behavior for each one.

Reference

Core controls are defined in tangible-blocks/includes/template/controls/fields.

Register

To register a new control, you need to use the register_control() method, defined by tangible_blocks.

This method takes 2 arguments:

  • $type - The name of the control in our system.
  • $builder_types - An associative array, with the equivalent name of the control for each builder
    • beaver-builder - See available control types.
    • elementor - See available control types. We get Elementor's control types from a static variable. To avoid errors when Elementor is not installed, we use a method to get it safely (see this commit).
    • gutenberg - Gutenberg works differently, we will have to create our own control fields. Instead of passing the control type here, we pass the type of variable we will save (Gutenberg needs to know it to work). You can see allowed types here.

$tangible_blocks = tangible_blocks();

$type = 'text';
$builder_types = [
    'elementor'       => $tangible_blocks->get_elementor_control_type('TEXT'),
    'beaver-builder'  => 'text',
    'gutenberg'       => 'string',
];

$control = $tangible_blocks->register_control( $type, $builder_types );

For each controls defined, tangible_blocks get all his attributes and return it an associative array.

For example, if we have this control:

<Control type="text" name="text-value" label="Text" default="Header text" />

We will get an array like this one:

[
    'type'    => 'text',
    'name'    => 'text-value',
    'label'   => 'text-value',
    'default' => 'text-value',
]

We will use this array to pass parameters to each builder, in a format they expect.

To acheive this, we register a callback (different for each builder), with the following arguments:

  • $field - The associatve array with control's attributes.
  • $type - The type of field according to the current builder (defined in register_control()).

Example for Elementor and Beaver Builder:


/**
 * @see https://developers.elementor.com/elementor-controls/text-control/
 */

$control->elementor(function($field, $type) {
    return [
        'label'   => $field['label'],
        'type'    => $type,
        'default' => isset($field['default']) ? $field['default'] : ''
    ];
});

/**
 * @see https://docs.wpbeaverbuilder.com/beaver-builder/developer/custom-modules/cmdg-10-setting-fields-reference/#text-field
 */

$control->beaver_builder(function($field, $type) {
    return [
        'label'   => $field['label'],
        'type'    => $type,
        'default' => isset($field['default']) ? $field['default'] : ''
    ];
});

For Gutenberg, we will create a React component to handle each control type. The $field data are available in JavaScript, so we don't need to pass everything in this callback. In PHP, Gutenberg never needs more than the default value and the type.

Example for Gutenberg:


/**
 * @see https://developers.elementor.com/elementor-controls/text-control/
 */

$control->gutenberg(function($field, $type) {
    return [
      'type'    => $type,
      'default' => isset($field['default']) ? $field['default'] : ''
    ];
});

The React component must be added in getField():


const { wp } = window

const {
  components: { TextControl },
} = wp

export const getField = (field, props) => {

    // ...

    switch(field.type) {

        // ...

        /**
         * When it's possible, we try to use existing components from WordPress core
         * 
         * @see https://developer.wordpress.org/block-editor/reference-guides/components/ 
         */ 

        case 'text':
            return( 
                <TextControl
                    label={ field.label }
                    value={ value } // Value is defined earlier in the function
                    onChange={ value => props.setAttributes({[field.name]: value}) }
                />
            )

        // ...
    }

}

Minimal configuration needed to define a control (without the React part):


$tangible_blocks->register_control('text', [
  'elementor'       => $tangible_blocks->get_elementor_control_type('TEXT'),
  'beaver-builder'  => 'text',
  'gutenberg'       => 'string',
])
  ->elementor(function($field, $type) {
    return [
      'label'   => $field['label'],
      'type'    => $type,
      'default' => isset($field['default']) ? $field['default'] : ''
    ];
  })
  ->beaver_builder(function($field, $type) {
    return [
      'label'   => $field['label'],
      'type'    => $type,
      'default' => isset($field['default']) ? $field['default'] : ''
    ];
  })
  ->gutenberg(function($field, $type) {
    return [
      'type'    => $type,
      'default' => isset($field['default']) ? $field['default'] : ''
    ];
  });

Optional methods

There is other methods available to handle advanced controls:

  • render() - This method allow the value returned by builders to be modified before being rendered
    • $value - Current value from the builders
    • $field - Array with the attributes from <Control />

$control->render(function($value, $field) {
    return esc_html($value);
});
  • filter_value()
    • $value - Current value from the builder
    • $builder - Which builder this value comes from. Possible value: elementor, beaver-builder, gutenberg
    • $field - Array with the attributes from <Control />
    • $settings - All the settings. This value will be different according to the builder.

$control->filter_value(function($value, $builder, $field, $settings) {

    switch($builder) {

        case 'beaver-builder':
            // $settings is an object, each setting is stored under $setting->{ setting-name }
            return $value;

        case 'elementor':
            // $settings is an array, each setting is stored under $setting[{ setting-name }]
            return $value;

        case 'gutenberg':
            // $settings is an array, each setting is stored under $setting[{ setting-name }]
            return $value;

    }
});
  • sub_values() - Sub-values are usefull if we want to make alternative/partial renders for in same control

    • $values - Array of sub-values we want to register
  • render_sub_values()

    • $name - Name of the subvalue currently rendered. It has to be a name we registered in sub_values())
    • $builder - Which builder this value comes from. Possible value: elementor, beaver-builder, gutenberg
    • $field - Array with the attributes from <Control />
    • $settings - All the settings. This value will be different according to the builder.

/**
 * Assuming that main render is something like 100%
 * 
 * We can add a subvalue to get the number only
 * 
 * {{ setting-name }} will display 100%
 * {{ setting-name-int }} will display 100
 */

$control->sub_values([
    'int',
]);

$control->render_sub_values(function($name, $builder, $field, $settings) {

    // Get value from $settings

    if( $name === 'int' ) return echo substr($value, 0, -1);

});
  • context() - By default, controls are available in every context (template, style and script), but it can be changed with this method

$control->context(['template', 'style']); // Only in template and style

  • default() - Format the default before it's used by builders
    • $value - Default value
    • $builder - Current builder

$control->default(function($value, $builder) {

    if( $builder !== 'beaver-builder' ) return $value;

    // Modifications to make the default value accepted by beaver builder

    return $new_value;
});

Diagram

To understand which method you need to use according to your case, you can refer to this diagram:

  • Green methods are required
  • Blue methods are optional

control diagram

Control aliases

Register a control alias allows us to re-use an existing control and populate it with values.

Once registered, a control alias will work as a regular control.

For example, we can create an alias of a select control and populate it with pages:


$pages = get_posts([
    'post_type'   => 'page',
    'numberposts' => -1
]);

$options = [];
foreach ($pages as $page) {
    $options[ $page->ID ] = $page->post_title;
}

$tangible_blocks->register_control_alias(

    // The name of the alias
    'page-select',  

    // The name of the original control (it can be a custom control)
    'select', 

    // Attributes of the original control we want to populate
    [ 
        'multiple'  => true, 
        'options'   => $options 
    ]

]);

The alias can then be used like this:


<Control type="page-select" name="page-ids" label="Page select" />

Custom controls

For classic controls, we rely on the existing logic from each builder to create the UI of our fields.

In the case of custom controls, instead of reusing existing fields, we will create our own and register them in each builder.

It can be used to implement controls that don't exist in builders, or to handle complex controls. An advantage is that we will have the same UI and the same returned value everywhere.


$tangible_blocks = tangible_blocks();

$custom_control = $tangible_blocks->register_custom_control([
    'type' => 'custom-control-name',
]);

All the methods available on a classic control can be used on a custom one (render(), filter_value() ... etc), except:

  • gutenberg()
  • beaver_builder()
  • elementor()

We don't need to filter the data passed for each builder as long as the same react component will handle the render.

To link a react component to a custom control, we need to modify the switch in assets/src/template-block-fields/index.js.


export const getControl = (control, handler, value, field) => {

  switch(control.type) {

    // ... etc

    case 'custom-control-name':
      return <MyCustomControl handler={ handler } initialValue={ value } { ...field } />

    // ... etc

  }
}

There are multiple variables we can use to init the component:

  • control - Configuration of the control. The variable will be the same for each control of the same type.
  • handler - The callback function used to save the value.
  • value - Previously saved value for this control.
  • field - Attributes of the control. This can be different between controls of the same type.

If we need to pass data to the js, there is an optional method we can use:


$custom_control->enqueue(function($script_handle, $builder) {
    wp_localize_script($script_handle, 'CustomDataExample', [ 'Builder' => $builder ]);
});
  • $handle - The handle of the script used to enqueue our controls. It's not the same according to the builder.
  • $builder - Possible value: elementor, beaver-builder, gutenberg