Skip to content

Instantly share code, notes, and snippets.

@zulfajuniadi
Created October 15, 2014 14:17
Show Gist options
  • Save zulfajuniadi/bd95fe5e047cb9aa01c7 to your computer and use it in GitHub Desktop.
Save zulfajuniadi/bd95fe5e047cb9aa01c7 to your computer and use it in GitHub Desktop.
Backbone VirtualDOM using fiduswriter/diffDOM
/*
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>VirtualDOM Example</title>
</head>
<body>
<div id="output"></div>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone-min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/backbone-localstorage.js/1.1.13/backbone.localStorage-min.js"></script>
<script type="text/javascript" src="app.js"></script>
</body>
</html>
*/
;(function(){
/*
* fiduswriter/diffDOM Source Available under LGPL-V3 at https://github.com/fiduswriter/diffDOM
* LGPL-V3 can be viewed at: http://opensource.org/licenses/lgpl-3.0.html
* fiduswriter/diffDOM diffDOM.js Source Minified on 8-OCT-2014
*/
;!function(){function R(a,b,c){!function(d){a[b]=a[c],a[c]=d}(a[b])}var a,b=0,c=1,d=2,e=3,f=4,g=5,h=6,i=7,j=8,k=9,l=10,m=11,n=12,o=13,p=14,q=15,r=16,s=17,t=18,u=19,v=20,w=21,x=22,y=23,z=24,A=25,B=26,C=27,D=28,E=29,F=30,G=function(a){var b=this;Object.keys(a).forEach(function(c){b[c]=a[c]})};G.prototype={toString:function(){return JSON.stringify(this)}};var H=function(a,b){this.old=a,this["new"]=b};H.prototype={contains:function(a){return a.length<this.length?a["new"]>=this["new"]&&a["new"]<this["new"]+this.length:!1},toString:function(){return this.length+" element subset, first mapping: old "+this.old+" \u2192 new "+this["new"]}};var I=function X(a,b,c){if(!a||!b)return!1;if(a.nodeType!==b.nodeType)return!1;if(3===a.nodeType)return 3!==b.nodeType?!1:c?!0:a.data===b.data;if(a.nodeName!==b.nodeName)return!1;if(a.childNodes.length!==b.childNodes.length)return!1;for(var d=!0,e=a.childNodes.length-1;e>=0;e--)d=c?d&&a.childNodes[e].nodeName===b.childNodes[e].nodeName:d&&X(a.childNodes[e],b.childNodes[e],!0);return d},J=function(a){var c,d,e,f,g,b=a.cloneNode(!0);if(8!=a.nodeType&&3!=a.nodeType){for(c=a.querySelectorAll("textarea"),d=b.querySelectorAll("textarea"),g=0;g<c.length;g++)d[g].value!==c[g].value&&(d[g].value=c[g].value);for(a.value&&a.value!==b.value&&(b.value=a.value),e=a.querySelectorAll("option"),f=b.querySelectorAll("option"),g=0;g<e.length;g++)e[g].selected&&!f[g].selected?f[g].selected=!0:!e[g].selected&&f[g].selected&&(f[g].selected=!1);a.selected&&!b.selected?b.selected=!0:!a.selected&&b.selected&&(b.selected=!1)}return b},K=function(a){var c,b={};if(3===a.nodeType)b[z]=a.data;else if(8===a.nodeType)b[C]=a.data;else{if(b[B]=a.nodeName,a.attributes&&a.attributes.length>0)for(b[A]=[],c=0;c<a.attributes.length;c++)b[A].push([a.attributes[c].name,a.attributes[c].value]);if(a.childNodes&&a.childNodes.length>0)for(b[D]=[],c=0;c<a.childNodes.length;c++)b[D].push(K(a.childNodes[c]));a.value&&(b[y]=a.value),a.checked&&(b[E]=a.checked),a.selected&&(b[F]=a.selected)}return b},L=function(a,b){var c,d;if(a.hasOwnProperty(z))c=document.createTextNode(a[z]);else if(a.hasOwnProperty(C))c=document.createComment(a[C]);else{if("svg"===a[B]||b?(c=document.createElementNS("http://www.w3.org/2000/svg",a[B]),b=!0):c=document.createElement(a[B]),a[A])for(d=0;d<a[A].length;d++)c.setAttribute(a[A][d][0],a[A][d][1]);if(a[D])for(d=0;d<a[D].length;d++)c.appendChild(L(a[D][d],b));a[y]&&(c.value=a[y]),a[E]&&(c.checked=a[E]),a[F]&&(c.selected=a[F])}return c},M=function(a,b,c,d){var j,k,l,e=0,f=[],g=a.length,h=b.length,i=[];for(j=0;g+1>j;j++)i[j]=[];for(k=0;g>k;k++)for(l=0;h>l;l++)c[k]||d[l]||!I(a[k],b[l])?i[k+1][l+1]=0:(i[k+1][l+1]=i[k][l]?i[k][l]+1:1,i[k+1][l+1]>e&&(e=i[k+1][l+1],f=[k+1,l+1]));if(0===e)return!1;var m=[f[0]-e,f[1]-e],n=new H(m[0],m[1]);return n.length=e,n},N=function(a,b){var c=function(a){a.slice();for(var b=0,d=a.length;d>b;b++)a[b]instanceof Array&&(a[b]=c(a[b]))};b instanceof Array&&(b=c(b));var d=function(){return b};return new Array(a).join(".").split(".").map(d)},O=function(a,b,c){var e=N(a.childNodes.length,!0),f=N(b.childNodes.length,!0),g=0;return c.forEach(function(a){var b,c;for(b=a.old,c=b+a.length;c>b;b++)e[b]=g;for(b=a["new"],c=b+a.length;c>b;b++)f[b]=g;g++}),{gaps1:e,gaps2:f}},P=function(a,b){a=J(a),b=J(b);for(var i,c=a.childNodes,d=b.childNodes,e=N(c.length,!1),f=N(d.length,!1),g=[],h=!0;h;)if(h=M(c,d,e,f)){for(g.push(h),i=0;i<h.length;i++)e[h.old+i]=!0;for(i=0;i<h.length;i++)f[h.new+i]=!0}return g},Q=function(a,b,c,d){if(0===c.length)return!1;var x,z,A,D,E,F,H,k=O(a,b,c),l=k.gaps1,m=l.length,n=k.gaps2,o=l.length,B=o>m?m:o,I=o>m?l:n;for(x=0,B=I.length;B>x;x++){if(l[x]===!0){if(E=a.childNodes[x],3===E.nodeType){if(3===b.childNodes[x].nodeType&&E.data!=b.childNodes[x].data){for(H=E;H.nextSibling&&3===H.nextSibling.nodeType;)if(H=H.nextSibling,b.childNodes[x].data===H.data){F=!0;break}if(!F)return A={},A[p]=e,A[q]=d.concat(x),A[r]=E.data,A[s]=b.childNodes[x].data,new G(A)}return A={},A[p]=i,A[q]=d.concat(x),A[y]=E.data,new G(A)}return A={},A[p]=g,A[q]=d.concat(x),A[t]=K(E),new G(A)}if(n[x]===!0)return E=b.childNodes[x],3===E.nodeType?(A={},A[p]=j,A[q]=d.concat(x),A[y]=E.data,new G(A)):(A={},A[p]=h,A[q]=d.concat(x),A[t]=K(E),new G(A));if(l[x]!=n[x]){D=c[l[x]];var J=Math.min(D["new"],a.childNodes.length-D.length);if(J!=x){var L=!1;for(z=0;z<D.length;z++)a.childNodes[J+z].isEqualNode(a.childNodes[x+z])||(L=!0);if(L)return A={},A[p]=f,A[u]=D,A[v]=x,A[w]=J,A[q]=d,new G(A)}}}return!1},S=function(){this.list=[]};S.prototype={list:!1,add:function(a){var b=this.list;a.forEach(function(a){b.push(a)})},forEach:function(a){this.list.forEach(a)}};var T=function(a,o){"undefined"==typeof a?a=!1:(b="add attribute",c="modify attribute",d="remove attribute",e="modify text element",f="relocate group",g="remove element",h="add element",i="remove text element",j="add text element",k="replace element",l="modify value",m="modify checked",n="modify selected",p="action",q="route",r="oldValue",s="newValue",t="element",u="group",v="from",w="to",x="name",y="value",z="text",A="attributes",B="nodeName",C="comment",D="childNodes",E="checked",F="selected"),"undefined"==typeof o&&(o=10),this.debug=a,this.diffcap=o};T.prototype={diff:function(b,c){return a=0,b=J(b),c=J(c),this.debug&&(this.t1Orig=K(b),this.t2Orig=K(c)),this.tracker=new S,this.findDiffs(b,c)},findDiffs:function(b,c){do{if(this.debug&&(a++,a>this.diffcap))throw window.diffError=[this.t1Orig,this.t2Orig],new Error("surpassed diffcap:"+JSON.stringify(this.t1Orig)+" -> "+JSON.stringify(this.t2Orig));difflist=this.findFirstDiff(b,c,[]),difflist&&(difflist.length||(difflist=[difflist]),this.tracker.add(difflist),this.apply(b,difflist))}while(difflist);return this.tracker.list},findFirstDiff:function(a,b,c){var d=this.findOuterDiff(a,b,c);if(d.length>0)return d;var e=this.findInnerDiff(a,b,c);return e&&("undefined"==typeof e.length&&(e=[e]),e.length>0)?e:!1},findOuterDiff:function(a,e,f){var g;if(a.nodeName!=e.nodeName)return g={},g[p]=k,g[r]=K(a),g[s]=K(e),g[q]=f,[new G(g)];var h=Array.prototype.slice,i=function(a,b){return a.name>b.name},j=a.attributes?h.call(a.attributes).sort(i):[],t=e.attributes?h.call(e.attributes).sort(i):[],u=function(a,b){for(var c=0,d=b.length;d>c;c++)if(b[c].name===a.name)return c;return-1},v=[];if((a.value||e.value)&&a.value!==e.value&&"OPTION"!==a.nodeName&&(g={},g[p]=l,g[r]=a.value,g[s]=e.value,g[q]=f,v.push(new G(g))),(a.checked||e.checked)&&a.checked!==e.checked&&(g={},g[p]=m,g[r]=a.checked,g[s]=e.checked,g[q]=f,v.push(new G(g))),j.forEach(function(a){var e,b=u(a,t);if(-1===b)return e={},e[p]=d,e[q]=f,e[x]=a.name,e[y]=a.value,v.push(new G(e)),v;var g=t.splice(b,1)[0];a.value!==g.value&&(e={},e[p]=c,e[q]=f,e[x]=a.name,e[r]=a.value,e[s]=g.value,v.push(new G(e)))}),a.attributes||a.data===e.data||(g={},g[p]=o,g[q]=f,g[r]=a.data,g[s]=e.data,v.push(new G(g))),v.length>0)return v;if(t.forEach(function(a){var c;c={},c[p]=b,c[q]=f,c[x]=a.name,c[y]=a.value,v.push(new G(c))}),(a.selected||e.selected)&&a.selected!==e.selected){if(v.length>0)return v;g={},g[p]=n,g[r]=a.selected,g[s]=e.selected,g[q]=f,v.push(new G(g))}return v},findInnerDiff:function(a,b,c){var k,d=P(a,b),f=d.length;if(0===f&&3===a.nodeType&&3===b.nodeType&&a.data!==b.data)return k={},k[p]=e,k[r]=a.data,k[s]=b.data,k[q]=c,new G(k);if(2>f){var l,m,n,o,u,v;for(n=0,o=Math.max(a.childNodes.length,b.childNodes.length);o>n;n++){if(u=a.childNodes[n],v=b.childNodes[n],u&&!v)return 3===u.nodeType?(k={},k[p]=i,k[q]=c.concat(n),k[y]=u.data,new G(k)):(k={},k[p]=g,k[q]=c.concat(n),k[t]=K(u),new G(k));if(v&&!u)return 3===v.nodeType?(k={},k[p]=j,k[q]=c.concat(n),k[y]=v.data,new G(k)):(k={},k[p]=h,k[q]=c.concat(n),k[t]=K(v),new G(k));if((3!=u.nodeType||3!=v.nodeType)&&(m=this.findOuterDiff(u,v,c.concat(n)),m.length>0))return m;if(l=this.findInnerDiff(u,v,c.concat(n)))return l}}return this.findFirstInnerDiff(a,b,d,c)},findFirstInnerDiff:Q,apply:function(a,b){var c=this;return"undefined"==typeof b.length&&(b=[b]),0===b.length?!0:(b.forEach(function(b){return c.applyDiff(a,b)?void 0:!1}),!0)},getFromRoute:function(a,b){b=b.slice();for(var c,d=a;b.length>0;){if(!d.childNodes)return!1;c=b.splice(0,1)[0],d=d.childNodes[c]}return d},textDiff:function(a,b,c,d){a.data=d},applyDiff:function(a,z){var A=this.getFromRoute(a,z[q]);if(z[p]===b){if(!A||!A.setAttribute)return!1;A.setAttribute(z[x],z[y])}else if(z[p]===c){if(!A||!A.setAttribute)return!1;A.setAttribute(z[x],z[s])}else if(z[p]===d){if(!A||!A.removeAttribute)return!1;A.removeAttribute(z[x])}else if(z[p]===l){if(!A||"undefined"==typeof A.value)return!1;A.value=z[s]}else if(z[p]===o){if(!A||"undefined"==typeof A.data)return!1;A.data=z[s]}else if(z[p]===m){if(!A||"undefined"==typeof A.checked)return!1;A.checked=z[s]}else if(z[p]===n){if(!A||"undefined"==typeof A.selected)return!1;A.selected=z[s]}else if(z[p]===e){if(!A||3!=A.nodeType)return!1;this.textDiff(A,A.data,z[r],z[s])}else if(z[p]===k){var B=L(z[s]);A.parentNode.replaceChild(B,A)}else if(z[p]===f){var F,G,C=z[u],D=z[v],E=z[w];if(G=A.childNodes[E+C.length],E>D)for(var H=0;H<C.length;H++)F=A.childNodes[D],A.insertBefore(F,G);else{G=A.childNodes[E];for(var H=0;H<C.length;H++)F=A.childNodes[D+H],A.insertBefore(F,G)}}else if(z[p]===g)A.parentNode.removeChild(A);else if(z[p]===i){if(!A||3!=A.nodeType)return!1;A.parentNode.removeChild(A)}else if(z[p]===h){var I=z[q].slice(),J=I.splice(I.length-1,1)[0];A=this.getFromRoute(a,I);var B=L(z[t]);if(J>=A.childNodes.length)A.appendChild(B);else{var G=A.childNodes[J];A.insertBefore(B,G)}}else if(z[p]===j){var I=z[q].slice(),J=I.splice(I.length-1,1)[0],B=document.createTextNode(z[y]);if(A=this.getFromRoute(a,I),!A||!A.childNodes)return!1;if(J>=A.childNodes.length)A.appendChild(B);else{var G=A.childNodes[J];A.insertBefore(B,G)}}return!0},undo:function(a,b){b=b.slice();var c=this;b.length||(b=[b]),b.reverse(),b.forEach(function(b){c.undoDiff(a,b)})},undoDiff:function(a,q){q[p]===b?(q[p]=d,this.applyDiff(a,q)):q[p]===c?(R(q,r,s),this.applyDiff(a,q)):q[p]===d?(q[p]=b,this.applyDiff(a,q)):q[p]===e?(R(q,r,s),this.applyDiff(a,q)):q[p]===l?(R(q,r,s),this.applyDiff(a,q)):q[p]===o?(R(q,r,s),this.applyDiff(a,q)):q[p]===m?(R(q,r,s),this.applyDiff(a,q)):q[p]===n?(R(q,r,s),this.applyDiff(a,q)):q[p]===k?(R(q,r,s),this.applyDiff(a,q)):q[p]===f?(R(q,v,w),this.applyDiff(a,q)):q[p]===g?(q[p]=h,this.applyDiff(a,q)):q[p]===h?(q[p]=g,this.applyDiff(a,q)):q[p]===i?(q[p]=j,this.applyDiff(a,q)):q[p]===j&&(q[p]=i,this.applyDiff(a,q))}},"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=T),exports.diffDOM=T):this.diffDOM=T}.call(this);
var dd = new diffDOM();
Backbone.VirtualDomRenderer = function(){
if(!this._dom)
this._dom = document.createElement('div');
if(!this._applyDiff)
this._applyDiff = _.debounce(function(){
this._dom.innerHTML = this.template(this.templateData());
var diff = dd.diff(this.el, this._dom);
if(diff.length > 0) {
dd.apply(this.el, diff);
this.trigger('virtualdomrenderer:rendered');
}
}, 17);
if(!this.template)
throw 'template method must be callable on view';
if(!this.templateData)
throw 'templateData method must be callable on view';
this._applyDiff();
return this;
};
}).call(this);
var Todos = new (Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage("Todos")
}));
var TodosView = Backbone.View.extend({
editing: {},
collection: Todos,
template: _.template('<h4><% if(editing.id){ %>Edit Mode<% } else { %>Insert Mode<% } %></h4><ul><% for(var i in datas) { %><li><%= datas[i].title %> <button class="edit" data-id="<%= datas[i].id %>">E</button> <button class="remove" data-id="<%= datas[i].id %>">&times;</button></li><% } %></ul><div><input type="text" <% if(editing.id) {%> value="<%= editing.attributes.title %>" <%} %> placeholder="Enter to save" /></div>'),
templateData: function() {
return {
editing: this.editing,
datas: this.collection.toJSON()
};
},
render: Backbone.VirtualDomRenderer,
events: {
'click .edit': function(e) {
var id = $(e.currentTarget).data('id');
if(this.editing.id && this.editing.id === id) {
this.editing = {};
} else {
this.editing = this.collection.get(id);
}
this.render();
},
'click .remove': function(e) {
var id = $(e.currentTarget).data('id');
if(id === this.editing.id)
this.editing = {};
this.collection.get(id).destroy();
},
'keyup input': function(e) {
var target = $(e.currentTarget);
var value = target.val().trim();
if(e.keyCode === 13 && value) {
var id = this.editing.id;
if(id) {
this.collection.get(id).set({
title: value
}).save();
} else {
this.collection.create({
title: value
});
}
}
}
},
initialize: function() {
this.on('virtualdomrenderer:rendered', function(){
console.log('rendered');
});
this.collection.on('all', function(){
this.render();
}.bind(this)).fetch();
}
});
var view = new TodosView({el: '#output'});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment