Model-View-Controller

MVC, MVVM, MV*

Früher war mit Fachkonzept-GUI-Datenhaltung das gleiche gemeint. Vielleicht hilft diese Anmerkung, um sich richtig reinzuversetzen. Dieses Dokument ist etwas weniger gut, als es sein könnte.

Aus dem Inhalt (sowas in der Art wird das hier):

Geschichte

Kann ich noch nicht

Zuerst habe ich das Modell für etwas moderner gehalten. Doch MVC stammt bereits aus den 70er Jahren. Und zwar von Smalltalk ab.

Das lässt sich als Dreieck darstellen, mit zwei Pfeilen zwischen View und Model, einem vom View zum Controller und einem vom Controller zum Modell.

So eine Grafik muss ich noch anfertigen.

Model-View-Controller

Ich wollte zunächst völlig eigenständig ein MVC entwickeln, abgucken werde ich auch jetzt nicht. Aber ausser der Entscheidung, keine ES5 Accessoren für den Observerpart zu wählen, sondern Events, muss ich mich an das richtige Modell halten.

-- mvc cut --- server --

Achso, dann ist da die Geschichte mit MVC auf dem Server, in Python, mit Ruby, mit PHP. Zum Schluss hatte man den View und schickte das gefuellte Template zum Client.

Modularisierung

Für spätere Concatenation

Eine Single Page App, mit einem Model, und einem Widget, sieht in Modulen im Browser, ohne Optimierung, in etwa so aus.

	<script src="m/m.js"></script>
	<script src="v/v.js"></script>
	<script src="app.js"></script>
	

Das MV* Pattern bringt Ordnung in den Code. Durch eine saubere Trennung von Daten, Ansicht und Anwendungslogik, kann man die Anwendung sehr gut strukturieren, leichter warten und besser erweitern. Zum anderen hat es den Vorteil, besser verstanden zu werden.

	app/models/
	app/views/
	app/controllers/
	app/
	

Beispiele, wie es im Verzeichnis aussehen kann.

	app/models/a.js
	app/models/b.js
	app/views/a/a.htm
	app/views/a/a.js
	app/views/a/a.css
	app/views/b.js
	app/controllers/a.js
	app/controllers/b.js
	app/app.js
	app/loader.js
	app/index.html
	app/README.md
	app/package.json
	app/.git
	

Bei requireJS sieht das wieder etwas anders aus. Wenn ich das richtig überflogen habe, kann das aber konfiguriert werden. (Ich finde das nächstes mal raus.)

	    app/scripts/app.js
	    app/scripts/require.js
	    app/app.html
	    app/
	

In Webapplikationen werden diese Files mit Tools wie grunt oder yeoman, r.js und vielen anderen, optimiert, wobei das JavaScript concatenated wird, die ScriptTags durch einen Load abgeloest werden usw. Dazu komme ich auch, wenn ich mit Loader und MVC fertig bin, dann habe ich eine Basis zum optimieren.

grunt wird hier bald zum Einsatz kommen, ich denke mir es auszuprobieren. Wenn ich dann selbst eins schreiben will, kann ich das machen, ansonsten bleibe ich dabei.

	<script data-main="app/complete" src="lib.min.js"/<
    	

Das gleiche wie oben, nur nach der Optimierung:

    	app/complete.js
    	

Dafür wurde der Loader dazu verwendet, die Module wieder auf Disk zu mappen, aber vorher muessen die Pfade optimiert werden, damit der Loader nicht versucht was aus den Unterverzeichnissen zu holen

    	    // app/views/a.js
    	    define(function (require, exports, module) {
    		return "<div data-bind='output:model-a' id='view-a'></div>';
    	    });
    	

Wird zu

    	    // app/complete.js
    	    define("views/a", function anonymous(require, exports, module) {
    	    	return "<div data-bind='output:model-a' id='view-a'></div>';
    	    });
    	

Das gute an AMD ist, dass Verzeichnisse genutzt werden, die einen optimalen und einmaligen Namensraum für Module bietet. Es kann wirklich nur ein Modul einen Namen haben. (Wie es nur ein file an der stelle eines files geben kann, jeder name ist 100% einmalig).

require

Da es heute üblich ist, AMD zu verwenden, habe auch ich mir einen eigenen AMD Loader programmiert, bzw. bin gerade dabei, er laedt aber bereits einen ganzen Baum Module (selbstaufloesend) mit lokalen requires soviel man will.

Manifest (manifest.json)

Manifeste und Pakete sind in etwa vergleichbar. Mit einem Manifest wird der Name der App definiert, sowie die Verzeichnisse und Zugriffsrechte, beispielsweise. Den Begriff kann man nicht synonym mit Package verwenden, aber aehnlich sind sie auf jedenfall. Chrome Extensions haben ein Manifest.

Pakete (package.json)

Das Package nach CommonJS drueckt neben den Meta-Daten noch Dependencies mit Versionsnummer, Hooks für app, test und co. aus. Und unterscheidet sich darin von Web-App manifest.json Dateien, wie man sie z.B. in Chrome Extensions findet.

Die Package Definition nach CommonJS entspricht in etwa dem, was der npm node package manager für Daten verarbeitet. Es sind aber nicht alle Startbegriffe für die App abgedeckt, beziehungsweise ein eigener Namespace für die Config.

Daher denke ich mir Pakete sollten um ein manifest ergaenzt werden, kann aber sein, dass ich meine Meinung noch aender. Ich komm ja jetzt erst zum "App" programmieren.

Hmmm. Hier weicht der Herr vom Glossar des Actor Models ab, aber nicht nur hier..

Events

Messages meint er, auf nem Channel.

In der Informatik sendet Bob mit Bob.foo() bereits eine Botschaft foo, die andere Nachricht ist die, mit dem String für den Channel, oder dem String für den Event Namen. So läuft das dann in die Praxis. Ich bin mit dem Sprechen zur Zeit etwas wackelig, liegt schon lange zurück. (Habe ich mir mal durch Bücher angeeignet gehabt.)

Events werden die Basis für mein MVC Pattern bilden. Ich werde anstelle der Accessor Definitionen, die ES5 bietet, aufgrund der Browser-Begebenheiten, einen eigenen Event-Emitter verwenden, weil ich so ein problemlos rauf-und-runter konvertierbares Stück Software präsentieren kann, was in neuen und alten Browsern läft. (Auch wenn ich wirklich nichts von abwärtskompatiblität für Netscape 4 oder IE6 oder 8 halte.)

Jetzt muss ich kurz dazwischenwerfen: Es werden normal eh events dafür verwendet, das geht aus dem Original von Smalltalk vor, genaugenommen keine Events, sondern Messages. Das mit dem Getter und Setter ist wohl von modernen Ajax Libs wie Knockout und Angular mit beeinflusst, dass ich was moderner gedacht habe. Aber selbst die basieren, ich wette, auf Events.

