Rails, CoffeeScript - classでラップしてネームスペース管理


# 経緯

- グローバルで変数名が衝突しないよう、ネームスペース・モジュール管理をやるべきらしい。
- JSは多用しないので、可能であればRailsサイドで解決したい。
- 今後は意識して実践していきたいのでメモ。


# 手法

調べてみると[いろんな手法](http://qiita.com/mnishiguchi/items/62f588e8886f5907c101#%E8%B3%87%E6%96%99)があったが、この方法が気に入った。

## ネームスペース用のディレクトリーを作る（幾つでも良い）

```
/javascripts
    /namespace    # ネームスペース用のディレクトリー
        base.coffee
        charts.coffee
        moving_items.coffee
        [..]
    application.js
    namespace.js  # ネームスペース設定ファイル
```

## ネームスペースをアセットパイプラインに読み込んでもらう

```js:/app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
//= require jquery.turbolinks
//= require bootstrap
//= require turbolinks
//...
//= require namespace
```

```js:/app/assets/javascripts/namespace.js
//= require ./namespace/base
//= require_tree ./namespace
```

## baseファイルにネームスペース用オブジェクトを準備

```coffeescript:/app/assets/javascripts/namespace/base.coffee
@Namespace = {}

# $(document).on "ready page:load", ->
#  $('a[href^="/documents/"]').attr('target', '_blank')
```

## JSコードをネームスペース配下のクラスでラップする

クラス内のコードはインスタンス化されるまで待機することになる

###constructorで何かを戻り値として返す例

```coffeescript:/app/assets/javascripts/namespace/charts.coffee
# Chart.js configuration

Chart.defaults.global.tooltipEvents = ["mousemove", "touchstart", "touchmove"]
Chart.defaults.global.scaleLabel    = "<%=value%>cu.ft"

class @Components.ChartComponent

  constructor: (chartType)->
    return @createClass(chartType)

  createClass: (chartType) ->

    React.createClass
      displayName: "#{chartType}Chart"
      propTypes:
        name:    React.PropTypes.string
        data:    React.PropTypes.oneOfType([React.PropTypes.array, React.PropTypes.object])
        height:  React.PropTypes.number
        width:   React.PropTypes.number
        options: React.PropTypes.object

      getInitialState: ->
        chartInstance: null

      render: ->
        React.DOM.canvas
          ref:   @props.name
          style: { height: @props.height, width: @props.width }

      componentDidMount: ->
        @initializeChart()

      componentWillUnmount: ->
        @state.chartInstance.destroy() if @state.chartInstance

      initializeChart: ->
        canvas = React.findDOMNode(@refs[@props.name])
        ctx    = canvas.getContext("2d")
        chart  = new Chart(ctx)[chartType](@props.data)
        @setState.chartInstance = chart
```

###constructorでイベントリスナーを登録する例

```coffeescript:/app/assets/javascripts/namespace/moving_items.coffee
# moving_items/new, moving_items/edit

class @Namespace.MovingItems

  constructor: ->

    setVolume = (volume) -> $("#moving_item_volume").val(volume)
    setSlider = (volume) -> $("#volume_slider").val(volume)

    # Slider

    document.getElementById('volume_slider').addEventListener 'change', ->
      setVolume(document.getElementById('volume_slider').value)

    # AutoComplete

    $('#moving_item_name').autocomplete
      source: Object.keys( $('#suggestions').data('items') )
      select: (e, ui) =>
        itemVolume = $('#suggestions').data('items')[ui.item.value]
        setVolume(itemVolume)
        setSlider(itemVolume)

    $('#moving_item_room').autocomplete
      source: $('#suggestions').data('rooms')

    $('#moving_item_category').autocomplete
      source: $('#suggestions').data('categories')
```

## HAMLの`:coffee`フィルターを利用しJSコードを呼び出しインスタンス化

```haml:/app/views/movings/_chart_panel.html.haml
:coffee
  window.BarChartComponent = new Namespace.ChartComponent("Bar")
  window.PieChartComponent = new Namespace.ChartComponent("Pie")

.panel.panel-blue
  .panel-heading
    /...
  - if @total_volume > 0
    .panel-body
      .row
        .col-sm-6
          = react_component 'BarChartComponent', { name: "MovingBarChart",
            data: @dataForBarChart, height: 200, width:  400 }
        .col-sm-6
          = react_component 'PieChartComponent', { name: "MovingPieChart",
            data: @dataForPieChart, height: 200, width:  200 }
```

同様のことが、`content_for(:javascript)`、`= yield(:javascript)`でも可能。

```haml:/app/views/layouts/application.html.haml
!!!
%html
  %head
    /...
  %body
    = yield               # ページコンテンツ
    /...
    = yield(:javascript)  # 必要なJSコードを生成する
```

```haml:/app/views/movings/_chart_panel.html.haml
- content_for(:javascript) do
  :coffeescript
    jQuery ->
      window.BarChartComponent = new Namespace.ChartComponent("Bar")
      window.PieChartComponent = new Namespace.ChartComponent("Pie")
/...
```


# 資料

- [Essential JavaScript Namespacing Patterns](http://addyosmani.com/blog/essential-js-namespacing/)
- [JavaScript Namespacing with the Rails Asset Pipeline](http://blog.endpoint.com/2014/03/javascript-namespacing-with-rails-asset.html)
- [Write maintainable JavaScript/CoffeeScript in Rails](http://cookieshq.co.uk/posts/write-maintainable-javascript-in-rails/)
- [CoffeeScriptのclassをグローバルに定義する。](http://qiita.com/kaminaly/items/c1fc738dcacc6dc262c6)