読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

RailsでAngularJSを使ってTodoアプリを作成 - 3. AngularJSのコントローラーの作成

AngularJS Rails中級 連載

f:id:nipe880324:20150112194806p:plain


「RailsでAngularJSを使ってTodoアプリを作成」の連載3回目です。
前回までは、RailsにAngularJSを導入したり、UI Bootstrapの導入を行いました。

これから本格的にTodoアプリを作っていきます。
今回は、AngularJSのコントローラーを作成することで、Todoの追加、削除、完了機能を作成します。
(データの永続化はRailsのAPIと連携する必要があるので、第4回目で説明します)


* 連載記事一覧
f:id:nipe880324:20150114203508j:plain:w480

動作確認

  • Rails 4.2.0
  • AngularJS 1.3.8
  • Bootstrap 3.3.1
  • UI Bootstrap 0.12.0

目次

  1. Todoリストの雛形を作成
  2. AngularJSのコントローラーの作成
  3. Todoの追加機能
  4. Todoの削除機能
  5. Todoの完了機能

1. Todoリストの雛形を作成

では、まず、Todoリストの雛形となるHTMLを作成していきます。index.html.erbをごそっと下記の内容と入れ替えます。

<!-- app/views/templates/index.html.erb -->
<div class="container" ng-controller="TodoListCtrl" ng-init="init()">
  <h1>Todo リスト</h1>

  <div class="panel panel-success">

    <div class="panel-heading">
      <span>{{list.name}}</span>
    </div>

    <form id="new_todo">
      <div class="input-group">
        <input type="text" id="todoDescription" class="form-control input-lg" autofocus="autofocus" maxlength="255">
        <button class="btn btn-success btn-lg" type="submit">追加</button>
      </div>
    </form>

    <ul class="list-group">
      <li class="list-group-item" ng-repeat="todo in list.todos">
        <div class="todo-completed">
          <input type="checkbox">
        </div>
        <div class="todo-description">
          <span>{{todo.description}}</span>
        </div>
        <div class="todo-buttons pull-right">
          <button class="btn btn-danger btn-xs pull-right" type="button">
            <span class="glyphicon glyphicon-ban-circle"></span>
          </button>
        </div>
      </li>
    </ul>
  </div>
</div>

ng-controllerディレクティブは、AngularJSのコントローラーとHTML要素を紐づけることができます。ここでは、TodoListCtrlとこのHTMLを紐付けています。
ng-initは、ページがロードされたときに呼ばれます。TodoListCtrl内のinit()メソッドを呼ぶように設定しています。
ng-repetaは、配列などを繰り返しすることができます。li要素内をlist.todos分だけ繰り返し作成します。


デザインも追加しておきます。app/assets/stylesheets/todo_list.sassを作成し、以下の内容をコピーします。

form#new_todo
  .input-group
    width: 100%
    display: inline-flex

  .form-control:focus
    z-index: 2

  input[type=text]
    height: auto
    border-radius: 0

  button[type=submit]
    width: 140px
    border-radius: 0

.list-group-item
  width: 100%
  display: inline-flex

  &:first-child
    border-top-left-radius: 0
    border-top-right-radius: 0

  &.completed
    .todo-description span
      color: #a9a9a9
      text-decoration: line-through

  .todo-completed
    display: inline-block
    vertical-align: top
    margin-top: 7px

  .todo-list-description, .todo-description
    width: 90%
    margin-left: 10px
    line-height: 2.45em

  .todo-list-buttons, .todo-buttons
    margin-top: 5px

2. AngularJSのコントローラーの作成

次にAngularJSのコントローラーを作成します。
まず、app/assets/javascripts/controllersというフォルダを作成します。
そして、TodoListCtrl.coffeeというファイルを作ります。

# app/assets/javascripts/controllers/TodoListCtrl.coffee

# コントローラーを定義する。今はこのように記載すると覚えておけば良い。
angular.module('sampleApp').controller "TodoListCtrl", ($scope) ->

  # 初期データを用意するメソッド
  # $scope.list.name  としてアクセスできる
  # $scope.list.todos としてアクセスできる
  $scope.init = ->
    $scope.list = {
      'name'  : 'Todoリスト1',
      'todos' : [
        { 'description' : 'todo description1'},
        { 'description' : 'todo description2'}
      ]
    }


では、ここで画面を表示してみましょう。
initメソッドで定義したTodoリスト名(list.name)とTodoの概要(list.todos[x].description)が表示されています。
f:id:nipe880324:20150112204901j:plain:w480




3. Todoの追加機能

では、この調子でコントローラーにメソッドを定義していきましょう。
テキストフィールドにTodo概要を入力し、「追加」ボタンを押すとTodoをリストに追加するようにします。

まず、Todo概要をng-modelに紐付け、「追加」ボタンが押された時にTodoListCtrlのメソッドを呼び出すようにng-submitを追加します。

