miércoles, 4 de septiembre de 2013

Lista de tareas simple con Symfony 2: Parte 3

Vista de la página principal

Podríamos recurrir a un método muy común utilizado con PHP  que consiste en  incluir un 'header' y un 'footer' en cada una de las plantillas con las partes que se repiten en todas las páginas/vistas. Sin embargo es recomendado utilizar el motor de plantillas Twig que incorpora Symfony2. Y aprovechar la posibilidad que proporciona de heredar plantillas.

Vamos a ver una breve introducción a algunos conceptos Twig:

A) Sintaxis en TWIG:

- {{.....}} es utilizado para imprimir una variable o expresión en la plantilla.
- {%....%} es utilizado para controlar la lógica de la plantilla. Por lo tanto dentro insertaremos las condiciones y o los bucles for, foreach...
- {#...#} es utilizado para insertar comentarios. Equivalente al comentario multilinea de /**/ de PHP.

B) Herencia

En Symfony 2 una plantilla se puede decorar con elementos de otra plantilla. Por lo que podemos crear un una plantilla base que contiene todos los elementos comunes de tu sitio definidos como bloques. Una plantilla hija puede extender ({% extends 'TareaListadoBundle::layout.html.twig' %}) el diseño base y reemplazar cualquiera de sus bloques. O utilizar el contenido del bloque padre mediante {{ parent() }} y añadir otro contenido. Pero recuerda que las plantillas no están obligadas a definir todos los bloques de la plantilla padre. Ya que si no se definen se heredan tal cual.

Nota: Si se utiliza {%extends%} debe de ser la primera etiqueta de la plantilla.

C) Ubicación de las plantillas

Cada paquete contiene sus plantillas en su directorio (y subdirectorios) Resources/views. La mayoría de las plantillas se encuentran dentro de un paquete. Aunque también podrían estar en el directorio app/Resources/views/.
3 tipos de sintaxis para acceder a una plantillas:
- Symfony 2 utiliza una sintaxis de cadena bundle:controlador:plantilla para las plantillas. Por lo tanto cada plantilla relacionada con un controlador deberá estar dentro del directorio Resources/views/nombre_controlador/.
- Sin embargo también podemos utilizar la sintaxis bundle:plantilla para acceder a plantillas que se suelen utilizar como base para otras plantillas.
- Si la plantilla se encuentra en app/Resources/views/ accederemos a ella mediante ::plantilla.

D) Integrando Controladores

Como vimos en el controlador TareaController, al final de la acción llamamos al método render para renderizar la plantilla listado.html.twig. Además le pasamos un array de datos, con la clave 'tareas' y el objeto $tareas (que es un array), que extrajimos de nuestro repositorio.

 public function indexAction()  
   {  
     $em = $this->getDoctrine()  
           ->getEntityManager();  
     $tareas = $em->getRepository('TareaListadoBundle:Tarea')  
            ->getTareas();  
     return $this->render('TareaListadoBundle:Tarea:listado.html.twig', array(  
       'tareas' => $tareas  
     ));  
   }  

Pero si se da la situación que una plantilla no tiene acceso a una variable o cierta información necesaria, es posible reproducir un controlador. Los controladores se ejecutan rápidamente y promueven la buena organización y reutilización de código.

 <div id="sidebar">  
   {{ render(controller('DemoBlogBundle:Article:recentArticles', { 'max': 3 })) }}  
 </div>  

En este ejemplo llamamos a recentArticlesAction del controlador ArticleController. Y además le pasamos la variable max con valor 3 (a dicha acción/método)

E) Nombres de plantilla

Cada nombre de plantilla también cuenta con dos extensiones que especifican el formato y motor de esa plantilla. Cualquier plantilla Symfony2 se puede escribir en Twig o PHP, y la última parte de la extensión (.twig o .php) especifica cuál de los dos motores se debe utilizar y por lo tanto como debe procesar Symfony2 la plantilla. La primera parte de la extensión, (por ejemplo .html, .css, etc.) es el formato final que la plantilla debe generar. Esta simplemente es una táctica de organización utilizada en caso de que el mismo recurso se tenga que reproducir como HTML (index.html.twig), XML (index.xml.twig), o cualquier otro formato.