Ich finde, die ES5 Accessoren können nicht genug, es gibt nicht genug Traps. Nur get und set, aber nichts anderes. ES6 bietet Proxy, damit sind Modelle total einfach (trivial) zu ueberwachen. Aber in der heutigen Welt ist es glaube ich noch besser, den Code des Modells auf Methoden, die auch get und set heissen, zu schreiben, der am Ende der get und set Routine ein this.emit("get", name, data) und this.emit("set", name, data) von sich gibt. Die kann man dann problemlos mit einem Callback abhorchen.

Was die Performance angeht. Es geht gegenueber einem Direktzugriff oder einem C++ Proxy natürlich was verloren, aber, in Anbetracht des Fortschritts und der immer schneller werdenden Computer und Telefone, kaum ins Gewicht fällt. Man nutzt für ein 3D Spiel kein Model was für eine Anwendung konzipiert ist.

Event Emitter

Ich sollte mir lieber erstmal einen "Actor" schreiben. Mit PubSub Funktion. Das triffts dann namentlich besser.

    var Emitter = function () {
	var emitter = this instanceof Emitter === false ? new Emitter() : this;
	if (typeof emitter._eventListeners === "undefined") { // ^
	    Object.defineProperty(emitter, "_eventListeners", {
		value: {},
		enumerable: false
	    });
	}
	return emitter;
    };
    
    Emitter.prototype = {
	
	constructor: Emitter,
	
	on: function (event, callback) {
	    if (typeof event !== "string" || typeof callback !== "function")
		throw new TypeError("on: event (str), callback (fn) expected");
	    if (!this._eventListeners[event]) {
		this._eventListeners[event] = [];
	    }
	    this._eventListeners[event].push(callback);
	    return this;
	},
	
	once: function (event, callback) {
	    var emitter = this, removable;
	    if (typeof event !== "string" || typeof callback !== "function")
		throw new TypeError("once: event (str), callback (fn) expected");
	    removable = function () {
		callback.apply(this, arguments);
		emitter.remove(event, removable);
	    };
	    this.on(event, removable);
	    return this;
	},	
	
	emit: function (event) {
	    var i, j, list, args, emitter = this;
	    if (typeof event !== "string")
		throw new TypeError("emit: event (str) expected");
	    if (list=this._eventListeners[event]) {
		j = list.length;
		if (j > 0) {
		    args = [].slice.call(arguments,1);
		    for (var i=0; i < j; i++) {
			list[i].apply(emitter, args);
		    }
		}
	    }
	    return this;
	},
	remove: function (event, callback) {
	    if (typeof event !== "string" || typeof callback !== "function")
		throw new TypeError("remove: event (str), callback (fn) expected");
	    if (this._eventListeners[event]) {
		this._eventListeners[event] = 
		this._eventListeners[event].filter(function (listener) {
		    return listener !== callback;
		});
	    }
	    return this;
	},
	removeAll: function (event) {
	    if (typeof event !== "string")
		throw new TypeError("removeAll: event (str) expected");
	    if (this._eventListeners[event]) {
		this._eventListeners[event] = [];
	    }	
	    return this;
	}
    };
	

Emitter wie diese schreibe ich in fünf Minuten ohne Tippfehler. Ist ja auch nicht schwer. Dieser hier throwt übrigens nicht, wenn ein falscher Parameter übergeben wird, die fünf Minuten muss ich nochmal investieren, bei falschen Parametern zu throwen habe ich mir angewöhnt.

Ich paste den Source das naechstemal neu rein und schreibe auch meinen Text neu. Das Dokument hier muss sich jetzt erstmal formen, schliesslich mache ich das _zum ersten mal_ und bin auch kein Student oder Azubi gewesen, sondern Arbeitsloser. Dementsprechend habe ich manchmal ein paar Defizite im wissenschaftlichen Arbeiten, die auffallen. Entsprechend der Art, wie ich die Dokumente erstelle. Ich krakel zuviel.

Publisher-Subscriber

Ein anderes Modell, Nachrichten an zu übermitteln.

Moment, ich muss das Modell ACTOR noch recherchieren, um das Smalltalk-80 Actor Model fuer diese MVC Architektur nutzen zu koennen.


    Publisher = function () {
        var publisher = this instanceof Publisher === false ? new Publisher() : this;
        Object.defineProperty(publisher, "_subscriptions", {
            value: {},
            enumerable: false
        });
        return publisher;
    };
    Publisher.prototype = {
        constructor: Publisher,
        subscribe: function (event, callback) {
            if (typeof event === "string" && typeof callback === "function") {
                this._subscriptions[event] = this._subscriptions[event] || [];
                this._subscriptions[event].push(callback);
            } else {
                throw new TypeError("Wrong arguments: event (string), callback (function) required");
            }
            return this;
        },
        publish: function (event) {
            var publisher = this;
            var subscribers = this._subscriptions[event];
            var args = [].slice.call(arguments, 1);
            if (subscribers) {
                forEach(subscribers, function (s) {
                    if (typeof s === "function") {
                        s.apply(publisher, args);
                    }
                });
            }
            return this;
        }
        /* unsubscribe: function () */
    };

Die Übermittlung von Botschaften ist eine der Grundlagen für den Erfolg des MVC Musters.

Modelle

Das Datenmodell, beschreibt die Daten der Anwendung. Eine Anwendung besteht genaugenommen aus mehreren Modellen, von denen jedes seinen eigenen View (GUI/Ansicht) besitzt.

Das Datenmodell ist in den WebApps von heute ("dem Style von heute") meist mit einem Store verbunden, so dass man nur Modell.speichern() tippen muss, um es persistent festzuhalten. Zum Speichern eines Modells kommen sowohl der Server, als auch heute der Client (IndexedDB, oder LocalStorage) in Frage. Kommt auf den Use Case an.

(Wie kommt dieses schreckliche "Use Case" bloss hier rein. Horch horch.)

	    // Vorher Objekt Emitter auf die Konsole kopieren	    
	    
	    function Model(data) {
		"use strict"; 
		this._data = data || {};
	    }
	    Model.prototype = Object.create(new Emitter(), {
		"get": {
		    value: function (name) {
			// this.emit("get", this, name, value);
			return this._data[name];
		    },
		    configurable: false
		},
		"set": {
		    value: function (name, value) {
			this._data[name] = value;
			this.emit("change", this, name, value);
		    },
		    configurable: false
		},
		"toString": {
		    value: function () {
			return "[object Model]"; // +id fuer change event statt model object passen (insec.)
		    },
		    configurable: false
		}
	    });
	    
	    var model = new Model();
	    // Das hier registriert der View
	    model.on("change", function (model, name, value) {
		console.log(model.toString()+"changed "+name+" to "+value);
	    });
	    
	    // Ich bin jetzt der Controller
	    model.set("abc", "to-def");
	    model.set("abc", "to-xyz");	    
	    model.set("def", "to-zzz");	    	    
	    
	    // Das ist noch nicht, was wir wollen, oder? Mehr dazu gleich in Beispiel 2
	    console.log(model._data.abc);
	    console.log(model._data.def);	    
	    

