Construirea sistemului de pornire cu PHP Disponibilitatea programării și alegerile

Ce veți crea

Acest tutorial face parte din programul Build Your Startup With PHP pe Tuts +. În această serie, vă conduc prin lansarea unei lansări de la concept la realitate utilizând aplicația mea Planificator de întâlniri ca exemplu de viață reală. La fiecare pas de-a lungul drumului, voi lansa codul Planificatorului de Întâlniri ca exemple de sursă deschisă pe care le puteți învăța. Voi aborda, de asemenea, problemele de afaceri legate de pornire în momentul în care apar.

Tot codul pentru Planificatorul întâlnirilor este scris în cadrul Yii2 Framework for PHP. Dacă doriți să aflați mai multe despre Yii2, verificați seria mea paralelă Programming With Yii2 la Tuts +. De asemenea, vă recomandăm să verificați site-ul bazei de cunoștințe pentru întrebările Yii2, Exchange Yii2 Developer Exchange.

Codificarea funcționalității întâlnirii programului se întinde pe cel puțin patru episoade. Acesta este cel de-al doilea dintre aceste patru episoade, care se concentrează pe adăugarea AJAX la pagina de planificare pentru a permite utilizatorilor să-și stabilească disponibilitatea și să aleagă locurile, datele și orele. Dacă ați pierdut tutorialul anterior privind planificarea unei întâlniri, vă rugăm să mergeți înapoi și să o citiți înainte de a continua.

În următorul tutorial, vom acoperi livrarea cererii de întâlnire prin e-mail. Vom reveni mai târziu pentru a optimiza și polona interfața cu utilizatorul, deoarece este esențială pentru succesul acestui produs.

Afișarea disponibilității din baza de date

Vizualizarea programului de întâlnire a fost relativ complexă cu codul. Amplasarea vizuală a coloanelor tabelului nu se referă direct la modul în care stocăm datele conexe în baza noastră de date și în schema noastră. Din fericire, fiecare întâlnire nu are un set mare de date cu opțiuni de loc și dată, deci nu prezintă o problemă de performanță deosebită.

În schema noastră, disponibilitatea locului pentru organizatori și participanți (adică dacă un loc este acceptabil pentru ei pentru această întâlnire) este stocat în MeetingPlaceChoice masa. Folosind modelul nostru relațional, fiecare întâlnire are multe MeetingPlaces care are multe MeetingPlaceChoices

Nu confunda MeetingPlaceChoice tabel cu selecția finală pentru un loc stocat în MeetingPlace-> Starea.

Tabelul prezentat mai sus va apărea diferit atunci când organizatorul îl vede:

  • Locul 1 | Organizator | Participant | MeetingPlace.choice
  • Locul 2 | Organizator | Participant | MeetingPlace.choice

De când participantul îl vede:

  • Locul 1 | Participant | Organizator | (poate face MeetingPlace.choice)
  • Locul 2 | Participant | Organizator | (poate face MeetingPlace.choice)

Acum, să discutăm despre implementarea acestor tabele în vizualizări.

Afișarea unui tabel cu Bootstrap

Pentru moment, am ales să afișez fiecare zonă, de ex. locul sau data și ora, în propriul panou Bootstrap cu tabele.

În \ Frontend al \ opinii \ reuniune \ view.php, veți vedea includerea în panoul Locații astfel:

render ('... / meeting-place / _panel', ['model' => $ model, 'placeProvider' => $ placeProvider,])?> 

Iată o parte din locul de întâlnire panou vizualizare fișier. Aceasta configurează grila de tabelă și include un widget de vizualizare a listei pentru a afișa rândurile:

 

$ model-> id], ['class' => 'btn btn-primar glyphicon-plus'])
număr> 0):?> $ placeProvider, 'itemOptions' => ['class' => 'item'], 'layout' => 'items', 'itemView' $ placeProvider-> număr],])?>
numără> 1) echo Yii :: t ('frontend', 'Alege'); ?>

Să aruncăm o privire mai atentă la locul de întâlnire listă.

Afișarea rândurilor cu Widget-uri de comutare pentru Bootstrap

Yii Listview va afișa un rând de date pentru fiecare loc. Codul funcționează aproape identic pentru perioadele de timp.

Folosesc widget-ul de intrare a switch-ului Yujia de la Krajee pentru comutatorul Bootstrap în locul câmpurilor de selectare plictisitoare și a casetelor combo:

Îmi place modul în care opțiunea tri-stat ne permite să prezentăm participanților o stare unică înainte de a face o selecție; ne permite, de asemenea, să arătăm organizatorului că participantul nu a făcut încă o selecție.

