Deze tutorial is onderdeel van de Building Your Startup With PHP-serie op Tuts +. In deze serie begeleid ik je door een opstart van concept naar realiteit te starten met behulp van mijn Meeting Planner-app als een echt voorbeeld. Bij elke stap die ik maak, zal ik de Meeting Planner-code vrijgeven als open source voorbeelden waar je van kunt leren. Ik zal ook opstartgerelateerde zakelijke problemen aanpakken zodra deze zich voordoen.
Alle code voor Meeting Planner is geschreven in het Yii2 Framework voor PHP. Als je meer wilt weten over Yii2, bekijk dan mijn parallelle serie Programming With Yii2 op Tuts +. Misschien wil je ook mijn knowledge base-site bekijken voor Yii2-vragen, The Yii2 Developer Exchange.
De codering voor de functionaliteit van de planningsvergadering strekt zich uit over ten minste vier afleveringen. Dit is de tweede van deze vier afleveringen, die zich richt op het toevoegen van AJAX aan de planningspagina zodat gebruikers hun beschikbaarheid kunnen instellen en plaatsen, datums en tijden kunnen kiezen. Als u de vorige zelfstudie over het plannen van een vergadering hebt gemist, gaat u terug en leest u deze voordat u doorgaat.
In de volgende zelfstudie behandelen we het afleveren van het vergaderverzoek via e-mail. We komen later terug om de gebruikersinterface te optimaliseren en op te frissen, omdat dit van cruciaal belang is voor het succes van dit product.
De weergave van het vergaderrooster was relatief ingewikkeld om te coderen. De visuele lay-out van de tabelkolommen heeft niet direct te maken met de manier waarop we de gerelateerde gegevens opslaan in onze database en schema. Gelukkig beschikt elke vergadering niet over een grote gegevensset met opties voor plaats en datum, dus dit levert geen bepaald prestatieprobleem op.
In ons schema is de plaatsbeschikbaarheid voor organisatoren en deelnemers (dus of een plaats aanvaardbaar is voor deze vergadering) opgeslagen in de MeetingPlaceChoice
tafel. Met behulp van ons relationele model heeft elke vergadering er veel Ontmoetingsplaatsen
die veel heeft MeetingPlaceChoices
.
Verwar het niet MeetingPlaceChoice
tabel met de definitieve selectie voor een plaats die is opgeslagen in MeetingPlace-> Status
.
De bovenstaande tabel wordt anders weergegeven als de organisator deze bekijkt:
Vanaf wanneer de deelnemer het bekijkt:
Laten we nu bespreken hoe deze tabellen in de views kunnen worden geïmplementeerd.
Voor nu koos ik ervoor om elk gebied weer te geven, bijvoorbeeld plaats of datum en tijd, in een eigen Bootstrap-paneel met tabellen.
In \ Frontend \ uitzicht \ vergadering \ view.php
, je ziet de opname voor het deelvenster Plaatsen als volgt:
= $this->render ('... / meeting-place / _panel', ['model' => $ model, 'placeProvider' => $ placeProvider,])?>
Hier is een deel van de ontmoetingsplek
panel view-bestand. Dit stelt het tabelraster in en bevat een lijstweergave-widget om de rijen weer te geven:
tel> 0):?>= Yii::t('frontend','Places') ?>
= Html::a(", ['meeting-place/create', 'meeting_id' => $ model-> id], ['class' => 'btn btn-primary glyphicon glyphicon-plus'])?>
= ListView::widget([ 'dataProvider' => $ placeProvider, 'itemOptions' => ['class' => 'item'], 'layout' => 'items', 'itemView' => '_list', 'viewParams' => ['placeCount' => $ placeProvider-> tel],])?> =Yii::t('frontend','You') ?> =Yii::t('frontend','Them') ?> tel> 1) echo Yii :: t ('frontend', 'Choose'); ?>
Laten we de ontmoetingsplek
lijstweergave.
De Yii ListView toont een rij met gegevens voor elke plaats. De code werkt bijna identiek voor de datumtijden.
Ik gebruik Krajee's Yii2 Switch Input Widget voor de Bootstrap Switch in plaats van saaie selectievakjes en keuzelijsten met invoervak:
Ik vind de manier waarop de driestatenoptie ons toestaat de deelnemers een unieke staat te laten zien voordat ze een selectie maken; het stelt ons ook in staat de organisator te laten zien dat de deelnemer nog geen selectie heeft gemaakt.
Laten we de code kolom voor kolom bekijken. Hier is het paneel 'Plaats' en de tabel die we implementeren:
In de eerste kolom gebruik ik de Yii Html-link-helper om de naam van de plaats naar zijn eigen weergavepagina te hotlinken - let erop hoe we de plaats slak gebruiken.
= Html::a($model->place-> name, BaseUrl :: home (). '/ place /'.$ model-> place-> slug)?> De organizerkolom
Om de selecties van de organisator te vinden, doorlopen we de array van
MeetingPlaceChoices
, passen bijgebruikersnaam
naarmeeting-> owner_id
:foreach ($model->meetingPlaceChoices als $ mpc) if ($ mpc-> user_id == $ model-> meeting-> owner_id) if ($ mpc-> status == $ mpc :: STATUS_YES) $ waarde = 1; else $ value = 0; echo SwitchInput :: widget (['type' => SwitchInput :: CHECKBOX, 'name' => 'meeting-place-choice', 'id' => 'mpc -'. $ mpc-> id, 'value' = > $ waarde, 'pluginOptions' => ['size' => 'mini', 'onText' => '' 'OffText'=>'',' onColor '=>' success ',' offColor '=>' danger ',],]); ?> Om uw beschikbaarheid op een specifieke plaats te selecteren, gebruiken we de checkbox-modus van de schakelingang, d.w.z. deze plaats werkt voor u (aan) of niet (uit).
De
waarde
eigenschap zet de schakelaar op laden. Het ID dat overeenkomt met hetMeetingPlaceChoice-> id
wordt gebruikt voor AJAX hieronder om deze specifieke schakelaar te identificeren.U kunt ook opmerken dat we glyphicons gebruiken voor ja en nee in plaats van labels.
De deelnemerskolom
De code voor de deelnemer implementeert driestandenschakelaars. d.w.z. deze plek werkt voor jou (aan), het doet niet (uit) of je hebt nog niet aangegeven (onbepaald):
foreach ($model->meetingPlaceChoices als $ mpc) if (count ($ model-> meeting-> participants) == 0) break; if ($ mpc-> user_id == $ model-> meeting-> participants [0] -> participant_id) if ($ mpc-> status == $ mpc :: STATUS_YES) $ value = 1; else if ($ mpc-> status == $ mpc :: STATUS_NO) $ waarde = 0; else if ($ mpc-> status == $ mpc :: STATUS_UNKNOWN) $ value = -1; echo SwitchInput :: widget (['type' => SwitchInput :: CHECKBOX, 'name' => 'meeting-place-choice', 'id' => 'mpc -'. $ mpc-> id, 'tristate' = > true, 'indeterminateValue' => - 1, 'indeterminateToggle' => false, 'disabled' => true, 'value' => $ value, 'pluginOptions' => ['size' => 'mini', 'onText '=>'' 'OffText'=>'',' onColor '=>' succes ',' offColor '=>' gevaar '],]); ?> Wanneer we ondersteuning toevoegen voor vergaderingen waarin de deelnemer plaatsen en datumtijden kan voorstellen, voegen we ook tri-state widgets toe aan de organizerkolom.
De schakelaar voor plaats- en datumtijd kiezen weergeven
Als de organisator de vergadering bekijkt, kunnen we de uiteindelijke locatie en datum van de vergadering kiezen. Binnenkort zullen we ook ondersteuning voor vergaderingen toevoegen, zodat de deelnemer deze kan kiezen.
In dit geval maakt de gebruiker een selectie over rijen (een van de vermelde plaatsen kiezen). Hiervoor moeten we de schakelaarinvoer gebruiken in de radioknopmodus. Voor de AJAX-evenementen voor de choosers kunnen we gewoon luisteren naar de eigenschap name - er is geen ID nodig, omdat er maar één selectie mogelijk is voor het panel.
Ik wilde ook dat de keuzeschakelaar anders leek dan de schakelaars voor beschikbaarheid, dus ik maakte ze breder en gebruikte verschillende kleuren.
if ($placeCount>1) if ($ model-> status == $ model :: STATUS_SELECTED) $ value = $ model-> id; else $ value = 0; echo SwitchInput :: widget (['type' => SwitchInput :: RADIO, 'name' => 'place-chooser', 'items' => [['waarde' => $ model-> id],], 'waarde' => $ waarde, 'pluginOptions' => ['size' => 'mini', 'handleWidth' => 60, 'onText' => '' 'OffText'=>''],' labelOptions '=> [' style '=>' font-size: 12px '],]); ?>
Nu zal ik je laten zien hoe we de AJAX-ondersteuning voor al deze kiezers hebben geïmplementeerd.
Vanzelfsprekend wilde ik voorkomen dat gebruikers moesten wijzigingen in deze formulieren opslaan. In plaats daarvan wilde ik dat de schakelaars via AJAX van status veranderen zonder een paginavernieuwing.
De code is verdeeld over het instellen van gebeurtenislisteners om te reageren op statuswijzigingen en controlleracties om de wijzigingen in onze database vast te leggen. Het is ook iets anders voor de schakelaars in het selectievakje ten opzichte van de radioswitches.
We creëren gebeurtenislisteners om code uit te voeren wanneer de status van een knop wordt gewijzigd. De luistergebeurtenis is JavaScript-code die wordt gegenereerd door PHP in de paneelweergave (voor de hele tabel met opties).
Hier is de code onderaan \ Frontend \ uitzicht \ ontmoetingsplaats \ _panel.php
:
id, 'val': e.target.value, // e.target.value is geselecteerd Succesmodel van MeetingPlaceChoice: function (data) return true; ); ); ... JS; $ position = \ yii \ web \ View :: POS_READY; $ this-> registerJs ($ script, $ positie); ?>
Trouwens, als iemand mij de naam van het JS-block kan vertellen voor PHP, post het dan in de comments sectie. Ik wil graag weten. Sommige dingen zijn moeilijk te vinden.
De registerJs
functie in Yii maakt het script voor een bepaalde $ position
op de pagina. In dit geval is het een klaar evenement.
De bovenstaande code stelt luisteraargebeurtenissen in voor alle keuzeknoppen met plaatstoetsenaar voor alle plaatsen op de eigenschap name. De doelwaarde van het evenement vertegenwoordigt de gekozen ontmoetingsplaats-id. Ik zal in een ogenblik meer over de AJAX-functie praten.
Met andere woorden, de overstapradiogebeurtenissen reageren op de organisator (in het algemeen) door een plaats- of datumtijd te kiezen om de vergadering te finaliseren, het ID van de ontmoetingsplaats of de vergadertijd te verzenden..
Hier is de code voor het luisteren naar wijzigingen in de beschikbaarheid met de selectievakjes voor schakelopties:
// gebruikers kunnen aangeven of een plaats een optie voor hen is $ ('input [name = "meeting-place-choice"]'). on ('switchChange.bootstrapSwitch', function (e, s) // console. log (e.target.id, s); // true | false // set intval om via AJAX door te geven vanuit booleaanse toestand if (s) state = 1; else state = 0; $ .ajax (url: '/ mp / meetingplacechoice / set ', data: id: e.target.id,' state ': state, success: function (data) return true;););
De luisteraar is ingesteld voor iedereen ontmoetingsplaats-choice
name eigenschappen, maar het moet de ID doorgeven om precies aan te geven wat MeetingPlaceChoice
wordt veranderd.
Ter verduidelijking, de gebeurtenislisteners voor schakelinvoerschakelaars stellen gebruikers in staat om te zeggen dat ze beschikbaar zijn of niet voor een plaats- of datumtijd. Ze zenden ontmoetingsplaats-choice
id of ontmoetingsplaats-time
ID kaart.
Laten we nu eens nader bekijken hoe de AJAX-gebeurtenissen onze PHP-gebaseerde controlleracties oproepen om statuswijzigingen in de database te registreren.
Hier is de code opnieuw voor de ontmoetingsplek
keuzerondje:
$ .ajax (url: '/ mp / meetingplace / choose', data: id: $ model-> id, 'val': e.target.value, // e.target.value is geselecteerd Model succes van MeetingPlaceChoice : function (data) return true;);
De URL geeft het pad naar de Ontmoetingsplek
de actie van de controller kiest:
openbare functie actionChoose ($ id, $ val) // meeting_place_id moet actief worden ingesteld // andere meeting_place_id voor deze vergadering moet inactief worden ingesteld $ meeting_id = intval ($ id); $ MTG = Ontmoeting :: find () -> waarin ([ 'id' => $ MEETING_ID]) -> één (); if (Yii :: $ app-> user-> getId ()! = $ mtg-> owner_id) return false; // to do - controleer ook deelnemer id als deelnemers voor anyeach mogen kiezen ($ mtg-> meetingPlaces als $ mp) if ($ mp-> id == intval ($ val)) $ mp-> status = MeetingPlace: : STATUS_SELECTED; else $ mp-> status = MeetingPlace :: STATUS_SUGGESTED; $ mp-> save (); return true;
De inkomende $ id
vertegenwoordigt de MEETING_ID
. De waarde vertegenwoordigt de gekozen Ontmoetingsplek
ID kaart. STATUS_SELECTED
geeft aan dat de plaats is gekozen, terwijl STATUS_SUGGESTED
geeft alleen aan dat het is voorgesteld (niet gekozen).
Deze code doorloopt de vergaderingsplekken van elke vergadering en werkt de status van de geselecteerde plaats bij.
Laten we opnieuw kijken naar de code voor de schakelopties die bepalen of iemand beschikbaar is voor een specifieke plaats:
$ .ajax (url: '/ mp / meetingplacechoice / set', data: id: e.target.id, 'state': state, success: function (data) return true;);
Deze evenementen noemen het MeetingPlaceChoice
de ingestelde actie van de controller met een string waarvan het achtervoegsel de id van de bevat MeetingPlaceChoice
record dat moet worden bijgewerkt:
public function actionSet ($ id, $ state) // caution - inkomende AJAX type problemen met val $ id = str_replace ('mpc -', ", $ id); $ mpc = $ this-> findModel ($ id); if (Yii :: $ app-> user-> getId ()! = $ mpc-> user_id) return false; if (intval ($ state) == 0 of $ state == 'false') $ mpc-> status = MeetingPlaceChoice :: STATUS_NO; else $ mpc-> status = MeetingPlaceChoice :: STATUS_YES; $ mpc-> save (); return $ mpc-> id;
Om veiligheidsredenen moeten we verifiëren dat de AJAX-aanvraag is gestart door de daadwerkelijke gebruiker die deze wijzigingen kan aanbrengen. Deze code doet dat:
if (Yii :: $ app-> user-> getId ()! = $ mtg-> owner_id) return false;
en
if (Yii :: $ app-> user-> getId ()! = $ mpc-> user_id) return false;
Zonder deze controles zou het voor een hacker gemakkelijk zijn om een script te schrijven om de vergaderinstellingen voor iedereen te wijzigen.
De AJAX-code voor het aangeven van beschikbaarheid voor datumtijden en het maken van keuzes is vrijwel identiek.
Om alle bovenstaande functies te ondersteunen, moeten we ook een code toevoegen die records toevoegt aan de MeetingPlaceChoice
en MeetingTimeChoice
tabellen wanneer deelnemers, plaatsen en datumtijden zijn toegevoegd. Hiervoor gebruiken we de afterSave-gebeurtenissen van Yii.
Wanneer een deelnemer wordt toegevoegd, moeten we nieuw toevoegen MeetingPlaceChoice
rijen voor elke Ontmoetingsplek
en nieuw MeetingTimeChoice
rijen voor elke Ontmoetingstijd
. Hier is de code in het deelnemende model dat dit automatisch voor ons afhandelt:
openbare functie afterSave ($ insert, $ changedAttributes) parent :: afterSave ($ insert, $ changedAttributes); if ($ insert) // if Participant is added // add MeetingPlaceChoice & MeetingTimeChoice this participant $ mt = new MeetingTime; $ MT-> addChoices ($ this-> MEETING_ID, $ this-> participant_id); $ mp = nieuwe MeetingPlace; $ MP-> addChoices ($ this-> MEETING_ID, $ this-> participant_id);
Wanneer een nieuwe plaats wordt toegevoegd, nieuw MeetingPlaceChoices
nodig voor elke deelnemer:
openbare functie afterSave ($ insert, $ changedAttributes) parent :: afterSave ($ insert, $ changedAttributes); if ($ insert) // als MeetingPlace is toegevoegd // voeg MeetingPlaceChoice toe voor eigenaar en deelnemers $ mpc = new MeetingPlaceChoice; $ MPC> addForNewMeetingPlace ($ this-> MEETING_ID, $ this-> suggested_by, $ this-> id);
Evenzo, wanneer een nieuwe datumtijd wordt toegevoegd, zijn nieuwe gegevens nodig voor MeetingTimeChoice
voor elke deelnemer:
openbare functie afterSave ($ insert, $ changedAttributes) parent :: afterSave ($ insert, $ changedAttributes); if ($ insert) // if MeetingTime is added // add MeetingTimeChoice voor eigenaar en deelnemers $ mtc = new MeetingTimeChoice; $ MTC> addForNewMeetingTime ($ this-> MEETING_ID, $ this-> suggested_by, $ this-> id);
Er wordt van uitgegaan dat wanneer de organisator van de vergadering een plaats of datumtijd toevoegt, deze in eerste instantie voor hen werkt.
Zodra er ten minste één uitgenodigde deelnemer is, één plaats en één keer, kan de organisator van de vergadering de vergadering afronden. In de toekomst zullen we de deelnemers ook toelaten de vergadering af te ronden.
Hoewel deze code in de toekomst enigszins zal veranderen, is er een functie in het Meeting-model die de view vertelt of de Afronden
knop:
public function canFinalize () // controleer of vergadering door kijker kan worden afgerond als ($ this-> canSend ()) // organisator kan altijd finaliseren als ($ this-> viewer == Meeting :: VIEWER_ORGANIZER) $ this -> isReadyToFinalize = true; anders // kijker is een deelnemer // heeft de deelnemer een keer gereageerd of is er slechts één keer // heeft de deelnemer gereageerd op één plaats of is er slechts één plaats
Dit is de weergavecode:
= Html::a(Yii::t('frontend', 'Finalize'), ['finalize', 'id' => $ model-> id], ['class' => 'btn btn-success'. (! $ model-> isReadyToFinalize? 'disabled': ")])?>
Zodra de vergadering is afgerond, verandert MeetingPlanner de modus van ondersteunende planning naar het faciliteren van de aanwezigheden van de deelnemers door een aantal coole functies die we in toekomstige tutorials zullen behandelen.
Ik wilde een paar problemen noemen die ik tegenkwam toen ik de code schreef voor dit relatief ingewikkelde gedeelte.
De SwitchInput
staten werden via JavaScript verzonden als Booleaanse typen, b. waar of niet waar, maar ik moest deze omzetten in gehele getallen om ze met succes via AJAX naar de controllers te verzenden.
// gebruikers kunnen aangeven of een plaats een optie voor hen is $ ('input [name = "meeting-place-choice"]'). on ('switchChange.bootstrapSwitch', function (e, s) // console. log (e.target.id, s); // true | false // set intval om via AJAX door te geven vanuit boolean state if (s) state = 1; else state = 0;
De numerieke ID's van de MeetingPlaceChoice
en MeetingTimeChoice
overlappende widgets. Het kostte me een tijdje om erachter te komen waarom de switch-widgets niet meer goed voor mij konden worden weergegeven toen ik de keuzemogelijkheden toevoeg. Omdat er overlappende ID's waren, werden de switch-widgets alleen voor het eerste object weergegeven.
Het was noodzakelijk om voorvoegsels toe te voegen, zoals mpc-
of mtc-
naar de ID's en haal deze eruit in de acties van de controller.
echo SwitchInput :: widget (['type' => SwitchInput :: CHECKBOX, 'name' => 'meeting-place-choice', 'id' => 'mpc -'. $ mpc-> id, 'tristate' = > true,
Dit is waar we dat prefix in de controller verwijderen om het model te laden:
public function actionSet ($ id, $ state) // caution - inkomende AJAX type problemen met val $ id = str_replace ('mpc -', ", $ id); $ mpc = $ this-> findModel ($ id);
Het kostte me een tijdje om te ontdekken hoe de initiële laadstatus / waarde voor de schakelaarinvoerwidget in de keuzerondmodus kon worden ingesteld. Er was geen documentatie waaruit bleek hoe dit te doen. Ik heb hier eindelijk een uitlegger voor anderen opgeschreven: Instelling van de schakelstand van de schakelaar van de schakelaar voor de status van de schakelaar.
Nu alle AJAX op zijn plaats zit en werkt, wordt het tijd om enkele van de overblijvende delen van de planningsweergave voor vergaderingen af te werken om voorbereidingen te treffen voor uitnodigingen die zijn afgeleverd en die door deelnemers moeten worden bekeken.
De weergave van het vergaderrooster die de deelnemers zien, is bijvoorbeeld anders van lay-out dan de organisator en verschilt afhankelijk van de bevoegdheden die de organisator heeft gedelegeerd.
De kolommen u en die kolommen moeten bijvoorbeeld worden gewijzigd van de huidige implementatie. Er moeten uitgebreide vergadermodel-instellingen zijn die bepalen of deelnemers plaatsen en datumtijden kunnen voorstellen en de vergadering kunnen finaliseren.
Verderop in de toekomst wil ik misschien meerdere deelnemers toestaan en meer kolommen met beschikbaarheid weergeven voor de organisatieweergave - deze functionaliteit maakt geen deel uit van ons minimaal haalbare product (MVP).
Ik moet ook de implementatie van de MeetingLog
die elke wijziging die tijdens het planningsproces aan een vergadering is aangebracht registreert. Dit biedt een soort geschiedenis van planning voor elke vergadering. ik kan gebruiken afterSave ()
evenementen hiervoor ook.
Bekijk de komende tutorials in onze Building Your Startup With PHP-serie - een lijst met aankomende onderwerpen is nu te vinden in onze inhoudsopgave.
Voel je vrij om je vragen en opmerkingen hieronder toe te voegen; Ik neem over het algemeen deel aan de discussies. Je kunt me ook bereiken via Twitter @reifman of mij rechtstreeks een e-mail sturen.