Das Modell Objekt sollte man normal nicht in den Callback passen, weil das extrem insecure ist. :-) Man gibt zuviel preis.

Für _data sollte ich mir eine Closure einfallen lassen, anstelle einer Property. Das heisst, dass ich anstelle des Modell Prototypes, die Dinger im Constructor erzeuge.

	    // Vorher Objekt Emitter auf die Konsole kopieren
	    
	function Model (data) {
	    "use strict"; 
	    var model = this;
	    if (!this instanceof Model) {
		model = new Model(data);
	    } else {

		// Diese Property ist sicherer als this._data
		data = data || {}; 
		
		// alle weiteren funktionen arbeitten mit der Closure Model
		Object.defineProperties(model, {
		    "get": {
			value: function (name) {
			    // this.emit("get", name, value);
			    return data[name];
			},
			configurable: false
		    },
	    	    "set": {
			    value: function (name, value) {
			    data[name] = value;
			    this.emit("change", name, value);
			},
			configurable: false
		    },
		    "toString": {
			value: function () {
			    return "[object Model]"; // spass
			},
			configurable: false
		    }
		});
	    }
	    return model;

	}
	Model.prototype = new Emitter();
	
	
	var model = new Model();
	// Das hier registriert der View
	model.on("change", function (name, value) {
	    console.log("changed "+name+" to "+value);
	    
	});
	
	// Ich bin jetzt der Controller
	model.set("abc", "to-def");
	model.set("abc", "to-xyz");	    
	model.set("def", "to-zzz");	    	    
	    
	// Das gibt jetzt eine Fehlermeldung. _data ist jetzt private
	console.log(model._data.abc);
	console.log(model._data.def);	    
	    
	    

Schon besser. Jetzt ist die data Property in Sicherheit.

Das ist aber ES5, für das MVC denke ich mir, zu Beginn erstmal die 100% Backwards Kompatiblität zu wahren.

Dennoch, BEMERKT IHR DEN FEHLER? Ich gebe zuviele Capabilities an den "change" Callback mit (model, name, value) indem ich das model passe. (Das kommt in der Regel bei vielen noch recht oft vor)

	    
	    // ES3 kompatible Fassung
	    
		function Model (data) {
    		    var model = this;
		    if (!this instanceof Model) {
			model = new Model(data);
		    } else {
			data = data || {}; 
			model.get = function (name) {
			    // this.emit("get", this, name, value);
			    return data[name];
			};
        		model.set = function (name, value) {
			    data[name] = value;
			    this.emit("change", this, name, value);
			};
			model.toString = function () {
			    return "[object Model]"; 
			};
		    }
		    return model;
		}
		Model.prototype = new Emitter();
	
	    // Testen
	    var model = new Model();
	    // View
	    model.on("change", function (model, name, value) {
		console.log(model.toString()+"changed "+name+" to "+value);
	    });
	    // Controller
	    model.set("abc", "to-def");
	    model.set("abc", "to-xyz");	    
	    model.set("def", "to-zzz");	    	    
	
	    

ES5 ist aber besser, da man damit die Objekte sealen oder freezen kann.

Achtung! Der folgende Text kommt erst nach der bereits vollstaendigen Beschreibung eines Modells, welches folgende Techniken _nicht_ beruecksichtigt.

Nebenprodukte

...der Modellentwicklung. Dinge, die nicht überall laufen.

Property-Observer

Mit ES3 nur Callbacks. Mit ES5 nur halbautomatisch. Mit ES6 Proxies vollautomatisch. Mit Object.observe endlich nativ, wie in den Applikationsframeworks bereits optimiert (mit Log).

Verschiedene Varianten

Das folgende Beispiel nutzt ES5 Object.defineProperty. Das läuft nicht in den alten Browsern. (Ist aber eigentlich ein Widerspruch in sich für mich, weil ich keinen Bock auf IE bis 9 habe.

Es ist aber nicht ganz so flexibel, weil es nur ein statischer Proxy ist. Mit Sicherheit kann man noch einen dirtyCheck einbauen, der bei jedem get und set prüft, ob sich bei dem Objekt sonst noch was veraendert hat.

Es geht auch was mit setTimeout und Prüfintervallen, wie man es beim Router machen kann, aber das ein andermal.

Aber das was mit ES6 Proxies geht, geht hiermit noch lange nicht (gar nicht).

Die beste Variante ist, einfach Events zu schicken, oder Observer Callbacks zu rufen. Moeglich wird es dadurch, dass der Programmierer eurer API dazu verpflichtet wird, das Modell ueber model.get(name|[]); model.set({}|name,value); zu benutzen.

Man muss die Methoden nutzen, anstelle model.x = 12; direkt zuweisend zu schreiben. zu schreiben. Damit nicht nur die direkte Zuweisung verboten, sondern ist auch das Notifying ohne ES5 und nicht-standard Methoden ohne Probleme zu machen.

Mit ES6 Proxies waere das kein Problem, weil man die dann _komplett_ ueberwachen kann.

Ein model.set(x, 12); oder model.set({ x: 12, y:12, z: 14}); steigert die Lesbarkeit gegenueber model.x=12;model.y=13; model.y=14;.

Hier erstmal ein statischer Proxy. Der Code ist genauso wie der Text nochmal zu machen, denke ich mir. Aber er sollte erstmal genuegen, was raus zu lernen.

var observe = function (object, callback) {
	var proxy = {};
	Object.keys(object).forEach(function(key) { // fuer alle keys object
	    Object.defineProperty(proxy, key, {		// richte dem proxy eine property ein
		get: function () {
		    console.log("getter: "+key);
		    console.dir(arguments);
		    callback({ getter: key, value: object[key] });
		    return object[key];
		},
		set: function (v) {		// und gebe die daten ans original weiter
		    console.log("setter: "+key);
		    console.dir(arguments);
		    var oldValue = object[key];
		    object[key] = v;
		    callback({ setter: key, oldValue: oldValue, newValue: v });		    
		}
	    });
	});
	return proxy;
};

(function () {
    var model = { x: 1, y: 2, z:3 }; 
    var o2 = {}
    var observer = function (record) {
	console.dir(record);
    };
    o = observe(model, observer);
    o.x = 1;
    o.y = 2;
    o2.x = 1;
    o2.y = 2;
    o.x;
    o.y;
    o.abc = 12; // wird nicht ueberwacht 
}());

// ergebnis
setter: x
{ '0': 1 }
{ setter: 'x', oldValue: 1, newValue: 1 }
setter: y
{ '0': 2 }
{ setter: 'y', oldValue: 2, newValue: 2 }
getter: x
{}
{ getter: 'x', value: 1 }
getter: y
{}
{ getter: 'y', value: 2 }

Das Beispiel ist nur ein hypothetisches, was ich praktisch nicht verwenden werde. Wie gesagt, ich werde auf einfache Observer mit model.get und model.set setzen, also ein Objekt von Emitter extenden und wenn was ist, feuerun und registrierte Callbacks rufen.

Die Idee für einen Observer habe ich zum einen selbst, zum anderen habe ich auch mal im Internet geguckt. Hier gibt es was besseres:

Es ist ein vieldiskutiertes Thema für EcmaScript, es gibt auch einen Strawman für Object.observe, sowie Videos von einem Raphael, der Object.observe mit der V8 implementiert hat, der erklärt, dass Mutation Events veraltet sind (DOMNodeInserted und Co.), weil sie zu oft feuern und die Browserhersteller die gar nicht so gut finden, es ab DOM4 die MutationObserver gibt, die alle Aufrufe wie ein Protokoll führen, und er eine Library MutationSummary hat, die dieses Log auf die wenigen Änderungen, die nach dem Callback übrigbleiben herausrechnet.

Das zeigt er mit einem Mirror, der eine Seite spiegelt, die er aendert, wobei halt das Changelog erst ausgewertet wurde, bevor sie drüben angezeigt wurde, aber praktisch zeitgleich, und siehe da, die passt sich dem Resultat an.

Etwas ähnliches hat er dann für seine V8 Implementation von Object.observe geschrieben. ChangeSummary. Auch die stellt er mit einem Screencast vor. Ich finde das ist interessanter Stoff, weil Object.observe a) vielgefragt ist und b) dazu das offizielle Strawman Proposal existiert. Ich glaube, das ist sogar offiziell mit von Google, weil er deren HTML5 Slides nimmt.

