Mostrando entradas con la etiqueta vistas. Mostrar todas las entradas
Mostrando entradas con la etiqueta vistas. Mostrar todas las entradas

domingo, 31 de enero de 2010

Una idea para no redirigir tanto

Estaba escribiendo el esqueleto de unas acciones para gestionar el registro y confirmación de usuarios cuando me di cuenta de que hay situaciones en las que puede ser buena idea usar Controller->render() para dirigir al usuario a páginas que le informen sobre el resultado de sus acciones, en lugar de Controller->redirect() para hacer lo mismo.

Por ejemplo:

function register() {
if ($this->data) {
if ($this->User->register($this->data)) {
$this->render('registration_ok');
}
}
}


En esta acción, si el registro se hace correctamente, la acción muestra la vista 'registration_ok', que mostraría un mensaje explicando al usuario que su registro ha sido correcto y lo que debe o puede hacer a continuación.

En otro caso, se mostraría la vista por defecto 'register'.

viernes, 14 de agosto de 2009

8 preguntas para diseñar una interfaz web

Pensando en cómo estructurar las vistas administrativas del proyecto acabé redactando una especie de "checklist" con la que me he guiado para determinar qué elementos debería tener cada una de ellas. No tengo mucho tiempo estos días para hacer un bonito PDF Pincha en el enlace para obtener un PDF con la checklist, pero pensé que escribiéndolas aquí bastaría para tenerlas a mano y si además alguien pudiese hacer sugerencias sobre ellas pues mejor.

La idea de partida era imaginar qué preguntas se haría un usuario al llegar a una de las vistas y si en ella habría elementos capaces de responder a las 8 preguntas. Se supone que si la interfaz responde clara y rápidamente, es una buena interfaz.

Las preguntas son:

1. ¿Puedo volver a donde estaba sin tocar nada?

2. ¿Dónde estoy ahora?

3. ¿Qué se supone que puedo/debo hacer aquí?

4. ¿Cómo lo puedo hacer?

5. ¿Dónde se lleva a cabo la acción?

6. ¿Puedo arrepentirme y salir sin estropear nada?

7. ¿Cómo ejecuto/confirmo la acción?

8. ¿Puedo volver al punto de partida o moverme a otro lugar?

martes, 11 de agosto de 2009

Usando Media View para descargar archivos

Estoy trabajando en un plugin para gestionar archivos subidos a una aplicación web y entre otras cosas he aprendido a utilizar las Media Views para facilitar las descargas de esos mismos archivos, estén almacenados donde estén.

La documentación base que he utilizado es la página del manual y un post del siempre recomendable blog sobre CakePHP de Teknoid. A partir de aquí, y tras unas pocas pruebas parece que he conseguido dominar este tipo de vistas.

Qué es una Media View

Se trata de un tipo de vista especial para enviar archivos binarios al usuario. Se utiliza en lugar de las vistas por defecto y la verdad es que se encarga de casi todo ella solita.

Hay dos ventajas principales en usar MediaView en lugar de enlaces directos al archivo:

  • Podemos enviar archivos que no estén bajo el webroot, evitando así que los usuarios puedan descargarlos reconstruyendo la URL, algo relativamente fácil conociendo cómo funciona CakePHP
  • Al realizar la descarga a través de una acción de un controlador, ésta puede tener el acceso controlado a través de Auth o el método que tengamos, con lo cual, incluso con la URL sólo los usuarios autorizados podrían descargar el archivo.
Empleando Media View

Para poder empezar a utilizarlas debemos especificarlo en la acción apropiada del controlador, con la siguiente línea:

$this->view = 'Media';

A continuación hay que pasarle varios parámetros para identificar el archivo, una forma sencilla sería así:

$params = array(
'id' => 'nombre_del_archivo.ext',
'name' => 'Nombre Humano del Archivo',
'download' => true,
'extension' => 'ext',
'mimeType' => array('ext' => 'mimeType/subtype'),
'path' => 'path/a/la/carpeta/del/archivo/',
);
$this->set($params);

Trataré de explicarme:

