本教程是 Envato Tuts +上的“ 使用PHP构建启动”系列 的一部分。 在本系列文章中,我 将以 我的 Meeting Planner 应用程序作为真实示例 ,指导您完成从概念到现实的启动 。 在此过程的每一步中,我都会将Meeting Planner代码作为开放源代码示例发布,您可以从中学习。 我还将解决与启动相关的业务问题。
介绍
在今天的教程中,我将指导您完成对会议安排界面的初始全面更改。 我的目标是使用Ajax使所有常见的计划活动都可以进行而无需刷新页面。 事实证明,其中某些方面很简单,而其他方面则非常复杂。 在这一集中,我将重点介绍简单的部分:如何在基于PHP的Yii应用程序中基本构造Ajax UX请求。
在第二部分中,我将介绍更困难的内容-在初始页面加载后调试Ajax并重新初始化Bootstrap小部件。 我还将分享如何使用Google的Chrome浏览器开发者控制台来帮助我识别损坏的代码。
坦率地说,尽管最初的更新进展顺利,但我遇到了许多障碍和困难,以至于有些时候我认为我可能不得不放弃Beta版的目标。
奇怪的是,有些代码路径似乎使我接近完成,然后遇到了无法克服的障碍,而我必须重新开始一种新方法。 最终,我能够通过Ajax成功完成Beta版的完整计划。
今天就跟随我,指导我完成工作的核心部分。
如果您还没有尝试过Meeting Planner ,请继续使用新的交互式功能安排您的第一次会议 。 我确实参与了下面的评论主题,所以请告诉我您的想法! 您也可以通过Twitter @reifman与我联系 。 如果您想为以后的教程提出新功能或主题,我特别感兴趣。
提醒一下,Meeting Planner的所有代码都是在PHP的Yii2框架中编写的。 如果您想了解有关Yii2的更多信息,请查看我们的平行系列“ 使用Yii2编程” 。
简化计划UX
对于产品的这一阶段,我的主要目标是使用Ajax实现所有关键的计划功能,并消除当前用于编辑主题,添加参与者,添加日期时间,位置和注释的页面刷新。
背景
当我较早地在站点中构建了一些Ajax时,我有一些想法,哪些会进展顺利,哪些会困难。
和我一起学习加速日程安排的开始要素。
编辑会议主题
我从编辑会议主题面板开始,因为它由几个静态字段,一个输入和一个文本区域组成。 但是,主题字段确实使用jQuery Typeahead小部件 。 小部件会使事情复杂化,因为您需要能够在考虑Ajax的情况下对其进行初始化。
在这种情况下,我将预加载隐藏的表单,并将其一起加载小部件库。 没有真正的复杂性。 在下一个情节中,您会看到日期时间和位置面板上的Bootstrap Switch小部件使此操作变得更加困难。
预加载所有JavaScript
因此,为了简化每个计划面板(参与者,主题,日期时间,位置和注释)的混淆,我将它们预先加载并扩展Meeting.js的初始内容。
我还扩展了MeetingAsset.php定义以包含更多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由会议view.php文件加载:
<?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);
加载主题面板
主题和会议详细信息是_panel_what.php部分的一部分。 在下面,我将其设置为隐藏在#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 {
?>
我在_panel_what.php中钩了编辑按钮(带有铅笔图标),以调用JavaScript showWhat()
切换功能来显示或隐藏编辑表单。 这是代码:
<?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();']);
}
?>
showWhat()
函数如下所示:
// 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();
}
这是隐藏和显示的/frontend/views/meeting/_form.php的顶部。 这是显示输入和文本区域的位置:
<?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>
通过Ajax更新主题和会议详细信息
当用户更新会议表单时,将调用以下Ajax:
// 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();
}
});
}
函数actionUpdatewhat
在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;
}
注意Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
它将Yii方法配置为返回JSON,而不是HTML。
另外, Meeting:isAttendee()
函数是一项身份验证检查,以确保登录的用户是与会者,然后再更新会议数据。
到目前为止我学到了什么
如您所见,将所有这些片段都进行邻接需要花费大量的代码。
挑战之一是人们试图同时在如此多的文件和两种不同的语言之间切换。 PHP和JavaScript具有不同的处理方式。 例如,使用PHP中的句点和JavaScript中的加号来完成字符串的连接。 偶尔尝试构造查询参数字符串的语言之间快速切换可能导致错误。
在基于PHP的Ajax函数中还需要进行严格的安全检查。 在今天的教程中,您会看到它的开始,但是在使代码完全生效之前,我将不得不添加更彻底的检查。
最后,随着我的前进,我尝试重用符号和结构化方法,以使所有代码都具有与之相似的组成和术语,尽管使用的是不同的数据元素。
发送会议记录
会议笔记也是静态textarea字段。 但是,添加注释时,可能需要在页面上进行不断更新的注释线程(例如,不只是一个会议主题)。 重要的是要告诉用户我们将通知参与者有关该笔记的信息。
例如,我在计划中取消了“提交”按钮UX,因此计划是快速而有效的。 新的会议计划者用户通常对此感到困惑,因此我添加了警报以使他们知道我们会处理。
通过Ajax编码注释
首先,有_panel.php作为会议记录。 我预先构建了隐藏的错误警报,可以根据需要通过jQuery显示这些警报。 我计划在将来对此进行简化和标准化,包括使其更易于使用消息的本地化。
在下面的示例中, noteMessage1
和noteMessage2
均被隐藏加载。
<?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>
这是查找空白便笺,显示适当的错误或通过Ajax提交内容以请求便笺线程更新并显示成功警报的jQuery:
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){
}
});
}
要做的事情之一是处理Ajax中的错误。 要做到这一点并不容易,并且需要到处都有相当详细的体系结构来支持这一点—目前,我一直在继续处理此类错误。
这是我为所有各种面板及其警报消息重用和构建的displayAlert()
JavaScript函数:
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;
}
}
更新注释线程
当用户提交新笔记时,会通过Ajax调用MeetingNoteController.php actionUpdatethread()
。 这是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;
}
我有时会尝试简单地返回最新的内容(即最新注释)并插入先前的内容上方。 但是,事实证明这很麻烦,尤其是在表行中显示日期时间和位置的情况下。
现在,我替换整个更新的内容线程,并通过Ajax替换整个面板。 这是_thread.php部分,用于加载所有注释,包括新注释:
<?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; ?>
我希望今天就足够学习和尝试。
实际上,我花了大约五到七个漫长的编码天,其中包括一个通宵达旦的工作,以完成本集和即将上映的这本书背后的所有代码。 我好几年没拉通宵了。 尽管如此,结果仍然令人鼓舞。
下一个是什么?
我希望对Yii和PHP的Ajax开发基础有所帮助。 通过这个过程,我当然学到了很多东西,并且这些更改使会议安排比以往更快,更容易。
在下一集中,我将介绍剩余的功能,添加日期时间和地点,以及使用Google Chrome浏览器调试工具为我提供帮助。
在等待下一个情节时, 安排您的第一次会议并尝试使用Ajax安排功能。 另外,如果您在下面的评论中分享您的经验,我也将不胜感激,并且我始终对您的建议感兴趣。 您也可以直接通过Twitter @reifman与我联系 。
Meeting Planner预览版现已发布。 现在可以与您的朋友和同事分享使用。
最后,基于SEC新的众筹规则的实施,我开始尝试WeFunder。 您可以根据需要在此处关注我们的个人资料 。 我还将在以后的教程中写更多有关此的内容。
在“ 用PHP构建您的启动”系列中观看即将发布的教程。