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
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
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
Laravelで学ぶ Webアプリケーションチューニング入門/web_application_tuning_101
hanhan1978
4
930
ITヒヤリハットを整理してみた ~ライフサイクルと原因から考える再発防止策~
koukimiura
1
110
【SRE NEXT 2026 Lunch Session】一人目専任SREの立ち上げを加速する ― AIと進めたオンボーディングで2分を0.04秒にした話
pkshadeck
PRO
0
2.8k
地域 SRE コミュニティ最前線 - ホンマでっかSRE勉強会
tk3fftk
0
250
AIキャラアプリkaiwaの低遅延音声通話基盤をどう作ったか - AWS Gravitonで支える低遅延・低コストAI Agent基盤
mogamit
0
170
LaravelLive Japan の裏方のすべて — 第188回 PHP勉強会@東京 (2026-06-24)
suguruooki
2
150
SLOをサービス品質の共通言語にするために 取り組んできたこと
wakana0222
0
510
PHP に部分適用が来るぞ!……ところで何それ?おいしいの? #phpcon / phpcon-2026
shogogg
0
260
霧の中の代数的エフェクト
funnyycat
1
410
Performance Engineering for Everyone
elenatanasoiu
0
270
Apache Hive: Toward a Cloud Native Lakehouse
okumin
0
140
Haskell/Servantを通してWebミドルウェアを捉え直す
pizzacat83
1
590
Featured
See All Featured
Believing is Seeing
oripsolob
1
170
Making Projects Easy
brettharned
120
6.7k
Reflections from 52 weeks, 52 projects
jeffersonlam
356
21k
Ethics towards AI in product and experience design
skipperchong
2
330
What does AI have to do with Human Rights?
axbom
PRO
1
2.3k
Claude Code のすすめ
schroneko
67
230k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.6k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
67
56k
Build your cross-platform service in a week with App Engine
jlugia
234
18k
The SEO identity crisis: Don't let AI make you average
varn
0
520
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