Pomodoro-App mit Angular selber programmieren
So baust du dir deine eigene Pomodoro-App
Die ‚Pomodoro-Technik‘ wurde in den späten 80ern von Francesco Cirillo zum Zweck des Zeitmanagements entwickelt. Dabei nutzte er eine tomatenförmige Küchenuhr, um seine Arbeitszeit in 25-minütige Arbeitsphasen gefolgt von fünfminütigen Pause zu teilen. Eben jene Pausen sollen Produktivitätssteigernd wirken. In genau diesem Moment schreibe ich unter dem Einfluss dieser leistungssteigernden Tomate, und es fühlt sich einfach wunderbar an.
Leider konnte ich keinen Timer, außer der OSX-App Pomodoro One finden, der genau das tut was ich will: Mich nach 25 Minuten über den Beginn der Pause in Kenntnis setzen und den Timer für die Pause starten. Doch hier verzage ich nicht. Ein echter Tomatensaftblut-Hacker nimmt hier sein Schicksal in die Hand und baut seinen eigenen Pomodoro-Timer. In diesem Artikel zerlegen wir den Timer in seine Einzelteile.
Timer-App für den Browser programmieren
Pomodoro-App auf CodePen zusammenbasteln
Ziel ist ein Timer, der zwei unterschiedlich lange Zeitspannen abwechselnd ablaufen lässt und dem Nutzer Bescheid gibt, sobald eine Phase endet. Für derartig kleine, auf den Browser beschränkte Applikationen ist die Plattform meiner Wahl der Online-Code-Sandkasten CodePen.
Wir setzen hier auf das JavaScript-MVVM-Framework AngularJS, welches von Google entwickelt wird. Damit die Entwicklung nicht durch allzu viel Redundanz in der Syntax des Qullcodes ausgebremst wird, nutze ich Jade, Stylus und Coffeescript als Template- bzw. Präprozessorsprachen für HTML, CSS und JavaScript. Die HMTL, CSS und JS-Version des Quellcodes zu sehen, kann mit dem kleinen, oberhalb der Editorfelder liegenden ‚View Compiled‘-Button angezeigt werden. Wir betrachten in diesem Artikel nicht das Styling der App, sondern rein funktionale Aspekte, sprich Struktur und Programmierung.
Als Erstes tauchen wir in die HTML-Struktur der Applikation ein:
doctype html
html(ng-app="pomodoroApp")
head
link(href='http://fonts.googleapis.com/css?family=Open+Sans:400,300' rel='stylesheet' type='text/css')
title Pomodorski
.card(ng-class="{work: !break && running, break: break && running, booted: booted}" ng-controller='PomodoroController')
.countdown {{countDown | clockTime}}
.controls
input(ng-model='workDuration' )
input(ng-model='breakDuration')
button(ng-click="startSession('work')") Work
button(ng-click="startSession('break')") Break
Das Attribut ng-app="pomodoroApp"
ist eine so genannte Direktive. Direktiven dienen dazu, Bereiche oder Elemente in der Applikation zu markieren, die von AngularJS verarbeitet und erweitert werden. In diesem Fall handelt es sich um das „Territorium“ des Applikationsmoduls pomodoroApp
. Der Timer im Kartenformat, hier mit der Klasse .card
versehen, ist der Zugriffsbereich des Controllers, also dessen View. AngularJS ist ein sogenanntes Model-View-ViewModel Framework. Das folgende Diagramm stellt die Bereiche innerhalb einer Angular-Applikation dar:
Die gerade erwähnte View sieht und hat Zugriff auf die innerhalb des ViewModels liegenden Variablen. Diese Variablen werden mit Werten aus dem Model populiert. Somit hat weder das Model noch die View Kenntnis von der jeweils anderen Komponente, ihre einzige Schnittstelle ist das so genannte ViewModel. In AngularJS stellt der $scope
Dienst das ViewModel zur Verfügung. In unserem Fall modifiziert der PomodoroController
unser ViewModel und die View reagiert auf jede dieser Änderungen und zeigt sie an. Diese Verhaltensweise wird als Reaktivität bezeichnet. Betrachten wir die View nun genauer.
.card(ng-class="{work: !break && running, break: break && running, booted: booted}" ng-controller='PomodoroController')
.countdown {{countDown | clockTime}}
.controls
input(ng-model='workDuration' )
input(ng-model='breakDuration')
button(ng-click="startSession('work')") Work
button(ng-click="startSession('break')") Break
Die ng-class
Direktive dient dazu, der Timer-Karte Klassen zuzuweisen (Überraschung!), und erlaubt somit, den aktuellen Zustand des Timers farblich wider zu spiegeln. .countdown {{countDown | clockTime}}
zeigt die verbleibende Zeit der aktuellen Phase an. Da der Controller nicht im MM:SS-Format arbeitet, sondern nur Sekunden herunter zählt, wird die Ausgabe mithilfe des clockTime
Filters in ein für uns einfacher lesbares Format umgewandelt. Die ng-model
Direktiven weisen den Eingabefeldern Variablen im $scope
zu. Somit können wir selbst Zeiten für unseren Arbeitsrhythmus definieren. Ein ng-click
auf die Buttons startet eine neue Phase, also entweder Arbeitszeit oder Pause.
Im folgenden betrachten wir den Backend-Code.
app = angular.module 'pomodoroApp', []
app.controller 'PomodoroController',
class PomodoroController
constructor: ($scope,$interval) ->
Zunächst wird ein neues Angular-Modul initialisiert, welches wir als Applikation einsetzen. Module können jedoch auch als einzelne Teile einer Applikation eingesetzt werden. Somit sind wir in der Lage Applikationen als Teile eines größeren Programms einsetzen. Dem App-Modul wird nun der in der View bereits zugewiesene Controller übergeben. Das Prinzip der Dependency Injection erlaubt uns, Dienste an den Controller zu übergeben. Das ist hier zum Einen $scope
und zum Anderen $interval
welchen wir später für unseren Countdown nutzen.
# Ask for notifications
Notification.requestPermission (status) =>
notify('Notifications enabled')
@notificationsEnabled = status is "granted"
notify = (message)-> new Notification(message, {body: "Pomodorski", icon: "http://www.wpclipart.com/food/fruit/tomato/tomato.png"});
Wir wollen, dass der Nutzer über den Beginn einer Pause bzw. Arbeitsphase informiert wird. Doch weil ein alert()
die Ausführung von JS pausiert und somit den Eingriff des Nutzers erfordert, setzen wir auf die Notification
API moderner Browser. Zunächst fragen wir die Berechtigungen zum Senden von Notifications an. Die übergebene Callback-Funktion speichert, ob die Erlaubnis gewährt wurde um ggf. den Nutzer später über das fehlen der Notifications in Kenntnis zu setzen. Läuft aller glatt, teilen wir mit, dass Notifications nun verfügbar sind. Die notify
nutzen wir, um die Syntax für das Senden neuer Notifications etwas zu vereinfachen.
# setup
$scope.booted = true
$scope.running = false
$scope.break = false
$scope.workDuration = 25
$scope.breakDuration = 5
$scope.countDown = $scope.workDuration * 60
@notificationsEnabled = false
In diesem Abschnitt wurden die Standardwerte der Applikation definiert.
# converter method
# converter method
sessionDurationInSeconds = (type) =>
if type == 'work'
duration = $scope.workDuration
$scope.break = false
if type == 'break'
duration = $scope.breakDuration
$scope.break = true
duration * 60
Die sessionDurationInSeconds
Methode gibt die Dauer der angefragten Session in Sekunden zurück. Der Ausgabewert basiert auf den Werten im Viewmodel.
# countdown method
count = -> $scope.countDown--
Die count
Methode reduziert den Wert von countDown
, also die momentan verbleibende Zeit in Sekunden, um eins.
$scope.startSession = (type) =>
alert('Warning: no notifications') if !@notificationsEnabled
# stop old timer and set session properties
$interval.cancel @currentSession if angular.isDefined @currentSession
$scope.countDown = sessionDurationInSeconds(type)
@currentSession = $interval count , 1000, $scope.countDown
@currentSession.then () ->
newSessionType = if type == 'work' then 'break' else 'work'
$scope.startSession(newSessionType)
notify('Time for ' + newSessionType)
$scope.running = true
Falls Notifications nicht verfügbar sein sollten wenn startSession
aufgerufen wird, geben wir eine Warnung aus. Falls bereits eine Countdown-schleife existieren sollte, stoppen wir sie. Nun wird eine neue $interval
Schleife gestartet. Sie ruft jedes mal unsere count
Methode auf, verzögert um 1000 Millisekunden und zwar so oft, wie Sekunden im countdown
definiert sind. Mit then
wird der Start einer neuen Schleife ausgelöst, sobald die aktuelle endet.
app.filter 'clockTime', ->
(totalSeconds) ->
hours = Math.floor(totalSeconds / 3600)
totalSeconds %= 3600
minutes = Math.floor(totalSeconds / 60)
seconds = totalSeconds % 60
seconds = "0" + seconds if seconds < 10
minutes + ':' + seconds
Der letzte Teil der Applikation ist der clockTime
Filter. Er wandelt eine Eingabe in Sekunden in das für uns lesbare MM:SS Format um.
Fazit Pomodoro-App mit Angular
Times up!
Die Entwicklung einer Applikation diesen Maßstabs stellt einen sehr guten Einstieg in AngularJS dar und bietet ein befriedigendes Erfolgserlebnis. Ich hoffe, dass auch du auf den Geschmack von Tomaten-Timern und Angular gekommen bist.