Să trecem prin coloana de cod după coloană. Iată panoul Loc și masa pe care o implementăm:

Coloana locului

În prima coloană, folosesc link-ul Yii Html helper pentru a trimite numele locului în pagina proprie de vizionare - observați cum folosim locația slug.

   place-> nume, BaseUrl :: home (). '/ place /'.$ model-> loc-> slug)?> 

Coloana organizatorului

Pentru a găsi selecțiile organizatorului, vom trece prin matricea de MeetingPlaceChoices, potrivire numele de utilizator la a ședință> owner_id:

  meetingPlaceChoices ca $ mpc) if ($ mpc-> user_id == $ model-> meeting-> owner_id) dacă ($ mpc-> status == $ mpc :: STATUS_YES) $ value = 1; altfel valoarea $ = 0; echo SwitchInput :: widget (['type' => SwitchInput :: CHECKBOX, 'name' => 'meeting-place-choice', 'id' => 'mpc - > valoarea $, 'pluginOptions' => ['size' => 'mini', 'onText' =>' 'OffText'=>'',' onColor '=>' succes ',' offColor '=>' pericol ',],]); ?> 

Pentru a vă selecta disponibilitatea într-un anumit loc, folosim modulul căsuței de control a intrării comutatorului, adică acest loc funcționează pentru dvs. (activat) sau nu (oprit).

 valoareproprietatea setează comutatorul la sarcină. Id - ul corespunzător MeetingPlaceChoice-> id este folosit pentru AJAX de mai jos pentru a identifica acest switch special.

De asemenea, puteți observa că folosim glificonii pentru da și nu în locul etichetelor.

Coloana participantului

Codul pentru participant implementează comutatoare tri-statale. adică acest loc funcționează pentru dvs. (pe), nu este (dezactivat) sau nu ați indicat încă (nedeterminat):

 meetingPlaceChoices ca $ mpc) if (numar ($ model-> meeting-> participanti) == 0) pauza; dacă ($ mpc-> user_id == $ model-> întâlnire-> participanți [0] -> participant_id) if ($ mpc-> status == $ mpc :: STATUS_YES) $ value = 1; altfel dacă ($ mpc-> status == $ mpc :: STATUS_NO) $ value = 0; altfel dacă ($ mpc-> status == $ mpc :: STATUS_UNKNOWN) $ value = -1; echo SwitchInput :: widget (['tip' => SwitchInput :: CHECKBOX, 'name' => 'meeting-place-choice', 'id' => 'mpc - > true, 'indeterminateValue' => - 1, 'indeterminateToggle' => false, 'disabled' => true, 'value' => value value, 'pluginOptions' => ['size' => 'mini', onText '=>'' 'OffText'=>'',' onColor '=>' succes ',' offColor '=>' pericol '],]); ?> 

Atunci când adăugăm suport pentru întâlniri care permit participantului să sugereze locații și date de timp, vom adăuga și widget-uri de stat în coloana organizator.

Afișarea butonului Alegeți locul și data

Dacă organizatorul vizionează întâlnirea, îi vom permite să aleagă locația finală a ședinței și ora datei. În curând, vom adăuga suport pentru întâlniri pentru a permite participantului să aleagă aceste.

În acest caz, utilizatorul face o selecție pe rânduri (alegerea unuia dintre locurile listate). Acest lucru necesită utilizarea intrării comutatorului în modul buton radio. Pentru evenimentele AJAX pentru alegătorii, putem asculta doar numele proprietății - nu este nevoie de un id, deoarece există o singură selecție posibilă pentru panoul.

De asemenea, mi-am dorit ca switch-ul de alegere să apară diferit de switch-urile de disponibilitate, așa că le-am făcut mai largi și au folosit culori diferite. 

  1) dacă ($ model-> status == $ model :: STATUS_SELECTED) $ value = $ model-> id;  altceva $ value = 0;  echo SwitchInput :: widget (['type' => SwitchInput :: RADIO, 'name' => 'loc-chooser', 'items' => 'value' => valoare $ ', pluginOptions' => ['size' => 'mini', 'handleWidth' => 60, 'onText'' 'OffText'=>''],' labelOptions '=> [' stil '=>' font-size: 12px '],]); ?>  

Acum vă voi prezenta modul în care am implementat suportul AJAX pentru toți acești aleși.

Implementarea suportului AJAX

Evident, am vrut să evit utilizatorii să salveze modificările acestor forme. În schimb, am vrut ca switch-urile să schimbe starea prin AJAX fără o reîmprospătare a paginii.

Codul este împărțit între setarea ascultătorilor de evenimente pentru a reacționa la modificările de stare și acțiunile controlerului pentru a înregistra modificările din baza noastră de date. Este, de asemenea, ușor diferită pentru comutatoarele casetei de control față de comutatoarele radio.

Construirea ascultătorilor de evenimente

Creăm ascultători de evenimente pentru a executa codul ori de câte ori se schimbă starea unui buton. Evenimentul de ascultare este codul JavaScript generat de PHP în panoul de vizualizare (pentru întregul tabel de opțiuni).

Iată codul din partea de jos a paginii \ Frontend al \ vederi \ loc de întâlnire \ _panel.php:

id, 'val': e.target.value, // e.target.value este selectat succesul modelului MeetingPlaceChoice: funcția (data) return true; ); ); ... JS; $ position = \ yii \ web \ Vizualizare :: POS_READY; $ this-> registerJs ($ script, pozitie $); ?> 

Apropo, dacă cineva îmi poate spune numele blocului JS pentru PHP, postați-l în secțiunea de comentarii. As vrea sa stiu. Unele lucruri sunt greu de căutat.

registerJs funcția în Yii face script-ul pentru un anumit poziţia $ pe pagină. În acest caz, este un eveniment gata.

Codul de mai sus stabilește evenimentele ascultătorilor pentru toate butoanele radio ale locului-selector pentru toate locațiile de către proprietatea nume. Valoarea țintă a evenimentului va reprezenta ID-ul locului de întâlnire ales. Voi vorbi mai multe despre funcția AJAX într-un moment.

Cu alte cuvinte, evenimentele radio ale comutatorului răspund la organizator (în general) alegând un loc sau o dată pentru a finaliza întâlnirea, transmiterea ID-ului locului de întâlnire sau a ID-ului întâlnirii.

Iată codul pentru a asculta modificările de disponibilitate cu casetele de selectare de la intrarea comutatorului:

// utilizatorii pot spune dacă un loc este o opțiune pentru aceștia $ ('input [name = "meeting-place-choice"]'). ("switchChange.bootstrapSwitch", funcția (e, s) // console. log (e.target.id, s); // true | false // set intll să treacă prin AJAX din starea booleană if (s) state = 1; altul state = 0; $ .ajax (url: '/ mp / meetingplacechoice / set ', date: id: e.target.id,' state ': state, succes: functie (data) return true;););

Ascultătorul este pregătit pentru toți loc de întâlnire-alegere nume proprietăți, dar trebuie să treacă ID-ul pentru a indica exact ce MeetingPlaceChoice se schimbă.

Pentru a clarifica, casetele de ascultători ale evenimentelor pentru căsuțele de intrare pentru comutare permit utilizatorilor să spună că sunt disponibile sau nu pentru un loc sau pentru o dată. Ei trimit loc de întâlnire-alegere id sau loc de întâlnire-time id.

Acum, să ne uităm mai atent la modul în care evenimentele AJAX numesc acțiunile controlorului bazate pe PHP pentru a înregistra modificările de stare în baza de date.

Construirea acțiunilor controlerului

Iată codul din nou pentru locul de întâlnire selectorul butonului radio:

$ .ajax (url: '/ mp / întâlnire loc / alege', date: id: $ model-> id, 'val': e.target.value, // e.target.value este selectat succesul modelului MeetingPlaceChoice : funcția (data) return true;); 

Adresa URL indică calea către Locul de întâlnire regulatorul alege acțiunea:

public function actionChoose ($ id, $ val) // meeting_place_id trebuie setat activ // alt meeting_place_id pentru aceasta intalnire trebuie setat inactive $ meeting_id = intval ($ id); $ MTG = Meeting :: găsi () -> în cazul în care ([ 'id' => $ MEETING_ID]) -> unul (); dacă (Yii :: $ app-> user-> getId ()! = $ mtg-> owner_id) returnează false; // a face - verificați și ID-ul participantului dacă participanții au permisiunea de a alege foreach ($ mtg-> meetingPlaces ca $ mp) if ($ mp-> id == intval ($ val)) $ mp-> status = : STATUS_SELECTED;  altceva $ mp-> status = MeetingPlace :: STATUS_SUGGESTED;  $ mp-> salvați ();  return true; 

Intrarea $ id reprezintă MEETING_ID. Valoarea reprezintă cea aleasă Locul de întâlnire id. STATUS_SELECTED indică faptul că locul a fost ales, în timp ce STATUS_SUGGESTED indică doar că a fost sugerat (nu este ales).

Acest cod buclează prin locurile de întâlnire ale fiecărei întâlniri și actualizează starea locului selectat.

Să luăm din nou în considerare codul pentru casetele de introducere a comutării care determină dacă cineva este disponibil pentru un anumit loc:

$ .ajax (url: '/ mp / meetingplacechoice / set', date: id: e.target.id, 'state': state, succes: function (data) return true;); 

Aceste evenimente îl sună pe MeetingPlaceChoice controlează acțiunea cu un șir al cărui sufix conține id-ul MeetingPlaceChoice înregistrare care trebuie actualizată:

funcția publică funcțiaSet ($ id, $ state) // caution - probleme de tip AJAX primite cu val $ id = str_replace ('mpc -', '$ id); $ mpc = $ this-> findModel ($ id); dacă (Yii :: $ app-> user-> getId ()! = $ mpc-> user_id) return false; dacă (intval ($ state) == 0 sau $ state == 'false') $ mpc-> = MeetingPlaceChoice :: STATUS_NO; altceva $ mpc-> status = MeetingPlaceChoice :: STATUS_YES; $ mpc-> save (); retur $ mpc-> id;

Asigurarea cererilor AJAX

Din motive de securitate, trebuie să verificăm dacă cererea AJAX a fost inițiată de utilizatorul real care poate efectua aceste modificări. Acest cod face asta:

 dacă (Yii :: $ app-> user-> getId ()! = $ mtg-> owner_id) returnează false;

și

dacă (Yii :: $ app-> user-> getId ()! = $ mpc-> user_id) returnează false; 

Fără aceste verificări, ar fi ușor pentru un hacker să scrie un script pentru a modifica setările de întâlnire pentru oricine și toată lumea.

Codul AJAX pentru indicarea disponibilității pentru orele de date și efectuarea alegerilor este aproape identic.

Sprijinirea setărilor de disponibilitate

Pentru a susține toate caracteristicile de mai sus, trebuie să adăugăm și codul care adaugă înregistrări la MeetingPlaceChoice și MeetingTimeChoice mese ori de câte ori participanții, locurile și data sunt adăugate. Pentru aceasta, vom folosi evenimentele Yii după Salvare.

Atunci când un participant este adăugat, trebuie să adăugăm noi MeetingPlaceChoice rânduri pentru fiecare Locul de întâlnire și noi MeetingTimeChoice rânduri pentru fiecare MeetingTime. Iată codul din modelul Participare care gestionează automat acest lucru pentru noi:

 funcția publică după Setați ($ insert, $ changedAttributes) părinte :: după Salvează ($ insert, $ changedAttributes); dacă ($ insert) // dacă participantul este adăugat // adăuga MeetingPlaceChoice & MeetingTimeChoice acest participant $ mt = new MeetingTime; $ Mt-> addChoices ($ this-> MEETING_ID, $ this-> participant_id); $ mp = new MeetingPlace; $> AddChoices (înaltă medie $ this-> MEETING_ID, $ this-> participant_id);  

Când se adaugă un loc nou, nou MeetingPlaceChoices sunt necesare pentru fiecare participant:

funcția publică după Setați ($ insert, $ changedAttributes) părinte :: după Salvează ($ insert, $ changedAttributes); dacă ($ insert) // dacă este adăugat MeetingPlace // adăugați MeetingPlaceChoice pentru proprietar și participanți $ mpc = new MeetingPlaceChoice; $ Mpc-> addForNewMeetingPlace ($ this-> MEETING_ID, $ this-> suggested_by, $ this-> id); 

În mod similar, atunci când se adaugă o nouă dată, sunt necesare intrări noi MeetingTimeChoice pentru fiecare participant:

funcția publică după Setați ($ insert, $ changedAttributes) părinte :: după Salvează ($ insert, $ changedAttributes); dacă ($ insert) // dacă este adăugat MeetingTime // adăugați MeetingTimeChoice pentru proprietar și participanți $ mtc = new MeetingTimeChoice; $ Mtc-> addForNewMeetingTime ($ this-> MEETING_ID, $ this-> suggested_by, $ this-> id);  

Se presupune că atunci când organizatorul întâlnirii adaugă un loc sau o dată data, acesta funcționează pentru ei inițial.

Alegerea locului final, a datei și a orei

După ce participă cel puțin un participant invitat, un loc și o dată, organizatorul întâlnirii poate finaliza întâlnirea. În viitor, vom permite, de asemenea, participanților să finalizeze întâlnirea.

În timp ce acest cod se va schimba un pic înainte, există o funcție în modelul de întâlnire care spune opinia dacă să activeze Definitivarea buton:

funcția publică canFinalize () // verifică dacă întâlnirea poate fi finalizată de vizualizator dacă ($ this-> canSend ()) // organizatorul poate întotdeauna să finalizeze dacă ($ this-> viewer == Meeting :: VIEWER_ORGANIZER) $ this -> isReadyToFinalize = true;  altfel // spectatorul este un participant // participantul a răspuns la o singură dată sau există doar o singură dată // participantul a răspuns la un loc sau există un singur loc 

Iată codul de vizualizare:

 $ model-> id], ['class' => 'btn btn-success'.) 

Odată ce întâlnirea este finalizată, MeetingPlanner va schimba modul de la sprijinirea planificării la facilitarea participării participanților printr-o varietate de caracteristici interesante pe care le vom acoperi în tutoriale viitoare.

Codificarea problemelor pe care le-am întâlnit

Am vrut să menționez câteva probleme pe care le-am întâlnit în timp ce scriu codul pentru această secțiune relativ complicată.

Tipurile AJAX

SwitchInput au fost trimise prin JavaScript ca tipuri booleene, de ex. adevăr sau fals, dar a trebuit să le convertesc la valori întregi pentru a le transmite cu succes către AJAX controlorilor.

// utilizatorii pot spune dacă un loc este o opțiune pentru aceștia $ ('input [name = "meeting-place-choice"]'). ("switchChange.bootstrapSwitch", funcția (e, s) // console. log (e.target.id, s); // true | false // set intll pentru a trece prin AJAX din starea booleană if (s) state = 1; else state = 0; 

ID-uri suprapuse

Codurile numerice ale codului numeric MeetingPlaceChoice și MeetingTimeChoice widgeturile se suprapun. Mi-a trebuit un timp să-mi dau seama de ce widget-urile pentru comutatoare s-au oprit în mod corespunzător pentru mine când am adăugat posibilitățile de alegere. Deoarece existau ID-uri care se suprapun, widget-urile de comutare au fost redate doar pentru primul obiect.

A fost necesar să adăugăm prefixe, cum ar fi MPC- sau MTC- la numerele de identificare și le eliminați în acțiunile controlerului.

 echo SwitchInput :: widget (['tip' => SwitchInput :: CHECKBOX, 'name' => 'meeting-place-choice', 'id' => 'mpc - > true,

Iată în cazul în care am scos prefixul în controler pentru a încărca modelul:

 funcția publică funcțiaSet ($ id, $ state) // caution - probleme de tip AJAX primite cu val $ id = str_replace ('mpc -', '$ id); $ mpc = $ this-> findModel ($ id); 

Swith Input Widget Starea încărcării butonului radio

Mi-a trebuit o vreme să descopăr cum să setați starea inițială de încărcare / valoarea pentru widget-ul de intrare a comutării în modul buton radio. Nu a existat nici o documentație care să arate cum să procedăm astfel. În cele din urmă am scris un explicator aici pentru alții: Setarea stării butonului radio Buton de intrare pentru comutatorul de intrare.

Ce urmeaza?

Acum, că toate AJAX-urile sunt la locul lor și lucrează, este timpul să finalizăm unele dintre zonele rămase din planificarea întâlnirii pentru a pregăti invitațiile care au fost livrate și trebuie văzute de participanți.

De exemplu, vizualizarea programului de întâlnire pe care participanții îl va vedea va fi diferită în aspect decât organizatorul și va diferi în funcție de puterile pe care organizatorul le-a delegat.

De exemplu, coloanele dvs. și ele vor trebui să se schimbe de la implementarea lor actuală. Va trebui să extindeți setările modelului întâlnirilor care determină dacă participanții pot sugera locații și data și pot finaliza întâlnirea.

În continuare, în viitor, aș putea să doresc mai mulți participanți și să trebuiască să afișeze mai multe coloane de disponibilitate pentru vizualizarea de organizare - această funcție nu face parte din produsul nostru viabil minim (MVP).

De asemenea, trebuie să închei implementarea MeetingLog care va înregistra orice schimbare adusă unei întâlniri în timpul procesului de planificare. Aceasta va oferi un fel de istorie de planificare pentru fiecare întâlnire. pot folosi afterSave () evenimente pentru acest lucru, de asemenea.

Urmăriți tutorialele viitoare din Building Your Startup With PHP series - o listă cu subiectele viitoare este postată acum în Cuprinsul nostru.

Nu ezitați să adăugați întrebările și comentariile de mai jos; În general, particip la discuții. Puteți să mă contactați și pe Twitter @reifman sau să mă trimiteți direct prin e-mail.

Link-uri conexe

  • Programarea cu Yii2: Noțiuni de bază
  • Yii Developer Exchange
Cod