Dojo time ago widget (i18n)

Tijdens de vorige tutorial hebben we onze eerste Dojo widget geschreven. In deze tutorial ga ik iets uitgebreider te werk gaan en ga ik een Dojo time ago widget schrijven (zoals je op Twitter kan zien), maar in deze tutorial ga ik tevens gebruik maken van i18n om de widget in verschillende talen beschikbaar te maken.

Project structuur

De IDE laat ik net zoals vorige keer volledig open, je zou in principe zelfs aan de slag kunnen met kladblok. Wat je echter wel nodig hebt is de juiste projectstructuur.

Net zoals vorige keer hebben we een

index.html

webpagina nodig waar we de widget op gaan plaatsen. Daarnaast hebben we een

assets

-map nodig met daarin de map

js/my/nls/nl

.

In de map

assets/js

maak je dan het bestand

additions.js

aan, dit zal de JavaScript code bevatten die we aanroepen vanuit de index-pagina.
In de map

assets/js/my

maak je zowel het bestand

timeAgo.html

als

timeAgo.js

aan. Dit zijn de twee bestanden die de widget gaan vormen (de template en de logica erachter).
Ten slotte maak je zowel in de map

assets/js/my/nls

en de map

assets/js/my/nls/nl

het bestand

resources.js

aan.

Je project zou er dan moeten uitzien als in onderstaande afbeelding.

project-structure

Index HTML pagina

De volgende stap is net zoals vorige keer dat we de index-pagina gaan voorzien van de nodige code om de Dojo widget te kunnen genereren.
Deze pagina is zeer gelijkaardig aan wat we vorige keer gedaan hebben, enkel zullen we hier te maken hebben met een andere widget en hebben we geen CSS meer. De uitleg over wat deze pagina zoal doet kan je nalezen in m’n vorige tutorial.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>TimeAgo example</title>

        <script type="text/javascript">
            dojoConfig = {
                parseOnLoad : true,
                async: true,
                packages: [
                    {
                        name: 'my',
                        location: location.pathname.replace(/\/[^/]+$/, '') + '/assets/js/my'
                    }
                ]
            }
        </script>
        <script src="//ajax.googleapis.com/ajax/libs/dojo/1.8.3/dojo/dojo.js"></script>
        <script type="text/javascript" src="assets/js/additions.js"></script>
    </head>
    <body>
        <span data-dojo-type="my/timeAgo" data-dojo-props="date: '2013-01-26T16:05:00Z', frequency: 15000"></span>
    </body>
</html>

Merk wel op dat we hier twee properties kunnen instellen, de datum en de update-frequentie in milliseconden.

Additions.js

Ook dit script heeft veel weg van wat we vorige keer gedaan hebben en stelt op zich niet veel voor, maar hier kan uiteraard alle JavaScript code geplaatst worden die je denkt nodig te hebben.

require(["my/timeAgo"]);

Ook dit kan je nalezen in m’n vorige tutorial, dus hierover ga ik niets meer vertellen.

Widget HTML template

De HTML template voor de widget is dit maal vrij eenvoudig en bestaat uit één enkel

<span>

veld.

<span class="${baseClass}" data-dojo-attach-point="timeNode">Just a moment ago</span>

Net zoals vorige keer maken we hier gebruik van de placeholder

${baseClass)

die in de JavaScript code van de widget gedefinieerd zal worden. We gebruiken hier tevens een

data-dojo-attach-point

om in de JavaScript code makkelijk de HTML DOM node van de template te wijzigen.

Internationalization (i18n)

Nieuw in deze widget is dat we gebruik gaan maken van de

dojo/i18n

module waarmee we de widget in meerdere talen beschikbaar kunnen maken. Open hiervoor

resources.js

in

assets/js/my/nls

.

Internationalization houdt in dat we eigenlijk een abstractielaag gaan plaatsen tussen de views/model laag (in dit geval de HTML template en de widget JavaScript code) en de taalafhankelijke zaken. Door zo’n abstractielaag kunnen we eenvoudig van taal wisselen (omdat de views taal-onafhankelijk worden) en kunnen we ook meerdere talen gaan voorzien.

In

resources.js

plaats je de volgende code:

define({
    root: {
        momentsAgo: "Just a moment ago",
        secondsAgo: "seconds ago",
        minuteAgo: "A minute ago",
        minutesAgo: "minutes ago",
        halfHourAgo: "Half an hour ago",
        hourAgo: "An hour ago",
        hoursAgo: "hours ago",
        today: "today",
        yesterday: "yesterday"
    },

    nl: true
});

Zoals je kan zien staan hier verschillende key/value pairs in opgesomd. De key in het internationalization proces is de unieke sleutel waarmee we de taalafhankelijke label gaan ophalen, in dit geval zijn dat de keys zoals

momentsAgo

,

secondsAgo

,

minuteAgo

, enz.
De implementatie die we hier gebruiken is de default-implementatie, namelijk als er geen localization beschikbaar is voor jouw locale.

De locale die in deze widget beschikbaar is, is Nederlands, dat kan je onderaan definieren via

nl: true

. Als je in België woont is jouw locale waarschijnlijk eerder nl-BE, dus dan zou je het volgende moeten doen:

"nl-BE": true

. Omdat een dash een gereserveerd is als minteken moeten we hier echter wel quotes rond de locale plaatsen, mochten we deze wensen te voorzien.

De

dojo/i18n

module gaat dan op zoek naar een bestand

resources.js

in een map met de naam van je locale, dus in dit geval is dat

assets/js/my/nls/nl/resources.js

.

In dit bestand gaan we dus alle key/value pairs terug opsommen, maar dan met Nederlandse tekst. Dat wordt dan:

define({
    momentsAgo: "Enkele seconden geleden",
    secondsAgo: "seconden geleden",
    minuteAgo: "Een minuut geleden",
    minutesAgo: "minuten geleden",
    halfHourAgo: "Een half uur geleden",
    hourAgo: "Een uur geleden",
    hoursAgo: "uren geleden",
    today: "Vandaag",
    yesterday: "Gisteren"
});

Zoals je kan zien zijn dit terug dezelfde keys, maar dan met Nederlandse vertalingen van alle termen.

Widget JavaScript code

Dan hebben we ten slotte ook nog de JavaScript code van de widget zelf die we gaan bespreken. De code voor

assets/js/my/timeAgo.js

is:

require([
    "dojo/_base/declare",
    "dijit/_WidgetBase",
    "dijit/_TemplatedMixin",
    "dojo/date",
    "dojo/date/locale",
    "dojo/html",
    "dojo/dom-attr",
    "dojo/text!my/timeAgo.html",
    "dojo/i18n!my/nls/resources",
    "dojox/timing",

], function(declare, _WidgetBase, _TemplatedMixin, date, dateLocale, html, domAttr, template, locale, timing){

    declare("my/timeAgo", [_WidgetBase, _TemplatedMixin], {
            templateString: template,
            baseClass: 'timeAgo',

            _frequency: 30000,
            _date: null,
            _setDateAttr: function(date) {
                this._set("_date", new Date(date));
            },
            _setFrequencyAttr: function(freq) {
                this._set("_frequency", freq);
            },
            _setTimeAgo: function() {
                var d = new Date();
                var secondDiff = date.difference(this._date, d, 'second');
                var minuteDiff = Math.floor(secondDiff / 60);
                var hourDiff = Math.floor(secondDiff / 3600);
                var dateDiff = Math.floor(secondDiff / 86400);

                var text = locale.momentsAgo;

                if (d.getFullYear() != this._date.getFullYear()) {
                    text = dateLocale.format(this._date, {
                        selector: "date",
                        formatLength: "short"
                    });
                } else if (dateDiff >= 2) {
                    text = dateLocale.format(this._date, {
                        selector: "date",
                        datePattern: "d MMM"
                    });
                } else if (dateDiff >= 1) {
                    text = locale.yesterday;
                } else if (hourDiff > 12) {
                    text = locale.today;
                } else if (hourDiff > 1) {
                    text = hourDiff + " " + locale.hoursAgo;
                } else if (hourDiff == 1) {
                    text = locale.hourAgo;
                } else if (minuteDiff >= 30) {
                    text = locale.halfHourAgo;
                } else if (minuteDiff >= 2) {
                    text = minuteDiff + " " + locale.minutesAgo;
                } else if (minuteDiff >= 1) {
                    text = locale.minuteAgo;
                } else if (secondDiff >= 5) {
                    text = secondDiff + " " + locale.secondsAgo;
                } else {
                    text = locale.momentsAgo;
                }

                html.set(this.timeNode, text);
            },

            postCreate: function() {
                domAttr.set(this.timeNode, "data-date", this._date.toISOString());
                this._setTimeAgo();
                var obj = this;
                var _t = new timing.Timer(this._frequency);
                _t.onTick = function() {
                    obj._setTimeAgo();
                };
                _t.start();
            }
    });
});

Zoals je kan zien heeft dit veel weg van de widget-code uit de vorige tutorial, de meeste zaken laat ik dan ook open (kan je nalezen in m’n vorige tutorial).
Wat we hier extra hebben is een veld

