29
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AngularJSAdvent Calendar 2014

Day 8

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

Posted at

はじめに

Rails 4で作るドラッグアンドドロップで表示順を変更できるサンプルアプリ(スクリーンキャスト付き)』という記事,読まれたことがある方も多いと思います.

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

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

ソースコード

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

izumin5210/sortable-table-sandbox

デモ

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

https://sortable-table-sandbox-angular.herokuapp.com/

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

元は普通のgemで管理されていましたが,Rails Assetsに変えています.完全に趣味です.
JS/CSSのライブラリってbowerで管理したくなるやん?
(Rails Assetsについては『bowerパッケージをbundlerで管理するRails Assetsを使ってみた』を参考にするといいと思いますです)

また,AngularJSをメインに据えるにあたってjQueryを完全に駆逐してやろうと考えていたのですが…
今回のメインであるui-sortableがjQuery及びjQuery UIに依存してました.ざんねん.
(無理やりjQuery非依存に出来ないかな…)

Gemfile(jnchitoさんver.)
gem 'jquery-rails'
gem 'jquery-ui-rails'
gem 'bootstrap-sass'
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とか弄り倒してカスタマイズするときはこっちのが捗る.

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-cspng-cloakとかをいい感じにしてくれるやつ(AngularJS付属品)

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のモジュール定義するやつ

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")も書く.

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 => sortableOptionsFruitsCtrlのぷろぱてぃ).

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

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サービス(ngResource)を利用してます.
$resourceactionオプションでPATCHメソッドなsortアクションを定義.

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

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.coffeefruit.js.coffeeにいろいろ追加していくようになるので,長い目で見ると(?)こっちのが楽になるかも.
jQueryよりは処理があっちこっち散らばったりしづらい…のかな?

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

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

謝辞

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

参考文献

使ったライブラリとか

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

29
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?