Ce tutoriel fait partie de la série Construire son démarrage avec PHP sur Tuts +. Dans cette série, je vous guide dans le lancement d'une startup du concept à la réalité en utilisant mon application Meeting Planner comme exemple concret. À chaque étape, je publierai le code de Meeting Planner sous forme d’exemples open source à partir desquels vous pourrez apprendre. Je traiterai également les problèmes liés au démarrage au fur et à mesure qu'ils surviennent.
Tout le code de Meeting Planner est écrit dans le framework Yii2 pour PHP. Si vous souhaitez en savoir plus sur Yii2, consultez mes séries parallèles Programming With Yii2 at Tuts +. Vous pouvez également consulter mon site de base de connaissances pour les questions Yii2, The Yii2 Developer Exchange.
Le codage de la fonctionnalité de réunion programmée s'étend sur au moins quatre épisodes. Il s'agit du deuxième épisode de ces quatre épisodes. Il s'agit d'ajouter AJAX à la page de planification afin de permettre aux utilisateurs de définir leur disponibilité et de choisir des lieux, des dates et des heures. Si vous avez manqué le didacticiel précédent sur la planification d'une réunion, veuillez le lire et le lire avant de continuer..
Dans le prochain tutoriel, nous couvrirons la livraison de la demande de réunion par courrier électronique. Nous y reviendrons plus tard pour optimiser et peaufiner l'interface utilisateur, car elle est essentielle au succès de ce produit..
La vue du calendrier des réunions était relativement complexe à coder. La disposition visuelle des colonnes de la table ne concerne pas directement la façon dont nous stockons les données connexes dans notre base de données et notre schéma. Heureusement, chaque réunion n'a pas un grand ensemble de données d'options lieu et date, cela ne pose donc pas de problème de performances particulier..
Dans notre schéma, la disponibilité des lieux pour les organisateurs et les participants (c.-à-d. Si un lieu leur convient pour cette réunion) est stockée dans la MeetingPlaceChoice
table. En utilisant notre modèle relationnel, chaque réunion a beaucoup Lieux de rencontre
qui a beaucoup MeetingPlaceChoices
.
Ne confondez pas le MeetingPlaceChoice
table avec la sélection finale pour une place stockée dans MeetingPlace-> status
.
Le tableau ci-dessus apparaîtra différemment lorsque l'organisateur l'affiche:
À partir du moment où le participant le voit:
Voyons maintenant comment implémenter ces tables dans les vues.
Pour l'instant, j'ai choisi d'afficher chaque zone, par exemple lieu ou date et heure, dans son propre panneau Bootstrap avec des tables.
Dans \ frontend \ views \ meeting \ view.php
, vous verrez l'inclusion pour le panneau Lieux comme ceci:
= $this->render ('… / meeting-place / _panel', ['model' => $ model, 'placeProvider' => $ placeProvider,])?>
Voici une partie de la lieu de rencontre
fichier de vue de panneau. Ceci configure la grille de la table et inclut un widget en vue liste pour afficher les lignes:
compte> 0):?>= Yii::t('frontend','Places') ?>
= Html::a(", ['meeting-place/create', 'meeting_id' => $ model-> id], ['class' => 'btn btn-primaire glyphicon glyphicon-plus'])?>
= ListView::widget([ 'dataProvider' => $ placeProvider, 'itemOptions' => ['class' => 'item'], 'layout' => 'items', 'itemView' => '_list', 'viewParams' => ['placeCount' => $ placeProvider-> count],])?> =Yii::t('frontend','You') ?> =Yii::t('frontend','Them') ?> count> 1) echo Yii :: t ('frontend', 'Choose'); ?>
Regardons de plus près le lieu de rencontre
voir la liste.
Yii Listview affichera une ligne de données pour chaque lieu. Le code fonctionne presque à l'identique pour les dates.
J'utilise le widget d'entrée de commutateur Yii2 de Krajee pour le commutateur d'amorçage à la place des cases à cocher ennuyeuses et des listes déroulantes:
J'aime la manière dont l'option tri-state nous permet de montrer aux participants un état unique avant de faire leur sélection. cela nous permet également de montrer à l'organisateur que le participant n'a pas encore fait de sélection.
Passons en revue le code colonne par colonne. Voici le panneau et la table Place que nous mettons en place:
Dans la première colonne, j'utilise l'assistant de lien Yii Html pour créer un lien direct entre le nom du lieu et sa propre page de vue. Remarquez comment nous utilisons le slug de lieu..
= Html::a($model->place-> name, BaseUrl :: home (). '/ place /'.$ model-> place-> slug)?> La colonne organisateur
Pour trouver les sélections de l'organisateur, nous parcourons le tableau de
MeetingPlaceChoices
, correspondant àidentifiant d'utilisateur
àréunion-> propriétaire_id
:foreach ($model->meetingPlaceChoices as $ mpc) if ($ mpc-> id_utilisateur == $ modèle-> meeting-> id_propriétaire) if ($ mpc-> état == $ mpc :: STATUS_YES) $ valeur = 1; sinon $ valeur = 0; echo SwitchInput :: widget (['type' => SwitchInput :: CHECKBOX, 'nom' => 'choix du lieu de réunion', 'id' => 'mpc -'. $ mpc-> id, 'valeur' = > $ value, 'pluginOptions' => ['size' => 'mini', 'onText' => '',' offText '=>'',' onColor '=>' success ',' offColor '=>' danger ',],]); ?> Pour sélectionner votre disponibilité à un endroit spécifique, nous utilisons le mode de case à cocher de l'entrée du commutateur, à savoir que cet endroit fonctionne pour vous (activé) ou non (désactivé)..
le
valeur
Cette propriété active le commutateur. L'identifiant correspondant auMeetingPlaceChoice-> id
est utilisé pour AJAX ci-dessous pour identifier ce commutateur particulier.Vous remarquerez peut-être aussi que nous utilisons des glyphicons pour oui et non à la place des étiquettes..
La colonne du participant
Le code du participant implémente des commutateurs à trois états. c’est-à-dire que cet endroit fonctionne pour vous (on), il n’est pas (off) ou vous n’avez pas encore indiqué (indéterminé):
foreach ($model->meetingPlaceChoices as $ mpc) if (nombre ($ modèle-> réunion-> participants) == 0) pause; if ($ mpc-> user_id == $ modèle-> réunion-> participants [0] -> participant_id) if ($ mpc-> état == $ mpc :: STATUS_YES) $ valeur = 1; else if ($ mpc-> statut == $ mpc :: STATUS_NO) $ valeur = 0; else if ($ mpc-> statut == $ mpc :: STATUS_UNKNOWN) $ valeur = -1; echo SwitchInput :: widget (['type' => SwitchInput :: CHECKBOX, 'nom' => 'choix du lieu de réunion', 'id' => 'mpc -'. $ mpc-> id, 'tristate' = > true, 'indeterminateValue' => - 1, 'indeterminateToggle' => false, 'disabled' => true, 'value' => $ valeur, 'pluginOptions' => ['size' => 'mini', 'onText '=>'',' offText '=>'',' onColor '=>' succès ',' offColor '=>' danger '],]); ?> Lorsque nous ajoutons un support pour les réunions permettant au participant de suggérer des lieux et des dates, nous ajoutons également des widgets à trois états à la colonne de l'organiseur..
Affichage de l’interrupteur Choisir l’heure et la date
Si l'organisateur visualise la réunion, nous lui permettons de choisir le lieu et la date de la réunion finale. Bientôt, nous ajouterons également un support pour les réunions afin de permettre au participant de choisir ces options..
Dans ce cas, l'utilisateur effectue une sélection sur plusieurs lignes (en choisissant l'un des emplacements répertoriés). Cela nécessite l'utilisation de l'entrée du commutateur en mode bouton radio. Pour les événements AJAX pour les sélecteurs, nous pouvons simplement écouter le nom property-no id is required, car une seule sélection est possible pour le panneau..
Je voulais aussi que le commutateur de choix soit différent des commutateurs de disponibilité, je les ai donc élargis et utilisé des couleurs différentes..
if ($placeCount>1) if ($ model-> status == $ model :: STATUS_SELECTED) $ value = $ model-> id; else $ value = 0; echo SwitchInput :: widget (['type' => SwitchInput :: RADIO, 'nom' => 'sélecteur de lieu', 'items' => [['valeur' => $ model-> id],], 'valeur' => $ valeur, 'pluginOptions' => ['size' => 'mini', 'handleWidth' => 60, 'onText' => '',' offText '=>''],' labelOptions '=> [' style '=>' taille de police: 12px '],]); ?>
Maintenant, je vais vous expliquer comment nous avons implémenté le support AJAX pour tous ces sélecteurs..
Évidemment, je voulais éviter d’obliger les utilisateurs à enregistrer les modifications apportées à ces formulaires. Au lieu de cela, je voulais que les commutateurs changent d'état via AJAX sans rafraîchissement de la page..
Le code est divisé entre la configuration d'écouteurs d'événements pour réagir aux changements d'état et les actions du contrôleur pour enregistrer les changements dans notre base de données. C'est également légèrement différent pour les commutateurs à cocher par rapport aux commutateurs radio.
Nous créons des écouteurs d'événements pour exécuter du code chaque fois que l'état d'un bouton est modifié. L'événement d'écoute est un code JavaScript généré par PHP dans la vue du panneau (pour le tableau complet des options)..
Voici le code au bas de \ frontend \ views \ meeting-place \ _panel.php
:
id, 'val': e.target.value, // e.target.value est sélectionné Succès du modèle MeetingPlaceChoice: function (data) return true; ); );… JS; $ position = \ yii \ web \ View :: POS_READY; $ this-> registerJs ($ script, $ position); ?>
En passant, si quelqu'un peut me dire le nom du raccourci JS pour PHP, postez-le dans la section commentaires. J'aimerais savoir. Certaines choses sont difficiles à rechercher.
le enregistrerJs
fonction dans Yii rend le script pour un particulier $ position
sur la page. Dans ce cas, il s'agit d'un événement prêt à l'emploi.
Le code ci-dessus configure les événements du programme d'écoute pour tous les boutons radio du sélecteur de lieu pour tous les lieux par la propriété name. La valeur cible de l'événement représentera l'identifiant du lieu de réunion choisi. Je vais parler plus de la fonction AJAX dans un instant.
En d'autres termes, les événements radio de commutation répondent au choix de l'organisateur (généralement) d'un lieu ou d'une date pour finaliser la réunion, en transmettant l'identifiant du lieu de la réunion ou celui de l'heure de la réunion..
Voici le code pour écouter les changements de disponibilité à l'aide des cases à cocher de saisie du commutateur:
// les utilisateurs peuvent dire si un lieu est une option pour eux $ ('input [nom = "lieu de réunion choix"]'). on ('switchChange.bootstrapSwitch', fonction (e, s) // console. log (e.target.id, s); // true | false // configure intval pour qu'il passe via AJAX à partir de l'état booléen if (s) state = 1; sinon state = 0; $ .ajax (url: '/ mp / meetingplacechoice / set ', données: id: e.target.id,' état ': état, succès: fonction (données) retour vrai;););
L'écouteur est configuré pour tous lieu de rencontre choix
propriétés de nom, mais il doit passer l'id pour indiquer exactement lequel MeetingPlaceChoice
est en train d'être changé.
Pour clarifier, les écouteurs d'événements pour les cases à cocher de saisie permettent aux utilisateurs de dire qu'ils sont disponibles ou non pour un lieu ou une date. Ils envoient lieu de rencontre choix
identifiant ou lieu de rendez-vous
identifiant.
Voyons maintenant de plus près comment les événements AJAX appellent nos actions de contrôleur basées sur PHP pour enregistrer les changements d'état dans la base de données..
Voici à nouveau le code pour le lieu de rencontre
sélecteur de bouton radio:
$ .ajax (url: '/ mp / meetingplace / choice', données: id: $ modèle-> id, 'val': e.target.value, // e.target.value est sélectionné Succès du modèle MeetingPlaceChoice : function (data) return true;);
L'URL indique le chemin d'accès à la Lieu de rencontre
Action choisie du contrôleur:
fonction publique actionChoose ($ id, $ val) // meeting_place_id doit être défini comme actif // tout autre meeting_place_id pour cette réunion doit être défini inactif $ meeting_id = intval ($ id); $ mtg = Meeting :: find () -> Where (['id' => $ meeting_id]) -> one (); if (Yii :: $ app-> user-> getId ()! = $ mtg-> owner_id) retourne false; // à faire - vérifiez également l'ID du participant si les participants sont autorisés à choisir foreach ($ mtg-> meetingPlaces en tant que $ mp) if ($ mp-> id == intval ($ val)) $ mp-> status = MeetingPlace: : STATUS_SELECTED; else $ mp-> status = MeetingPlace :: STATUS_SUGGESTED; $ mp-> save (); return true;
Le entrant $ id
représente le meeting_id
. La valeur représente l'élu Lieu de rencontre
identifiant. STATUS_SELECTED
indique que le lieu a été choisi, alors que STATUS_SUGGESTED
indique seulement que cela a été suggéré (non choisi).
Ce code parcourt les lieux de réunion de chaque réunion et met à jour l'état du lieu sélectionné..
Examinons à nouveau le code des cases à cocher de saisie pour déterminer si une personne est disponible pour un lieu spécifique:
$ .ajax (url: '/ mp / meetingplacechoice / set', données: id: e.target.id, 'état': état, réussite: fonction (données) retour vrai;);
Ces événements appellent le MeetingPlaceChoice
Action définie du contrôleur avec une chaîne dont le suffixe contient l'identifiant de la MeetingPlaceChoice
enregistrement à mettre à jour:
public function actionSet ($ id, $ state) // caution - problèmes de type AJAX entrants avec val $ id = str_replace ('mpc -', ", $ id); $ mpc = $ this-> findModel ($ id); if (Yii :: $ app-> user-> getId ()! = $ mpc-> user_id) renvoie false; if (intval ($ state) == 0 ou $ state == 'false') $ mpc-> status = MeetingPlaceChoice :: STATUS_NO; sinon $ mpc-> status = MeetingPlaceChoice :: STATUS_YES; $ mpc-> save (); retourne $ mpc-> id;
Pour des raisons de sécurité, nous devons vérifier que la demande AJAX a été initiée par l'utilisateur réel pouvant apporter ces modifications. Ce code fait que:
if (Yii :: $ app-> user-> getId ()! = $ mtg-> owner_id) retourne false;
et
if (Yii :: $ app-> user-> getId ()! = $ mpc-> user_id) retourne false;
Sans ces vérifications, il serait facile pour un pirate informatique d'écrire un script pour modifier les paramètres de réunion pour tout le monde..
Le code AJAX pour indiquer la disponibilité pour les dates et faire des choix est presque identique.
Afin de prendre en charge toutes les fonctionnalités ci-dessus, nous devons également ajouter du code qui ajoute des enregistrements à la liste. MeetingPlaceChoice
et MeetingTimeChoice
tables à chaque fois que les participants, lieux et dates sont ajoutés. Pour cela, nous utilisons les événements afterSave de Yii.
Lorsqu'un participant est ajouté, nous devons en ajouter de nouveaux. MeetingPlaceChoice
des lignes pour chaque Lieu de rencontre
et nouveau MeetingTimeChoice
des lignes pour chaque L'heure de rendez-vous
. Voici le code dans le modèle Participate qui gère cela automatiquement pour nous:
fonction publique afterSave ($ insert, $ modifiedAttributes) parent :: afterSave ($ insert, $ modifiedAttributes); if ($ insert) // si un participant est ajouté // ajouter MeetingPlaceChoice & MeetingTimeChoice ce participant $ mt = new MeetingTime; $ mt-> addChoices ($ this-> meeting_id, $ this-> participant_id); $ mp = new MeetingPlace; $ mp-> addChoices ($ this-> meeting_id, $ this-> participant_id);
Lorsqu'un nouveau lieu est ajouté, un nouveau MeetingPlaceChoices
sont nécessaires pour chaque participant:
fonction publique afterSave ($ insert, $ modifiedAttributes) parent :: afterSave ($ insert, $ modifiedAttributes); if ($ insert) // si MeetingPlace est ajouté // ajoute MeetingPlaceChoice pour le propriétaire et les participants $ mpc = new MeetingPlaceChoice; $ mpc-> addForNewMeetingPlace ($ this-> meeting_id, $ this-> suggéré_by, $ this-> id);
De même, quand une nouvelle heure est ajoutée, de nouvelles entrées sont nécessaires pour MeetingTimeChoice
pour chaque participant:
fonction publique afterSave ($ insert, $ modifiedAttributes) parent :: afterSave ($ insert, $ modifiedAttributes); if ($ insert) // si MeetingTime est ajouté // ajouter MeetingTimeChoice pour le propriétaire et les participants $ mtc = new MeetingTimeChoice; $ mtc-> addForNewMeetingTime ($ this-> meeting_id, $ this-> suggéré_by, $ this-> id);
Il est supposé que lorsque l'organisateur de la réunion ajoute un lieu ou une date et heure, cela fonctionne pour eux initialement.
Lorsqu'il y a au moins un participant invité, un lieu et une fois, l'organisateur de la réunion peut finaliser la réunion. À l'avenir, nous autoriserons également les participants à finaliser la réunion..
Bien que ce code change un peu plus tard, il existe une fonction dans le modèle de réunion qui indique à la vue si elle doit être activée ou non. Finaliser
bouton:
fonction publique canFinalize () // vérifie si la réunion peut être finalisée par le spectateur si ($ this-> canSend ()) // l'organisateur peut toujours finaliser si ($ this-> viewer == Meeting :: VIEWER_ORGANIZER) $ this -> isReadyToFinalize = true; else // le spectateur est un participant // le participant a-t-il répondu à une fois ou existe-t-il seulement une fois // le participant a-t-il répondu à un lieu ou n'y a-t-il qu'un seul lieu
Voici le code de vue:
= Html::a(Yii::t('frontend', 'Finalize'), ['finalize', 'id' => $ model-> id], ['class' => 'btn btn-success'. (! $ model-> isReadyToFinalize? 'disabled': ")])?>
Une fois la réunion finalisée, MeetingPlanner changera de mode pour passer de la planification assistée à la présence de participants grâce à diverses fonctionnalités intéressantes que nous couvrirons dans les prochains tutoriels..
Je voulais mentionner quelques problèmes que j'ai rencontrés lors de l'écriture du code de cette section relativement complexe.
le SwitchInput
Les états ont été envoyés via JavaScript sous forme de types booléens, par exemple. vrai ou faux, mais j'avais besoin de les convertir en valeurs entières pour les transmettre avec succès via AJAX aux contrôleurs.
// les utilisateurs peuvent dire si un lieu est une option pour eux $ ('input [nom = "lieu de réunion choix"]'). on ('switchChange.bootstrapSwitch', fonction (e, s) // console. log (e.target.id, s); // true | false // configure intval pour qu'il passe via AJAX à partir de l'état booléen if (s) state = 1; else state = 0;
Les identifiants numériques du MeetingPlaceChoice
et MeetingTimeChoice
les widgets se chevauchent. Il m'a fallu un certain temps pour comprendre pourquoi les widgets de commutateur ont cessé de s'afficher correctement lorsque j'ai ajouté les fonctionnalités de choix. En raison du chevauchement des identifiants, les widgets de commutateur ne sont rendus que pour le premier objet..
Il était nécessaire d’ajouter des préfixes tels que mpc-
ou mtc-
aux identifiants et les dépouiller dans les actions du contrôleur.
echo SwitchInput :: widget (['type' => SwitchInput :: CHECKBOX, 'nom' => 'choix du lieu de réunion', 'id' => 'mpc -'. $ mpc-> id, 'tristate' = > vrai,
Voici où nous supprimons ce préfixe dans le contrôleur pour charger le modèle:
public function actionSet ($ id, $ state) // caution - problèmes de type AJAX entrants avec val $ id = str_replace ('mpc -', ", $ id); $ mpc = $ this-> findModel ($ id);
Il m'a fallu un certain temps pour découvrir comment définir l'état / la valeur de chargement initial du widget d'entrée de commutateur en mode bouton radio. Il n'y avait pas de documentation montrant comment faire cela. J'ai finalement écrit un explicatif ici pour les autres: Réglage de l'état du commutateur Bouton d'interface du widget d'entrée.
Maintenant que tout le AJAX est en place et opérationnel, il est temps de terminer certains des aspects restants de la vue de planification de la réunion afin de préparer les invitations qui ont été envoyées et doivent être consultées par les participants..
Par exemple, la présentation du calendrier de la réunion que les participants voient sera différente de celle de l'organisateur et différente selon les pouvoirs délégués par l'organisateur..
Par exemple, les colonnes vous et eux devront être modifiées par rapport à leur implémentation actuelle. Il faudra élargir les paramètres du modèle de réunion afin de déterminer si les participants peuvent suggérer des lieux et des dates et finaliser la réunion..
Plus tard, je souhaiterai peut-être autoriser plusieurs participants et avoir besoin d'afficher davantage de colonnes de disponibilité pour la vue d'organisation. Cette fonctionnalité ne fait pas partie de notre produit minimum viable (MVP)..
J'ai également besoin de terminer la mise en œuvre du Journal de réunion
qui enregistrera chaque modification apportée à une réunion au cours du processus de planification. Cela fournira une sorte d’historique de planification pour chaque réunion. je peux utiliser afterSave ()
événements pour cela aussi.
Surveillez les prochains tutoriels dans notre série Bâtir votre démarrage avec PHP. Une liste des sujets à venir est maintenant affichée dans notre table des matières..
N'hésitez pas à ajouter vos questions et commentaires ci-dessous; Je participe généralement aux discussions. Vous pouvez également me joindre sur Twitter @reifman ou m'envoyer un email directement.