Refactoring to better fit the MVVM pattern
authorveltr
Fri, 17 Aug 2012 12:50:00 +0200
changeset 20 bd58970ffd16
parent 19 d49b927bfe62
child 21 b43dd87f7ffa
Refactoring to better fit the MVVM pattern
client/css/renkan.css
client/data/dynamic-data.json
client/js/full-json.js
client/js/json-serializer.js
client/js/main.js
client/js/model.js
client/js/paper-renderer.js
client/js/random-data.js
client/js/view-model.js
client/render-test.html
--- a/client/css/renkan.css	Thu Aug 16 17:24:13 2012 +0200
+++ b/client/css/renkan.css	Fri Aug 17 12:50:00 2012 +0200
@@ -45,6 +45,18 @@
     font-size: 10px; font-family: Arial, Helvetica, sans-serif;
 }
 
+.Rk-Bins {
+    background: #F0F0F0; position: absolute; left: 0; top: 0; width: 200px; bottom: 0;
+}
+
+.Rk-Render {
+    position: absolute; left: 200px; top: 0; right: 0; bottom: 0;
+}
+
+.Rk-Editor {
+    position: absolute; left: 0; top: 0;
+}
+
 .Rk-CloseX {
     float: right; cursor: pointer;
 }
@@ -91,4 +103,15 @@
 
 .Rk-ZoomOut:hover {
     background-position: -21px -20px;
-}
\ No newline at end of file
+}
+
+ul.Rk-TabButtons {
+}
+
+li.Rk-TabButton {
+    float: left; padding: 2px; font-size: 13px; border-top-left-radius: 5px; border-top-right-radius: 5px; background: #CCCCCC;
+}
+
+.Rk-TabDiv {
+    width: 100%; clear: both; overflow: auto; border-top: 1px solid #ccc;
+}
--- a/client/data/dynamic-data.json	Thu Aug 16 17:24:13 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-{"title":"Test Graph","users":[{"id":"anonymous","title":"anonymous","uri":"","color":"#0000ff"},{"id":"u-cybunk","title":"Samuel","uri":"http://twitter.com/cybunk","color":"#e00000"},{"id":"u-raphv","title":"Raphael","uri":"http://twitter.com/raphv","color":"#00a000"}],"nodes":[{"id":"n-001","title":"連環 (Renkan)","description":"Vive Renkan !","uri":"http://ja.wikipedia.org/wiki/%E7%99%BE%E5%AD%A6%E9%80%A3%E7%92%B0","created_by":"u-cybunk","position":{"x":-26.522137994214717,"y":-210.84128058998363}},{"id":"n-002","title":"Savoir","description":"","uri":"http://fr.wikipedia.org/wiki/Savoir","created_by":"u-raphv","position":{"x":51.05688741877592,"y":2.5968600661189982}},{"id":"n-003","title":"Connaissance","description":"","uri":"http://fr.wikipedia.org/wiki/Connaissance","created_by":"u-raphv","position":{"x":289.41278995919333,"y":-148.06006021345246}},{"id":"n-005","title":"nœud","description":"","uri":"http://fr.wikipedia.org/wiki/Th%C3%A9orie_des_graphes","created_by":"u-cybunk","position":{"x":-348.6577736928668,"y":36.915363564739906}},{"id":"n-006","title":"arête","description":"","uri":"http://fr.wikipedia.org/wiki/Th%C3%A9orie_des_graphes","created_by":"u-cybunk","position":{"x":-310.04965494954644,"y":-269.5988072780878}},{"id":"node-2012-07-31-f1c2d0fd52a9d0e2-0016","title":"graphe","description":"","uri":"","created_by":"anonymous","position":{"x":-190.13786466320713,"y":-97.7768211544552}},{"id":"node-2012-07-31-6d574a0c9d5d2931-0026","title":"Test","description":"","uri":"","created_by":"anonymous","position":{"x":156.5765089679022,"y":-297.9314037374282}}],"edges":[{"id":"e-001","title":"(untitled edge)","uri":"","from":"n-001","to":"n-002","created_by":"u-raphv"},{"id":"e-002","title":"(untitled edge)","uri":"","from":"n-002","to":"n-003","created_by":"u-raphv"},{"id":"edge-2012-07-31-f1c2d0fd52a9d0e2-0013","title":"(untitled edge)","uri":"","from":"n-006","to":"n-001","created_by":"anonymous"},{"id":"edge-2012-07-31-f1c2d0fd52a9d0e2-001d","title":"(untitled edge)","uri":"","from":"node-2012-07-31-f1c2d0fd52a9d0e2-0016","to":"n-005","created_by":"anonymous"},{"id":"edge-2012-07-31-f1c2d0fd52a9d0e2-0020","title":"(untitled edge)","uri":"","from":"node-2012-07-31-f1c2d0fd52a9d0e2-0016","to":"n-001","created_by":"anonymous"},{"id":"edge-2012-07-31-f1c2d0fd52a9d0e2-0023","title":"(untitled edge)","uri":"","from":"node-2012-07-31-f1c2d0fd52a9d0e2-0016","to":"n-006","created_by":"anonymous"},{"id":"edge-2012-07-31-6d574a0c9d5d2931-002c","title":"(untitled edge)","uri":"","from":"node-2012-07-31-6d574a0c9d5d2931-0026","to":"n-001","created_by":"anonymous"}]}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/js/full-json.js	Fri Aug 17 12:50:00 2012 +0200
@@ -0,0 +1,130 @@
+/* Saves the Full JSON at each modification */
+
+Rkns.RemoteModels.FullJson = Rkns.Utils.inherit(Rkns.RemoteModels._Base);
+
+Rkns.RemoteModels.FullJson.prototype._init = function(_project, _opts) {
+    this.url = _opts.url;
+    this.load(this.url);
+    if (typeof _opts.http_method == "undefined") {
+        _opts.http_method = 'PUT';
+    }
+    this.http_method = _opts.http_method;
+    var _this = this;
+    this.fullSave
+        = this.addUser
+        = this.addNode
+        = this.addEdge
+        = this.updateNode
+        = this.updateEdge
+        = this.removeNode
+        = this.removeEdge
+        = Rkns._.throttle(function() {
+            _this._save.apply(this, Array.prototype.slice.call(arguments,0));
+        }, 2000);
+}
+
+Rkns.RemoteModels.FullJson.prototype.load = function(_url) {
+    var _this = this;
+    Rkns.$.getJSON(_url, function(_data) {
+        _this.deserialize(_data);
+        _this.handleCallbacks();
+    });
+}
+
+Rkns.RemoteModels.FullJson.prototype.deserialize = function(_serializedData) {
+    if (typeof _serializedData === "string") {
+        _serializedData = JSON.parse(_serializedData);
+    }
+    var _proj = this._project;
+    _proj.title = _serializedData.title || "(untitled project)";
+    if (typeof _serializedData.users === "object" && _serializedData.users) {
+        Rkns._(_serializedData.users).each(function(_data) {
+            var _userData = {
+                id: _data.id,
+                title: _data.title,
+                uri: _data.uri,
+                color: _data.color
+            };
+            _proj.addUser(_userData);
+        });
+    }
+    if (typeof _serializedData.nodes === "object" && _serializedData.nodes) {
+        Rkns._(_serializedData.nodes).each(function(_data) {
+            var _nodeData = {
+                id: _data.id,
+                title: _data.title,
+                description: _data.description,
+                uri: _data.uri,
+                created_by: _data.created_by,
+                position: {
+                    x: _data.position.x,
+                    y: _data.position.y
+                }
+            };
+            _proj.addNode(_nodeData);
+        });
+    }
+    if (typeof _serializedData.edges === "object" && _serializedData.edges) {
+        Rkns._(_serializedData.edges).each(function(_data) {
+            var _edgeData = {
+                id: _data.id,
+                title: _data.title,
+                uri: _data.uri,
+                from: _data.from,
+                to: _data.to,
+                created_by: _data.created_by
+            };
+            _proj.addEdge(_edgeData);
+        });
+    }
+}
+
+Rkns.RemoteModels.FullJson.prototype.serialize = function() {
+    var _res = {
+        title: this._project.title,
+        users: this._project.users.map(function(_user) {
+            return {
+                id: _user.id,
+                title: _user.title,
+                uri: _user.uri,
+                color: _user.color
+            }
+        }),
+        nodes: this._project.nodes.map(function(_node) {
+            return {
+                id: _node.id,
+                title: _node.title,
+                description: _node.description,
+                uri: _node.uri,
+                created_by: _node.created_by.id,
+                position: {
+                    x: _node.position.x,
+                    y: _node.position.y
+                }
+            }
+        }),
+        edges: this._project.edges.map(function(_edge) {
+            return {
+                id: _edge.id,
+                title: _edge.title,
+                uri: _edge.uri,
+                from: _edge.from.id,
+                to: _edge.to.id,
+                created_by: _edge.created_by.id
+            }
+        })
+    }
+    return _res;
+}
+
+Rkns.RemoteModels.FullJson.prototype._save = function() {
+    var _data = JSON.stringify(this.serialize());
+    Rkns.$.ajax({
+    	type: this.http_method,
+    	url: this.url,
+    	contentType: "application/json",
+    	data: _data,
+    	success: function(data, textStatus, jqXHR) {
+        }
+    });
+};
--- a/client/js/json-serializer.js	Thu Aug 16 17:24:13 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-Rkns.Serializers.BasicJson = Rkns.Utils.inherit(Rkns.Serializers._Base);
-
-Rkns.Serializers.BasicJson.prototype._init = function() {
-    this.load(this._project._opts.url);
-    var _this = this;
-    this.save = Rkns._.throttle(function() {
-        _this._save.apply(this, Array.prototype.slice.call(arguments,0));
-    }, 2000);
-}
-
-Rkns.Serializers.BasicJson.prototype.load = function(_url) {
-    var _this = this;
-    Rkns.$.getJSON(_url, function(_data) {
-        _this.deserialize(_data);
-        _this.handleCallbacks();
-    });
-}
-
-Rkns.Serializers.BasicJson.prototype.deserialize = function(_serializedData) {
-    if (typeof _serializedData === "string") {
-        _serializedData = JSON.parse(_serializedData);
-    }
-    var _proj = this._project;
-    _proj.title = _serializedData.title || "(untitled project)";
-    if (typeof _serializedData.users === "object" && _serializedData.users) {
-        Rkns._(_serializedData.users).each(function(_data) {
-            var _userData = {
-                id: _data.id,
-                title: _data.title,
-                uri: _data.uri,
-                color: _data.color
-            };
-            _proj.addUser(_userData);
-        });
-    }
-    if (typeof _serializedData.nodes === "object" && _serializedData.nodes) {
-        Rkns._(_serializedData.nodes).each(function(_data) {
-            var _nodeData = {
-                id: _data.id,
-                title: _data.title,
-                description: _data.description,
-                uri: _data.uri,
-                created_by: _data.created_by,
-                position: {
-                    x: _data.position.x,
-                    y: _data.position.y
-                }
-            };
-            _proj.addNode(_nodeData);
-        });
-    }
-    if (typeof _serializedData.edges === "object" && _serializedData.edges) {
-        Rkns._(_serializedData.edges).each(function(_data) {
-            var _edgeData = {
-                id: _data.id,
-                title: _data.title,
-                uri: _data.uri,
-                from: _data.from,
-                to: _data.to,
-                created_by: _data.created_by
-            };
-            _proj.addEdge(_edgeData);
-        });
-    }
-}
-
-Rkns.Serializers.BasicJson.prototype.serialize = function() {
-    var _res = {
-        title: this._project.title,
-        users: this._project.users.map(function(_user) {
-            return {
-                id: _user.id,
-                title: _user.title,
-                uri: _user.uri,
-                color: _user.color
-            }
-        }),
-        nodes: this._project.nodes.map(function(_node) {
-            return {
-                id: _node.id,
-                title: _node.title,
-                description: _node.description,
-                uri: _node.uri,
-                created_by: _node.created_by.id,
-                position: {
-                    x: _node.position.x,
-                    y: _node.position.y
-                }
-            }
-        }),
-        edges: this._project.edges.map(function(_edge) {
-            return {
-                id: _edge.id,
-                title: _edge.title,
-                uri: _edge.uri,
-                from: _edge.from.id,
-                to: _edge.to.id,
-                created_by: _edge.created_by.id
-            }
-        })
-    }
-    return _res;
-}
-
-Rkns.Serializers.BasicJson.prototype._save = function() {
-    var _data = JSON.stringify(this.serialize());
-    Rkns.$.ajax({
-    	type: 'PUT',
-    	url: this._project._opts.url,
-    	contentType: "application/json",
-    	data: _data,
-    	success: function(data, textStatus, jqXHR) {
-        }
-    });
-};
--- a/client/js/main.js	Thu Aug 16 17:24:13 2012 +0200
+++ b/client/js/main.js	Fri Aug 17 12:50:00 2012 +0200
@@ -33,9 +33,9 @@
 
 Rkns._ = _;
 
