Статьи

Создание вашего стартапа: встречи с несколькими участниками

Конечный продукт
Что вы будете создавать

Это руководство является частью серии « Создай свой стартап с помощью PHP» на Envato Tuts +. В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе я буду публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете извлечь уроки. Я также буду решать вопросы, связанные с бизнесом по мере их возникновения.

Планирование встреч с несколькими участниками всегда было частью моего плана, но не частью самого раннего продукта с минимальной жизнеспособностью (MVP). Выпущена альфа-версия Meeting Planner с расписанием 1: 1. Цель поддержки группового планирования находилась в списке задач, таких как гора Эверест, для альпиниста, стремящегося к семи встречам на высшем уровне (а я даже не открытый альпинист).

Встречи с несколькими участниками являются наиболее сложными для планирования и, следовательно, ценными для предлагаемого продукта. Я был взволнован, когда список задач бета-тестирования достиг того момента, когда я смог начать работу над этим.

Я планировал, проектировал и программировал с учетом групповых встреч с самого начала. Я надеялся, что обновление сайта для этой функции не потребует значительных изменений UX или обновлений кодирования. Оказалось, что требуется средний путь, 7-10 дней очень сфокусированной работы и тестирования, но без серьезной реорганизации.

На самом деле, тестирование оказалось самым сложным аспектом построения этой функции. Это также помогло выявить недостатки в более раннем коде. Просто это не так просто … отправка на несколько адресов электронной почты, проверка того, что каждый из них получает все правильные уведомления, но не неправильные уведомления, и видит все правильные пункты меню по всему сайту.

В сегодняшнем уроке я расскажу о включении нескольких участников, обновлении UX для групп, назначении организаторов, удалении участников и сортировке опций даты, времени и места по их популярности среди участников.

В следующем уроке я опишу остальную часть работы: просмотр всех областей сайта, затронутых несколькими собраниями участников, обработка и умное отображение списков получателей с различным статусом, правильное управление уведомлениями и фильтрацией уведомлений для групп, и, наконец, обновление недавно запущенной функции изменения запроса на встречу .

Пожалуйста, назначьте встречу группы сегодня ! Поделитесь своими мыслями и отзывами в комментариях ниже.

Я участвую в обсуждениях, но вы также можете связаться со мной @reifman в Twitter. Я всегда открыт для новых идей для планировщика собраний, а также предложений для будущих серий.

Напоминаем, что весь код для Meeting Planner предоставляется с открытым исходным кодом и написан на Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с моей параллельной серией Программирование с Yii2 . Я много слышал о Laravel, но Yii2 всегда отвечает моим потребностям быстро и легко.

Когда я впервые разработал интерфейс планирования Планировщика собраний , он показал текущую доступность другого участника в своем собственном столбце. И это немного сбивало с толку, так как были отключены элементы управления.

Meeting Planner Startup Series - The old You Them Availability Panel

В то время я беспокоился о том, как бы освободить место для отображения доступности групп.

К счастью, когда я перестроил UX для лучшего восприятия , я заменил столбец доступности участников на небольшую текстовую сводку:

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

Текстовое резюме о наличии по совпадению будет хорошо работать для групповых встреч.

Первым делом изменив дизайн для мобильных устройств, я преодолел самый значительный барьер UX для встреч нескольких участников!

Давайте начнем изучать весь код и тестирование, необходимое для нескольких встреч участников.

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

Самым забавным аспектом групповых встреч является то, что их активизация была простой. Мне просто нужно было отключить отключение кнопки « плюс» на панели « Кто» для собраний на этапе планирования:

1
2
3
4
5
<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 в модели участника:

1
2
3
4
5
class Participant extends \yii\db\ActiveRecord
{
    …
 
    const MEETING_LIMIT = 15;

Он используется в ParticipantController::actionCreate() при ParticipantController::actionCreate() :

1
2
3
4
5
6
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:

01
02
03
04
05
06
07
08
09
10
<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»>
    <?= MiscHelpers::getDisplayName($model->owner_id) ?>
    <span class=»caret»>
  </button>
  <ul class=»dropdown-menu»>
      <li><?= Html::a(Yii::t(‘frontend’,’Send a message’),Url::to(‘mailto:’.$model->owner->email))?></li>
  </ul>
</div>

Любой может теперь отправить сообщение любому участнику (функции заметок собрания в настоящее время распространяются на всех участников собрания).

Организаторы видят более глубокий выпадающий список, который позволяет им назначить дополнительных организаторов, например, Сделать организатора . Теперь это очень крутая функция. Организаторы будут получать более полные уведомления и иметь больше возможностей на всех этапах планирования. Они также могут удалить участников .

Я решил по прихоти AJAXify все эти пункты меню. Оказалось, что это требует нескольких сложных часов кодирования.

Вот код, который определяет начальное меню кнопок и подготавливает JavaScript:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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»>
      <?= MiscHelpers::getDisplayName($p->participant->id) ?>
      <span class=»caret»>
    </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 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 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 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;»]);
          <?php
          }
          ?>
    </ul>
  </div>

Существует так много состояний кнопок, цветов и звездочек, которые нужно обновлять, так как изменения вносятся в интерактивном режиме на странице, поэтому код становится довольно сложным. Я добавил функции в файл JavaScript toggleOrganizer() для функции toggleOrganizer() , т. toggleParticipant() Организатор make / unset и toggleParticipant() ,   т.е. удалить / восстановить участника в качестве участника.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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;
     }
  });
}

Для обработки запросов переключения и обновления баз данных необходимы сопутствующие методы контроллера JSON в ParticipantController.php:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<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 а затем на окружающий div для более позднего panel-body . Они управляют открытием и разрушением каждой панели.

Это привело к небольшим косметическим проблемам, таким как нежелательное заполнение списка элементов, которые мне нужно будет убрать в будущем.

Хотя я планировал создать несколько участников с самого начала, было несколько незначительных или скромных улучшений инфраструктуры для их поддержки.

Хотя модели MeetingTimeChoice и MeetingPlaceChoice отслеживают, предпочитают ли участники определенное время и места, я хотел отслеживать общую доступность для всех участников в каждое время и место. Это позволило бы мне сортировать места и время по их популярности и показывать самые популярные настройки в верхней части панелей.

Сначала я создал миграцию, чтобы добавить это к обеим моделям. Нередко моя миграция затрагивает несколько моделей, что делает этот тип особенным:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
$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

Чтобы отследить, являются ли участники организаторами и разрешить ли в будущем отказ от уведомлений о конкретных собраниях, я добавил эту миграцию для таблицы «Участники»:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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 для работы с этими свойствами:

01
02
03
04
05
06
07
08
09
10
11
12
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;

И я знал, что было бы полезно иметь некоторые вспомогательные функции в рамках массивной модели Meeting. Например, IsOrganizer() сообщает мне, является ли текущий зритель организатором собрания:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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» .