Schrijf je in voor events in KnockoutJs

In mijn vorige blog heb ik laten zien hoe KnockoutJs fraai in Asp.Net MVC3 integreert. Zonder de werking van MVC3 te ondermijnen, kan nu voorkomen worden dat je JavaScript een spaghetti wordt.

KnockoutJs is namelijk een MVVM implementatie waardoor de opmaak (de DOM elementen) en de achterliggende JavaScript code van elkaar gescheiden worden. Wijzigingen in de DOM (invoer van tekst, een checkbox aanvinken, een button click, etc.) worden opgepakt door het ViewModel van KnockoutJs en andere controls reageren op die ViewModel wijzigingen door bindings.

Prima. Voorbeeldje:

Je kan met het omzetten van een checkbox een boolean waarde in het ViewModel manipuleren (via een binding) en die wordt dan weer ‘geobserveerd’ door een div. Die div wordt dan namelijk (afhankelijk van de boolean waarde) zichtbaar of onzichtbaar via de Visible binding.

(de onderstaande codevoorbeelden zijn aanpassingen op het voorbeeld uit de andere blog)

Op de Edit.cshtml zetten we een div met de binding:

<div data-bind="visible: IsScary">   
  Did you know? Addams's original cartoons were one-panel gags. 
  <br/>
  The characters were undeveloped and unnamed until later versions.
</div>

Dit werkt prima, maar wat als je die visible/invisible had willen oplossen met jQuery? Dan had je in code een .toggle() willen uitvoeren, of het willen oplossen via een .show() en een .hide() met eventueel een vertraging.

Een FOUT alternatief zou dan zijn om gebruik te maken van een extra dependentObservable…

Op de Edit.cshtml staat nu gewoon een div zonder binding:

<div id="didYouKnow">   
  Did you know? Addams's original cartoons were one-panel gags.
  <br/>
  The characters were undeveloped and unnamed until later versions.
</div>

En in de home.edit.js is het KnockoutJs ViewModel uitgebreid:

$(function () {
    var clientViewModel = ko.mapping.fromJS(serverViewModel);
 
    //skip some code
 
    clientViewModel.DoSomethingWhenTheScarybooleanChanges 
                      = ko.dependentObservable(function () {
        if (this.IsScary()) {
            $('#didYouKnow').show(500);
        }
        else {
            $('#didYouKnow').hide(250);
        }
        return 'dummy';
    }, clientViewModel);
 
    ko.applyBindings(clientViewModel);
});

Dit werkt prima. De div verdwijnt sneller dan dat deze weer getoond wordt als het vinkje aangezet wordt.

Pas dit niet toe!

Er is nu een directe koppeling tussen het ViewModel en een control op de pagina. Als het control verdwijnt of hernoemd wordt, dan moet het VIEWMODEL aangepast worden.

Het zelfde geldt voor code in de lees en schrijf ‘methodes’ op de property (read en write callbacks; zie example 1 in de KnockOut documentatie).

Er is een simpele en elegante oplossing!

Kijk, uiteindelijk moeten we ergens de code schrijven voor de show en de hide; daar is geen ontkomen aan. Maar we willen die code loskoppelen van het ViewModel. Het ViewModel mag hoogstens een seintje geven dat er iets veranderd mag worden. Maar wat er gaat veranderen, dat speelt zicht BUITEN het ViewModel af.

Wat we willen is een Event. En KnockoutJS kent events (zie Explicit Subscribing to Observables (onderaan de pagina))

Herschrijf de vorige code  naar de nieuwe ViewModel invulling:

$(function () {
    var clientViewModel = ko.mapping.fromJS(serverViewModel);
 
    // skip some code
 
    var isScarySubscription = 
        clientViewModel.IsScary.subscribe(OnIsScareChanged);
 
    ko.applyBindings(clientViewModel);
});
 

function OnIsScareChanged(isScary) {
    if (isScary) {
        $('#didYouKnow').show(500);
    }
    else {
        $('#didYouKnow').hide(250);
    }
}

Wat we hier zien is dat op het boolean veld IsScary een ‘event’ OnIsScaryChanged wordt geabonneerd. Dit abonnement wordt vertegenwoordigd door de variabele isScarySubscription. En we zien dat aan de functie heel netjes een boolean isScary wordt meegegeven die de nieuwe waarde van de checkbox bevat.

De functie met daarin directe referenties naar controls is nu dus netjes losgekoppeld van het ViewModel. Sterker nog, we kunnen de functie ook ‘fysiek’ loskoppelen van het ViewModel via code. Voer maar eens net achter de applyBindings de volgende regel uit:

isScarySubscription.dispose();

Als we nu de checkbox van waarde veranderen, dan gebeurt er met de div… helemaal niks. Het event is namelijk net zo snel ontkoppeld als weer gekoppeld aan de IsScary ‘property’.

Advertenties