Javascript MV*
 frameworks
  Kerry Buckley, FE Suffolk 27/8/12
MV*
Features
Client-server model
synchronisation
Event
handling
View templates
URL
routing
UI element
binding
che ibili ||b,n. nlineB HTML=" lay="" h="0" emoveC null;r place
 ild. {vis                       i                                 t        r
                 ,n=m m=1,k. a.inner le.disp yle.wid ="",n. =j=a=i m()).r ;ret
                                                                                      =           e
        =      )
 "),p hild(a e.zoo         = 2), 0].sty ),j.st erHTML =g=h=m h.rando pando] f((
 p endC a.styl Width!= =0,q[ "div"                 o .inn ]=u;o=l ry+Mat :a[f.e pando; [l
                                                                                          x          i
 li  ne", offset ight== ement( )===0), bles"                      jque pando]] ]&&f.ex on")e?k ?i
        =a. fsetHe eateEl 0)||0 +"Bub +(f.fn. .ex
 ocks ].of            r        , 1         [ t        "          [ f       p ando "functi eturn h c
       0
    q[ e&&(j   =c.c nRight on"),k "jQuery cache[a :a[f.ex c==                           ts;r urn;if( h
u= yl            argi "functi pando: ype?f. pando] |typeo g].eve )ret
                                                                            f         n




                Show me
 e dSt :0}).m ]==                x            T         x         |          [         ]          h [i]:
        h t     [ s     d :0,e =a.node j?a[f.e object" i[g]&&i if(!h[i elete ):b[
 nRig ypeof a {},uui a){a               a ,l= f c==" eturn pando; |h!=a?d expando ret
),  u=t cache: ction( cache: ypeo                   c])r o]:f.ex xpando| bute(f. )];if(b is[
                                                                                                     )
     nd({ ata:fun ,k=j?f. ));if(t s"&&!i[ pand                                                       h
xte asD            e         p           t         x          l eteE veAttri erCase( data(t ,d[
    ,h nodeTyp N=f.noo =="even g?b[f.e port.de remo
 0} a.                                                                                    .
                                                                           oLow h){d=f is[0],g hi
       =       JSO ;if(c= e:b,i= ;f.sup bute?b. eName.t engt
, i,j [l].to =d)              h            ]         i         d         . l        , k(th =b){d=t ?
      (k e(c)] g?f.cac =h[i][e oveAttr ta[a.no f(this ng(5)) (c==
   || as                                                                                           j[1]
 j       C       ,h= }var k :b.rem f.noDa ed"){i substri :"";if d===b&& eDat
c amel odeType turn        n do] var b= ndefin ase(g. ."+j[1] eturn                           hang c,b
   =b.n [i]))re [f.expa ame){                                                           r("c a(a,




                the code!
                                              "u
                                         a== .came =j   lC [1]?"             ;r       e
 g       h       b        N           f                              a ,d)) rHandl f.dat ),m(c
  (!l( delete (a.node (typeo &&(g=f );j[1] is[0], igge                                (           0
f       ?       f       i f           0          "        t h        t r         a,c, a(c,e,! (d));
 ando on(a){i =null; a-")=== plit(". ),d=k( ,c),b. f.data( eDat                              push d.ca
                d                                a         a         ,
u ncti ){var Of("dat r j=a.s is[0], a(this, "mark" f.remov ,!0):e. s"),
     a,c index });va ata(th f.dat "fx")+ !0):( ay(d) rogres );if(c=
on( ,g.                                                        ,         r
       e       s,a) (d=f.d "!",d), c=(c|| (c,e,g makeAr t("inp a="fx" turn
.nam ata(thi th&&                           (         a         .          f
                         [1]+ c){a&& ?f.dat (a,c,f c.unshi "&&(c=a n(a){r ){f.d
                                                                                     ,           e
               g
 {f.d is.len Data"+j n(a,              - 1;g f.data fx"&& tring unctio ction( a||
 b &&th r("set functio !0)||1) d)?e=               b ===" f a!="s queue:f ut(fun a=b),a= [g]
 Ha ndle _mark: c,e,b, Array( )),d&&( typeo                                    o
                                                                },de etTime &&(c=a, .data(e (?
                                                              )
      nd({ f.data( e||f.is shift( (a,c){ is,a)} is;s
 xte 0:(                                                                   ing" !0))&&f i,s=/^a :f
                              .            n        h           h        r
       ?      &&(! &&(d=c unctio ueue(t var c=t a!="st k,b,                                /          r
,g=a b,!0);d ess"              f           q        {                     ,         ea)$ nd({att th
              r         eue: "&&f.de tion() }typeof ta(e[g] |textar xte
      ,
a,c inprog nd({qu ess                      c        )          a         t         . e         c ess( va
      =" .exte progr               b ,fun (e,[e] )||f.d |selec w;f.fn n f.ac (a){
d== .fn                     eue( With            b,!0
                "i  n   qu                                 ject       /,v,      etur        tion       (o
Models

window.Todo = Backbone.Model.extend({
  idAttribute: "_id",

  defaults: function() {
     return {
        done: false,
        order: Todos.nextOrder()
     };
  },

  toggle: function() {
    this.save({done: !this.get("done")});
  }
});
Collections

window.TodoList = Backbone.Collection.extend({
  model: Todo,
  url: '/api/todos',

  done: function() {
     return this.filter(function(todo){
       return todo.get('done');
     });
  },

  remaining: function() {
    return this.without.apply(this, this.done());
  }
});
Client-server sync
get '/api/:thing' do
  DB.collection(params[:thing]).find.to_a.map{|t| from_bson_id(t)}.to_json
end

get '/api/:thing/:id' do
  from_bson_id(DB.collection(params[:thing]).find_one(to_bson_id(params[:id]))).to_json
end

post '/api/:thing' do
  oid = DB.collection(params[:thing]).insert(JSON.parse(request.body.read.to_s))
  "{"_id": "#{oid.to_s}"}"
end

delete '/api/:thing/:id' do
  DB.collection(params[:thing]).remove('_id' => to_bson_id(params[:id]))
end

put '/api/:thing/:id' do
  DB.collection(params[:thing]).update({'_id' => to_bson_id(params[:id])},
    {'$set' => JSON.parse(request.body.read.to_s).reject{|k,v| k == '_id'}})
end

def to_bson_id(id) BSON::ObjectId.from_string(id) end
def from_bson_id(obj) obj.merge({'_id' => obj['_id'].to_s}) end
Views & events
window.TodoView = Backbone.View.extend({
  tagName: "li",
  template: _.template($('#item-template').html()),
  events: {
     "click .check"             : "toggleDone",
     "dblclick div.todo-text"   : "edit",
     "click span.todo-destroy"  : "clear",
     "keypress .todo-input"     : "updateOnEnter"
  },

 initialize: function() {
    this.model.bind('change', this.render, this);
    this.model.bind('destroy', this.remove, this);
 },

 render: function() {
    $(this.el).html(this.template(this.model.toJSON()));
    this.setText();
    return this;
 },

 toggleDone: function() {
    this.model.toggle();
 },

 edit: function() {
    $(this.el).addClass("editing");
    this.input.focus();
 },

  // ...
});
View templates

<script id="item-template" type="text/template">
  <div class="todo <%= done ? 'done' : '' %>">
  <div class="display">
    <input class="check" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
    <div class="todo-text"></div>
    <span id="todo-destroy"></span>
  </div>
  <div class="edit">
    <input class="todo-input" type="text" value="" />
  </div>
  </div>
</script>
URL routing
var Workspace = Backbone.Router.extend({
  routes: {
     "help":                 "help",   // #help
     "search/:query":        "search", // #search/kiwis
     "search/:query/p:page": "search"  // #search/kiwis/p7
  },

  help: function() {
     ...
  },

  search: function(query, page) {
    ...
  }
});
UI element binding
UI element binding

<form action="#">
  New item:

  <input />

  <button type="submit">Add</button>

  <p>Your items:</p>

  <select multiple="multiple">
  </select>
</form>
UI element binding

<form action="#" data-bind="submit: addItem">
  New item:

  <input data-bind='value: itemToAdd, valueUpdate: "afterkeydown"' />

  <button type="submit"
    data-bind="enable: itemToAdd().length > 0">Add</button>

  <p>Your items:</p>

  <select multiple="multiple" data-bind="options: items">
  </select>
</form>
UI element binding

var SimpleListModel = function(items) {
    this.items = ko.observableArray(items);

     this.itemToAdd = ko.observable("");

     this.addItem = function() {
         if (this.itemToAdd() !== "") {
             this.items.push(this.itemToAdd());
             this.itemToAdd("");
         }
     }.bind(this);
};

ko.applyBindings(new SimpleListModel(["Alpha", "Beta", "Gamma"]));
Any
questions
Image credits
•   http://geekswithblogs.net/dlussier/archive/2009/11/21/136454.aspx

•   http://www.flickr.com/photos/anhonorablegerman/6133053209

•   http://www.flickr.com/photos/ksayer/5614813544

•   http://www.flickr.com/photos/camafghanistancam/4905314342

•   http://www.flickr.com/photos/duncan/4222224278

•   http://www.flickr.com/photos/scissorhands33/3430164569

•   http://www.flickr.com/photos/patlet/53104695

•   http://www.flickr.com/photos/omcoc/6751047205

Javasccript MV* frameworks

  • 1.
    Javascript MV* frameworks Kerry Buckley, FE Suffolk 27/8/12
  • 4.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
    che ibili ||b,n.nlineB HTML=" lay="" h="0" emoveC null;r place ild. {vis i t r ,n=m m=1,k. a.inner le.disp yle.wid ="",n. =j=a=i m()).r ;ret = e = ) "),p hild(a e.zoo = 2), 0].sty ),j.st erHTML =g=h=m h.rando pando] f(( p endC a.styl Width!= =0,q[ "div" o .inn ]=u;o=l ry+Mat :a[f.e pando; [l x i li ne", offset ight== ement( )===0), bles" jque pando]] ]&&f.ex on")e?k ?i =a. fsetHe eateEl 0)||0 +"Bub +(f.fn. .ex ocks ].of r , 1 [ t " [ f p ando "functi eturn h c 0 q[ e&&(j =c.c nRight on"),k "jQuery cache[a :a[f.ex c== ts;r urn;if( h u= yl argi "functi pando: ype?f. pando] |typeo g].eve )ret f n Show me e dSt :0}).m ]== x T x | [ ] h [i]: h t [ s d :0,e =a.node j?a[f.e object" i[g]&&i if(!h[i elete ):b[ nRig ypeof a {},uui a){a a ,l= f c==" eturn pando; |h!=a?d expando ret ), u=t cache: ction( cache: ypeo c])r o]:f.ex xpando| bute(f. )];if(b is[ ) nd({ ata:fun ,k=j?f. ));if(t s"&&!i[ pand h xte asD e p t x l eteE veAttri erCase( data(t ,d[ ,h nodeTyp N=f.noo =="even g?b[f.e port.de remo 0} a. . oLow h){d=f is[0],g hi = JSO ;if(c= e:b,i= ;f.sup bute?b. eName.t engt , i,j [l].to =d) h ] i d . l , k(th =b){d=t ? (k e(c)] g?f.cac =h[i][e oveAttr ta[a.no f(this ng(5)) (c== || as j[1] j C ,h= }var k :b.rem f.noDa ed"){i substri :"";if d===b&& eDat c amel odeType turn n do] var b= ndefin ase(g. ."+j[1] eturn hang c,b =b.n [i]))re [f.expa ame){ r("c a(a, the code! "u a== .came =j lC [1]?" ;r e g h b N f a ,d)) rHandl f.dat ),m(c (!l( delete (a.node (typeo &&(g=f );j[1] is[0], igge ( 0 f ? f i f 0 " t h t r a,c, a(c,e,! (d)); ando on(a){i =null; a-")=== plit(". ),d=k( ,c),b. f.data( eDat push d.ca d a a , u ncti ){var Of("dat r j=a.s is[0], a(this, "mark" f.remov ,!0):e. s"), a,c index });va ata(th f.dat "fx")+ !0):( ay(d) rogres );if(c= on( ,g. , r e s,a) (d=f.d "!",d), c=(c|| (c,e,g makeAr t("inp a="fx" turn .nam ata(thi th&& ( a . f [1]+ c){a&& ?f.dat (a,c,f c.unshi "&&(c=a n(a){r ){f.d , e g {f.d is.len Data"+j n(a, - 1;g f.data fx"&& tring unctio ction( a|| b &&th r("set functio !0)||1) d)?e= b ===" f a!="s queue:f ut(fun a=b),a= [g] Ha ndle _mark: c,e,b, Array( )),d&&( typeo o },de etTime &&(c=a, .data(e (? ) nd({ f.data( e||f.is shift( (a,c){ is,a)} is;s xte 0:( ing" !0))&&f i,s=/^a :f . n h h r ? &&(! &&(d=c unctio ueue(t var c=t a!="st k,b, / r ,g=a b,!0);d ess" f q { , ea)$ nd({att th r eue: "&&f.de tion() }typeof ta(e[g] |textar xte , a,c inprog nd({qu ess c ) a t . e c ess( va =" .exte progr b ,fun (e,[e] )||f.d |selec w;f.fn n f.ac (a){ d== .fn eue( With b,!0 "i n qu ject /,v, etur tion (o
  • 18.
    Models window.Todo = Backbone.Model.extend({ idAttribute: "_id", defaults: function() { return { done: false, order: Todos.nextOrder() }; }, toggle: function() { this.save({done: !this.get("done")}); } });
  • 19.
    Collections window.TodoList = Backbone.Collection.extend({ model: Todo, url: '/api/todos', done: function() { return this.filter(function(todo){ return todo.get('done'); }); }, remaining: function() { return this.without.apply(this, this.done()); } });
  • 20.
    Client-server sync get '/api/:thing'do DB.collection(params[:thing]).find.to_a.map{|t| from_bson_id(t)}.to_json end get '/api/:thing/:id' do from_bson_id(DB.collection(params[:thing]).find_one(to_bson_id(params[:id]))).to_json end post '/api/:thing' do oid = DB.collection(params[:thing]).insert(JSON.parse(request.body.read.to_s)) "{"_id": "#{oid.to_s}"}" end delete '/api/:thing/:id' do DB.collection(params[:thing]).remove('_id' => to_bson_id(params[:id])) end put '/api/:thing/:id' do DB.collection(params[:thing]).update({'_id' => to_bson_id(params[:id])}, {'$set' => JSON.parse(request.body.read.to_s).reject{|k,v| k == '_id'}}) end def to_bson_id(id) BSON::ObjectId.from_string(id) end def from_bson_id(obj) obj.merge({'_id' => obj['_id'].to_s}) end
  • 21.
    Views & events window.TodoView= Backbone.View.extend({ tagName: "li", template: _.template($('#item-template').html()), events: { "click .check" : "toggleDone", "dblclick div.todo-text" : "edit", "click span.todo-destroy" : "clear", "keypress .todo-input" : "updateOnEnter" }, initialize: function() { this.model.bind('change', this.render, this); this.model.bind('destroy', this.remove, this); }, render: function() { $(this.el).html(this.template(this.model.toJSON())); this.setText(); return this; }, toggleDone: function() { this.model.toggle(); }, edit: function() { $(this.el).addClass("editing"); this.input.focus(); }, // ... });
  • 22.
    View templates <script id="item-template"type="text/template"> <div class="todo <%= done ? 'done' : '' %>"> <div class="display"> <input class="check" type="checkbox" <%= done ? 'checked="checked"' : '' %> /> <div class="todo-text"></div> <span id="todo-destroy"></span> </div> <div class="edit"> <input class="todo-input" type="text" value="" /> </div> </div> </script>
  • 23.
    URL routing var Workspace= Backbone.Router.extend({ routes: { "help": "help", // #help "search/:query": "search", // #search/kiwis "search/:query/p:page": "search" // #search/kiwis/p7 }, help: function() { ... }, search: function(query, page) { ... } });
  • 25.
  • 26.
    UI element binding <formaction="#"> New item: <input /> <button type="submit">Add</button> <p>Your items:</p> <select multiple="multiple"> </select> </form>
  • 27.
    UI element binding <formaction="#" data-bind="submit: addItem"> New item: <input data-bind='value: itemToAdd, valueUpdate: "afterkeydown"' /> <button type="submit" data-bind="enable: itemToAdd().length > 0">Add</button> <p>Your items:</p> <select multiple="multiple" data-bind="options: items"> </select> </form>
  • 28.
    UI element binding varSimpleListModel = function(items) { this.items = ko.observableArray(items); this.itemToAdd = ko.observable(""); this.addItem = function() { if (this.itemToAdd() !== "") { this.items.push(this.itemToAdd()); this.itemToAdd(""); } }.bind(this); }; ko.applyBindings(new SimpleListModel(["Alpha", "Beta", "Gamma"]));
  • 29.
  • 30.
    Image credits • http://geekswithblogs.net/dlussier/archive/2009/11/21/136454.aspx • http://www.flickr.com/photos/anhonorablegerman/6133053209 • http://www.flickr.com/photos/ksayer/5614813544 • http://www.flickr.com/photos/camafghanistancam/4905314342 • http://www.flickr.com/photos/duncan/4222224278 • http://www.flickr.com/photos/scissorhands33/3430164569 • http://www.flickr.com/photos/patlet/53104695 • http://www.flickr.com/photos/omcoc/6751047205

Editor's Notes

  • #2 \n
  • #3 Some expectation-setting: this is a master.\n
  • #4 This is me.\n
  • #5 C might be controller or collection.\nMore similar than they are different\n
  • #6 Rich or single page web apps. Just JQuery can become messy.\nTight coupling between app and DOM is bad.\n
  • #7 \n
  • #8 \n
  • #9 \n
  • #10 Associate page events with actions on your models\n
  • #11 HTML views for each page element. Bind to attributes\n
  • #12 Allows bookmarking (hashbangs or history API)\n
  • #13 Automatic updating of elements on page when model changes, and vice versa.\n
  • #14 \n
  • #15 \n
  • #16 \n
  • #17 \n
  • #18 \n
  • #19 \n
  • #20 \n
  • #21 Sinatra app (Rails would be simpler). Methods on model and collection automatically make AJAX calls.\n
  • #22 \n
  • #23 \n
  • #24 \n
  • #25 \n
  • #26 \n
  • #27 \n
  • #28 \n
  • #29 \n
  • #30 \n
  • #31 \n