Agregar widgets en un Fragmento de Página en Liferay 7. 1

Hace pocos meses que la nueva versión de Liferay ya está disponible para todos nosotros, ésta es la versión Liferay 7.1 la cual trae consigo características nuevas é interesantes.

Una de éstas características nuevas son los Fragmentos de Página (Page Fragments), los cuales son pequeños modulos de HTML, CSS y JS los cuales puedes añadir a Páginas de Contenido para dar forma a una página con contenido relativamente estático. (Las Páginas de Contenido son también algo nuevo en ésta versión de 7.1).

Pensemos en una página que no requiere de mostrar contenido dinámico, en la cual quieres presentar un banner en la parte superior compuesto por una imágen y un encabezado después, un componente con 3 cards que presentan distinta información y finalmente quieres mostrar un formulario de registro.

Bien, vamos concentrarnos en el último componente, en éste caso el formulario. Para lograrlo, tenemos que seguir los sigueintes pasos:

1.- Ve a la sección de Content -> Forms desde el menu de administración del sitio en tu instancia de Liferay previamente ya levantada.

2.- Ya en la sección de Formularios, crea una forma de manera tradicional y publícalo.

1.- Creando Formulario de registro

3.- Ahora, nuevamente desde el menu de administración, dirigete a Build -> Page Fragments.

4.- Para poder crear un fragmento nuevo, necesitas crear una colección, ésto para tener una organización de tus fragmentos. En nuestro caso, creamos una colección llamda Fragmentos – Registro.

5.- Ahora que ya tenemos esa colección lista, seleccionala y crea una Página de Fragmento, que como ya te has dado cuenta, en Liferay 7.1 el botón para crear/agregar un nuevo elemento está en la parte superior derecha, a diferencia de la versión 7.0 que lo tiene en la esquinar inferior derecha.

6.- Dale un nombre a tu Fragmento de Página y guardala, depués de eso notarás que tienes disponible en tu pantalla una página con 4 divisiones.

* La primera es para añadir HTML, verás que ya tienes un div con una clase generada dinamiamente, la sugerencia es que no lo cambies y dentro de ése div agregues el HTML que necesitas.

 * La segunda es para que puedas añadir CSS, donde verás la misma clase de la sección de HTML, donde podrás agregar reglas para estilizar tu HTML.

 * La tercera división es donde podrás añadir Javascript en caso de ser necesario.

 * Y finalmente, la cuarta división es donde podrás previsualizar el HTML, estilos y funcionalidad javascript que añadiste previamente.

2.- Cómo crear un Fragmento de Página

7.- Bien, ahora que ya conocemos nuestra área de trabajo para crear un Fragmento de Página, te preguntarás como puedo agregar un Widget/Aplicación/Portlet a un Fragmento de Página, bueno para eso Liferay nos proporciona un par de tags que podemos utilizar en nuestro HTML cuando estamos haciendo un Fragmento de Página.

  • <ltr-editable></ltr-editable>
  • <ltr-widget-[portlet-alias]></ltr-widget-[portlet-alias]>

Donde <ltr-editable> nos va a ser útil para hacer posible la edición de texto ó imágenes, por ejemplo:

<p><ltr-editable id="miParrafo" type="text">Aquí va mi párrafo</ltr-editable></p>

Donde una vez añadido nuestro Fragmento de Página a una Página de Contenido, podremos editar el texto de nuestro párrafo dandole doble click sobre el texto.

Ahora si, después de la espera, te platico que el tag para añadir Widgets/Aplicaciones/Portles a un Fragmento de Página es <ltr-widget-[portlet-alias]> donde el portlet alias es una propiedad nueva en los Portlets de Liferay 7.1, la cual se agrega se agrega de la siguiente manera:

@Component(
	immediate = true,
	property = {
		"com.liferay.fragment.entry.processor.portlet.alias=form",
        .
        .
        .
        }
);

Entonces, para poder injectar nuestro Widget de Formulario, necesitamos conocer el portlet alias, para eso, visitamos el repositorio de Liferay Portal, donde hicimos una busqueda de la propiedad portlet alias.

Ya con nuestro portlet alias en mano, procedemos a añadir nuestro tag dentro de nuestro Fragmento de Página en el espacio designado para el HTML.

<div class="fragment_36091">
  <lfr-widget-form></lfr-widget-form>
</div>

 

Una vez insertado nuestro tag, podrás ver en el spacio de previualización que tienes ya la opción para seleccionar el formulario previamente creado.

Puedes seleccionarlo desde el Fragmento de Página, pero no prodrás visualizar la formar hasta que éste sea añadido a una Página de Contenido.

Bueno, ahora ya sabes que es un Fragmento de Página, como crear uno y sobre todo, como añadir Widget dentro de ellos.

Si quieres saber un poco más sobre los Fragmentos de Página da click aquí.

Si tienes comentarios o dudas, escribelo en el apartado de comentarios al final de éste post, saludos.

Vue, el framework progresivo que llegó para quedarse

Estamos a mitad de 2018 y como siempre es un placer poder seguir en contacto y continuar aprendiendo cosas interesantes que nos ayuden a crear mejores proyectos de software.

Hoy te voy a platicar sobre Vue.js el llamado “Framework Progresivo” para creación de aplicaciones.  Vue.js fue creado con la simplicidad en mente, su aceptación se ha debido en gran medida a que incrementa mucho la productividad mediante el uso de un API muy bien documentado y fácil de comprender, incluso si no se tiene tanta experiencia en web.

¿Por qué aprender Vue?

A mi gusto, Vue provee un API mucho más sencillo de utilizar y manejar cuando se compara contra frameworks más robustos como Angular 2+; Vue fue de hecho inspirado en la primera versión de Angular conocida como AngularJS; de las propias palabras de su creador Evan You:

I started Vue as a personal project when I was working at Google Creative Labs in 2013. My job there involved building a lot of UI prototypes. After hand-rolling many of them with vanilla JavaScript and using Angular 1 for a few, I wanted something that captured the declarative nature of Angular’s data binding, but with a simpler, more approachable API. That’s how Vue started.
Fuente: Github Open Stories

Ventajas de usar Vue

  • No necesitas aprender un nuevo lenguaje como TypeScript para poder usarlo, aunque si te gusta TypeScript Vue es compatible con este lenguaje
  • Puedes usarlo como un reemplazo de jQuery para manipulación del DOM
  • Para casos más complejos puedes usar todo el poder del front-end moderno: ES2015+, Webpack, pre-procesadores, etc.
  • Curva de aprendiza muy corta
  • No requieres que tu aplicación sea de tipo SPA, ya que Vue puede irse adaptando a como lo necesites: por ejemplo lo puedes integrar con una vista de Laravel Blade, en tu app de Spring MVC, o en un módulo de tu CMS favorito (Drupal, Joomla, Liferay, IBM WCM, Umbraco, etc.)
  • Puedes usarlo en combinación con Node.js para hacer server-side rendering
  • Y más…

¿Cuándo usar Vue y no Angular?

Angular es un framework mucho más robusto que Vue.js eso quiere decir, que tiene muchos módulos y componentes avanzados que quizá no necesites en tu aplicación. Aquí hay algunos casos de uso donde NO se recomendaría Angular.

  • La aplicación NO es una Single Page Application. Angular fue pensando 100% para SPAs, si tu no estás desarrollando una Angular no es buena opción.
  • Tu aplicación sólo se comunica con muy pocos servicios REST externos (menos de diez end-points). Angular es muy bueno para aplicaciones empresariales complejas, pero para algo más sencillo es muy pesado cuando se compara con Vue.js.
  • Necesitas crear componentes sencillos integrados en la capa de vista de algún framework como Laravel o Spring MVC. Hay ocasiones que tenemos un framework de backend que hace casi todo, excepto que necesitamos interacción del usuario mediante JavaScript, Angular no sería natural en este caso de uso. Recuerda Angular funciona genial con SPAs.

Entonces ¿ya estás listo para continuar con Vue.js?

¿Dónde puedo aprender Vue?

En Webtraining.Zone tenemos un curso de Vue.js el cual fue impartido en vivo el martes 23 de enero de 2018. Este curso está disponible en: https://webtraining.zone/cursos/curso-profesional-vue-js

¿Cómo obtengo acceso al curso de Vue?

Si tienes una membresía en Webtraining.Zone ¡ya tienes incluido este curso!

Spoiler alert!
React is coming…

Tu servidor:
Alex Arriaga

Angular 5, la evolución sigue

Si bien la nueva versión de Angular había sido planeada para ser liberada el pasado 23 de octubre de 2017; eso no sucedió, pero no te preocupes hoy se acaba de liberar el Release Candidate #7 por lo que es muy probable que en muy próximos días Angular 5 ya esté con nosotros. Finalmente tenemos versión 5.0 estable y lista para ser usada.

En este último release se han corregido algunos issues menores, es decir, ya nada crítico está siendo arreglado en estos momentos.

¿Qué esperar en la versión 5?

Optimización en los bundles

Al parecer se acabaron los tiempos donde teníamos bundles de más de 500 Kb para aplicaciones sencillas; eso nos llena de alegría, ya que una de las mayores limitantes (hasta la versión 4) era que nuestros archivos generados eran aún pesados, incluso para aplicaciones con muy pocos componentes.

Ahead Of Time Compilation por defecto para la Angular CLI

Oh sí, mi buen amigo, finalmente ya tendremos AoT desde el inicio cuando se crean proyectos que usen la CLI de Angular. Esto es definitivamente algo excelente, ya que nos olvidaremos de estar pasando flags extraños al estar creando nuestra aplicación para producción.

Mejor Desempeño para Event Listeners

Se han aplicado algunas mejoras en la forma en cómo se registran eventos, como el documentado en este pull request.

¿Progressive Web Apps con Angular? Por supuesto que sí

En la versión 2 y 4, no existían mecanismos completos y bien documentados sobre la creación de Aplicaciones Web Progresivas, ¿y qué crees? en la versión 5, el equipo detrás de este genial framework ha puesto especial énfasis en este tema que cada vez toma más relevancia en todos los frameworks modernos. Angular no se quedará atrás y por supuesto que ya se está trabajando en hacer este tipo de aplicaciones mucho más robustas usando mecanismos de caché en el browser y otras mejorar que ya platicaremos más adelante.

Componentes de Material Design Listos para Server-side Rendering

Esto quizá es de las cosas que personalmente más espero, ya que actualmente Angular está muy “joven” en cuanto server-side rendering, incluso con la adopción de Angular Universal por parte de Google, la verdad es que nos nos han brindado una forma estable y potente para este tema, que es el motivo por el que muchos desarrolladores e ingenieros de software han descartado Angular para aplicaciones donde el SEO sea vital.

Tengo mis reservas en este tema, quisiera probar si realmente estos componentes funcionarán como es debido. Esperemos que sí.

¿Qué sigue?

  • Evidentemente, ya estamos ansiosos por probar la versión estable de Angular 5, así que tan pronto como se libere, estaremos probando todo lo nuevo en el día cero.
  • Angular 6 se empezará a cocinar tan pronto como la 5 sea estable ¿qué? sí recuerda que Angular adoptó el sistema SEMVER con lo cual se ha prometido tener nuevas versiones de Angular cada 6 meses; eso quiere decir que más o menos entre marzo y abril del próximo año tendremos Angular 6…

