Tutorial Jobeet con Symfony2 Día 7: Jugando con la página Category

De WikiSalud
Saltar a: navegación, buscar
Este artículo es parte del proyecto Tutorial Jobeet con Symfony2

Hoy haremos un página de Categoría como es descrita en los requirimientos del segundo día: "El usuario ve una lista de todos las ofertas de trabajo de la categoría ordenadas por fecha y paginados con 20 ofertas por página"

Contenido

La ruta Category

Primero, necesitamos añadir una ruta para definir una pequeña URL para la página de categoría. La añadimos al inicio del archivo de rutas:

src/Ens/JobeetBundle/Resources/config/routing.yml
# src/Ens/JobeetBundle/Resources/config/routing.yml
EnsJobeetBundle_category:
    pattern:  /category/{slug}
    defaults: { _controller: EnsJobeetBundle:Category:show }


Para conseguir aporrear una categoría necesitamos añadir el método getSlug a nuestra clase de categoría:

'
// src/Ens/JobeetBundle/Entity/Category.php
// ...
 
use Ens\JobeetBundle\Utils\Jobeet;
 
class Category
{
  // ...
 
  public function getSlug()
  {
    return Jobeet::slugify($this->getName());
  }
 
  // ...
}


El enlace a Category

Ahora, editamos la plantilla index.html.twig de el controlador Job y añadimos el enlace a la página de Categorías.

src/Ens/JobeetBundle/Resources/views/Job/index.html.twig
<!-- src/Ens/JobeetBundle/Resources/views/Job/index.html.twig -->
<!-- some HTML code -->
 
            <h1><a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug }) }}">{{ category.name }}</a></h1>
 
<!-- some HTML code -->
 
          </table>
 
          {% if category.morejobs %}
            <div class="more_jobs">
              and <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug }) }}">{{ category.morejobs }}</a>
              more...
            </div>
          {% endif %}
        </div>
      {% endfor %}
    </div>
{% endblock %}


En la plantilla anterior usamos category.morejobs, así que definamosla:

src/Ens/JobeetBunlde/Entity/Category.php
// src/Ens/JobeetBunlde/Entity/Category.php
// ...
 
    /**
    * @var string
    */
    private $more_jobs;
 
// ...
 
public function setMoreJobs($jobs)
{
  $this->more_jobs = $jobs >=  0 ? $jobs : 0;
}
 
public function getMoreJobs()
{
  return $this->more_jobs;
}
 
// ...


La propiedad more_jobs mantiene el número de ofertas de trabajo activas para la categoría, menos el número de trabajo listados en la página principal. Ahora, en JobController, necesitamos configurar el valor more_jobs para cada categoría:

src/Ens/JobeetBundle/Controller/JobRepository.php
// src/Ens/JobeetBundle/Controller/JobController.php
// ...
 
public function indexAction()
{
  $em = $this->getDoctrine()->getEntityManager();
 
  $categories = $em->getRepository('EnsJobeetBundle:Category')->getWithJobs();
 
  foreach($categories as $category)
  {
    $category->setActiveJobs($em->getRepository('EnsJobeetBundle:Job')->getActiveJobs($category->getId(), $this->container->getParameter('max_jobs_on_homepage')));
    $category->setMoreJobs($em->getRepository('EnsJobeetBundle:Job')->countActiveJobs($category->getId()) - $this->container->getParameter('max_jobs_on_homepage'));
  }
 
  return $this->render('EnsJobeetBundle:Job:index.html.twig', array(
    'categories' => $categories
  ));
}
// ...


La función countActiveJobs tiene que ser añadida a JobRepository

src/Ens/JobeetBundle/Controller/JobController.php
// src/Ens/JobeetBundle/Repository/JobRepository.php
// ...
 
public function countActiveJobs($category_id = null)
{
  $qb = $this->createQueryBuilder('j')
    ->select('count(j.id)')
    ->where('j.expires_at > :date')
    ->setParameter('date', date('Y-m-d H:i:s', time()));
 
  if($category_id)
  {
    $qb->andWhere('j.category = :category_id')
    ->setParameter('category_id', $category_id);
  }
 
  $query = $qb->getQuery();
 
  return $query->getSingleScalarResult();
}
// ...


