Статьи

Создание вашего стартапа: динамические формы Ajax для планирования

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

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

В сегодняшнем уроке я проведу вас через первоначальный набор всесторонних изменений в интерфейсе планирования собраний. Моя цель состояла в том, чтобы использовать Ajax, чтобы сделать все обычные действия по планированию возможными без обновления страницы. Некоторые аспекты этого оказались простыми, а другие довольно сложными. В этом эпизоде ​​я сосредоточусь на простых деталях: как в основном создавать запросы Ajax UX в вашем приложении Yii на основе PHP.

Во второй части я расскажу о более сложных вещах — отладке Ajax и повторной инициализации виджетов Bootstrap после начальной загрузки страницы. Я также поделюсь, как я использовал консоль разработчика браузера Chrome от Google, чтобы помочь мне определить неработающий код.

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

Как ни странно, были пути кода, которые, казалось, приблизили меня к завершению, а затем преодолели непреодолимое препятствие — и мне пришлось бы начинать заново с нового подхода. В конечном итоге мне удалось успешно завершить полное планирование через бета-версию Ajax.

Следуйте сегодня, пока я проведу вас через основную часть работы.

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

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

Моя основная цель на этом этапе продукта состояла в том, чтобы реализовать все основные функции планирования с помощью Ajax и устранить обновления страницы, необходимые в настоящее время для редактирования темы, добавления участников, добавления времени, мест и заметок.

Поскольку ранее я встроил Ajax на сайт, у меня были идеи, что будет хорошо, а что будет сложно.

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

Стартапы Ajax - Панель Темы Встречи, Загруженная через Ajax

Я начал с редактирования панели темы собрания, потому что она состоит из пары статических полей, одного ввода и одного текстового поля. Однако в поле субъекта используется виджет jQuery Typeahead . Виджеты могут усложнить ситуацию, потому что вы должны иметь возможность инициализировать их с учетом Ajax.

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

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

Я также расширил определение MeetingAsset.php, добавив в него больше JavaScript:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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 загружается файлом Meeting.php:

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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).’&nbsp;’;
         } ?>
         </div>
         <div id=»editWhat» class=»hidden»>
           <?= $this->render(‘_form’, [
               ‘model’ => $model,
               ‘subjects’ => $model->defaultSubjectList(),
           ]) ?>
         </div>
       </div>
     </div>
     <?php
   } else {
     ?>

Я подключил кнопку редактирования (со значком карандаша) в _panel_what.php, чтобы вызвать функцию showWhat() JavaScript showWhat() для отображения или скрытия формы редактирования. Вот этот код:

1
2
3
4
5
6
7
8
<?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() из meeting.js показана ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
// 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, которую он скрывает и показывает. Здесь появляются поля input и textarea:

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
<?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:

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

01
02
03
04
05
06
07
08
09
10
11
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. Быстрое переключение между языками, время от времени пытающееся создать строки параметров запроса, может привести к ошибкам.

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

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

Стартапы Ajax - Панель примечаний к собраниям, загружаемая через Ajax

Заметки о встрече также являются статическими полями текстовой области Однако может существовать непрерывная цепочка заметок, которую необходимо обновлять на странице при ее добавлении (например, не только по одной теме собрания). И важно сообщить пользователю, что мы уведомим участников о заметке.

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

Стартапы Ajax - Заметки о встрече Ajax Alert

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

В приведенном ниже примере оба noteMessage1 и noteMessage2 загружаются как скрытые.

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
<?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»>&times;</button>
<span id=»noteMessage1″>
<?= Yii::t(‘frontend’,»Thanks for your note. We’ll automatically share it with other participants.»)?>
<span id=»noteMessage2″>
<?= Yii::t(‘frontend’,’Please be sure to type a note.’)?>
</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’) ?>
      </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>

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

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
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);
    },
    error: function(error){
    }
  });
}

Одна из задач связана с обработкой ошибок в Ajax. Это нелегко сделать, и для этого требуется достаточно детальная архитектура везде — пока я пошел дальше, не обрабатывая такого рода ошибки.

Вот displayAlert() JavaScript displayAlert() которую я повторно использовал и построил для всех различных панелей и их предупреждающих сообщений:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
function displayAlert(alert_id,msg_id) {
   // which alert box ie which panel alert
   switch (alert_id) {
     case ‘noteMessage’:
       // which msg to display
       switch (msg_id) {
         case ‘noteMessage1’:
         $(‘#noteMessage1’).removeClass(‘hidden’);
         $(‘#noteMessage2’).addClass(‘hidden’);
         $(‘#noteMessage’).removeClass(‘hidden’).addClass(‘alert-info’).removeClass(‘alert-danger’);
         break;
         case ‘noteMessage2’:
         $(‘#noteMessage1’).addClass(‘hidden’);
         $(‘#noteMessage2’).removeClass(‘hidden’);
         $(‘#noteMessage’).removeClass(‘hidden’).removeClass(‘alert-info’).addClass(‘alert-danger’);
         break;
       }
     break;
     case ‘participantMessage’:
       // which msg to display
       $(‘#participantMessageTell’).addClass(‘hidden’);
       $(‘#participantMessageError’).addClass(‘hidden’);
       $(‘#participantMessageOnlyOne’).addClass(«hidden»);
       $(‘#participantMessageNoEmail’).addClass(«hidden»);
       switch (msg_id) {
         case ‘participantMessageTell’:
         $(‘#participantMessageTell’).removeClass(‘hidden’);
         $(‘#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’);
       $(‘#placeMsg2’).addClass(‘hidden’);
       $(‘#placeMsg3’).addClass(‘hidden’);
       switch (msg_id) {
         case ‘placeMsg1’:
           $(‘#placeMsg1’).removeClass(‘hidden’);
           $(‘#placeMessage’).removeClass(‘hidden’).addClass(‘alert-info’).removeClass(‘alert-danger’);
         break;
         case ‘placeMsg2’:
           $(‘#placeMsg2’).removeClass(‘hidden’);
           $(‘#placeMessage’).removeClass(‘hidden’).removeClass(‘alert-info’).addClass(‘alert-danger’);
         break;
       }
     break;
     case ‘timeMessage’:
       // which msg to display
       $(‘#timeMsg1’).addClass(‘hidden’);
       $(‘#timeMsg2’).addClass(‘hidden’);
       //$(‘#timeMsg3’).addClass(‘hidden’);
       switch (msg_id) {
         case ‘timeMsg1’:
           $(‘#timeMsg1’).removeClass(‘hidden’);
           $(‘#timeMessage’).removeClass(‘hidden’).addClass(‘alert-info’).removeClass(‘alert-danger’);
         break;
         case ‘timeMsg2’:
           $(‘#timeMsg2’).removeClass(‘hidden’);
           $(‘#timeMessage’).removeClass(‘hidden’).removeClass(‘alert-info’).addClass(‘alert-danger’);
         break;
       }
     break;
   }
 }

Когда пользователь отправляет новую заметку, MeetingNoteController.php actionUpdatethread() вызывается через Ajax. Вот PHP:

01
02
03
04
05
06
07
08
09
10
11
12
13
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, которая загружает все заметки, включая новую:

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

Надеюсь, на сегодня хватит учиться и попробовать.

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

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

В следующем эпизоде ​​я расскажу об остальных функциях, добавив дату и время, и используя инструменты отладки браузера Google Chrome, чтобы помочь мне.

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

Предварительный выпуск Meeting Planner теперь доступен. Теперь можно поделиться им со своими друзьями и коллегами.

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

Следите за будущими уроками в серии « Построение стартапа с помощью PHP» .