Tijd om het framework van onze nieuwe site weer eens wat verder op te vullen. Dat wil zeggen: we gaan een aantal nieuwe controllers maken, een navigatiesysteem installeren, en een minimum aan styling aanbrengen. Het zal niet verbazen dat het Zend Framework hiervoor de nodige hulpmiddelen aanbiedt.
Controllers
Welke hebben we eigenlijk precies nodig? Dat wordt bepaald door de functies die we willen aanbieden. Om nog even het geheugen op te frissen: we willen nieuws presenteren, en recensies over e-boeken en gadgets om ze te lezen: Ipads, Kindles, maar ook andere apparaten om e-boeken te lezen. Een zoekfunctie is natuurlijk belangrijk, er komen reacties onder artikelen. Er moet een uitgebreid beheersgedeelte komen.
Als je ervan uitgaat dat elke action verantwoordelijk wordt gemaakt voor een bepaalde functie, dan ligt het voor de hand om de controller-structuur zo in te richten dat elke controller een serie gelijksoortige actions groepeert. Die redenering volgend, betekent dat dat we een artikelController nodig hebben waarin nieuws en recensies worden gebundeld; en een gebruikerscontroller die we gebruiken om nieuwe gadgets te publiceren.
Maar functionaliteit is niet het enige criterium op grond waarvan we onze controllers en actions indelen. Ook de navigatie voor de gebruiker speelt een rol. Zoals je je wellicht herinnert, werkt de naamgeving van je controllers en actions rechtstreeks door in de URL-structuur; /user/registratie komt overeen met de registratieAction in de UserController. Nu kunnen we met een specifieke ZF-component, de Router, wel aanpassingen doorvoeren aan deze structuur, maar dat brengt weer zijn eigen complicaties met zich mee. In een latere blogpost zal ik nog terugkomen op de router.
Deze overwegingen leiden tot de volgende controller-structuur; naast de al bestaande Index- en UserController heb ik een BoekenController, een GadgetsController, een NieuwsController en een ZoekController gemaakt. Het procédé ken je inmiddels, als het goed is: met behulp van Zend_Tool maak je in één klap de controller, de indexAction van de controller en de benodigde views aan. Voor elk van bovengenoemde controllers volstaat het in een terminal dit commando in te geven:
php bin/zf.php create controller BoekenLater gaan we in een aparte module alle beheerstaken onderbrengen. Voor het moment volstaan bovengenoemde controllers; deze leveren de pagina’s aan die voor elke gebruike in principe, aan de ‘voorkant’ van de applicatie, benaderbaar zullen zijn.
Navigatie
We hebben nu weliswaar de juiste pagina’s gemaakt, maar die zijn nog niet benaderbaar. Om onze navigatie in te richten, gebruiken we Zend_Navigation. Dit is een uiterst praktische component; op basis van een xml-structuur kun je een menu samenstellen, maar ook een sitemap en een ‘kruimelpad’; dat is al veel functionaliteit voor een enkel xml-bestand, maar hiermee houdt het nog niet op. Zend_Navigation is slim genoeg om de menu-structuur aan te passen aan de al dan niet ingelogde status van de gebruiker, en de juiste submenu’s te tonen afhankelijk van de pagina waar de gebruiker zich bevindt.
De xml-structuur zelf is heel simpel. Mijn bestand ziet er op dit moment als volgt uit:
- <?xml version="1.0" encoding="UTF-8"?>
- <nav>
- <home>
- <label>Home</label>
- <controller>index</controller>
- <action>index</action>
- </home>
- <nieuws>
- <label>Nieuws</label>
- <controller>nieuws</controller>
- <action>index</action>
- <pages>
- <page1>
- <label>Boeken</label>
- <controller>nieuws</controller>
- <action>boeken</action>
- </page1>
- <page2>
- <label>Gadgets</label>
- <controller>nieuws</controller>
- <action>gadgets</action>
- </page2>
- <page3>
- <label>Algemeen</label>
- <controller>nieuws</controller>
- <action>algemeen</action>
- </page3>
- </pages>
- </nieuws>
- <boeken>
- <label>Boeken</label>
- <controller>boeken</controller>
- <action>index</action>
- <pages>
- <page1>
- <label>Fictie</label>
- <controller>boeken</controller>
- <action>fictie</action>
- </page1>
- <page2>
- <label>Non-fictie</label>
- <controller>boeken</controller>
- <action>nonFictie</action>
- </page2>
- <page3>
- <label>Thrillers</label>
- <controller>boeken</controller>
- <action>thrillers</action>
- </page3>
- <page4>
- <label>Jeugd</label>
- <controller>boeken</controller>
- <action>jeugd</action>
- </page4>
- </pages>
- </boeken>
- <gadgets>
- <label>Gadgets</label>
- <controller>gadgets</controller>
- <action>index</action>
- <pages>
- <page1>
- <controller>gadgets</controller>
- <action>eReaders</action>
- <label>E-readers</label>
- </page1>
- <page2>
- <controller>gadgets</controller>
- <action>tablets</action>
- <label>Tablets</label>
- </page2>
- </pages>
- </gadgets>
- <users>
- <label>Gebruikers</label>
- <controller>user</controller>
- <action>index</action>
- </users>
- </nav>
Zoals je ziet hebben we een root-element nav, waarbinnen onze navigatie is ondergebracht. Ons navigatiemenu voorziet verder in vijf pagina’s, het eerste niveau in het xml-bestand: home, nieuws, boeken, gadgets en gebruikers. Als deze pagina’s geen submenu hebben, volstaat het om drie kenmerken van elk item weer te geven: het label, en de bijbehorende controller en action. Zijn er wel submenu’s, dan worden er xml-elementen onder het eerste niveau geplakt: onder ‘pages’ kun je een onbeperkt aantal pagina’s plaatsen, elk weer gekarakteriseerd door een controller, een action en een label.
Je kunt in principe nog dieper gaan, maar dat is bijna nooit aan te bevelen; een menustructuur die meer dan twee niveau’s kent, wordt al gauw onoverzichtelijk.
Nu we een indeling hebben voor onze navigatie, moeten we onze applicatie nog vertellen waar deze te vinden is en waar ze ingezet moet worden. We gebruiken daarvoor een zogeheten plugin – een klasse die naar behoefte wordt geladen bij de start van elke request. Zend Framework biedt verschillende soorten plugins, maar we zijn nu op zoek naar een specifieke soort. Omdat onze navigatie nauw samenhangt met de views die worden gegenereerd, hebben we een plugin nodig die in een laat stadium wordt geladen en die logisch samenwerkt met de views. Die plugin wordt geleverd door Zend_Layout – tevens verantwoordelijk voor de weergave van onze paginasjablonen.
De architectuur van een plugin is simpel. Als de applicatie wordt gemeld dat er een plugin-klasse geladen moet worden, zoekt hij na het laden van die klasse naar methoden die een vastgestelde naam moeten hebben. Vaak zijn dat methoden die een fase in het laden van de applicatie weergeven: preDispatch bijvoorbeeld wordt aangeroepen voordat het request wordt doorgegeven aan de juiste controller, routeShutdown vindt plaats meteen nadat bekend is welke controller wordt aangeroepen.
Zo ziet onze layout-pluginklasse eruit:
- <?php
- class GJB_Plugins_Layout extends Zend_Layout_Controller_Plugin_Layout
- {
- public function preDispatch(Zend_Controller_Request_Abstract $request) {
- $xmlNav = new Zend_Config_Xml(APPLICATION_PATH . '/configs/nav.xml');
- $navigation = new Zend_Navigation($xmlNav);
- $this->getLayout()->getView()->navigation($navigation);
- }
- }
- ?>
De methode preDispatch accepteert het request als argument; in deze methode hebben het request (nog) niet nodig, maar in andere plugins kan het erg praktisch zijn om toegang te hebben tot het requestobject. In de eerste regel wordt ons xml-bestand uitgelezen; en vervolgens wordt dit bestand meegegeven als parameter aan een nieuwe instantie van Zend_Navigation. Het echte werk gebeurt eigenlijk pas in de derde regel: het navigation-object wordt aan het view-object doorgegeven; binnen elke view is de navigatie nu beschikbaar met de aanroep $this->navigation(). De navigatie wordt nu weergegeven als een geneste lijst (met ul en li elementen) die je vervolgens op de gebruikelijke manier van een stijl kunt voorzien. Deze aanroep zelf heb ik in het layout-bestand geplaatst; je wilt je navigatie immers constant houden ongeacht op welke pagina de gebruiker zich bevindt.
Het enige wat nog resteert is om onze applicatie te vertellen dat ze een nieuwe pluginklasse moet laden. Er zijn in principe twee manieren om dat te doen: het kan in de bootstrap-klasse, maar we geven hier de voorkeur aan een simpele toevoeging aan het applicatie ini-bestand. Daar wordt immers al aangegeven waar de layout zelf gevonden kan worden. Het volstaat om deze regel toe te voegen:
resources.layout.pluginClass = GJB_Plugins_LayoutZoals je ziet hoef je alleen maar aan te geven hoe je je plugin hebt genoemd.
Stylesheets
Ik heb een eerste opzet gemaakt van de stylesheet:
- @CHARSET "UTF-8";
-
- html, body, * {
- font-family: Georgia, "Times New Roman", serif;
- }
- img {
- border: 0 none;
- }
- #logo img {
- font-size:44px;
- color:#0077FF;
- }
- #mainWrapper {
- width: 960px;
- background-color:#fdfdfd;
- margin:0 auto;
- border-left: 1px solid #c3c3c3;
- border-right: 1px solid #c3c3c3;
- padding: 0 10px;
- }
-
- #header {
- height: 150px;
- border-bottom:1px solid #c3c3c3;
- padding: 10px 0;
- margin-bottom:10px;
- position:relative;
- top:0;
- left:0;
- }
- #header div {
- float:left;
- }
- #header #sysMsg {
- clear:right;
- float:right;
- }
- #mainContent {
- float:left;
- width: 620px;
- }
- #sidebar {
- float:right;
- width: 336px;
- }
- #sidebar #login {
- text-align:right;
- }
- .col {
- width: 198px;
- margin-right:10px;
- float:left;
- border-right: 1px dotted #c3c3c3;
- }
- .col.last {
- margin-right:0;
- }
- .colContent {
- width: 190px;
- margin: 0 auto;
- }
- #navigation {
- position:absolute;
- bottom: 5px;
- left:300px;
- }
- ul.navigation li {
- display:inline;
- margin-right: 20px;
- }
- ul.navigation li ul {
- display:none;
- }
- .clear {
- clear:both;
- }
- #header #search {
- float:right;
- width: 336px;
- text-align:right;
- }
- #header #logo {
- width: 620px;
- }
Ik ben geen designer, dus dit is vast voor verbetering vatbaar; en het echte elesio ligt, op het moment van schrijven, bij de ontwerper. Het definitieve stylesheet zal er dus ongetwijfeld anders uitzien, maar voor het moment doen we het hiermee.
Het valt je misschien op dat een aantal basisregels voor het gelijktrekken van het uiterlijk van de site over verschillende browsers ontbreekt. Dat komt omdat we die regels ook niet zelf gaan maken, maar de CSS-bibliotheek van Yahoo gebruiken. In ons sjabloonbestand layout.phtml roepen we de stylesheets als volgt aan:
- <?php
- $this->headLink()->appendStylesheet('http://yui.yahooapis.com/3.0.0/build/cssreset/reset-min.css');
- $this->headLink()->appendStylesheet('http://yui.yahooapis.com/3.0.0/build/cssbase/base-min.css');
- $this->headLink()->appendStylesheet('http://yui.yahooapis.com/3.0.0/build/cssfonts/fonts-min.css');
- $this->headLink()->appendStylesheet('/css/main.css');
Elke view-instantie biedt een methode aan die headLink() wordt genoemd; deze methode levert je een headLink object op. Aan dit object hang je alle verschillende links die je in de head-sectie van je html-pagina wil tonen. Dat zijn meestal dus de stylesheets (<link type=’text/css’ rel=’stylesheet’ … />), maar ook je favicon wordt op deze manier aan je document gehangen. De eerste drie linkjes in de code hierboven verwijzen naar de basis-css die Yahoo biedt; de laatste is de link naar ons eigen stylesheet. Omdat we later in ons layout-script de aanroep <?php echo $this->headLink();?> hebben staan, worden deze vier stylesheets beschikbaar gemaakt voor onze pagina’s. In de html-code ziet dat er zo uit:
<link href="http://yui.yahooapis.com/3.0.0/build/cssreset/reset-min.css" media="screen" rel="stylesheet" type="text/css" />
<link href="http://yui.yahooapis.com/3.0.0/build/cssbase/base-min.css" media="screen" rel="stylesheet" type="text/css" />
<link href="http://yui.yahooapis.com/3.0.0/build/cssfonts/fonts-min.css" media="screen" rel="stylesheet" type="text/css" />
<link href="/css/main.css" media="screen" rel="stylesheet" type="text/css" />Tot besluit
Onze acties van vandaag leiden tot deze pagina:
Nu je weet hoe stylesheets gekoppeld worden, en je zelf je navigatie kunt aanpassen, kun je het ontwerp helemaal naar je eigen zin bewerken.
We zijn nog niet helemaal klaar met frontend-functionaliteit: de submenu’s worden nog niet getoond. Dat gaan we met behulp van javascript, en meer specifiek met JQuery, in orde brengen. De volgende klus die we gaan ondernemen: de ingelogde gebruikers moeten de mogelijkheid krijgen om gadgets, nieuws en recensies te plaatsen. Dat betekent veel formulier en databasewerk, de volgende keer!





Dit is het techblog van Raker. Meer over mij en mijn bedrijf vindt u
Follow Me!