F) Enlazando recursos

En una página web normalmente necesitaremos utilizar scripts JavaScript, imagenes, hojas de estilo... Evidentemente todos estos recursos pueden ser accedidos mediante su ruta relativa o absoluta. Pero Symfony 2 proporciona una opción más dinámica mediante la función asset().
La función asset se encarga de generar las rutas correctas en el servidor a partir de la ruta en Bundle proporcionada. Lo que proporciona portabilidad a la aplicación.

Ejemplos:

   <script src="{{ asset('bundles/tarealistado/js/jquery.js') }}"></script>   
   <script src="{{ asset('bundles/tarealistado/js/jquery-ui.js') }}"></script>   
   <link rel="stylesheet" href="{{ asset('bundles/tarealistado/css/main.css') }}" type="text/css"/>   
   <link rel="shortcut icon" href="{{ asset('bundles/tarealistado/images/favicon.ico') }}" />  

A fin de que la función asset vincule correctamente los recursos, necesitamos copiar o crear un enlace simbólico de los recursos del Bundle (src/Tarea/ListadoBundle/Resource/public) al directorio /web/ de la aplicación.

G) Rutas

Un error cuando se crean vistas es codificar cada una de las URL's. Esto provoca que si en algún momento las rutas varían, haya que modificar cada una de estás. Y estas pueden aparecer en muchos sitios de nuestra aplicación web.
En lugar de codificar las URL en las plantillas, hay que utilizar la función path de Twig  para generar URL basadas en la configuración de enrutado. Y al modificar dicha configuración de enrutado, las plantillas automáticamente generarán las nuevas URL's.

 #src/Tarea/ListadoBundle/Resources/config/routing.yml  
 homepage:  
   pattern: /  
   defaults: { _controller: TareaListadoBundle:Tarea:index }  
   requirements:  { _method: "GET" }  

    <div class="content">  
      <h1><a href="{{ path('homepage') }}">  
        Tasker: gestor de tareas  
      </a></h1>  
    </div>  

Vamos a ver un ejemplo con una ruta más complicada, imagina la siguiente entrada en la tabla de enrutamiento:

 #src/Tarea/ListadoBundle/Resources/config/routing.yml   
 eliminar:  
   pattern: /eliminar/{idTarea}  
   defaults: { _controller: TareaListadoBundle:Tarea:delete }  
   requirements:    
     _method: "GET"   
     tarea_id: \d+  

Pues la única diferencia respecto al primer ejemplo, es que debemos de pasar, en formato de array asociativo, la clave con el nombre de la variable y su valor.

 <div id="titulo">  
       <span>Edición de Tarea</span>  
       <a href="{{ path('eliminar',{ 'idTarea' : tarea.id }) }}"><img src="{{ asset('bundles/tarealistado/images/delete.png') }}" alt="borrar tarea"/> Eliminar Tarea</a>  
     </div>  

H) Variables globales de plantilla

La variable app proporciona acceso a ciertas variables de la aplicación:

- app.security - Proporciona el contexto de seguridad.
- app.user - Proporciona el objeto Usuario actual.
- app.request - Proporciona el objeto Petición.
- app.session - Proporciona el objeto Sesión.
- app.environment - Proporciona el entorno actual («dev», «prod», etc.)
- app.debug - Devuelve True si está en modo de depuración. False en caso contrario.

I) Mensajes flash

Es posible almacenar pequeños mensajes que se pueden guardar en la sesión del usuario para exactamente una petición adicional. Esto es útil para dar información al usuario tras haberse realizado una acción. Este tipo de mensajes se conocen como mensajes 'flash'. Están diseñados para utilizarlos a través de redirecciones.

 if ($editForm->isValid()) {  
     $em->persist($tarea);  
     $em->flush();    
     $this->get('session')->getFlashBag()->add('notice', '!Los ultimos cambios en la tarea han sido guardados!');  
     return $this->redirect($this->generateUrl('homepage'));  

Después de procesar la petición, el controlador establece un mensaje flash notice y luego redirige al usuario. Recuerda que al redirigir a una ruta, la configuración del enrutado ejecutara el controlador correspondiente y este, a su vez, renderizá una plantilla. Y dicha plantilla podrá mostrar el mensaje de la siguiente forma:

  {% if app.session.FlashBag('notice') %}  
     <div class="flash_notice">  
        {{ app.session.flash('notice') }}  
     </div>  
  {% endif %}  

J) Incluyendo otras plantillas.

