gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
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!
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_type
ajustado a doctrine_recipe
:
... lines 1 - 5 | |
use Symfony\Component\DependencyInjection\Attribute\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.
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.
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()
:
{{ dump() }} |
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.
"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
}
}