Calendario de syfmony día ocho: Interacciones AJAX ==================================================== Previamente en symfony --------------------- Después de siete horas de trabajo, la aplicación askeet ha avanzado bien. La página de inicio muestra una lista de preguntas, el detalle de una pregunta muestra sus respuestas, los usuarios tienen una página de perfil, y una lista temática esta disponible para cada página en la **sidebar**. Nuestro FAQ mejorado-por-la-comunidad esta en la dirección correcta (mira la lista de acciones disponibles para [ayer](7.txt)), y el usuario aun no puede alterar su propia información por ahora. Si la base para la manipulación de datos en la web han sido por mucho tiempo los formularios, hoy técnicas AJAX y mejoras en la usabilidad pueden cambiar la forma en una aplicación es construida. Y eso aplica a askeet, también. Este tutorial le mostrara como agregar interacciones mejoradas-por-AJAX a askeet. El objetivo es permitir al usuario registrado declarar interés en una pregunta. Agregar un indicador en el layout --------------------------------- Mientras una petición asíncrona está pendiente, los usuario de website proveídos de AJAX no tiene ninguna de las pistas usuales que su acción fue tomada en cuenta y que el resultado será pronto mostrado. Esta es la razón por la cual cada página conteniendo interacciones AJAX debería ser capaz de mostrar un indicador de actividad. Para este propósito, agregue arriba de `` en el `layout.php` global: [php] Aunque oculto por defecto, este `>div>` será mostrado cuando una petición AJAX este pendiente. Esta vacío, pero la hoja de estilo `main.cc` (ubicada en el directorio `askeet/web/css`) le da forma y contenido: [css] div#indicator { position: absolute; width: 100px; height: 40px; left: 10px; top: 10px; z-index: 900; background: url(/images/indicator.gif) no-repeat 0 0; } ![indicador de actividad](/images/askeet/indicator.gif) Agregar una interacción AJAX para declarar interés --------------------------------------------------- Una interacción ajax esta compuesta por tres partes: un llamador (un enlace, un botón o cualquier control que el usuario manipula para lanzar la acción), una acción en el servidor, y una zona en la página para mostrar el resultado de la acción al usuario. ### Llamador Vayamos de nuevo a la muestra de la pregunta. Si recuerdas el [día cuatro](4.txt), una pregunta puede ser mostrada en la lista de preguntas y en los detalles de una pregunta. ![lista de preguntas](/images/askeet/pager_navigation_day7.gif) Es por eso que el código para el titulo de la pregunta y el bloque interés fue refactorizado en el fragmento `_interested_user.php`. Abre este fragmento nuevamente, y agregar un enlace para permitir a los usuario declarar sus intereses: [php]
getInterestedUsers() ?>
Este enlace va a hacer más que simplemente redireccionar hacia otra página. De hecho si ya declaro interés acerca de la pregunta, el/ella debe poder declararlo de nuevo. Y si el usuario no se encuentra autenticado... bueno, veremos este caso más tarde. El enlace esta escrito en una función helper, que necesita ser creada en `askeet/apps/frontend/lib/helper/UserHelper.php`: [php] isAuthenticated()) { $interested = InterestPeer::retrieveByPk($question->getId(), $user->getSubscriberId()); if ($interested) { // already interested return 'interested!'; } else { // didn't declare interest yet return link_to_remote('interested?', array( 'url' => 'user/interested?id='.$question->getId(), 'update' => array('success' => 'block_'.$question->getId()), 'loading' => "Element.show('indicator')", 'complete' => "Element.hide('indicator');".visual_effect('highlight', 'mark_'.$question->getId()), )); } } else { return link_to('interested?', 'user/login'); } } ?> La función `link_to_remote` es el primer componente de una interacción AJAX: El llamador. Declara cual es la acción debe ser llamada cuando el usuario cliquea en el enlace (aquí: `user/interested`) y cual zona de la página necesita ser actualizada con el resultado de la acción (aquí: el elemento de id `block_XX`). Dos manejadores de eventos (`loading` y `complete`) son agregados y asociados a las funciones javascript de [prototype](http://prototype.conio.net/). La librería prototype ofrece herramientas javascript muy útiles para aplicar efectos visuales en una página web con un simple llamado función. Su único defecto es la falta de documentación, pero el código es bastante sencillo. Elegimos utilizar un helper en lugar de un parcial debido que esta función contiene mucho más código PHP que código HTML. No olvide agregar el id `id="block_getId() ?>"` al fragmento `question/_list`. [php]
$question)) ?>
>**Note**: Esto funciona solo si definió apropiadamente el alias `sf` en la configuración de su servidor web, como fue explicado durante el [día uno](1.txt). ### Zona de resultados. El atributo `update` del helper javascript `link_to_remote()` específica la zona de resultados. En este caso, el resultado de la acción `user/interested` remplazara el contenido del elemento de id `block_XX`. Si se encuentra confundido, mire lo que la integración del fragmento en la plantilla: [php] ...
getInterestedUsers() ?>
... La zona de resultado es la parte entre los dos comentarios. La acción, una vez ejecutada, remplazara este contenido. El interese del segundo id (`mark_XX`) es puramente visual. El manejador del evento `complete` del helper `link_to_remote` resalta el `
` `interested_mark` del interés clicqueado... después que la acción retorne e incremente el número de interés. ### Acción del Server El llamado AJAX apunta a la acción `user/interested`. Esta acción debe ser crear un nuevo registro en la tabla `Interest` para la pregunta actual y el usuario actual. Así es como se hace en symfony: [php] public function executeInterested() { $this->question = QuestionPeer::retrieveByPk($this->getRequestParameter('id')); $this->forward404Unless($this->question); $user = $this->getUser()->getSubscriber(); $interest = new Interest(); $interest->setQuestion($this->question); $interest->setUser($user); $interest->save(); } Recuerde que el método `->save()` del objeto `Interest` fue modificado para incrementar el campo `interested_user` del `User` relacionado. Así el número de usuarios interesados acerca de la pregunta actual será mágicamente incrementada en la pantalla después de la llamada a la acción. ¿Y que debería mostrar la plantilla `interestedSuccess.php` resultante? [php] $question)) ?> Muestra el fragmento `_interested_user.php` del módulo `question`. Este es el más grande interés de haber escrito este fragmento en primer lugar. También debemos desactivar el layout para esta plantilla (`modules/user/config/view.yml`): interestedSuccess: has_layout: off ### Pruebas finales El desarrollo del interés AJAX esta terminado. Puedes probarlo ingresando un login/password existente en la página de login, mostrando la lista de preguntas y luego clicqueando un enlace 'interested?'. El indicador aparece mientras la petición es enviada al servidor. Entonces, el numero es incrementado en remarcado cuando el servidor responde. Note que el enlace 'interested?' es ahora un texto 'interested!' sin enlace, gracias a nuestro helper `link_to_user_interested`: ![ajax](/images/askeet/ajax.gif) Si desea más ejemplos acerca de los helpers AJAX, puedes leer el [tutorial carrito de compra drag-and-drop](http://www.symfony-project.com/tutorial/symfony_ajax.html), mira el [screencast](http://downloads.symfony-project.com/demo/cart/cart.mov) asociado o lee el [capitulo relacionado del libro](http://www.symfony-project.com/content/book/page/javascript.html). Agregar un formulario de 'sign-in' ---------------------------------- Previamente mencionamos que sólo usuarios registrados pueden declarar interés sobre un pregunta. Esto significa que si un usuario-no-autorizado cliquea en un enlace 'interested?', la página de login debe ser mostrada primero. Pero espere. ¿Por que un usuario cargaría una nueva página para loguearse, y perder contacto con la pregunta que el/ella ha declarado interés? Una mejor idea sería tener un formulario de login en la página. Eso es lo que vamos a hacer. ### Agregar un formulario de login oculto al layout Abre el layout global (en `askeet/apps/frontend/templates/layout.php`), y agrega (entre el div `header` y el `content`): [php] Una vez más, este formulario esta escondido por defecto. La etiqueta `referer` contiene el parámetro `referer` de la petición si existe, o sino la actual URI. ### Hacer que el formulario aparezca cuando un usuario-no-autenticado cliqueee en un enlace interested? ¿Recuerdas el helper `User` que escribimos previamente? Ahora lidiaremos con el caso cuando el usuario no está autenticado. Abre nuevamente el archivo `askeet/lib/helper/UserHelper.php` y cambie la linea: [php] return link_to('interested?', 'user/login'); con está: [php] return link_to_function('interested?', visual_effect('blind_down', 'login', array('duration' => 0.5))); Cuando el usuario no esta autenticado, el enlace en la la palabra 'interested?' lanza un efecto javascript de prototype (`blind_down`) que revela el elemento de id `login` - y este es el formulario que acabamos de agregar al layout. ### Loguear al usuario La acción `user/login` ya fue escrita durante el quinto día, y refactorizado durante el día seis. ¿Debemos modificarlo nuevamente? [php] public function executeLogin() { if ($this->getRequest()->getMethod() != sfRequest::POST) { // display the form $this->getRequest()->getParameterHolder()->set('referer', $this->getRequest()->getReferer()); return sfView::SUCCESS; } else { // handle the form submission // redirect to last page return $this->redirect($this->getRequestParameter('referer', '@homepage')); } } Después de todo, no. Funciona perfectamente como esta, el manejo del referer, redireccionará al usuario a la página donde el/ella se encontraba cuando el enlace fue cliqueado. Prueba la funcionalidad AJAX ahora. Un usuario sin registrarse será presentado con un formulario de login sin dejar la página actual. Si el nickname y el password son reconocidos, la página se refrescara y el usuario será capaz de cliquear en el enlace 'interested?' que intentó clicar anteriormente. ![formulario de login revelado](/images/askeet/div_login_form.gif) >**Nota**: En muchas interacciones AJAX como esta, la plantilla del servidor es simplemente un `include_partial`. Esto es debido a que un resultado inicial es generalmente presentado cuando la página se presenta por primera vez, y porque la parte que es actualizada por la acción AJAX es parte de la plantilla inicial. Nos vemos mañana ------------- Lo más difícil en diseñar interacciones AJAX es definir apropiadamente el llamador, la acción del server, y la zona resultante. Una vez que los conoces, symfony provee los helpers que realizan el resto. Para estar seguro de que entiendes, mira como implementamos el mismo mecanismo para declarar interés para la declarar interés para la relevancia de un respuesta. Esta vez, la acción AJAX llamada es `user/vote`, el parcial es `_answer.php` es separado en dos partes (creando el parcial `_user_vote.php`), y dos helpers `link_to_user_relevancy_up()` y `link_to_user_relevancy_down` son creado en el helper `User`. El módulo `User` gano una acción `vote` y una plantilla `voteSuccess.php. No olvide de establecer el layout a `off` para esta plantilla también. Askeet comienza a verse como una aplicación web 2.0. Y es solo el comienzo: En unos días, le agregaremos algunas más interacciones AJAX. Mañana tomaremos la ocasión para hacer un repaso general de técnicas MVC en symfony, e implementar una librería externa. Si encuentra un problema mientras tratas de seguir el tutorial de hoy, puedes descargar el código completo etiquetado `release_day_8` desde el [repositorio SVN de askeet](http://svn.askeet.com/tags/release_day_8). Si no tienes ningún problema, ven al [foro askeet](http://www.symfony-project.com/forum/index.php/f/8/) para responder preguntas de otros.