La plantilla se incluye utilizando la función {{ include() }}. El nombre de la plantilla sigue la convención nombrada anteriormente. A la plantilla se le pueden pasar variables en un array de objetos:

 {# src/Demo/BlogBundle/Resources/views/Article/details.html.twig #}  
 <h2>{{ article.title }}</h2>  
 <h3 class="byline">by {{ article.authorName }}</h3>  
 <p>  
   {{ article.body }}  
 </p>  

 {# src/Demo/BlogBundle/Resources/views/Article/list.html.twig #}  
 {% extends 'DemoBlogBundle::layout.html.twig' %}  
 {% block body %}  
   <h1>Artículos<h1>  
   {% for article in articles %}  
     {{ include('DemoBlogBundle:Article:details.html.twig', {'article': article}) }}  
   {% endfor %}  
 {% endblock %}  

1. Creación de la plantilla base (layout.html.twig)

Así que vamos a empezar creando la  plantilla base llamada layout.html.twig

 {#src/Tarea/ListadoBundle/Resources/views/layout.html.twig#}  
 <!DOCTYPE html>  
 <html>  
  <head>  
   <title>  
    {% block title %}  
     Tasker  
    {% endblock %}  
   </title>  
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
   {% block stylesheets %}  
    <link href='http://fonts.googleapis.com/css?family=Irish+Grover' rel='stylesheet' type='text/css'>  
    <link href='http://fonts.googleapis.com/css?family=La+Belle+Aurore' rel='stylesheet' type='text/css'>  
    <script src="{{ asset('bundles/tarealistado/js/jquery.js') }}"></script>  
    <script src="{{ asset('bundles/tarealistado/js/jquery-ui.js') }}"></script>  
    <link rel="stylesheet" href="{{ asset('bundles/tarealistado/css/main.css') }}" type="text/css"/>  
   {% endblock %}  
   {% block javascripts %}  
   {% endblock %}  
   <link rel="shortcut icon" href="{{ asset('bundles/tarealistado/images/favicon.ico') }}" />  
  </head>  
   <body>  
   <div id="container">  
    <div id="header">  
     <div class="content">  
      <h1><a href="{{ path('homepage') }}">  
       Tasker: gestor de tareas  
      </a></h1>  
     </div>  
    </div>  
    <div id="container">  
     <div class="content">  
       {% if app.session.FlashBag('notice') %}  
        <div class="flash_notice">  
         {{ app.session.flash('notice') }}  
        </div>  
       {% endif %}  
       {% if app.session.FlashBag('error') %}  
        <div class="flash_error">  
         {{ app.session.flash('error') }}  
        </div>  
       {% endif %}  
       {% block content %}  
       {% endblock %}  
     </div>  
    </div>  
    <div id="footer">  
     <div class="content">  
      <!--<ul>  
       <li><a href="">Sobre Tasker</a></li>  
      </ul>-->  
     </div>  
    </div>  
   </div>  
  </body>  
 </html>  

A fin de que la función asset vincule correctamente los recursos, necesitamos copiar o crear un enlace simbólico de los recursos del Bundle (src/Tarea/ListadoBundle/Resource/public) al directorio /web/ de la aplicación.. Esto lo puedes hacer con la siguiente orden:

 $ php app/console assets:install web --symlink  

Si estás usando un sistema operativo que no es compatible con enlaces simbólicos, tal como Windows, tendrás que olvidarte de la opción de enlace simbólicos. Por lo tanto se utilizaremos la instrucción anterior sin la opción --symlink

 $ php app/console assets:install web 

Esta instrucción (sin opción de enlace simbólico), va a copiar los recursos desde los directorios Resources/public de todos los Bundles al directorio /web/ de la aplicación. Puesto que los archivos se copian en realidad, será necesario ejecutar esta tarea cada vez que realices un cambio a un recurso público en cualquiera de tus Bundles (no es así con los enlaces simbolicos).

Nota: Recuerda que ya se comentó que el único directorio público y por lo tanto accesible por los usuarios será /web/. Y es donde estarán los controladores frontales, que accederán al código del Bundle, y los recursos.

2. Plantilla listado.html.twig

Ahora podremos extender la plantilla base anterior y sobreescribir (o añadir) el contenido de los bloques definidos. Los bloques que no especifiquemos serán heredados tal como están definidos en la citada plantilla base.

Recuerda que al llamar al método de renderizar la plantilla, desde TareaController, pasabamos el array de tareas:

 public function indexAction()  
   {  
     $em = $this->getDoctrine()  
           ->getEntityManager();  
     $tareas = $em->getRepository('TareaListadoBundle:Tarea')  
            ->getTareas();  
     return $this->render('TareaListadoBundle:Tarea:listado.html.twig', array(  
       'tareas' => $tareas  
     ));  
   }  


 <!-- src/Tarea/ListadoBundle/Resources/views/Tarea/listado.html.twig -->  
 {% extends 'TareaListadoBundle::layout.html.twig' %}  
 {% block stylesheets %} {# vamos a especificar el bloque de la plantilla padre #} 
  {{ parent() }}  {# queremos el contenido del bloque padre y añadiremos nuevo #}
  <link rel="stylesheet" href="{{ asset('bundles/tarealistado/css/tareas.css') }}" type="text/css" media="all" />  
 {% endblock %}  
 {% block content %}  {# especificaremos este bloque que en la plantilla padre estaba vacio #}
   <div id="tareas">     
     <div id="titulo">  
       <span>Tareas</span>  
       <a href="{{ path('insertar') }}"><img src="{{ asset('bundles/tarealistado/images/plus.png') }}" alt="añadir tarea"/> Inserta Tarea</a>  
     </div>  
     <div id="lista">  
     {% for tarea in tareas %}  
      <ul>  
       <li class="prioridad">  
         {% if tarea.prioridad == 'alta' %}  
           <span class="alta">{{tarea.prioridad}}</span>    
         {% elseif tarea.prioridad == 'media' %}  
           <span class="media">{{tarea.prioridad}}</span>    
         {% elseif tarea.prioridad == 'baja' %}  
           <span class="baja">{{tarea.prioridad}}</span>    
         {% endif %}  
       </li>   
       <li class="editar"><a href="{{ path('editar',{ 'tarea_id' : tarea.id }) }}">[Editar]</a></li>   
       <li class="fecha">{{ tarea.fechaRealizar |date('j F, Y') }}</li>  
       {% if tarea.estado == true %}  
         <li class="descrip realizada">{{ tarea.descripcion }}</li>   
       {% else %}  
         <li class="descrip">{{ tarea.descripcion }}</li>   
       {%endif%}  
       <li class="borrar"><a href="{{ path('eliminar',{ 'tarea_id' : tarea.id }) }}">x<a></li>   
      </ul>  
      <div class="sep"></div>  
     {% endfor %}  
     </div>  
   </div>  
 {% endblock %}  

Con lo visto hasta el momento, ya podríamos ejecutar la URL  http://localhost/ListaTareasSymfony/web/ (en mi caso) en el navegador y obtendríamos la tabla con las tareas añadidas . Pero evidentemente sin estilos, se verá algo no muy atractivo visualmente. Así que vamos a crear unos estilos para que se vea algo mejor.

Nota: Podemos definir el host virtual para no tener que introducir '/web/ en la URL.

3. Estilos

src/Tarea/ListadoBundle/Resources/public/css/main.css (enlazado en la plantilla base layout.html.twig)


 /*  
 Reset CSS  
 Copyright (c) 2008, Yahoo! Inc. All rights reserved.  
 Code licensed under the BSD License:  
 http://developer.yahoo.net/yui/license.txt  
 version: 2.6.0  
 */  
 html{color:#000;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}del,ins{text-decoration:none;}  
 body{font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}select,input,button,textarea{font:99% arial,helvetica,clean,sans-serif;outline: none;}table{font-size:inherit;font:100%;}pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;}  
 h1{  
   font-family: 'Irish Grover', cursive;   
   font-size: 68px;   
 }  
 a  
 {  
  color: #524F46;  
  outline: none;  
  text-decoration: none;  
 }  
 body  
 {  
  font: 14px "Lucida Grande", Lucida, Verdana, sans-serif;  
  background-color: #E0D6D6;  
 }  
 #header h1{  
   margin-bottom: 20px;  
 }  
 #container  
 {  
  margin-left: auto;  
  margin-right: auto;  
 }  
 .content {  
  width: 780px;  
  margin: 0 auto;  
 }  
 #titulo{  
   padding: 6px;  
   padding-left: 15px;  
   background: #8BCFC6 !important;  
   height: 28px;  
   border-bottom: 1px solid #c2c2c2;  
   vertical-align: middle;  
   border-radius: 5px 5px 0px 0px;  
 }  
 #titulo span{  
   color: #555;  
   font-size: 18px;  
   text-transform: capitalize;  
 }  
 #titulo a{  
   margin-left: 20px;  
   font-weight: bolder;  
   color:white;  
   border-radius: 5px;  
   text-transform: capitalize;  
   background: #9BDFD8;  
   padding: 5px;  
 }  
 #titulo a:hover{  
    background: #79BCB6;  
 }  
 #titulo img{  
   width: 12px;  
 }  
 .flash_notice span{  
   color:#00aa00;  
   font-weight: bolder;  
 }  

