Atomのフレームワーク、SpacePen


# SpacePenって
>## 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.  
[atom/space-pen](https://github.com/atom/space-pen)

SpacePen(無重力ペン)はView層とControle層を合わせたCoffeeScriptのjQueryオブジェクト扱うフレームワークだとか何とか…

# 何に使うの？
AtomのPackageとかのUIはこれで書く。
もちろんそのままブラウザ上に出力させるHTMLにも使えちゃう。

----

とりあえずAtomパッケージ作るにあたって必要になったので学習がてらにReadmeを元に使い方を書いてみる。

# どんな感じに書くの

## 基本的な奴
`View`Classの`@content`に例えば次のように書く。

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

```

見ての通りHTMLのタグ名がそのまま`@content`クラス内のメソッドになっている。

また、jQueryで次のように表現するようなものは

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

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

```

SpacePenでは

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

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


view = new Spacecraft
view.addSpacecraft "Enterprise"

```
って出来るらしい。

更にコンストラクタで引数渡したりとかも、ほら。

```coffee
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"])

```

jQueryのprototypeをオーバーライドしたり

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

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

初期化メソッドも。

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

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

## アウトレット/イベント
outletイベントを使えばクラス内の他のメソッドからその要素のjQueryオブジェクトが扱える。

```coffee
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>")
```

`click`イベントをつけておけばクリックしたらメソッド呼び出せる…ボタンとか。

```coffee
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の詳細

 - タグメソッド (`@div`,`@h1`みたいなの)
     ここまで見てきたようにメソッド名がそのままhtml要素になります。
     タグメソッドには次の3種類の引数がわたせます。
    - 文字列
      文字列をHTMLエスケープしてタグ内のテキストにします。(ex:`@div "foo"` → `<div>foo</div>`)
    - ハッシュ
      キーと値のペアはHTMLの属性になります。(ex:`@img src: "url"` → `<img src="url">`)
    - function (=>)
      関数をそのまま引数として渡すと子要素として扱われます。(ex:`@ol => @li "me"`→`<ol><li>me</li></ol>`)

  - テキストメソッド
    - `@text(string)`
      引数のテキストをHTMLエスケープして文字列として出力します。
    - `@raw(string)`
      テキストをエスケープせずにHTMLとして出力します。事前に形成されたHTMLを使うにはこれを利用します。

## Subviews
  `@subview`を使えばコードをモジュール化出来ます。
  `@subview(name,view)`メソッドは別のviewを呼び出し、そこに出力します。

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

  ```

## Freeform Markup Generation

SpacePenマークアップにViewクラスを使用する必要はありません。
`view.render`をタグメソッドを呼び出す関数(`=>`ではなく`->`)と呼び出します。(?)
これはアドホック使用用のドキュメントフラグメントを返します。
このメソッドは便宜上グローバル変数`$$`に割り当てられています。

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

## jQuery拡張

### $.fn.view

任意のDOMオブジェクトのViewオブジェクトを取得出来ます。

```coffee
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

```

### After Attach 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.

SpacePen extends jQuery manipulation methods like append, replaceWith, etc. to call afterAttach hooks on your view objects when they are appended to other elements. The hook will be called with a boolean value indicating whether the view is attached to the main DOM or just to another DOM fragment. If afterAttach is called with true, you can assume your object is attached to the page.

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

  afterAttach: (onDom) ->
    if onDom
      console.log "With CSS applied, my height is", @height()
    else
      console.log "I just attached to", @parent()
```

### Before Remove Hooks

SpacePen calls the beforeRemove hook whenever a view is removed from the DOM via a jQuery method. This works if the view is removed directly with remove or indirectly when a method like empty or html is called on a parent element. This is a good place to clean up subscriptions and other view-specific state.

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

  initialize: ->
    $(window).on 'resize.spacecraft', -> ...

  beforeRemove: ->
    $(window).off('.spacecraft')
```

# HTMLで使う

[example](https://github.com/atom/space-pen/tree/master/examples)ではこのようにしています。
(別途coffee.js,jQuery.js,spec-penのコアファイルが読み込まれているので試す場合は配置に注意してください。--リポジトリをそのままclone or 展開 したのを鯖に上げればおｋ。)

```html
<!DOCTYPE HTML>
<html>
<head>
  <title>Space Pen Example</title>

  <!-- coffeescript -->
  <script type="text/javascript" src="../vendor/coffee-script.js"></script>

  <!-- dependencies -->
  <script type="text/javascript" src="../vendor/jquery.js"></script>

  <!-- implementation -->
  <script type="text/coffeescript" src="../src/space-pen.coffee"></script>

  <!-- specs -->
  <script type="text/coffeescript" src="../examples/example.coffee"></script>
</head>

<body></body>

</html>
```

```coffee
class HelloView extends View
  @content: (params) ->
    @div =>
      @div params.greeting
      @label for: 'name', "What is your name? "
      @div =>
        @input name: 'name', outlet: 'name'
        @button click: 'sayHello', "That's My Name"
      @div outlet: "personalGreeting"

  initialize: (params) ->
    @greeting = params.greeting

  sayHello: ->
    @personalGreeting.html("#{@greeting}, #{@name.val()}")

$('body').append(new HelloView(greeting: "Hi there"))

```

説明するまでも無いだろうので解説は省略。

# Atomのスタイルガイド
メニューのPackage > Styleguide > showとかから見れます。
SpacePenとHTMLのそれぞれのマークアップの仕方が見れますね。

----

# さいごに

SpacePenはAtomのパッケージ作るには必須とも言えますね。めんどくさいわけでもないので書けるように…

----

~~この記事はAtomから書いてAtomから投稿しています。これについては近いうちに公開するつもりなのでお楽しみに！(?)~~
~~Atomは既にQiita投稿できるMarkdownのメモ帳化しています！~~ 結局公開できないままでした......他の方が似たようなの公開されてましたね。

<div class="alert alert-success">
<p>Atom曰く<a href=https://github.com/atom/atom/issues/4673#issuecomment-67402158>SpacePenの時代は終わった！これからはWebComponentsだっ！</a>だそうです。</p>

<p>嘘です。そんなことかいてないです(?)
そこまでは言ってないけど新しいspace-penが出来る......？</p>

<p>2か月前で止まってるIssueだけれども気になったので貼っておきました。<br>
以上。</p>
</div>