Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)



Este tutorial es parte de la serie Construyendo Tu Startup Con PHP en Envato Tuts+. En esta serie, te guío a través de lanzar una startup desde concepto hasta realidad usando mi app Planificador de Reuniones como un ejemplo de la vida real. Cada paso a lo largo del camino, liberaré el código de Planificador de Reuniones como ejemplos de código abierto de los cuales puedes aprender. También abordaré temas de negocios relacionados con startups conforme vayan surgiendo.
Introducción
En el tutorial de hoy, te guiaré a través del conjunto inicial de cambios exhaustivos para la interfaz de programación de reuniones. Mi meta era usar Ajax para hacer posible la programación de actividades sin referscar la página. Algunos de estos aspectos resultaron ser simples y otros bastante complicados. En este episodio, me enfocaré en las partes simples: cómo construir de manera básica peticiones Ajax UX en tu aplicación php basada en Yii.
En la parte dos, cubriré las cosas más difíciles--depurar Ajax y reinicializar widgets de Bootstrap después de la carga inicial de la página. También compartiré cómo usé la consola de desarrollador del navegador Google Chrome para ayudarme a identificar código roto.
Francamente, mientras que las actualizaciones iniciales resultaron bien, me encontré con muchos bloqueos y dificultades en que hubo momentos en donde pensé que tendría que abandonar mi meta para la liberación beta.
Extrañamente, hubo senderos de código que parecieran acercarme a la solución pero entonces me encontraba con un bloqueo insuperable--y tenía que volver a empezar desde cero con una nueva aproximación. Por último, fui capaz de completar exitosamente la programación completa por medio de Ajax para la liberación beta.
Sígueme hoy mientras te guío a través de la parte principal del trabajo.
Si no has probado Planificador de Reuniones aún, ve y programa tu primera reunión con las nuevas capacidades interactivas. Participo en la sección de comentarios de abajo, ¡así que dime que piensas! También puedes contactarme en Twitter @reifman. Estoy especialmente interesado si quieres sugerir nuevas características o temas para futuros tutoriales.
Como recordatorio, todo el código para Planificador de Reuniones está escrito en el Framework Yii2 para PHP. Si quisieras aprender más acerca de Yii2, revisa nuestra serie paralela Programando Con Yii2.
Coordinando la UX de Programación
Mi metal principal para esta etapa del producto fue implementar todas las características de programación clave y eliminar el refrescar la página requerido actualmente para editar el asunto, agregar participantes, agregar fechas, lugares y notas.
Antecedentes
Mientras que he construido algo de Ajax en el sitio antes, tuve algunas ideas sobre qué iría bien y qué sería difícil.
Únete a mi mientras te guío a través de los elementos iniciales de 'ajaxificar' la programación.
Editando el Asunto de la Reunión