Array Observer

Ich habe mich mehrmals gefragt, wie ich denn einen Array ueberwachen kann. Mit gettern und settern kann man eher so Sachen wie "innerHTML", dass hinter einem Wert einer Variable erst eine aktuelle Berechnung steckt, anstatt sowas, verfassen. Das liegt daran, dass selbst in ES5 die Objekt Meta-API zu wenige Traps hat. Es reicht für strict mode und tamper-proofness, aber nicht für das MVC Ding (in diesem Fall das optimale Modell) in Vollendung.

Die Antwort, wie ich den Array ueberwache kam mir dann. Es gibt KEINEN Programmierer, der einem Array a[i] = 12; zuweist, wenn er ein neues Element erzeugt. An der Stelle werden Operationen wie Push und Co verwendet. Die sollte man dekorieren, dass ausserdem der Observer gerufen wird.

Diese Funktion erzeugt eine Kopie des Arrays, dessen Funktionen ueberschrieben wurden, um sowohl die Original-Array Operation als auch den notifier zu uebernehmen.

Kombiniert werden muss dieses Beispiel noch mit dem Property Descriptor, weil der Array jetzt zwar die Methoden hat, aber a[i] = x[i]; in einem Loop nicht ueberwacht. Ein manueller Check für sowas ist in den Frameworks drin, man ruft nach der ganzen Änderung eine Methode. Uns wird es reichen, wenn wir einen Proxy Array haben, der die Daten weitergibt. push richtet somit neben dem

Diesen Code muss ich noch um das obere Beispiel ergaenzen. Er deckt nur die Array Methoden ab, aber keine for (;;i++) a[i] = x[i]; ab. Oder: Dazu sollte man direkt nach der Iteration eine .check() Routine rufen, die die Proxyvariable mit dem letzten Zustand vergleicht.

Sollten sich die Indizes des Arrays nicht redefinieren lassen im Objekt oa, werde ich aus [] ein {} machen, der Original Array bekommt die Daten durchgereicht, und der Programmierer merkt gar nichts, da Zugriff ueber object[0] bei beiden das gleiche sucht. Hier muss ich nochmal gucken, wie sich die Propertydeskriptoren bei shift und Co verhalten (wegen redefinition). Und genau wegen dem ganzen Aufwand werde ich für das Model einfach change Events schicken.


var observeArray = function (array, observer) {

    if (!Array.isArray(array)) throw new TypeError("First argument should be an Array - this function returns a copy of the array with the notification.");
    if (typeof observer !== "function") throw new TypeError("Second argument should be a function (fname, [arguments], retVal) callback) called for each access to the array methods");
    
    var oa = array.concat();

    // Sorry Leute, Array Funktionen sind unsichtbar fuer eine Aufzaehlung. Aber das geht.
    ["push", "pop", "shift", "reduce", "reduceRight", "join", "map", "filter", "some", "every", "forEach"].forEach(function(p) {
    
	    oa[p] = function () {
		var val, args = [].slice.call(arguments);
		val = [][p].apply(oa, args);
		observer(p, args, val);
		return val;
	    };
	    
    });
    return oa;
};

var a = [1,2,3];
var b = observeArray(a, function (fname, args, ret) { 
    console.log("array function = "+fname);
    console.log("function arguments = "+JSON.stringify(args));
    console.log("return value = "+ret);
});

b.push(2);
b.push(4);
b.shift();
b.pop();
b.push(10);
b.pop();
b.push("Hallo, das geht wirklich");

Das Ergebnis kann sich sehen lassen.

array function = push
function arguments = [2]
return value = 4
array function = push
function arguments = [4]
return value = 5
array function = shift
function arguments = []
return value = 1
array function = pop
function arguments = []
return value = 4
array function = push
function arguments = [10]
return value = 4
array function = pop
function arguments = []
return value = 10
array function = push
function arguments = ["Hallo, das geht wirklich"]
return value = 4

Anstatt es uns "kompliziert" zu machen und einen Observer mit mehreren "neuen" Techniken zu programmieren, werde ich zum Schluss Events nutzen. (Als ich das Dokument vorbereite habe ich erstmal mit dem Unsinn angefangen, mir die Property Observer runterzuleiern um das Konzept zu pruefen, und dabei zu widerlegen, wie ihr lest)

Wir werden diese Funktionen gleich nutzen, um unser Model an den View zu binden, aber auch um gleich ein Ereignis zu feuern, an dem sich jeder (?) anmelden kann, der unser Modell kennt. Das folgende ist simpler als

Events als Notifier. Listener als Observer.

Halb-automatisches Property überwachen

