Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Cargador de valores + Plantilla de vista previa

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Así que nuestro navegador de contenidos funcionaba de maravilla... hasta que seleccionamos un elemento. En ese momento, eligió hacer una cosa extraña: ¡explotar! La llamada Ajax que falló dice

El cargador de valores para el tipo de valor doctrine_recipe no existe.

Para repasar: tenemos un tipo de valor personalizado llamado doctrine_recipe, que creamos para poder añadir cuadrículas y listas de entidades Recipe. Para que esto funcione, tenemos (1): un conversor de valores para convertir los objetos Recipe a un formato que entiendan los diseños. (2) una clase de consulta que nos permita utilizar colecciones dinámicas. (3) una clase de backend de navegador para permitirnos seleccionar elementos manuales. Y ahora (4), necesitamos un cargador de valores que sea capaz de tomar el "id" de estos elementos seleccionados manualmente y convertirlos en objetosRecipe. Ésta será la última "cosa" que necesitaremos para nuestro tipo de valor, ¡lo prometo!

Crear y etiquetar el cargador de valores

Dentro del directorio src/Layouts/, crea una nueva clase llamada RecipeValueLoader, haz que implemente ValueLoaderInterface y genera los dos métodos que necesita:

... lines 1 - 2
namespace App\Layouts;
use Netgen\Layouts\Item\ValueLoaderInterface;
class RecipeValueLoader implements ValueLoaderInterface
{
public function load($id): ?object
{
// TODO: Implement load() method.
}
public function loadByRemoteId($remoteId): ?object
{
// TODO: Implement loadByRemoteId() method.
}
}

Son bastante sencillos. Pero, antes de rellenarlos, vuelve a la ruta Ajax y actualiza para ver... exactamente el mismo error. ¿Por qué? Como hemos visto con otras cosas, necesitamos "asociar" este RecipeValueLoader a nuestro tipo de valor doctrine_recipe. ¿Cómo? Sin sorpresas Con una etiqueta. Digamos #[AutoconfigureTag()] y esta vez se llamanetgen_layouts.cms_value_loader. Para el segundo argumento, pasa value_typeajustado a doctrine_recipe:

... lines 1 - 5
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('netgen_layouts.cms_value_loader', ['value_type' => 'doctrine_recipe'])]
class RecipeValueLoader implements ValueLoaderInterface
{
... lines 11 - 19
}

¡Perfecto! Si recargamos ahora... ¡mejor! Ese error se debe a que aún no hemos rellenado la lógica.

Añadir la lógica

Muy sencillo, necesitamos tomar el ID y devolver el objeto Recipe. Para ello, crea un constructor que acepte un argumento RecipeRepository $recipeRepository. Y... déjame limpiar las cosas:

... lines 1 - 4
use App\Repository\RecipeRepository;
... lines 6 - 9
class RecipeValueLoader implements ValueLoaderInterface
{
public function __construct(private RecipeRepository $recipeRepository)
{
}
... lines 15 - 24
}

Ahora, aquí abajo, devuelve $this->recipeRepository->find() y pasa $id. Para loadByRemoteId(), que sólo necesitamos si vamos a utilizar la función de importación para mover contenido entre bases de datos, sólo return $this->load($id):

... lines 1 - 9
class RecipeValueLoader implements ValueLoaderInterface
{
... lines 12 - 15
public function load($id): ?object
{
return $this->recipeRepository->find($id);
}
public function loadByRemoteId($remoteId): ?object
{
return $this->load($remoteId);
}
}

Y ahora... ¡la llamada Ajax funciona! Y lo que es más importante, si actualizamos todo el administrador de diseños... ¡sí! ¡Mira nuestra cuadrícula! ¡Tenemos cuatro elementos manuales! ¡Eso es genial! Podemos reordenarlos si queremos, añadir más, eliminarlos, lo que sea.

Prueba a publicar esta página y luego recarga la página de inicio. ¡Ahí están! Aunque faltan nuestras "últimas recetas". ¡Vaya! Creo que accidentalmente también cambié esto a una colección manual. Vuelve a cambiarla a una colección dinámica, se ve bien, publica y.... ahora... genial: todo está de vuelta.

