Block Bindings API

Block Bindings API позволяет привязать данные из внешнего источника к атрибутам блока.

Например, можно связать атрибут url блока Image с функцией, которая возвращает URL картинки:

<!-- wp:image {
	"metadata":{
		"bindings":{
			"url":{
				"source":"my-plugin/get-random-image"
			}
		}
	}
} -->

После этого значение атрибута берётся не из сохранённой разметки блока, а из указанного источника.

Block Bindings API доступен с WordPress 6.5.

Часть возможностей появилась позже:

  • WP 6.7 - UI в редакторе, JS-регистрация источников, useBlockBindingsUtils().
  • WP 6.9 - core/post-data, core/term-data, getFieldsList(), фильтры supported attributes.
  • WP 7.0 - Pattern Overrides работают для любых атрибутов, которые поддерживают Block Bindings, включая кастомные блоки.

Коротко

Block Bindings API нужен для связи атрибутов блока с динамическими данными.

Основной синтаксис хранится в metadata.bindings:

{
	"metadata": {
		"bindings": {
			"content": {
				"source": "core/post-meta",
				"args": {
					"key": "my_custom_field"
				}
			}
		}
	}
}

Главное после WP 7.0:

  • supported attributes стали общей точкой расширения для Block Bindings и Pattern Overrides;
  • Pattern Overrides можно использовать с кастомными блоками;
  • для динамических блоков WordPress передаёт вычисленные binding-значения прямо в render_callback();
  • для кастомных источников можно сделать полноценный UI через registerBlockBindingsSource() и getFieldsList().

Совместимые блоки и атрибуты

По умолчанию bindings работают не со всеми атрибутами блоков. Сейчас в ядре поддерживаются такие блоки и атрибуты:

Блок Атрибуты
core/image id, url, title, alt, caption
core/heading content
core/paragraph content
core/button url, text, linkTarget, rel
core/navigation-link url
core/navigation-submenu url
core/post-date datetime

С WP 6.9 этот список можно расширять фильтрами:

С WP 7.0 это особенно важно: если атрибут добавлен в supported attributes, он может использоваться не только для Block Bindings, но и для Pattern Overrides.

Пример добавления поддержки атрибута linkDestination для блока Image:

add_filter(
	'block_bindings_supported_attributes_core/image',
	static function ( $supported_attributes ) {
		$supported_attributes[] = 'linkDestination';

		return $supported_attributes;
	}
);

Пример для кастомного блока:

add_filter(
	'block_bindings_supported_attributes_my-plugin/card',
	static function ( $supported_attributes ) {
		$supported_attributes[] = 'title';
		$supported_attributes[] = 'description';

		return $supported_attributes;
	}
);

Источники ядра

В WordPress есть несколько встроенных источников:

  • core/post-meta
  • core/post-data
  • core/term-data
  • core/pattern-overrides

core/post-meta

Источник core/post-meta привязывает атрибуты блока к метаполям записи.

Требования:

  • метаполе должно быть зарегистрировано с show_in_rest => true;
  • ключ метаполя не должен начинаться с _;
  • тип метаполя должен подходить к типу атрибута блока.

Пример регистрации метаполя:

register_meta(
	'post',
	'my_custom_field',
	array(
		'show_in_rest' => true,
		'single'       => true,
		'type'         => 'string',
		'label'        => 'Custom field',
	)
);

Пример привязки к Paragraph:

<!-- wp:paragraph {
	"metadata":{
		"bindings":{
			"content":{
				"source":"core/post-meta",
				"args":{
					"key":"my_custom_field"
				}
			}
		}
	}
} -->
<p>Fallback content</p>
<!-- /wp:paragraph -->

Fallback content нужен как запасное значение. Если binding не вернёт данные, блок сможет показать сохранённый контент.

core/post-data

С WP 6.9 источник core/post-data даёт доступ к данным текущей записи.

Доступные поля:

Поле Что возвращает
date дату публикации
modified дату последнего изменения
link permalink записи

Пример:

<!-- wp:paragraph {
	"metadata":{
		"bindings":{
			"content":{
				"source":"core/post-data",
				"args":{
					"field":"date"
				}
			}
		}
	}
} -->
<p>Fallback content</p>
<!-- /wp:paragraph -->

core/term-data

С WP 6.9 источник core/term-data даёт доступ к данным термина таксономии.

Он работает только там, где есть term context. Например, внутри core/term-template или внутри кастомного блока, который передаёт termId и taxonomy.

Доступные поля:

Поле Что возвращает
id ID термина
name название термина
link URL архива термина
slug slug
description описание
parent ID родительского термина
count количество записей в термине

Пример вывода названий терминов:

<!-- wp:terms-query {
	"termQuery":{
		"taxonomy":"category",
		"perPage":5
	}
} -->
<div class="wp-block-terms-query">
	<!-- wp:term-template {"layout":{"type":"default"}} -->
	<!-- wp:paragraph {
		"metadata":{
			"bindings":{
				"content":{
					"source":"core/term-data",
					"args":{
						"field":"name"
					}
				}
			}
		}
	} -->
	<p>Category Name</p>
	<!-- /wp:paragraph -->
	<!-- /wp:term-template -->
</div>
<!-- /wp:terms-query -->

Для блоков навигации есть отдельная обработка совместимости: core/navigation-link и core/navigation-submenu могут брать данные не из context, а из атрибутов самого блока.

core/pattern-overrides

Источник core/pattern-overrides используется для переопределения контента внутри Synced Pattern.

Идея такая: структура и дизайн паттерна остаются синхронизированными, но отдельные атрибуты вложенных блоков можно менять в каждом экземпляре паттерна. Подробнее тут.

С WP 7.0 Pattern Overrides больше не ограничены небольшим списком core-блоков. Любой атрибут, который поддерживает Block Bindings, может быть доступен для overrides. Это работает и для кастомных блоков.

Как это работает

  • Внутри паттерна у блока должен быть metadata.name.
  • В metadata.bindings указывается источник core/pattern-overrides.
  • Экземпляр паттерна хранит переопределённые значения в атрибуте content.
  • Ключом служит metadata.name блока.
  • Структура данных такая: pattern/overrides -> {block_name} -> {attribute_name}.

Пример для одного атрибута:

<!-- wp:paragraph {
	"metadata":{
		"name":"custom-heading",
		"bindings":{
			"content":{
				"source":"core/pattern-overrides"
			}
		}
	}
} -->
<p>Default text</p>
<!-- /wp:paragraph -->

Пример экземпляра паттерна с переопределённым контентом:

<!-- wp:block {
	"ref":123,
	"content":{
		"custom-heading":{
			"content":"My custom heading text"
		}
	}
} /-->

Если нужно разрешить переопределять все поддерживаемые атрибуты блока, можно использовать __default:

<!-- wp:button {
	"metadata":{
		"name":"custom-button",
		"bindings":{
			"__default":{
				"source":"core/pattern-overrides"
			}
		}
	}
} -->
<div class="wp-block-button">
	<a class="wp-block-button__link wp-element-button" href="#">Default button</a>
</div>
<!-- /wp:button -->

__default означает: подключить к core/pattern-overrides все атрибуты блока, которые поддерживают Block Bindings.

Pattern Overrides для кастомного блока

Сначала нужно добавить нужные атрибуты в supported attributes:

add_filter(
	'block_bindings_supported_attributes_my-plugin/card',
	static function ( $supported_attributes ) {
		$supported_attributes[] = 'title';
		$supported_attributes[] = 'description';

		return $supported_attributes;
	}
);

Затем в разметке паттерна можно подключить overrides:

<!-- wp:my-plugin/card {
	"title":"Default title",
	"description":"Default description",
	"metadata":{
		"name":"custom-card",
		"bindings":{
			"__default":{
				"source":"core/pattern-overrides"
			}
		}
	}
} /-->

Для динамических блоков с WP 7.0 обычно не нужно вручную читать $block->context['pattern/overrides']. WordPress сам вычислит связанные значения и передаст их в render_callback() через $attributes.

function my_plugin_render_card( $attributes ) {
	$title       = $attributes['title'] ?? '';
	$description = $attributes['description'] ?? '';

	return sprintf(
		'<div class="my-card"><h3>%s</h3><p>%s</p></div>',
		esc_html( $title ),
		esc_html( $description )
	);
}

Для статических блоков WordPress пытается заменить значения в HTML через HTML API. Если атрибут сложно найти в сохранённой разметке, может понадобиться render_callback() или фильтр render_block.

Кастомный источник

Кастомный источник нужен, когда данные берутся не из стандартных источников ядра.

Например:

  • из внешнего API;
  • из настроек плагина;
  • из данных пользователя;
  • из сложной логики темы;
  • из кастомной таблицы.

Источник можно зарегистрировать на сервере через PHP и в редакторе через JavaScript. Эти регистрации могут работать вместе.

Серверная регистрация отвечает за рендер на фронтенде. Регистрация в редакторе отвечает за отображение и редактирование значения в UI.

Серверная регистрация

Функция:

register_block_bindings_source( $name, $args );