Ahora deberías ver los resultados en el navegador:

Primera vista08.png

Controlador para Creación de Categorías

Es hora de crear el controlador categoría. Crea un nuevo archivo CategoryController.php en tu directorio de Controller

src/Ens/JobeetBundle/Controller/CategoryController
// src/Ens/JobeetBundle/Controller/CategoryController
 
namespace Ens\JobeetBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Ens\JobeetBundle\Entity\Category;
 
/**
* Category controller.
*
*/
class CategoryController extends Controller
{
 
}

Podriamos usar el comando doctrine:generate:crud como hicimos para el trabajo controlador, pero no necesitaremos el 90% del código generado, asi que solo creamos un nuevo controlador de la nada.

Actualizar la base de datos

Necesitamos añadir una columna slug para la tabla categoría y una llamada en el ciclo de vida para configurar el valor de esta columna:

src/Ens/JobeetBundle/Resources/config/doctrine/Category.orm.yml
Ens\JobeetBundle\Entity\Category:
  type: entity
  repositoryClass: Ens\JobeetBundle\Repository\CategoryRepository
  table: category
  id:
    id:
      type: integer
      generator: { strategy: AUTO }
  fields:
    name:
      type: string
      length: 255
      unique: true
    slug:
      type: string
      length: 255
      unique: true
  oneToMany:
    jobs:
      targetEntity: Job
      mappedBy: category
    category_affiliates:
      targetEntity: CategoryAffiliate
      mappedBy: category
  lifecycleCallbacks:
    prePersist: [ setSlugValue ]
    preUpdate: [ setSlugValue ]


Remover de la entidad Categoria (src/Ens/JobeetBundle/Entity/Category.php) el método getSlug que creamos antes y ejecuta el comando Doctrine para actualizar la clase de la entidad Category:

php app/console doctrine:generate:entities EnsJobeetBundle

Ahora usted debería tener lo siguiente añadido a Category.php:

src/Ens/JobeetBundle/Entity/Category.php
// src/Ens/JobeetBundle/Entity/Category.php
// ...
 
/**
* @var string $slug
*/
private $slug;
 
/**
* Set slug
*
* @param string $slug
*/
public function setSlug($slug)
{
    $this->slug = $slug;
}
/**
* Get slug
*
* @return string
*/
public function getSlug()
{
    return $this->slug;
}
/**
* @ORM\prePersist
*/
public function setSlugValue()
{
    // Add your code here
}

Cambia la función setSlugValue a lo siguiente:

src/Ens/JobeetBundle/Entity/Category.php
public function setSlugValue()
{
    $this->slug = Jobeet::slugify($this->getName());
}


Ahora tenemos que eliminar la base de datos y crearla de nuevo con la nuevo con la nueva columna categoría y cargar las fixtures <TODO: Creo que debo cambiar esto>

php app/console doctrine:database:drop --force
php app/console doctrine:database:create // Probablemente tendrá que hacerlo desde su gestor de base de datos
php app/console doctrine:schema:update --force
php app/console doctrine:fixtures:load

Página Category

Ahora tenemos todo en su lugar para crear el método showAction(). Añadimos el siguiente código al archivo CategoryController.php:

src/Ens/JobeetBundle/Controller/CategoryController.php
// src/Ens/JobeetBundle/Controller/CategoryController.php
// ...
 
public function showAction($slug)
{
    $em = $this->getDoctrine()->getEntityManager();
 
    $category = $em->getRepository('EnsJobeetBundle:Category')->findOneBySlug($slug);
 
    if (!$category) {
        throw $this->createNotFoundException('Unable to find Category entity.');
    }
 
    $category->setActiveJobs($em->getRepository('EnsJobeetBundle:Job')->getActiveJobs($category->getId()));
 
    return $this->render('EnsJobeetBundle:Category:show.html.twig', array(
        'category' => $category,
    ));
}
// ...


Para mantener el orden, cree el directorio src/Ens/JobeetBundle/Resources/views/Category
El último paso es crear la plantilla show.html.twig

