建立您的初创企业:与多个参与者开会

最终产品图片
您将要创造的

本教程是 Envato Tuts +上的“ 使用PHP构建启动”系列 的一部分。 在本系列文章中,我 将以 我的 Meeting Planner 应用程序作为真实示例 ,指导您完成从概念到现实的启动 在此过程的每一步中,我都会将Meeting Planner代码作为开放源代码示例发布,您可以从中学习。 我还将解决与启动相关的业务问题。

小组会议简介

与多个参与者安排会议始终是我计划的一部分,但不属于最早的最低可行产品(MVP)的一部分。 Meeting Planner的Alpha版本仅以1:1的计划启动。 支持小组日程安排的目标像登上珠穆朗玛峰一样位于任务列表上,目标是针对七个峰会的登山者(我什至都​​不是户外登山者)。

安排多个参与者会议是最具挑战性的,因此对于会议计划器产品而言非常有价值。 当Beta任务列表达到可以开始从事此工作的程度时,我感到很兴奋。

我从一开始就一直在计划,设计和编码小组会议。 我希望为此功能更新站点将不需要进行大量的UX更改或编码更新。 事实证明,这需要一条中间路线,需要7到10天的重点工作和测试,而无需进行重大重新架构。

实际上,事实证明,测试是构建此功能最困难的方面。 它还有助于揭示早期代码中的缺点。 只是这并不容易...发送到多个电子邮件地址,检查每个电子邮件地址都收到所有正确的通知,但没有收到错误的通知-并查看整个站点中所有正确的菜单选项。

在今天的教程中,我将介绍启用多个参与者,升级组的UX,任命组织者,删除参与者,以及根据参与者的受欢迎程度对日期,时间和地点选项进行排序。

在下一个教程中,我将描述其余的工作:复查受多个参与者会议影响的站点的所有区域,处理并智能显示各种状态的收件人列表,适当地管理组的通知和通知过滤,最后升级最近启动的请求会议更改功能

尝试安排小组会议

今天安排一个小组会议 ! 在下面的评论中分享您的想法和反馈。

我确实参与了讨论,但是您也可以在Twitter上与我联系@reifman 。 我总是乐于接受Meeting Planner的新功能建议以及有关未来系列剧集的建议。

提醒一下,Meeting Planner的所有代码都是开源提供的,并使用PHP的Yii2框架编写。 如果您想了解有关Yii2的更多信息,请查看我的平行系列“ 使用Yii2编程” 。 我听说过有关Laravel的很棒的事情,但是Yii2总是可以轻松快捷地满足我的需求。

回头看

当我第一次设计Meeting Planner计划界面时 ,它在其自己的列中显示了其他参与者的当前可用性。 由于存在禁用的控件,这有点令人困惑。

Meeting Planner Startup Series - The old You Them Availability Panel

当时,我担心如何腾出空间来显示群组的可用性。

幸运的是,当我重建UX以获得更好的响应体验时 ,我用一个小的文本摘要替换了参与者的可用性列:

Meeting Planner Startup Series - The newer built for mobile responsive planning view

可用性的文本摘要恰好适合小组会议。

通过首先针对移动设备进行重新设计,我解决了多次参加会议的最大用户体验障碍!

小组会议的编码

让我们开始遍历所有代码并测试多个参与者会议所需的内容。

允许多个参与者

Meeting Planner Startup Series - Who Panel - Enabled Button for More Participants

小组会议最有趣的方面是激活会议很简单。 我只需要在计划阶段关闭“ 谁”面板上的加号图标按钮的禁用功能即可:

<div class="col-lg-2 col-md-2 col-xs-2">
      <div style="float:right;">
        <?= Html::a(Yii::t('frontend', ''), ['/participant/create', 'meeting_id' => $model->id], ['class' => 'btn btn-primary '.($model->status>=$model::STATUS_CONFIRMED?'disabled':'').' glyphicon glyphicon-plus']) ?>
      </div>
    </div>

然后,我开始创建一个 参与者模型中的MEETING_LIMIT

class Participant extends \yii\db\ActiveRecord
{
    ...

    const MEETING_LIMIT = 15;

它在提交时用于ParticipantController::actionCreate()中:

public function actionCreate($meeting_id)
 {
    if (!Participant::withinLimit($meeting_id)) {
       Yii::$app->getSession()->setFlash('error', Yii::t('frontend','Sorry, you have reached the maximum number of participants per meeting. Contact support if you need additional help or want to offer feedback.'));
       return $this->redirect(['/meeting/view', 'id' => $meeting_id]);
    }

推进用户体验和相关功能

很长一段时间以来,我一直希望允许会议组织者删除参与者,地点和日期时间,而又不会使用户界面混乱。 同样,我意识到可能会有多个命令要对参与者执行。

在“ 高级命令”教程的紧凑型Bootstrap下拉按钮中找到了很多实用程序之后,我决定将其用于显示会议与会者:

Meeting Planner Startup Series - New Buttons and Dropdown Menu for Organizers

组织者以星号表示。 拒绝参加会议的与会者以橙色显示。 组织者删除的与会者以红色显示。

这是我新的部分/frontend/views/participant/_buttons.php中的代码:

<div class="btn-group btn-participant">
  <button type="button" class="btn btn-default btn-sm dropdown-toggle " data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
      <span class="glyphicon glyphicon-star red-star aria-hidden="true"></span>
    <?= MiscHelpers::getDisplayName($model->owner_id) ?>
    <span class="caret"></span>
  </button>
  <ul class="dropdown-menu">
      <li><?= Html::a(Yii::t('frontend','Send a message'),Url::to('mailto:'.$model->owner->email))?></li>
  </ul>
</div>

现在任何人都可以向任何参与者发送消息(会议记录功能当前已分发给所有会议参与者)。

组织者看到了更深的下拉菜单,这使他们可以任命其他组织者,即Make Organer 。 现在,这是一个非常酷的功能。 组织者将在整个计划阶段收到更完整的通知,并拥有更多权力。 他们还可以删除参与者

将AJAX功能构建到参与者按钮中

我一时兴起就决定将所有这些菜单选项都AJAXify。 事实证明,这需要几个小时的编码。

这是定义初始按钮菜单并准备JavaScript的代码:

<?php
if (count($model->participants)>0) {
  foreach ($model->participants as $p) {
    if ($p->participant->id==Yii::$app->user->getId()) {
      continue;
    }
    $btn_color = 'btn-default';
    if ($p->status == Participant::STATUS_DECLINED) {
      $btn_color = 'btn-warning';
    } else if ($p->status == Participant::STATUS_REMOVED || $p->status == Participant::STATUS_DECLINED_REMOVED) {
      $btn_color = 'btn-danger';
    }
  ?>
  <div class="btn-group btn-participant">
    <button id="btn_<?= $p->id ?>" type="button" class="btn <?= $btn_color ?> btn-sm dropdown-toggle " data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
        <span id="star_<?= $p->id ?>" class="glyphicon glyphicon-star red-star <?= (!$p->isOrganizer())?'hidden':''?>" aria-hidden="true"></span>
      <?= MiscHelpers::getDisplayName($p->participant->id) ?>
      <span class="caret"></span>
    </button>
    <ul class="dropdown-menu">
        <li><?= Html::a(Yii::t('frontend','Send a message'),Url::to('mailto:'.$p->participant->email))?></li>
        <?php if ($model->isOrganizer()) {
          ?>
          <li role="separator" class="divider"></li>
            <li id="mo_<?= $p->id ?>" class="<?= ($p->isOrganizer())?'hidden':''?>"><?= Html::a(Yii::t('frontend','Make organizer'),'javascript:void(0);',['onclick' => "toggleOrganizer($p->id,true);return false;"]); ?></li>
            <li id="ro_<?= $p->id ?>" class="<?= (!$p->isOrganizer())?'hidden':''?>"><?= Html::a(Yii::t('frontend','Revoke organizer role'),'javascript:void(0);',['onclick' => "toggleOrganizer($p->id,false);return false;"]); ?></li>
          <li id="rp_<?= $p->id ?>" class="<?= ($p->status == Participant::STATUS_REMOVED || $p->status == Participant::STATUS_DECLINED_REMOVED)?'hidden':''?>"><?= Html::a(Yii::t('frontend','Remove participant'),'javascript:void(0);',['onclick' => "toggleParticipant($p->id,false,$p->status);return false;"]); ?></li>
          <li id="rstp_<?= $p->id ?>" class="<?= ($p->status != Participant::STATUS_REMOVED && $p->status != Participant::STATUS_DECLINED_REMOVED)?'hidden':''?>"><?= Html::a(Yii::t('frontend','Restore participant'),'javascript:void(0);',['onclick' => "toggleParticipant($p->id,true,$p->status);return false;"]); ?></li>
          <?php
          }
          ?>
    </ul>
  </div>

当页面上以交互方式进行更改时,要更新的按钮状态,颜色和星号太多,因此代码变得相当复杂。 我将功能添加到toggleOrganizer() JavaScript文件中的toggleOrganizer() ,即make / unset toggleParticipant()toggleParticipant()  即删除/恢复参与者作为参与者。

function toggleOrganizer(id, val) {
  if (val === true) {
    arg2 = 1;
  } else {
    arg2 =0;
  }
  $.ajax({
     url: $('#url_prefix').val()+'/participant/toggleorganizer',
     data: {id: id, val: arg2},
     success: function(data) {
       if (data) {
         if (val===false) {
            $('#star_'+id).addClass("hidden");
            $('#ro_'+id).addClass("hidden");
            $('#mo_'+id).removeClass("hidden");
         } else {
           $('#star_'+id).removeClass("hidden");
           $('#ro_'+id).removeClass("hidden");
           $('#mo_'+id).addClass("hidden");
         }
       }
        return true;
     }
  });
}

function toggleParticipant(id, val, original_status) {
  if (val === true) {
    arg2 = 1;
  } else {
    arg2 =0;
  }
  $.ajax({
     url: $('#url_prefix').val()+'/participant/toggleparticipant',
     data: {id: id, val: arg2, original_status: original_status},
     success: function(data) {
       if (data) {
         if (val===false) {
            $('#rp_'+id).addClass("hidden");
            $('#rstp_'+id).removeClass("hidden");
            $('#btn_'+id).addClass("btn-danger");
            $('#btn_'+id).removeClass("btn-default");
         } else {
           $('#rp_'+id).removeClass("hidden");
           $('#rstp_'+id).addClass("hidden");
           if (original_status==100) {
             $('#btn_'+id).addClass("btn-warning");
             $('#btn_'+id).removeClass("btn-danger");
           } else {
             $('#btn_'+id).addClass("btn-default");
             $('#btn_'+id).removeClass("btn-danger");
           }
         }
       }
        return true;
     }
  });
}

这些在ParticipantController.php中需要随附的JSON控制器方法来处理切换请求和更新数据库:

public function actionToggleorganizer($id,$val) {
  Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
  // change setting
  $p=Participant::findOne($id);
  if ($p->meeting->isOrganizer()) {
    $p->email = $p->participant->email;
    if ($val==1) {
      $p->participant_type=Participant::TYPE_ORGANIZER;
    } else {
      $p->participant_type=Participant::TYPE_DEFAULT;
    }
    $p->update();
    return true;
  } else {
    return false;
  }

}

public function actionToggleparticipant($id,$val) {
  Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
  // change setting
  $p=Participant::findOne($id);
  if ($p->meeting->isOrganizer()) {
    $p->email = $p->participant->email;
    if ($val==0) {
      if ($p->status == Participant::STATUS_DECLINED) {
          $p->status=Participant::STATUS_DECLINED_REMOVED;
      } else {
        $p->status=Participant::STATUS_REMOVED;
      }
    } else {
      if ($p->status == Participant::STATUS_DECLINED_REMOVED) {
          $p->status=Participant::STATUS_DECLINED;
      } else {
        $p->status=Participant::STATUS_DEFAULT;
      }
    }
    $p->update();
    return true;
  } else {
    return false;
  }
}

在面板上激活手风琴功能

Meeting Planner Startup Series - Open and Closed Panels with Bootstrap Accordion Feature

这时,我还意识到,随着会议计划的复杂性增加,接收者和选项的增加,滚动的内容也会越来越多。 我决定为会议视图上的所有面板实施Bootstrap手风琴功能

换句话说,您现在可以单击标题以折叠或打开每个和/或所有面板。

这是对聚会场所_panel.php的局部的更改:

<div class="panel panel-default">
  <!-- Default panel contents -->
  <div class="panel-heading" role="tab" id="headingWhere">
    <div class="row">
      <div class="col-lg-10 col-md-10 col-xs-10" ><h4 class="meeting-place">
      <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseWhere" aria-expanded="true" aria-controls="collapseWhere"><?= Yii::t('frontend','Where') ?></a>
      </h4><p>
        <div class="hint-text heading-pad">
        <?php if ($placeProvider->count<=1) { ?>
          <?= Yii::t('frontend','add places for participants or switch to \'virtual\'') ?>
      <?php } elseif ($placeProvider->count>1) { ?>
          <?= Yii::t('frontend','are listed places okay?&nbsp;') ?>
        <?php
          }
        ?>
...
<div id="collapseWhere" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingWhere">
    <div class="panel-body">
      <?php
        $style = ($model->switchVirtual==$model::SWITCH_VIRTUAL?'none':'block');
       ?>
      <div id ="meeting-place-list" style="display:<?php echo $style; ?>">
      <?php
       if ($placeProvider->count>0):
      ?>
      <table class="table">
        <?= ListView::widget([
               'dataProvider' => $placeProvider,
               'itemOptions' => ['class' => 'item'],
               'layout' => '{items}',
               'itemView' => '_list',
               'viewParams' => ['placeCount'=>$placeProvider->count,'isOwner'=>$isOwner,'participant_choose_place'=>$model->meetingSettings['participant_choose_place'],'whereStatus'=>$whereStatus],
           ]) ?>
      </table>

注意上面panel-heading上的设置,然后注意后面的panel-body周围的div。 这些控制每个面板的打开和折叠。

这导致了一些小的外观问题,例如在项目列表周围出现不必要的填充,我将来需要清理这些问题。

小组会议的模型基础架构

尽管我几乎从一开始就计划有多个参与者,但在基础设施方面有一些次要的改进以支持他们。

虽然MeetingTimeChoiceMeetingPlaceChoice模型跟踪参与者是否喜欢特定的日期时间和地点,但我想跟踪每个日期和地点所有参与者的总体可用性。 这将使我能够根据地点和时间的受欢迎程度对其进行排序,并在面板顶部显示最受欢迎的设置。

首先,我创建了一个迁移,以将其添加到两个模型中。 我的迁移很少会影响多个模型,这使得这种特殊情况变得如此:

<?php

use yii\db\Schema;
use yii\db\Migration;

class m160824_235517_extend_meeting_place_and_time extends Migration
{
  public function up()
  {
    $tableOptions = null;
    if ($this->db->driverName === 'mysql') {
        $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
    }
    $this->addColumn('{{%meeting_time}}','availability',Schema::TYPE_SMALLINT.' NOT NULL DEFAULT 0');
    $this->addColumn('{{%meeting_place}}','availability',Schema::TYPE_SMALLINT.' NOT NULL DEFAULT 0');
  }

  public function down()
  {
    $this->dropColumn('{{%meeting_time}}','availability');
    $this->dropColumn('{{%meeting_place}}','availability');
  }
}

有了这种能力,我就可以从MeetingController::actionView()开始显示可能的会议日期时间和地点, MeetingController::actionView()参加者的受欢迎程度排序:

$timeProvider = new ActiveDataProvider([
            'query' => MeetingTime::find()->where(['meeting_id'=>$id]),
            'sort' => [
              'defaultOrder' => [
                'availability'=>SORT_DESC
              ]
            ],
        ]);
        $placeProvider = new ActiveDataProvider([
            'query' => MeetingPlace::find()->where(['meeting_id'=>$id]),
            'sort' => [
              'defaultOrder' => [
                'availability'=>SORT_DESC
              ]
            ],
        ]);

您可以在下面的计划屏幕截图中看到这一点:

Meeting Planner Startup Series - Sorted Date Times and Places By Popularity

为了跟踪参与者是否是组织者,并允许将来退出特定会议的通知,我为“参与者”表添加了此迁移:

<?php

use yii\db\Schema;
use yii\db\Migration;

class m160825_074740_extend_participant_add_type extends Migration
{
  public function up()
  {
    $tableOptions = null;
    if ($this->db->driverName === 'mysql') {
        $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
    }
    $this->addColumn('{{%participant}}','participant_type',Schema::TYPE_SMALLINT.' NOT NULL DEFAULT 0');
    $this->addColumn('{{%participant}}','notify',Schema::TYPE_SMALLINT.' NOT NULL DEFAULT 0');
  }

  public function down()
  {
    $this->dropColumn('{{%participant}}','participant_type');
    $this->dropColumn('{{%participant}}','notify');
  }
}

我还在Participant.php中添加了一些常量以使用这些属性:

class Participant extends \yii\db\ActiveRecord
{
    const TYPE_DEFAULT = 0;
    const TYPE_ORGANIZER = 10;

    const NOTIFY_ON = 0;
    const NOTIFY_OFF = 1;

    const STATUS_DEFAULT = 0;
    const STATUS_REMOVED = 90;
    const STATUS_DECLINED = 100;
    const STATUS_DECLINED_REMOVED = 110;

我知道在大型会议模型中具有一些辅助功能会很有帮助。 例如, IsOrganizer()告诉我当前查看者是否是会议组织者:

public function isOrganizer() {
      $user_id = Yii::$app->user->getId();
      if ($user_id == $this->owner_id) {
        return true;
      } else {
        foreach ($this->participants as $p) {
          if ($user_id == $p->participant_id) {
            if ($p->participant_type == Participant::TYPE_ORGANIZER) {
              return true;
            } else {
              return false;
            }
          }
      }
    }
    return false;
  }

等等,还有吗?

如您所见,构建此功能有很多基础。 在下一个情节中,我将介绍启动多个参与者会议所需的开发和测试的后半部分:收件人字符串,通知,请求以及对请求的响应。

如果还没有,请安排与Meeting Planner的第一次会议,然后尝试所有这些。 请在下面的评论中分享您的反馈。

有关众筹的教程也正在编写中 ,因此请关注我们的WeFunder Meeting Planner页面

您也可以与我联系@reifman 。 我总是乐于接受新功能的想法和主题建议,以供将来的教程使用。

请阅读“ 用PHP构建您的启动”系列,继续关注所有这些以及以后的教程。

相关链接

翻译自: https://code.tutsplus.com/tutorials/building-your-startup-meetings-with-multiple-participants--cms-27132

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值