Die beste Loesung für JavaScript von heute, denke ich.

    Model.prototype.set = function (name, value) {
	var model = this;
	var oldValue = model._data[name];
	var change_record = { name:name, value:value, oldValue:oldValue}; // = {name,value,oldValue} destructuring syntax kommt in ES6
	model._data[name] = value;
        model.emit("change", change_record);
        return model; // chain
    };

    // Achso, so richtet man den View ein (pseudo code)
    
    var View = function (model) {
	var elmap = {
	    "code" : document.querySelector("#view-element-for-code")
	};
	var view = this;
	model.on("change", function (change_record) {
	    // update view
	    elmap[change_record.name].innerHTML = elmap[change_record.value];	    
	});
	
    };
    
    model.set("code", "a+b;");

Harmony Proxies

Total trivial, Observer zu schreiben waere es, mit den Harmony Proxies, die leider nicht ueberall vorhanden sind, aber auf node.js zum Beispiel mit der v8 Maschine kommen. Und wenn ihr eh ein Webkit-Only Demo habt, mit V8, denn koennt ihr die nutzen. Und Firefox wird sowas immer haben. Spidermonkey ist doch mit von Brendan Eich.

Wie gesagt, am Schluss, wenn ich das Tutorial zu einem gemacht habe, und M., V., und C. programmiert sind, wird es eh mit einem einfachen Callback je Pfeil des Modells laufen. Aber was man mit dem Handler hier machen kann, ist schon geil...

#!/usr/local/bin/node --harmony_proxies --harmony_collections --harmony_typeof

exports.observeObject = function (object) {
     var handler = {
        get: function (obj, name) {
    	    console.log("get: "+ name);
    	    return object[name];
        
        },
        set: function (proxy, name, value) {
            console.log("set: " + name + " = " +value );
            return object[name] = value;
        },
        has: function (proxy, name) {
    	    return Object.hasOwnProperty.call(object, name);
        },
        delete: function (name) {
            console.log("delete: " + name);
    	    return delete object[name];
        },
        enumerate: function () {
    	    return ["first"];
        }
     };
     var proxy = Proxy.create(handler, object);
     return proxy;
};

if (!module.parent) {
    var test1 = {
	first: "Edward",
	last: "Gerhold"
    };
    
    var p1 = exports.observeObject(test1);
    console.log(p1.first);
    p1.first = "Eddi";
    p1.last = "Gerhi";
    for (var p in p1) {
	console.log("enumerate: "+ p);
    }
    console.log(test1.first);
    console.log(test1.last);
    p1.x = 12;
    console.dir(test1.x);
    delete p1.x;
    
}

Ausgabe

get: first
Edward
set: first = Eddi
set: last = Gerhi
enumerate: first
Eddi
Gerhi
set: x = 12
12
delete: x

Views

Ein Modell hat einen View. Das ist grundsaetzlich in der heutigen Zeit als das Template zu sehen. In JavaScript ist er mehr ein oder mehrere DOM-Elemente umfassend. Anderseits, wer Desktop GUI und Software Ergonomie kennt, kennt Widgets und Standards, die einen "View", einen Frame, ein Window, ein Rectangle, noch genauer beschreiben. Der View ist in der WebApp Elemente zur Darstellung, aber auch ein Formular. Ein Formular sollte aber ein eigener View sein, gegenueber der Ausgabe. Der Formular View schickt dann Events, wenn er veraendert wurde, und der Controller horcht, oder das Model, je nachdem welche Interaktion hinter steckt.

Ein View kann ein Formular sein, ein View kann aber auch die Ausgabe sein. Es kommt darauf an, welcher View fuer das Modell gewaehlt wird. Man kann natuerlich die Ansicht wechseln in einer Anwendung.

	    function View() {
	    
	    }
	    

Viewbinding

Ich hatte bei meinem ersten Versuch automatische Inputfelder.

		<input type=text data-binding="in:mytext:out:innerHTML:append"> 
	    

Und ebenso hatte ich innerHTML und {Templating} implementiert.

		function updateInnerHTML (el, obj, prop) {
		    var template = el.innerHTML; // speichere Template aus dem innerHTML 
	    	    Object.defineProperty(obj, prop, {
			set: function (v) {
			    el.innerHTML = lib.s.supplant(template, { prop: v });
			}
		    });
		}
		
	    

Diese Funktion erstetzt jede {property} im innerHTML dann automatisch, wenn die property geschrieben wird, neu, dazu wird das vorher abgespeicherte Template genutzt.

Zuerstmal der Code eines alten Datebinder Versuchs.

Dieser Code sucht alle Element mit data-binding Attribut raus und erkennt die unten mit den Regulaeren Ausdruecken getesteten Befehle.

Dabei werden Properties des Models gelesen und geschrieben, wie man unschwer erkennen kann. Es gab auch mal eine Demoseite dazu, ich weiss nur nicht, ob die noch funktioniert.

Der folgende Ansatz nutzt ES5 Getter und Setter und keine .get oder .set Methoden auf dem Modell Objekt, um den Binder zu notifizieren. Die andere Seite ist, im DOM nutzt man die onchange, onfocus, onblur, onkeypress, onkeydown, onkeyup Event Handler, um umgekehrt zu notifizieren, dass im View was eingegeben wurde.