Añadir la vista previa

Así que ya podemos seleccionar elementos manuales a través del navegador de contenido... aunque cuando añadimos originalmente la configuración para todo esto, establecimos una plantilla de vista previa... ¡pero nunca la creamos!

Abramos de nuevo el navegador de contenidos. Entonces, en la rejilla manual, pulsa "Añadir elementos". La plantilla de vista previa activa el modo de vista previa aquí arriba. Si hacemos clic en un elemento, nos muestra una vista previa. Bueno, lo haría... excepto porque en realidad no hemos añadido esa plantilla.

Para que esto funcione, tenemos que hacer dos pequeñas cosas. Primero, abrirRecipeBrowserBackend. Aquí nos hemos saltado algunos métodos. Por ejemplo, omitimosgetSubLocations() y getSubLocationsCount() porque sólo son necesarios si tienes una jerarquía de ubicaciones.

También nos hemos saltado loadItem(). Se utiliza para la vista previa. Nos pasará el ID de lo que se ha cargado y necesitamos devolver un ItemInterface. Así de sencillo, podemos devolver un new RecipeBrowserItem() -que es la pequeña clase que envuelve al Recipe - pasando $this->recipeRepository->find($value):

... lines 1 - 12
class RecipeBrowserBackend implements BackendInterface
{
... lines 15 - 32
public function loadItem($value): ItemInterface
{
return new RecipeBrowserItem($this->recipeRepository->find($value));
}
... lines 37 - 88
}

¡Genial! Lo único que tenemos que hacer es... ¡crear la plantilla de vista previa! En templates/nglayouts/, añade un nuevo directorio llamado content_browser/, y dentro, un nuevo archivo llamado recipe_preview.html.twig. Para empezar, sólo tienes que imprimir la funcióndump():

Lo bueno es que ni siquiera necesitamos actualizar. Mientras hagamos clic en un elemento en el que no hayamos hecho ya clic... ¡funciona! Y mira esta variable item: es una instancia de RecipeBrowserItem... así que una instancia de esta clase de aquí.

Eso es genial... excepto que RecipeBrowserItem no tiene una forma de que obtengamos el objeto Recipe real. Afortunadamente, podemos arreglar eso nosotros mismos. Después de todo, ¡esta es nuestra clase! Iré a "Código"->"Generar" para generar un métodogetRecipe():

... lines 1 - 7
class RecipeBrowserItem implements ItemInterface
{
... lines 10 - 33
public function getRecipe(): Recipe
{
return $this->recipe;
}
}

Ahora, en la plantilla, podemos decir {{ item.recipe.name }}. Y para hacerlo más elegante, añade un <img cuyo src se establezca en item.recipe.imageUrl... también con un atributo alt:

<strong>{{ item.recipe.name }}</strong>
<br><br>
<img src="{{ asset(item.recipe.imageUrl) }}" alt="Recipe Image">

Una vez más, no necesitamos actualizar. Si haces clic en un elemento que ya has previsualizado, lo cargará desde la memoria. Pero si haces clic en uno nuevo... ¡sí! ¡Ahí está nuestra vista previa! Genial.

Vale, ya hemos terminado con los elementos manuales, el navegador de contenidos y todo esto. Por cierto, hay una forma de añadir más columnas a esta tabla, como nombre de archivo, tamaño de archivo, fecha de creación, etc. No vamos a hablar de eso, pero es totalmente posible.

Comprobación de estado: en este punto, tenemos la capacidad de añadir un diseño a cualquier página, reordenar el contenido de la página, añadir título, texto, bloques HTML, o incluso listas y cuadrículas de recetas dinámicas. Eso es mucho poder. ¡Ahora quiero más poder! Quiero que sea posible utilizar la cuadrícula y los bloques de lista para añadir otros elementos a nuestra página... elementos que no viven en absoluto en nuestra base de datos. Eso a continuación.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice