aboutsummaryrefslogtreecommitdiff
path: root/atom/packages/ex-mode/node_modules/space-pen/README.md
blob: 715170feac75fba1105b8dc6f8f215f69239e789 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# SpacePen [![Build Status](https://travis-ci.org/atom/space-pen.svg?branch=master)](https://travis-ci.org/atom/space-pen)

**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).**

## Write markup on the final frontier

SpacePen is a powerful but minimalistic client-side view framework for
CoffeeScript. It combines the "view" and "controller" into a single jQuery
object, whose markup is expressed with an embedded DSL similar to Markaby for
Ruby.

## Changes In Version 4

This version of SpacePen depends on HTML 5 custom elements to support lifecycle
hooks that previously depended on all DOM manipulation being performed via
jQuery. The `afterAttach` and `beforeRemove` hooks have been replaced with
`attached` and `detached` and their semantics have been altered.

If you need to use SpacePen in an environment that doesn't support custom
elements, consider using the previous major version or switching frameworks.

## Basics

View objects extend from the View class and have a @content class method where
you express their HTML contents with an embedded markup DSL:

```coffeescript
class Spacecraft extends View
  @content: ->
    @div =>
      @h1 "Spacecraft"
      @ol =>
        @li "Apollo"
        @li "Soyuz"
        @li "Space Shuttle"
```

Views descend from jQuery's prototype, so when you construct one you can call
jQuery methods on it just as you would a DOM fragment created with `$(...)`.

```coffeescript
view = new Spacecraft
view.find('ol').append('<li>Star Destroyer</li>')

view.on 'click', 'li', ->
  alert "They clicked on #{$(this).text()}"
```

But SpacePen views are more powerful than normal jQuery fragments because they
let you define custom methods:

```coffeescript
class Spacecraft extends View
  @content: -> ...

  addSpacecraft: (name) ->
    @find('ol').append "<li>#{name}</li>"


view = new Spacecraft
view.addSpacecraft "Enterprise"
```

You can also pass arguments on construction, which get passed to both the
`@content` method and the view's constructor.

```coffeescript
class Spacecraft extends View
  @content: (params) ->
    @div =>
      @h1 params.title
      @ol =>
        @li name for name in params.spacecraft

view = new Spacecraft(title: "Space Weapons", spacecraft: ["TIE Fighter", "Death Star", "Warbird"])
```

Methods from the jQuery prototype can be gracefully overridden using `super`:

```coffeescript
class Spacecraft extends View
  @content: -> ...

  hide: ->
    console.log "Hiding Spacecraft List"
    super()
```

If you override the View class's constructor, ensure you call `super`.
Alternatively, you can define an `initialize` method, which the constructor will
call for you automatically with the constructor's arguments.

```coffeescript
class Spacecraft extends View
  @content: -> ...

  initialize: (params) ->
    @title = params.title
```

## Outlets and Events

SpacePen will automatically create named reference for any element with an
`outlet` attribute. For example, if the `ol` element has an attribute
`outlet=list`, the view object will have a `list` entry pointing to a jQuery
wrapper for the `ol` element.

```coffeescript
class Spacecraft extends View
  @content: ->
    @div =>
      @h1 "Spacecraft"
      @ol outlet: "list", =>
        @li "Apollo"
        @li "Soyuz"
        @li "Space Shuttle"

  addSpacecraft: (name) ->
    @list.append("<li>#{name}</li>")
```

Elements can also have event name attributes whose value references a custom
method. For example, if a `button` element has an attribute
`click=launchSpacecraft`, then SpacePen will invoke the `launchSpacecraft`
method on the button's parent view when it is clicked:

```coffeescript
class Spacecraft extends View
  @content: ->
    @div =>
      @h1 "Spacecraft"
      @ol =>
        @li click: 'launchSpacecraft', "Saturn V"

  launchSpacecraft: (event, element) ->
    console.log "Preparing #{element.name} for launch!"
```
## Markup DSL Details

### Tag Methods (`@div`, `@h1`, etc.)

As you've seen so far, the markup DSL is pretty straightforward. From the
`@content` class method or any method it calls, just invoke instance methods
named for the HTML tags you want to generate. There are 3 types of arguments you
can pass to a tag method:

* *Strings*: The string will be HTML-escaped and used as the text contents of the generated tag.

* *Hashes*: The key-value pairs will be used as the attributes of the generated tag.

* *Functions* (bound with `=>`): The function will be invoked in-between the open and closing tag to produce the HTML element's contents.

If you need to emit a non-standard tag, you can use the `@tag(name, args...)`
method to name the tag with a string:

```coffeescript
@tag 'bubble', type: "speech", => ...
```

### Text Methods

* `@text(string)`: Emits the HTML-escaped string as text wherever it is called.

* `@raw(string)`: Passes the given string through unescaped. Use this when you need to emit markup directly that was generated beforehand.

## Subviews

Subviews are a great way to make your view code more modular. The
`@subview(name, view)` method takes a name and another view object. The view
object will be inserted at the location of the call, and a reference with the
given name will be wired to it from the parent view. A `parentView` reference
will be created on the subview pointing at the parent.

```coffeescript
class Spacecraft extends View
  @content: (params) ->
    @div =>
      @subview 'launchController', new LaunchController(countdown: params.countdown)
      @h1 "Spacecraft"
      ...
```

## Freeform Markup Generation

You don't need a View class to use the SpacePen markup DSL. Call `View.render`
with an unbound function (`->`, not `=>`) that calls tag methods, and it will
return a document fragment for ad-hoc use. This method is also assigned to the
`$$` global variable for convenience.

```coffeescript
view.list.append $$ ->
  @li =>
    @text "Starship"
    @em "Enterprise"
```

## jQuery extensions

### $.fn.view
You can retrieve the view object for any DOM element by calling `view()` on it.
This usually shouldn't be necessary, as most DOM manipulation will take place
within the view itself using outlet references, but is occasionally helpful.

```coffeescript
view = new Spacecraft
$('body').append(view)

# assuming no other li elements on the DOM, for example purposes,
# the following expression should be true
$('li').view() == view
```

### Attached/Detached Hooks
The `initialize` method is always called when the view is still a detached DOM
fragment, before it is appended to the DOM. This is usually okay, but
occasionally you'll have some initialization logic that depends on the view
actually being on the DOM. For example, you may depend on applying a CSS rule
before measuring an element's height.

For these situations, use the `attached` hook. It will be called whenever your
element is actually attached to the DOM. Past versions of SpacePen would also
call this hook when your element was attached to another detached node, but that
behavior is no longer supported.

To be notified when your element is detached from the DOM, implement the
`detached` hook.

```coffeescript
class Spacecraft extends View
  @content: -> ...

  attached: ->
    console.log "With CSS applied, my height is", @height()

  detached: ->
    console.log "I have been detached."
```

## Hacking on SpacePen

```sh
git clone https://github.com/atom/space-pen.git
cd space-pen
npm install
npm start
```

* Open http://localhost:1337 to run the specs
* Open http://localhost:1337/benchmark to run the benchmarks
* Open http://localhost:1337/examples to browse the examples