gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
¡Míranos! Hemos llegado al último tema del tutorial. Ya hemos transformado nuestro sitio estático en uno en el que podemos reordenar el diseño de cada página, mezclarlo con código personalizado de plantillas Twig y añadir contenido dinámico. Eso es... algo impresionante. Por supuesto, no hemos cubierto todo lo que puedes hacer con los Diseños, pero ahora eres realmente peligroso.
Un tema que no hemos tratado es cómo crear un bloque totalmente nuevo, pero está documentado y, a estas alturas, creo que no sería demasiado difícil. ¿Por qué deberías crear un bloque personalizado? Supongamos que tienes algo superpersonalizado como nuestra área "Héroe" o esta área "suscribirse al boletín", que en realidad está potenciada por el paquete UX Live Component de Symfony, que le da el elegante comportamiento Ajax.
En cualquier caso, si quieres algo así en tu página, la forma más sencilla de añadirlo es... como hice yo en este proyecto: poner la lógica en Symfony, renderizar dentro de un bloque Twig, y luego incluir ese bloque Twig dentro de Layouts.
¿Pero qué pasa si queremos que el usuario administrador pueda añadir esto a varias páginas cuando quiera? Entonces sería útil crear un bloque personalizado. Los bloques personalizados también pueden tener opciones, así que incluso podrías permitirles personalizarlo de alguna manera.
De todos modos, hagamos un último reto relacionado con los bloques: crear un plugin de bloques. Ve a una página de demostración de habilidades. Hmm, probablemente nos vendría bien un poco más de margen entre estos bloques. Y esa es una necesidad bastante común. Podríamos manejar esto añadiendo una clase CSS que establezca el margen. Pero quiero hacerlo aún más fácil.
Ve al administrador de Diseños y edita el Diseño de habilidad individual. Bien, supongamos que queremos añadir algo de margen aquí. Para ello, quiero que el usuario administrador pueda hacer clic en cualquier bloque del sistema -por ejemplo, este bloque de columnas- y, en la pestaña de diseño, seleccionar el margen superior o inferior que necesite de un nuevo campo de formulario.
Es un objetivo bastante descabellado... ¡porque, para conseguirlo, necesitamos poder modificar todos los bloques del sistema! Afortunadamente, ése es exactamente el objetivo de un plugin de bloque: ampliar uno -o todos- los bloques.
Manos a la obra. En el directorio src/Layouts/
, crea una nueva clase PHP llamada, qué tal, VerticalWhitespacePlugin
. Tiene que implementar PluginInterface
. Pero en la práctica, extendemos una clase Plugin
que implementa esa interfaz por nosotros. Ve a "Código"->"Generar", o Command
+N
en un Mac, e implementa el único método que necesitamos: getExtendedHandlers()
:
... lines 1 - 2 | |
namespace App\Layouts; | |
use Netgen\Layouts\Block\BlockDefinition\Handler\Plugin; | |
class VerticalWhitespacePlugin extends Plugin | |
{ | |
public static function getExtendedHandlers(): iterable | |
{ | |
// TODO: Implement getExtendedHandlers() method. | |
} | |
} |
Vale, cada bloque del sistema -es decir, cada elemento de aquí del menú de la izquierda- tiene una clase detrás llamada manejador de bloques. Nuestro trabajo en getExtendedHandlers()
es devolver un iterable de todos los "manejadores" que queramos extender. Por ejemplo, si sólo quisieras extender el bloque del título, podríasyield TitleHandler::class
. ¿Cómo sabía que debía utilizar esa clase? Bueno, la mayoría de las veces puedes adivinarlo: el bloque de título tiene un TitleHandler
. Pero si quieres mirar más a fondo, puedes ver todos los manejadores del sistema ejecutando:
php bin/console debug:container --tag=netgen_layouts.block_definition_handler
De todos modos, en nuestro caso, queremos anular cada bloque. Así que podemosyield
BlockHandlerDefinitionInterface::class, porque cada manejador de bloque debe implementar esa interfaz:
... lines 1 - 4 | |
use Netgen\Layouts\Block\BlockDefinition\BlockDefinitionHandlerInterface; | |
... lines 6 - 7 | |
class VerticalWhitespacePlugin extends Plugin | |
{ | |
public static function getExtendedHandlers(): iterable | |
{ | |
yield BlockDefinitionHandlerInterface::class; | |
} | |
} |
Y sí, acabo de olvidar por completo la palabra Definition
. ¡Uy! Arreglaré esta mala interfaz en un minuto.
Para saber qué hacer a continuación, vuelve al menú "Código"->"Generar", selecciona "Anular métodos" y elige buildParameters()
. No necesitamos llamar al método padre porque está vacío:
... lines 1 - 6 | |
use Netgen\Layouts\Parameters\ParameterBuilderInterface; | |
... lines 8 - 9 | |
class VerticalWhitespacePlugin extends Plugin | |
{ | |
... lines 12 - 16 | |
public function buildParameters(ParameterBuilderInterface $builder): void | |
{ | |
... lines 19 - 27 | |
} | |
} |
Parámetro es la palabra que utiliza Layouts para las opciones del formulario que puedes personalizar en la parte derecha de la pantalla para cada bloque. Gracias a nuestro método getExtendedHandlers()
, cuando Layouts construya esas opciones para cualquier bloque, ahora llamará a este método y podremos añadir nuevos parámetros.
También necesitamos una declaración use
para este espacio de nombresParameterType
:
... lines 1 - 7 | |
use Netgen\Layouts\Parameters\ParameterType; | |
class VerticalWhitespacePlugin extends Plugin | |
{ | |
... lines 12 - 16 | |
public function buildParameters(ParameterBuilderInterface $builder): void | |
{ | |
$builder->add( | |
'vertical_whitespace:enabled', | |
ParameterType\Compound\BooleanType::class, | |
[ | |
'default_value' => false, | |
'label' => 'Enable Vertical Whitespace?', | |
'groups' => [self::GROUP_DESIGN], | |
], | |
); | |
} | |
} |
¡Genial! Como puedes ver, Layouts viene con un montón de "tipos de campo" incorporados, comoBooleanField
, que se mostrará como una casilla de verificación. Su valor predeterminado es falso y tiene una etiqueta. Ah, ¿y este grupo? ¿Recuerdas que hay dos pestañas: "Diseño" y "Contenido"? Aquí es donde determinas dentro de cuál debe vivir tu parámetro.
Y la primera clave - vertical_whitespace:enabled
es el nombre interno de este campo. Verás cómo lo utilizamos en un minuto.
Antes de que lo intentemos, el futuro Ryan acaba de informarme de que... ¡he metido la pata! Típico, desplázate hacia arriba. ¡Estoy cediendo la clase equivocada! Rendimiento BlockDefinitionHandlerInterface::class
:
... lines 1 - 4 | |
use Netgen\Layouts\Block\BlockDefinition\BlockDefinitionHandlerInterface; | |
... lines 6 - 9 | |
class VerticalWhitespacePlugin extends Plugin | |
{ | |
public static function getExtendedHandlers(): iterable | |
{ | |
yield BlockDefinitionHandlerInterface::class; | |
} | |
... lines 16 - 28 | |
} |
Así está mejor.
Ahora vamos a probar. Actualiza... haz clic en cualquier bloque... déjame encontrar mi bloque Título... y... ¡ahí está! ¡En cualquier bloque vemos el nuevo campo!
Pero, la idea real es que, si el usuario lo activa, le mostramos dos campos más donde puede seleccionar el margen superior o inferior.
Para ello, después del primer campo, pegaré dos parámetros más:
... lines 1 - 9 | |
class VerticalWhitespacePlugin extends Plugin | |
{ | |
... lines 12 - 16 | |
public function buildParameters(ParameterBuilderInterface $builder): void | |
{ | |
$builder->add( | |
'vertical_whitespace:enabled', | |
... lines 21 - 26 | |
); | |
$builder->get('vertical_whitespace:enabled')->add( | |
'vertical_whitespace:top', | |
ParameterType\ChoiceType::class, | |
[ | |
'default_value' => 'medium', | |
'label' => 'Top Spacing', | |
'options' => [ | |
'None' => 'none', | |
'Small' => 'small', | |
'Medium' => 'medium', | |
'Large' => 'large', | |
], | |
'groups' => [self::GROUP_DESIGN], | |
], | |
); | |
$builder->get('vertical_whitespace:enabled')->add( | |
'vertical_whitespace:bottom', | |
ParameterType\ChoiceType::class, | |
[ | |
'default_value' => 'medium', | |
'label' => 'Bottom Spacing', | |
'options' => [ | |
'None' => 'none', | |
'Small' => 'small', | |
'Medium' => 'medium', | |
'Large' => 'large', | |
], | |
'groups' => [self::GROUP_DESIGN], | |
], | |
); | |
} | |
} |
Estos son básicamente como el primero. La gran diferencia es que, aquí arriba, dijimos$builder->add()
. Pero ahora tenemos $builder->get('vertical_whitespace:enabled')
y luego ->add()
. Esto hace que estos campos sean hijos del primero.
Esto está muy bien. Actualiza y... vamos a buscar el bloque Columna. Haz clic en "Activar espacio en blanco vertical". ¡Guau! ¡Aparecen los otros dos campos! Hagamos un espaciado superior "Medio" y un espaciado inferior "No". Publícalo.
Sin embargo, no debería sorprenderte demasiado que cuando actualicemos la página... ¡no ocurra absolutamente nada! Hemos añadido esas opciones... pero aún no las estamos utilizando en ningún sitio. Para ello, necesitamos anular una plantilla.
Pensemos: queremos que este margen superior e inferior se aplique a todos los bloques del sistema. Y, afortunadamente, todos los bloques del sistema acaban extendiéndose ablock.html.twig
: éste de aquí, en el directorio nglayouts/themes/
.
Cópialo. Luego, anúlalo a través del sistema temático. Si seguimos la ruta...standard/block
... standard/block
... el nuevo archivo debería vivir aquí:block.html.twig
. Pega el contenido dentro.
Para asegurarte de que funciona, pon un poco de TEST
:
{% set css_class = ['ngl-block', 'ngl-' ~ block.definition.identifier, 'ngl-vt-' ~ block.viewType, css_class|default(block.parameter('css_class').value)]|join(' ') %} | |
{% set css_id = css_id|default(block.parameter('css_id').value) %} | |
{% set set_container = block.parameter('set_container').value %} | |
{% if show_empty_wrapper is not defined %} | |
{% set show_empty_wrapper = false %} | |
{% endif %} | |
{% set block_content = (block('content') is defined ? block('content') : '')|trim %} | |
{% if block_content is not empty or show_empty_wrapper %} | |
<div class="{{ css_class }}" {% if css_id is not empty %} id="{{ css_id }}" {% endif %}> | |
TEST | |
{% if set_container %}<div class="container">{% endif %} | |
{{ block_content|raw }} | |
{% if set_container %}</div>{% endif %} | |
</div> | |
{% endif %} |
¡Ok! Actualiza el frontend. ¡Yupi! Sí, definitivamente funciona. Vamos... quita eso.
En la parte superior de la plantilla, tenemos una variable llamada css_class
, que está establecida en algunas clases principales. Y ¡eh! ¡Llama a block.parameter('css_class')
! Sí, ¡eso es lo que lee el campo "Clase CSS" de las opciones del bloque!
Luego, utiliza |join(' ')
para combinar todo esto en una cadena.
Voy a eliminar ese join()
... y luego cambiaré el nombre de esta variable a css_classes
:
{% set css_classes = ['ngl-block', 'ngl-' ~ block.definition.identifier, 'ngl-vt-' ~ block.viewType, css_class|default(block.parameter('css_class').value)] %} | |
... lines 2 - 21 |
Estamos configurando las cosas para que podamos modificar fácilmente esa variable. Aquí abajo, justo antes de block_content
, vuelve a crear esa variable css_class
configurada comocss_classes|join('
'):
... lines 1 - 8 | |
{% set css_class = css_classes|join(' ') %} | |
{% set block_content = (block('content') is defined ? block('content') : '')|trim %} | |
... lines 11 - 21 |
Esta variable se utiliza en un montón de sitios diferentes y en plantillas hijo, así que tenemos que asegurarnos de que sigue establecida.
De todas formas, aquí arriba, ahora tenemos una matriz css_classes
. ¡Vamos a utilizarla! Voy a pegar tres variables, cada una ajustada al valor de nuestros tres parámetros:
... lines 1 - 2 | |
{% set set_container = block.parameter('set_container').value %} | |
{% set use_whitespace = block.parameter('vertical_whitespace:enabled').value is same as(true) %} | |
{% set whitespace_top = block.parameter('vertical_whitespace:top').value %} | |
{% set whitespace_bottom = block.parameter('vertical_whitespace:bottom').value %} | |
... lines 8 - 29 |
Aquí es donde resulta útil el nombre del parámetro que utilizamos en la clase.
Ahora, muy sencillo, si use_whitespace
, entonces añade algunas clases de margen. También pegaré ese código:
... lines 1 - 4 | |
{% set use_whitespace = block.parameter('vertical_whitespace:enabled').value is same as(true) %} | |
{% set whitespace_top = block.parameter('vertical_whitespace:top').value %} | |
{% set whitespace_bottom = block.parameter('vertical_whitespace:bottom').value %} | |
{% if use_whitespace %} | |
{% set css_classes = css_classes|merge(['whitespace-top-' ~ whitespace_top]) %} | |
{% set css_classes = css_classes|merge(['whitespace-bottom-' ~ whitespace_bottom]) %} | |
{% endif %} | |
... lines 12 - 29 |
Así, para el margen superior, añadiremos un nuevo whitespace-top-
seguido de none
,small
, medium
o large
. Y lo mismo para el inferior.
Estas nuevas clases son totalmente inventadas: no forman parte del CSS de Bootstrap ni de nada, pero podrías hacer esto más inteligente para reutilizarlas. Pero para nosotros, si abres assets/styles/app.css
... cerca de la parte superior, ¡allá vamos!
... lines 1 - 12 | |
.whitespace-top-small { | |
padding-top: 2rem; | |
} | |
.whitespace-top-medium { | |
padding-top: 4rem; | |
} | |
.whitespace-top-large { | |
padding-top: 8rem; | |
} | |
.whitespace-bottom-small { | |
padding-bottom: 2rem; | |
} | |
.whitespace-bottom-medium { | |
padding-bottom: 4rem; | |
} | |
.whitespace-bottom-large { | |
padding-bottom: 8rem; | |
} | |
... lines 31 - 108 |
Antes del tutorial, ya he preparado esas clases.
Así que... ¡debería funcionar! Muévete y actualiza. ¡Ya está! Nuestro bloque tiene un pequeño espacio en blanco superior extra... que proviene de nuestra nueva clase.
Y... ¡listo!, ¡Woo! ¡Gran trabajo, equipo! ¡Ahora eres un campeón de Layouts! Hacednos saber qué cosas chulas estáis construyendo con él. Y si tienes alguna pregunta, como siempre, estamos a tu disposición en la sección de comentarios.
Muy bien, gracias y hasta la próxima.
"Houston: no signs of life"
Start the conversation!
// composer.json
{
"require": {
"php": ">=8.1.0",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.7", // v3.7.0
"doctrine/doctrine-bundle": "^2.7", // 2.7.0
"doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
"doctrine/orm": "^2.13", // 2.13.3
"easycorp/easyadmin-bundle": "^4.4", // v4.4.1
"netgen/layouts-contentful": "^1.3", // 1.3.2
"netgen/layouts-standard": "^1.3", // 1.3.1
"pagerfanta/doctrine-orm-adapter": "^3.6",
"sensio/framework-extra-bundle": "^6.2", // v6.2.8
"stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
"symfony/console": "5.4.*", // v5.4.14
"symfony/dotenv": "5.4.*", // v5.4.5
"symfony/flex": "^1.17|^2", // v2.2.3
"symfony/framework-bundle": "5.4.*", // v5.4.14
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/proxy-manager-bridge": "5.4.*", // v5.4.6
"symfony/runtime": "5.4.*", // v5.4.11
"symfony/security-bundle": "5.4.*", // v5.4.11
"symfony/twig-bundle": "5.4.*", // v5.4.8
"symfony/ux-live-component": "^2.x-dev", // 2.x-dev
"symfony/ux-twig-component": "^2.x-dev", // 2.x-dev
"symfony/validator": "5.4.*", // v5.4.14
"symfony/webpack-encore-bundle": "^1.15", // v1.16.0
"symfony/yaml": "5.4.*", // v5.4.14
"twig/extra-bundle": "^2.12|^3.0", // v3.4.0
"twig/twig": "^2.12|^3.0" // v3.4.3
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
"symfony/debug-bundle": "5.4.*", // v5.4.11
"symfony/maker-bundle": "^1.47", // v1.47.0
"symfony/stopwatch": "5.4.*", // v5.4.13
"symfony/web-profiler-bundle": "5.4.*", // v5.4.14
"zenstruck/foundry": "^1.22" // v1.22.1
}
}