Todo app met Meteor

April 27th, 2013 | 12 min read | JavaScript, Meteor.js, Web

Meteor is een krachtig platform waarmee je op een zeer snelle manier web applicaties kan ontwikkelen, builden en deployen. Als dat niet genoeg is, wacht maar tot je de voordelen ziet! In deze tutorial ga ik een todo list applicatie creëren en aan de hand daarvan de voordelen tonen die Meteor kan bieden.

Voorbereiding

Om het Meteor platform te installeren (Mac & Linux) dien je het volgende commando uit te voeren:

curl http://install.meteor.com | /bin/sh

Hiermee ga je een Shell-script downloaden en uitvoeren. Voor Windows is er momenteel nog geen officiële support, maar je kan wel de Windows installer gebruiken die je hier kan vinden. Dat is nu het voordeel van open source… bestaat het niet? Dan maakt de community het wel.

install-meteor

Eenmaal klaar kan je beginnen met een project aan te maken. Maak een nieuwe project-map aan, bijvoorbeeld todo-app. Dit doe je door middel van:

meteor create todo-app

.
Daarna kan je het project uitvoeren door middel van:

cd todo-app
meteor

Even geduld en …. de template applicatie is gedeployed op http://localhost:3000.

create-project

HTML templating

Wat je tot nu toe gedaan hebt is dat je het Meteor platform geïnstalleerd hebt, een nieuw project hebt aangemaakt (creëert een template) en dit template/hello world project uitgevoerd hebt op je lokale machine.
Bij het creëeren van jouw project zijn er drie bestanden aangemaakt, namelijk todo-list.html, todo-list.css en todo-list.js.

Het eerste wat we gaan doen is de HTML code voorzien. De code hiervoor (todo-list.html) is:

<head>
    <title>Todo app</title>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet" />
    <script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script>
</head>

<body>
    <div class="container">
        <h1>Todo lijst</h1>
        {{> todo_list}}
    </div>
</body>