¿Tendremos un Curso Profesional de Angular 5 en Webtraining.Zone?

Por supuesto que sí, tan pronto como tengamos versión estable, estaremos creando un proyecto completamente desde cero para que puedas aprender todo lo nuevo.

Ya tenemos fecha y hora: 18 de Noviembre 2017 en punto de las 10 AM (Hora México Centro).

Inscríbete ahora al Curso Profesional de Angular 5 con Material Design: https://webtraining.zone/cursos/curso-profesional-de-angular-5-con-material-design
Nos vemos en ese curso, mientras tanto coméntame ¿qué opinas de Angular? ¿has tenido oportunidad de explorarlo?

Tu amigo Alex Arriaga

Angular vs Vue.js ¿qué framework me conviene aprender?

Hoy por hoy la cantidad de opciones entre las que podemos elegir para crear aplicaciones de front-end ha crecido exponencialmente; todos los días hay nuevas ideas, nuevas herramientas y metodologías y con ello llega el gran reto de poder distinguir bajo que circunstancias o casos de uso una es mejor que la otra.

En este artículo te platicaré un poco de mi experiencia personal con dos de los mejores frameworks (en mi opinión) que tenemos a la mano; asimismo te dejo un video de un curso rápido que tuvimos donde comparamos ambos frameworks.

Aclaro que este artículo NO se enfoca en cuestiones de performance, sino que trata de ser un punto de vista más general.

¿Por qué no incluí a React?

Porque hasta ahora no he tenido la oportunidad de trabajar con él a nivel profesional.

Angular

Creado por Google, como evolución de AngularJS. Es un framework muy robusto para creación de Single Page Applications, provee todo lo necesario para crear aplicaciones de cualquier tamaño. La comunidad alrededor de Angular es muy dinámica por lo que todos los días se generan nuevos módulos para las versiones más recientes.

Sitio oficial: https://angular.io/

¿Te gustaría aprender Angular desde cero?

Checa este Curso Profesional de Angular que tenemos en Webtraining.Zone

Ventajas

  • Preparado para cualquier tipo de aplicaciones
  • Excelente documentación técnica
  • Un buen número de módulos creados por la comunidad
  • Sugiere muy buenas convenciones para nombrar tus archivos, componentes, módulos y servicios
  • Hace uso extensivo de un stack moderno que incluye: TypeScript y Webpack.
  • Posee una Command Line Interface: Angular CLI que permite generar código boilerplate muy rápidamente
  • Preparado para lazy loading
  • Provee un modo producción y un modo desarrollo
  • Listo para Ahead of Time Compilation
  • Angular es MUY bien pagado en el mundo front-end empresarial

Desventajas

  • Angular es un framework complejo, por lo que la curva de aprendizaje puede ser alta dependiendo de tu experiencia previa
  • Es necesario aprender TypeScript, que aunque es un super set de JavaScript, agrega nuevas cosas como por ejemplo los tipos, las interfaces, los modificadores de acceso, etc. que pueden parecerte no naturales al principio (sobretodo si vienes del mundo de JavaScript 100%)
  • Es forzoso utilizar un proceso que incluya un bundler y module loader (como Webpack o Rollup.js)

Vue.js

Creado Evan You, es considerado un framework progresivo, es decir, es un framework que puede irse haciendo tan robusto como se necesite; ya que puede ir desde la simple inclusión de éste como una biblioteca JavaScript regular que sólo crea pequeños widgets, hasta un framework muy potente para crear aplicaciones completas.

Sitio oficial: https://vuejs.org/

Ventajas

  • No necesitas aprender algo nuevo, ya que se puede usar con ES5 o incluso con ES2015 si así lo deseas.
  • Puedes usarlo directamente en tu sitio web sin necesidad de tener un complejo sistema de empaquetado (es decir, no necesita Webpack para trabajar en su formato más simple).
  • Es un framework muy ligero
  • La curva de aprendizaje es muy amigable
  • Tiene una documentación muy “digerible” (se lee y comprende muy bien)

Desventajas

  • Mantenido por un pequeño grupo de personas, lo cual podría suponer un riesgo menor ya que la comunidad alrededor de Vue.js es muy grande (no te preocupes, puedes usarlo)
  • Vue.js te da mucha libertad, lo que para un desarrollador poco experimentado puede resultar peligroso cuando se trate de aplicaciones muy grandes
  • En general he visto que el salario ofrecido para un desarrollador de Vue.js es menor al de un desarrollador de Angular

¿Qué opinas? ¿cómo te ha ido con alguno de estos frameworks? ¿qué tipo de aplicaciones has hecho? ¿crees que sea bueno tener tantas opciones para elegir?

 

Como cambiar el editor de textos WYSIWYG de un campo text-html en Liferay 7

Por defecto Liferay tiene un editor de textos muy particular llamado AlloyEditor. Este editor es muy práctico pero a la vez poco intuitivo, ya que si estás muy acostumbrado a un editor como Microsoft Word -por poner un ejemplo-, el primero puede causarte conflictos a la hora de querer usarlo.

Como podrás ver en la imagen anterior, AlloyEditor no tiene una barra de herramientas fija en la parte superior como la mayoría de los editores. Para poder hacer uso de las herramientas que provee AlloyEditor debemos seleccionar el texto a editar y automáticamente nos mostrará un pequeña barra de herramientas para editar nuestro contenido; por ejemplo, poner el texto en negritas, hacer una lista, insertar un link entre otras opciones.

