Construire votre démarrage avec PHP Planification de la disponibilité et des choix

Ce que vous allez créer

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..

Affichage de la disponibilité de la base de données

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:

  • Lieu 1 | Organisateur | Participant | MeetingPlace.choice
  • Lieu 2 | Organisateur | Participant | MeetingPlace.choice

À partir du moment où le participant le voit:

  • Lieu 1 | Participant | Organisateur | (peut peut-être faire MeetingPlace.choice)
  • Lieu 2 | Participant | Organisateur | (peut peut-être faire MeetingPlace.choice)

Voyons maintenant comment implémenter ces tables dans les vues.

Affichage d'une table avec Bootstrap

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:

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:

 

$ model-> id], ['class' => 'btn btn-primaire glyphicon glyphicon-plus'])?>
compte> 0):?> $ placeProvider, 'itemOptions' => ['class' => 'item'], 'layout' => 'items', 'itemView' => '_list', 'viewParams' => ['placeCount' => $ placeProvider-> count],])?>
count> 1) echo Yii :: t ('frontend', 'Choose'); ?>

Regardons de plus près le lieu de rencontre voir la liste.

Affichage des lignes avec les widgets du commutateur Bootstrap

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:

La colonne de lieu

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..

   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:

  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 valeurCette propriété active le commutateur. L'identifiant correspondant au MeetingPlaceChoice-> 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é):

 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.. 

  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..

Implémentation du support AJAX

É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.

Construire des auditeurs d'événements

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..

Construire les actions du contrôleur

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;

Sécurisation des demandes AJAX

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.

Prise en charge des paramètres de disponibilité

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.

Choisir le lieu final, la date et l'heure

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:

 $ 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..

Problèmes de codage rencontrés

Je voulais mentionner quelques problèmes que j'ai rencontrés lors de l'écriture du code de cette section relativement complexe.

Types AJAX

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; 

Chevauchement d'identifiants

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); 

État du chargement du bouton radio du widget d'entrée Widget

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.

Et après?

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.

Liens connexes

  • Programmation avec Yii2: Mise en route
  • Le Yii Developer Exchange