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.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
610
Asynchronous Web Interfaces
maccman
16
2.1k
Building large JS apps
maccman
20
2.3k
Other Decks in Programming
See All in Programming
モバイル交通系ICへのチャージ実例から考える、クロスプラットフォーム開発におけるiOS実機テスト設計とCI運用
yusuga
1
520
AIは賢い。でも実行環境は? CLIおじさんがAI時代に伝えたいこと ~ CLIおじさんがAI時代に伝えたいこと ~
curekoshimizu
1
250
世界の中心で、AI(App Intents)をさけぶ ー App Intents中心設計の実践ガイド
touyou
0
620
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
3
360
Security issues being discussed on Web Platforms
petamoriken
0
990
thread_parallel_with_free-threaded_Python_and_NumPy.pdf
riku_sakamoto
0
350
スマート反転とウェブアクセシビリティ
camiha
0
210
Java 27新機能 / Java 27 new features
kishida
2
150
AI活用は、個人から組織へ|マルチプレイヤーエージェントハーネス「QM」の社内活用事例 / AI use is moving from individuals to orgs
rkaga
1
120
手動確認はもう限界 〜XCUITestでCustom URL Schemeの遷移を起動種別ごとに自動テストする〜 / Testing Custom URL Schemes with XCUITest
otouto
0
310
大喜利で理解するLLM as a Judge / Understanding LLM-as-a-Judge through Ogiri
rockname
0
150
スマートフォンでモールス信号を送受信する 〜スマートフォンのLEDとカメラで作る光通信の設計と実装〜
atsuki_seo
0
160
Featured
See All Featured
AI: The stuff that nobody shows you
jnunemaker
PRO
10
1k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2.1k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
1k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
300
Design in an AI World
tapps
1
320
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
910
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
5
700
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.6k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
23k
Agile that works and the tools we love
rasmusluckow
331
22k
The Curse of the Amulet
leimatthew05
3
15k
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