Si aún no estás convencido de quedarte con este editor, Liferay nos da la oportunidad de cambiarlo por algún otro de los que ya trae pre-cargados como (alloyeditor_creole, ckeditor, ckeditor_bbcode, ckeditor_creole, simple, tinymce and tinymce_simple).

Antes de pasar a cambiar el editor, debemos mencionar que cuando instalamos ó configuramos Liferay la primera vez, se van a generar varios archivos .properties dentro de los cuales se definen propiedades y características de inicio para Liferay.

Uno de estos archivos es portal.properties el cual se encuentra bajo /tomcat/webapps/ROOT/WEB-INF/lib/portal-impl.jar. Si tienes curiosidad, te recomiendo sólo hecharle un vistazo procurando no modificar ninguna de las propiedades presentes en ese archivo. Para poder modificar o agregar propiedades de nuestro archivo portal.properties debemos crear un archivo llamado portal-ext.properties (en caso de que no exista) bajo el directorio /tomcat/webapps/ROOT/WEB-INF/classes.

Bien, ahora que sabemos eso y explorando un poco dentro del archivo portal.properties nos podremos dar cuenta de que hay una sección con propiedades relacionadas con el editor de textos.

En la imagen anterior se ve claramente que tenemos comentarios (todo lo que inicia con el caracter #) con el nombre de la sección “Editors” además de tener también una  descripción de sobre los editores disponibles.

Como pudimos verlo en el título de este artículo, el acrónimo WYSIWYG es parte de la descripción y parte de los nombres de las propiedades, esto se debe a que es un término muy común cuando hablamos de editores de texto. El significado de este acrónimo por sus siglas en inglés es What You See Is What You Get que en español se traduce como “Lo que ves es lo que obtienes”.

Después de ese pequeño dato de cultura general, te platico que la propiedad que debemos modificar para poder cambiar el editor de textos que viene por defecto es editor.wysiwyg.impl.portlet.ddm.text_html.ftl la cual tiene asignado por defecto el valor de AlloyEditor.

Como mencionamos hace unos instantes, la manera correcta de modificar el archivo portal.properties es a través del archivo portal-ext.properties el cual va a sobreescribir los valores de las propiedades que anexemos a éste.

Tomando como base lo anterior, debemos copiar de portal.properties la propiedad a modificar a nuestro archivo portal-ext.properties. Ya que hemos copiado dicha propiedad, podemos asignarle cualquiera de los valores mencionados en la descripción de estas propiedades, en nuestro caso decidimos usar el editor ckeditor.

Ya con los cambios hechos podemos guardar el archivo portal-ext.properties y cerrarlo.

Algo muy importante y que no debemos dejar pasar es reiniciar el portal, para que de esta manera surtan efecto los cambios realizados.

Finalmente, después de haber reiniciado el portal, podrás ir al contenido y editar cualquiera de los assets/artículos que cuenten con un campo text-html y verás establecido ckeditor como editor por defecto.

Esperamos que este post te sea de mucha ayuda, nos vemos en el siguiente post y no olvides dejarnos tus comentarios.

¿Cómo ejecutar código JavaScript después de que cambie de página en Liferay?

Bien sabido es que la interfaz de Liferay 7 fue transformada a una Single Page Application ó SPA -por sus siglas en inglés-. 

Los SPA son aplicaciones web que no precisamente hacen un redireccionamiento entre las páginas que contenga la aplicación, ya que la aplicación se carga una sola vez. Esto significa que la URL de nuestra aplicación no va cambiar de manera natural, eso también es manipulado manualmente (usualmente con un módulo de routing). Esto significa que si tu quieres ejecutar una bloque de código en común para todas las páginas, y dicho código lo añades en las configuraciones avanzadas globales, ya sea de public pages ó private pages, este código sólo será ejecutado la primera vez en que llegas a la aplicación.

Al darte cuenta de esto, es muy probable que pienses en poner este bloque de código en las configuraciones avanzadas de cada una de las páginas en donde deseas ejecutar ese código. Pero ¿qué pasaría si tuvieras un portal con diez páginas, y además manejas navegación de segundo nivel? Te darás cuenta de que esto no es mantenible, ya que cada vez que quieras modificar por alguna razón ese bloque de código, tendrás que hacerlo en todas las páginas donde agregaste ese código.

En las SPAs la manera de navegar entre páginas se le conoce como routing o enrutamiento. 

Algunos frameworks como Angular, Ember o Meteor han adoptado los principios de SPA, así que si ya has utilizado alguno de ellos, esto te debe ser familiar. Y bueno, Liferay 7 no es la excepción, ya que también tiene su manera de navegar entre páginas. 

Entonces, volviendo a nuestro problema inicial de cómo ejecutar un bloque de código en varias páginas sin tener que forzar un redireccionamiento y cargado de página por completo, en Liferay tenemos un objeto expuesto en el browser.

Este objeto tiene varias propiedades muy útiles, dentro de las cuales está nuestra SPA. Ésta propiedad nos ofrece otras propiedades donde podemos saber el tiempo de expiración de la cache de Liferay, notificaciones de usuario, paths que han sido excluidos, así como una propiedad llamada app

La propiedad app también tiene otras propiedades, pero para nuestro caso, nos importa la propiedad de events_, que como el nombre lo dice, cuenta con eventos que nos dicen en qué momento se ejecutan las siguientes acciones:

  • Comenzar a navegar entre las páginas
  • Antes de navegar
  • Al terminar de navegar

Con estos eventos, es suficiente para ejecutar nuestro código de manera exitosa al cambiar entre la diferentes páginas en nuestro SPA sin tener que recargar la(s) páginas.

Si has usado jQuery y sus eventos, esto te será familiar:

Liferay.SPA.app.on("endNavigation", function(event) {
    // Ejecuta algo aquí después de haber cambiado de página
});

Lo anterior usa una función (on), la cual registra un evento y una acción a ejecutar a través de una función después de que se haya disparado el evento endNavigation.

Como puedes observar el evento endNavigation se dispara en el momento en que la navegación de nuestra SPA ha cambiado y terminado, es decir; imaginemos que te encuentras en la página Home te tu sito y en la navegación das un click a otra página llamada About Us, cuando la SPA haya terminado de llevarte de la página Home a la de About Us se ejecutará el código anterior.

Entonces, ahora ya sabes como ejecutar código JavaScript sin tener que recargar la página en Liferay 7.

No hay mucha documentación oficial sobre el objeto de Liferay 7 que está expuesto en el objeto global window, así que te recomiendo explorarlo un poco más para descubrir qué otras cosas podemos obtener de éste.

Importar Recursos en Liferay 7 desde el tema

Liferay provee a los desarrolladores de una herramienta que te fascilitará la importación de recursos como Pages, Structures, Templates, Applications Display Templates (ADTs), Web Content y Documents and Media a un Portal de Liferay.

Todo esto es posible gracias al modulo de importación de recursos de Liferay, mejor conocido como resource importer tool, la cual a traves del deploy de un Tema de Liferay hará toda la mágia.

Comencemos por saber que todos los recursos que deseemos importar desde el tema deben estar bajo el folder [theme-name]/src/WEB-INF/src/resources-importer.

Todos los assets a importar deben estar bajo la siguiente estrucutura de carpetas:

- WEB-INF
    -src
        -resources-importer             <---  Directorio base.
            -document_library   
                -documents              <---  Directorio donde debes agregar el contenido de tipo document and media(imágenes, videos, documentos).
            -journal
                -articles               <---  Directorio donde debes añadir el contenido web.
                -structures             <---  Directorio que contiene las estrucuturas
                -templates              <---  Directorio para los templates.
            -templates
                -application_display
                    -asset_entry        <---  Directorio para ATDs (Application Display Templates).
            sitemap.json                <---  Este archivo provee todo el mapping de los recursos que vamos a importar.

En la estrucutura anterior podemos notar que los recursos a importar estan separados por folders.

Estructuras

Las estructuras son aquellas que nos dan el esqueleto de campos ó elementos disponibles para crear contenido, campos que pueden ser de tipo texto, fecha, documento, html entre algunos otros.

Para poder añadir estos campos con el resource importer desde el tema, debemos crear un archivos con extensión JSON, el cual debe contener una propiedad array  (availableLanguageIds) para los idiomas, de igual manera tendrá una propiedad que define el idioma por defecto (defaultLanguageId) y para añadir nuestros campos tendremos nuevamente una propiedad array (fields), la cual contendrá un objecto por cada campo. Cada objeto tendrá el label, el name, el tipo de dato, propiedades booleanas que nos en las que definimos si el campo es repetible, si es indexable, si queremos mostrar el label entre algunas otras. Para tener un poco más claro esto, dale un vistazo a la estructura siguiente:

{
    "availableLanguageIds": [
        "en_US"
    ],
    "defaultLanguageId": "en_US",
    "fields": [
        {
            "label": {
                "en_US": "Summary"
            },
            "predefinedValue": {
                "en_US": ""
            },
            "style": {
                "en_US": ""
            },
            "tip": {
                "en_US": "A short, clear description that states the main facts or ideas of the content. "
            },
            "dataType": "string",
            "indexType": "keyword",
            "localizable": true,
            "name": "summary",
            "readOnly": false,
            "repeatable": false,
            "required": false,
            "showLabel": true,
            "type": "textarea"
        },
        {
            "label": {
                "en_US": "Body"
            },
            "predefinedValue": {
                "en_US": ""
            },
            "style": {
                "en_US": ""
            },
            "tip": {
                "en_US": ""
            },
            "dataType": "html",
            "fieldNamespace": "ddm",
            "indexType": "keyword",
            "localizable": true,
            "name": "body",
            "readOnly": false,
            "repeatable": false,
            "required": false,
            "showLabel": true,
            "type": "ddm-text-html"
        },
        {
            "label": {
                "en_US": "Thumbnail"
            },
            "predefinedValue": {
                "en_US": ""
            },
            "style": {
                "en_US": ""
            },
            "tip": {
                "en_US": "This image is shown when the item is in a list. Suggested size: 150px wide x 100px high."
            },
            "dataType": "document-library",
            "fieldNamespace": "ddm",
            "indexType": "keyword",
            "localizable": true,
            "name": "thumbnail",
            "readOnly": false,
            "repeatable": false,
            "required": false,
            "showLabel": true,
            "type": "ddm-documentlibrary"
        },
        {
            "label": {
                "en_US": "Image"
            },
            "predefinedValue": {
                "en_US": ""
            },
            "style": {
                "en_US": ""
            },
            "tip": {
                "en_US": ""
            },
            "dataType": "document-library",
            "fieldNamespace": "ddm",
            "indexType": "keyword",
            "localizable": true,
            "name": "image",
            "readOnly": false,
            "repeatable": false,
            "required": false,
            "showLabel": true,
            "type": "ddm-documentlibrary"
        },
        {
            "label": {
                "en_US": "Attachment"
            },
            "predefinedValue": {
                "en_US": ""
            },
            "style": {
                "en_US": ""
            },
            "tip": {
                "en_US": ""
            },
            "dataType": "document-library",
            "fieldNamespace": "ddm",
            "indexType": "",
            "localizable": false,
            "name": "attachment",
            "readOnly": false,
            "repeatable": false,
            "required": false,
            "showLabel": true,
            "type": "ddm-documentlibrary"
        }
    ]
}

Plantillas (Templates)

Bien, ahora te preguntarás como puedes obtener la información de de los campos de una estructura, bueno, para eso tenemos los templates, los cuales fungen como plantillas para renderear los campos de una estrucutura.

Para añadir templates al resource importer, debemos crear un folder el cual debe de coincidir en nombre con el nombre del folder que va a contener a los articles, como por ejemplo:

Estos pueden ser escritos con sintaxis de Velocity ó Freemarker (el recomendado por Liferay) los cuales son motores para la generación de plantillas, estos separan la lógica de la vista y son server site, es decir, se compilan de lado del cliente.

<#-- start getting entry data -->
<#assign entryID = .vars['reserved-article-id'].data >
<#assign entryTitle = .vars['reserved-article-title'].data >
<#assign entryAuthor = .vars['reserved-article-author-name'].data >
<#assign entryAuthorEmail = .vars['reserved-article-author-email-address'].data >
<#assign entryPublishDate = .vars['reserved-article-display-date'].data >
<#assign entrySummary = summary.getData() >
<#assign entryBody = body.getData() >
<#assign entryImage = image.getData() >
<#if entryImage?? && entryImage != "">
    <#assign entryImageAltText = entryImage>
</#if>
<#assign entryThumbnail = thumbnail.getData()>
<#-- end getting entry data -->
<h1>${entryTitle}</h1>
  <#--<p>hello world start</p>
  <liferay-portlet:runtime portletName="com_liferay_hello_world_web_portlet_HelloWorldPortlet" />
  <p>hello world end</p>-->

<p class="text-muted">${entryAuthor} | ${entryPublishDate}</p>
<div class="well">
    <p>${entrySummary}</p>
</div>
<#if entryImage?? && entryImage != "">
    <div class="panel panel-default content-image">
        <img src="${entryImage}" alt="${entryImageAltText}" class="img-responsive">
    </div>
    <#if entryImageAltText?? && entryImageAltText != "">
        <p class="text-muted" style="font-weight: bold; font-size: .8em; margin-bottom: 20px; margin-top: -10px;"><i class="glyphicon glyphicon-camera"></i> ${entryImageAltText}</p>
    </#if>
</#if>

${entryBody}

<style>
/* hide the back arrow and title included by ful_content.jsp [https://github.com/liferay/com-liferay-asset/blob/76dcfe6d2b86be772f179223b81d9c6f2c495786/asset-publisher-web/src/main/resources/META-INF/resources/display/full_content.jsp]
   
   @todo: develop a custom version of the asset publisher that does not include this.
  */
.portlet-content-container > .portlet-body > div > div.h2 > span {
    display: none;
}
</style>

En el código anterior tenemos un template el cual obtiene el título de una noticia, el autor, el resumen (summary), el cuerpo (body), el thumbnail y una imagen. Todos estos campos son puestos entre tags de HTML para darle cuerpo y forma a nuestras noticias.

Documentos

Para el caso del directorio documents pudes agregarlos directamente bajo este los recursos de tipo imágen, video ó algún otro documento como PDF, XLS, DOCX, etc. o bien, puedes crear subfolders para separar y tener mejor ordenados estos tus ducumentos.

Contenido Web (Articles)

Para añadir contenido debes agregar un folder contenedor dentro de articles. Al igual que con los documentos, tu puedes agregar un folders extra para ordenar de mejor manera tu contenido.

Por ejemplo un folder llamado news, entonces nos quedaría algo así:

../journal/articles/web content/news

Por otro lado el contenido que deseemos añadir debe estar en formato XML.

<?xml version="1.0"?>

<root available-locales="en_US" default-locale="en_US">
	<dynamic-element name="summary" type="text_box" index-type="keyword">
		<dynamic-content language-id="en_US"><![CDATA[This is the Title]]></dynamic-content>
	</dynamic-element>
	<dynamic-element name="body" type="text_area" index-type="keyword">
		<dynamic-content language-id="en_US">
        <![CDATA[<p>This is the Content.</p>]]></dynamic-content>
	</dynamic-element>
	<dynamic-element name="thumbnail" type="document_library" index-type="keyword">
		<dynamic-content language-id="en_US" type="journal"><![CDATA[ [$FILE=content_02.jpg$] ]]></dynamic-content>
	</dynamic-element>
	<dynamic-element name="image" type="document_library" index-type="keyword">
		<dynamic-content language-id="en_US" type="journal"><![CDATA[ [$FILE=content_04.jpg$] ]]></dynamic-content>
	</dynamic-element>
</root>

Como podemos ver en el bloque anteior, todos los elementos que añadimos en nuestra estructura son nodos de XML con que agregan el contenido a traves del tag <dynamic-element> que a su vez contiene el tag <dynamic-content>. Dentro de este ultimo tag tenenos una notación particular ![CDATA[ …]] dentro de la cual va el contenido, como el summary, body y las imágenes. 

El tag <dynamic-element> tiene como atributos el nombre del elemento, el tipo y el tipo de indexamiento, los cuales fueron proporcionados en el JSON de la estrucutura.

El tag <dynamic-content> tiene como atributos el idioma y el tipo de contenido.

Aplication Display Templates (ADTs)

Los Applications Display Templates o ADTs son utiles para renderear un set de articles, los cuales pueden ser iterados usando sintaxis de Freemarker. Para poder agregar ADTs a nuestro resource importer requerimos de un folder llamdo asset_entry el cual debe estar bajo application_display. 

En la imágen anterior se puede ver la estructura de directorios explicada anteriormente. A continuación el código del ADT News and Events Archive.

<#if entries?has_content>

    <!-- start header html -->
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">All News</h3>
        </div>
        <ul class="list-group">
    <!-- end header html -->

    <!-- start item loop -->
    <#list entries as entry>
        <#assign renderer = entry.getAssetRenderer()>
        <#assign className = renderer.getClassName() >
        <#if className == "com.liferay.journal.model.JournalArticle" >
            <#assign journalArticle = renderer.getArticle() >
            <#assign document = saxReaderUtil.read(journalArticle.getContent()) >

            <!-- start item data -->
            <#assign entryViewURL = assetPublisherHelper.getAssetViewURL(renderRequest, renderResponse,entry) />
            <#assign entryTitle = entry.getTitle(locale) />
            <#assign entryPublishDate = dateUtil.getDate(entry.getPublishDate(), "dd MMM yyyy", locale) />
            <#assign entryAuthor = entry.getUserName() />

            <#assign entrySummary = document.valueOf("//dynamic-element[@name='summary']") />
            <#assign entryBody = document.valueOf("//dynamic-element[@name='body']") />
            <#assign entryThumbnail = document.valueOf("//dynamic-element[@name='thumbnail']") />
            <!-- end item  data -->

            <!-- start item html -->
            <li class="list-group-item">
                <div class="media">
                    <div class="media-left">
                        <a href="${entryViewURL}">
                            <div class="media-object" alt="..." style="width: 150px; height: 100px; background-image: url(${entryThumbnail}); background-repeat: no-repeat; background-position: center; background-size: cover"></div>
                        </a>
                    </div>
                    <div class="media-body">
                        <h4 class="media-heading"><a href="${entryViewURL}">${entryTitle}</a></h4>
                        <div class="text-muted small">${entryAuthor} | ${entryPublishDate} </div>
                        ${entrySummary}
                    </div>
                    <div class="media-right">
                        <a href="<@getEntryEditURL />"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span></a>
                    </div>
                </div>
            </li>
            <!-- end item html -->

            <!-- start macros -->
            <#macro getEntryEditURL>
                <#if renderer.hasEditPermission(themeDisplay.getPermissionChecker())>
                    <#assign redirectURL = renderResponse.createRenderURL() />

                    ${redirectURL.setParameter("struts_action", "/asset_publisher/add_asset_redirect")}
                    ${redirectURL.setWindowState("pop_up")}

                    <#assign editPortletURL = renderer.getURLEdit(renderRequest, renderResponse, windowStateFactory.getWindowState("pop_up"), redirectURL) />

                    <#if validator.isNotNull(editPortletURL)>
                        <#assign entryEditURL = "javascript:Liferay.Util.openWindow({dialog: {width: 960}, id:'" + renderResponse.getNamespace() + "editAsset', title: '" + entryTitle + "', uri:'" + htmlUtil.escapeURL(editPortletURL.toString()) + "'});" />                        
                        ${entryEditURL}
                    </#if>
                </#if>
            </#macro>
            <!-- end macros -->

        </#if>
    </#list>
    <!-- end item loop -->

    <!-- start footer -->        
        </ul>
    </div>
    <!-- end footer -->
</#if>

 

Sitemap

Pareciera que ya tenemos todo listo para importar nuestros recurso, pero no, aún falta algo muy importante, el archivo sitemap.json 

En este archivo especificaremos las páginas que deseamos crear, los layout templates que queremos usar para cada página, el mapping de nuestro contenido web, los assets y la configuración de los portlets la cual es porporcionada por el tema.

La primera propiedad de nuestro archivo json, es un array con el tipo de páginas que queremos incluir en el importer, publicPages (páginas publicas) ó privatePages (páginas privadas). Posteriormente un objeto que contiene porpiedades que definen el friendlyURL (url amigable), layoutTemplateId (plantilla a utilizar en nuestra página), name (nombre de nuestra página). También contiene un array de columnas (columns) donde añadiremos nuestros portlets (esto no significa que las columnas sean el layout, para eso esta el layoutTemplateId) los cuales son añadidos en la propiedad portletId y portletPreferences, en está ultima propiedad es donde definimos configuraciones para nuestro portlet, por ejempo, si tenemos un Asset Publisher Portlet, este tiene configuraciones como el tipo de ADT con el que va a presentar los articles, configuración de estilos con el portlet decorator, el comportamiento del los links para ver el contenido en otra página o en el mismo portlet entre algunas otras.

Vamos a imaginar que deseamos crear cuatro páginas publicas llamadas home, my workspace, my life, about us, las cuales usarán un layout template de  una columna, y en el que cada página solo contendrá un portlet, en este caso el asset publisher portlet. Para hacer un poco más ulustrativa esta idea, a continuación el sitemap.json.

{
	"publicPages": [
		{
			"columns": [
				[
					{
						"portletId": "com_liferay_asset_publisher_web_portlet_AssetPublisherPortlet",
						"portletPreferences": {
							"assetLinkBehavior": "viewInPortlet",
							"delta": "3",
							"displayStyle": "ddmTemplate_NEWS-AND-EVENTS-ARCHIVE",
							"displayStyleGroupId": "${groupId}",
							"portletSetupPortletDecoratorId": "barebone",
							"showAddContentButton": "false"
						}
					}
				]
			],
			"friendlyURL": "/home",
			"layoutTemplateId": "1_column",
			"name": "Home"
		},
        {
			"columns": [
				[
					{
						"portletId": "com_liferay_asset_publisher_web_portlet_AssetPublisherPortlet",
						"portletPreferences": {
							"assetLinkBehavior": "viewInPortlet",
							"delta": "3",
							"displayStyle": "ddmTemplate_NEWS-AND-EVENTS-ARCHIVE",
							"displayStyleGroupId": "${groupId}",
							"portletSetupPortletDecoratorId": "barebone",
							"showAddContentButton": "false"
						}
					}
				]
			],
			"friendlyURL": "/my-workspace",
			"layoutTemplateId": "1_column",
			"name": "My Workspace"
		},
        {
			"columns": [
				[
					{
						"portletId": "com_liferay_asset_publisher_web_portlet_AssetPublisherPortlet",
						"portletPreferences": {
							"assetLinkBehavior": "viewInPortlet",
							"delta": "3",
							"displayStyle": "ddmTemplate_NEWS-AND-EVENTS-ARCHIVE",
							"displayStyleGroupId": "${groupId}",
							"portletSetupPortletDecoratorId": "barebone",
							"showAddContentButton": "false"
						}
					}
				]
			],
			"friendlyURL": "/my-life",
			"layoutTemplateId": "1_column",
			"name": "My Life"
		},
        {
			"columns": [
				[
					{
						"portletId": "com_liferay_asset_publisher_web_portlet_AssetPublisherPortlet",
						"portletPreferences": {
							"assetLinkBehavior": "viewInPortlet",
							"delta": "3",
							"displayStyle": "ddmTemplate_NEWS-AND-EVENTS-ARCHIVE",
							"displayStyleGroupId": "${groupId}",
							"portletSetupPortletDecoratorId": "barebone",
							"showAddContentButton": "false"
						}
					}
				]
			],
			"friendlyURL": "/about-us",
			"layoutTemplateId": "1_column",
			"name": "About Us"
		}
	]
}

 

Ya casi terminamos, lo unico que nos falta es decirle a nuestro tema donde deseamos hacer importar recurso al momento de hacer deploy de nuestro tema ya que por defecto, Liferay crea un site template para importar nuestros recursos, pero como le digo eso? bien, cuando construimos un tema usando el theme genarator tool, automáticamente se crea un archivo llamado liferay-plugin-package.properties.

Con las propiedades resources-importer-target-class-name y resources-importer-target-value=[site name] le podemos especificar un site, solo debemos de estar seguros del nombre de site que estamos proporcionando, ya que si nos equivocaramos, podríamos perder contenido que ya tengamos creado en Liferay.

Para nuestro ejemplo, agregaremos esas propiedades de la siguiente manera:

resources-importer-target-class-name=com.liferay.portal.kernel.model.Group
resources-importer-target-value=My New Liferay Site

Ahora solo a hacer el deploy de nuestro tema para importar contenido en nuestro ambiente de Liferay.

Para más información puedes consultar la documentación oficial de resource importer de Liferay.

Iterar campos repetibles en un Application Display Template de Liferay 7

Una de las cosas más complicadas de comprender es cómo usar los llamados repeatable fields en un Application Display Template (ADT) en Liferay 7; debido a que no disponemos de tanta documentación oficial. Por tal motivo quisiera compartir contigo un ejemplo de una ADT para el famoso Asset Publisher; comencemos:

  1. Imagina que tienes una Estructura (Structure) con un campo de tipo “Documents and Library” llamado attachment el cual ha sido configurado como repeatable=true.
  2. Ahora bien, necesitas crear un listado de todos los attachments y obtener tanto el link al documento, como su etiqueta (label).
<#if entries?has_content>
    <!-- START: item loop -->
    <div class="container">
        <div class="row">
            <#list entries as entry>
                <#assign renderer = entry.getAssetRenderer()/>
                <#assign className = renderer.getClassName() />
                <#if className == "com.liferay.journal.model.JournalArticle">
                    <#assign journalArticle = renderer.getArticle() />
                    <#assign document = saxReaderUtil.read(journalArticle.getContent()) />
                    <#assign allAttachments = document.selectNodes( "//dynamic-element[@name='attachment']") />
                    <!-- START: item html -->
                    <div class="col-xs-12 col-sm-12 col-md-6 col-lg-6">
                        <div class="media card-file">
                            <div class="media-body">
                                <#list allAttachments.iterator() as attachment>

                                    <#if attachment.valueOf( "dynamic-element[@name='fileLabel']")??>
                                        <#assign fileLabel = attachment.valueOf( "dynamic-element[@name='fileLabel']") />
                                        <#else>
                                            <#assign fileLabel="Download File" />
                                    </#if>

                                    <#if attachment.valueOf( "dynamic-content")??>
                                        <#assign fileLink = attachment.valueOf( "dynamic-content") />
                                        <#else>
                                            <#assign fileLink="#" />
                                    </#if>

                                    <div class="card-file__attachment">
                                        <a href="${fileLink?trim}" target="_blank">${fileLabel?trim}</a>
                                    </div>
                                </#list>
                            </div>
                        </div>
                    </div>
                    <!-- END: item html -->
                </#if>
            </#list>
        </div>
    </div>
    <!-- END: item loop -->
</#if>

Lo más importante en el código de Freemarker anterior es que estamos utilizando una query de XPath para obtener todos los nodos del XML que representa nuestro Journal Article:

<#assign allAttachments = document.selectNodes( "//dynamic-element[@name='attachment']") />

Acto seguido procedemos a iterar ese NodeList, usando la instrucción #list de Freemarker:

<#list allAttachments.iterator() as attachment>
// Do something here :)
</#list>

Tip. Si tienes dudas qué es una ADT; esta es la definición oficial de la documentación de Liferay:

The application display template (ADT) framework allows Liferay administrators to override the default display templates, removing limitations to the way your site’s content is displayed. With ADTs, you can define custom display templates used to render asset-centric applications. For example, you may want to show blog entries horizontally instead of vertically, or list your assets in the asset publisher application in different sizes.
https://dev.liferay.com/discover/portal/-/knowledge_base/6-2/using-application-display-templates