1 # SpacePen [![Build Status](https://travis-ci.org/atom/space-pen.svg?branch=master)](https://travis-ci.org/atom/space-pen)
3 **Version 5.x of SpacePen is intended to be included as a direct dependency of 1.0-compatible Atom packages. If you're looking for SpacePen 3.x, used in [Atom Core](https://github.com/atom/atom), check out the [3.x branch](https://github.com/atom/space-pen/tree/3.x).**
5 ## Write markup on the final frontier
7 SpacePen is a powerful but minimalistic client-side view framework for
8 CoffeeScript. It combines the "view" and "controller" into a single jQuery
9 object, whose markup is expressed with an embedded DSL similar to Markaby for
12 ## Changes In Version 4
14 This version of SpacePen depends on HTML 5 custom elements to support lifecycle
15 hooks that previously depended on all DOM manipulation being performed via
16 jQuery. The `afterAttach` and `beforeRemove` hooks have been replaced with
17 `attached` and `detached` and their semantics have been altered.
19 If you need to use SpacePen in an environment that doesn't support custom
20 elements, consider using the previous major version or switching frameworks.
24 View objects extend from the View class and have a @content class method where
25 you express their HTML contents with an embedded markup DSL:
28 class Spacecraft extends View
38 Views descend from jQuery's prototype, so when you construct one you can call
39 jQuery methods on it just as you would a DOM fragment created with `$(...)`.
43 view.find('ol').append('<li>Star Destroyer</li>')
45 view.on 'click', 'li', ->
46 alert "They clicked on #{$(this).text()}"
49 But SpacePen views are more powerful than normal jQuery fragments because they
50 let you define custom methods:
53 class Spacecraft extends View
56 addSpacecraft: (name) ->
57 @find('ol').append "<li>#{name}</li>"
61 view.addSpacecraft "Enterprise"
64 You can also pass arguments on construction, which get passed to both the
65 `@content` method and the view's constructor.
68 class Spacecraft extends View
73 @li name for name in params.spacecraft
75 view = new Spacecraft(title: "Space Weapons", spacecraft: ["TIE Fighter", "Death Star", "Warbird"])
78 Methods from the jQuery prototype can be gracefully overridden using `super`:
81 class Spacecraft extends View
85 console.log "Hiding Spacecraft List"
89 If you override the View class's constructor, ensure you call `super`.
90 Alternatively, you can define an `initialize` method, which the constructor will
91 call for you automatically with the constructor's arguments.
94 class Spacecraft extends View
97 initialize: (params) ->
101 ## Outlets and Events
103 SpacePen will automatically create named reference for any element with an
104 `outlet` attribute. For example, if the `ol` element has an attribute
105 `outlet=list`, the view object will have a `list` entry pointing to a jQuery
106 wrapper for the `ol` element.
109 class Spacecraft extends View
113 @ol outlet: "list", =>
118 addSpacecraft: (name) ->
119 @list.append("<li>#{name}</li>")
122 Elements can also have event name attributes whose value references a custom
123 method. For example, if a `button` element has an attribute
124 `click=launchSpacecraft`, then SpacePen will invoke the `launchSpacecraft`
125 method on the button's parent view when it is clicked:
128 class Spacecraft extends View
133 @li click: 'launchSpacecraft', "Saturn V"
135 launchSpacecraft: (event, element) ->
136 console.log "Preparing #{element.name} for launch!"
138 ## Markup DSL Details
140 ### Tag Methods (`@div`, `@h1`, etc.)
142 As you've seen so far, the markup DSL is pretty straightforward. From the
143 `@content` class method or any method it calls, just invoke instance methods
144 named for the HTML tags you want to generate. There are 3 types of arguments you
145 can pass to a tag method:
147 * *Strings*: The string will be HTML-escaped and used as the text contents of the generated tag.
149 * *Hashes*: The key-value pairs will be used as the attributes of the generated tag.
151 * *Functions* (bound with `=>`): The function will be invoked in-between the open and closing tag to produce the HTML element's contents.
153 If you need to emit a non-standard tag, you can use the `@tag(name, args...)`
154 method to name the tag with a string:
157 @tag 'bubble', type: "speech", => ...
162 * `@text(string)`: Emits the HTML-escaped string as text wherever it is called.
164 * `@raw(string)`: Passes the given string through unescaped. Use this when you need to emit markup directly that was generated beforehand.
168 Subviews are a great way to make your view code more modular. The
169 `@subview(name, view)` method takes a name and another view object. The view
170 object will be inserted at the location of the call, and a reference with the
171 given name will be wired to it from the parent view. A `parentView` reference
172 will be created on the subview pointing at the parent.
175 class Spacecraft extends View
176 @content: (params) ->
178 @subview 'launchController', new LaunchController(countdown: params.countdown)
183 ## Freeform Markup Generation
185 You don't need a View class to use the SpacePen markup DSL. Call `View.render`
186 with an unbound function (`->`, not `=>`) that calls tag methods, and it will
187 return a document fragment for ad-hoc use. This method is also assigned to the
188 `$$` global variable for convenience.
191 view.list.append $$ ->
200 You can retrieve the view object for any DOM element by calling `view()` on it.
201 This usually shouldn't be necessary, as most DOM manipulation will take place
202 within the view itself using outlet references, but is occasionally helpful.
205 view = new Spacecraft
206 $('body').append(view)
208 # assuming no other li elements on the DOM, for example purposes,
209 # the following expression should be true
210 $('li').view() == view
213 ### Attached/Detached Hooks
214 The `initialize` method is always called when the view is still a detached DOM
215 fragment, before it is appended to the DOM. This is usually okay, but
216 occasionally you'll have some initialization logic that depends on the view
217 actually being on the DOM. For example, you may depend on applying a CSS rule
218 before measuring an element's height.
220 For these situations, use the `attached` hook. It will be called whenever your
221 element is actually attached to the DOM. Past versions of SpacePen would also
222 call this hook when your element was attached to another detached node, but that
223 behavior is no longer supported.
225 To be notified when your element is detached from the DOM, implement the
229 class Spacecraft extends View
233 console.log "With CSS applied, my height is", @height()
236 console.log "I have been detached."
239 ## Hacking on SpacePen
242 git clone https://github.com/atom/space-pen.git
248 * Open http://localhost:1337 to run the specs
249 * Open http://localhost:1337/benchmark to run the benchmarks
250 * Open http://localhost:1337/examples to browse the examples