Empiezo por el final. A la vista habría que pasarle los parámetros id, name, etc. Pasando un array con las claves necesarias hacemos lo mismo. A mí al principio me extrañó esta manera de hacerlo (lo típico es pasar $this->set('params', $params);) pero al ver el código de media.php, me quedó claro que CakePHP desempaqueta el array. En consecuencia, puedes ponerle al array el nombre que quieras, no hace falta que sea $params mientras que las claves sean correctas.

Ahora voy por los parámetros o claves del array.

id: Es el nombre del archivo que vamos a enviar, tal cual está en el sistema de archivos.

name: Es el nombre con el que queremos que el usuario guarde el archivo en su disco duro, esto nos permite ponerle un nombre humanizado si preferimos.

extension: Se explica solo, es la extensión del archivo

download: si está en true, fuerza la descarga el archivo

mimeType: esta parte tiene truco. A mí me costó un poquito cogerle el punto. La clase MediaView "reconoce" una buena cantidad de tipos mime que podrían tener los archivos, pero no todos. El problema es que no se descargan si no proporcionamos el mimeType correcto. Afortunadamente, en esta clave podemos declarar uno o más mimeType aceptables para cada archivo concreto. La forma de declararlos es poner la extensión como clave y como valor el tipo mime. En mi caso, al subir el archivo tomo nota del mimeType y no tengo más que recuperarlo en la acción de descarga y pasarlo en esta clave. De este modo me ha permitido descargar diversos tipos de archivos que MediaView no admitía (como los .rar).

path: es la ruta al archivo relativa a la carpeta de la aplicación (concretamente utiliza el valor de la constante APP), por lo cual, podemos descargar archivos que no estén bajo el webroot, sino en un nivel superior y fuera de él. Esto es ideal si no queremos que los usuarios puedan descargar archivos "imaginándose" la URL. Debe llevar el separador de directorios al final. Recuerda construir los paths usando la constante DS si necesitas que la aplicación sea portable.

size: se puede especificar la clave size para indicar el tamaño de archivo, pero yo no he conseguido que los archivos se descargasen. No sé dónde esta el error.

Y ya está.

domingo, 3 de febrero de 2008

Temas para las aplicaciones Cake

Como estoy manteniendo un desarrollo que voy a usar en diferentes proyectos, me interesaba estudiar el asunto de crear temas para cada uno de ellos.

Por si no tienes claro de qué estoy hablando, los temas tienen que ver con el aspecto visual de las aplicaciones. A mí me interesa sobre todo, poder tener un tema diferente para cada proyecto concreto, aunque compartan buena parte de la funcionalidad y el código de la mayoría de las vistas.

La mejor referencia que he encontrado sobre el particular es este artículo de Tarique Sani y es tan sencillo que apenas lleva un par de minutos preparar una aplicación para dar un soporte básico de los temas. Reproduzco los pasos básicos

En el código

En tu app_controller añade la siguiente línea

var $view = 'Theme';


Básicamente la línea anterior le dice a Cake que vamos a usar temas en la aplicación.

En beforeFilter o beforeRender de los Controllers adecuados (o en el app_controller) añade una línea para indicarle a CakePHP el nombre del tema que vas a utilizar

$this->theme = 'mitema'


El nombre lo puedes aportar directamente, o bien leyendo una variable de configuración, o un ajuste de usuario guardado en la sesión... de dónde quieras.

Cómo se hacen los temas

Como se supone que ya sabes hacer vistas y hojas de estilo y demás, ya sabes hacer temas. Para empezar a crear temas para la aplicación Cake lo primero que tienes que hacer es crear una carpeta "themed" dentro de "views" y otra "themed" dentro de webroot.

Dentro de cada una de ellas crearás una carpeta por cada uno de los temas que vayas a crear. Por ejemplo, "mitema".

Así, la ruta quedará así

/app/views/themed/mitema


/app/webroot/themed/mitema


¿Y qué habremos de colocar ahí? Pues la versión "tematizada" de nuestras vistas, hojas de estilo, javascripts o imágenes que necesitemos, de manera que la estructura habitual de views y webroot quede reproducida bajo la carpeta del tema.

Así, por ejemplo, si necesitamos una vista "tematizada" para la acción /posts/index crearemos el archivo

/app/views/themed/mitema/posts/index.ctp


Y si, por otro lado, queremos un hoja de estilo específica para un tema, usaremos

/app/webroot/themed/mitema/css/estilos.css


Sin embargo, no necesitas reescribir todas tus vistas ni recursos.

Fall-back

Lo mejor de todo es que Cake tiene un sistema de "red de seguridad" para los temas. Esto es, cuando usamos temas, CakePHP buscará layouts, vistas y demás recursos en la carpeta del tema, y si no lo encuentra, buscará en la carpeta básica.

De este modo, puedes escribir tus vistas, hojas de estilo, recursos web comunes y luego tematizar únicamente aquellos que realmente lo necesiten. Por ejemplo, a lo mejor sólo tienes que tocar unas hojas de estilo y un layout. O alguna vista para una acción en particular.

sábado, 14 de julio de 2007

Acceder a valores de los campos de un formulario

Con html::tagValue ($campo) puedes acceder en la vista a los valores de los campos del modelo del que estás construyendo un formulario (son los que el Controller tenga en Controller::data).

O sea, cuando uno hace cosas como ésta:

$this->data = $this->Model->Read ()

puede acceder a esos datos mediante el método tagValue del HtmlHelper.

Muy útil para construir enlaces en la vista, sin tener que crear una variable de vista para guardarlos.

domingo, 8 de julio de 2007

Título de la página desde el controller

Conocía este consejillo de Armando Sosa, para definir el título de la página desde la vista. Pero buscando no sé qué en el API, encontré que si ajustas la variable 'title' en el controller para pasársela a la view, queda fijada como título de la página. O sea, en la acción deseada del controlador ponemos:

$this->set ('title', 'Lo que quieras que sea el título');

Actualización (8-7-07)

La parte "mala" es que $title no aparece como variable de la vista. Sin embargo, puedes usar $this->pageTitle en la vista para acceder a ella.

martes, 12 de junio de 2007

Organizando las vistas para Ajax

Siguiendo con el ejercicio anterior he descubierto unas cuantas cosas más. Por ejemplo, los problemas que supone la validación CakePHP interaccionando con un planteamiento "ajaxiano" de la vista.

La solución que he encontrado es la siguiente:

De lo general a lo particular

Lo primero sería hacer un croquis general de lo que voy a necesitar poner en la vista. En mi caso, por ejemplo, un título, un formulario y una tabla. Hay que tener en cuenta las partes que se van a tener que actualizar solas, en un momento dado, o las que habrá que actualizar globalmente.

Lo mejor parece ser descomponer la vista en partes, valorando si necesitan ser incluidas en una div, y crear elements para generar cada una de ellas. De este modo dispondremos de piezas de construcción para generar las vistas definitivas. Procura ponerles nombres bien descriptivos a los elementos.

Preparando las vistas

Con los elements ya preparados generamos las vistas que nos haga falta, aprovechando el método This::Element ('elemento').

La idea es crear tantas vistas como podamos necesitar. Intentaré explicarlo:

Normalmente la primera petición para cierta página será una petición normal, sin Ajax ni cosas raras. Entonces tendremos que tener una vista que muestre todo lo necesario. En mi ejemplo, una vista que muestre el título, el formulario y la tabla. Esa podría ser index.ctp, si mi acción es index.

Como mi tabla tiene enlaces Ajax para ordenar los resultados y éstos piden que se actualice sólo la div que contiene la tabla, necesitaré una vista que sólo muestre la tabla. La llamaré tabla.ctp.

El formulario envía una petición Ajax al servidor para añadir entradas sin actualizar toda la página. El resultado del formulario debería actualizar la tabla, pero puede ocurrir que tenga que actualizarse también el propio formulario, por ejemplo si hay errores en la validación en CakePHP. En este caso, podría crear una nueva plantilla, aunque veo que index.ctp me sirve para esto sin tener que duplicar el trabajo.