-Rkns.Serializers = {};
+Rkns.RemoteModels = {};
 
-Rkns.Serializers._Base = function(_project) {
+Rkns.RemoteModels._Base = function(_project, _opts) {
     if (typeof _project !== "undefined") {
         this._project = _project;
         this._callbackQueue = [];
@@ -43,23 +43,31 @@
     }
 }
 
-Rkns.Serializers._Base.prototype.deserialize = function() {}
+Rkns.RemoteModels._Base.prototype.fullSave
+    = Rkns.RemoteModels._Base.prototype.addUser
+    = Rkns.RemoteModels._Base.prototype.addNode
+    = Rkns.RemoteModels._Base.prototype.addEdge
+    = Rkns.RemoteModels._Base.prototype.updateNode
+    = Rkns.RemoteModels._Base.prototype.updateEdge
+    = Rkns.RemoteModels._Base.prototype.removeNode
+    = Rkns.RemoteModels._Base.prototype.removeEdge
+    = function() {}
 
-Rkns.Serializers._Base.prototype.deferCallback = function(_callback) {
+Rkns.RemoteModels._Base.prototype.deferCallback = function(_callback) {
     var _this = this;
     Rkns._.defer(function() {
         _callback.call(_this);
     });
 }
 
-Rkns.Serializers._Base.prototype.handleCallbacks = function() {
+Rkns.RemoteModels._Base.prototype.handleCallbacks = function() {
     this._loaded = true;
     while (this._callbackQueue.length) {
         this.deferCallback(this._callbackQueue.splice(0,1)[0]);
     }
 }
 
-Rkns.Serializers._Base.prototype.onLoad = function(_callback) {
+Rkns.RemoteModels._Base.prototype.onLoad = function(_callback) {
     if (this._loaded) {
         this.deferCallback(_callback);
     } else {
@@ -67,117 +75,78 @@
     }
 }
 
-Rkns.Serializers._Base.prototype.save = function() {}
+Rkns.RemoteModels._Base.prototype.save = function() {}
+
+Rkns.Bins = {}
+
+Rkns.Bins._Base = function(_project) {
+    if (typeof _project !== "undefined") {
+        this._project = _project;
+    }
+}
+
+Rkns.Bins._Base.prototype.baseTemplate = '';
 
-Rkns.Project = function(_opts) {
-    if (typeof _opts.serializer == "undefined") {
-        _opts.serializer = "BasicJson";
+Rkns.Bins._Base.prototype.addTab = function(_title) {
+    var _tabDiv = Rkns.$('<div>');
+    _tabDiv.addClass('Rk-TabDiv')
+        .css("display","none")
+        .appendTo('.Rk-TabDivs');
+    var _tabButton = Rkns.$('<li>');
+    _tabButton.addClass('Rk-TabButton')
+        .html(_title)
+        .click(function() {
+            Rkns.$('.Rk-TabButton').removeClass("active");
+            Rkns.$(this).addClass("active");
+            Rkns.$('.Rk-TabDiv').hide();
+            _tabDiv.show();
+        })
+        .appendTo('.Rk-TabButtons');
+    return {
+        button: _tabButton,
+        contents: _tabDiv
+    }
+}
+
+/* Point of entry */
+
+Rkns.Renkan = function(_opts) {
+    if (typeof _opts.remotemodel == "undefined") {
+        _opts.remotemodel = "BasicJson";
     }
     if (typeof _opts.language == "undefined" || typeof Rkns.i18n[_opts.language] == "undefined") {
         _opts.language = "en";
     }
-    this.l10n = Rkns.i18n[_opts.language];
-    this._opts = _opts;
-    this.users = new Rkns.Model.List();
-    this.nodes = new Rkns.Model.List();
-    this.edges = new Rkns.Model.List();
-    if (typeof this._opts.user === "object") {
-        this.current_user = this.addUser(this._opts.user)
+    if (typeof _opts.container == "undefined") {
+        _opts.container = "renkan";
+    }
+    this.project = new Rkns.ViewModel.Project();
+    this.project.l10n = Rkns.i18n[_opts.language];
+    if (typeof _opts.user === "object") {
+        this.current_user = this.project.addUser(_opts.user)
     }
-    this.serializer = new Rkns.Serializers[_opts.serializer](this);
-    this.renderer = new Rkns.Renderer.Scene(this);
+    Rkns.$("#" + _opts.container).html(this.template());
+    this.project.remotemodel = new Rkns.RemoteModels[_opts.remotemodel](this.project, _opts);
+    this.project.renderer = new Rkns.Renderer.Scene(this.project, _opts);
     var _this = this;
-    this.serializer.onLoad(function() {
-        if (typeof _this.current_user === "undefined") {
-            _this.current_user = _proj.users[0];
+/*    if (typeof _opts.bins === "object") {
+        this.bins = Rkns._(_opts.bins).map(function(_type) {
+            return new Rkns.Bins[_type](_this);
+        });
+        Rkns.$('.Rk-TabButton:last').addClass("active");
+        Rkns.$('.Rk-TabDiv:last').show();
+} */
+    this.project.remotemodel.onLoad(function() {
+        if (typeof _this.project.current_user === "undefined") {
+            _this.project.current_user = _this.project.users[0];
         }
-        _this.renderer.draw();
+        _this.project.renderer.draw();
     });
 }
 
-Rkns.Project.prototype.addNode = function(_props, _render_save) {
-    var _node = new Rkns.Model.Node(this, _props);
-    this.nodes.push(_node);
-    if (typeof _render_save !== "undefined" && (_render_save&Rkns._RENDER)) {
-        var _controller = this.renderer.addController("Node", _node);
-        _controller.redraw();
-    }
-    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
-        this.serializer.save();
-    }
-    return _node;
-}
-
-Rkns.Project.prototype.addEdge = function(_props, _render_save) {
-    var _edge = new Rkns.Model.Edge(this, _props);
-    this.edges.push(_edge);
-    if (typeof _render_save !== "undefined" && (_render_save&Rkns._RENDER)) {
-        var _controller = this.renderer.addController("Edge", _edge);
-        _controller.redraw();
-    }
-    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
-        this.serializer.save();
-    }
-    return _edge;
-}
-
-Rkns.Project.prototype.addUser = function(_props, _render_save) {
-    var _user = new Rkns.Model.User(this, _props);
-    this.users.push(_user);
-    return _user;
-}
-
-Rkns.Project.prototype.updateElement = function(_element, _props, _render_save) {
-    Rkns._(_props).each(function(_v, _k) {
-        _element[_k] = _v;
-    });
-    if (typeof _render_save !== "undefined" && (_render_save&Rkns._RENDER)) {
-        if (typeof _element.__controller !== "undefined") {
-            _element.__controller.redraw();
-        } else {
-            this._renderer.redraw();
-        }
-    }
-    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
-        this.serializer.save();
-    }
-}
-
-Rkns.Project.prototype.removeNode = function(_node, _render_save) {
-    this.nodes.removeId(_node.id);
-    if (typeof _node.__controller !== "undefined") {
-        this.renderer.removeController(_node.__controller);
-    }
-    var _this = this;
-    this.edges = this.edges.filter(function(_edge) {
-        var _keep = _edge.from !== _node && _edge.to !== _node;
-        if (!_keep && typeof _edge.__controller !== "undefined") {
-            _this.renderer.removeController(_edge.__controller);
-        }
-        return _keep;
-    });
-    if (typeof _render_save !== "undefined" && (_render_save&Rkns._RENDER)) {
-        this.renderer.redraw();
-    }
-    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
-        this.serializer.save();
-    }
-    return _node;
-}
-
-Rkns.Project.prototype.removeEdge = function(_edge, _render_save) {
-    this.edges.removeId(_edge.id);
-    if (typeof _edge.__controller !== "undefined") {
-        this.renderer.removeController(_edge.__controller);
-    }
-    if (typeof _render_save !== "undefined" && (_render_save&Rkns._RENDER)) {
-        this.renderer.redraw();
-    }
-    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
-        this.serializer.save();
-    }
-    return _edge;
-}
+Rkns.Renkan.prototype.template = Rkns._.template(
+    '<div class="Rk-Bins"></div><div class="Rk-Render"></div>'
+);
 
 /* Utility functions */
 
--- a/client/js/model.js	Thu Aug 16 17:24:13 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,202 +0,0 @@
-Rkns.Model = {}
-
-Rkns.Model._BaseElement = function(_project, _props) {
-    if (typeof _props !== "undefined") {
-        this._project = _project;
-        this.id = _props.id || Rkns.Utils.getUID(this.type);
-        this.title = _props.title || "(untitled " + this.type + ")";
-        this.description = _props.description || "";
-        this.uri = _props.uri || "";
-    }
-}
-
-Rkns.Model._BaseElement.prototype.addReference = function(_propName, _list, _id, _default) {
-    var _element = _list.getElement(_id);
-    if (typeof _element === "undefined" && typeof _default !== "undefined") {
-        this[ _propName ] = _default;
-        this[ _propName + "_id" ] = _default.id;
-    } else {
-        this[ _propName + "_id" ] = _id;
-        this[ _propName ] = _element;
-    }
-}
-
-Rkns.Model._BaseElement.prototype.updateGraphics = function() {
-    this._project.renderer.redraw();
-}
-
-Rkns.Model._BaseElement.prototype.updateData = function() {
-    this._project.serializer.save(this);
-}
-
-/* Element Class Generator */
-
-Rkns.Model._elementClass = function(_type) {
-    return Rkns.Utils.inherit(Rkns.Model._BaseElement, function() {
-        this.type = _type;
-    });
-}
-
-/* User Model */
-
-Rkns.Model.User = Rkns.Model._elementClass("user");
-
-Rkns.Model.User.prototype._init = function(_project, _props) {
-    this.color = _props.color || "#666666";
-}
-
-/* Node Model */
-
-Rkns.Model.Node = Rkns.Model._elementClass("node");
-
-Rkns.Model.Node.prototype._init = function(_project, _props) {
-    this.addReference("created_by", this._project.users, _props.created_by, _project.current_user);
-    this.position = _props.position;
-    this.description = _props.description || "";
-}
-
-Rkns.Model.Node.prototype.setPosition = function(_x, _y) {
-    if (typeof _x === "object") {
-        if (typeof _x.x !== "undefined" && typeof _x.y !== "undefined") {
-            this.position.x = _x.x;
-            this.position.y = _x.y;
-        } else {
-            if (typeof _x.length !== "undefined") {
-                this.position.x = _x[0];
-                this.position.y = _x[1];
-            }
-        }
-    } else {
-        if (typeof _y !== "undefined") {
-            this.position.x = +_x;
-            this.position.y = +_y;
-        }
-    }
-}
-
-/* Edge Model */
-
-Rkns.Model.Edge = Rkns.Model._elementClass("edge");
-
-Rkns.Model.Edge.prototype._init = function(_project, _props) {
-    this.addReference("created_by", this._project.users, _props.created_by, _project.current_user);
-    this.addReference("from", this._project.nodes, _props.from);
-    this.addReference("to", this._project.nodes, _props.to);
-}
-
-/* List Helper Functions -- See Metadataplayer */
-
-Rkns.Model.List = function() {
-    Array.call(this);
-    this.idIndex = [];
-}
-
-Rkns.Model.List.prototype = new Array();
-
-Rkns.Model.List.prototype.hasId = function(_id) {
-    return Rkns._(this.idIndex).include(_id);
-}
-
-Rkns.Model.List.prototype.getIds = function(_id) {
-    return this.idIndex;
-}
-
-/* On recent browsers, forEach and map are defined and do what we want.
- * Otherwise, we'll use the Underscore.js functions
- */
-if (typeof Array.prototype.forEach === "undefined") {
-    Rkns.Model.List.prototype.forEach = function(_callback) {
-        var _this = this;
-        Rkns._(this).forEach(function(_value, _key) {
-            _callback(_value, _key, _this);
-        });
-    }
-}
-
-if (typeof Array.prototype.map === "undefined") {
-    Rkns.Model.List.prototype.map = function(_callback) {
-        var _this = this;
-        return Rkns._(this).map(function(_value, _key) {
-            return _callback(_value, _key, _this);
-        });
-    }
-}
-
-/* We override Array's filter function because it doesn't return an Rkns.Model.List
- */
-Rkns.Model.List.prototype.filter = function(_callback) {
-    var _this = this,
-        _res = new Rkns.Model.List();
-    _res.addElements(Rkns._(this).filter(function(_value, _key) {
-        return _callback(_value, _key, _this);
-    }));
-    return _res;
-}
-
-Rkns.Model.List.prototype.slice = function(_start, _end) {
-    var _res = new Rkns.Model.List();
-    _res.addElements(Array.prototype.slice.call(this, _start, _end));
-    return _res;
-}
-
-Rkns.Model.List.prototype.splice = function(_start, _end) {
-    var _res = new Rkns.Model.List();
-    _res.addElements(Array.prototype.splice.call(this, _start, _end));
-    this.idIndex.splice(_start, _end);
-    return _res;
-}
-
-/* Array has a sort function, but it's not as interesting as Underscore.js's sortBy
- * and won't return a new Rkns.Model.List
- */
-Rkns.Model.List.prototype.sortBy = function(_callback) {
-    var _this = this,
-        _res = new Rkns.Model.List();
-    _res.addElements(Rkns._(this).sortBy(function(_value, _key) {
-        return _callback(_value, _key, _this);
-    }));
-    return _res;
-}
-
-Rkns.Model.List.prototype.push = function(_el) {
-    if (typeof _el === "undefined" || typeof _el.id === "undefined") {
-        return;
-    }
-    var _index = (Rkns._(this.idIndex).indexOf(_el.id));
-    if (_index === -1) {
-        this.idIndex.push(_el.id);
-        Array.prototype.push.call(this, _el);
-    } else {
-        this[_index] = _el;
-    }
-}
-
-Rkns.Model.List.prototype.addElements = function(_array) {
-    var _this = this;
-    Rkns._(_array).forEach(function(_el) {
-        _this.push(_el);
-    });
-}
-
-Rkns.Model.List.prototype.removeId = function(_id) {
-    var _index = (Rkns._(this.idIndex).indexOf(_id));
-    if (_index !== -1) {
-        this.splice(_index,1);
-    }
-}
-
-Rkns.Model.List.prototype.removeIds = function(_list) {
-    var _this = this;
-    Rkns._(_list).forEach(function(_id) {
-        _this.removeId(_id);
-    });
-}
-
-Rkns.Model.List.prototype.getElement = function(_id) {
-    var _index = Rkns._(this.idIndex).indexOf(_id);
-    if (_index === -1) {
-        return undefined;
-    } else {
-        return this[_index];
-    }
-}
--- a/client/js/paper-renderer.js	Thu Aug 16 17:24:13 2012 +0200
+++ b/client/js/paper-renderer.js	Fri Aug 17 12:50:00 2012 +0200
@@ -164,7 +164,7 @@
 
 Rkns.Renderer.Node.prototype._init = function() {
     this._renderer.node_layer.activate();
-    this.type = "node";
+    this.type = "Node";
     this.circle = new paper.Path.Circle([0, 0], Rkns._NODE_RADIUS);
     this.circle.fillColor = '#ffffff';
     this.circle.__controller = this;
@@ -203,7 +203,7 @@
                 y: _coords.y
             }
         };
-    this._project.updateElement(this._element, _data, Rkns._SAVE);
+    this._project.updateElement(this.type, this._element, _data, Rkns._SAVE);
     this._renderer.redraw();
 }
 
@@ -249,7 +249,7 @@
 
 Rkns.Renderer.Edge.prototype._init = function() {
     this._renderer.edge_layer.activate();
-    this.type = "edge";
+    this.type = "Edge";
     this.from_controller = this._element.from.__controller;
     this.to_node_controller = this._element.to.__controller;
     this.line = new paper.Path();
@@ -388,7 +388,7 @@
     var _hitResult = paper.project.hitTest(_event.point);
     if (_hitResult && typeof _hitResult.item.__controller !== "undefined") {
         var _target = _hitResult.item.__controller;
-        if (_target.type === "node" && this.from_controller._element.id !== _target._element.id) {
+        if (_target.type === "Node" && this.from_controller._element.id !== _target._element.id) {
             this._project.addEdge({
                 from: this.from_controller._element.id,
                 to: _target._element.id
@@ -456,7 +456,7 @@
             description: _this.editor_$.find(".Rk-Edit-Description").val(),
             uri: _this.editor_$.find(".Rk-Edit-URI").val()
         }
-        _this._project.updateElement(
+        _this._project.updateNode(
             _element,
             _data,
             Rkns._SAVE
@@ -525,7 +525,7 @@
             title: _this.editor_$.find(".Rk-Edit-Title").val(),
             uri: _this.editor_$.find(".Rk-Edit-URI").val()
         }
-        _this._project.updateElement(
+        _this._project.updateEdge(
             _element,
             _data,
             Rkns._SAVE
@@ -546,7 +546,7 @@
 
 Rkns.Renderer.NodeEditButton.prototype._init = function() {
     this._renderer.node_layer.activate();
-    this.type = "node-edit-button";
+    this.type = "Node-edit-button";
     this.sector = Rkns.Renderer.Utils.sector(this, 1 + Rkns._NODE_RADIUS, 3 * Rkns._NODE_RADIUS, - 90, 30, 2, 'img/edit.png');
 }
 
@@ -589,7 +589,7 @@
 
 Rkns.Renderer.NodeRemoveButton.prototype._init = function() {
     this._renderer.node_layer.activate();
-    this.type = "node-remove-button";
+    this.type = "Node-remove-button";
     this.sector = Rkns.Renderer.Utils.sector(this, 1 + Rkns._NODE_RADIUS, 3 * Rkns._NODE_RADIUS, -210, -90, 2, 'img/remove.png');
 }
 
@@ -632,7 +632,7 @@
 
 Rkns.Renderer.NodeLinkButton.prototype._init = function() {
     this._renderer.node_layer.activate();
-    this.type = "node-link-button";
+    this.type = "Node-link-button";
     this.sector = Rkns.Renderer.Utils.sector(this, 1 + Rkns._NODE_RADIUS , 3 * Rkns._NODE_RADIUS, 30, 150, 2, 'img/link.png');
 }
 
@@ -669,7 +669,7 @@
 
 Rkns.Renderer.EdgeEditButton.prototype._init = function() {
     this._renderer.edge_layer.activate();
-    this.type = "edge-edit-button";
+    this.type = "Edge-edit-button";
     this.sector = Rkns.Renderer.Utils.sector(this, 5 , 2 * Rkns._NODE_RADIUS, - 60, 60, 2, 'img/edit.png');
 }
 
@@ -711,7 +711,7 @@
 
 Rkns.Renderer.EdgeRemoveButton.prototype._init = function() {
     this._renderer.edge_layer.activate();
-    this.type = "edge-remove-button";
+    this.type = "Edge-remove-button";
     this.sector = Rkns.Renderer.Utils.sector(this, 5, 2 * Rkns._NODE_RADIUS, - 240, -120, 2, 'img/remove.png');
 }
 Rkns.Renderer.EdgeRemoveButton.prototype.moveTo = function(_pos) {
@@ -752,13 +752,13 @@
     this._project = _project;
     this._MARGIN_X = 80;
     this._MARGIN_Y = 50;
-    var _canvas_id = this._project._opts.canvas_id;
-    this.$ = Rkns.$("#"+_canvas_id);
-    this.editor_$ = Rkns.$(".Rk-Editor");
-    this.editor_$.html(this.editorTemplate({
+    this.$ = Rkns.$(".Rk-Render");
+    this.$.html(this.template({
         l10n: this._project.l10n
-    }));
-    paper.setup(document.getElementById(_canvas_id));
+    }))
+    this.canvas_$ = this.$.find(".Rk-Canvas");
+    this.editor_$ = this.$.find(".Rk-Editor");
+    paper.setup(this.canvas_$[0]);
     this.scale = 1;
     this.offset = paper.view.center;
     this.totalScroll = 0;
@@ -782,24 +782,24 @@
     _tool.onMouseUp = function(_event) {
         _this.onMouseUp(_event);
     }
-    this.$.mousewheel(function(_event, _delta) {
+    this.canvas_$.mousewheel(function(_event, _delta) {
         _this.onScroll(_event, _delta);
     })
-    this.$.dblclick(function(_event) {
+    this.canvas_$.dblclick(function(_event) {
         _this.onDoubleClick(_event);
     });
     this.editor_$.find(".Rk-ZoomOut").click(function() {
         _this.offset = new paper.Point([
-            _this.$.width(),
-            _this.$.height()
+            _this.canvas_$.width(),
+            _this.canvas_$.height()
         ]).multiply( .5 * ( 1 - Math.SQRT1_2 ) ).add(_this.offset.multiply( Math.SQRT1_2 ));
         _this.scale *= Math.SQRT1_2;
         _this.redraw();
     });
     this.editor_$.find(".Rk-ZoomIn").click(function() {
         _this.offset = new paper.Point([
-            _this.$.width(),
-            _this.$.height()
+            _this.canvas_$.width(),
+            _this.canvas_$.height()
         ]).multiply( .5 * ( 1 - Math.SQRT2 ) ).add(_this.offset.multiply( Math.SQRT2 ));
         _this.scale *= Math.SQRT2;
         _this.redraw();
@@ -810,8 +810,10 @@
     }
 }
 
-Rkns.Renderer.Scene.prototype.editorTemplate = Rkns._.template(
-    '<div class="Rk-ZoomButtons"><div class="Rk-ZoomIn" title="<%=l10n.zoom_in%>"></div><div class="Rk-ZoomOut" title="<%=l10n.zoom_out%>"></div></div>'
+Rkns.Renderer.Scene.prototype.template = Rkns._.template(
+    '<canvas class="Rk-Canvas" resize></canvas><div class="Rk-Editor">'
+    + '<div class="Rk-ZoomButtons"><div class="Rk-ZoomIn" title="<%=l10n.zoom_in%>"></div><div class="Rk-ZoomOut" title="<%=l10n.zoom_out%>"></div></div>'
+    + '</div>'
 );
 
 Rkns.Renderer.Scene.prototype.toPaperCoords = function(_point) {
@@ -833,7 +835,7 @@
         _maxy = Math.max.apply(Math, _yy);
     this.scale = Math.min((paper.view.size.width - 2 * this._MARGIN_X) / (_maxx - _minx), (paper.view.size.height - 2 * this._MARGIN_Y) / (_maxy - _miny));
     this.offset = paper.view.center.subtract(new paper.Point([(_maxx + _minx) / 2, (_maxy + _miny) / 2]).multiply(this.scale));
-    this.controllers = new Rkns.Model.List();
+    this.controllers = new Rkns.ViewModel.List();
     this._project.nodes.forEach(function(_node) {
         _this.addController("Node", _node);
     });
@@ -904,10 +906,10 @@
     var _hitResult = paper.project.hitTest(_event.point);
     if (_hitResult && typeof _hitResult.item.__controller !== "undefined") {
         this.click_target = _hitResult.item.__controller;
-        if (this.click_target.type === "node" && _hitResult.type === "stroke") {
+        if (this.click_target.type === "Node" && _hitResult.type === "stroke") {
             this.addTempEdge(this.click_target, _event.point);
         }
-        if (this.click_target.type === "node-link-button") {
+        if (this.click_target.type === "Node-link-button") {
             this.addTempEdge(this.click_target.node_controller, _event.point);
         }
     } else {
@@ -936,7 +938,7 @@
 Rkns.Renderer.Scene.prototype.onScroll = function(_event, _scrolldelta) {
     this.totalScroll += _scrolldelta;
     if (Math.abs(this.totalScroll) >= 1) {
-        var _off = this.$.offset(),
+        var _off = this.canvas_$.offset(),
             _delta = new paper.Point([
                 _event.pageX - _off.left,
                 _event.pageY - _off.top
@@ -954,7 +956,7 @@
 }
 
 Rkns.Renderer.Scene.prototype.onDoubleClick = function(_event) {
-    var _off = this.$.offset(),
+    var _off = this.canvas_$.offset(),
         _point = new paper.Point([
             _event.pageX - _off.left,
             _event.pageY - _off.top
--- a/client/js/random-data.js	Thu Aug 16 17:24:13 2012 +0200
+++ b/client/js/random-data.js	Fri Aug 17 12:50:00 2012 +0200
@@ -1,6 +1,6 @@
-Rkns.Serializers.RandomData = Rkns.Utils.inherit(Rkns.Serializers._Base);
+Rkns.RemoteModels.RandomData = Rkns.Utils.inherit(Rkns.RemoteModels._Base);
 
-Rkns.Serializers.RandomData.prototype._init = function() {
+Rkns.RemoteModels.RandomData.prototype._init = function() {
     this._USER_COUNT = 5;
     this._NODE_COUNT = 20;
     this._EDGE_COUNT = 40;
@@ -8,18 +8,18 @@
     this.load();
 }
 
-Rkns.Serializers.RandomData.prototype.load = function() {
+Rkns.RemoteModels.RandomData.prototype.load = function() {
     var _p = this._project;
     _p.title = "Random Generated Data";
     for (var i = 0; i < this._USER_COUNT; i++) {
-        _p.users.push(new Rkns.Model.User(_p, {
+        _p.users.push(new Rkns.ViewModel.User(_p, {
             id: "user-"+i,
             title: "User #"+(1+i),
             color: this.user_colors[i]
         }));
     }
     for (var i = 0; i < this._NODE_COUNT; i++) {
-        _p.nodes.push(new Rkns.Model.Node(_p, {
+        _p.nodes.push(new Rkns.ViewModel.Node(_p, {
             id: "node-"+i,
             title: "Node #"+(1+i),
             created_by: "user-" + Math.floor(this._USER_COUNT*Math.random()),
@@ -37,7 +37,7 @@
         while(_from === _to) {
             _to = Math.floor(this._NODE_COUNT*Math.random());
         }
-        _p.edges.push(new Rkns.Model.Edge(_p, {
+        _p.edges.push(new Rkns.ViewModel.Edge(_p, {
             id: "edge-"+i,
             title: "Edge #"+(1+i),
             created_by: "user-" + Math.floor(this._USER_COUNT*Math.random()),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/js/view-model.js	Fri Aug 17 12:50:00 2012 +0200
@@ -0,0 +1,310 @@
+/* Defines the View Model */
+
+Rkns.ViewModel = {}
+
+/* Project Class */
+
+Rkns.ViewModel.Project = function() {
+    this.users = new Rkns.ViewModel.List();
+    this.nodes = new Rkns.ViewModel.List();
+    this.edges = new Rkns.ViewModel.List();
+}
+
+Rkns.ViewModel.Project.prototype.addNode = function(_props, _render_save) {
+    var _node = new Rkns.ViewModel.Node(this, _props);
+    this.nodes.push(_node);
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._RENDER)) {
+        var _controller = this.renderer.addController("Node", _node);
+        _controller.redraw();
+    }
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
+        this.remotemodel.addNode(_node);
+    }
+    return _node;
+}
+
+Rkns.ViewModel.Project.prototype.addEdge = function(_props, _render_save) {
+    var _edge = new Rkns.ViewModel.Edge(this, _props);
+    this.edges.push(_edge);
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._RENDER)) {
+        var _controller = this.renderer.addController("Edge", _edge);
+        _controller.redraw();
+    }
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
+        this.remotemodel.addEdge(_edge);
+    }
+    return _edge;
+}
+
+Rkns.ViewModel.Project.prototype.addUser = function(_props, _render_save) {
+    var _user = new Rkns.ViewModel.User(this, _props);
+    this.users.push(_user);
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
+        this.remotemodel.addUser(_user);
+    }
+    return _user;
+}
+
+
+Rkns.ViewModel.Project.prototype.updateNode = function(_element, _props, _render_save) {
+    this.updateElement("Node", _element, _props, _render_save)
+}
+
+Rkns.ViewModel.Project.prototype.updateEdge = function(_element, _props, _render_save) {
+    this.updateElement("Edge", _element, _props, _render_save)
+}
+
+Rkns.ViewModel.Project.prototype.updateElement = function(_type, _element, _props, _render_save) {
+    Rkns._(_props).each(function(_v, _k) {
+        _element[_k] = _v;
+    });
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._RENDER)) {
+        if (typeof _element.__controller !== "undefined") {
+            _element.__controller.redraw();
+        } else {
+            this._renderer.redraw();
+        }
+    }
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
+        this.remotemodel[ "update" + _type ](_element);
+    }
+}
+
+Rkns.ViewModel.Project.prototype.removeNode = function(_node, _render_save) {
+    this.nodes.removeId(_node.id);
+    if (typeof _node.__controller !== "undefined") {
+        this.renderer.removeController(_node.__controller);
+    }
+    var _this = this;
+    this.edges = this.edges.filter(function(_edge) {
+        var _keep = _edge.from !== _node && _edge.to !== _node;
+        if (!_keep && typeof _edge.__controller !== "undefined") {
+            _this.renderer.removeController(_edge.__controller);
+        }
+        return _keep;
+    });
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._RENDER)) {
+        this.renderer.redraw();
+    }
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
+        this.remotemodel.removeNode(_node.id);
+    }
+    return _node;
+}
+
+Rkns.ViewModel.Project.prototype.removeEdge = function(_edge, _render_save) {
+    this.edges.removeId(_edge.id);
+    if (typeof _edge.__controller !== "undefined") {
+        this.renderer.removeController(_edge.__controller);
+    }
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._RENDER)) {
+        this.renderer.redraw();
+    }
+    if (typeof _render_save !== "undefined" && (_render_save&Rkns._SAVE)) {
+        this.remotemodel.removeEdge(_edge.id);
+    }
+    return _edge;
+}
+
+/* Base Element */
+
+Rkns.ViewModel._BaseElement = function(_project, _props) {
+    if (typeof _props !== "undefined") {
+        this._project = _project;
+        this.id = _props.id || Rkns.Utils.getUID(this.type);
+        this.title = _props.title || "(untitled " + this.type + ")";
+        this.description = _props.description || "";
+        this.uri = _props.uri || "";
+    }
+}
+
+Rkns.ViewModel._BaseElement.prototype.addReference = function(_propName, _list, _id, _default) {
+    var _element = _list.getElement(_id);
+    if (typeof _element === "undefined" && typeof _default !== "undefined") {
+        this[ _propName ] = _default;
+        this[ _propName + "_id" ] = _default.id;
+    } else {
+        this[ _propName + "_id" ] = _id;
+        this[ _propName ] = _element;
+    }
+}
+
+Rkns.ViewModel._BaseElement.prototype.updateGraphics = function() {
+    this._project.renderer.redraw();
+}
+
+Rkns.ViewModel._BaseElement.prototype.updateData = function() {
+    this._project.remotemodel.save(this);
+}
+
+/* Element Class Generator */
+
+Rkns.ViewModel._elementClass = function(_type) {
+    return Rkns.Utils.inherit(Rkns.ViewModel._BaseElement, function() {
+        this.type = _type;
+    });
+}
+
+/* User Model */
+
+Rkns.ViewModel.User = Rkns.ViewModel._elementClass("user");
+
+Rkns.ViewModel.User.prototype._init = function(_project, _props) {
+    this.color = _props.color || "#666666";
+}
+
+/* Node Model */
+
+Rkns.ViewModel.Node = Rkns.ViewModel._elementClass("node");
+
+Rkns.ViewModel.Node.prototype._init = function(_project, _props) {
+    this.addReference("created_by", this._project.users, _props.created_by, _project.current_user);
+    this.position = _props.position;
+    this.description = _props.description || "";
+}
+
+Rkns.ViewModel.Node.prototype.setPosition = function(_x, _y) {
+    if (typeof _x === "object") {
+        if (typeof _x.x !== "undefined" && typeof _x.y !== "undefined") {
+            this.position.x = _x.x;
+            this.position.y = _x.y;
+        } else {
+            if (typeof _x.length !== "undefined") {
+                this.position.x = _x[0];
+                this.position.y = _x[1];
+            }
+        }
+    } else {
+        if (typeof _y !== "undefined") {
+            this.position.x = +_x;
+            this.position.y = +_y;
+        }
+    }
+}
+
+/* Edge Model */
+
+Rkns.ViewModel.Edge = Rkns.ViewModel._elementClass("edge");
+
+Rkns.ViewModel.Edge.prototype._init = function(_project, _props) {
+    this.addReference("created_by", this._project.users, _props.created_by, _project.current_user);
+    this.addReference("from", this._project.nodes, _props.from);
+    this.addReference("to", this._project.nodes, _props.to);
+}
+
+/* List Helper Functions -- See Metadataplayer */
+
+Rkns.ViewModel.List = function() {
+    Array.call(this);
+    this.idIndex = [];
+}
+
+Rkns.ViewModel.List.prototype = new Array();
+
+Rkns.ViewModel.List.prototype.hasId = function(_id) {
+    return Rkns._(this.idIndex).include(_id);
+}
+
+Rkns.ViewModel.List.prototype.getIds = function(_id) {
+    return this.idIndex;
+}
+
+/* On recent browsers, forEach and map are defined and do what we want.
+ * Otherwise, we'll use the Underscore.js functions
+ */
+if (typeof Array.prototype.forEach === "undefined") {
+    Rkns.ViewModel.List.prototype.forEach = function(_callback) {
+        var _this = this;
+        Rkns._(this).forEach(function(_value, _key) {
+            _callback(_value, _key, _this);
+        });
+    }
+}
+
+if (typeof Array.prototype.map === "undefined") {
+    Rkns.ViewModel.List.prototype.map = function(_callback) {
+        var _this = this;
+        return Rkns._(this).map(function(_value, _key) {
+            return _callback(_value, _key, _this);
+        });
+    }
+}
+
+/* We override Array's filter function because it doesn't return an Rkns.ViewModel.List
+ */
+Rkns.ViewModel.List.prototype.filter = function(_callback) {
+    var _this = this,
+        _res = new Rkns.ViewModel.List();
+    _res.addElements(Rkns._(this).filter(function(_value, _key) {
+        return _callback(_value, _key, _this);
+    }));
+    return _res;
+}
+
+Rkns.ViewModel.List.prototype.slice = function(_start, _end) {
+    var _res = new Rkns.ViewModel.List();
+    _res.addElements(Array.prototype.slice.call(this, _start, _end));
+    return _res;
+}
+
+Rkns.ViewModel.List.prototype.splice = function(_start, _end) {
+    var _res = new Rkns.ViewModel.List();
+    _res.addElements(Array.prototype.splice.call(this, _start, _end));
+    this.idIndex.splice(_start, _end);
+    return _res;
+}
+
+/* Array has a sort function, but it's not as interesting as Underscore.js's sortBy
+ * and won't return a new Rkns.ViewModel.List
+ */
+Rkns.ViewModel.List.prototype.sortBy = function(_callback) {
+    var _this = this,
+        _res = new Rkns.ViewModel.List();
+    _res.addElements(Rkns._(this).sortBy(function(_value, _key) {
+        return _callback(_value, _key, _this);
+    }));
+    return _res;
+}
+
+Rkns.ViewModel.List.prototype.push = function(_el) {
+    if (typeof _el === "undefined" || typeof _el.id === "undefined") {
+        return;
+    }
+    var _index = (Rkns._(this.idIndex).indexOf(_el.id));
+    if (_index === -1) {
+        this.idIndex.push(_el.id);
+        Array.prototype.push.call(this, _el);
+    } else {
+        this[_index] = _el;
+    }
+}
+
+Rkns.ViewModel.List.prototype.addElements = function(_array) {
+    var _this = this;
+    Rkns._(_array).forEach(function(_el) {
+        _this.push(_el);
+    });
+}
+
+Rkns.ViewModel.List.prototype.removeId = function(_id) {
+    var _index = (Rkns._(this.idIndex).indexOf(_id));
+    if (_index !== -1) {
+        this.splice(_index,1);
+    }
+}
+
+Rkns.ViewModel.List.prototype.removeIds = function(_list) {
+    var _this = this;
+    Rkns._(_list).forEach(function(_id) {
+        _this.removeId(_id);
+    });
+}
+
+Rkns.ViewModel.List.prototype.getElement = function(_id) {
+    var _index = Rkns._(this.idIndex).indexOf(_id);
+    if (_index === -1) {
+        return undefined;
+    } else {
+        return this[_index];
+    }
+}
--- a/client/render-test.html	Thu Aug 16 17:24:13 2012 +0200
+++ b/client/render-test.html	Fri Aug 17 12:50:00 2012 +0200
@@ -12,17 +12,16 @@
         <script src="lib/paper.js"></script>
         <script src="js/main.js"></script>
         <script src="js/i18n.js"></script>
-        <script src="js/model.js"></script>
-        <script src="js/json-serializer.js"></script>
-        <script src="js/random-data.js"></script>
+        <script src="js/view-model.js"></script>
+        <script src="js/full-json.js"></script>
+        <script src="js/twitter-bin.js"></script>
         <script src="js/paper-renderer.js"></script>
         <script type="text/javascript">
             var _proj;
             $(function() {
-                _proj = new Rkns.Project({
+                _proj = new Rkns.Renkan({
                     url: "data/simple-persist.php",
-                    //serializer: "RandomData",
-                    canvas_id: "renkanvas",
+                    remotemodel: "FullJson",
                     user: {
                         id: "anonymous",
                         title: "anonymous",
@@ -34,24 +33,15 @@
         <link rel="stylesheet" href="css/renkan.css" />
         <style type="text/css">
             #topbar { position: absolute; left: 0; top: 0; right: 0; height: 40px; background: #f0f0f0; font-size: 20px; color: #a0a0a0; }
-            #leftbar { position: absolute; left: 0; top: 40px; width: 200px; bottom: 0; background: #d0d0d0; font-size: 20px; color: #ffffff; }
-            #renkan-container { position: absolute; top: 40px; left: 200px; right: 0; bottom: 0; overflow: hidden; }
-            #renkanvas { position: absolute; top: 0; left: 0; right: 0; bottom: 0; }
-            .Rk-Editor { position: absolute; top: 0; left: 0; }
+            #renkan {
+                position: absolute; left: 0; top: 40px; width: 200px; bottom: 0;
+            }
         </style>
     </head>
 
     <body>
         <div id="topbar">Renkan Top Bar</div>
-        <div id="bottompart">
-            <div id="leftbar">Renkan "Stock Box"</div>
-            <div id="renkan-container">
-                <canvas id="renkanvas" resize></canvas>
-                <div class="Rk-Editor">
-<!--                    <div class="Rk-Button-Edit"></div>-->
-                </div>
-            </div>
-        </div>
+        <div id="renkan"></div>
         
     </body>
 </html>