Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Spine
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Alex MacCaw
October 04, 2011
Programming
1.3k
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
160
A JavaScript Web App Deconstructed
maccman
6
600
Asynchronous Web Interfaces
maccman
16
2.1k
Building large JS apps
maccman
20
2.3k
Other Decks in Programming
See All in Programming
JAWS-UG横浜 #102 AWSサ終供養LT会 成仏できない AWS サービスたち 〜本日、三体供養します〜
maroon1st
0
340
php-fpmのプロセスが枯渇した日-調査・対処・そして本当にやるべきだったこと-
shibuchaaaan
0
250
PHP初心者セッション2026 〜生成AIでは見えない裏側を知る:今だからLAMPを通して仕組みを学ぶ〜
kashioka
0
870
S3 を使うアプリケーションをローカル完結で動かすことに全力を注いでみた / Running S3 Apps Offline
contour_gara
0
400
【やさしく解説 設計編・中級 #4】ルールの寿命と、システムの年輪
panda728
PRO
2
180
Cloudflare is Agents
chimame
0
150
jsmini JavaScript Engine を作ってみた話
yosuke_furukawa
PRO
0
300
霧の中の代数的エフェクト
funnyycat
1
490
夏だ!祭りだ!祭りとはドメインモデリングでは?
ryugen04
0
200
Welcome to the "Parametricity" 🏙️ − Generic だけど Specific な世界 −
guvalif
PRO
1
210
Japan Community Day at Kubecon + CloudNativeCon Japan 2026: Learning Container Privilege Control by Building My Own Low-Level Container Runtime
ternbusty
1
150
the container ship “Apple Silicon”@WWDC26 Recap -Japan-\(region).swift
shingangan
0
110
Featured
See All Featured
Site-Speed That Sticks
csswizardry
13
1.4k
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
240
Everyday Curiosity
cassininazir
0
270
Google's AI Overviews - The New Search
badams
0
1.1k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
500
New Earth Scene 8
popppiees
3
2.5k
Building Adaptive Systems
keathley
44
3.2k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
Paper Plane (Part 1)
katiecoart
PRO
1
10k
Technical Leadership for Architectural Decision Making
baasie
3
470
Fireside Chat
paigeccino
42
4k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
340
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