Complexe webapplicaties maak je met MVC

Complexe webapplicaties maak je met MVC

Rutger Hokke
Geplaatst op 23 december 2013

Met WordPress websites kun je alle kanten op. De complexiteit van WordPress varieert van relatief eenvoudige blog sites met enkele pagina’s en berichten tot sites met interactieve kaarten of complexe webapplicaties.

Bij webapplicaties en interactieve kaarten reageert de website op handelingen van de gebruiker en dit kan aardig complex worden. Met het MVC (Model, View, Controller) principe houd je grip op de webapplicaties doordat de componenten van wijzigingen naar elkaar ‘luisteren’.

In dit blogbericht leg ik uit waarom MVC zo goed werkt bij complexe webapplicaties.

Webapplicaties reageren op de gebruiker

Als de gebruiker iets op een site doet, reageert de site hier op. Denk bijvoorbeeld aan het aanvinken van een checkbox of het selecteren van een element uit een dropdown.
De website moet deze reactie wel kunnen afhandelen. Eén van de meest gebruikte libraries hiervoor ter wereld is jQuery. Ook in WordPress is dat de standaard meegeleverde library.

In onderstaand voorbeeld wordt bijvoorbeeld bij een invulformulier gecheckt of de ingevulde tekst in het veld email wel voldoet aan de voorwaarden om een geldig e-mailadres te zijn:

jQuery( '.email').change( function( event ) {
var field = event.target;
if( !isValidEmail( field )) {
foutmelding( field, ‘geen geldig e-mail adres’);
}
});

Componenten webapplicatie beïnvloeden elkaar

Een webapplicatie bestaat uit verschillende componenten: bijvoorbeeld een lijst, een kaart, een formulier, etc. Gebeurtenissen in een component kunnen andere componenten beïnvloeden. Een vinkje in een formulier maakt dat een lijst andere gegevens toont. Een wijziging in een dropdown verandert de kleur van een icoon.

Voor kleine, eenvoudige webapplicaties kan een dergelijke lijst met gebeurtenissen en handelingen voldoende zijn, maar als een webapplicatie groter wordt en het aantal gebeurtenissen en componenten groeit, dan wordt het aantal onderlinge invloeden tussen de componenten te groot.

Simpele webapplicatie

Complexe webapplicatie

Voorbeeld webapplicatie

Neem bijvoorbeeld de site www.basisscholenapeldoorn.nl. Het is mogelijk om een lijst met favoriete scholen bij te houden en deze per e-mail te versturen.

Het toevoegen van een favoriete school kan op verschillende manieren:

  • klik op ‘Favoriet’ in de ballon.
  • klik op ‘Favoriet’ in het detailscherm.

Het verwijderen van een favoriet kan ook op verschillende manieren:

  • klik op ‘Favoriet’ in de ballon.
  • klik op ‘Favoriet’ in het detailscherm.
  • klik op ‘Verwijder’ in de lijst met favoriete scholen.

Wat gebeurt er als iemand op ‘verwijderen’ klikt in de lijst?

basisscholen1

Als een school van uit de lijst wordt verwijderd, is dit te zien op een aantal verschillende plaatsen:

  • De icoon in de kaart verandert van een sterretje in een school.
  • De knop ‘Favoriet’ in de ballon wordt zwart.
  • In het menu wordt de teller met 1 verlaagd. Als er geen favoriete scholen meer in de lijst staan, verdwijnt de link ‘e-mail favorieten’ uit het menu.
  • De lijst met favoriete scholen wordt bijgewerkt.

basisscholen2

jQuery zonder MVC

In een eenvoudige webapplicatie is het nog mogelijk om bij elke gebeurtenis op te sommen welke handelingen uitgevoerd moeten worden.

<pre>jQuery('.ballon .favoriet').click( function( event ) {
    if( active ) {
        addToList( school );
        setColor( buttons, 'green');
    } else {
        removeFromList( school );
        setColor( buttons, 'black');
    }
    modifyCounter();
    modifyList();
});</pre>
jQuery( '.lijst .verwijder').click( function( event ) {
removeFromList( school );
setColor( button, ‘black’);
modifyCounter();
modifyList();
});

Een groot nadeel van deze aanpak is dat je precies moet beschrijven welke veranderingen doorgevoerd moeten worden bij elke wijziging. Naarmate er componenten worden toegevoegd aan de applicatie wordt dit steeds gecompliceerder.