Основные аргументы:

Аргумент Описание
label название источника
uses_context список context-значений, которые нужны callback-функции
get_value_callback функция, которая возвращает значение для атрибута

Вызывать регистрацию нужно на хуке init.

Пример:

add_action(
	'init',
	static function () {
		register_block_bindings_source(
			'wpmovies/visualization-date',
			array(
				'label'              => 'Visualization Date',
				'uses_context'       => [ 'postId' ],
				'get_value_callback' => wpmovies_get_visualization_date( ... ),
			)
		);
	}
);

function wpmovies_get_visualization_date( $source_args, $block, $attribute_name ) {
	if ( empty( $source_args['key'] ) || empty( $block->context['postId'] ) ) {
		return null;
	}

	return get_post_meta( $block->context['postId'], $source_args['key'], true );
}

Пример использования источника в блоке:

<!-- wp:paragraph {
	"metadata":{
		"bindings":{
			"content":{
				"source":"wpmovies/visualization-date",
				"args":{
					"key":"wp_movies_visualization_date"
				}
			}
		}
	}
} -->
<p>Fallback date</p>
<!-- /wp:paragraph -->

Фильтр block_bindings_source_value

С WP 6.7 значение, которое вернул источник, можно изменить через фильтр block_bindings_source_value.

Фильтр получает:

  • $value - значение источника;
  • $name - имя источника;
  • $source_args - аргументы источника;
  • $block_instance - экземпляр блока;
  • $attribute_name - имя атрибута.

Пример:

add_filter( 'block_bindings_source_value', 'wpmovies_format_visual_date', 10, 5 );

function wpmovies_format_visual_date( $value, $name, $source_args, $block_instance, $attribute_name ) {
	if ( 'wpmovies/visualization-date' !== $name ) {
		return $value;
	}

	if ( empty( $value ) ) {
		return '';
	}

	return wp_date( 'd.m.Y', strtotime( $value ) );
}

Регистрация источника в редакторе

С WP 6.7 источник можно зарегистрировать в редакторе через JavaScript:

import { registerBlockBindingsSource } from '@wordpress/blocks';

Функция:

registerBlockBindingsSource( args );

Основные параметры:

Параметр Описание
name имя источника
label название для UI
usesContext нужный block context
getValues получить значения для редактора
setValues сохранить значения
canUserEditValue разрешить или запретить редактирование
getFieldsList список полей для выпадающего списка UI

Пример:

import { registerBlockBindingsSource } from '@wordpress/blocks';
import { store as coreDataStore } from '@wordpress/core-data';

registerBlockBindingsSource( {
	name: 'wpmovies/visualization-date',
	label: 'Visualization Date',
	usesContext: [ 'postId', 'postType' ],

	getValues( { select, context } ) {
		const { getEditedEntityRecord } = select( coreDataStore );

		const record = getEditedEntityRecord(
			'postType',
			context.postType,
			context.postId
		);

		return {
			content: record?.meta?.wp_movies_visualization_date || '',
		};
	},

	setValues( { dispatch, context, bindings } ) {
		dispatch( coreDataStore ).editEntityRecord(
			'postType',
			context.postType,
			context.postId,
			{
				meta: {
					wp_movies_visualization_date: bindings?.content?.newValue,
				},
			}
		);
	},

	canUserEditValue() {
		return true;
	},
} );

getValues()

getValues() вызывается, когда редактору нужно получить значение связанного атрибута.

Функция получает объект с данными:

  • bindings - bindings для текущего источника;
  • clientId - client ID блока;
  • context - block context;
  • select - селекторы data store.

Функция должна вернуть объект вида:

{
	content: 'Value'
}

Ключ объекта должен совпадать с именем атрибута блока.

setValues()

setValues() вызывается, когда пользователь меняет значение связанного атрибута в редакторе.

Функция получает:

  • bindings - bindings, включая newValue;
  • clientId - client ID блока;
  • context - block context;
  • dispatch - actions data store;
  • select - selectors data store.

getFieldsList()

С WP 6.9 getFieldsList() позволяет показать поля кастомного источника в UI Block Bindings.

Когда пользователь выбирает поле из списка, редактор сам добавляет binding к нужному атрибуту блока.

Функция должна вернуть массив объектов:

Поле Описание
label название поля в UI
type тип значения
args аргументы, которые будут записаны в binding

Пример:

registerBlockBindingsSource( {
	name: 'my-plugin/custom-fields',
	label: 'Custom Fields',

	getFieldsList() {
		return [
			{
				label: 'Author Name',
				type: 'string',
				args: {
					field: 'author_name',
				},
			},
			{
				label: 'Publication Year',
				type: 'string',
				args: {
					field: 'publication_year',
				},
			},
			{
				label: 'Page Count',
				type: 'number',
				args: {
					field: 'page_count',
				},
			},
		];
	},

	getValues( { bindings } ) {
		const values = {};

		Object.entries( bindings ).forEach( ( [ attributeName, binding ] ) => {
			values[ attributeName ] = getValueByField( binding.args.field );
		} );

		return values;
	},
} );

type должен подходить к типу атрибута. Например, поле с type: 'number' не будет показываться для строкового атрибута.

Удаление и получение источников

С WP 6.7 в редакторе доступны функции для работы с зарегистрированными источниками.

unregisterBlockBindingsSource()

Удаляет источник по имени:

import { unregisterBlockBindingsSource } from '@wordpress/blocks';

unregisterBlockBindingsSource( 'plugin/my-custom-source' );

getBlockBindingsSources()

Возвращает все зарегистрированные источники:

import { getBlockBindingsSources } from '@wordpress/blocks';

const sources = getBlockBindingsSources();

getBlockBindingsSource()

Возвращает один источник по имени:

import { getBlockBindingsSource } from '@wordpress/blocks';

const source = getBlockBindingsSource( 'plugin/my-custom-source' );

Block Bindings Utils - useBlockBindingsUtils()

С WP 6.7 есть хук useBlockBindingsUtils(). Он помогает менять metadata.bindings у блока из JS.

import { useBlockBindingsUtils } from '@wordpress/block-editor';

const { updateBlockBindings, removeAllBlockBindings } = useBlockBindingsUtils();

updateBlockBindings()

Создаёт, обновляет или удаляет привязки у текущего блока.

import { useBlockBindingsUtils } from '@wordpress/block-editor';

const { updateBlockBindings } = useBlockBindingsUtils();

function connectUrl() {
	updateBlockBindings( {
		url: {
			source: 'my-plugin/url-source',
		},
	} );
}

function disconnectUrl() {
	updateBlockBindings( {
		url: undefined,
	} );
}

removeAllBlockBindings()

Удаляет все bindings у блока:

import { useBlockBindingsUtils } from '@wordpress/block-editor';

const { removeAllBlockBindings } = useBlockBindingsUtils();

function clearBindings() {
	removeAllBlockBindings();
}

useBlockEditingMode()

Хук. Позволяет узнать или изменить режим редактирования блока. Находится в @wordpress/block-editor.

Управляет тем, какой UI будет доступен для блока и его внутренних блоков. Режим наследуется вложенными блоками, если у них не задан свой режим. Если вызвать хук вне контекста блока, режим применяется ко всем блокам.

Доступные режимы:

  • default - обычное редактирование блока.
  • contentOnly - скрывает неконтентные элементы интерфейса: настройки блока, mover-кнопки, часть toolbar-контролов и т.д.
  • disabled - полностью запрещает редактирование блока, его нельзя выбрать.

Если вызвать хук без параметра:

const mode = useBlockEditingMode();

он просто вернет текущий режим редактирования.

Если передать режим:

useBlockEditingMode( 'contentOnly' );

он установит этот режим для текущего блока и его вложенных блоков.

Пример проверки contentOnly

Иногда нужно понять, что блок сейчас находится в contentOnly режиме, и показать другой UI. Например, когда боковая панель с настройками скрыта, можно вынести управление изображениями в BlockControls.

const { BlockControls, InspectorControls, useBlockEditingMode } = wp.blockEditor;
const { Dropdown, ToolbarButton } = wp.components;

const isContentOnlyMode = ( useBlockEditingMode() || 'default' ) === 'contentOnly';

if ( isContentOnlyMode ) {
	return (
		<BlockControls group="other">
			<Dropdown
				popoverProps={ { placement: 'bottom-start' } }
				renderToggle={ ( { isOpen, onToggle } ) => (
					<ToolbarButton
						label="Images"
						icon="format-image"
						onClick={ onToggle }
						isPressed={ isOpen }
					/>
				) }
				renderContent={ () => (
					<div style={ { padding: '1rem', width: '320px' } }>
						<FocalImagesControl
							params={ {
								setAttributes,
								desktopImage,
								tabletImage,
								mobileImage,
							} }
						/>
					</div>
				) }
			/>
		</BlockControls>
	);
}

Такой прием полезен для кастомных блоков внутри паттернов или template parts, где WordPress ограничивает редактирование только контентом, но разработчику всё равно нужно оставить доступ к отдельным контентным настройкам.