src/Tarea/ListadoBundle/Resources/public/css/tareas.css  (enlazado en la plantilla hija listado.html.twig)

 #tareas{  
   margin: 20px 0;  
 }  
 #titulo{  
   padding: 6px;  
   padding-left: 13px;  
   background: #8BCFC6 !important;  
   height: 28px;  
   color: #555;  
   border-bottom: 1px solid #c2c2c2;  
   vertical-align: middle;  
   border-radius: 5px 5px 0px 0px;  
 }  
 #titulo span{  
   color: #555;  
   font-size: 18px;  
   text-transform: capitalize;  
 }  
 #lista{  
   background-color: #FFFDC9;  
   padding-bottom: 10px;  
   border-radius: 0px 0px 5px 5px;  
 }  
 ul{  
   width: 100%;  
 }  
 ul li{  
   display: inline-block;  
   padding: 5px 0;  
 }  
 .sep{  
   border-bottom: 1px solid #DEB85F;  
   clear: both;  
 }  
 .alta{  
   background: #ca5b59;  
 }  
 .media{  
   background: #4a97bd;  
 }  
 .baja{  
   background: #bbb;  
 }  
 .prioridad, .editar,.fecha,.descrip  
 {  
   float:left;  
 }  
 .prioridad,.editar{  
   width: 80px;  
   text-align: center;  
 }  
 .editar a{  
   color:#666;  
 }  
 .editar a:hover{  
   text-decoration: underline;  
 }  
 .fecha{  
   width: 100px;  
   color: #aaa;  
 }  
 .realizada{  
   text-decoration: line-through;  
 }  
 .descrip  
 {  
   width: 500px;  
 }  
 .borrar{  
   width: 15px;  
   float: right;  
 }  
 .editar, .fecha, .descrip,.borrar{  
   margin-top:5px;  
 }  
 .prioridad span{  
   font-weight: bolder;  
   color:white;  
   border-radius: 5px;  
   padding: 5px;  
   text-transform: capitalize;  
   display: block;  
   width: 50px !important;  
   margin-left: 15px;  
 }  


El resultado será parecido a la siguiente captura (http://localhost/ListaTareasSymfony/web/ ):



Entradas relacionadas

Lista de tareas simple con Symfony 2: Parte 1
Lista de tareas simple con Symfony 2: Parte 2
Introducción a Symfony 2
Guía de instalación de un sistema LAMP

No hay comentarios:

Publicar un comentario