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
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
いまどきの Codex で開発する visionOS アプリの開発スタイルについて
karad
0
110
AIが無かった頃の素敵な出会いの話
codmoninc
1
400
言語を使う側から、作る側へ。 自作 Lisp で得た新たな気づき。
andpad
0
160
生成AI導入の「期待外れ」を乗り越える ー 開発フロー改革が目指す、真の組織変革
starfish719
0
4.1k
torikago - Ruby::Boxで照らすモジュラモノリスの実行境界
se4weed
1
350
Google Apps Script で Ruby を動かす
kawahara
0
140
S3 を使うアプリケーションをローカル完結で動かすことに全力を注いでみた / Running S3 Apps Offline
contour_gara
0
410
【やさしく解説 設計編・中級 #1】一つの車に、運転手は一人 ~ある倉庫システムの事例から~
panda728
PRO
0
210
the container ship “Apple Silicon”@WWDC26 Recap -Japan-\(region).swift
shingangan
0
110
Lean は証明の正しさを確認するためだけのツールって思ってませんか?
inoueasei
1
150
Built Our Own Background Agent at LayerX
layerx
PRO
9
5.1k
PostgreSQL 18で考えるUUID主キー
kazuhiro1982
0
470
Featured
See All Featured
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
34
2.8k
Discover your Explorer Soul
emna__ayadi
2
1.2k
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
450
Rails Girls Zürich Keynote
gr2m
96
14k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
550
Navigating Weather and Climate Data
rabernat
0
460
Practical Orchestrator
shlominoach
191
12k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
360
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
Ruling the World: When Life Gets Gamed
codingconduct
0
290
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.9k
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