Calendario de symfony día diez: Alterar datos con formularios AJAX =========================================================== Previamente en symfony ---------------------- Después de la revisión de técnicas conocidas de ayer, algunos de Uds. tienen necesidad de interacción. Mostrar preguntas con formato enriquecido y listas, incluso paginadas, no es suficiente para hacer una aplicación viva. Y el corazón del concepto askeet es permitir a cualquier usuario registrado que formule una nueva pregunta, y a cualquier usuario que responda una existente. No es tiempo que lleguemos a esto? Agregar una nueva pregunta -------------------------- La barra de navegación construida durante [día siete](7.txt) ya contiene un enlace para agregar nuevas pregunta. Enlaza a la acción `question/add`, que esta esperando por ser desarrollada. ### Restringir el acceso a usuarios registrados Primero de todo, solo usuarios registrados pueden agregar una nueva pregunta. Para restringir el acceso a la acción `question/add`, crea un `security.yml` en el directorio `askeet/apps/frontend/modules/question/config/`: add: is_secure: on credentials: subscriber all: is_secure: off Cuando un usuario no registrado intente acceder a una acción restringido, symfony redirecciona el/ella a la acción login. Esta debe ser definida en la aplicación `settings.yml` bajo las claves `login_module` y `login_action`: all: .actions: login_module: user login_action: login Más información acerca de restringir el acceso a acciones puede ser encontrada en el [capítulo de seguridad](http://www.symfony-project.com/content/book/page/security.html) de el libro de symfony. ### La plantilla `addSuccess.php` La acción `question/add` será utilizada para ambos, mostrar el formulario y manejar el formulario. Esto significa que, para mostrar el formulario, solo necesitas un acción vacía. Adicionalmente, el formulario será mostrado nuevamente en caso de error en la validación de datos: [php] public function executeAdd() { } public function handleErrorAdd() { return sfView::SUCCESS; } Ambas acciones mostrarán la plantilla `addSuccess.php`: [php]
get('title')) ?>
get('body')) ?>
Tanto el control `title` y `body` tienen un valor por defecto (el segundo argumento del helper de formulario) definido por el parámetro de la petición del mismo nombre. Porqué es esto? Porque vamos a agregar un archivo de validación al formulario. Si la validación falla, el formulario es mostrado nuevamente, y las entradas previas del usuario aun están en los parámetros de la petición. Pueden ser utilizados como los valores por defecto de los elementos del formulario. ![error en el formulario con entradas previas mantenidas](/images/askeet/add_question_error.gif) Las entradas anteriores no se perdieron en caso de que falle la validación de formularios. Esto es lo ultimo que puede esperar de una aplicación amistosa-con-el-usuario. Pero, para lograr esto, necesitas un archivo de validación de formulario ### Validación de Formularios Crea un directorio `validation/` en el modulo `question`, y agregue en un archivo de validación `add.yml`: methods: post: [title, body] names: title: required: Yes required_msg: You must give a title to your question body: required: Yes required_msg: You must provide a brief context for your question validators: bodyValidator bodyValidator: class: sfStringValidator param: min: 10 min_error: Please, give some more details Si necesita más información acerca de la validación, dirijase al [día seis](6.txt) o lea el [capítulo de validación de formularios](http://www.symfony-project.com/content/book/page/validate_form.html) del libro de symfony. ## Manejando el envío del formulario Ahora edite nuevamente la acción `question/add` para manejar el envío del formulario: [php] public function executeAdd() { if ($this->getRequest()->getMethod() == sfRequest::POST) { // create question $user = $this->getUser()->getSubscriber(); $question = new Question(); $question->setTitle($this->getRequestParameter('title')); $question->setBody($this->getRequestParameter('body')); $question->setUser($user); $question->save(); $user->isInterestedIn($question); return $this->redirect('@question?stripped_title='.$question->getStrippedTitle()); } } Recuerde que el método `->setTitle()` también establecerá el `stripped_title`, y el método `->setBody()` establecerá también el campo ``html_body`, porque sobreescrivimos estos métodos en la clase del modelo `Question.php`. Un usuario creando una pregunta declarar interés en el. Esto es para prevenir preguntas con interés 0, que sería my triste. El final de la acción contienen un `->redirect()` al detalle de una pregunta creada. La mayor ventaja sobre un `->forward()` es que si el usuario recarga la página de detalle después, el formulario no será enviado de nuevo. Además, el botón 'atrás' funciona como es esperado. Esa es una regla general: No debería terminar un envío de un formulario con una `->forward()`. Lo mejor es que la acción aun funciona para mostrar el formulario, esto es si la petición no esta en el modo POST. Se comportara exactamente como la acción vacía escrita previamente, retornando el valor por defecto `sfView::SUCCESS` que lanzara la plantilla `addSuccess.php`. No olvide crear el método `isInterestedIn()` en el modelo `User`: public function isInterestedIn($question) { $interest = new Interest(); $interest->setQuestion($question); $interest->setUserId($this->getId()); $interest->save(); } Como una menor refactorización, puede utilizar este método en la acción `user/interested` para remplazar el código que hace lo mismo. Adelantese, pruébelo ahora. Utilizando uno de los usuarios de prueba, puede agregar una pregunta. Agregue una nueva pregunta -------------------------- La adición de la pregunta, será implementada de un forma ligeramente diferente. No hay necesidad de redirigir al usuario a una nueva página con el formulario, entonces a otra página nuevamente para que la respuesta sea mostrada. Así que el formulario de respuestas será AJAX, y la nueva respuesta aparecerá inmediatamente en la página de detalles de la pregunta. ### Agregue el formulario AJAX Cambie le final de la plantilla `modules/question/template/showSuccess.php` por: [php] ...
getAnswers() as $answer): ?>
$answer)) ?>
'@add_answer', 'update' => array('success' => 'add_answer'), 'loading' => "Element.show('indicator')", 'complete' => "Element.hide('indicator');".visual_effect('highlight', 'add_answer'), )) ?>
isAuthenticated()): ?> getNickname() ?>
get('body')) ?>
getId()) ?>
### Un poco de refactorización La función `link_to_login()` debe ser agregada al helper `UserHelper.php`: [php] function link_to_login($name, $uri = null) { if ($uri && sfContext::getInstance()->getUser()->isAuthenticated()) { return link_to($name, $uri); } else { return link_to_function($name, visual_effect('blind_down', 'login', array('duration' => 0.5))); } } Esta función hace algo que ya hemos visto en otros `User` helpers: muestra un enlace al formulario AJAX de login. Así que remplace las llamadas `link_to_function()` en `link_to_user_interested()` y `link_to_user_relevancy()` por llamadas a `link_to_login()`. No olvide el enlace a `@add_question` en el `modules/sidebar/templates/_default.php`. Si, esto es refactorización. ### Manejando el envío del formulario Incluso si aun involucra un fragmento, el método elige manejar el AJAX de una forma ligeramente diferente del descrito en el [día ocho](8.txt). Esto es porque queremos que el resultado del envío del formulario remplace el formulario. Es por eso que el parámetro `update` del helper `form_remote_tag()` apunta al contenedor del mismo formulario, en lugar de otra zona. El fragmento `_answer.php` será incluido en el resultado de la respuesta de la acción de adición, así que el resultado final luce como sigue: [php] ...
...
Probablemente haya adivinado como el helper javascript `form_remote_tag()` funciona: Maneja el envío del formulario a la acción especificada en el argumento `url` mediante un objeto XMLHttpRequest. El resultado de la acción remplaza el elemento especificado en el argumento `updated`. Y, y justo como el ayudante `link_to_remote()` del [día ocho](8.txt), que cambia la visibilidad del indicador de actividad entre encendido y apagado de acuerdo con el envío de petición y remarca la parte actualizada al final de la transacción AJAX. Agregaremos algunas palabras acerca del usuario asociado a la nueva pregunta. Previamente hemos mencionado que las respuestas deben estar relacionadas a un usuario. Si el usuario esta autentificado, entonces su `user_id` es utilizado para la nueva respuesta. En otro caso, en otro caso el usuario `anonymous` es utilizado, excepto que el usuario elija loguearse entonces. El helper `link_to_login()`, ubicado en el helper en `GlobalHelper.php`, cambie la visibilidad del formulario oculto en el layout. Navegue el código askeet para ver su código. ### La acción `answer/add` La regla `@add_answer` dada como el argumento `url` del formulario AJAX apunta a la acción `answer/add`: add_answer: url: /add_anwser param: { module: answer, action: add } (En caso de que se pregunta, esta configuración debe ser agregada al archivo de configuración de la aplicación `routing.yml`) Aquí esta el contenido de la acción: [php] public function executeAdd() { if ($this->getRequest()->getMethod() == sfRequest::POST) { if (!$this->getRequestParameter('body')) { return sfView::NONE; } $question = QuestionPeer::retrieveByPk($this->getRequestParameter('question_id')); $this->forward404Unless($question); // user or anonymous coward $user = $this->getUser()->isAuthenticated() ? $this->getUser()->getSubscriber() : UserPeer::retriveByNickname('anonymous'); // create answer $this->answer = new Answer(); $this->answer->setQuestion($question); $this->answer->setBody($this->getRequestParameter('body')); $this->answer->setUser($user); $this->answer->save(); return sfView::SUCCESS; } $this->forward404(); } Primero que nada, esta acción no se llama en modo POST, esto significa que alguien escribió la URI en la barra de direcciones del navegador. La acción no esta diseñada para ese tipo de peticiones (hacker), así que devuelve un error 404 en ese caso. Para determinar el usuario a establecer como el autor de la respuesta, la acción comprueba si el usuario actual está autenticado. Si no es el caso, la acción utiliza el usuario 'Anonymous Coward' (covarde anónimo), gracias al nuevo método `::retrieveByNickName()` de la clase `UserPeer`. Comprueva el código si tienes alguna duda acerca de lo que hace este método. Después de eso, todo está listo para crear la nueva pregunta y pasar la respuesta a la plantilla `addSuccess.php`. Como era de esperar, esta plantilla contiene sólo una linea, el `include_partial`: [php] $answer)) ?> También necesitamos desactivar el layout para esta acción en `frontend/modules/answer/config/view.yml`: addSuccess: has_layout: off Por último, si el usuario envía una respuesta vacía, no queremos guardarla. Así que la parte de manejo de datos es ignorada, y la acción no devuelve nada - esto simplemente borrara el formulario de la página. Podríamos haber realizado un manajeador de errores en este formulario AJAX, pero esto implicaría poner el formulario en otro fragmento. Esto no vale el esfuerzo por ahora. ### Pruébelo Es eso todo? Si, el formulario AJAX este listo para ser utilizado, limpio y seguro. Pruébelo mostrando las listas de respuestas a una pregunta y agregando una nueva respuesta a ella. La página no necesita refrescarse, y la nueva respuesta aparece al final de la lista de respuestas previas. Eso fue simple, no es así? Nos vemos mañana -------------- Formulario clásico y AJAX son igualmente fáciles de implementar en una aplicación. Y con estas dos adiciones, la aplicación askeet tiene todas sus características requeridas para hacerla funcionar. Una cosa más: No hemos detallado el modo de registrar un nuevo usuario. Esta característica fue agregada al [repositorio SVN askeet](http://svn.askeet.com/tags/release_day_10/) actual de cualquier forma, pues es muy similar a lo que ha sido hecho hoy. Así que diez días es todo lo que tomo construir una versión (muy) beta de un FAQ mejorado-con-AJAX con symfony. No obstante, queremos que askeet sea más que eso. Para ayudar construir la comunidad askeet, necesitamos que el sitio distribuya canales de sindicación, para que las personas haciendo preguntas puedan registrase para recibir las respuestas en un agregador de canales. Eso será el tutorial de mañana. Algunos de Uds. ya han sugerido algunas ideas para el día 21. Expanda la lista o apoye las sugerencias de otros visitando el [el foro de askeet](http://www.symfony-project.com/forum/index.php/f/8/)