_frequency

(met setter

_setFrequencyAttr

) die in een later stadium van de code zullen bepalen wat het update-interval is voor de widget.

Daarnaast hebben we ook nog een functie 

_setTimeAgo

waar eigenlijk alle logica in verwerkt is om de juiste tekst te tonen.
Als eerste stap gaan we het verschil in dagen, uren, minuten en seconden bepalen. Indien de datum namelijk nog maar even geleden is, gaan we iets tonen als “10 seconden geleden”, maar als de datum gisteren is, dan tonen we gewoonweg “Gisteren”, de seconde aanduiding is dan niet zo belangrijk meer. Dat verschil in tijd bepalen we in het stuk:

var d = new Date();
var secondDiff = date.difference(this._date, d, 'second');
var minuteDiff = Math.floor(secondDiff / 60);
var hourDiff = Math.floor(secondDiff / 3600);
var dateDiff = Math.floor(secondDiff / 86400);

In het volgende deel gaan we eigenlijk voor elke situatie de gepaste tekst aanmaken. De eerste twee situaties zijn echter iets speciaal omdat we dan gewoon de datum (dag en maand of dag, maand en jaar indien het van een vorig jaar is) weergeven.
Hiervoor gebruiken we de

dojo/date/locale

module die zal bepalen wat de juiste uitvoer moet zijn (taalafhankelijk) voor de ingevoerde formatting.

In het eerste deel gebruiken we de aanduiding

formatLength: "short"

wat een korte datum-weergave zal geven die afhankelijk is van je locale (dag/maand/jaar of maand/dag/jaar of …).
In het tweede deel maken we gebruik van

datePattern: "d MMM"

om de datum te weergeven. Hiermee tonen we de datum als “dag maand” waarbij enkel de eerste drie letters van de maand getoond worden. Ook dit is taalafhankelijk en zal in het Nederlands “1 Mei” geven en in het Engels bijvoorbeeld “1 May”.

In alle daarop volgende situaties gaan we gewoon het verschil in dagen/uren/minuten/seconden gebruiken om één van de eerder gedefinieerde locale-termen te tonen. Hiervoor maken we gebruik van de

dojo/i18n

module en zoals je bovenaan (bij alle modules) kan zien staat daar

"dojo/i18n!my/nls/resources"

. Door dit te gebruiken specifieer je, net zoals bij templates, ook al een parameter die in dit geval de locatie van de locale-afhankelijke resources bevat.
Het aanroepen hiervan doe je door middel van de module aan te roepen en daarachter de key, dus bijvoorbeeld

locale.yesterday

.

Op het einde gaan we de aangemaakte tekst weergeven in de HTML node die we via het

data-dojo-attach-point

hebben opgegeven.

Ten slotte hebben we ook nog de

_postCreate

functie waarin we net zoals vorige keer een timer gaan starten die telkens het tekstveld zal updaten. Het interval is dit maal echter instelbaar via het

_frequency

attribuut.

Daarnaast gaan we ook nog de originele datum als attribuut meegeven, zodat ook de absolute datum beschikbaar blijft. Om zo’n attribuut toe te voegen aan een DOM-node maken we gebruik van de

dojo/dom-attr

module, die via de

set

-functie ons in staat stelt om het attribuut ”

data-date

in te stellen.

Uitvoeren

Met die laatste functie hebben we dan ook de volledige widget geschreven. Nieuw in deze tutorial is het gebruik van de

dojo/i18n

,

dojo/dom-attr

en de

dojo/date/locale

module. Vooral die eerste is enorm handig en geeft opnieuw een voordeel ten opzichte van jQuery, waar deze zaken (naar mijn weten) niet standaard ingebakken zijn.

Als we nu de widget testen in onze web browser krijgen we het resultaat uit onderstaande afbeelding te zien:

result-1

Als we nu echter even wachten krijgen we iets anders te zien, bijvoorbeeld:

result-2

Als we nu de locale van onze web browser aanpassen (in Firefox is dat onder Content of Inhoud).

firefox-locale

en we vernieuwen de pagina, dan krijgen we:

result-3

Zoals je kan zien hebben we nu dus de widget zowel in het Nederlands als het Engels beschikbaar gemaakt, zonder ook maar iets van widget code dubbel te moeten schrijven.

Download project

Download – timeago-example.tar.gz (2 kB)

Demo

Tagged , , , .

g00glen00b

Consultant at Cronos and Tech lead at Aquafin. Usually you can find me trying out new libraries and technologies. Loves both Java and JavaScript.