Cómo hacer un blog con Contentful y AngularJS

Contentful es un CMS con API como base, listo para ser usado con AngularJS

Como les contaba en el post anterior, me cambié a Contentful porque decidí trabajar con AngularJS y utilizar una API.

Había pensado en armar mi propia API, de hecho lo hice; pero hacer un sistema y todo lo que conlleva me dió una tremenda flojera.

Sin embargo, volví a la carga cuando conocí a Contentful, un CMS basado en API. Era justo lo que necesitaba. Así que en este post les contaré cómo sacarle provecho a esta innovadora forma de hacer blogs.

Ingredientes

  1. 1 Diseño o Template en HTML
  2. Una pizca de conocimientos de AngularJS
  3. Tener una cuenta en Contentful

Preparación

1. El diseño del sitio

Comienza por el diseño. Puedes usar tus habilidades de Frontend para crear uno o simplemente buscar uno y usarlo como base para tu sitio. En mi caso, utilicé uno que tenía guardado hace casi más de un año.

En este diseño es importante mencionar que debes tener al menos dos vistas:

  1. El home
  2. La vista del post

Para quienes usan WordPress o conocen los themes de WordPress, es similar a tener el diseño de index.php, y single.php

Para nuestro ejemplo, usaremos la primera búsqueda que encontré al buscar "bootstrap blog template", es decir, http://startbootstrap.com/template-overviews/clean-blog/.

Lo bueno de este template es que tiene la fuente en Github, así que podemos clonarlo en nuestro computador y comenzar a hacerle los cambios necesarios para nuestro siguiente paso.

Así que partan por ir a su directorio de trabajo preferido y clonen el template, si no usan consola, no se sientan mal, siempre pueden usar la App de Github. Para quienes si usamos consola deben escribir:

git clone git@github.com:IronSummitMedia/startbootstrap-clean-blog.git miblog

Este comando clonará el template en un directorio llamado "miblog" y nos dejará todo listo para comenzar a trabajar.

Instalando el template

2. Agregando AngularJS

El siguiente paso es agregar AngularJS a nuestro template base. Para realizar esto podemos utilizar bower como herramienta de instalación o simplemente ir a angularjs.org y descargar la última versión estable.

Si no estás relacionado con AngularJS te recomiendo la lectura del siguiente tutorial

En mi caso, utilicé bower e instalé AngularJS con el siguiente comando: bower install angularjs

Este comando instaló la versión 1.4.8 que es la última estable, si quieres usar otra versión como la 1.5 o la 2, puedes especificarlo en la instalación usando angularjs#version.

También instalaremos angular-route para definir las rutas de nuestra single-page-app, también angular-marked para interpretar los contenidos que vienen desde contentful y por supuesto, angular-contenful para poder obtener los contenidos desde la API.

El comando para hacer lo anterior es: bower install angular-route angular-marked angular-contentful

Ahora que tenemos todo instalado, crearemos la estructura en un directorio llamado src, en la raíz del proyecto y vamos a crear el archivo app.js. Lo que nos deja la siguiente estructura:

Estructura

Con esta estructura armada comenzaremos el desarrolo de una aplicación simple, en nuestro archivo app.js.

2.1 Modificando el home

Lo primero es agregar nuestros scripts en el siguiente orden:

  1. angular
  2. angular-route
  3. marked
  4. angular-marked
  5. angular-contentful
  6. app.js

Lo que da algo como esto:

<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/angular-route/angular-route.min.js"></script>
<script src="bower_components/marked/lib/marked.js"></script>
<script src="bower_components/angular-marked/dist/angular-marked.min.js"></script>
<script src="bower_components/angular-contentful/dist/angular-contentful.min.js"></script>
<script src="src/app.js"></script>

Y luego, configuraremos nuestra app en el tag htmldel archivo index.html, del siguiente modo:

<html lang="en" ng-app="blogApp">

Acá se define el nombre de nuestra aplicación, que debe ser la misma que definamos en el archivo app.js:

// Blog App
var blogApp = angular.module('blogApp', []);
2.2 Configurando las rutas

El segundo paso es configurar las rutas correspondientes al "index" y a la vista de un post. Para esto usaremos angular-route, un módulo que permite mostrar templates en base a la ruta.

Para configurar las rutas lo primero que debemos hacer es agregar el módulo en nuestra definición de la BlogApp:

var blogApp = angular.module('blogApp', ['ngRoute']);

