vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

Angularにも触れてみる 2

前回の続きです。

やったこと

DBからデータを取得する

サーバ側からデータを送信するのも、Javascriptからデータを送信する時と同じく$httpを使用できるのですが、 そもそもログイン時のやりとりや低レベルな部分での操作が必要ないなら$resourceを使った方が良いようで(低レベルな部分を隠蔽できるため)、今回はそちらを使ってみました。

準備

  1. https://code.angularjs.org/からダウンロードするなどして、「angular-route.min.js」をページから追加で読み込めるようにします。
  2. Javascriptで、$resourceを使ってサーバにアクセスするRESTの部分をインスタンス化します。
  3. Step2をコントローラから呼び出します。

$resourceを使ってデータを取得する

addnewpost.js

(function () {
  // Angularモジュールを作成する.
  var newPostApp = angular.module('NewPostApp', ['ngResource']);
  // タグ情報の取得、新規追加.
  newPostApp.factory('TagList', ['$resource', function($resource)
  {
    return $resource('/gettaglist', {}, {
      get: {method: 'GET', cache: true, isArray: true},
      save: {method: 'POST', cache: false, isArray: false}
    });
  }]);
  // コントローラーの作成.
  newPostApp.controller('NewPostCtrl',[
    '$scope', '$http', 'TagList',
    function($scope ,$http, TagList) {
      // 既存のタグを取得.
      $scope.getTagLists = function()
      {
        TagList.get({},
          function success(data){
            $scope.existedTags = data;
          },
          function error(data){
            alert('error ' + data);
          });
      };
    };
〜省略〜
    // ページロード時にカテゴリ情報を取得.
    $scope.getTagLists();
〜省略〜
  • 依存性注入として、ngResourceを指定しています。
  • 「newPostApp.factory」でタグ情報を取得するget、(今回は特に触りませんが)新規追加をするsaveをインスタンスとして作成し、コントローラから利用できるようにします。
  • コントローラでは「$scope」などと同じように「newPostApp.factory」で作成した「TagList」を読み込んでおり、「TagList.get()」のように使用することができます。
  • コントローラが持つfunctionの中に、「$scope.getTagLists();」を加えることで、ページロード時に「$scope.getTagLists = function()」を実行できるようになります。

既存のタグ情報をJSONとして返す

app.rb

〜省略〜
get '/gettaglist' do
  aryTagList = Tag.order(tag_id: 'desc')

  aryJsonList = []
  aryTagList.each do |tag|
    aryJsonList << {
      tagid: tag.tag_id,
      tagname: tag.tag_name
    }
  end
  aryJsonList.to_json
end
〜省略〜
  • DBに登録されているタグ情報をすべて返しています(数が多くなりすぎるようなら調整したほうが良いかもしれませんね)。

取得したタグ情報をページに表示する

newpost.slim

DOCTYPE
html lang='ja' ng-app='NewPostApp'
  head
    meta charset='utf-8'
    title Add New Post
  body
    header
      a.header_title href='/' Vaguely
    body
      main#new ng-controller='NewPostCtrl'
        form ng-submit='addPost()'
          input#new_title type='text' placeholder='タイトルを入力' ng-model='postTitle' required='true'
          textarea#new_article placeholder='本文を入力' ng-model='postArticle' required='true'
          input#new_category type='text' ng-model='postNewCategory'
          div ng-repeat='existedTag in existedTags'
            input type='checkbox' value='{{existedTag.tagid}}' ng-model='existedTag.checked' id='{{"tag_" + existedTag.tagid}}'
            label for='{{"tag_" + existedTag.tagid}}' ng-bind='existedTag.tagname'
          input#new_btn_submit type='submit' value='投稿'
          input#new_cancel type='reset' value='キャンセル'
      script src='/js/angular.min.js'
      script src='/js/angular-resource.min.js'
      script src='/js/addnewpost.js'
  • タグ名をチェックボックスでそれぞれ表示します(今回はやっていませんがこれと同じように、 CSSやラベルを利用して、タグ名を表示して選択中は色を変える、というような表示にしようかなと思っています。
  • Angularで配列を展開するには「ng-repeat」を使用します。

チェックボックスのIDと、ラベルのforを設定する

「{{}}」の中で四則計算ができるため、「tag_」+タグのIDをIDとすることにしました。
※現状だと単純にタグIDでも問題ない気もしますが、今後他要素のIDを設定した時に余計な苦労を産みそうなので。

チェックボックスにチェックが入っているかを調べる

地味に苦労した部分です...。

「ng-repeat」を使って数が決まっていない項目の値を取得したい場合、「ng-model」に「tag1」「tag2」のように入れていくのは面倒そうです。
かといってチェックしている値を一括で勝手に取ってきてくれるような便利メソッドも無いようで...。

サーバから取得したタグ情報を格納した「existedTags」に、「checked」という要素を追加することで対応しました。

特に上記のslim上で「ng-model」にセットしている以外は前もって要素を追加する必要はなく、 「NullReferenceException」などで日頃怒られまくっている自分からすると軽く衝撃を受けましたw

なお、ページ表示後一度もチェックを付け外ししないで「submit」ボタンを押すと、 「existedTag.checked」の中身はundefinedになりますが、単純にif文で利用するだけなので特には問題にならないかと。

というところでJavascriptのコード全文です。

addnewpost.js

(function () {
  // Angularモジュールを作成する.
  var newPostApp = angular.module('NewPostApp', ['ngResource']);
  newPostApp.factory('TagList', ['$resource', function($resource)
  {
    return $resource('/gettaglist', {}, {
      get: {method: 'GET', cache: true, isArray: true},
      save: {method: 'POST', cache: false, isArray: false}
    });
  }]);
  // コントローラーの作成.
  newPostApp.controller('NewPostCtrl',[
    '$scope', '$http', 'TagList',
    function($scope ,$http, TagList) {
      // 既存のタグを取得.
      $scope.getTagLists = function()
      {
        TagList.get({},
          function success(data){
            $scope.existedTags = data;
          },
          function error(data){
            alert('error ' + data);
          });
      };
      $scope.addPost = function()
      {
        var postTags = [];
        // チェックされたタグのIDを配列に追加する.
        angular.forEach($scope.existedTags, function(existedTag)
        {
          if(existedTag.checked)
          {
            postTags.push(existedTag.tagid);
          }
        });
        // Postでデータを送信する(タイトル、記事本文、タグIDをJSONとして送信).
        $http.post('/createnewpost', {title: $scope.postTitle, article: $scope.postArticle, tags: postTags}).
            success(function(data){
              console.log('success');
            }).
            error(function(data, status, headers, config) {
              console.log('fail');
            });
      };
      // ページロード時にカテゴリ情報を取得.
      $scope.getTagLists();
    }
  ]);
}).call(this);

Postで送信したデータの受け取り(サーバ側)

app.rb

〜省略〜
post '/createnewpost' do
  jsnParams = ActiveSupport::JSON.decode(request.body.read)
  puts jsnParams['title']
  puts jsnParams['article']
  jsnParams['tags'].each do |selectedtag|
    puts selectedtag
  end
  puts 'end'
end
〜省略〜

※なぜか「puts 'end'」の部分を外してループ処理で終わるようにすると、 「Rack::Lint::LintError: Status must be >=100 seen as integer」というエラーが発生します。
今回はデータを受け取ったあとDBへの登録処理を行うため実際には問題は起こらなくなるとは思いますが、気になる問題ですねぇ…。

参照

Learning AngularJS - O'REILLY

$resource

ページロード時のfunction実行

ng-repeat

チェックボックスの値を取得する