В этой статье мы рассмотрим, как можно передавать данные из родительского блока в дочерние блоки. Это позволяет нам эффективно использовать атрибут, который мы можем изменить в одном месте, чтобы получить изменения во вложенных блоках.
Данная статья является вольным переводом урока How to Pass Data Between Custom Blocks with Block Context. Вы можете скачать готовый код блока из репозитория курса на Github, если застрянете на каком-либо этапе.
Здесь мы создадим пользовательский блок для некого сайта ресторана. У нас будет родительский блок "Menu", который будет поддерживать дочерние блоки "dish" и отображать их в сетке.
Различные типы ресторанов оформляют меню по-разному. Для наглядности предположим, что мы хотим поддерживать два стиля меню: элегантный минималистичный (для всех «новоамериканских» ресторанов, появляющихся в наши дни) и карточный стиль, поддерживающий изображения (для типичных заведений, продающих еду на вынос).
Вот пример того, как они должны выглядеть:
Без использования контекста блока мы могли бы подойти к этому вопросу одним из двух способов:
- У нас может быть два разных типа внутренних блоков (карточка/минимальный) или элемент управления в нашем блоке пунктов меню, который переключает стиль. А что, если ресторан решит сделать ребрендинг? Им придется пройтись по каждому пункту меню и поменять стиль один за другим. Хуже того, с разными типами дочерних блоков им придется удалить все блоки пунктов меню одного стиля и заменить их другим стилем.
- Мы могли бы добавить переключатель стилей в блок меню, который изменяет класс, а затем использовать CSS для изменения стиля дочерних блоков. Это решает проблему необходимости обновления каждого элемента, но даже у довольно умного CSS есть ограничения, и такой подход усложняет организацию стилей, поскольку CSS пересекается между двумя блоками.
Используем контекст блока
Используя контекст блока, мы можем добавить атрибут стиля меню к нашему родительскому блоку, а затем использовать этот атрибут в наших блоках блюд и отображать различную разметку на основе выбранного в админке значения по условию.
Это позволяет нам делать больше, чем обычные изменения CSS. Например, наш минимальный вариант меню округлит цену до ближайшего целого числа и не будет отображать центы — нет необходимости вручную переформатировать цену для каждого элемента или оборачивать центы в специальный тег span
, чтобы скрыть их с помощью CSS.
Настройка нашего плагина
Мы быстро пройдем весь процесс создания, поскольку мы уже рассматривали его в последних нескольких уроках. Если в какой-то момент вы почувствуете, что запутались, можете вернуться и пересмотреть материал:
Мы собираемся настроить многоблочную среду разработки. Первый запуск нужен в терминале папке plugins вашего тестового сайта на WordPress, где вы будете смотреть работу блока:
1 | npx @wordpress/create-block block-context-menu-plugin |
Затем удалите все содержимое папки src, а также папку build для полной уверенности.
Далее перейдите в папку src и выполните следующие команды. Пожалуйста, используйте пространство имен 'fsd'
, так как это позволит вам скопировать мой css без внесения каких-либо изменений:
1 2 | npx @wordpress/create-block --namespace fsd --variant dynamic --no-plugin menu npx @wordpress/create-block --namespace fsd --variant dynamic --no-plugin dish |
Это создаст необходимые файлы для нашего блока "menu
" и "dish
".
Обратите внимание, что для каждого блока существует еще файл render.php
для вывода содержимого блока.
Теперь нам просто нужно убедиться, что оба зарегистрированы в главном php-файле нашего плагина. Мы можем использовать наш фрагмент из последнего урока об управлении блоками , который автоматически зарегистрирует все наши блоки:
1 2 3 4 5 6 7 8 9 | function create_block_block_starter_kit_block_init() { // Генерирует массив из путей папок в папке build $block_directories = glob(__DIR__ . "/build/*", GLOB_ONLYDIR); foreach ($block_directories as $block) { register_block_type( $block ); } } add_action( 'init', 'create_block_block_starter_kit_block_init' ); |
Файлы стилей для блока
Для блока menu
в style.scss:
1 2 3 4 5 6 7 8 9 | .wp-block-fsd-menu { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; @media (max-width: 768px){ grid-template-columns: 1fr; } } |
Для блока menu
в файле editor.scss:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | .wp-block-fsd-menu { border: 1px dotted black; padding: 8px; } .wp-block-fsd-menu { display: block; .block-editor-block-list__layout { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } } |
Стили для блока dish - файл style.scss:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | // Стили для'Card' Style .menu-item--card border: 1px solid #eee; box-shadow: 0px 2px 12px rgba(0,0,0,0.1); border-radius: 12px; overflow: hidden; .menu-item__image-container { width: 100%; height: 200px; position: relative; img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; } } .menu-item__text-container { padding: 16px; display: flex; gap: 16px; align-items: center; justify-content: space-between; h5, p { margin: 0; } } } // Стили для 'Minimal' .menu-item--minimal { text-align: center; margin-bottom: 20px; h5 { margin-bottom: 8px; border-bottom: 1px solid black; } p { margin: 0; } } |
Стили для блока dish
в файле editor.scss:
1 2 3 4 5 6 7 | .wp-block-fsd-dish { border: 1px solid #eee; box-shadow: 0px 2px 12px rgba(0,0,0,0.1); border-radius: 6px; overflow: hidden; padding: 8px; } |
На этом этапе, если вы запустите процесс разработки с помощью npm run start
, активируете плагин и откроете страницу или публикацию в редакторе, вы должны увидеть два наших блока в инструменте вставки.
npm run start
. Это очистит папку сборки и обеспечит ее синхронизацию с вашим каталогом src
. Если вы когда-либо увидите странные ошибки с вашими блоками, это должно быть вашим первым шагом.Давайте начнем разрабатывать функционал.
Настройка блока меню
Первое, что мы сделаем для нашего блока меню, — настроим компонент InnerBlocks, чтобы мы могли добавлять в него блоки блюд. В нашем наборе файлов для каждого блока есть edit.js для отображения контента блока в админке и index.js, который является главным файлом блока и, кроме того, содержит функцию save()
, которая отвечает за сохранение блока и его отображение на сайте.
Давайте установим по умолчанию в блок меню два блюда, чтобы он не выглядел пустым при добавлении:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; export default function Edit({attributes, setAttributes}) { const ALLOWED_BLOCKS = [ 'fsd/dish' ]; const TEMPLATE = [ [ 'fsd/dish', {} ], [ 'fsd/dish', {} ] ]; return ( <> <div { ...useBlockProps() }> <InnerBlocks allowedBlocks={ALLOWED_BLOCKS} template={TEMPLATE} orientation="horizontal" /> </div> </> ) } |
Далее, очень важно помнить, что при каждом использовании внутренних блоков вы должны возвращать содержимое внутренних блоков в функцию сохранения блока!
Если вы пропустите этот шаг, блок будет выглядеть работающим, но ничего не будет сохранено.
1 2 3 4 5 6 | import { InnerBlocks } from '@wordpress/block-editor'; registerBlockType( metadata.name, { edit: Edit, save: () => <InnerBlocks.Content /> } ); |
Наконец, мы изменим шаблон рендеринга, чтобы вывести наши внутренние блоки блюд внутри блока меню:
1 2 3 | <div <?php echo get_block_wrapper_attributes(); ?>> <?php echo $content; ?> </div> |
Отлично, мы скоро вернемся к этому блоку, чтобы добавить переключатель стилей, но сейчас давайте создадим блок dish
, который поместится внутри этого блока-контейнера с названием menu
.
Создание нашего блока с одним блюдом
Для нашего блока блюд у нас будет четыре атрибута: Имя, Цена, URL изображения и ID изображения. Вы можете расширить это, если хотите.
Мы можем продолжить и добавить их в файл block.json. Пока мы там, продолжайте и также добавьте атрибут «parent» и перечислите наш блок меню. Таким образом, наши блюда могут быть вставлены только внутрь блока меню:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | "attributes": { "price": { "type": "string" }, "name": { "type": "string" }, "image_id": { "type": "number" }, "image_url": { "type": "string" } }, "parent": ["fsd/menu"], |
Теперь давайте создадим редактор для блока блюд (dish
). Окончательный код в файле edit.js в папке dish будет таким:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | import { useBlockProps, MediaPlaceholder } from '@wordpress/block-editor'; import { TextControl } from '@wordpress/components' export default function Edit({attributes, setAttributes}) { return ( <> <div { ...useBlockProps() }> <div className='dish-image-container'> {attributes.image_url && attributes.image_id ? ( <> <img src={attributes.image_url} /> <button className="button-remove" onClick={() => setAttributes({image_url: "", image_id: null})}>Remove</button> </> ) : ( <MediaPlaceholder onSelect = { ( image ) => { setAttributes( { image_url: image.url, image_id: image.id } ); } } allowedTypes = { [ 'image' ] } multiple = { false } labels = { { title: 'Dish Image' } } > </MediaPlaceholder> ) } </div> <div className='dish-text-container'> <TextControl label="Name" value={ attributes.name } onChange={ ( value ) => setAttributes({name: value}) } /> <TextControl label="Price" type='number' value={ attributes.price } onChange={ ( value ) => setAttributes({price: value}) } /> </div> </div> </> ); } |
Вы заметите, что мы используем компонент Media Placeholder . Это отличный вариант для обработки изображений в блоке.
Также у нас есть два компонента TextControl для названия блюда и цены. Обратите внимание, что для элемента управления ценой мы установили тип "number
". Может показаться немного странным, что элемент управления "text
" установлен на тип "number
", но по сути вы можете думать о TextControl как об элементе ввода HTML, просто со стилями WordPress.
Убедитесь, что ваш процесс разработки запущен с помощью npm run start
, и протестируйте свой блок. Если у вас есть какие-либо ошибки, дважды проверьте, что вы импортировали все нужные компоненты.
При добавлении блока меню на страницу вы должны увидеть что-то вроде этого:
Отлично! Теперь давайте просто построим нашу функцию рендеринга.
Сейчас мы просто создадим рендер для блока стиля карточки в файле render.php, а затем вернемся и добавим наш минимальный стиль меню.
1 2 3 4 5 6 7 8 9 | <div <?php echo get_block_wrapper_attributes(["class" => "menu-item--card"]); ?>> <div class="menu-item__image-container"> <img src="<?php echo $attributes["image_url"] ?>" alt=""> </div> <div class="menu-item__text-container"> <h5><?php echo $attributes['name'] ?></h5> <p>$<?php echo $attributes['price'] ?></p> </div> </div> |
Добавьте в админке фото блюд и введите названия и цену. Когда вы проверите фронтенд вашего сайта, вы должны увидеть что-то вроде этого:
На этом этапе у нас есть полностью функционирующее меню и блок блюд. Однако мы не будем на этом останавливаться. Давайте рассмотрим, как можно использовать контекст блока, чтобы позволить нашим пользователям быстро переключать стили меню в блоке меню и заставить наши блоки блюд реагировать соответствующим образом.
Добавление переключателя стилей в наш блок меню
Чтобы получить функционал переключателя вариаций стилей, нам нужно добавить атрибут в наш блок меню для управления стилем меню. Для этого мы будем использовать 'enum
':
1 2 3 4 5 6 | "attributes": { "menu_style": { "enum": ["card" ,"minimal"], "default": "card" } }, |
Далее мы перейдем к функции в файле menu/edit.js. Мы используем элементы управления инспектора, чтобы добавить элемент управления select
на нашу боковую панель, связанную с этим атрибутом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import { useBlockProps, InnerBlocks, InspectorControls } from '@wordpress/block-editor'; import { PanelBody, SelectControl } from '@wordpress/components' export default function Edit({attributes, setAttributes}) { const ALLOWED_BLOCKS = [ 'fsd/dish' ]; const TEMPLATE = [ [ 'fsd/dish', {} ], [ 'fsd/dish', {} ] ]; return ( <> <div { ...useBlockProps() }> <InnerBlocks allowedBlocks={ALLOWED_BLOCKS} template={TEMPLATE} orientation="horizontal" /> </div> {/* Begin Sidebar Inspector Zone */} <InspectorControls> <PanelBody title="Settings"> <SelectControl label="Menu Style:" onChange={(val) => setAttributes({menu_style: val})} value={attributes.menu_style} options={ [ { label: "Card", value: "card" }, { label: "Minimal", value: "minimal" } ] } /> </PanelBody> </InspectorControls> {/* End Sidebar Inspector Zone */} </> ) } |
Теперь вы сможете переключать стили меню в админке, но, к в настоящее время переключение не имеет никакого эффекта:
Теперь начинается самое интересное. Нам нужно передать этот атрибут вниз из родительского компонента menu
в дочерние блоки dish
. Мы можем сделать это, добавив элемент в каждый файл block.json.
Добавление контекста блока
Во-первых, в нашем блоке меню мы можем добавить следующее после наших атрибутов:
1 2 3 | "providesContext": { "menu/menu_style": "menu_style" }, |
ProvidesContext принимает массив пар ключ/значение. Ключ должен быть строкой пространства имен – это то, на что вы будете ссылаться из дочерних блоков. Важно использовать пространство имен, чтобы избежать конфликтов с другими значениями, которые могут находиться в глобальном контексте.
Значение, присвоенное этой строке пространства имен, должно быть именем атрибута, который вы хотите добавить к поставщику контекста.
В файле block.json вашего блока dish
добавьте следующее:
1 | "usesContext": ["menu/menu_style"], |
UsesContext просто принимает массив имен ключей контекста, которые мы хотим сделать доступными для нашего блока.
На этом этапе наши блоки dish
имеют доступ к атрибуту menu_style
.
Перейдите в файл render.php вашего блока блюд и добавьте следующую строку выше всего остального:
1 2 | <?php echo $block->context['menu/menu_style'] ?> // ... Rest of template |
Вот как мы можем получить доступ к атрибутам контекста из шаблонов PHP. Если вы сохраните и проверите фронтенд вашего сайта, вы должны увидеть название стиля меню, выводимого рядом с нашими карточками в сетке.
Вы можете попробовать изменить стиль в блоке меню, и вы увидите, как это изменение отразится во всех наших дочерних блоках.
Условный рендеринг на основе контекста блока
На данный момент у нас есть масса вариантов использования этих данных. Одним из распространенных подходов является использование оператора switch
для условной визуализации различной разметки на основе значения.
В файле render.php меняем код для блока блюд на следующий:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?php switch ($block->context['menu/menu_style']) { case 'minimal': ?> <div <?php echo get_block_wrapper_attributes(["class" => "menu-item--minimal"]); ?>> <h5><?php echo $attributes['name'] ?></h5> <p>- <?php echo round($attributes['price']) ?> - </p> </div> <?php break; default: ?> <div <?php echo get_block_wrapper_attributes(["class" => "menu-item--card"]); ?>> <div class="menu-item__image-container"> <img src="<?php echo $attributes["image_url"] ?>" alt=""> </div> <div class="menu-item__text-container"> <h5><?php echo $attributes['name'] ?></h5> <p>$<?php echo $attributes['price'] ?></p> </div> </div> <?php break; } ?> |
В этом случае мы можем использовать стиль нашей карточки по умолчанию, но если переменная menu_style контекста блока установлена
Продолжайте и попробуйте переключаться между нашими двумя шаблонами. Вы должны иметь возможность легко менять все пункты меню одновременно между нашим минимальным или карточным стилем отображения.
Итак, цель достигнута. Но мы все еще можем сделать еще один шаг вперед, чтобы улучшить работу редактора.
Условный рендеринг в редакторе
Так же, как мы можем получить доступ к нашим контекстным переменным в нашем шаблоне рендеринга, мы можем получить к ним доступ и в файле edit.js.
Поскольку наш «минимальный» стиль меню не отображает изображение, я думаю, имеет смысл скрыть выбор изображения, когда мы меняем стиль в родительском блоке меню.
Мы можем добиться этого, заключив его в дополнительный оператор if
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | export default function Edit({attributes, setAttributes, context}) { return ( <> <div { ...useBlockProps() }> {context['menu/menu_style'] === 'card' && ( <div className='dish-image-container'> {attributes.image_url && attributes.image_id ? ( <> <img src={attributes.image_url} /> <button className="button-remove" onClick={() => setAttributes({image_url: "", image_id: null})}>Remove</button> </> ) : ( <MediaPlaceholder onSelect = { ( image ) => { setAttributes( { image_url: image.url, image_id: image.id } ); } } allowedTypes = { [ 'image' ] } multiple = { false } labels = { { title: 'Dish Image' } } > </MediaPlaceholder> ) } </div> )} <div className='dish-text-container'> <TextControl label="Name" value={ attributes.name } onChange={ ( value ) => setAttributes({name: value}) } /> <TextControl label="Price" type='number' value={ attributes.price } onChange={ ( value ) => setAttributes({price: value}) } /> </div> </div> </> ); } |
Отлично! Теперь попробуйте поменять стили меню. Когда вы это сделаете, все дочерние элементы скроют выбор изображения в редакторе
Примечание: Вы можете не согласиться с этим подходом. Некоторые пользователи могут смутиться, когда исчезает средство выбора изображений, и подумать, что они потеряли все свои изображения – даже если они все еще сохранены в атрибутах. Вам придется решить самостоятельно, нужно ли вносить это изменение.
Резюме
Итак, в этой статье мы разобрались с тем, как передавать данные из родительского блока в дочерний с помощью контекста.
Контекст блока — это продвинутая тема для создания пользовательских блоков. Вам, вероятно, это не понадобится слишком часто, но для определенных случаев использования это действительно является самым простым способом.
Другим прекрасным примером может служить блок сетки команды с дочерними блоками членов команды. Вы можете иметь аналогичный элемент управления заменой стилей в главном блоке сетки команды, чтобы предоставить некоторые варианты отображения информации без необходимости вручную редактировать отдельные блоки каждого члена команды.