Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Spine
Search
Alex MacCaw
October 04, 2011
Programming
1.4k
11
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Spine
JavaScript MVC Framework
Alex MacCaw
October 04, 2011
More Decks by Alex MacCaw
See All by Alex MacCaw
Fronteers
maccman
0
170
A JavaScript Web App Deconstructed
maccman
6
620
Asynchronous Web Interfaces
maccman
16
2.1k
Building large JS apps
maccman
20
2.3k
Other Decks in Programming
See All in Programming
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
810
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
800
IBM Bob Dojo #1 仕様駆動開発入門
oniak3ibm
PRO
0
410
Atomic Design, Enforced: Scaling Mobile Design Systems (next.app devCon / droidCon Berlin 2026)
steliosf
PRO
0
170
Findy - エンジニア向け会社紹介/Findy Company Deck
findyinc
6
400k
JIS keyboard support in Omarchy
lef237
0
140
[2026-09-26]空論ジェネリックプロセス~テスト資産とAIで紡ぐ、再現可能なパフォーマンスチューニングの話~
tosite
0
270
FactoryBotをやめればRSpecは速くなるのか?
yuta_onishi_97
0
820
Rでドレミ/Do-Re-Mi_with_R
florets1
0
130
設計次第でAIコードの読む量は減らせる / designing-for-code-reading
minodriven
35
17k
Verilogで学ぶCPU自作入門
uyuki234
8
4.2k
GraphQL の onError と Nullability のこれから
quramy
0
100
Featured
See All Featured
Ten Tips & Tricks for a 🌱 transition
stuffmc
1
270
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.4k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.9k
Abbi's Birthday
coloredviolet
4
10k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
400
Fashionably flexible responsive web design (full day workshop)
malarkey
409
67k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3.1k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
430
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
2
2k
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
340
Building Applications with DynamoDB
mza
96
7.2k
Building the Perfect Custom Keyboard
takai
2
910
Transcript
Spine JavaScript MVC Framework
None
createBox(t, s): function{ return ['<div class="x-box-blue">', '<div class="x-box-tl"><div class="x-box-tr"><div class="x-box-tc"></div></div></div>',
'<div class="x-box-ml"><div class="x-box-mr"><div class="x-box-mc"><h3>', t, '</h3>', s, '</div '<div class="x-box-bl"><div class="x-box-br"><div class="x-box-bc"></div></div></div>', '</div>'].join(''); }, emailMultiple : function(){ " " if(!Aireo.multipleSelect()){ " " " return " " } " " var nodes = assetView.getSelectedNodes() " " var query_string = [] " " for(var i=0; i < nodes.length; i ++){ " " " var data = Aireo.lookup[nodes[i].id]; " " " if(data.is_folder){ " " " query_string.push('folders[]=' + data.id) " " " " } else { " " " query_string.push('assets[]=' + data.id) " " " } " " } " " Email.init(lc.multiple_file_folders, query_string.join('&'), '',true); " " }, " " emailSelected : function(e){ " " if(!Aireo.currentlySelectedObject){ " " " return; " " } " " if(Aireo.multipleSelect()){ " " " Aireo.emailMultiple() " " } else { " " if(Aireo.currentlySelectedObject){ " " " var data = Aireo.currentlySelectedObject; " " if(Aireo.currentlySelectedObject.type == 'folder'){ " " Email.init(data.name,data.id,'folders',false);" " " } " " else if(Aireo.currentlySelectedObject.type == 'asset') {" " " Email.init(data.name,data.id,'assets',false);" " " } " " }
The Problem with JavaScript • No structure • No namespacing
• No dependency management • No conventions • Poor JS implementations • Misunderstanding and ignorance
Hope
Namespacing
The Good Parts
MVC
None
None
None
?
Spine
MVC
Don’t block
Simplicity
None
1.0
Features • CoffeeScript (although not required) • MVC and ORM
• Ajax and Local Storage • HTML5 History integration • Simple API • Excellent documentation
Models
class Task extends Spine.Model @configure "Task", "name", "done" @extend Spine.Model.Local
@active: -> @select (item) -> !item.done @done: -> @select (item) -> !!item.done @destroyDone: -> rec.destroy() for rec in @done()
class Task extends Spine.Model @configure "Task", "name", "done" @extend Spine.Model.Local
@active: -> @select (item) -> !item.done @done: -> @select (item) -> !!item.done @destroyDone: -> rec.destroy() for rec in @done()
class Task extends Spine.Model @configure "Task", "name", "done" @extend Spine.Model.Local
@active: -> @select (item) -> !item.done @done: -> @select (item) -> !!item.done @destroyDone: -> rec.destroy() for rec in @done()
Controllers
@el
class Tasks extends Spine.Controller constructor: -> Task.bind('refresh change', @render) render:
=> tasks = Task.all() @html require('views/tasks')(tasks)
class Tasks extends Spine.Controller constructor: -> Task.bind('refresh change', @render) render:
=> tasks = Task.all() @html require('views/tasks')(tasks)
class Tasks extends Spine.Controller constructor: -> Task.bind('refresh change', @render) render:
=> tasks = Task.all() @html require('views/tasks')(tasks)
class Tasks extends Spine.Controller constructor: -> Task.bind('refresh change', @render) render:
=> tasks = Task.all() @html require('views/tasks')(tasks)
Views
<div class="item"> <input type="checkbox" <%- if @done: %>checked="checked"<% end %>>
<span> <%= @name %> <a class="destroy"></a> </span> </div>
<div class="item"> <input type="checkbox" <%- if @done: %>checked="checked"<% end %>>
<span> <%= @name %> <a class="destroy"></a> </span> </div>
<div class="item"> <input type="checkbox" <%- if @done: %>checked="checked"<% end %>>
<span> <%= @name %> <a class="destroy"></a> </span> </div>
<div class="item"> <input type="checkbox" <%- if @done: %>checked="checked"<% end %>>
<span> <%= @name %> <a class="destroy"></a> </span> </div>
class Tasks extends Spine.Controller events: 'click .item .destroy': 'destroy' constructor:
-> Task.bind('refresh change', @render) render: => tasks = Task.all() @html require('views/tasks')(tasks) destroy: (e) -> task = $(e).item() task.destroy()
class Tasks extends Spine.Controller events: 'click .item .destroy': 'destroy' constructor:
-> Task.bind('refresh change', @render) render: => tasks = Task.all() @html require('views/tasks')(tasks) destroy: (e) -> task = $(e).item() task.destroy()
Binding
class Tasks extends Spine.Controller events: 'click .item .destroy': 'destroy' constructor:
-> Task.bind('refresh change', @render) render: => tasks = Task.all() @html require('views/tasks')(tasks) destroy: (e) -> task = $(e).item() task.destroy()
class Tasks extends Spine.Controller events: 'click .item .destroy': 'destroy' constructor:
-> Task.bind('refresh change', @render) render: => tasks = Task.all() @html require('views/tasks')(tasks) destroy: (e) -> task = $(e).item() task.destroy() 1. record is destroyed
class Tasks extends Spine.Controller events: 'click .item .destroy': 'destroy' constructor:
-> Task.bind('refresh change', @render) render: => tasks = Task.all() @html require('views/tasks')(tasks) destroy: (e) -> task = $(e).item() task.destroy() 1. record is destroyed 2. change callback is triggered
class Tasks extends Spine.Controller events: 'click .item .destroy': 'destroy' constructor:
-> Task.bind('refresh change', @render) render: => tasks = Task.all() @html require('views/tasks')(tasks) destroy: (e) -> task = $(e).item() task.destroy() 1. record is destroyed 2. change callback is triggered 3. tasks are re-rendered
More? •Classes & Modules •Routing •Ajax •Dependency Manager (Hem)
Spine Mobile
UX
None
None
? Stage Panel Transitions
class ContactsCreate extends Panel # ... class ContactsList extends Panel
constructor: -> super @addButton('Add Contact', @add) add: -> @navigate('/contacts/create', trans: 'right') class Contacts extends Spine.Controller constructor: -> super @list = new ContactsList @create = new ContactsCreate @routes '/contacts': (params) -> @list.active(params) '/contacts/create': (params) -> @create.active(params)
class ContactsCreate extends Panel # ... class ContactsList extends Panel
constructor: -> super @addButton('Add Contact', @add) add: -> @navigate('/contacts/create', trans: 'right') class Contacts extends Spine.Controller constructor: -> super @list = new ContactsList @create = new ContactsCreate @routes '/contacts': (params) -> @list.active(params) '/contacts/create': (params) -> @create.active(params)
class ContactsCreate extends Panel # ... class ContactsList extends Panel
constructor: -> super @addButton('Add Contact', @add) add: -> @navigate('/contacts/create', trans: 'right') class Contacts extends Spine.Controller constructor: -> super @list = new ContactsList @create = new ContactsCreate @routes '/contacts': (params) -> @list.active(params) '/contacts/create': (params) -> @create.active(params)
class ContactsCreate extends Panel # ... class ContactsList extends Panel
constructor: -> super @addButton('Add Contact', @add) add: -> @navigate('/contacts/create', trans: 'right') class Contacts extends Spine.Controller constructor: -> super @list = new ContactsList @create = new ContactsCreate @routes '/contacts': (params) -> @list.active(params) '/contacts/create': (params) -> @create.active(params)
None
None
Full Featured
None
None
@maccman http://alexmaccaw.co.uk