El módulo se llama, ‘ngRoute` y se configura del siguiente modo:

blogApp
    .config(['$routeProvider',
        function($routeProvider) {

            $routeProvider
            .when('/post/:slug', {
                templateUrl: 'post.html',
                controller: 'PostController'
            })
            .when('/', {
                templateUrl: 'home.html',
                controller: 'HomeController'
            })
            .otherwise({
                redirectTo: '/'
            });

        }
    ])
;

Acá le estamos diciendo a nuestra aplicación que si encuentra la ruta /post/slug entregue el contenido de post.html y que utilice el controlador PostController. Y que en caso contrario, nos lleve a la raíz.

2.3 Creando los controladores

Luego que creamos las rutas, creamos los controladores encargados de la interacción entre la vista y la API.

Para eso, agregamos posterior a la configuración (.config()), dos métodos más:

.controller('HomeController', ['$scope', 
    function($scope) {

    }
])

.controller('PostController', ['$scope', 
    function($scope) {

    }
])  

El primer controlador será el que controle el home de la app, por eso le pusimos HomeController, y el segundo será el que controle la vista del post, por eso lleva el nombre de PostController.

2.4 Directiva ng-view

Antes de declarar los controladores en la vista, debemos definir dónde estará nuestra vista, la cual será reemplazada por el contenido del Home.

Acá el análisis es simple, llevaremos el <!-- Page Header --> y el <!-- Main Content --> a un archivo llamado home.html, y en ese espacio pondremos la directiva <div id="view" ng-view></div>

El resultado es el esperado, el sitio queda tal cual venía y el archivo home.html tiene todo menos <!-- Navigation --> y <!-- Footer -->, además de las definiciones fuera de estos elementos.

Para probar que todo funciona bien abre el archivo index.html y veras el sitio principal, luego revisa #/post/test y verás la interfaz del post

¿Qué quiere decir esto? Quiere decir que debemos dejar limpio el archivo post.html, dejándolo solamente los elmentos <header> y <article>.

3. Creando la cuenta en Contentful

El último paso es la integración de Contentful, para esto debemos crear una cuenta gratuita en Contentful donde podemos registrarnos normalmente o usando nuestra cuenta de Google o de Github.

Registro

Luego, al ingresar te preguntarán tu especialdad, seguramente para darte mejores herramientas. En mi caso, utilicé la opción "I write code".

Especialidad

El siguiente paso es crear el espacio, para esto el wizard cuenta con 4 tipos, yo elegí el template "Blog", que es lo que buscamos realizar.

Tipo

Después te preguntarán los datos de tu espacio. Yo escribí lo siguiente:

Datos

Y comenzará el proceso de creación de API keys, contenidos por defecto y otras configuraciones más.

Wizard

Al terminar, estarás dentro de contentful. En el menú superior veremos las funcionalidades principales de la App.

Menu

Acá, iremos al menú APIs, y seleccionaremos "Content Delivery API", para obtener las credenciales necesarias para obtener el contenido.

CDA

Dentro de esta opción existen 3, usaremos la API para la WEB. Al ingresar veremos la sección API ACCESS TOKENS, que es la información que usaremos en nuestro blog para acceder a los contenidos.

Access Tokens

4. Integrando Contentful

Con la información anterior estaremos listos para integrar Contentful en nuestra BlogApp.

Para esto agregaremos los módulos restantes: angular-contentful y angular-marked en el archivo app.js:

var blogApp = angular.module('blogApp', ['ngRoute', 'contentful', 'hc.marked']);

Luego, debemos configurar nuestro contentful con los datos del espacio creado, sobre la configuración de $routeProvider:

blogApp
    .config(['$routeProvider', 'contentfulProvider',
        function($routeProvider, contentfulProvider) {

            contentfulProvider.setOptions({
                space: '',
                accessToken: ''
            });

Los valores que van en space están la api de Website, el primero es el SPACE ID y el segundo es el API ACCESS TOKEN → Production

Con este módulo integrado, ya podemos hacer la consulta de los posts a nuestra API. Usando la documentación del siguiente link (https://github.com/jvandemo/angular-contentful) podemos comenzar a armar nuestro sitio.

4.1 Agregando posts al home

Para agregar los posts al home partiremos definiendo HomeController dentro de nuestra interfaz. Para esto, en el archivo home.html agregaremos un <div ng-controller="HomeController"></div> como contenedor global.

<div ng-controller="HomeController">

    <!-- Page Header -->
    <!-- Set your background image for this header on the line below. -->
    <header class="intro-header" style="background-image: url('img/home-bg.jpg')">
        <div class="container">
            <div class="row">
    ...
    ...
    ...
    ...
                <ul class="pager">
                    <li class="next">
                        <a href="#">Older Posts &rarr;</a>
                    </li>
                </ul>
            </div>
        </div>
    </div>

</div>

Primero vamos a obtener los posts usando el módulo contenful y definiremos la variable entries dentro del controlador.

 .controller('HomeController', ['$scope', 'contentful', 
    function($scope, contentful) {
        contentful
        .entries()
        .then(
            function(response){
                $scope.entries = response.data;
            },
            function(response){
                console.log('Algo pasó que no se trajo las entradas :(');
            }
        )
    }
])

Si todo funciona bien, veremos algo como esto en consola:

Consola

Ahora, el tema es que la API está trayendo todos los contenidos independientes del tipo. Esto es porque el CMS no cuenta con una estructura definida y varía según el tipo de contenido:

Consola

Por lo tanto, debemos filtrar por el tipo de contenido Post. Para ésto usaremos la API de Referencia que entrega Contenful.

Lo que hacemos, es agregar el filtro en el método entries() el cual tiene como parámetro opcional un queryString.

El parámetro a utilizar es el ID alfanumérico del tipo de contenido, que podemos obtener al editar el tipo de contenido en la URL.

Consola

En la URL, el ID corresponde a 2wKn6yEnZewu2SCCkus4as, en el caso de ustedes será otro ID alfanumérico. Esto es algo que contentful debería mejorar, sin duda.

Ya que tenemos nuestro ID, filtraremos los contenidos en el controlador, del siguiente modo:

contentful
    .entries('content_type=2wKn6yEnZewu2SCCkus4as')
    .then(

El siguiente paso es agregar los siguientes posts en la lista inferior. Para esto usaremos las directivas ng-repeat que hará la repetición. Eliminamos todos los elementos repetidos y dejamos solo un <div class="post-preview"> incluyendo el <hr>. El elemento Pager Lo dejaremos comentado por ahora:

<!-- Main Content -->
<div class="container">
    <div class="row">
        <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
            <div class="post-preview" ng-repeat="entry in entries.items">
                <a href="#/post/{{ entry.fields.slug }}>
                    <h2 class="post-title">
                        {{ entry.fields.title }}
                    </h2>
                    <h3 class="post-subtitle">
                    </h3>
                </a>
                <p class="post-meta">Posted by <a href="#">{{entry.fields.author[0].fields.name}}</a> on {{entry.fields.date|date:'medium'}}</p>
            </div>
            <hr>

            <!-- Pager -->
            <!--ul class="pager">
                <li class="next">
                    <a href="#">Older Posts &rarr;</a>
                </li>
            </ul-->
        </div>
    </div>
</div>

Si revisan bien, usamos el atributo fields definido en la variable entry, para obtener los datos del post. También aplicamos el filtro date para mostrar la fecha más entendible.

Otro punto importante es el link, donde usamos el slug como llave para encontrar el contenido en la vista de Post.

Por el momento ignoraremos el elemento post-subtitle pero podemos crear un atributo "excerpt" en nuestro content-type o usar una función que nos recorte parte del contenido.

4.2 Visualizando un Post

Para visualizar un post haremos click en una de las entradas, esto nos llevará a la url #/post/<slug del post>.

Aquí, vamos a modificar el controlador PostController inyectando contentful y $routeParams, para obtener el slug.

.controller('PostController', ['$scope', '$routeParams',  'contentful',  
    function($scope, $routeParams, contentful) {
        contentful
        .entries('content_type=2wKn6yEnZewu2SCCkus4as&fields.slug='+$routeParams.slug+'&limit=1')
        .then(
            function(response){
                $scope.entry = response.data.items[0];
            },
            function(response){
                console.log('Algo pasó que no se trajo las entradas :(');
            }
        )
    }
])  

Acá repetimos la búsqueda por tipo de contenido, agreamos el atributo fields.slug y usamos $routeParams.slug para el valor. Además, solo obtenemos un valor usando el atributo limit=1.

Al igual que en home.html, en post.html también debemos agregar el elemento <div ng-controller="PostController"></div> como contenedor del post.

Luego, modificaremos el intro-header del siguiente modo:

<header class="intro-header" style="background-image: url('img/post-bg.jpg')">
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
                <div class="post-heading">
                    <h1>{{ entry.fields.title }}</h1>
                    <h2 class="subheading"></h2>
                    <span class="meta">Posted by <a href="#">{{entry.fields.author[0].fields.name}}</a> on {{entry.fields.date|date:'medium'}}</span>
                </div>
            </div>
        </div>
    </div>
</header>

Y para el contenido, usaremos marked, el plugin que convierte los contenidos desde markdown a html.

<!-- Post Content -->
<article>
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
                <div marked="entry.fields.body"></div>
            </div>
        </div>
    </div>
</article>    

Resultado y detalles finales

El resultado de todo lo anterior se ve del siguiente modo:

Resultado

Si lo quieren ver funcionando pueden visitar el siguiente link: http://labs.alvaroveliz.cl/angular-contentful-blog/

Si quieren implementar este método, pueden agregar detalles finales, utilizando otros modulos como por ejemplo:

Si tienen comentarios sobre el post o si encontraron errores, pueden dejarlos acá abajo.

¡Gracias por leer!

Blog, Tutorial, Tutorial Javascript
11 minutos