Library.add("databinder", function(lib) {

	lib.DataBinder = function DataBinder() { 	
	}
	
	lib.DataBinder._ensureObjectBindingObject = function (object) {	// just create the thing if it is not there
	    if (!object.__databindings__) {
		    object.__databindings__ = Object.create(null);
		    Object.defineProperty(object, "__databindings__", {
			enumerable : false
		    });
	    }
	}
	lib.DataBinder._ensureObjectBindingProperty = function (object, property, overwrite) { // just create it if it isnt there
		if (!object.__databindings__[property] || overwrite) {
		    var o = object.__databindings__[property] = Object.create(null);
		    o.ongetters = [];
		    o.onsetters = [];
		    o.value = undefined;
		}
	}
	
	lib.DataBinder.assign = 
	lib.DataBinder.prototype.assign = function (object, propertyName, value) {	// the same as object[propertyName] = value :-)
	    object[propertyName] = value;
	}

	lib.DataBinder.registerProperty = 			/* supply an object and some property name, and the code creates some observable getter/setter variable */
	lib.DataBinder.prototype.registerProperty = function (object, propertyName, initialValue, overwrite) {
	    object = object === null ? this : object;
	    var binding = Object.getOwnPropertyDescriptor(object, propertyName);
	    
	    if (!binding || (binding && overwrite) || overwrite) {
		    this._ensureObjectBindingObject(object);	
		    this._ensureObjectBindingProperty(object, propertyName, overwrite);
		    
		    Object.defineProperty(object, propertyName, 
			{
			    get: function() {
				var p = object.__databindings__[propertyName];
				p.ongetters.forEach(function(g) {
				    g.call(object, propertyName, p.value);
			        }); 
				return p.value
			    },
			    set: function(v) {
				var p = object.__databindings__[propertyName];
				p.value = v;
				p.onsetters.forEach(function(s) {
				    s.call(object, p.value, propertyName);
			        });
			    },
			    enumerable : true
			}
		    );

		if (initialValue != undefined) {
		    object[propertyName] = initialValue;
		}
	    } else {
		console.error("lib.DataBinder.registerProperty: object["+propertyName+"] exists. You could set overwrite to true.");
	    }
	} 
	
	lib.DataBinder.registerHandler =	/* register the callbacks, to get noticed, if the property is set. do cool stuff with. */
	lib.DataBinder.prototype.registerHandler = function (object, propertyName, handler) {
	    object = object === null ? this : object;
		this._ensureObjectBindingObject(object);	
		this._ensureObjectBindingProperty(object, propertyName);
	    var binding = Object.getOwnPropertyDescriptor(object, propertyName),
	    p = object.__databindings__[propertyName];
	    if (!binding) {
		console.error("lib.DataBinder.registerHandler:  object["+propertyName+"] is undefined. Please registerProperty(object, name[,value[,overwrite]]) first");
	    } else {
		p.onsetters.push(handler);
	    }
	}

	lib.DataBinder.registerGetHandler = /* Maybe useless, coz it is called, if you get the value from an object variable (ongetters) */
	lib.DataBinder.prototype.registerGetHandler = function (object, propertyName, handler) {
	    object = object === null ? this : object;
		this._ensureObjectBindingObject(object);	
		this._ensureObjectBindingProperty(object, propertyName);
	    var binding = Object.getOwnPropertyDescriptor(object, propertyName),
	    p = object.__databindings__[propertyName];
	    if (!binding) {
		console.error("lib.DataBinder.registerGetHandler:  object["+propertyName+"] is undefined. Please registerProperty(object, name[,value[,overwrite]]) first");
	    } else {
		p.ongetters.push(handler);
	    }
	}

	lib.DataBinder.__databindings__ = Object.create(null);			// DOM Bindings go here, Object Bindings go into object.__databindings__
	lib.DataBinder.prototype.__databindings__ = Object.create(null);	// If registerHandler(null, ist called, this.__databindings__ is used, too
	
	lib.DataBinder.registerDOMBinding = 		/* this function combines data-binding attributes set in the dom. Format: data-bindings="prop:in|out|out-supplant|elProp|inputevent" */
	lib.DataBinder.prototype.registerDOMBinding = function( property ) {
		var that = this, bindings = this.__databindings__[property];	
		if (!bindings) {
		    bindings = this.__databindings__[property] = {
			input : [],
			output : []
		    };
		}
		lib.select("*[data-binding]").forEach(function(el) {
		    var binding, att;
			att = el.getAttribute("data-binding");
			att = att.replace(/\s/g, "");
	    		att = att.split(":");
			if (att[0] === property) {
			    binding = Object.create(null);
			    binding.node     = el;
			    binding.operator = att[1];
			    binding.value    = att[2];
			    if (/^(in)/.test(binding.operator))  {
				binding.event = att[3] || "keyup";
				bindings.input.push(binding);
			    } else if (/^(out)/.test(binding.operator))  {
			    	bindings.output.push(binding);
			    }
			}
		});
		this.registerProperty(bindings, property);
		bindings.input.forEach(function(b) {
		    lib.dom.listen(b.node, b.event, function(e) {
			bindings[property] = b.node[b.value];
		    });
		
		});
		bindings.output.forEach(function(b) {
		    var handler;
		    if (b.operator === "out") {
			handler = function(v) {
			    b.node[b.value] = v;
			}
		    } else if (b.operator === "out-supplant") {
			b.template = b.node[b.value]; 
			handler = function (v) {
				var s = {}; s[property] = v;
				b.node[b.value] = lib.s.supplant(b.template, s);
			}
		    } else if (b.operator === "out-append") {
			b.template = b.node[b.value];
			handler = function(v) {
			    b.node[b.value] = b.template + v;
			}
		    } else if (b.operator === "out-prepend") {
			b.template = b.node[b.value];
			handler = function(v) {
			    b.node[b.value] = v + b.template;
			}
		    }
		    that.registerHandler(bindings, property, handler);
		});
	}
	
	lib.DataBinder.registerDOMHandler =				/* add callbacks to data-binding="" properties to let the system get with, too */
	lib.DataBinder.prototype.registerDOMHandler = function(propertyName, handler) {
	    var bindings = this.__databindings__[propertyName];
	    if (!bindings) {
		console.error("lib.DataBinder.registerDOMHandler: "+propertyName+" is undefined. Please registerDOMBinding(name) first");
	    } else {
		this.registerHandler(bindings, propertyName, handler);
	    }
	}
	
}); // Library.add(function(lib));
	

Controller

Der Controller ist die Verbindung zum Rest der Anwendung hin. Der Controller hat unterschiedliche Aufgaben. In den Web Apps besonders das Routing der Requests, ansonsten instanziieren der Modelle und Views, das Verkuepfen jener.

	    function Controller () {
		// has model
		// has view
		// hat die capabilites alleine
		// und ich erzeuge den controller
		// wuerde ich meinen
	    }
	    

Router

Router mappen URLs zu Methoden. Rufen zu einer bestimmten URL eine bestimmte Funktion.

In der Web App ist der Router der Controller der ganzen Http-Hrefs die geklickt werden koennen. Angesichts Ajax hat man sich dem hashchange bedient um http://example.com/app/view#!/url/service das "/url/service" zu extrahieren. History.pushState und .replaceState und onpopstate ist neu.

Google hat Ajax Applicationen crawlable gemacht. Es gibt sogar fuer das #! einen entsprechenden _fragment_identifier_ Hack im HTML head Element, den ich jetzt nich auswendig kenn, er wird mit einem meta tag identifiziert <meta name="_fragment_identifier_" content="!"> oder so. Googlet mal nach, den findet ihr selbst..

HTML5 bietet dann noch das API um die Location Bar und die Vor- und Zurueck Knoepfe zu kontrollieren. history.pushState(state, title=null, href) wechselt sofort die URL in der Bar und der letzte State geht in die History.

history.pushState({state}, null, "href")

Der state Parameter ist dazu da, dass man den Zustand, das koennen Variablen oder URLs mit Parametern, die aktuelle CD, oder Spielstand, oder was auch immer sein.

Der zweite Parameter null steht für den title, wird aber vorerst nicht in den Browsern unterstützt (man soll null schreiben). Den Titel, wenn man ihn setzen will, sollte man im state speichern und manuell setzen.

Der dritte Parameter href setzt die Location Bar auf den String, der übergeben wird. Gibt man "http://localhost/ichspinne an, zeigt der Browser http://localhost/ichspinne an

history.replaceState(state, title, href)

