Javascript – ToDo List mit dem JS-Framework „flight“

Im letzten Beitrag habe ich euch die Entwicklung eines kleinen Tools in AngularJS versprochen. Allerdings heißt es nicht umsonst: „Nichts ist so sicher wie die Lageänderung“. Das wissen wir als Frontend Entwickler nur zu gut. Ich bin nämlich auf das interessante Framework flight gestoßen. Es ist von Twitter entwickelt und vor Kurzem veröffentlicht worden. Dieses möchte ich euch heute kurz vorstellen und eine kleine ToDo-List in flight umsetzen.

Flight ist ein event-gesteuertes Framework. Javascript-Module – in flight components genannt – stellen ihre Features nach Außen über Events zur Verfügung. Nur über diese Events kommuniziert ein Modul mit anderen Elementen auf der Website. Ein Modul kennt nur sich selber; es hat keinerlei Kenntnis über die Beschaffenheit der Umgebung, wo es eingesetzt wird. Ist euch diese Architektur noch einigermaßen fremd oder braucht ihr mehr Input zu flight, schaut euch hier die Docs an. Twitter hat auch eine recht große Beispiel-Anwendung (Mail-Client) und den Code dazu veröffentlicht.

ToDo List in flight

Hier zeige ich euch wie ihr eine kleine ToDo-List mit flight realisieren könnt.

HTML

<div id="todoContainer">
     <h3>Example ToDo App with Twitter Flight Framework</h3>

     <p><span id="todoCount"></span> von <span id="totalTodoCount"></span> &uuml;brig</p>

       <ul id="listTodos" style="list-style-type: none;">
           <li><input type="checkbox"><label>Wasser kaufen</label> <a href="#">[archiv]</a></li>
       </ul>

    <input type="text" name="todoTask" placeholder="Eintrag hinzuf&uuml;gen">
    <button id="addTodo">hinzufügen</button>
</div>

An den #todoContainer binden wir später das JS-Modul, oder wie man in flight sagt, die Komponente. Der Rest ist einfaches Markup zum testen:

  • Eine Liste mit allen ToDo-Einträgen
  • Die Möglichkeit ToDo-Items hinzuzufügen
  • Als erledigt zu markieren
  • Oder Dinge ins Archiv zu verbannen
  • Außerdem werden die ToDo-Einträge gezählt. (Insgesamt, noch zu erledigen)

Skripte:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src='js/lib/es5-shim/es5-shim.js'></script>
<script src='js/lib/es5-shim/es5-sham.js'></script>
<script data-main="js/app.js" src='js/lib/requirejs/requirejs.js'></script>

Diese Skripte werden im Footer eingebunden. Ihr braucht sie um flight zum Leben zu erwecken. Ihr braucht:

  • jQuery
  • ES5-shim (EcmaScript 5 Support)
  • requireJS (Javascript File/Module Loader)

Natürlich braucht ihr auch noch flight selbst. Hier könnt ihr euch das Paket laden.

App initiieren

Im Beispiel laden wir das Skript app.js, um die Anwendung zu initiieren. Das Skript sieht wie folgt aus:

'use strict';

define(
    [
        'components/todo'
    ],

    function(Todo) {
        Todo.attachTo('#todoContainer');
    }
);

Wir laden die todo-Komponente (components/todo.js) und binden sie an das HTML-Element #todoContainer. Nur in diesem Kontext wird das Modul eingesetzt. Rein logisch kann eine Komponente auch auf andere Seitenelemente zugreifen, ist jedoch nicht vorgesehen und würde das gesamte Prinzip überflüssig machen. Also standfest bleiben 😉

todo.js

Kommen wir zum eigentlichen Modul. Die Code-Basis:

'use strict';

define(
    [
        'lib/flight/component'
    ],

    function(defineComponent) {
        return defineComponent(todo);

        function todo() {
            var templates = ({
                todoItem: '<li><input type="checkbox"><label>##TODOTITLE##</label><a href="#">[archiv]</a></li>'
            });

            this.defaultAttrs({
                addTodoBtn: '#addTodo',
                markDone: '.markDone',
                addArchiv: '.addArchiv',
                todoName: '[name=todoTask]',
                todoList: '#listTodos',
                totalTodoCount: '#totalTodoCount',
                todoCount: '#todoCount'
            });

            this.addTodoItem = function() {                
                var title = this.select('todoName').val();
                var todoList = this.select('todoList');

                if(!title || title == '') return;

                var newTodoItem = templates.todoItem;
                newTodoItem = newTodoItem.replace('##TODOTITLE##', title); 

                this.select('todoList').append(newTodoItem);
                this.select('todoName').val('');

                this.trigger('updateTodoCountsTrigger');
            }

            this.markAsDone = function(e) {
                $(e.target).next().toggleClass('done');

                this.trigger('updateTodoCountsTrigger');
            }

            this.addToArchiv = function(e) {
                $(e.target).parent().remove();

                this.trigger('updateTodoCountsTrigger');
            }

            this.updateTodoCounts = function() {
                var totalTodo = $(this.select('todoList')).find('li').length;
                var todoUnDone = $(this.select('todoList')).find('label').not('.done').length;

                this.select('totalTodoCount').text(totalTodo);
                this.select('todoCount').text(todoUnDone);
            }

            this.after('initialize', function() {
                this.on('click', {
                    addTodoBtn: this.addTodoItem,
                    markDone: this.markAsDone,
                    addArchiv: this.addToArchiv
                });

                this.on('updateTodoCountsTrigger', this.updateTodoCounts);

                this.trigger('updateTodoCountsTrigger');
            });

            this.reset = function() {
                // remove every component instance and all event listeners
                defineComponent.teardownAll();
            };
        }
    }
);

Zu den oben beschriebenen Markup gibt es jeweils passende Methoden:

  • addToArchiv
  • markAsDone
  • addTodoItem
  • updateTodoCounts

Die Methoden erklären sich sicherlich von selbst. In einer echten Umgebung werden Items natürlich auch serverseitig gelöscht, hinzugefügt usw. Hier soll es erst mal nur um die Art der Implementierung in flight gehen. Alle flight Komponenten basieren auf das Skript component.js. Deswegen ist es auch im header als Abhängigkeit definiert.

Weitere Features:

  • defaultAttrs
    Der Komponente werden initiierende Basis-Werte mitgegeben
  • initialize
    Beim Starten einer Komponente können hier diverse Aktionen abgefeuert werden. In unserem Beispiel das Zählen der Todo-Elemente und das Binding der Events.
  • trigger
    Kernfeature des Frameworks. Auf bestimmte Aktionen lassen sich Events ausführen. Bspw. nach Entfernen eines Todo-Items, diese erneut zählen.
  • reset
    Wird ein Modul nicht mehr gebraucht, kann es über diese Methode einfach eliminiert werden. Alle registrierten Events werden dadurch gelöscht und die Komponente kann keinen Schaden mehr anrichten.

Habt ihr alles in eurer Umgebung eingerichtet, und ein wenig Test-Content erzeugt, sollte es in etwa so bzw. so ähnlich aussehen:

example-todo-app