lunes, 20 de septiembre de 2010

CakePHP y Javascript

En principio, no necesitas hacer nada especial para incorporar código javascript en las vistas de tu aplicación CakePHP, aparte de escribir el código, naturalmente. Simplemente has de insertarlo tal y como lo harías en cualquier documento HTML: utilizando la etiqueta script.


<script type="text/javascript" charset="utf-8">
 // Código
</script>


Por supuesto, en algún momento necesitarás utilizar scripts que se encuentren en archivos .js, convenientemente guardados en la carpeta js del webroot de tu aplicación. Para esta tarea es buena idea recurrir a HtmlHelper en CakePHP 1.3 (o a JavascriptHelper en la versión 1.2), ya que lo enlazará correctamente incluso si tu instalación de Cake está muy personalizada.

Por ejemplo:


<?php echo $this->Html->script('jquery.form', array('inline' => false)); ?>


o bien


<?php echo $javascript->link('ckeditor/ckeditor', NULL, false);  ?>


En estos ejemplos, puedes ver que para llamar al archivo javascript no es necesario incluir la extensión y que si el script se encuentra en una subcarpeta de js, hay que incluirlo en el nombre.

Por otro lado, la opción "inline" = false (en el segundo ejemplo, se correspondería con el tercer parámetro del método link) hace que la llamada a nuestro script se incluya en la cabecera del documento. Más exactamente allí donde hayamos realizado un echo $scripts_for_layout. De no indicar nada o ponerlo en true, el script se incluirá en el lugar de la vista donde realicemos el echo.

Puesto que todo lo que acontece en Javascript está en el lado del cliente, no hay más de lo que debas preocuparte en lo que respecta a la aplicación, exceptuando, claro está, el caso de trabajar con Ajax que veremos dentro de un momento, tras un pequeño inciso.

Los helpers Ajax/Javascript han transmutado en Js

La presencia del JavascriptHelper (y de AjaxHelper) en la versión 1.2 o del JsHelper en la versión 1.3 pueden llevarte a pensar que son de algún modo necesarios para poder trabajar con Javascript en una aplicación CakePHP.

Como decía al principio, esto no es así. Realmente no los necesitas si no quieres usarlos, pero te pueden echar una mano, que para eso están. Estos Helpers están ahí para ayudarte a generar algunos bloques de código más o menos típicos y repetitivos de la manera más sencilla posible.

Con todo, en la versión 1.2 podían resultar hasta un poco estorbo, ya que, el AjaxHelper en particular está basado en la librería Prototype, mientras que mucha gente opta desde hace tiempo por otras como jQuery o MooTools.

A consecuencia de esta constatación, el equipo de CakePHP decidió unificar AjaxHelper y JavascriptHelper en el nuevo JsHelper, con la ventaja añadida de poder definir qué framework Javascript preferimos utilizar. Por otro lado, un par de funciones que eran responsabilidad de JavascriptHelper han pasado a HtmlHelper. En concreto, la JavascriptHelper->link() es ahora HtmlHelper->script(), y JavascriptHelper->codeBlock() pasa a ser HtmlHelper->codeBlock(), lo que tiene bastante lógica.

Ajax

Ajax nos puede plantear algunos problemas particulares, ya que se trata básicamente de Javascript que interacciona con nuestra aplicación en segundo plano. Y, si bien, podemos organizar las cosas de modo que aprovechemos el código, debemos tener en cuenta algunos puntos.

RequestHandler Component

Para empezar, nos interesa recurrir a este componente especializado en gestionar información sobre las peticiones que recibe la aplicación y las respuestas que envía.

En general, suelo ponerlo en AppController, pues me puede interesar su uso en muchos lugares distintos de la aplicación. Aunque para usarlo basta incluirlo en el array de components del controlador que lo necesite.

En el caso que nos ocupa, es decir, procesar peticiones que vienen a través de Ajax, RequestHandler nos permite identificarlas mediante el método isAjax(), que devuelve true en el caso que te puedes imaginar. Además, preselecciona por nosotros el layout Ajax (básicamente un layout vacío que solo mostrará el contenido de la vista que toque).

De este modo, podemos bifurcar una acción para hacer cosas distintas si ha sido solicitada a través de Ajax o no. O también podemos asegurarnos de que una acción sólo se ejecuta si se ha pedido por Ajax.

RequestHandler identifica una petición Ajax cuando viene marcada con una cabecera determinada. Esto puede ser un problema si acción no es solicitada a través del objeto XHttpRequest. Por ejemplo, si se utiliza la técnica de iframes, en principio la petición es indistinguible de una petición estándar. En este caso se pueden llevar a cabo diferentes estrategias, como pueden ser "marcar" de algún modo la petición para interpretarla correctamente en el servidor, o bien tener una acción del controlador específica para procesar esa circunstancia, acción a la que "apuntaría" nuestro iframe.

En cualquier caso, dedicar una o varias acciones a procesar peticiones Ajax puede ser un buen planteamiento aunque depende de los casos y de lo "DRY" que quieras mantener el código.

Ajax y Auth

Otro problema que puede aparecer cuando utilizamos peticiones Ajax es que se produce un problema relacionado con el funcionamiento de AuthComponent y el comportamiento de algunos navegadores.

Si una acción requiere que el usuario esté autentificado, la petición Ajax falla porque Auth no reconoce que el usuario está autentificado y redirige al login.

Para dar como autentificado a un usuario, AuthComponent se basa no sólo en los datos de login almacenado en la sesión, sino que en cada recarga comprueba que la sesión no ha sido "secuestrada" por un nuevo agente de usuario. Si observas el contenido de $_SESSION en una aplicación CakePHP verás una variable Agent que identifica al navegador, junto con las demás variables.

En varios navegadores XHttpRequest genera una nueva identificación de agente lo que provoca que CakePHP considere inválida la información de autentificación almacenada en la sesión y vuelva a solicitar al usuario que se conecte mostrando la vista de login.

Una posible solución es poner en false la variable de configuración "Session.checkAgent" en core.php. También puedes deshabilitar este chequeo de forma temporal en la propia petición sólo si esta se hace a través de Ajax y antes de que Auth entre en acción. En AppController puedes poner:


function beforeFilter() {
    parent::beforeFilter();
    if ($this->RequestHandler->isAjax()) {
        Configure::write('Session.checkAgent', false);
    }
    // debug($this->params);
    $this->Auth->allow('display');
}


Otra técnica consiste en reiniciar a mano la sesión.

Respuestas Ajax

Las respuestas Ajax también pueden necesitar un pequeño ajuste antes de ser enviadas de vuelta al cliente.

Si la respuesta generada y esperada es HTML no tendrás que hacer nada especial, pero si la respuesta es JSON es posible que debas enviar una cabecera adecuada para que el navegador se entere de que se trata de JSON y no de otra cosa. Además, debes evitar que intente descargarlo, cosa que ocurre si envías lo que parece la cabecera propia de JSON: application/json.

En mi caso, la cabecera que ha funcionado es application/javascript.

Para ello, podemos utilizar también RequestHandler, indicando que vamos a enviar Javascript.


$this->RequestHandler->respondAs('js');


(Me surge ahora la dudo de si esto es lo que se considera Literal Javascript).

Debug

En muchos casos, aunque no necesariamente en todos, tendrás que poner el modo de debug de CakePHP a cero para que la respuesta Ajax sea aceptable. De otro modo, la información de debug puede hacer irreconocible la respuesta para el lado del cliente (no suele dar problemas si es una respuesta HTML que vaya a mostrarse en un elmento), pero seguro que los dará si es JSON o XML, que son formatos bien estructurados.

Simplemente, añade la línea


Configure::write('debug',0);


en el código que desarrolla la respuesta Ajax.

En estos casos, lo mejor es que hagas un log con la información que necesites para depuración.

Herramientas

Para comprobar el buen funcionamiento de tu código Javascript asegúrate de instalar FireBug en Firefox o activar las herramientas de Desarrollo en Safari (y otros navegadores basados en Webkit, como Chrome).

En ambos podrás estudiar el funcionamiento del código Javascript (marcando puntos de parada, inspeccionando variables y objetos...), así como las solicitudes y respuestas Ajax, lo cual también te ayudará a comprender cómo funcionan las cosas.

2 comentarios:

Manuel dijo...

Me ha sido de mucha utilidad este post, pero... sabes alguna forma de integrar facilmente cakephp con extjs? llevo semanas investigando por mi cuenta cómo hacerlo para mantener una estructura lógica en la aplicación pero... no hay manera.

Gracias de antemano.

Frankie dijo...

No conozco extjs como para dar una respuesta, pero una idea que he visto trabajando con jQuery y CakePHP es que recomiendan organizar los archivos javascript reproduciendo la estructura de vistas. Es decir que si tienes un script para app/views/posts/edit.ctp, lo pongas en app/webroot/js/posts_edit.js o incluso app/webroot/js/posts/edit.js