Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

Этот учебник является частью серии Пишем свой стартап на PHP на Envato Tuts +. В этой серии я показываю вам запуск приложения от самой концепции до реального приложения, используя приложение «Планировщик встреч» в качестве примера. На каждом шаге я буду выкладывать код планировщика собраний в качестве примеров с открытым исходным кодом. Я также рассмотрю связанные с запуском бизнес-вопросы по мере их возникновения.
Введение в групповые встречи
Планирование встреч с несколькими участниками всегда было частью моего плана, но не частью самого раннего минимального жизнеспособного продукта (MVP). Альфа-релиз Планировщика собраний запускается только с расписанием 1: 1. Цель поддержки группового планирования состояла в списке задач, таких как гора Эверест, для альпиниста, нацеленного на семь вершин (и я даже не альпинист на открытом воздухе).
Несколько встреч участников являются наиболее сложными для планирования и, следовательно, ценными для предлагаемого Планировщика собраний. Я был взволнован, когда список бета-задач дошел до того, что я мог бы начать работу над этим.
Я планировал архитектуру и кодирование с учетом групповых встреч с самого начала. Я надеялся, что обновление сайта для этой функции не потребует значительных изменений UX или изменений в коде. Оказалось, что требуется средний путь, 7-10 дней очень целенаправленной работы и тестирования, но не нужно будет ничего переделывать.
Фактически, тестирование оказалось самым сложным аспектом построения этой функции. Это также помогло выявить недостатки в более раннем коде. Просто это не так уже просто ... отправка на несколько адресов электронной почты, проверка того, что каждый из них получает все правильные уведомления - и видит все правильные параметры меню на всем сайте.
В сегодняшнем учебном пособии я расскажу о том, как включить нескольких участников, обновить UX для групп, назначить организаторов, удалить участников, а также отсортировать даты, время и место по их популярности среди участников.
В следующем уроке я опишу оставшуюся часть работы: обзор всех областей сайта, затронутых встречами с несколькими участниками, обработка и интеллектуальное отображение списков получателей различного статуса, правильное управление уведомлениями и фильтрация уведомлений для групп и, наконец, обновление недавней функции запроса на изменение собрания.
Попробуйте спланировать групповое собрание
Пожалуйста, запланируйте встречу с группой сегодня! Поделитесь своими мыслями и отзывами в комментариях ниже.
Я участвую в обсуждениях, но вы также можете связаться со мной @reifman в Twitter. Я всегда открыт для новых идей для планировщика собраний, а также предложений для будущих эпизодов этой серии.
Напомним, что весь код для Планировщика собраний предоставляется с открытым исходным кодом и написан на Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с моей параллельной серией Программирование с Yii2. Я слышал о Laravel, но Yii2 всегда быстро и легко удовлетворяет мои потребности.
Глядя назад
Когда я впервые разработал интерфейс планирования планировщика собраний, он отображал текущую доступность другого участника в своем собственном столбце. И это было немного запутанно, так как были отключенные элементы управления.

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

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

Самый смешной аспект групповых встреч - то, что их активизация была простой. Мне просто нужно было отключить отключение кнопки «плюс» на панели «Кто» для встреч на этапе планирования:
<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;
Он используется в MemberantController:: 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]); }
Улучшение UX и связанных с ним функций
В течение долгого времени я хотел разрешить организаторам собрания удалять участников, места и даты, не загромождая интерфейс пользователя. Точно так же я понял, что для участников может быть несколько команд.
Найдя такую полезную утилиту в кнопке Bootstrap dropdown в учебнике Advanced Commands, я решил использовать ее для отображения участников собрания:

Организаторы обозначаются звездочкой. Участники, которые отклонили встречу, отображаются оранжевым цветом. Участники, которых удаляют организаторы, отображаются красным цветом.
Вот код в моем отображении /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>
Теперь любой может отправить сообщение любому участнику (функции заметок на собрания в настоящее время распространяются среди всех участников собрания).
Организаторы видят более глубокое раскрывающееся меню, которое позволяет им указать дополнительных организаторов, т.е. Сделать организатором. Теперь это очень крутая функция. Организаторы получат более полные уведомления и получат больше возможностей на всех этапах планирования. Они также могут удалить участников.
Создание функций AJAX в кнопках участника
Я решил добавить AJAX на все эти параметры меню. Для этого потребовалось несколько сложных часов кодирования.
Вот код, который определяет начальное меню кнопок и готовит 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>
Есть так много состояний кнопок, цветов и звезд, которые нужно обновить, так как изменения сделаны интерактивно на странице, что код становится довольно сложным. Я добавил функции в файл JavaScript meeting.js для toggleOrganizer()
, т. е. сделать/удалить организатора, и 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; } }); }
Эти требуемые сопутствующие методы контроллера JSON в EntantController.php обрабатывают запросы переключения и обновляют базу данных:
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; } }
Активация функции аккордеона на панелях

В то же время я также понял, что по мере того, как планы собраний будут усложняться с большим количеством получателей и вариантов, будет больше прокрутки. Я решил реализовать функцию аккордеона 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? ') ?> <?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
отслеживают, предпочитают ли участники конкретные даты и места, я хотел отслеживать общую доступность для всех участников в каждый момент времени. Это позволит мне сортировать места и время по их популярности, а также показывать самые популярные настройки в верхней части панелей.
Во-первых, я создал миграцию, чтобы добавить это в обе модели. Редко, что миграция влияет на несколько моделей, что делает этот вид особым:
<?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()
:
$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 ] ], ]);
Вы можете увидеть это в действии на скриншоте ниже:

Чтобы отслеживать, являются ли участники организаторами и разрешено ли в будущем отказаться от уведомлений конкретного собрания, я добавил эту миграцию для таблицы Participant:
<?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'); } }
Я также добавил в Constant.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;
И я знал, что было бы полезно иметь некоторые вспомогательные функции в рамках массивной модели Meeting. Например, 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; }
Подождите, а что еще?
Как вы можете видеть, для создания этой функции есть много возможностей. В следующем эпизоде я расскажу о второй половине разработки и тестирования, необходимых для запуска встреч с несколькими участниками: строки получателей, уведомления, запросы и ответы на запросы.
Если вы еще этого не сделали, то запланируйте свою первую встречу с Планировщиком встреч и попробуйте все это. Пожалуйста, поделитесь своими отзывами в комментариях ниже.
Учебник по crowdfunding также находится в работе, поэтому, пожалуйста, зайдите на страницу WeFunder Meeting Planner.
Вы также можете связаться со мной @reifman. Я всегда открыт для новых идей и предложений для будущих учебников.
Оставайтесь с нами и будьте в курсе всех новых уроков серии Пишем стартап на PHP.
Ссылки по теме
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 weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post