Code met MVC: Model/View/Controller

Om de code onderhoudbaar te houden is het nodig om de afhankelijkheden tussen componenten te verkleinen. We splitsen de code op in Modellen, Views en Controllers (MVC).

Model

Het model bevat de data waarop de veranderingen plaatsvinden. In ons voorbeeld zijn er 2 modellen: een School en een lijst met favorieten

School = new Model( {
attributes: {
favoriet: 'boolean'
naam: 'string',
adres: 'string',
// etc.
},

init: function() {
// als het attribuut 'favoriet' wijzigt,
// wordt de school toegevoegd of verwijderd uit de lijst
this.bind('favoriet', function() {
if( self.favoriet ) {
favorieten.push( self );
} else {
favorieten.remove( self );
}
}
}
});

// favorieten is de lijst met alle favoriete scholen
favorieten = new School.List([]);

View

Een view is een weergave van een Model. In ons geval zijn er diverse views:

  • ballon
  • icoon
  • favorietenlijst
  • menu

De view luistert naar wijzigingen in het Model en past zichzelf automatisch aan. Bijvoorbeeld in de ballon.html

<div><input type="button" value="Meer informatie" />
<input class="&quot;&lt;%=school.attr('favoriet')" type="button" />"
value="Favoriet" /&gt;</div>

of in menu.html

<div>Favorieten
<a href="#toon">
&lt;%=favorieten.attr('length')%&gt;
&lt;%=favorieten.attr('length') == 1 ?'school' :'scholen'%&gt;
</a>
&lt;% if( favorieten.attr('length')) { %&gt;
<a href="#mail">e-mail favorieten</a>
&lt;% } %&gt;</div>

Controller

Een controller is de lijm tussen de View en een Model. Bij de controller worden gebeurtenissen vertaald naar wijzigingen in het Model in de ballonController worden de gebeurtenissen in de ballon bijgehouden:


// de gebruiker klikt op de knop ‘Favoriet’
'.favorite click': function( element, event ) {

// zoek de school die bij deze ballon hoort
var school = this.options.school;

// wijzig het attribuut favorite van deze school
school.attr('favorite', !school.attr('favorite'));
}

de lijstController houdt de gebeurtenissen in de lijst in de gaten:

// de gebruiker klikt op de link ‘verwijder’
'a.verwijder click': function( element, event ) {

// zoek de bijbehorende school
var school = $(element).closest('.school').instance();

// zet het attribuut favorite op false
school.attr('favorite', false );
}

Een groot verschil met de eerdere code is dat er alleen data worden aangepast. De school is favoriet of niet. Wat daarvan de gevolgen zijn is hier niet van belang. De gebruiker geeft aan dat de school (niet langer) favoriet is en dat wordt uitgevoerd. Het School Model stuurt vervolgens echter een signaal naar alle views die zijn gekoppeld dat er een wijziging heeft plaatsgevonden en alle views verversen zichzelf.

Uitbreidbaar

In de loop van het project blijkt dat er nog een extra detail-scherm moet komen voor de school, waar ook een knop ‘Favoriet’ in staat. We hoeven nu alleen een nieuwe View ‘details.html’ te maken met daarin

<div><input type="button" /> class="&lt;%=school.attr('favoriet') ? 'on' : 'off'%&gt;"
value="Favoriet" /&gt;</div>

en een detailController voor de gebeurtenissen in het detailscherm:

// de gebruiker klikt op de knop ‘Favoriet’
'.favorite click': function( element, event ) {
// zoek de school die bij dit detailscherm hoort
var school = this.options.school;

// wijzig het attribuut favorite van deze school
school.attr('favorite', !school.attr('favorite'));
}

Met de nieuwe Controller en View is een nieuwe component toegevoegd. Er hoeft verder niets te worden aangepast; alles blijft werken als voorheen.

Javascript frameworks

Er zijn de afgelopen jaren veel verschillende javascript frameworks ontwikkeld die volgens het Model/View/Controller principe werken. Bij Max.nl hebben we jarenlang ervaring met CanJS.

Blijf op de hoogte!

Rutger Hokke

Rutger staat altijd klaar om mee te denken voor een oplossing voor al je technische issues. Voor een interactieve kaart of een koppeling met een extern systeem ben je bij hem aan het juiste adres. Hij schrijft dan ook over internettechnieken die het leven mooier maken.

Meer over Rutger