Der hier ist für unsere AJAX Anwendung wahrscheinlich viel interessanter, wenn man keinen Vor- Zurueck- Knopf haben will. Mit replaceState (was ich gleich ebenfalls einsetzen will) ersetzt man den aktuellen Zustand des Automaten, anstelle ihn auf die Liste zu pushen.

    > http://linux-swt.de/sdfsdf/abc
    
    history.replaceState(null, null, "def");
    > http://linux-swt.de/sdfsdf/def
    
history.back(), history.forward(), history.go()

Das sind die beiden Funktionen, die den Vor- und Zurueck-Knopf bedienen. Wenn die .go() Methode ohne Url gerufen wird, laedt sie die aktuelle Seite nochmal. Was passiert, wenn ich alle Eventhandler angeschlossen habe, werde ich im weiteren Verlauf beschreiben.

Ich werde den richtigen Code, das hier ist nur ein unbrauchbares Skelett, sehr bald, (die Tage) hier posten.

Nein, mir ist sowas nicht peinlich:

	

    var Router = function () {
	var router = this instanceof Router ? this : new Router();
	if (!router.routes) { // check for 		^^ new Router run already
	    router.routes = {};
	    if (isBrowser) {
		if (history && typeof history.pushState === "function") {
		    var _onpopstate = function (e) {
			// Args, kann das nicht auswendig.
			
			// Ich zeig euch gleich, wie ich
			// die Route und den State verbinde
			// und alle Schnipsel lade, die der
			// State verlangt (den ich definiere)
		
		    };
		    window.addEventListener("popstate", _onpopstate, false);
		} else if ('onhashchange' in window || window.onhashchange === null) {
		    router.five = false;
		    var _onhashstate = function (e) {
			var hash = document.location.hash;
		    };
		    window.addEventListener("hashchange", _onhashchange, false);
		}
	    } else if (isNode) {
		// schlechter einfach die zu kombinieren
		// auf node habe ich das request objekt
		// und das response objekt aus "net".
		// da brauche ich parameter fuer den Konstruktor
		// um das zu referenzieren, history im Browser
		// ist global, aber das hier nicht.
		
		// Requires: Adapter (Browser/Node) und Fassade (beide Adapter)
	    }
	}
	return router;
    };
    Router.prototype = new Emitter();
    Router.prototype.addRoute = function (name, callback) {
	if (typeof name !== "string" || typeof callback !== "function") 
	    throw new TypeError("route: wrong argumetns: name and callback expected");
	this.routes[name] = callback;
    };
    

Das ist natürlich Quatsch. Aber ich zeig euch das nachher schon noch :)=

Erweiterungsmoeglichkeiten

Ich notier mir das schonmal für den Router

Hier kann man zum Beispiel modules:// oder rest:// registrieren...

	navigator.registerProtocolHandler( scheme, url, title ) 
    

Hier kann man für zum Beispiel zip Files einen Handler registrieren.

	navigator.registerContentHandler( mimeType, url, title ) 
    

Router (Server)

Kennt ihr das Express Framework? Ja? Kennt ihr meinen ersten Router, den ich vor Express versucht habe, nein? Das Listing vom Browser Router ist noch nicht fertig. Ihr koennt sicher sein, dass ich URL.parse(request.url) vorhabe und ebenfalls auf die Situation mit einem route Callback reagiere.

	
	
    
    

Model View Presenter

Controller, Command, Selection, Model. Und View, Interaction, Controller.

Im Presentermodell sind noch einige Fragen zwischen die Pfeile gestellt.

Mehr dazu das naechste mal. Das kriege ich schon hin.

Model View ViewModel

Das ViewModel drückt wie das Wort schon aus, was der Controller ist.

Wenn das nicht stimmt, habe ich Pech. Aber ich darf mich auch irren, bis ich fertig bin ;-)

Mehr dazu das naechste mal. Ich kriege das alles noch raus und hin.

Beispielprogramm TO_DO_APP

Ich habe diese Todo App gerade so geschrieben. Was ich NICHT machte, war mein eigenes Diagramm von oben zu verfolgen. Ich habe jetzt einfach eine Todoapp aus dem ff. geschrieben. Ich habe nirgendwo nachgeguckt, ausser das Skript kurz bevor ich dachte, fertig zu sein, im Browser ausprobiert zu haben.

Es liegt nun an mir, daraus ein MVC zu machen. Das hier ist nur nahe dran, aber wenn man genau liest, verstosse ich gegen die Regeln auf dem Bild. Und die Regeln, die ich nicht kenne. Aber das ist kein Problem.

Aber Achtung: In diesem Beispiel ist alles verdreht, und der Controller hat kaum eine Funktion.

Der Quelltext

Das ist kein richtiges MVC, das ist mehr ein ViewModel. ;-) Wenn ich beide zusammen in ein Objekt fasse, ist es ein Viewmodel.

Fazit: TODO ist zu SIMPEL fuer eine zweckmaessige Verwendung des Patterns. Hier brauche ich gar kein Model - hier wuerden die DOM Nodes (update: das SIND Viewmodelle an sich! Reprise des mvc.html folgt 2013), ich habe ne Liste genommen, als Array reichen.

Fazit: Genaugenommen ist MVC zu fett fuer eine TO DO APP, die kann man noch kompakter und viel straigther schreiben.

Als ich gerade aufs Diagram oben guckte, wusste ich sofort, welche Referenzen ich wie zu tauschen habe. Das ist der angenehme Nebeneffekt des einfach drauflos zu schreiben.

Wer meint, ich haette HTML injecten sollen, weil das weniger Code ist, oder zumindest jQuery nehmen sollen, damit das auch weniger Code ist. Ist mir egal, hehe.

	
<div class="todo">
</div>