src/Ens/JobeetBundle/Resources/views/Category/show.html.twig
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block title %}
  Jobs in the {{ category.name }} category
{% endblock %}
 
{% block stylesheets %}
  {{ parent() }}
  <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/jobs.css') }}" type="text/css" media="all" />
{% endblock %}
 
{% block content %}
  <div class="category">
    <div class="feed">
      <a href="">Feed</a>
    </div>
    <h1>{{ category.name }}</h1>
  </div>
 
  <table class="jobs">
    {% for entity in category.activejobs %}
      <tr class="{{ cycle(['even', 'odd'], loop.index) }}">
        <td class="location">{{ entity.location }}</td>
        <td class="position">
          <a href="{{ path('job_show', { 'id': entity.id, 'company': entity.companyslug, 'location': entity.locationslug, 'position': entity.positionslug }) }}">
            {{ entity.position }}
          </a>
        </td>
        <td class="company">{{ entity.company }}</td>
      </tr>
    {% endfor %}
  </table>
{% endblock %}


Si la impaciencia es mucha, acceda a la página principal de Jobeet y haga click en la cabecera de cada categoría, lo que le dirige a la página de dicha categoría. Pero las pruebas deberían estar listas en un par de apartados más.

Incluyendo otras plantillas Twig

Note que hemos copiado y pegado el tag que crea una lista de ofertas de trabajo de la plantilla index.html.twig. Eso esta mal. Cuando tu necesitas reusar alguna porción de una plantilla, tu necesitas crear una nueva plantilla twig con ese código e incluirla donde tu lo necesites Crea el archivo list.html.twig
src/Ens/JobeetBundle/Resources/views/list.html.twig
<!-- src/Ens/JobeetBundle/Resources/views/Job/list.html.twig -->
 
<table class="jobs">
  {% for entity in jobs %}
    <tr class="{{ cycle(['even', 'odd'], loop.index) }}">
      <td class="location">{{ entity.location }}</td>
      <td class="position">
        <a href="{{ path('job_show', { 'id': entity.id, 'company': entity.companyslug, 'location': entity.locationslug, 'position': entity.positionslug }) }}">
          {{ entity.position }}
        </a>
      </td>
      <td class="company">{{ entity.company }}</td>
    </tr>
  {% endfor %}
</table>


Puedes incluir una plantilla usando el tag {% include %}. Reemplaza el código HTML de <table> de ambas plantillas con el tag include:

src/Ens/JobeetBundle/Resources/view/Job/index.html.twig
<!-- in src/Ens/JobeetBundle/Resources/view/Job/index.html.twig -->
{% include 'EnsJobeetBundle::list.html.twig' with {'jobs': category.activejobs} %}


src/Ens/JobeetBundle/Resources/view/Category/show.html.twig
<!-- in src/Ens/JobeetBundle/Resources/view/Category/show.html.twig -->
{% include 'EnsJobeetBundle::list.html.twig' with {'jobs': category.activejobs} %}


Lista de paginación

Al momento de escribir esto, Symfony2 no provee ninguna herramienta de paginación para resolver este problema, así que usaremos el viejo y clásico método

Primero, añadamos un parametro página a la ruta EnsJobeetBundle_category. El parametro página tendra una valor por defecto de 1, así que no será requerido

src/Ens/JobeetBundle/Resources/config/routing.yml
# src/Ens/JobeetBundle/Resources/config/routing.yml
 
EnsJobeetBundle_category:
    pattern: /category/{slug}/{page}
    defaults: { _controller: EnsJobeetBundle:Category:show, page: 1 }
 
# ...


El número de ofertas de trabajo serán definidas como un parametros personalizable en el archivo app/config/parameters.yml

app/config/parameters.yml
# app/config/parameters.yml
# ...
 
parameters:
    max_jobs_on_homepage: 10
    max_jobs_on_category: 20


Cambiar el método getActiveJobs en JobRepository para incluir un parametro $offset a ser usado por Doctrine cuando se recuperen ofertas de trabajo:

src/Ens/JobeetBundle/Repository/JobRepository.php
// src/Ens/JobeetBundle/Repository/JobRepository.php
// ...
 