También podría preparar una vista específica para cuando se solicita la acción con una petición no Ajax.

Y luego están las vistas para las demás acciones que programemos, por supuesto. En ellas hay que considerar los mismos aspectos acerca de si es posible que sean solicitadas a través de Ajax y qué contenido deben proporcionar.

En el controlador

Ahora bien, en el controlador tenemos que determinar qué tipo de petición está entrando y qué contenido tenemos que devolver. Tenemos dos armas:

RequestHandler->isAjax() es el método que nos permite saber si la petición es Ajax y hacer cosas diferentes en cada caso.

Controller::Render ('vista') nos permite especificar una vista concreta con la que mostrar el resultado de la acción.

Usándolas en combinación, podemos hacer que nuestra acción sepa qué contenido debe entregar a la petición Ajax (o de cualquier otro tipo) y mediante qué vista.

No te olvides de generar el contenido

Eso sí, hay que tener presente qué contenido es el que vamos a actualizar, porque las cosas pueden ser un poco diferentes que en el uso normal.

Por ejemplo, en este caso de la combinación de formulario de entrada más tabla de datos, he decidido que al añadir una entrada hay que actualizar la vista que contiene tanto el formulario como la tabla. Por lo tanto, la acción add, cuando es llamada mediante Ajax, tiene que lidiar no sólo con tomar los datos y hacer lo que haga falta con ellos, sino también obtener el listado para mostrar como si fuera una acción index y enviárselos a la vista index, en vez de a la vista por defecto (add).

Alternativamente, la versión "no Ajax" de la acción podría usar una vista diferente y necesitar información diferente.

Otros beneficios

Algunas de las ideas expuestas aquí, aparte de ser útiles e incluso necesarias para integrar Ajax, tienen algunos beneficios colaterales.