Comencé editando el panel del asunto de la reunión porque consiste en un par de campos estáticos, un input y un textarea. Sin embargo, el campo de asunto usa un widget Typehead de jQuery. Los widgets pueden complicar las cosas porque necesitas poder inicializarlos con Ajax en mente.
En ese caso, pre-cargo el formulario de manera oculta y cargo la librería del widget junto con este. Realmente no tiene complejidad. En el siguiente episodio, verás que el widget Bootstrap Switch en la fecha en los paneles de fecha y lugar lo hacen mucho más difícil.
Pre-cargando Todo el JavaScript
Así que, para simplificar el 'ajaxificar' cada panel de programación (participantes, asunto, fechas, lugares y notas), los cargaría por adelantado y expandiría el contenido inicial de meeting.js.
También expandí la definición MeetingAsset.php para incluír más JavaScript:
<?php namespace frontend\assets; use yii\web\AssetBundle; class MeetingAsset extends AssetBundle { public $basePath = '@webroot'; public $baseUrl = '@web'; public $css = [ 'css/bootstrap-combobox.css', ]; public $js = [ 'js/meeting.js', 'js/meeting_time.js', 'js/jstz.min.js', 'js/bootstrap-combobox.js', 'js/create_place.js', ]; public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', ]; }
Meetingasset es cargado por el archivo view.php de la Reunión:
<?php use yii\helpers\BaseHtml; use yii\helpers\Html; use yii\widgets\DetailView; use yii\widgets\ListView; use common\components\MiscHelpers; use frontend\assets\MeetingAsset; MeetingAsset::register($this);
Cargando el Panel de Asunto
El asunto y los detalles de la reunión son parte del parcial _panel_what.php. Abajo, lo establecí para ser oculto en la carga dentro de #editWhat
:
<?php if ($model->has_subject || $model->subject == \frontend\models\Meeting::DEFAULT_SUBJECT) { ?> <div id="collapseWhat" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingWhat"> <div class="panel-body"> <div id="showWhat"> <?php if (empty($model->message)) { echo Html::encode($this->title); // note: required because couldn't prevent extra space } else { echo Html::encode($this->title).': '.Html::encode($model->message).' '; } ?> </div> <div id="editWhat" class="hidden"> <?= $this->render('_form', [ 'model' => $model, 'subjects' => $model->defaultSubjectList(), ]) ?> </div> </div> </div> <?php } else { ?>
Enganché el botón de editar (con icono de lápiz) en _panel_what.php para llamar una función toggle JavaScript showWhat()
para mostrar u ocultar el formulario de edición. Aquí está ese código:
<?php if ($model->isOrganizer() && $model->status <= Meeting::STATUS_CONFIRMED) { //['update', 'id' => $model->id] echo Html::a('', 'javascript:void(0);', ['class' => 'btn btn-primary glyphicon glyphicon-pencil', 'title'=>'Edit','onclick'=>'showWhat();']); } ?>
La función showWhat()
de meeting.js se muestra abajo.
// show the message at top of what subject panel function showWhat() { if ($('#showWhat').hasClass( "hidden")) { $('#showWhat').removeClass("hidden"); $('#editWhat').addClass("hidden"); }else { $('#showWhat').addClass("hidden"); $('#editWhat').removeClass("hidden"); $('#meeting-subject').select(); } }; function cancelWhat() { showWhat(); }
Aquí está la parte superior de /frontend/views/meeting/_form.php que se oculta y muestra. Aquí es donde aparencen los campos input y textarea:
<?php use yii\helpers\Html; use yii\widgets\ActiveForm; use \kartik\typeahead\TypeaheadBasic; use common\components\MiscHelpers; /* @var $this yii\web\View */ /* @var $model frontend\models\Meeting */ /* @var $form yii\widgets\ActiveForm */ ?> <div class="meeting-form"> <?php $form = ActiveForm::begin(); ?> <div class="row"> <div class="col-md-6"> <?php echo $form->field($model, 'subject')->widget(TypeaheadBasic::classname(), [ 'data' => $subjects, 'options' => ['placeholder' => Yii::t('frontend','what\'s the subject of this meeting?'), 'id'=>'meeting-subject', //'class'=>'input-large form-control' ], 'pluginOptions' => ['highlight'=>true], ]); ?> </div> </div>
Actualizando el Asunto y los Detalles de la Reunión vía Ajax
Cuando el usuario actualiza el formulario de reunión, el siguiente Ajax es llamado:
// meeting subject panel function updateWhat(id) { // ajax submit subject and message $.ajax({ url: $('#url_prefix').val()+'/meeting/updatewhat', data: {id: id, subject: $('#meeting-subject').val(), message: $('#meeting-message').val() }, success: function(data) { $('#showWhat').text($('#meeting-subject').val()); showWhat(); } }); }
La función actionUpdatewhat
está dentro de MeetingController.php:
public function actionUpdatewhat($id,$subject='',$message='') { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; if (!Meeting::isAttendee($id,Yii::$app->user->getId())) { return false; } $m=Meeting::findOne($id); $m->subject = $subject; $m->message = $message; $m->update(); return true; }
Nota Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
el cuál configura un método Yii para regresar JSON, no HTML.
También, la función Meeting:isAttendee()
es una comprobación de autentificación para asegurarse de que el usuario con sesión iniciada es un participante antes de actualizar los datos de la reunión.
Lo Que He Aprendido Hasta Ahora
Como puedes ver, toma algo de código 'ajaxificar' estas piezas.
Uno de los retos es ser un humano tratando de cambiar entre tantos archivos al mismo tiempo y dos lenguajes diferentes. PHP y JavaScript tienen dos maneras diferentes de hacer las cosas. Por ejemplo, concatenar cadenas de texto se hace con un punto en PHP y un signo de suma en JavaScript. Cambiar rápidamente entre lenguajes ocasionalmente tratando de construir cadenas de consulta de parámetros puede llevar a errores.
También está la necesidad de comprobaciones intensivas de seguridad dentro de funciones de Ajax basasas en PHP. En el tutorial de hoy, ves el comienzo de esto, pero tendré que agregar más comprobaciones exhaustivas antes de tener el código completamente funcional.
Y finalmente, según avancé, intenté reusar notaciones y aproximaciones estructurales para que todo el código tuviera una composición y terminología similar--a pesar de trabajar con diferentes elementos de datos.
Enviando Notas de Reuniones



Las notas de reuniones también son campos textarea estéticos. Sin embargo, puede haber un hilo de notas en marcha que necesita ser actualizado en la página cuando una es agregada (ej. no solo un asunto de reunión). Y es importante decirle al usuario que notificaremos a los participantes acerca de la nota.
Por ejemplo, he eliminado el UX del botón enviar en la programación para que la planeación sea rápida y eficiente. Usuarios nuevos de Planificador de Reuniones están confundidos regularmente por esto así que he agregado alertas para hacerles saber que nos encargaremos de ello.



Codificando las Notas vía Ajax
Primero, está el _panel.php para la nota de la Reunión. Pre-construí alertas de error ocultas que pueden ser mostradas vía jQuery según se necesiten. Planeo simplificar y estandarizar esto en el futuro, incluyendo hacer más fácil usar localización para los mensajes.
En el ejemplo de abajo, ambos noteMessage1
y noteMessage2
son cargados como ocultos.
<?php use yii\helpers\Html; use yii\bootstrap\Collapse; ?> <div id="noteMessage" class="alert-info alert fade in hidden"> <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> <span id="noteMessage1"> <?= Yii::t('frontend',"Thanks for your note. We'll automatically share it with other participants.")?></span> <span id="noteMessage2"> <?= Yii::t('frontend','Please be sure to type a note.')?></span> </div> <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading" role="tab" id="headingNote" > <div class="row"> <div class="col-lg-10 col-md-10 col-xs-10"><h4 class="meeting-view"> <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseNote" aria-expanded="true" aria-controls="collapseNote"><?= Yii::t('frontend','Notes') ?></a></h4> <span class="hint-text"><?= Yii::t('frontend','send a message to others') ?></span> </div> <div class="col-lg-2 col-md-2 col-xs-2" > <div style="float:right;"> <?= Html::a('', 'javascript:void(0);', ['class' => 'btn btn-primary glyphicon glyphicon-plus', 'title'=>'Edit','onclick'=>'showNote();']); ?> </div> </div> </div> </div> <div id="collapseNote" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingNote"> <div class="panel-body nopadding"> <div id="editNote" class="hidden"> <?= $this->render('_form', [ 'model' => $model, ]) ?> </div> </div> <div id ="noteThread" class="nopadding"> <?= $this->render('_thread', [ 'model' => $model, 'noteProvider'=>$noteProvider, ]) ?> </div> </div> </div>
Aquí está el jQuery que busca una nota en blanco, muestra un error apropiado o envía el contenido vía Ajax para pedir una actualización de hilo de notas y muestra una alerta de éxito:
function updateNote(id) { note = $('#meeting-note').val(); if (note =='') { displayAlert('noteMessage','noteMessage2'); return false; } // ajax submit subject and message $.ajax({ url: $('#url_prefix').val()+'/meeting-note/updatenote', data: {id: id, note: note}, success: function(data) { $('#editNote').addClass("hidden"); $('#meeting-note').val(''); updateNoteThread(id); displayAlert('noteMessage','noteMessage1'); return true; } // to do - error display flash }); } function updateNoteThread(id) { // ajax submit subject and message $.ajax({ url: $('#url_prefix').val()+'/meeting-note/updatethread', data: {id: id}, type: 'GET', success: function(data){ $('#noteThread').html(data); // data['responseText'] }, error: function(error){ } }); }
Una de las tareas por hacer ahí es manejar errores en Ajax. No es fácil hacer esto y requiere una arquitectura bastante detallada en todos lados para soportar esto--por ahora, he continuado sin manejar este tipo de error.
Aquí está la función JavaScript displayAlert()
que he reutilizado y construido sobre los diversos paneles y sus mensajes de alerta:
function displayAlert(alert_id,msg_id) { // which alert box i.e. which panel alert switch (alert_id) { case 'noteMessage': // which msg to display switch (msg_id) { case 'noteMessage1': $('#noteMessage1').removeClass('hidden'); // will share the note $('#noteMessage2').addClass('hidden'); $('#noteMessage').removeClass('hidden').addClass('alert-info').removeClass('alert-danger'); break; case 'noteMessage2': $('#noteMessage1').addClass('hidden'); $('#noteMessage2').removeClass('hidden'); // no note $('#noteMessage').removeClass('hidden').removeClass('alert-info').addClass('alert-danger'); break; } break; case 'participantMessage': // which msg to display $('#participantMessageTell').addClass('hidden'); // will share the note $('#participantMessageError').addClass('hidden'); $('#participantMessageOnlyOne').addClass("hidden"); $('#participantMessageNoEmail').addClass("hidden"); switch (msg_id) { case 'participantMessageTell': $('#participantMessageTell').removeClass('hidden'); // will share the note $('#participantMessage').removeClass('hidden').addClass('alert-info').removeClass('alert-danger'); break; case 'participantMessageError': $('#participantMessageError').removeClass("hidden"); $('#participantMessage').removeClass("hidden").removeClass('alert-info').addClass('alert-danger'); break; case 'participantMessageNoEmail': $('#participantMessageNoEmail').removeClass("hidden"); $('#participantMessage').removeClass("hidden").removeClass('alert-info').addClass('alert-danger'); break; case 'participantMessageOnlyOne': $('#participantMessageOnlyOne').removeClass("hidden"); $('#participantMessage').removeClass("hidden").removeClass('alert-info').addClass('alert-danger'); break; } break; case 'placeMessage': // which msg to display $('#placeMsg1').addClass('hidden'); // will share the note $('#placeMsg2').addClass('hidden'); // will share the note $('#placeMsg3').addClass('hidden'); // will share the note switch (msg_id) { case 'placeMsg1': $('#placeMsg1').removeClass('hidden'); // will share the note $('#placeMessage').removeClass('hidden').addClass('alert-info').removeClass('alert-danger'); break; case 'placeMsg2': $('#placeMsg2').removeClass('hidden'); // will share the note $('#placeMessage').removeClass('hidden').removeClass('alert-info').addClass('alert-danger'); break; } break; case 'timeMessage': // which msg to display $('#timeMsg1').addClass('hidden'); // will share the note $('#timeMsg2').addClass('hidden'); // will share the note //$('#timeMsg3').addClass('hidden'); // will share the note switch (msg_id) { case 'timeMsg1': $('#timeMsg1').removeClass('hidden'); // will share the note $('#timeMessage').removeClass('hidden').addClass('alert-info').removeClass('alert-danger'); break; case 'timeMsg2': $('#timeMsg2').removeClass('hidden'); // will share the note $('#timeMessage').removeClass('hidden').removeClass('alert-info').addClass('alert-danger'); break; } break; } }
Actualizando los Hilos de Notas
Cuando un usuario envía una nueva nota, MeetingNoteController.php actionUpdateThread()
es llamado vía Ajax. Aquí está el PHP:
public function actionUpdatethread($id) { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; $m=Meeting::findOne($id); $noteProvider = new ActiveDataProvider([ 'query' => MeetingNote::find()->where(['meeting_id'=>$id]), 'sort'=> ['defaultOrder' => ['created_at'=>SORT_DESC]], ]); $result = $this->renderPartial('_thread', [ 'model' =>$m, 'noteProvider' => $noteProvider, ]); return $result; }
Algunas veces experimenté simplemente regresando el último objeto de contenido (ej. la nota más nueva) e insertando antes de los objetos previos. Sin embargo, esto resulto problemático, especialmente con objetos de tiempos y lugares que aparecieron en filas de tabla.
Por ahora, reemplazo el hilo de contenido actualizado completo y reemplazo el panel completo vía Ajax. Aquí está el parcial _thread.php que carga todas las notas, incluyendo la nueva:
<?php use yii\widgets\ListView; use yii\helpers\Html; if ($timeProvider->count>0): ?> <table class="table"> <?= ListView::widget([ 'dataProvider' => $timeProvider, 'layout' => '{items}', 'itemView' => '_list', ]) ?> </table> <?php endif; ?>
Espero que eso sea suficiente para estudiar y probar por hoy.
Literalmente pasé alrededor de cinco a siete largos días de codificar incluyendo una demañanada para completar todo el código detrás de este episodio y el siguiente. No me había desvelado en años. Aún así, los resultados han sido inspiradores.
Así qué, ¿Qué Sigue?
Espero haya sido de ayuda para ver los básico del desarrollo Ajax para Yii y PHP. Ciertamente aprendí mucho durante este proceso y los cambios han hecho programar reuniones increíblemente más rapido y fácil que antes.
En el siguiente episodio, cubriré las características restantes agregando fechas y lugares y el uso de las herramientas de depuración del navegador Google Chrome para asistirme.
Mientras esperas por el siguiente episodio, programa tu primera reunión y pruba las características de programación de Ajax. También, apreciaría si compartes tu experiencia abajo en los comentarios y siempre estoy interesado en tus sugerencias. También puedes contactarme directamente en Twitter @reifman.
La versión de vista previa de Planificador de Reuniones ya está disponible. Ya puedes compartirlo con tus amigos y colegas para que lo usen.
Finalmente, estoy comenzando a experimentar con WeFunder basado en la implementación de las nuevas reglas de crowdfunding de SEC. También puedes seguir nuestro perfíl ahí si quisieras. También escribiré más acerca de esto en un tutorial futuro.
Espera tutoriales por venir en la serie Construyendo Tu Starup Con PHP.
Enlaces Relacionados
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weekly