Advertisement
  1. Web Design
  2. HTML/CSS
  3. Bootstrap

Aufbau Ihres Startups mit PHP: Planen eines Meetings

Scroll to top
Read Time: 23 min
This post is part of a series called Building Your Startup With PHP.
Building Your Startup With PHP: User Settings, Profile Images and Contact Details
Building Your Startup With PHP: Scheduling Availability and Choices

() translation by (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Dieses Tutorial ist Teil der Reihe Building Your Startup With PHP auf Tuts+. In dieser Serie führe ich Sie durch den Start eines Startups vom Konzept zur Realität anhand meiner Meeting Planner-App als Beispiel aus dem wirklichen Leben. Bei jedem Schritt auf dem Weg veröffentlichen wir den Meeting Planner-Code als Open Source-Beispiele, aus denen Sie lernen können. Wir werden uns auch mit geschäftlichen Problemen im Zusammenhang mit Startups befassen, sobald diese auftreten.

Der gesamte Code für Meeting Planner ist im Yii2 Framework für PHP geschrieben. Wenn Sie mehr über Yii2 erfahren möchten, lesen Sie unsere parallele Serie Programming With Yii2 bei Tuts+. Vielleicht möchten Sie auch auf meiner Knowledge Base-Website nach Yii2-Fragen suchen, The Yii2 Developer Exchange.

In den letzten sechs Tutorials haben wir die Grundlagen für die Anwendungsunterstützung auf verschiedene Weise gelegt: Benutzer, Orte, Kontakte, Einstellungen und Lokalisierung. Ich wette, Sie werden genauso begeistert sein wie ich, dass wir endlich bereit sind, den Zeitplan für eine Besprechungsfunktion zu erstellen. Vielen Dank für Ihre Geduld, da ich die Infrastruktur für die unterhaltsamen, integralen Bestandteile dieser Anwendung aufgebaut habe.

Das Codierungs-Tutorial zur Unterstützung der Zeitplan-Meeting-Funktionalität erstreckt sich jedoch über mindestens vier Episoden. Diese beiden nächsten Episoden konzentrieren sich hauptsächlich darauf, Unterstützung für die grundlegende Benutzererfahrung aufzubauen, den Teilnehmer, den Ort sowie Datum und Uhrzeit für eine Besprechung auszuwählen und diese in der Datenbank zu speichern. Danach kümmern wir uns um die Zustellung der Besprechungsanfrage per E-Mail. Wir werden später in der Serie zurückkehren, um die Benutzeroberfläche zu optimieren und zu verbessern, da dies für den Erfolg dieses Produkts von entscheidender Bedeutung ist. Die Hauptaufgabe für dieses spätere Update besteht darin, Seitenaktualisierungen im Besprechungsplanungsprozess zu vermeiden.

Das Erstellen der Funktionen für dieses Tutorial erforderte eine Menge Code - einige davon wurden automatisch von Yii's Gii generiert und viele von Hand. Aufgrund der Komplexität der Bereitstellung der ersten Teile dieser Funktion habe ich mich auf eine recht rudimentäre Benutzeroberfläche konzentriert, die ich iterativ polieren werde.

Das Erstellen dieser Funktion berührt so viele Aspekte der Programmierung im Yii2-Framework: Active Record-Migrationen, Beziehungen und Validierungen, Gii-Codegenerierung, Bootstrap, Erweiterungen und Widgets der Yii2-JQuery-Benutzeroberfläche, AJAX, Rendern von Teilansichten, DRY-Codierungspraktiken usw. Es war schwierig auszuwählen, was hier behandelt werden sollte. Sie werden viele Änderungen am Repository gegenüber früheren Tutorial-Episoden bemerken.

Wenn Sie Fragen oder Kommentare haben, posten Sie diese bitte unten. Ich nehme an den Diskussionen teil.

Die Meetings-Seite

Bootstrap-Registerkarten

Eines der ersten Dinge, die ich tun muss, ist, verschiedene Registerkarten für zukünftige, vergangene und abgesagte Meetings zu erstellen.

The Meetings Page with Tabs for Upcoming Past and CanceledThe Meetings Page with Tabs for Upcoming Past and CanceledThe Meetings Page with Tabs for Upcoming Past and Canceled

Die Implementierung ist nur ein weiteres Beispiel dafür, wie großartig Bootstrap ist und wie solide die Yii2-Integration mit Bootstrap 3.x ist. Bootstrap verfügt über vorgefertigte Registerkarten.

In MeetingController laden wir Abfragen für jeden Besprechungstyp vor und rendern die Indexansicht:

1
public function actionIndex()
2
{
3
  // add filter for upcoming or past

4
    $upcomingProvider = new ActiveDataProvider([
5
        'query' => Meeting::find()->joinWith('participants')->where(['owner_id'=>Yii::$app->user->getId()])->orWhere(['participant_id'=>Yii::$app->user->getId()])->andWhere(['Meeting.status'=>[Meeting::STATUS_PLANNING,Meeting::STATUS_CONFIRMED]]),
6
    ]);
7
    $pastProvider = new ActiveDataProvider([
8
        'query' => Meeting::find()->joinWith('participants')->where(['owner_id'=>Yii::$app->user->getId()])->orWhere(['participant_id'=>Yii::$app->user->getId()])->andWhere(['Meeting.status'=>Meeting::STATUS_COMPLETED]),
9
    ]);
10
    $canceledProvider = new ActiveDataProvider([
11
        'query' => Meeting::find()->joinWith('participants')->where(['owner_id'=>Yii::$app->user->getId()])->orWhere(['participant_id'=>Yii::$app->user->getId()])->andWhere(['Meeting.status'=>Meeting::STATUS_CANCELED]),
12
    ]);
13
14
    return $this->render('index', [
15
        'upcomingProvider' => $upcomingProvider,
16
        'pastProvider' => $pastProvider,
17
        'canceledProvider' => $canceledProvider,
18
    ]);
19
}

Anschließend implementieren wir in der Indexansicht unseren Bootstrap-Code mit Registerkarten:

1
<h1><?= $this->title ?></h1>
2
3
<!-- Nav tabs -->
4
<ul class="nav nav-tabs" role="tablist">
5
  <li class="active"><a href="#upcoming" role="tab" data-toggle="tab">Upcoming</a></li>
6
  <li><a href="#past" role="tab" data-toggle="tab">Past</a></li>
7
  <li><a href="#canceled" role="tab" data-toggle="tab">Canceled</a></li>
8
</ul>
9
10
<!-- Tab panes -->
11
<div class="tab-content">
12
  <div class="tab-pane active" id="upcoming">
13
    <div class="meeting-index">
14
      
15
      <?= $this->render('_grid', [
16
          'dataProvider' => $upcomingProvider,
17
      ]) ?>
18
19
      </div> <!-- end of upcoming meetings tab -->
20
  </div>
21
  <div class="tab-pane" id="past">
22
23
    <?= $this->render('_grid', [
24
        'dataProvider' => $pastProvider,
25
    ]) ?>    
26
  </div> <!-- end of past meetings tab -->
27
  <div class="tab-pane" id="canceled">
28
    <?= $this->render('_grid', [
29
        'dataProvider' => $canceledProvider,
30
    ]) ?>
31
    
32
  </div> <!-- end of canceled meetings tab -->
33
  
34
</div>

Wenn wir uns näher mit dieser Serie befassen, werde ich eine Menge Platzhalter für die Arbeit übrig lassen. Eines dieser Dinge ist die Implementierung dieser Registerkarten über AJAX, damit wir nicht drei Abfragen im Voraus laden.

Ticketverfolgung

Ich werde auch damit beginnen, Tickets in der Issue-Tracking-Anwendung Lighthouse für meine zukünftige Arbeit zu erstellen, um die Nachverfolgung zu vereinfachen. Ich werde in einem zukünftigen Tutorial über Lighthouse sprechen.

Lighthouse Issue Tracker AJAXify Meeting Page TabsLighthouse Issue Tracker AJAXify Meeting Page TabsLighthouse Issue Tracker AJAXify Meeting Page Tabs

Was steckt hinter der Planung eines Meetings?

Die einfache Aufgabe, einen Rahmen für die Planung eines Meetings zu erstellen, erwies sich unter der Haube als recht komplex und detailliert. Ich werde dies schrittweise polieren, während wir uns durch die Serie bewegen.

Mein erstes Ziel war es, nur das grundlegende Framework zu erstellen, damit ich die Funktionen der Besprechungsplanung testen kann.

Besprechungen bestehen aus einer Handvoll ActiveRecord-Datenmodellen, z. Teilnehmer, MeetingTime, MeetingPlace, MeetingNote usw. Anfangs wollte ich nur die Yii-Codegenerierung verwenden, um CRUD für jedes dieser Modelle zu erstellen und es dann in eine einzelne Planungsseite zu integrieren.

Die Idee ist, MVC zu verwenden, um all diese Aktionen zu erstellen und dabei so weit wie möglich an der DRY-Methodik festzuhalten. Anfänglich wird die Benutzeroberfläche Seitenaktualisierungen durchführen, aber später werden wir zurückkommen und alle diese Modelle über AJAX mit demselben MVC-Code integrieren.

Das Formular zum Erstellen eines Meetings

Bei vielen Modellen habe ich zunächst den in früheren Tutorials beschriebenen Prozess zur Verwendung von Yii-Codegenerator Gii zum Erstellen von CRUD durchlaufen. Dann habe ich sie nach Bedarf angepasst. Im Moment bleibe ich bei einem sehr einfachen Formular zum Erstellen eines Meetings - es enthält noch nicht einmal die E-Mail-Adresse des Teilnehmers. Auf diese Weise kann ich schnell einen grundlegenden Besprechungsdatensatz erstellen und an der Planungsseite arbeiten.

The Create a Meeting FormThe Create a Meeting FormThe Create a Meeting Form

Sobald das Formular gesendet wurde, können Sie die Besprechungsseite anzeigen. Natürlich werde ich dieses Formular und den ersten Prozess rechtzeitig ändern.

Die Besprechungsseite

Erinnern Sie sich an mein Modell für das erste Tutorial in dieser Reihe:

The Original Meeting Planner Mockup for Scheduling a MeetingThe Original Meeting Planner Mockup for Scheduling a MeetingThe Original Meeting Planner Mockup for Scheduling a Meeting

Hier ist eine frühe Darstellung der Form, in der ich arbeite:

The Current Form of Meeting Planners Schedule a Meeting FeatureThe Current Form of Meeting Planners Schedule a Meeting FeatureThe Current Form of Meeting Planners Schedule a Meeting Feature

Es gibt eine Menge Infrastruktur, Code (sowohl automatisch generiert als auch manuell generiert) und Widgets von Drittanbietern, die dafür verantwortlich sind. Ich werde dich Stück für Stück durch das Buch führen.

Bootstrap-Panels und -Tabellen

Obwohl dies wahrscheinlich nicht das endgültige Design ist, habe ich mich für die Verwendung von Bootstrap-Bedienfeldern entschieden, um die Seite zwischen Eigenschaften, Orten, Datums- und Uhrzeitangaben sowie Notizen zu organisieren. Die Seite selbst wird durch die Aktion View des Besprechungscontrollers gerendert und ruft für jedes dieser Elemente Teilansichten zu den jeweiligen Modellen auf.

Ich musste es nicht so erstellen, aber ich wollte absichtlich das gesamte integrierte MVC-Framework von Yii nutzen und die Dinge so weit wie möglich integrieren. Ich hatte die Hoffnung, dass es in Zukunft einfacher sein wird, die gesamte Seite zu AJAXifizieren, Seitenaktualisierungen zu reduzieren und die Einfachheit und Wartbarkeit des Quellcodes zu erhöhen.

So funktioniert die Aktion View Meeting-Controller. Es lädt ActiveDataProviders für jedes der Modelle und rendert dann die Besprechungsansichtsdatei:

1
/**

2
     * Displays a single Meeting model.

3
     * @param integer $id

4
     * @return mixed

5
     */
6
    public function actionView($id)
7
    {
8
      $timeProvider = new ActiveDataProvider([
9
          'query' => MeetingTime::find()->where(['meeting_id'=>$id]),
10
      ]);
11
12
      $noteProvider = new ActiveDataProvider([
13
          'query' => MeetingNote::find()->where(['meeting_id'=>$id]),
14
      ]);
15
16
      $placeProvider = new ActiveDataProvider([
17
          'query' => MeetingPlace::find()->where(['meeting_id'=>$id]),
18
      ]);
19
20
      $participantProvider = new ActiveDataProvider([
21
          'query' => Participant::find()->where(['meeting_id'=>$id]),
22
      ]);
23
      $model = $this->findModel($id);
24
      $model->prepareView();
25
        return $this->render('view', [
26
            'model' => $model,
27
            'participantProvider' => $participantProvider,
28
            'timeProvider' => $timeProvider,
29
            'noteProvider' => $noteProvider,
30
            'placeProvider' => $placeProvider,
31
        ]);
32
    }

Teilansichten

Durch die Verwendung aller Ansichten in jedem der zugeordneten Modelle ist es ziemlich einfach, die gesamte Zeitplanseite mit MVC-Teilansichten anzuzeigen. In der Besprechungsansicht werden alle _panel-Ansichten für die anderen Modelle gerendert. Hier können Sie die Dokumentation zur Rendermethode von Yii2 einsehen.

1
        <?= $this->render('../participant/_panel', [
2
            'model'=>$model,
3
            'participantProvider' => $participantProvider,
4
        ]) ?>
5
6
        <?= $this->render('../meeting-place/_panel', [
7
            'model'=>$model,
8
            'placeProvider' => $placeProvider,
9
        ]) ?>       
10
                
11
        <?= $this->render('../meeting-time/_panel', [
12
            'model'=>$model,
13
            'timeProvider' => $timeProvider,
14
        ]) ?>
15
16
        <?= $this->render('../meeting-note/_panel', [
17
            'model'=>$model,
18
            'noteProvider' => $noteProvider,
19
        ]) ?>

Fehlende Modelle

Beim Erstellen dieser Funktionalität wurde mir klar, dass ich einige notwendige Modelle vernachlässigt hatte: MeetingPlaceChoice und MeetingTimeChoice. Diese sind erforderlich, um die Verfügbarkeit des Veranstalters und der Teilnehmer für bestimmte MeetingPlaces und MeetingTimes zu speichern.

Hier ist die Migration für MeetingPlaceChoice:

1
$this->createTable('{{%meeting_place_choice}}', [
2
          'id' => Schema::TYPE_PK,
3
          'meeting_place_id' => Schema::TYPE_INTEGER.' NOT NULL',
4
          'user_id' => Schema::TYPE_BIGINT.' NOT NULL',
5
          'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
6
          'created_at' => Schema::TYPE_INTEGER . ' NOT NULL',
7
          'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL',
8
      ], $tableOptions);
9
      $this->addForeignKey('fk_mpc_meeting_place', '{{%meeting_place_choice}}', 'meeting_place_id', '{{%meeting_place}}', 'id', 'CASCADE', 'CASCADE');
10
      $this->addForeignKey('fk_mpc_user_id', '{{%meeting_place_choice}}', 'user_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE');   

Hier ist die Migration für MeetingTimeChoice:

1
$this->createTable('{{%meeting_time_choice}}', [
2
          'id' => Schema::TYPE_PK,
3
          'meeting_time_id' => Schema::TYPE_INTEGER.' NOT NULL',
4
          'user_id' => Schema::TYPE_BIGINT.' NOT NULL',
5
          'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
6
          'created_at' => Schema::TYPE_INTEGER . ' NOT NULL',
7
          'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL',
8
      ], $tableOptions);
9
      $this->addForeignKey('fk_mtc_meeting_time', '{{%meeting_time_choice}}', 'meeting_time_id', '{{%meeting_time}}', 'id', 'CASCADE', 'CASCADE');
10
      $this->addForeignKey('fk_mtc_user_id', '{{%meeting_time_choice}}', 'user_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE');  

Mit den ActiveRecord-Migrationen von Yii können Sie Ihr Datenbankschema im Verlauf Ihres Produkts auf einfache Weise programmgesteuert erweitern.

Diese Modelle bestimmen den Status der Switch-Widgets (die die Verfügbarkeit der Benutzer widerspiegeln), die Sie oben in den Zeilen für jeden Ort und jede Uhrzeit sehen. Im folgenden Tutorial werde ich Ihnen zeigen, wie wir diese Widgets initialisieren und AJAX in Yii verwenden, um ihren Status ohne Seitenaktualisierung zu aktualisieren.

Planen von Warnungen

Die PrepareView ermittelt den Status der Besprechung und warnt den Benutzer bei Bedarf, dass die Einladung nicht gesendet wurde:

1
public function prepareView() {
2
        $this->setViewer();
3
        $this->canSend();
4
        $this->canFinalize();
5
        // has invitation been sent

6
         if ($this->canSend()) {
7
           Yii::$app->session->setFlash('warning', Yii::t('frontend','This invitation has not yet been sent.'));
8
      }
9
        // to do - if sent, has invitation been opened

10
        // to do - if not finalized, is it within 72 hrs, 48 hrs        

11
      }

Yii verfügt über eine integrierte Unterstützung für die Anzeige von Bootstrap-Warnungen, die als Flashes bezeichnet werden:

Meeting Planner setFlash Bootstrap alertsMeeting Planner setFlash Bootstrap alertsMeeting Planner setFlash Bootstrap alerts

Befehlsschaltflächen

Hier ist der Code für einen Beispielcontainer für Besprechungsansichten mit den oben gezeigten Befehlsschaltflächen:

1
<div class="panel panel-default">
2
    <!-- Default panel contents -->
3
    <div class="panel-heading">
4
      <div class="row">
5
        <div class="col-lg-12"><h1><?= Html::encode($this->title) ?></h1></div>
6
      </div>  
7
    </div>
8
    <div class="panel-body">
9
    <?= $model->message ?>
10
    </div>
11
    <div class="panel-footer">
12
      <div class="row">
13
        <div class="col-lg-6"></div>
14
        <div class="col-lg-6" >
15
          <div style="float:right;">
16
          <?= Html::a(Yii::t('frontend', 'Send'), ['finalize', 'id' => $model->id], ['class' => 'btn btn-primary '.(!$model->isReadyToSend?'disabled':'')]) ?>
17
18
          <?= Html::a(Yii::t('frontend', 'Finalize'), ['finalize', 'id' => $model->id], ['class' => 'btn btn-success '.(!$model->isReadyToFinalize?'disabled':'')]) ?>
19
          <?= Html::a('', ['cancel', 'id' => $model->id], ['class' => 'btn btn-primary glyphicon glyphicon-remove btn-danger','title'=>Yii::t('frontend','Cancel')]) ?>
20
21
          <?= Html::a('', ['update', 'id' => $model->id], ['class' => 'btn btn-primary glyphicon glyphicon-pencil','title'=>'Edit']) ?>
22
          </div>
23
        </div>
24
    </div> <!-- end row -->
25
    </div>
26
   </div>

Jede Schaltfläche wird mit den HTML-Hilfs- und Bootstrap-Schaltflächenstilen von Yii erstellt:

1
<?= Html::a(Yii::t('frontend', 'Send'), ['finalize', 'id' => $model->id], ['class' => 'btn btn-primary '.(!$model->isReadyToSend?'disabled':'')]) ?>

Für die Schaltflächen zum Abbrechen und Bearbeiten von Eigenschaften habe ich Glyphicons verwendet. Glyphicons sind wunderschön und frei in Bootstrap enthalten und in Yii2 integriert.

Was machen diese Befehle?

Sobald der Benutzer einen Teilnehmer und mindestens einen Ort sowie Datum und Uhrzeit hinzugefügt hat, kann er die Einladung senden. Diese Funktion sendet dem Benutzer eine Besprechungseinladung per E-Mail, die ich in Kürze in einem Tutorial beschreiben werde.

Über die Schaltfläche Finalisieren kann der Organisator (oder Teilnehmer) den Status des Meetings von "Planung" auf "Bevorstehend" ändern. Die Idee ist, dass nach Auswahl eines Ortes und einer Uhrzeit das Meeting "abgeschlossen" werden kann. Zuvor hat der Teilnehmer die Möglichkeit, optional andere Orte und Datumsangaben vorzuschlagen, und der Veranstalter (oder beide) hat die Möglichkeit, den endgültigen Ort und die Datums- und Uhrzeitangabe auszuwählen.

Mit der Schaltfläche Abbrechen wird die Besprechung abgebrochen und auf die Registerkarte Abgebrochen auf der Seite Besprechungen verschoben.

Teilnehmer

Als nächstes fügt der Benutzer Personen hinzu.

Meeting Planner Participant PanelMeeting Planner Participant PanelMeeting Planner Participant Panel

Hinweis: In meinem Produkt mit minimaler Lebensfähigkeit ist nur ein Teilnehmer zulässig, wir können jedoch später weitere hinzufügen.

Wenn Sie sich an die Freundes-Tabelle erinnern, die wir in einem früheren Lernprogramm erstellt haben, können Benutzer eine neue E-Mail-Adresse eingeben oder ihre Eingabe beschleunigen, indem die automatische Vervollständigung aus ihren vorhandenen Freunden und früheren Besprechungen geladen wird.

Meeting Planner invite a participant with autocompleteMeeting Planner invite a participant with autocompleteMeeting Planner invite a participant with autocomplete

In Zukunft werden wir hier mehr Optionen für die Benutzeroberfläche haben - einschließlich häufiger Teilnehmer.

Oben auf dem Teilnehmer-Controller laden wir die Freunde in ein Array, das vom JQuery-Widget für die automatische Vervollständigung verwendet wird. Die Unterstützung ist wiederum in Yii2 integriert:

1
/**

2
     * Creates a new Participant model.

3
     * If creation is successful, the browser will be redirected to the 'view' page.

4
     * @return mixed

5
     */
6
    public function actionCreate($meeting_id)
7
    {
8
      $mtg = new Meeting();
9
      $title = $mtg->getMeetingTitle($meeting_id);
10
        $model = new Participant();
11
        $model->meeting_id= $meeting_id;
12
        $model->invited_by= Yii::$app->user->getId();
13
        // load friends for auto complete field

14
        $friends = Friend::getFriendList(Yii::$app->user->getId());

Hier ist die _form.php in \frontend\views\participant:

1
<div class="participant-form">
2
    <?php $form = ActiveForm::begin(); ?>
3
    
4
    <?= $form->errorSummary($model); ?>
5
    
6
    <p>Email address:</p>
7
    <?php 
8
      // preload friends into array

9
      echo yii\jui\AutoComplete::widget([
10
          'model' => $model,
11
          'attribute' => 'email',
12
          'clientOptions' => [
13
          'source' => $friends,
14
           ],
15
          ]);        
16
    ?>

Ich habe die Entwurfsentscheidung getroffen, alle Teilnehmer in der Benutzertabelle zu speichern. Ich kann das bereuen - noch nicht sicher. Dies vereinfacht jedoch den Prozess, die es den Nutzern ermöglicht, die Site schnell zu nutzen, erheblich und vereinfacht das gesamte Datenmodell.

Wenn ein Benutzer jemanden einlädt, der dem System unbekannt ist (eine neue E-Mail-Adresse), registrieren wir ihn passiv in der Benutzertabelle.

1
if ($model->load(Yii::$app->request->post())) {
2
          if (!User::find()->where( [ 'email' => $model->email ] )->exists()) {
3
            // if email not already registered

4
            //  create new user with temporary username & password

5
            $temp_email_arr[] = $model->email;
6
            $model->username = Inflector::slug(implode('-', $temp_email_arr));
7
            $model->password = Yii::$app->security->generateRandomString(12);
8
            $model->participant_id = $model->addUser();
9
          } else {
10
            // add participant from user record

11
            $usr = User::find()->where( [ 'email' => $model->email ] )->one();
12
            $model->participant_id = $usr->id;
13
          }
14
          // validate the form against model rules

15
          if ($model->validate()) {
16
              // all inputs are valid

17
              $model->save();              
18
              return $this->redirect(['/meeting/view', 'id' => $meeting_id]);
19
          } 

Wir erstellen einen Benutzernamen basierend auf ihrer E-Mail-Adresse. Ich benutze Yiis Schneckengenerator in seinem Inflector-Helfer. Wir erstellen vorerst ein zufälliges Passwort mit dem Sicherheitshelfer von Yii. Wenn ich Vanilla PHP verwenden würde, müsste ich wahrscheinlich andere Funktionen für diese Funktionen integrieren. Stattdessen ist es direkt eingebaut.

Fahren wir mit dem Hinzufügen von Orten fort.

Setzt

Die Verwendung der MVC von Yii für jeden Controller und jedes Modell bietet große Vorteile, anstatt all diese Funktionen in den Meeting-Controller zu codieren. Dies macht das Verstehen und Verwalten des Codes viel einfacher und übersichtlicher.

Ich bemerkte jedoch schnell, dass ich die Standard-Breadcrumbs anpassen musste, um auf die aktuelle Besprechungsseite und nicht auf den Index oder die Ansicht für ein bestimmtes Modell zurückzugreifen.

Add a Meeting Place BreadcrumbsAdd a Meeting Place BreadcrumbsAdd a Meeting Place Breadcrumbs

Wir verwenden das MeetingPlace-Modell, um Besprechungen Orte hinzuzufügen. In \frontend\views\meet-place\create.php musste ich einfach die Links im breadcrumbs-Bereich anpassen:

1
<?php
2
3
use yii\helpers\Html;
4
5
/* @var $this yii\web\View */
6
/* @var $model frontend\models\MeetingPlace */
7
8
$this->title = Yii::t('frontend', 'Add a {modelClass}', [
9
    'modelClass' => 'Meeting Place',
10
]);
11
$this->params['breadcrumbs'][] = ['label' => Yii::t('frontend', 'Meetings'), 'url' => ['/meeting/index']];
12
13
$this->params['breadcrumbs'][] = ['label'=>$title,'url' => ['/meeting/view', 'id' => $model->meeting_id]];
14
$this->params['breadcrumbs'][] = $this->title;
15
16
?>

Unterstützung für das direkte Hinzufügen von Google Places hinzufügen

Add a Meeting Place from Your Places or via Google Places AutocompleteAdd a Meeting Place from Your Places or via Google Places AutocompleteAdd a Meeting Place from Your Places or via Google Places Autocomplete

Ich wollte nicht nur das Formular zur Ortserstellung anpassen, damit der Benutzer zuvor verwendete Orte hinzufügen kann, sondern auch, um neue Google Places im laufenden Betrieb hinzuzufügen.

Grundsätzlich musste ich die Unterstützung, die wir im Google Places-Tutorial hier in der MeetingPlace-Erstellung erstellt haben, replizieren:

1
<?php
2
use yii\helpers\ArrayHelper;
3
use yii\helpers\BaseHtml;
4
use yii\helpers\Html;
5
use yii\widgets\ActiveForm;
6
use frontend\models\UserPlace;
7
8
use frontend\assets\MapAsset;
9
MapAsset::register($this);
10
11
/* @var $this yii\web\View */
12
/* @var $model frontend\models\MeetingPlace */
13
/* @var $form yii\widgets\ActiveForm */
14
?>
15
16
<div class="meeting-place-form">
17
18
    <?php $form = ActiveForm::begin(); ?>
19
20
    <?= $form->errorSummary($model); ?>
21
22
    <h3>Choose one of your places</h3>
23
    <div class="row">
24
      <div class="col-md-6">
25
    <?= Html::activeDropDownList($model, 'place_id',
26
          ArrayHelper::map(UserPlace::find()->all(), 'place.id', 'place.name'),['prompt'=>Yii::t('frontend','-- select one of your places below --')] ) ?>                    
27
    <h3>- or -</h3>
28
    <h3>Choose from Google Places</h3>
29
      <p>Type in a place or business known to Google Places:</p>
30
        <?= $form->field($model, 'searchbox')->textInput(['maxlength' => 255])->label('Place') ?>
31
      </div>
32
      <div class="col-md-6">
33
        <div id="map-canvas">
34
          <article></article>
35
        </div>
36
      </div>
37
      </div> <!-- end row -->
38
        <?= BaseHtml::activeHiddenInput($model, 'name'); ?>
39
        <?= BaseHtml::activeHiddenInput($model, 'google_place_id'); ?>
40
        <?= BaseHtml::activeHiddenInput($model, 'location'); ?>
41
        <?= BaseHtml::activeHiddenInput($model, 'website'); ?>
42
        <?= BaseHtml::activeHiddenInput($model, 'vicinity'); ?>
43
        <?= BaseHtml::activeHiddenInput($model, 'full_address'); ?>
44
    <div class="clearfix"></div>
45
    <div class="row vertical-pad">
46
      <div class="form-group">      
47
          <?= Html::submitButton($model->isNewRecord ? Yii::t('frontend', 'Add Place') : Yii::t('frontend', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
48
      </div>
49
    </div>
50
51
    <?php ActiveForm::end(); ?>
52
53
</div>

Ich musste auch die ausgefeilte Validierungsunterstützung von Yii2 stärker nutzen. Im folgenden MeetingPlace-Modell verwenden wir die eindeutige Validierung von Yii2, um einen Fehler zu melden, wenn jemand versucht, einen bereits vorgeschlagenen Ort für ein Meeting zu finden:

1
    public function rules()
2
    {
3
        return [
4
            [['meeting_id', 'place_id', 'suggested_by'], 'required'],
5
            [['meeting_id', 'place_id', 'suggested_by', 'status', 'created_at', 'updated_at'], 'integer'],
6
            [['place_id'], 'unique', 'targetAttribute' => ['place_id','meeting_id'], 'message'=>Yii::t('frontend','This place has already been suggested.')],

Ich habe außerdem eine benutzerdefinierte Fehlerbedingung in der MeetingPlaceController-Erstellungsaktion hinzugefügt, wenn ein Nutzer Orte aus seiner Liste sowie Google Place auswählt - obwohl dies möglicherweise eine optionale Funktion ist, die beibehalten werden soll (haben Sie eine Meinung? Beitrag in den Kommentaren unten):

1
     public function actionCreate($meeting_id)
2
     {
3
       $mtg = new Meeting();
4
       $title = $mtg->getMeetingTitle($meeting_id);
5
         $model = new MeetingPlace();
6
         $model->meeting_id= $meeting_id;
7
         $model->suggested_by= Yii::$app->user->getId();
8
         $model->status = MeetingPlace::STATUS_SUGGESTED;
9
         $posted_form = Yii::$app->request->post(); 
10
         if ($model->load($posted_form)) {
11
          // check if both are chosen and return an error

12
           if ($model->place_id<>'' and $posted_form['MeetingPlace']['google_place_id']<>'') {    
13
             $model->addErrors(['place_id'=>Yii::t('frontend','Please choose one or the other')]);
14
             return $this->render('create', [
15
                  'model' => $model,
16
                   'title' => $title,
17
              ]);             
18
           }

Ich habe die addErrors-Methode von Yii2 verwendet.

Ich habe auch einen Fehler aus Episode drei behoben, bei dem mehrere Kartenfelder erstellt wurden, wenn jemand die Google Place-Auswahl änderte. Die Überprüfung der Anzahl der in der article-Auswahl vorhandenen Kinder hat Folgendes behoben:

1
function loadMap(gps,name) {
2
  if (document.querySelector('article').children.length==0) {
3
    var mapcanvas = document.createElement('div');
4
    mapcanvas.id = 'mapcanvas';
5
    mapcanvas.style.height = '300px';
6
    mapcanvas.style.width = '300px';
7
    mapcanvas.style.border = '1px solid black';    
8
    document.querySelector('article').appendChild(mapcanvas);  
9
  }

In Zukunft wird dieses Erstellungsformular eine Reihe wichtiger Funktionen enthalten:

  • Ermöglichen Sie Benutzern, ihre aktuelle Geolokalisierung hinzuzufügen.
  • Schlagen Sie vom Benutzer vorgeschlagene häufige Orte vor.
  • Bieten Sie schnellen Zugriff auf die Lieblingsorte des Benutzers.
  • Schlagen Sie Orte in der Nähe (in gleichem Abstand) des Benutzers und der Teilnehmer vor.
  • Schlagen Sie gesponserte Plätze von bezahlten Werbetreibenden vor.
  • Ermöglichen, dass Orte und Datums- und Uhrzeitangaben vom Veranstalter entfernt werden - möglicherweise unter der Bedingung, dass sie von den Teilnehmern nicht gesehen oder beantwortet wurden.

Es kann auch nützlich sein, Benutzern das Notieren bestimmter Orte und Datumszeiten zu ermöglichen. Zum Beispiel könnte ich bezeichnen, dass "dieser Ort am Donnerstagmorgen gut für mich funktioniert, aber nicht am Freitagnachmittag" oder "wenn Sie diesmal wählen, können wir es im Caffe Vita auf dem Capitol Hill tun". Wenn Sie eine Meinung zu dieser Funktion haben (was die Komplexität erhöhen würde), geben Sie unten einen Kommentar ab.

Anzeigen der Panels

Für jedes Modell verwenden wir eine ähnliche Hierarchie von Ansichten und Yii2-Komponenten. Der Meeting Controller rendert die Ansicht für _panel.php in \frontend\views\meeting-place:

1
<?php
2
use yii\helpers\Html;
3
use yii\widgets\ListView;
4
?>
5
<div class="panel panel-default">
6
  <!-- Default panel contents -->
7
  <div class="panel-heading">
8
    <div class="row">
9
      <div class="col-lg-6"><h4><?= Yii::t('frontend','Places') ?></h4></div>
10
      <div class="col-lg-6" ><div style="float:right;"><?= Html::a('', ['meeting-place/create', 'meeting_id' => $model->id], ['class' => 'btn btn-primary glyphicon glyphicon-plus']) ?></div>
11
    </div>
12
  </div>
13
  </div>
14
15
  <?php
16
   if ($placeProvider->count>0): 
17
  ?>
18
  <table class="table">
19
     <thead>
20
     <tr class="small-header">
21
       <td></td>
22
       <td ><?=Yii::t('frontend','You') ?></td>
23
        <td ><?=Yii::t('frontend','Them') ?></td>
24
        <td >
25
          <?php
26
           if ($placeProvider->count>1) echo Yii::t('frontend','Choose');
27
          ?>    </tr>
28
    </thead>
29
    <?= ListView::widget([ 
30
           'dataProvider' => $placeProvider, 
31
           'itemOptions' => ['class' => 'item'], 
32
           'layout' => '{items}',
33
           'itemView' => '_list',
34
           'viewParams' => ['placeCount'=>$placeProvider->count],
35
       ]) ?>
36
  </table>
37
    
38
  <?php else: ?>
39
  <?php endif; ?>
40
41
</div>

Der Umriss der Bootstrap-kompatiblen Tabelle befindet sich in _panel.php. Anschließend verwenden wir das Widget Yii2 Listview, um jede Datenzeile in Tabellenform anzuzeigen. Der itemView-Teil befindet sich in _list.php.

Beachten Sie, dass wir eine benutzerdefinierte Variable namens placeCount über viewParams übergeben. Dies ist praktisch zum Konfigurieren der Schaltflächen in der Tabelle.

Hier ist die Ansicht _list.php, die ich im nächsten Tutorial ausführlicher behandeln werde, einschließlich der Widgets für die Schaltereingabe und der AJAX-Implementierung.

1
<?php
2
use yii\helpers\Html;
3
use yii\helpers\BaseUrl;
4
use \kartik\switchinput\SwitchInput;
5
6
?>
7
8
<tr > 
9
  <td style >
10
        <?= Html::a($model->place->name,BaseUrl::home().'/place/'.$model->place->slug) ?>
11
  </td>
12
  <td style>
13
      <?
14
      foreach ($model->meetingPlaceChoices as $mpc) {
15
        if ($mpc->user_id == $model->meeting->owner_id) {
16
            if ($mpc->status == $mpc::STATUS_YES)
17
              $value = 1;
18
            else
19
              $value =0;
20
              echo SwitchInput::widget([
21
              'type'=>SwitchInput::CHECKBOX,
22
              'name' => 'meeting-place-choice',
23
              'id'=>'mpc-'.$mpc->id,          
24
              'value' => $value,
25
              'pluginOptions' => ['size' => 'mini','onText' => '<i class="glyphicon glyphicon-ok"></i>','offText'=>'<i class="glyphicon glyphicon-remove"></i>','onColor' => 'success','offColor' => 'danger',],
26
              ]);          
27
        }
28
      }      
29
      ?>
30
  </td>
31
  <td style>
32
    <?
33
  foreach ($model->meetingPlaceChoices as $mpc) {
34
    if (count($model->meeting->participants)==0) break;    
35
    if ($mpc->user_id == $model->meeting->participants[0]->participant_id) {
36
        if ($mpc->status == $mpc::STATUS_YES)
37
          $value = 1;
38
        else if ($mpc->status == $mpc::STATUS_NO)
39
          $value =0;
40
        else if ($mpc->status == $mpc::STATUS_UNKNOWN)
41
          $value =-1;
42
            echo SwitchInput::widget([
43
          'type'=>SwitchInput::CHECKBOX,         
44
          'name' => 'meeting-place-choice',
45
          'id'=>'mpc-'.$mpc->id,          
46
          'tristate'=>true,
47
          'indeterminateValue'=>-1,
48
          'indeterminateToggle'=>false,
49
          'disabled'=>true,
50
          'value' => $value,
51
          'pluginOptions' => ['size' => 'mini','onText' => '<i class="glyphicon glyphicon-ok"></i>','offText'=>'<i class="glyphicon glyphicon-remove"></i>','onColor' => 'success','offColor' => 'danger'],
52
      ]);          
53
    }
54
  }
55
    ?>
56
  </td>
57
  <td style>
58
      
59
      <?
60
      if ($placeCount>1) {
61
        if ($model->status == $model::STATUS_SELECTED) {
62
            $value = $model->id;
63
        }    else {
64
          $value = 0;        
65
        } 
66
        echo SwitchInput::widget([
67
          'type' => SwitchInput::RADIO,
68
          'name' => 'place-chooser',
69
            'items' => [
70
                [ 'value' => $model->id],
71
            ],
72
            'value' => $value,
73
            'pluginOptions' => [  'size' => 'mini','handleWidth'=>60,'onText' => '<i class="glyphicon glyphicon-ok"></i>','offText'=>'<i class="glyphicon glyphicon-remove"></i>'],
74
            'labelOptions' => ['style' => 'font-size: 12px'],
75
        ]);              
76
      }
77
      ?>
78
  </td>
79
</tr>

Daten & Zeiten

Um Daten und Zeiten hinzuzufügen, integrieren wir die Bootstrap JQuery Date Time-Auswahl über die Erweiterung 2Amigos Yii2 Date Time

MeetingPlanner Suggest a Meeting TimeMeetingPlanner Suggest a Meeting TimeMeetingPlanner Suggest a Meeting Time
1
<?php
2
3
use yii\helpers\Html;
4
use yii\widgets\ActiveForm;
5
use dosamigos\datetimepicker\DateTimePicker;
6
7
/* @var $this yii\web\View */
8
/* @var $model frontend\models\MeetingTime */
9
/* @var $form yii\widgets\ActiveForm */
10
?>
11
12
<div class="meeting-time-form">
13
14
  <div class="row">
15
    <div class="col-md-4">
16
    <?php $form = ActiveForm::begin(); ?>
17
18
    <?= DateTimePicker::widget([
19
        'model' => $model,
20
        'attribute' => 'start',
21
        'language' => 'en',
22
        'size' => 'ms',
23
        'clientOptions' => [
24
            'autoclose' => true,
25
            'format' => 'MM dd, yyyy HH:ii P',
26
            'todayBtn' => true,
27
            'minuteStep'=> 15, 
28
            'pickerPosition' => 'bottom-left',
29
        ]
30
    ]);?>   
31
    </div>
32
  </div>  
33
  <div class="clearfix"><p></div>
34
  <div class="row">
35
      <div class="col-md-4">
36
     <div class="form-group">
37
        <?= Html::submitButton($model->isNewRecord ? Yii::t('frontend', 'Add') : Yii::t('frontend', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
38
    </div>
39
    </div>
40
  </div>
41
    <?php ActiveForm::end(); ?>

Es gibt einige Verbesserungen, die ich in Zukunft an diesem Widget vornehmen möchte. Ich möchte es beim Laden automatisch öffnen, für das es derzeit keine Einstellung zu geben scheint.

Auch hier verwenden wir den eindeutigen Validator, um sicherzustellen, dass das bestimmte Datum und die Uhrzeit noch nicht zum Meeting hinzugefügt wurden:

1
public function rules()
2
    {
3
        return [
4
            [['meeting_id', 'start', 'suggested_by'], 'required'],
5
            [['meeting_id', 'start', 'suggested_by', 'status', 'created_at', 'updated_at'], 'integer'],
6
            [['start'], 'unique', 'targetAttribute' => ['start','meeting_id'], 'message'=>Yii::t('frontend','This date and time has already been suggested.')],
7
            
8
        ];
9
    }

Auf der Seite "Besprechungsansicht" wird das Bedienfeld "Daten und Zeiten" ähnlich wie "Orte" erstellt:

1
<?php
2
use yii\helpers\Html;
3
use yii\widgets\ListView;
4
?>
5
<div class="panel panel-default">
6
  <!-- Default panel contents -->
7
  <div class="panel-heading"><div class="row"><div class="col-lg-6"><h4><?= Yii::t('frontend','Dates &amp; Times') ?></h4></div><div class="col-lg-6" ><div style="float:right;"><?= Html::a(Yii::t('frontend', ''), ['meeting-time/create', 'meeting_id' => $model->id], ['class' => 'btn btn-primary  glyphicon glyphicon-plus']) ?></div></div></div></div>
8
9
  <?php
10
   if ($timeProvider->count>0): 
11
  ?>
12
  <!-- Table -->
13
  <table class="table">
14
     <thead>
15
     <tr class="small-header">
16
       <td></td>
17
       <td ><?=Yii::t('frontend','You') ?></td>
18
       <td ><?=Yii::t('frontend','Them') ?></td>
19
       <td >
20
         <?php
21
          if ($timeProvider->count>1) echo Yii::t('frontend','Choose');
22
         ?>
23
        </td>
24
    </tr>
25
    </thead>
26
    <?= ListView::widget([ 
27
           'dataProvider' => $timeProvider, 
28
           'itemOptions' => ['class' => 'item'], 
29
           'layout' => '{items}',
30
           'itemView' => '_list', 
31
           'viewParams' => ['timeCount'=>$timeProvider->count],           
32
       ]) ?>
33
  </table>
34
  <?php else: ?>
35
  <?php endif; ?>
36
</div>

Hier ist die Ansicht _list.php:

1
<?php
2
use yii\helpers\Html;
3
use frontend\models\Meeting;
4
use \kartik\switchinput\SwitchInput;
5
?>
6
7
<tr > 
8
  <td style >
9
        <?= Meeting::friendlyDateFromTimestamp($model->start) ?>
10
  </td>
11
  <td style>
12
      <?
13
      foreach ($model->meetingTimeChoices as $mtc) {
14
        if ($mtc->user_id == $model->meeting->owner_id) {
15
            if ($mtc->status == $mtc::STATUS_YES)
16
              $value = 1;
17
            else
18
              $value =0;
19
              echo SwitchInput::widget([
20
              'type' => SwitchInput::CHECKBOX,              
21
              'name' => 'meeting-time-choice',
22
              'id'=>'mtc-'.$mtc->id,
23
              'value' => $value,
24
              'pluginOptions' => ['size' => 'mini','onText' => '<i class="glyphicon glyphicon-ok"></i>','offText'=>'<i class="glyphicon glyphicon-remove"></i>','onColor' => 'success','offColor' => 'danger',],
25
              ]);          
26
        }
27
      }
28
      ?>
29
  </td>
30
  <td style>
31
    <?
32
    foreach ($model->meetingTimeChoices as $mtc) {
33
      if (count($model->meeting->participants)==0) break;
34
      if ($mtc->user_id == $model->meeting->participants[0]->participant_id) {
35
          if ($mtc->status == $mtc::STATUS_YES)
36
            $value = 1;
37
          else if ($mtc->status == $mtc::STATUS_NO)
38
            $value =0;
39
          else if ($mtc->status == $mtc::STATUS_UNKNOWN)
40
            $value =-1;
41
          echo SwitchInput::widget([
42
            'type' => SwitchInput::CHECKBOX,          
43
            'name' => 'meeting-time-choice',
44
            'id'=>'mtc-'.$mtc->id,
45
            'tristate'=>true,
46
            'indeterminateValue'=>-1,
47
            'indeterminateToggle'=>false,
48
            'disabled'=>true,
49
            'value' => $value,
50
            'pluginOptions' => ['size' => 'mini','onText' => '<i class="glyphicon glyphicon-ok"></i>','offText'=>'<i class="glyphicon glyphicon-remove"></i>','onColor' => 'success','offColor' => 'danger',],
51
        ]);          
52
      }
53
    }
54
    ?>
55
  </td>
56
  <td style>
57
      <?
58
      if ($timeCount>1) {
59
        if ($model->status == $model::STATUS_SELECTED) {
60
            $value = $model->id;
61
        }    else {
62
          $value = 0;        
63
        } 
64
        echo SwitchInput::widget([
65
            'type' => SwitchInput::RADIO,
66
            'name' => 'time-chooser',
67
            'items' => [
68
                [ 'value' => $model->id],
69
            ],
70
            'value' => $value,
71
            'pluginOptions' => [  'size' => 'mini','handleWidth'=>60,'onText' => '<i class="glyphicon glyphicon-ok"></i>','offText'=>'<i class="glyphicon glyphicon-remove"></i>'],
72
            'labelOptions' => ['style' => 'font-size: 12px'],
73
        ]);            
74
      }
75
      ?>
76
  </td>
77
</tr>
78

Anmerkungen

Mithilfe von Besprechungsnotizen können Benutzer nach Belieben kommunizieren und Orte und Datums- und Uhrzeitangaben auswählen, ohne sich gegenseitig separat per E-Mail benachrichtigen zu müssen.

Add a Meeting NoteAdd a Meeting NoteAdd a Meeting Note

So sehen Notizen auf der Besprechungsseite aus:

Meeting View Page with Meeting NoteMeeting View Page with Meeting NoteMeeting View Page with Meeting Note

Die Implementierung von Notizen ist nahezu identisch mit der obigen Implementierung von Orten und Datumsangaben. Weitere Informationen finden Sie in den Dateien MeetingNote-Controller und \frontend\views\meeting-note-Ansicht.

Was kommt als nächstes?

Ich hoffe, Sie haben mit diesem Tutorial etwas Neues gelernt. Achten Sie auf kommende Tutorials in meiner Reihe Erstellen Sie Ihr Startup mit PHP - es stehen viele unterhaltsame Funktionen an.

Im nächsten Tutorial werde ich mich eingehend mit der Implementierung der Optionen für Ort und Datum und Uhrzeit mit AJAX befassen. Danach beginnen wir mit dem Erstellen von E-Mail-Nachrichten, um die Nachrichteneinladungen zuzustellen, die Antworten der Teilnehmer auf der Zeitplanseite zu sammeln und Besprechungen für die reale Welt abzuschließen.

Bitte zögern Sie nicht, Ihre Fragen und Kommentare unten hinzuzufügen; Ich nehme in der Regel an den Diskussionen teil. Sie können mich auch auf Twitter @reifman erreichen oder mir direkt eine E-Mail senden.

ähnliche Links

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.