"Rails 4で作るドラッグアンドドロップで表示順を変更できるサンプルアプリ"をAngularJSで


# はじめに

『[**Rails 4で作るドラッグアンドドロップで表示順を変更できるサンプルアプリ(スクリーンキャスト付き)**](http://qiita.com/jnchito/items/391fb16d3f69fda9bdae)』という記事，読まれたことがある方も多いと思います．

RailsでAjax（via jQuery）を使ったデータの更新，ドラッグアンドドロップの実現，その他UXを向上させるためのいろいろな技などが詰まった最高にありがたい記事です．拝むしかない．
お世話になった方も多いのではないでしょうか．

本稿はjQuery & jQuery UIで実現されていた機能を，[**AngularJS**](https://angularjs.org/)を用いて実現してみるという内容になってます．
（ざんねんながらスクリーンキャストはありません．~~眠すぎて撮れませんでした．~~）
元ネタと大きく異なるところを列挙していくようなものになると思います．

## ソースコード

GitHubにおいてます．ごかくにんください．

[izumin5210/sortable-table-sandbox](https://github.com/izumin5210/sortable-table-sandbox)

## デモ

Herokuにあります．並び替えぐらいしか出来ないようになってます．

[https://sortable-table-sandbox-angular.herokuapp.com/](https://sortable-table-sandbox-angular.herokuapp.com/)

# フロントエンドのパッケージについて

元は普通のgemで管理されていましたが，[**Rails Assets**](https://rails-assets.org/)に変えています．完全に趣味です．
JS/CSSのライブラリって[**bower**](http://bower.io/)で管理したくなるやん？
（Rails Assetsについては『[bowerパッケージをbundlerで管理するRails Assetsを使ってみた](http://qiita.com/joker1007/items/8d2fa9960f3d53b8cb57)』を参考にするといいと思いますです）


また，[**AngularJS**](https://angularjs.org/)をメインに据えるにあたってjQueryを完全に駆逐してやろうと考えていたのですが…
今回のメインである[ui-sortable](https://github.com/angular-ui/ui-sortable)がjQuery及びjQuery UIに依存してました．ざんねん．
（無理やりjQuery非依存に出来ないかな…）

```ruby:Gemfile(jnchitoさんver.)
gem 'jquery-rails'
gem 'jquery-ui-rails'
gem 'bootstrap-sass'
```

```ruby:Gemfile(izumin5210ver.)
source 'https://rails-assets.org'

gem 'rails-assets-bootstrap-sass-official'
gem 'rails-assets-angularjs'
gem 'rails-assets-angular-resource'
gem 'rails-assets-angular-ui-sortable'
```

これらに伴い，`application.{css.scss,js}`もちょっとずつ変わってます．

## CSS

`bootstrap-sass-official`に入ってる`scss`をimportする方式に変更しています．これも趣味です，
bootstrapのvariablesとか弄り倒してカスタマイズするときはこっちのが捗る．

```scss:app/assets/stylesheets/application.css.scss
/*
 *= require angularjs/angular-csp
 *= require_self
 */

$icon-font-path: "bootstrap-sass-official/";

@import "bootstrap-sass-official/bootstrap-sprockets";
@import "bootstrap-sass-official/bootstrap";

@import 'table-sortable';

body { padding-top: 70px; }
```

* `_xxx.scss`を読んでほしくなかったから`require_tree .`は外した
* `_table-sortable.scss`はテーブルにスタイル当ててるだけ
* `body`のスタイルはbootstrapの`navbar-fixed-top`対策
* `angularjs/angular-csp`は`ng-cloak`とかをいい感じにしてくれるやつ（AngularJS付属品）


## JavaScript

ここは割りと普通．

```javascript:app/assets/javascripts/application.js
//= require jquery
//= require jquery-ui
//= require angularjs
//= require angular-resource
//= require angular-ui-sortable
//= require sortable_table_sandbox
//= require_tree .
```

`sortable_table_sandbox.js.coffee`はAngularJSのモジュール定義するやつ

```javascript:app/assets/javascripts/sortable_table_sandbox.js.coffee
app = angular.module 'SortableTableSandbox', ['ngResource', 'ui.sortable']
app.config ['$httpProvider', ($httpProvider) ->
  csrfToken = angular.element(document.querySelectorAll('meta[name=csrf-token]')).attr('content')
  $httpProvider.defaults.headers.common['X-CSRF-Token'] = csrfToken
]
```

CSRF Tokenまわりの設定とかはここでやっちゃうのが楽．
ちょっとごちゃついてるのはjQueryへの依存をなくそうとした涙ぐましい痕跡．

# fruits#index をAngularJSで描画


## View

`angular-ui-sortable`を利用するということで，やっぱり`ng-repeat`でtable描いたほうが早いかなーという考え方．
ここで`FruitsCtrl`を指定する（controller as記法が便利）．

また，ui-sortableのオプション（`ui-sortable='ctrl.sortableOptions'`）とどのオブジェクトを並び替え対象にするか（`ng-model="ctrl.fruits"`）も書く．

```slim:app/views/fruits/index.html.slim
h1 Listing fruits

table.table.table-hover.table-sortable[ng-controller="FruitsCtrl as ctrl"]
  thead
    tr
      th Name
      th

  tbody[ui-sortable='ctrl.sortableOptions' ng-model="ctrl.fruits"]
    tr.item[ng-repeat="fruit in ctrl.fruits"]
      td 
        | {{fruit.name}}
      td: a[ng-href="/fruits/{{fruit.id}}"] Show
```

## FruitsCtrl

`FruitsCtrl`の実装はこんな感じ．
通信周りとかはservice（model）にぶん投げてる（後述）．

Viewとの対応は以下のとおり．

* `ctrl.fruits` => `@fruits`（JSだと`this.fruits`）
* `ctrl.sortableOptions` => `sortableOptions`（`FruitsCtrl`のぷろぱてぃ）．

`Fruit`のオブジェクトの`row_order_position`をセットした後，`Fruit#$sort`を実行するだけで更新される．
**もうajaxメソッドのオプションいっぱい書かなくても済むんだ…！**

```coffeescript:app/assets/javascripts/controllers/fruits_ctrl.js.coffee
class FruitsCtrl

  constructor: (@$scope, @Fruit) ->
    @fruits = @Fruit.query()

  sortableOptions:
    stop: (e, ui) ->
      fruit = ui.item.scope().fruit
      fruit.row_order_position = ui.item.index()
      fruit.$sort()

app = angular.module 'SortableTableSandbox'
app.controller 'FruitsCtrl', [
  '$scope'
  'Fruit'
  FruitsCtrl
]
```

## Fruit

model部分は[$resource](https://docs.angularjs.org/api/ngResource/service/$resource)サービス（`ngResource`）を利用してます．
`$resource`の`action`オプションで`PATCH`メソッドな`sort`アクションを定義．

これを注入したらいい感じにfruit用のResourceクラスが取ってこれる（JS的に正しい表現なのかは置いといて…）．

```coffeescript:app/assets/javascripts/services/fruit.js.coffee
Fruit = ($resource) ->
  url = '/fruits/:id.json'
  paramsDefaults = id: '@id'
  actions =
    sort:
      url: '/fruits/:id/sort.json'
      method: 'PATCH'

  $resource(url, paramsDefaults, actions)

app = angular.module 'SortableTableSandbox'
app.factory 'Fruit', [
  '$resource'
  Fruit
]
```

# まとめ

jQuery版と比較すると，個人的にはとにかくViewがスッキリするなーと感じます．
ただ，JS（Coffee）自体の記述量はぱっと見だと多い気がしちゃうので敷居は高く感じるかも．
ただ実際はそんなことはなく，このあとは`fruits_ctrl.js.coffee`や`fruit.js.coffee`にいろいろ追加していくようになるので，長い目で見ると（？）こっちのが楽になるかも．
jQueryよりは処理があっちこっち散らばったりしづらい…のかな？

エンジニアの技量次第か…

なお`jquery-ui.effect-highlight`については~~面倒だったので~~作ってないです．
余裕あるときにでも自分で実装してみようかな．

# 謝辞

なんといっても元ネタとなる素晴らしい記事を書いてくださった@jnchitoさん．
これ以外の記事だったり書籍（[Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)）だったりコミュニティ（[西脇.rb & 神戸.rb](http://nishiwaki-koberb.doorkeeper.jp/)）だったりで数え切れないぐらいお世話になってます．
本当にありがとうございます（宣伝してるわけではない）．

# 参考文献

* [**Rails 4で作るドラッグアンドドロップで表示順を変更できるサンプルアプリ(スクリーンキャスト付き)**](http://qiita.com/jnchito/items/391fb16d3f69fda9bdae)
* [bowerパッケージをbundlerで管理するRails Assetsを使ってみた](http://qiita.com/joker1007/items/8d2fa9960f3d53b8cb57)
* [AngularJSリファレンス](http://www.amazon.co.jp/gp/product/B00NE7JV2O?ie=UTF8&camp=1207&creative=8411&creativeASIN=B00NE7JV2O&linkCode=shr&tag=masayuki5210-22)

## 使ったライブラリとか

元ネタと大きく違うところ

* [Rails Assets](https://rails-assets.org/)
* [AngularJS: API: $resource](https://docs.angularjs.org/api/ngResource/service/$resource)
* [angular-ui/ui-sortable](https://github.com/angular-ui/ui-sortable)