<template name="todo_list">
    {{#with tasks}}
        {{#if count}}
            <table class="table table-hover">
                <thead>
                    <tr>
                        <th class="span1">&nbsp;</th>
                        <th class="span11">Taak</th>
                    </tr>
                </thead>
                <tbody>
                    {{#each this}}
                        {{> task}}
                    {{/each}}
                </tbody>
            </table>
        {{else}}
            <div class="alert alert-info">
                <button type="button" class="close" data-dismiss="alert">&times;</button>
                <strong>Opgelet!</strong> U hebt nog geen taken toegevoegd.
            </div>
        {{/if}}
    {{/with}}
    {{> new_task}}
</template>

<template name="task">
    <tr>
        <td><input type="checkbox" value="{{_id}}"/></td>
        <td>{{description}}</td>
    </tr>
</template>

<template name="new_task">
    <form>
        <legend>Nieuwe taak</legend>
        <div class="controls controls-row">
            <input class="span10" type="text" id="description" placeholder="Taak" />
            <button class="btn span2" id="add-btn"><i class="icon-plus"></i> Nieuwe taak</button>
        </div>
    </form>
</template>

Zoals je kan zien wijkt dit redelijk af van gewoon wat HTML code. Daarom dat ik alles stap voor stap zal uitleggen. Het eerste wat je moet begrijpen is dat Meteor een platform is dat allerlei packages bundelt tot één geheel. Zo maken we in dit voorbeeld gebruik van de Handlebars.js template engine.

Het eerste wat we gedaan hebben is dat we een

<head>

hebben voorzien waarin we gebruik maken van Twitter Bootstrap (zo moet ik niet teveel over het design inzitten). Dit doen we in:

<head>
    <title>Todo app</title>
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet" />
    <script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script>
</head>

Vlak daaronder hebben we de default HTML voorzien, namelijk:

<body>
    <div class="container">
        <h1>Todo lijst</h1>
        {{> todo_list}}
    </div>
</body>

Zoals je kan zien staat hier enorm weinig in, waarom? Omdat alles via templates (namelijk

{{> todo_list}}

) geladen wordt. Dit heeft als voordeel dat we bepaalde collections, events, … kunnen koppelen aan zo’n template, maar da’s voor later. Het belangrijkste dat je moet onthouden is dat als je gebruik wenst te maken van business logica, je beter de view die daarvan gebruik gaat maken encapsuleert in een template.

Todo list template

Als we nu naar de template met de naam todo_list gaan kijken, dan vinden we de volgende template:

<template name="todo_list">
    {{#with tasks}}
        {{#if count}}
            <table class="table table-hover">
                <thead>
                    <tr>
                        <th class="span1">&nbsp;</th>
                        <th class="span11">Taak</th>
                    </tr>
                </thead>
                <tbody>
                    {{#each this}}
                        {{> task}}
                    {{/each}}
                </tbody>
            </table>
        {{else}}
            <div class="alert alert-info">
                <button type="button" class="close" data-dismiss="alert">&times;</button>
                <strong>Opgelet!</strong> U hebt nog geen taken toegevoegd.
            </div>
        {{/if}}
    {{/with}}
    {{> new_task}}
</template>

Deze template lijkt vrij complex maar uiteindelijk valt het nog allemaal vrij goed mee. Het eerste wat we controleren is ofdat we momenteel al taken ingevuld hebben. Dit doen we met:

{{#with tasks}}
    {{#if count}}
        ...
    {{else}}
        ...
    {{/if}}
{{/with}}

Hier zien we een eenvoudige if-else structuur op basis van het aantal elementen in de collectie. Hoe we deze collectie gaan toekennen zal ik straks in het JavaScript gedeelte bespreken.

Als we inderdaad al enkele taken ingevoerd hebben, dan gaan we een tabel tonen met daarin een lijst van alle taken en een checkbox die als we ze aanvinken, de taak zal verwijderen (markeren als voltooid). De tabelstructuur wordt getoond via:

<table class="table table-hover">
    <thead>
        <tr>
            <th class="span1">&nbsp;</th>
            <th class="span11">Taak</th>
        </tr>
    </thead>
    <tbody>
        {{#each this}}
            {{> task}}
        {{/each}}
    </tbody>
</table>

Op zich valt dit ook nog vrij goed mee. We maken een tabel met wat Twitter Bootstrap classnames (voor de style ervan) en om de rijen in de collectie te tonen maken we gebruik van de

{{#each}}

opdracht. Zoals je kan zien verwijst deze opnieuw naar een nieuwe template die ik zometeen zal bespreken.

Indien er nog geen taak toegevoegd is tonen we een informatief bericht, namelijk:

<div class="alert alert-info">
  <button type="button" class="close" data-dismiss="alert">&times;</button>
  <strong>Opgelet!</strong> U hebt nog geen taken toegevoegd.
</div>

Ook dit is gewoon een bericht met wat Twitter bootstrap classnames om er een alert van te maken.

Ten slotte hebben we ook nog een template voor een form om een nieuwe taak toe te voegen, namelijk

{{> new_task}}

.

Task template

Gelukkig zijn we nu van het meest complexe qua template vanaf. De task template wordt gebruikt om een eenvoudige table row te tonen.

<template name="task">
    <tr>
        <td><input type="checkbox" value="{{_id}}"/></td>
        <td>{{description}}</td>
    </tr>
</template>

Wat we hier doen is eigenlijk voor elke taak de beschrijving (

{{description}}

) tonen en een checkbox. Aan de checkbox geven we als waarde ook nog de ID van het item mee dat gegenereerd wordt voor ons.
Deze ID hebben we immers nadien nodig om het object met dat ID uit de database (MongoDB) te kunnen verwijderen.

Nieuwe taak template

Ten slotte hebben we ook nog de template voor een nieuwe taak. Wederom is hier niet veel speciaals te vinden.

<template name="new_task">
    <form>
        <legend>Nieuwe taak</legend>
        <div class="controls controls-row">
            <input class="span10" type="text" id="description" placeholder="Taak" />
            <button class="btn span2" id="add-btn"><i class="icon-plus"></i> Nieuwe taak</button>
        </div>
    </form>
</template>

Dit is een eenvoudige form die we gaan gebruiken om een nieuwe taak toe te voegen.

JavaScript code

Nu hebben we de volledige view geschreven en is het tijd voor wat logica erachter te plaatsen. Open het bestand todo-list.js en plaats er de volgende code:

Tasks = new Meteor.Collection("tasks");

if (Meteor.isClient) {
    Template.todo_list.tasks = function() {
        return Tasks.find({}, {
            sort: {
                description: 1
            }
        });
    };

    Template.new_task.events = {
        'click button#add-btn' : function() {
            var descriptionNode = document.getElementById('description');
            Tasks.insert({
                description: descriptionNode.value
            });
            descriptionNode.value = "";
        }
    };

    Template.todo_list.events = {
        'change input' : function(evt) {
            if (evt.target.checked) {
                Tasks.remove({
                    _id: evt.target.value
                });
            }
        }
    }
}

if (Meteor.isServer) {
    Meteor.startup(function () {
        // code to run on server at startup
    });
}

Ook deze code valt goed mee. Het eerste wat we gaan doen is controleren of het huidige script in de client of op de server uitgevoerd wordt. Één van de strategieën van Meteor is dat er geen service-platform en client-platform meer is (achter de schermen), maar enkel het Meteor-platform.
Dit platform kan voor zowel client- als serverside scripting gebruikt worden maar in deze tutorial gaan we enkel gebruik maken van client-code.

De controle ofdat de code op de server of de client uitgevoerd wordt kan je maken met:

if (Meteor.isClient) {
    ...
}

if (Meteor.isServer) {
    ...
}

het volgende wat we gaan doen is de todo_list vullen (die we in de template gebruiken). Dit doen we met:

Template.todo_list.tasks = function() {
    return Tasks.find({}, {
        sort: {
            description: 1
        }
    });
};

Wat we hier doen is een MongoDB query gebruiken om de juiste taken te vinden. We sorteren ook op de beschrijving/naam van de taak door in het sort-object het veld

description

te gebruiken. De waarde 1 wilt hier zeggen dat we van A naar Z sorteren. Indien we een negatief getal zouden opgeven zouden we van Z naar A sorteren.

Het volgende wat we doen is een click-event handler aan de knop koppelen, dit doen we met:

Template.new_task.events = {
    'click button#add-btn' : function() {
        ...
    }
};

Zoals je ziet is dit ook afhankelijk van de template waar we dit in gebruiken. Omdat de knop in de new_task template te vinden is, koppelen we deze event map (een map van event handlers) ook aan

Template.new_task

.
Als we op de knop klikken moeten we een nieuwe taak aan de lijst toevoegen. Dit doen we door een nieuw Task object toe te voegen aan MongoDB met:

var descriptionNode = document.getElementById('description');
Tasks.insert({
    description: descriptionNode.value
});
descriptionNode.value = "";

Daarnaast moeten we ook een event handler koppelen aan de checkbox, want als deze aangevinkt wordt moeten we uiteraard ook de taak verwijderen. Dit doen we op basis van een ID die automatisch gegenereerd wordt in het

_id

veld van het MongoDB object. De code hiervoor is:

Template.todo_list.events = {
    'change input' : function(evt) {
        if (evt.target.checked) {
            Tasks.remove({
                _id: evt.target.value
            });
        }
    }
}

Omdat we alle code op de client uitvoeren hebben we geen server-afhankelijke code nodig. We laten de gegenereerde code dan ook gewoon staan:

if (Meteor.isServer) {
    Meteor.startup(function () {
        // code to run on server at startup
    });
}

Project deployen

Geloof het of nooit, het project is volledig klaar. Sterker nog, de code die gewijzigd is is ook automatisch gedeployed en beschikbaar gemaakt in je browser. Je moet zelfs niet eens op vernieuwen klikken! Deze functionaliteit, hot code pushes genoemd, is een enorm handige troef van Meteor, de nieuwe code wordt namelijk rechtstreeks gepusht naar alle clients, waardoor het uitrollen van je applicatie dus eigenlijk peanuts is.

Als je naar http://localhost:3000 gaat zal je merken dat we onze todo applicatie te zien krijgen.

todo-app-1

Voeg een nieuwe taak toe en je ziet dat de taak automatisch zichtbaar wordt zonder dat we hier ook maar iets van AJAX calls of display update hebben moeten uitvoeren. Ook dit wordt gewoon via het Meteor.js platform aangeboden en noemen ze live page updates.

todo-app-2

Sterker nog, de nieuwe taak wordt direct naar alle clients gepusht, het mooie is dus dat alle data gesynchroniseerd wordt zonder enig probleem. Open de applicatie maar eens in een nieuw tabblad en voeg opnieuw een taak toe… ja hoor, in beide vensters is de taak vrijwel direct zichtbaar.

todo-app-3

Wat nu als je gigantisch veel wijzigingen aanbrengt vraag je je af? Moet je wachten op de server alvorens de view update? Nee hoor, dankzij latency compensation wordt de display direct geüpdate onafhankelijk van de response van de server. Mislukt er iets? Geen probleem, de view wordt direct gecorrigeerd.

Publish

Mooi aan het Meteor platform is ook dat ze aanbieden om apps te hosten, ideaal voor demos of kleine voorbeelden. Deploy je app op de meteor.com server via het volgende commando:

meteor deploy todo-list-demo.meteor.com

Je zal wellicht een andere naam moeten invoeren, maar als je daarna gewoon naar die URL gaat, in mijn geval http://todo-list-demo.meteor.com/ dan zie je ook dat de applicatie gedeployed is en voor iedereen zichtbaar is. Enorm handig dus!

Meteor.js staat nog vrijwel in z’n kinderschoenen, maar omdat het een open platform is dat allerlei andere packages geïntegreerd aanbiedt, kan je ook gewoonweg een tarball van je project maken dat gewoonweg met Node.js uitgevoerd kan worden. Dit doe je met:

meteor bundle todo-app.tar.gz

In deze tarball is een readme te vinden over hoe je deze applicatie met Node.js kan uitvoeren zonder dat je ook nog maar iets met Meteor moet doen.

Conclusie

Meteor mag dan in z’n kinderschoenen staan maar het biedt wel allerlei functies die tegenwoordig belangrijk worden, zoals latency compensation, live page updates, data synchronisation en hot code pushes.

De leercurve is ook vrij klein omdat het over JavaScript gaat (wat ondertussen elke web developer wel kent), en ook de tijd die je spendeeert om moderne web applicaties te creëeren wordt flink terug geschroefd dankzij Meteor. We hebben namelijk een (in mijn ogen) vrij mooie en functionele web applicatie gemaakt in relatief zeer weinig tijd en uiteindelijk hebben we nog geen 100 regels code geschreven.

Download project

Download – todo-app.tar.gz (10 kB)
Demo

Back to tutorialsContact me on TwitterDiscuss on Twitter

Profile picture

Dimitri "g00glen00b" Mestdagh is a consultant at Cronos and tech lead at Aquafin. Usually you can find him trying out new libraries and technologies. Loves both Java and JavaScript.