<style>
    .todo { border:1px solid black; border-radius:25px; background:white; color:black; font-family:helvetica; }
    .todo-list { list-style:none; margin-left: 10px; background:white; padding:25px; }
    .todo-listitem { margin:10px; padding:10px; border:1px solid darkgreen; border-radius:3px; background:lightgrey; font-family:arial; }
    .todo-cb { height:2em; width:2em; }
    .todo-text { border:0; background:#eefffe; }
    .todo-remove { background: darkblue; color: white; border:1px solid green; }
    .todo-input { margin:25px; margin-right:5px; color:darkred; border: 1px solid black; border-radius:8px;}
    .todo-add { background:black; color:white; border:1px solid darkred; }
</style>

<script>

    // Der Emitter ist ausserhalb der addEventListener Closure, da ich den in diesem Dokument mehrmals drankommen lasse.
    // So spare ich mir, und ihr euch, das Kopieren auf die Console. Ansonsten gehoert der auch mit in die addEventListener
    // Funktion und komplett gekapselt.

    function Emitter () {
	var emitter = this instanceof Emitter === false ? new Emitter() : this;
	if (typeof emitter._eventListeners === "undefined") { // ^
	    Object.defineProperty(emitter, "_eventListeners", {
		value: {},
		enumerable: false,
		configurable: false
	    });
	}
	return emitter;
    };
    
    Emitter.prototype = {
	constructor: Emitter,
	on: function (event, callback) {
	    if (typeof event !== "string" || typeof callback !== "function")
		throw new TypeError("on: event (str), callback (fn) expected");
	    if (!this._eventListeners[event]) {
		this._eventListeners[event] = [];
	    }
	    this._eventListeners[event].push(callback);
	    return this;
	},
	once: function (event, callback) {
	    var emitter = this;
	    function removable () {
		callback.apply(this, arguments);
		emitter.remove(event, removable);
	    }
	    if (typeof event !== "string" || typeof callback !== "function")
		throw new TypeError("once: event (str), callback (fn) expected");
	    this.on(event, removable);
	    return this;
	},	
	emit: function (event) { // andere Namen: trigger, fire, dispatchEvent
	    var i, j, list, args, emitter = this;
	    if (typeof event !== "string")
		throw new TypeError("emit: event (str) expected");
	    if (list=this._eventListeners[event]) {
		j = list.length;
		if (j > 0) {
		    args = [].slice.call(arguments,1);
		    for (var i=0; i < j; i++) {
			list[i].apply(emitter, args);
		    }
		}
	    }
	    return this;
	},
	remove: function (event, callback) {
	    if (typeof event !== "string" || typeof callback !== "function")
		throw new TypeError("remove: event (str), callback (fn) expected");
	    if (this._eventListeners[event]) {
		this._eventListeners[event] = 
		this._eventListeners[event].filter(function (listener) {
		    return listener !== callback;
		});
	    }
	    return this;
	},
	removeAll: function (event) {
	    if (typeof event !== "string")
		throw new TypeError("removeAll: event (str) expected");
	    if (this._eventListeners[event]) {
		this._eventListeners[event] = [];
	    }	
	    return this;
	}
    };

window.addEventListener("load", function (e) {
    function TodoModel () {
	var model = this;
	var todos = []; // { value, done }
	model.add = function (value, done) {
	    var record = { value: value, done: done }
	    todos.push(record);
	    model.store();
	    model.emit("add", record);
	};
	model.remove = function (zbindex) {
		todos = todos.slice(0, zbindex-1).concat(todos.slice(zbindex));
		model.store();
		model.emit("remove", zbindex);
	};
	model.store = function () {
	    if (typeof localStorage !== "undefined") {
		try {
		    localStorage.setItem("edwards_todo_example", JSON.stringify(todos));
		} catch (e) { console.error("store"); }
	    }
	};
	model.restore = function () {
	   if (typeof localStorage !== "undefined") {
		try {
		    var t = JSON.parse(localStorage.getItem("edwards_todo_example"));
		} catch (e) { console.error("restore"); }
		if (t) {
		    todos = t;
		    model.emit("restore");
		}
	   }
	};
	model.each = function (func) {
	    todos.forEach(func);
	};
	model.count = function () {
	    return todos.length;
	};
	model.change = function (zbindex, record) {
	    var keys = Object.keys(record); 
	    if (keys) {
		keys.forEach(function (key) {
	    	    todos[zbindex][key] = record[key];
		});
		model.store();
	    }
	};
	return Object.freeze(model);
    }
    TodoModel.prototype = new Emitter();

    function TodoView (parent, model) {
	var view = this;
	if (typeof parent === "string") { parent = document.querySelector(parent); } 
	if (typeof model !== "object" && typeof parent !== "object") { throw new TypeError("wrong args: parent, model expected"); }
	
	// the list
	var list = document.createElement("ul");
	list.className = "todo-list";
	parent.appendChild(list);
	
	// each new line
	model.on("add", function (record) {
	
	    // Ich setze hier den View, wenn das Model "add" emittiert.
	    
	    var count = model.count(); // new index
	    var listitem = document.createElement("li");
	    listitem.id = "todo-"+count;
	    listitem.className = "todo-li";
	    
	    var cb = document.createElement("input");
	    cb.type = "checkbox";
	    cb.checked = record.done;
	    cb.id = "cb"+count;
	    cb.className = "todo-cb";
	    cb.addEventListener("change", function (e) {
		var zbindex = count - 1;
		model.change(zbindex, { done: cb.checked });
	    }, false); // ich glaube, die geht nicht
	    listitem.appendChild(cb);
	    
	    var text = document.createElement("input");
	    text.type = "text";
	    text.name = "name";
	    text.id = "text"+count;
	    text.value = record.value;
	    text.className = "todo-text";
	    text.addEventListener("change", function (e) {
		var zbindex = count -1;
		model.change(zbindex, { value: text.value });
	    }, false);	    
	    listitem.appendChild(text);
	    
	    var remove = document.createElement("button");
	    remove.textContent = "Remove";
	    remove.id = "remove"+count;
	    remove.className = "todo-remove";
	    remove.addEventListener("click", function (e) {
		list.removeChild(listitem);
		model.remove(count);
	    }, false);
	    listitem.appendChild(remove);
	    
	    list.appendChild(listitem);
	});
	model.on("remove", function (zbindex) {
	    var listitem = document.querySelector("todo-"+zbindex);
	    if (listitem) {
		listitem.parentNode.removeChild(listitem);
	    }
	    // das sieht mir schon sehr memory leaky aus (siehe cb, text, remove, closure bei add)
	});
	model.on("restore", function () {
	    list.innerHTML = "";
	    model.each(function (record) {
		model.emit("add", record); // Ich sollte das Add auf dem view registrieren...Das hier ist noch verdreht
	    });
	    // crazy workarounds mit dem vorhandenen
	});
	
	var input = document.createElement("input");
	input.type = "text";
	input.className = "todo-input";
	var button = document.createElement("button");
	button.textContent = "Add";
	button.className = "todo-add";
	button.addEventListener("click", function (e) {
	    if (input.value) {
		model.add(input.value, false);
		input.value = "";
	    }
	}, false);
	parent.appendChild(input);
	parent.appendChild(button);
    }
    TodoView.prototype = new Emitter();

    function TodoController (parent) {
	"use strict";
	var model, view;
    	this.on("init", function () {
	    model = new TodoModel();
	    view = new TodoView(parent, model);
	    model.restore();
	});
	// Hier ist noch viel Platz
	this.on("end", function () {
	    model = null;
	    view = null;
	});	
    }
    TodoController.prototype = new Emitter();
    
    // The program. Mein Controller ist hier eher überflüssig.

    var controller = new TodoController(document.querySelector(".todo"));
    controller.emit("init");
    
    
}, false);
</script>

Keine Angst, ich mach den neu, um diese Seite weiter zu machen.