public function getActiveJobs($category_id = null, $max = null, $offset = null)
{
  $qb = $this->createQueryBuilder('j')
    ->where('j.expires_at > :date')
    ->setParameter('date', date('Y-m-d H:i:s', time()))
    ->orderBy('j.expires_at', 'DESC');
 
  if($max)
  {
    $qb->setMaxResults($max);
  }
 
  if($offset)
  {
    $qb->setFirstResult($offset);
  }
 
  if($category_id)
  {
    $qb->andWhere('j.category = :category_id')
       ->setParameter('category_id', $category_id);
  }
 
  $query = $qb->getQuery();
 
  return $query->getResult();
}
 
// ...


Cambiar el método showAction del CategoryController de la siguiente forma:

src/Ens/JobeetBundle/Controller/CategoryController.php
// src/Ens/JobeetBundle/Controller/CategoryController.php
// ...
 
public function showAction($slug, $page)
{
  $em = $this->getDoctrine()->getManager();
 
  $category = $em->getRepository('EnsJobeetBundle:Category')->findOneBySlug($slug);
 
  if (!$category) {
    throw $this->createNotFoundException('Unable to find Category entity.');
  }
 
  $total_jobs = $em->getRepository('EnsJobeetBundle:Job')->countActiveJobs($category->getId());
  $jobs_per_page = $this->container->getParameter('max_jobs_on_category');
  $last_page = ceil($total_jobs / $jobs_per_page);
  $previous_page = $page > 1 ? $page - 1 : 1;
  $next_page = $page < $last_page ? $page + 1 : $last_page;
 
  $category->setActiveJobs($em->getRepository('EnsJobeetBundle:Job')->getActiveJobs($category->getId(), $jobs_per_page, ($page - 1) * $jobs_per_page));
 
  return $this->render('EnsJobeetBundle:Category:show.html.twig', array(
    'category' => $category,
    'last_page' => $last_page,
    'previous_page' => $previous_page,
    'current_page' => $page,
    'next_page' => $next_page,
    'total_jobs' => $total_jobs
  ));
}

Finalmente, actualizamos la plantilla:

src/Ens/JobeetBundle/Resources/views/Category/show.html.twig
<!-- src/Ens/JobeetBundle/Resources/views/Category/show.html.twig -->
 
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block title %}
  Jobs in the {{ category.name }} category
{% endblock %}
 
{% block stylesheets %}
  {{ parent() }}
  <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/jobs.css') }}" type="text/css" media="all" />
{% endblock %}
 
{% block content %}
  <div class="category">
    <div class="feed">
      <a href="">Feed</a>
    </div>
    <h1>{{ category.name }}</h1>
  </div>
 
  {% include 'EnsJobeetBundle::list.html.twig' with {'jobs': category.activejobs} %}
 
  {% if last_page > 1 %}
    <div class="pagination">
      <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug, 'page': 1 }) }}">
        <img src="{{ asset('bundles/ensjobeet/images/first.png') }}" alt="First page" title="First page" />
      </a>
 
      <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug, 'page': previous_page }) }}">
        <img src="{{ asset('bundles/ensjobeet/images/previous.png') }}" alt="Previous page" title="Previous page" />
      </a>
 
      {% for page in 1..last_page %}
        {% if page == current_page %}
          {{ page }}
        {% else %}
          <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug, 'page': page }) }}">{{ page }}</a>
        {% endif %}
      {% endfor %}
 
      <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug, 'page': next_page }) }}">
        <img src="{{ asset('bundles/ensjobeet/images/next.png') }}" alt="Next page" title="Next page" />
      </a>
 
      <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug, 'page': last_page }) }}">
        <img src="{{ asset('bundles/ensjobeet/images/last.png') }}" alt="Last page" title="Last page" />
      </a>
    </div>
  {% endif %}
 
  <div class="pagination_desc">
    <strong>{{ total_jobs }}</strong> jobs in this category
 
    {% if last_page > 1 %}
      - page <strong>{{ current_page }}/{{ last_page }}</strong>
    {% endif %}
  </div>
{% endblock %}


El resultado:

Primera vista09.png
Herramientas personales
Espacios de nombres

Variantes
Acciones
Navegación
Herramientas