<!-- app/views/templates/index.html.erb -->
...
<form id="new_todo" ng-submit="addTodo(todoDescription)">
  <div class="input-group">
    <input type="text" id="todoDescription" class="form-control input-lg" autofocus="autofocus" maxlength="255" ng-model="todoDescription">
    <button class="btn btn-success btn-lg" type="submit">追加</button>
  </div>
</form>
...


ng-submitはフォームのサブミットボタン(今回は追加ボタン)が押された時に設定しているメソッドが呼び出されるAngularJSのディレクティブです。


では、TodoListCtrlにng-submitによって呼ばれるaddTodo(todoDescription)を定義します。

# app/assets/javascripts/controllers/TodoListCtrl.coffee

angular.module('sampleApp').controller "TodoListCtrl", ($scope) ->

  # 初期データを用意するメソッド
  # $scope.list.name  としてアクセスできる
  # $scope.list.todos としてアクセスできる
  $scope.init = ->
    $scope.list = {
      'name'  : 'Todoリスト1',
      'todos' : [
        { 'description' : 'todo description1'},
        { 'description' : 'todo description2'}
      ]
    }


  # todoを追加する
  $scope.addTodo = (todoDescription) ->
    # 新しいtodoを作成する
    todo = { 'description' : todoDescription }

    # initメソッドで用意したtodosの一番最初にtodoを追加する
    $scope.list.todos.unshift(todo)

    # Todo入力テキストフィールドを空にする
    $scope.todoDescription = ""


では、Todoを追加できることを確認します。
テキストフィールドにTodo概要を入力します。
f:id:nipe880324:20150112204858j:plain:w480


そして、「追加」ボタンを押すと、リストにTodoが追加されました。
f:id:nipe880324:20150112204859j:plain:w480




4. Todoの削除機能

今度は、Todoの削除を行います。
各Todoの右側の赤いボタンを押すとTodoを削除するようにします。
Todoの追加と同じで、HTMLにディレクティブを追加し、コントローラーにメソッドを追加していきます。


削除ボタン(各Todoの右側の赤いボタン)にng-clickディレクティブを追記します。
これは、ng-submitのように、リンクやボタンを押した時に、設定したメソッドを呼び出します。

<!-- app/views/templates/index.html.erb -->
...
<button class="btn btn-danger btn-xs pull-right" type="button" ng-click="deleteTodo(todo)">
  <span class="glyphicon glyphicon-ban-circle"></span>
</button>
...


次に、TodoListCtrlにng-clickによって呼ばれるdeleteTodo(todo)を定義します。

# app/assets/javascripts/controllers/TodoListCtrl.coffee

angular.module('sampleApp').controller "TodoListCtrl", ($scope) ->

  # 初期データを用意するメソッド
  $scope.init = ->
    ...

  # todoを追加する
  $scope.addTodo = (todoDescription) ->
    ...

  # todoを削除する
  $scope.deleteTodo = (todo) ->
    # indexOfメソッドでtodoのindexを探し、spliceメソッドで削除する
    $scope.list.todos.splice($scope.list.todos.indexOf(todo), 1)


では、Todoを削除できることを確認します。
削除ボタンを押すとTodoが削除されます。
f:id:nipe880324:20150112204856j:plain:w480




5. Todoの完了機能

次はTodoの完了機能を実装していきます。
Doneチェック(各Todoの左側のチェックボックス)をクリックすることでTodoの完了のON/OFF(表示が変わる)をできるようにします。

チェックボックスにng-model="todo.completed"を追加し、todo.completedの値とチェックのON/OFFが紐づくようにします。(ON = true, OFF = false)
そして、ng-class="{completed: todo.completed}"により、todo.compledの値がtrueの場合、completedというclass属性が追加され、falseのときになくなります。
ちなみに、既にcompletedというclass属性にTodoが完了したスタイリングをしています。

<!-- app/views/templates/index.html.erb -->
...
<li class="list-group-item" ng-repeat="todo in list.todos" ng-class="{completed: todo.completed}">
  <div class="todo-completed">
    <input type="checkbox" ng-model="todo.completed">
  </div>
  ...
</li>
...


次に、TodoListCtrlで作成しているTodoにcompletedという属性をfalseという値で追加します。

# app/assets/javascripts/controllers/TodoListCtrl.coffee

angular.module('sampleApp').controller "TodoListCtrl", ($scope) ->

  # 初期データを用意するメソッド
  $scope.init = ->
    $scope.list = {
      'name'  : 'Todoリスト1',
      'todos' : [
        { 'description' : 'todo description1', 'completed' : false },
        { 'description' : 'todo description2', 'completed' : false }
      ]
    }

  $scope.addTodo = (todoDescription) ->
    # todoを作成する
    todo = { 'description' : todoDescription, 'completed' : false }
    ...

  ...


では、Todoを完了できることを確認します。
チェックボックスを押すと、Todoが完了になります(スタイリングが変化します)
f:id:nipe880324:20150112204855j:plain:w480


以上です。