Un curso intensivo sobre filtros de bloques de WordPress

Los bloques en WordPress son geniales. Coloque algunos en la página, organícelos como desee y obtenga una página de destino bastante atractiva con poco esfuerzo. Pero ¿qué pasa si los bloques predeterminados en WordPress necesitan algunos ajustes? ¿Qué pasaría si pudiéramos eliminar las opciones de alineación en la configuración del bloque de portada? ¿O qué tal controlar el tamaño del bloque de botones?
Hay muchas opciones cuando se trata de ampliar la funcionalidad de los bloques centrales en WordPress. Podemos agregar una clase CSS personalizada a un bloque en el editor, agregar un estilo personalizado o crear una variación de bloque. Pero incluso eso puede no ser suficiente para obtener lo que necesita, y se ve en la necesidad de filtrar el bloque principal para agregar o eliminar funciones, o crear un bloque completamente nuevo desde cero.
Le mostraré cómo extender los bloques principales con filtros y también mencionaré cuándo es mejor crear un bloque personalizado en lugar de extensor uno principal.
Una nota rápida sobre estos ejemplos.
Antes de profundizar, me gustaría señalar que los fragmentos de código de este artículo se han sacado de contexto a propósito para centrarse en los filtros en lugar de crear herramientas y estructuras de archivos. Si incluye el código completo de los filtros, el artículo será difícil de seguir. Dicho esto, entiendo que no es obvio para alguien que recién está comenzando dónde colocar los fragmentos o cómo ejecutar scripts de compilación y hacer que todo funcione.
Para facilitarle las cosas, cree un complemento de WordPress con ejemplos de este artículo disponibles en mi GitHub. No dudes en descargarlo y explorar la estructura de archivos, las dependencias y los scripts de compilación. Hay un archivo README que le ayudará a empezar.
Filtros de bloqueo en pocas palabras
El concepto de filtros no es nuevo en WordPress. La mayoría de nosotros estamos familiarizados con la add_filter()
función en PHP. Permite a los desarrolladores modificar varios tipos de datos mediante ganchos.
Un ejemplo simple de un filtro PHP podría verse así:
function filter_post_title( $title ){ return 'strong' . $title . '/strong';};add_filter( 'the_title', 'filter_post_title' );
En este fragmento, creamos una función que recibe una cadena que representa el título de una publicación, luego la envuelve en una strong
etiqueta y devuelve un título modificado. Luego le decimos add_filter()
a WordPress que usa esa función en el título de una publicación.
Los filtros de JavaScript funcionan de manera similar. Hay una función de llamada JavaScript addFilter()
que vive en el wp.hooks
paquete y funciona casi como su hermano PHP. En su forma más simple, un filtro de JavaScript se parece a esto:
function filterSomething(something) { // Code for modifying something goes here. return something;}wp.hooks.addFilter( 'hookName', 'namespace', filterSomething );
Se parece bastante, ¿verdad? Una diferencia notable es que addFilter()
tiene namespace
un segundo argumento. Según el Manual de WordPress, “El espacio de nombres identifica de forma única una devolución de llamada en el formulario vendor/plugin/function
”. Sin embargo, los ejemplos del manual siguen patrones diferentes: plugin/what-filter-does
o plugin/component-name/what-filter-does
. Normalmente sigo este último porque mantiene los identificadores únicos durante todo el proyecto.
Lo que hace que los filtros de JavaScript sean difíciles de entender y usar es la naturaleza diferente de lo que pueden filtrar. Algunas cadenas filtradas, algunos objetos filtrados JavaScript y otros filtran componentes de React y requieren comprender el concepto de componentes de orden superior.
Además de eso, lo más probable es que necesites usar JSX, lo que significa que no puedes simplemente colocar el código en tu tema o complemento y esperar que funcione. Debe transpilarlo a JavaScript básico que los navegadores comprendan. Todo eso puede resultar intimidante al principio, especialmente si tienes experiencia en PHP y tienes conocimientos limitados de ES6, JSX y React.
¡Pero no temas! Tenemos dos ejemplos que cubren los conceptos básicos de los filtros de bloque para ayudarle a comprender la idea y sentirse cómodo trabajando con filtros de JavaScript en WordPress. Como recordatorio, si escribe este código para el Editor de bloques es nuevo para usted, explore el complemento con ejemplos de este artículo.
Sin más preámbulos, echemos un vistazo al primer ejemplo.
Eliminar las opciones de alineación del bloque de portada
Vamos a filtrar el bloque de Cubierta principal y eliminaremos las opciones de alineación Izquierda, Centro, Derecha y Amplia de su configuración de bloque. Esto puede resultar útil en proyectos en los que el bloque Portada solo se utiliza como héroe de página o como banner de algún tipo y no necesita estar alineado a la izquierda oa la derecha.
Usaremos el blocks.registerBlockType
filtro. Recibe la configuración del bloque y su nombre y debes devolver un objeto de configuración filtrado. La configuración de filtrado nos permite actualizar el supports
objeto que contiene la matriz de alineaciones disponibles. Hagámoslo paso a paso.
Comenzaremos agregando el filtro que simplemente registra la configuración y el nombre del bloque en la consola, para ver con qué estamos trabajando:
const { addFilter } = wp.hooks;function filterCoverBlockAlignments(settings, name) { console.log({ settings, name }); return settings;}addFilter( 'blocks.registerBlockType', 'intro-to-filters/cover-block/alignment-settings', filterCoverBlockAlignments,);
Analicémoslo. La primera línea es una desestructuración básica del wp.hooks
objeto. Nos permite escribir addFilter()
en el resto del archivo, en lugar de wp.hooks.addFilter()
. Esto puede parecer redundante en este caso, pero es útil cuando se utilizan varios filtros en el mismo archivo (como veremos en el siguiente ejemplo).
A continuación, definimos la filterCoverBlockAlignments()
función que realiza el filtrado. Por ahora, solo registra el objeto de configuración y el nombre del bloque en la consola y devuelve la configuración tal como está.
Todas las funciones de filtro reciben datos y deben devolver datos filtrados. De lo contrario, el editor fallará.
Y, por último, iniciamos el filtro con addFilter()
función. Le proporcionamos el nombre del gancho que vamos a utilizar, el espacio de nombres del filtro y una función que realiza el filtrado.
Si hemos hecho todo bien, deberíamos ver muchos mensajes en la consola. Pero tenga en cuenta que no todos se refieren al bloque Portada.
Esto es correcto porque el filtro se aplica a todos los bloques y no al específico que queremos. Para solucionarlo, debemos asegurarnos de aplicar el filtro solo al core/cover
bloque:
function filterCoverBlockAlignments(settings, name) { if (name === 'core/cover') { console.log({ settings, name }); } return settings;}
Una vez implementado esto, deberíamos ver algo como esto ahora en la consola:
No se preocupe si ve más declaraciones de registro que bloques de portada en la página. Todavía tengo que descubrir por qué ese es el caso. Si sabes por qué, ¡compártelo en los comentarios!
Y aquí viene la parte divertida: el filtrado real. Si ha creado bloques desde cero antes, entonces sabrá que las opciones de alineación se definen con Supports API. Permítanme recordarles rápidamente cómo funciona: podemos configurarlo true
para permitir todas las alineaciones, así:
supports: { align: true}
…o proporcionar una variedad de alineaciones para respaldar. El siguiente fragmento hace lo mismo que el de arriba:
supports: { align: [ 'left', 'right', 'center', 'wide', 'full' ]}
Ahora echemos un vistazo más de cerca al settings
objeto de uno de los mensajes de la consola que tenemos y veamos con qué estamos tratando:
Todo lo que tenemos que hacer es reemplazarlo align: true
dentro align: ['full']
de la supports
propiedad. Así es como podemos hacerlo:
function filterCoverBlockAlignments(settings, name) { if (name === 'core/cover') { return assign({}, settings, { supports: merge(settings.supports, { align: ['full'], }), }); } return settings;}
Me gustaría hacer una pausa aquí para llamar su atención sobre los métodos assign
y merge
lodash. Los usamos para crear y devolver un objeto nuevo y asegurarnos de que el settings
objeto original permanezca intacto. El filtro seguirá funcionando si hacemos algo como esto:
/* WRONG APPROACH! DO NOT COPY PASTE! */settings.supports.align = ['full'];return settings;
…pero esa es una mutación de objeto, que se considera una mala práctica y debe evitarse a menos que sepas lo que estás haciendo. Zell Liew analiza en A List Apart por qué la mutación puede dar miedo.
Volviendo a nuestro ejemplo, ahora solo debería haber una opción de alineación en la barra de herramientas del bloque:
Elimine la opción de alineación “central” porque la barra de herramientas de alineación le permite alternar entre “activar” y “desactivar” la alineación. Esto significa que los bloques de portada ahora tienen estados predeterminados y de “ancho completo”.
Y aquí está el fragmento completo:
const { addFilter } = wp.hooks;const { assign, merge } = lodash;function filterCoverBlockAlignments(settings, name) { if (name === 'core/cover') { return assign({}, settings, { supports: merge(settings.supports, { align: ['full'], }), });} return settings;}addFilter( 'blocks.registerBlockType', 'intro-to-filters/cover-block/alignment-settings', filterCoverBlockAlignments,);
Esto no fue nada difícil, ¿verdad? Ahora tiene un conocimiento básico de cómo funcionan los filtros con bloques. Subimos de nivel y echamos un vistazo a un ejemplo un poco más avanzado.
Agregar un control de tamaño al bloque de botones
Ahora agregamos un control de tamaño al bloque de botones principal. Será un poco más avanzado ya que necesitaremos hacer que algunos filtros funcionen juntos. El plan es agregar un control que permitirá al usuario elegir entre tres tamaños para un botón: Pequeño, Regular y Grande.
Puede parecer complicado, pero una vez que lo analicemos, verás que en realidad es bastante sencillo.
1. Agregue un atributo de tamaño al bloque de botones.
Lo primero que debemos hacer es agregar un atributo adicional que almacene el tamaño del botón. Usaremos el blocks.registerBlockType
filtro ya familiar del ejemplo anterior:
/** * Add Size attribute to Button block * * @param {Object} settings Original block settings * @param {string} name Block name * @return {Object} Filtered block settings */function addAttributes(settings, name) { if (name === 'core/button') { return assign({}, settings, { attributes: merge(settings.attributes, { size: { type: 'string', default: '', }, }), }); } return settings;}addFilter( 'blocks.registerBlockType', 'intro-to-filters/button-block/add-attributes', addAttributes,);
La diferencia entre lo que estamos haciendo aquí y lo que hicimos antes es que estamos filtrando attributes
en lugar del supports
objeto. Este fragmento por sí solo no hace mucho y no notarás ninguna diferencia en el editor, pero tener un atributo para el tamaño es esencial para que todo funcione.
2. Agregue el control de tamaño al bloque de botones.
Estamos trabajando con un nuevo filtro editor.BlockEdit
. Nos permite modificar el panel de Controles del Inspector (es decir, el panel de configuración a la derecha del editor de bloques).
/** * Add Size control to Button block */const addInspectorControl = createHigherOrderComponent((BlockEdit) = { return (props) = { const { attributes: { size }, setAttributes, name, } = props; if (name !== 'core/button') { return BlockEdit {...props} /; } return ( Fragment BlockEdit {...props} / InspectorControls PanelBody title={__('Size settings', 'intro-to-filters')} initialOpen={false} SelectControl label={__('Size', 'intro-to-filters')} value={size} options={[ { label: __('Regular', 'intro-to-filters'), value: 'regular', }, { label: __('Small', 'intro-to-filters'), value: 'small' }, { label: __('Large', 'intro-to-filters'), value: 'large' }, ]} onChange={(value) = { setAttributes({ size: value }); }} / /PanelBody /InspectorControls /Fragment ); };}, 'withInspectorControl');addFilter( 'editor.BlockEdit', 'intro-to-filters/button-block/add-inspector-controls', addInspectorControl,);
Esto puede parecer mucho, pero lo desglosaremos y veremos qué tan sencillo es en realidad.
Lo primero que habrás notado es la createHigherOrderComponent
construcción. A diferencia de otros filtros en este ejemplo, editor.BlockEdit
recibe un componente y debe devolver un componente. Es por eso que necesitamos usar un patrón de componentes de orden superior derivado de React.
En su forma más pura, el filtro para agregar controles se parece a esto:
const addInspectorControl = createHigherOrderComponent((BlockEdit) = { return (props) = { // Logic happens here. return BlockEdit {...props} /; };}, 'withInspectorControl');
Esto no hará más que permitirle inspeccionar el BlockEdit /
componente y su contenido props
en la consola. Esperemos que la construcción en sí tenga sentido ahora y podamos seguir rompiendo el filtro.
La siguiente parte es desestructurar los accesorios:
const { attributes: { size }, setAttributes, name,} = props;
Esto se hace para que podamos usar name
, setAttributes
y size
en el alcance del filtro, donde:
size
es el atributo del bloque que hemos agregado en el paso 1.setAttributes
es una función que nos permite actualizar los valores de los atributos del bloque.name
es un nombre del bloque. que escore/button
en nuestro caso.
A continuación, evitamos agregar controles a otros bloques sin darnos cuenta:
if (name !== 'core/button') { return BlockEdit {...props} /;}
Y si estamos tratando con un bloque de botones, envolvemos el panel de configuración en un Fragment /
(un componente que representa a sus hijos sin un elemento envolvente) y agregamos un control adicional para elegir el tamaño del botón:
return ( Fragment BlockEdit {...props} / {/* Additional controls go here */} /Fragment);
Finalmente, los controles adicionales se crean así:
InspectorControls PanelBody title={__('Size settings', 'intro-to-filters')} initialOpen={false} SelectControl label={__('Size', 'intro-to-filters')} value={size} options={[ { label: __('Regular', 'intro-to-filters'), value: 'regular' }, { label: __('Small', 'intro-to-filters'), value: 'small' }, { label: __('Large', 'intro-to-filters'), value: 'large' }, ]} onChange={(value) = { setAttributes({ size: value }); }} / /PanelBody/InspectorControls
Nuevamente, si ya ha construido bloques antes, es posible que ya esté familiarizado con esta parte. Si no, te animo a que estudies la biblioteca de componentes que viene con WordPress.
En este punto deberíamos ver una sección adicional en los controles del inspector para cada bloque de botones:
También podemos guardar el tamaño, pero eso no se reflejará en el editor ni en la interfaz. Arreglemos eso.
3. Agregue una clase de tamaño al bloque en el editor.
Como sugiere el título, el plan para este paso es agregar una clase CSS al bloque de botones para que el tamaño seleccionado se refleje en el propio editor.
Usaremos el editor.BlockListBlock
filtro. Es similar a editor.BlockEdit
en el sentido de que recibe el componente y debe devolverlo; pero en lugar de filtrar el panel del inspector de bloques, filtra el componente del bloque que se muestra en el editor.
import classnames from 'classnames';const { addFilter } = wp.hooks;const { createHigherOrderComponent } = wp.compose;/** * Add size class to the block in the editor */const addSizeClass = createHigherOrderComponent((BlockListBlock) = { return (props) = { const { attributes: { size }, className, name, } = props; if (name !== 'core/button') { return BlockListBlock {...props} /; } return ( BlockListBlock {...props} className={classnames(className, size ? `has-size-${size}` : '')} / ); };}, 'withClientIdClassName');addFilter( 'editor.BlockListBlock', 'intro-to-filters/button-block/add-editor-class', addSizeClass);
Es posible que ya hayas notado una estructura similar:
- Extraemos las variables
size
,className
yname
deprops
. - A continuación, verificamos si estamos trabajando con
core/button
el bloque y devolvemos un archivo sin modificarBlockListBlock
si no es así. - Luego agregamos una clase a un bloque según el tamaño del botón seleccionado.
Me gustaría hacer una pausa en esta línea ya que puede parecer confuso a primera vista:
className={classnames(className, size ? `has-size-${size}` : '')}
Estoy usando la utilidad classnames aquí, y no es un requisito; simplemente encuentro que usarla es un poco más limpio que hacer concatenaciones manuales. Me evita preocuparme por olvidarme de agregar un espacio delante de una clase o por tener que lidiar con espacios dobles.
4. Agregue la clase de tamaño al bloque en la parte frontal.
Todo lo que hemos hecho hasta este punto está relacionado con la vista del Editor de bloques, que es como una vista previa de lo que podríamos esperar en la interfaz. Si cambiamos el tamaño del botón, guardamos la publicación y verificamos el marcado del botón en la parte frontal, observe que la clase de botón no se aplica al bloque.
Para solucionar este problema, debemos asegurarnos de que realmente estamos guardando los cambios y agregando la clase al bloque en la interfaz. Lo hacemos con blocks.getSaveContent.extraProps
filtro, que se engancha a la función del bloque save()
y nos permite modificar las propiedades guardadas. Este filtro recibe accesorios de bloque, el tipo de bloque y los atributos del bloque, y debe devolver accesorios de bloque modificados.
import classnames from 'classnames';const { assign } = lodash;const { addFilter } = wp.hooks;/** * Add size class to the block on the front end * * @param {Object} props Additional props applied to save element. * @param {Object} block Block type. * @param {Object} attributes Current block attributes. * @return {Object} Filtered props applied to save element. */function addSizeClassFrontEnd(props, block, attributes) { if (block.name !== 'core/button') { return props; } const { className } = props; const { size } = attributes; return assign({}, props, { className: classnames(className, size ? `has-size-${size}` : ''), });}addFilter( 'blocks.getSaveContent.extraProps', 'intro-to-filters/button-block/add-front-end-class', addSizeClassFrontEnd,);
En el fragmento de arriba hacemos tres cosas:
- Comprobar si estamos trabajando con un
core/button
bloque y hacer un retorno rápido si no es así. - Extraiga las variables
className
y de los objetos y respectivamente.size
props
attributes
- Cree un nuevo
props
objeto con unaclassName
propiedad actualizada que incluya una clase de tamaño si es necesario.
Esto es lo que deberíamos esperar ver en el marcado, completo con nuestra clase de tamaño:
div a href="#"Click Me/a/div
5. Agregue CSS para los tamaños de botones personalizados.
¡Una cosita más antes de que terminemos! La idea es asegurarse de que los botones grandes y pequeños tengan estilos CSS correspondientes.
Estos son los estilos que se me ocurrieron:
.wp-block-button.has-size-large .wp-block-button__link { padding: 1.5rem 3rem;}.wp-block-button.has-size-small .wp-block-button__link { padding: 0.25rem 1rem;}
Si está creando un tema personalizado, puede incluir estos estilos de interfaz en la hoja de estilos del tema. Creé un complemento para el tema predeterminado Twenty Twenty One, por lo que, en mi caso, tuve que crear una hoja de estilo separada e incluirla usando wp_enqueue_style()
. Podrías trabajar directamente con la misma facilidad functions.php
si es ahí donde administras las funciones.
function frontend_assets() { wp_enqueue_style( 'intro-to-block-filters-frontend-style', plugin_dir_url( __FILE__ ) . 'assets/frontend.css', [], '0.1.0' );}add_action( 'wp_enqueue_scripts', 'frontend_assets' );
Al igual que en la interfaz, debemos asegurarnos de que los botones tengan el estilo adecuado en el editor. Podemos incluir los mismos estilos usando la enqueue_block_editor_assets
acción:
function editor_assets() { wp_enqueue_style( 'intro-to-block-filters-editor-style', plugin_dir_url( __FILE__ ) . 'assets/editor.css', [], '0.1.0' );}add_action( 'enqueue_block_editor_assets', 'editor_assets' );
¡Ahora deberíamos tener estilos para botones grandes y pequeños en la interfaz y en el editor!
Como mencioné anteriormente, estos ejemplos están disponibles como un complemento de WordPress que creé solo para este artículo. Entonces, si quieres ver cómo funcionan todas estas piezas juntas, descárgalas en GitHub y hackéalas. Y si algo no queda claro, no dudes en preguntar en los comentarios.
¿Usar filtros o crear un nuevo bloque?
Esta es una pregunta difícil de responder sin conocer el contexto. Pero hay un consejo que puedo ofrecer.
¿Alguna vez has visto un error como este?
Suele ocurrir cuando el marcado del bloque en la página es diferente del marcado generado por la save()
función del bloque. Lo que quiero decir es que es muy fácil generar este error al jugar con el marcado de un bloque con filtros.
Entonces, si necesita cambiar significativamente el marcado de un bloque más allá de agregar una clase, consideraría escribir un bloque personalizado en lugar de filtrar uno existente. Es decir, a menos que esté de acuerdo con mantener el marcado consistente para el editor y solo cambiar el marcado del front-end. En ese caso, puede utilizar el filtro PHP.
Hablando de que…
Consejo adicional:render_block()
Este artículo no estaría completo sin mencionar el render_block
anzuelo. Filtra el marcado de bloque antes de renderizarlo. Resulta útil cuando necesita actualizar el marcado del bloque más allá de agregar una nueva clase.
La gran ventaja de este enfoque es que no provocará ningún error de validación en el editor. Dicho esto, la desventaja es que sólo funciona en la parte frontal. Si tuviera que reescribir el ejemplo del tamaño del botón usando este enfoque, primero necesitaría eliminar el código que escribimos en el cuarto paso y agregar esto:
/** * Add button size class. * * @param string $block_content Block content to be rendered. * @param array $block Block attributes. * @return string */function add_button_size_class( $block_content = '', $block = [] ) { if ( isset( $block['blockName'] ) 'core/button' === $block['blockName'] ) { $defaults = ['size' = 'regular']; $args = wp_parse_args( $block['attrs'], $defaults ); $html = str_replace( 'divtoken punctuation", 'divtoken punctuation". esc_attr( $args['size']) . ' ', $block_content ); return $html;} return $block_content;}add_filter( 'render_block', 'add_button_size_class', 10, 2 );
Este no es el enfoque más limpio porque estamos inyectando una clase CSS usando str_replace()
, pero a veces esa es la única opción. Un ejemplo clásico podría ser trabajar con un bloque de terceros donde necesitamos agregar una div
clase a su alrededor para darle estilo.
Terminando
Los filtros de bloqueo de WordPress son poderosos. Me gusta cómo te permite desactivar muchas opciones de bloque no utilizadas, como hicimos con el bloque de portada en el primer ejemplo. Esto puede reducir la cantidad de CSS que necesita escribir, lo que, a su vez, significa una hoja de estilo más sencilla y menos mantenimiento, y menos sobrecarga cognitiva para cualquiera que use la configuración de bloque.
Pero como mencioné antes, usar filtros de bloque para modificaciones importantes puede resultar complicado porque es necesario tener en cuenta la validación de bloques.
Dicho esto, normalmente recurro a filtros de bloqueo si necesito:
- deshabilitar ciertas funciones de bloqueo,
- agregar una opción a un bloque y no puede/no quiere hacerlo con un estilo personalizado (y esa opción no debe modificar el marcado del bloque más allá de agregar/eliminar una clase personalizada), o
- modifique el marcado solo en la interfaz (usando un filtro PHP).
También suelo escribir bloques personalizados cuando los bloques principales requieren grandes ajustes de marcado tanto en el front-end como en el editor.
Si ha trabajado con filtros de bloqueo y tiene otras ideas, preguntas o comentarios, ¡hágamelo saber!
Recursos
- Referencia de filtro de bloque
- Manual del editor de bloques
- Complemento de demostración en GitHub
Deja un comentario