Al descomponer una vista, o una familia de vistas, en elementos reutilizables es más fácil, por ejemplo tener un único formulario para añadir y editar. Estamos siguiendo eso que llaman la metodología DRY (Don't Repeat Yourself).

Otro beneficio es que si una parte de una vista es relativamente complicada, al empaquetarla en un element podemos mantener controlada esa complejidad.

lunes, 11 de junio de 2007

Crear un feed RSS

Me llevó todo el domingo intentar desentrañar el pequeño misterio de generar feeds RSS con CakePHP a partir de este artículo de Nate, que lo explica, pero no demasiado.

La gracia es que acabé consiguiéndolo a medias: el feed salía pero no podía controlarlo a mi gusto (¿dónde c... se pone el channel?). Más tarde (a punto de ponerme a dormir) me di cuenta de que había pasado por alto ver cómo funcionaba la plantilla por defecto para rss, la cual tendría todas las respuestas. Efectivamente. Lo malo es que, acto seguido, voy y me encuentro este breve tutorial de Jiri Kupiainen donde explica todo el proceso. :-(

Ocurrió lo de siempre, te pegas contra la documentación, el código, el API... descubres como se hace algo, y una vez que lo tienes aparecen como por encanto artículos en Google que "no estaban ayer". Lo bueno, es que el aprendizaje por la vía dura suele ser bastante eficaz.

En fin, el caso es que basándome en ambas fuentes, he aquí mi propio tutorial para generar feeds rss... ¡a partir de cualquier modelo!

Paso 1: preparar Cake

Básicamente hay que decirle a CakePHP que queremos parsear url con extensiones, de la forma: /controller/action.rss. Esto es así porque la versión 1.2 de CakePHP puede seleccionar al vuelo diferentes vistas en función de la extensión que le pasemos. Por lo tanto, en un paso posterior crearemos una vista específica para el feed y la pondremos en el sitio adecuado para que la use Cake.

En tu /app/config/routes.php tienes que añadir:

Router::parseExtensions('rss');


Esto le indica a Cake que si se encuentra una URL con la extensión 'rss' busque las vistas en la carpeta /vistas/modelos/rss.

Podemos no especificar ninguna extensión, para que parsee cualquiera, o bien dar una lista limitada de ellas 'rss', 'xml'...

Paso 2: cosas para hacer en el Controller

Hay varias, así que vayamos por partes.

Lo primero es habilitar el uso del componente RequestHandler en el controlador. Aparte de para este tema de los feeds es útil siempre que necesites obtener información sobre la petición que está recibiendo el controlador. Lo segundo es indicar que quieres utilizar el RSSHelper, claro.

var $components = array('RequestHandler');
var $helpers = array ('RSSHelper');


Una cosa opcional: El componente HandleRequest te permite comprobar si el usuario está pidiendo un RSS (u otra cosa) y actuar en consecuencia. Por ejemplo, si es para el feed obtener sólo los últimos 15 items que cumplan ciertas condiciones y si no obtener todos. En cualquier caso, usarías:

if ($this->RequestHandler->prefers('rss')) {
// Cosas para hacer si es rss
} else {
// Cosas para hacer si no lo es
}


Lo segundo es enviarle los datos a la vista del feed (que aún no hemos creado, ¡ojo!). Lo puedes hacer así:


Configure::write('debug', '0');
$data = $this->Paginate ();
$this->set('channel', array ('title' => 'Ejemplo',
'link' => 'http://localhost:8888/micake/',
'description' => 'Frases y citas célebres para que las puedas leer'
));
$this->set ('frases', $data);


Lo explico un poco:

La primera línea es para desactivar el Debug (en el caso de que lo tengas distinto de 0, como ocurre en un entorno de pruebas o de desarrollo). Con Debug, Cake añade cosas a la salida generada y los feeds no validarían de ninguna forma aunque estuviesen bien construidos. Opcionalmente puedes poner un if para controlar si es necesario hacerlo o no.

La segunda línea es para recabar los datos de la manera habitual.

La tercera línea ajusta los valores para el channel. El layout por defecto para feeds rss espera que definas una variable $channel como un array con claves que serán los elementos del channel (como title, link, description y los demás). Si no la defines, CakePHP se busca la vida para poner algunos.

La cuarta línea pasa el array de datos que serán los ítems del modelo. En este caso, la variable es $frases, pero se supone que esto ya sabías hacerlo. Aquí no vamos a hacer nada más.

Paso 3: preparando la vista, o cómo pasar tu modelo al feed

La vista tienes que ponerla en /views/nombre_del_modelo_plural/rss/nombre_de_la_action.ctp

Y el contenido tiene que ser más o menos así:


$items = $rss->items($frases, 'convertirRSS');

echo $items;
function convertirRSS($data) {
return array(
'title' => $data['Frasecita']['frasecita'],
'link' => array('action' => 'view', $data['Frasecita']['id']),
'guid' => array('action' => 'view', $data['Frasecita']['id']),
'description' => $data['Frasecita']['frasecita'],
'author' => $data['Frasecita']['autor'],
'pubDate' => $data['Frasecita']['usado']
);
}


La explicación es como sigue:

Usamos el método rssHelper::items () para convertir el array de datos en elementos items del feed. Para convertir los campos del modelo en los items con sus correspondientes subelementos, usamos una función callback que, en este caso, hemos llamado convertirRSS.

La función tiene que tomar un array asociativo simple, cuyas claves son los nombres de los campos del modelo (y cuyos valores serán los de los diferentes registros, pero de eso se encarga el método items).

La función, por otra parte, tiene que devolver un array cuyas claves sean elementos válidos de item. Tarea nuestra es decidir cómo movemos los datos entre el modelo y los elementos del item.

Algunas observaciones interesantes en el ejemplo. Link y Guid piden url y entienden que se las pasemos como arrays, tal como se explica en el artículo sobre el router.

PubDate, admite campos de tiempo y los prepara en el formato adecuado.

Aparte, mirando el API, creo que hay soporte específico para enclosures, o sea, que el camino para feeds de podcasts está abierto. Aún no lo he investigado.

Paso 4: Ya está, ¿cómo suscribirse?

Pues sí, el feed ya está listo para servir. La URL para suscribirse sería:

http://exemple.com/controller/action.rss

Sustituye lo que haga falta. Supongo que se podrían hacer rutas para que ciertas url se sirvan como feeds.