こんにちは!

キャスレーコンサルティングのSI(システム・インテグレーション)部の嵯峨です。

今回は、Sails.jsを利用して簡単なWEBアプリケーションを作ってみたいと思います。

Sails.jsとは

Sails.jsはMCVアーキテクチャーに基づくNode.js用フレームワークの一つで、名前の通り「Node.js用のRuby on Rails ライクなフレームワーク」です。

面倒くさい環境構築を省略し、スピーディに開発を開始できるらしい。との情報を、購入したNode.jsの書籍で目にし、

「いち早く画面作成に取り掛かりたい私にとって、とても嬉しいフレームワークになること間違いない!」と感じ、使ってみることにしました。

今回作成するアプリの概要

今回は、Sails.jsを利用し、「画面のリストの項目をドラッグ&ドロップで並び替えて、表示順をDBに反映する」WEBアプリを作成します。

ちなみに、このWEBアプリで並べ変えようと考えているのは、弊社の企業理念と行動規範である「CASM」です!

00_casm

今回は公開されている3項目までしか表示出来ないのが残念ですが、こういうものを順番通りに覚えるのって大切ですよね。

CASMについては、弊社の社長ブログにて詳しく紹介されているので、興味のある方は是非ご覧になってください。

Node.jsのインストール

Sails.js を使う前提として、Node.js の動作環境が必要です。今回はWindows7にインストールをします。

Node.jsのサイトで最新のNode.jsをDOWNLOADSからWindows Installer(x86 or x64)をダウンロードし、インストールしてください。

今回は安定版のv4.4.7(2016年6月30日時点)をインストールしました。

npmの確認

Sails.js はnpmを使用して導入します。

npmとは、Node.jsのライブラリやパッケージを管理することができるツールですが、Sails.jsを導入するにはnpm のバージョンが 1.4.0 以上である必要があります。

まずは コマンドプロンプトにてnpm のバージョンを

npm --version

で確認します。

もし、バージョンが古い場合は以下コマンドでnpmをバージョンアップします。

npm update -g npm

Sails.jsのインストール

npm を使って Sails.js をインストールします。

※コマンドプロンプトは管理者権限で実行

npm install -g sails

2分~5分程時間がかかるので、少し待ちます。

プロジェクト作成

Sail.js のインストールが正常に完了したら、sails コマンドが使えます。

さっそく1つプロジェクト(casmapp)を作ってみましょう。

コマンドプロンプトでcdコマンドでアプリケーションを作成するディレクトリに移動し、以下のように入力します

sails new casmapp

フォルダ構成

生成されたフォルダ構成を見ると、MVCに対応するそれぞれのフォルダが配置されているのが分かります。

分かりやすいですね!

展開フォルダ

Controllerの作成

さっそく画面を作ろう!といきたいところですが、 コントローラーがないと画面表示もできないので、コントローラーを作成します。

コントローラーは以下のコマンドで作成できます。

sails generate controller 名前 アクション1 アクション2

ひとまず、casmコントローラーをindexアクションがついた状態で作成します。

sails generate controller Casm index

これでもう「sails lift」でサーバー起動していれば、作成したページにアクセスできる状態になります。

sails lift

ページは以下のurlでアクセス可能です。

http://ホスト名/コントローラー名/アクション名

今回は、

http://localhost:1337/Casm/index

にてアクセスできます。

初期画面

まだ実装してない!」と表示されるので、実装を続けます。

/api/controllersに「CasmController.js」というファイルが出来ているのでindexプロパティを以下のように編集してください。

module.exports = {
  /**
   * `CasmController.index()`
   */
  index: function (req, res) {
    return res.view({
      title: 'CASM並び替え',
      no1 : '1',
      no2 : '2',
      no3 : '3',
      casm1 : 'CSVで発想する!',
      casm2 : '仕事に誇りをもつ!',
      casm3 : '良いものは良いと言える!'
    });
  }
};

res.view()メソッドを呼び出すことで、「view」フォルダ内のコントローラー名フォルダを探し、その中にあるアクション名のViewテンプレートをロードしてレンダリングします。

view()部分にオブジェクトのプロパティ名として指定されたtitle等が、Viewテンプレートの変数として利用できるようになります。

Viewの作成

コントローラー側で用意した値をHTMLに受け渡して動的に表示するため、Viewテンプレートのejsを使用します。

コマンドプロンプトを開き以下コマンドでejsをインストールしましょう。

npm install ejs

インストールが完了したら、「view」フォルダにコントローラーと同じ名前のフォルダを作り、そこにアクション名のejsを作成します。

view

■views/casm/index.ejsの中身



<h1><%=title %>

<h1>


<ul class="sortable">
    

<li id="<%=no1%>"><%=no1%><%=casm1%></li>


    

<li id="<%=no2%>"><%=no2%><%=casm2%></li>


    

<li id="<%=no3%>"><%=no3%><%=casm3%></li>


</ul>


<%= %>で書かれたタグに変数名を指定するだけで、コントローラー側で用意した値を書き出すことが出来ます。

サーバーをリスタートさせて/Casm/indexにアクセスします。

sails lift

html書き出し

今度はこのリストのデータをDBから取得出来るように準備をします。

MySQLの準備

MySQLに接続する前に以下コマンドでモジュールをインストールします。

npm install -s sails-mysql

MySQLをインストールしたら、コマンドプロンプトを管理者として実行し、rootユーザーでMySQLサーバーへ接続します。

mysql -u root -p

それでは、さっそくデータの準備を行いましょう。

1)データベースを作成

CREATE DATABASE文を使って、「casley」という名前のデータベースを作成します。

CREATE DATABASE casley;

2)テーブルの作成

CREATE TABLE文にて「casley」データベースに「casm」テーブルを作成します。

このテーブルには数値型の「id」カラムと、文字列型の「casm」カラムが含まれます。

CREATE TABLE casley.casm (id int PRIMARY KEY, casm VARCHAR(300));

3)ユーザ登録(権限とパスワードの設定)

GRANT CREATE,SELECT,INSERT,UPDATE,DELETE,DROP
 ON casley.*
 TO user@localhost
 IDENTIFIED BY 'password';

FLUSH PRIVILEGES;

4)データの追加

デーブルが作成されましたので、sql文でデータを挿入します。

INSERT INTO casley.casm (id, casm) VALUES (1, "仕事に誇りをもつ!");
INSERT INTO casley.casm (id, casm) VALUES (2, "良いものは良いと言える!");
INSERT INTO casley.casm (id, casm) VALUES (3, "CSVで発想する!");

今回は順番通りにcasmを並び変える画面を作成するのが目的なので、casmの順番は適当に入力します!!

5)データ確認

SELECT文でデータの中身を確認して、準備完了です。

mysql> select * from casley.casm;
+------+--------------------------+
| id | casm |
+------+--------------------------+
| 1 | 仕事に誇りをもつ! |
| 2 | 良いものは良いと言える! |
| 3 | CSVで発想する! |
+------+--------------------------+
3 rows in set (0.00 sec)

データベース接続設定

まず、デフォルトDBの設定を config/models.js に記述します。

module.exports.models = {

 connection:"someMysqlServer",

 migrate: 'safe',

 autoCreatedAt: false,

 autoUpdatedAt: false

};

続けてDB接続の定義をconfig/connection.jsに記述します。

    someMysqlServer: {
     adapter : 'sails-mysql',
     host    : 'localhost',
     user    : 'user',
     password: 'password',
     database: 'casley'
   },

Modelの作成

Modelは以下コマンドで生成出来ます。

sails generate model Casm

「api/models/」フォルダにCasm.jsというファイルができているので、以下のように編集します。

module.exports = {
   attributes: {

        casm:'string'

  }
};

attributesには、「カラム名:型名」という形で値を用意します。

Viewの修正

Viewテンプレートを修正します。

■views/casm/index.ejs

↓ 修正前



<h1><%=title %>

<h1>


<ul class="sortable">
    

<li id="<%=no1%>"><%=no1%><%=casm1%></li>


    

<li id="<%=no2%>"><%=no2%><%=casm2%></li>


    

<li id="<%=no3%>"><%=no3%><%=casm3%></li>


</ul>


↓ 以下のように修正します。



<h1><%=title %>

<h1>


<ul class="sortable">
    <% for(var i = 0; i < datas.length; i++){ %>
    

<li id="<%=datas[i].id%>"><%=datas[i].id%><%=datas[i].casm%></li>


    <% } %>
</ul>


for文で変数datasのデータを順に表示するよう変更しました。

Controllerの修正(アクション処理)

続いて、DBから値を取得するよう、アクションの処理を修正します。

↓ 修正前

module.exports = {
  /**
   * `CasmController.index()`
   */
  index: function (req, res) {
    return res.view({
      title: 'CASM並び替え',
      no1 : '1',
      no2 : '2',
      no3 : '3',
      casm1 : 'CSVで発想する!',
      casm2 : '仕事に誇りをもつ!',
      casm3 : '良いものは良いと言える!'
    });
  }
};

↓ 以下のように修正します。

module.exports = {
 /**
 * `CasmController.index()`
 */
 index: function (req, res) {
     Casm.find().exec(function (err, records) {
         return res.view({
             title: 'CASM並び替え',
             datas: records
         });
     });
 }
};

title以外のデータを全てDBから取得するように変更しています。

データの操作はModelオブジェクトのメソッドを利用して行います。

Casmテーブルのデータを全検索する場合はCasmオブジェクトのfindメソッドを引数無しで呼び出します。

データの取得はfindの後にexecを実行することで、第一引数にエラー情報、第二引数に取得したデータが渡されます。第二引数はModelオブジェクトの配列になります。

「sails lift」コマンドでサーバーをリスタートさせて/Casm/indexにアクセスします

sails lift

dbから取得

DBの順番通りにデータが出力されるようになりました!

Sails.jsでJavascriptとCSSを読み込む

準備が整ったので、リストの項目をドラッグ&ドロップで並び替えられるようにしましょう!

まずページごとに外部のJavascriptとCSSを、ページに読み込めるようにするため、views/layout.ejsに以下の記述を追加します。

今回は、\assets\styles\casm.cssと、\assets\js\casm.jsを作成し、viewに取り込みます。

■views/layout.ejs

・・・・・

    <!--STYLES-->
    <link rel="stylesheet" href="/styles/casm.css"><!--★「casm.css」を追加-->
    <link rel="stylesheet" href="/styles/importer.css">
    <!--STYLES END-->

・・・・・

    <!--SCRIPTS-->
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22%2Fjs%2Fdependencies%2Fsails.io.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22%2Fjs%2Fcasm.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" /><!--★「casm.js」を追加-->
    <!--SCRIPTS END-->

1)jqueryとjquery-uiの読み込み↓

■views/casm/index.ejs

<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22http%3A%2F%2Fcode.jquery.com%2Fjquery-1.8.3.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22http%3A%2F%2Fcode.jquery.com%2Fui%2F1.11.3%2Fjquery-ui.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />

2)外部JavascriptとCSSを読み込み↓

■views/casm/index.ejs

<% script('/js/casm.js') -%>
<% stylesheet('/styles/casm.css') -%>

3)ドラッグ&ドロップ可能リストをformで囲む↓

■views/casm/index.ejs



<h1><%=title %>

<h1>


<form action="/casm/sort" method="post">


<ul class="sortable">
    <% for(var i = 0; i < datas.length; i++){ %>
    

<li id="<%=datas[i].id%>" name="<%=datas[i].id%>"><%=datas[i].id%><%=datas[i].casm%></li>


    <% } %>
</ul>


<input type="hidden" id="result" name="result" />
</form>


action=”/casm/sort” 、method=”post”にフォーム送信すると、リストのIDが、上から順番にコンマ区切りの文字列として格納されるように記述します。
例えば、3,2,1 と並び替えれば、「3,2,1」が結果として返ります。

■views/casm/index.ejs(完成版)

<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22http%3A%2F%2Fcode.jquery.com%2Fjquery-1.8.3.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22http%3A%2F%2Fcode.jquery.com%2Fui%2F1.11.3%2Fjquery-ui.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />
<% script('/js/casm.js') -%>
<% stylesheet('/styles/casm.css') -%>


<h1><%=title %>

<h1>


<form action="/casm/sort" method="post">


<ul class="sortable">
    <% for(var i = 0; i < datas.length; i++){ %>
    

<li id="<%=datas[i].id%>" name="<%=datas[i].id%>"><%=datas[i].id%><%=datas[i].casm%></li>


    <% } %>
</ul>


<input type="hidden" id="result" name="result" />
</form>


外部Javascriptの作成

■\assets\js\casm.js

 $(function() {
      $(".sortable").sortable();
      $(".sortable").disableSelection();
      $(".sortable").sortable({
        update: function(ev, ui) {
          var result = $(".sortable").sortable("toArray");
          $("#result").val(result);
          $("form").submit();
        }
      });
  });

このJavaScriptでリストの項目をドラッグ&ドロップできるようになります!

sortable の toArray メソッドを指定することで、要素の id を配列として取得できます。

今回は、並び替えが終了したタイミングでsubmitするため、DOM要素の位置が変更されたときに呼び出される”update”イベントを使用しています。

Controllerにsortアクションを追加する。

最後に、api\controllers\CasmController.jsにsortアクションを追加して、並び替えたリストをDBに書き込む処理を記載します。

■api\controllers\CasmController.js

 sort: function (req, res) {
       //req.body.result内に、リストのIDが上から順番にコンマ区切りの文字列で格納されています。
        var result = req.body.result;
        console.log('取得した配列:'+result);

        //配列に格納
        var result_array = result.split(',');

        /* ここからDBに格納する処理 */
        //1_casmデータを全て取得し、引数recordsに格納しておく。
        Casm.find().exec(function (err, records){
        //2_casmデータを全て削除する。
        Casm.destroy({}).exec(function createCB(err){
                //3_recordsを元に、casmデータを再作成する。
                var insertNum = 0;//idに対して番号を1からふる
                for(var num = 0 ; num < result_array.length ;num++){
                    //3-1_ログ書き出し
                    console.log(records[result_array[num]-1].id + ':' + records[result_array[num]-1].casm +
                        '→' + records[num].id + ':' + records[result_array[num]-1].casm);
                    //3-2_データ追加処理
                    Casm.create({
                        id  :records[num].id,
                        casm:records[result_array[num]-1].casm
                    }).exec(function insertRow(err){
                        insertNum++;
                        if(insertNum  == result_array.length){
                            //4_casmへリダイレクト
                            res.redirect('/casm');
                        }
                    });
                }
            });
        });
 },

■api\controllers\CasmController.js(完成版)

module.exports = {
 /**
 * `CasmController.index()`
 */
 index: function (req, res) {
     Casm.find().exec(function (err, records) {
         return res.view({
             title: 'CASM並び替え',
             datas: records
         });
     });
 },

 sort: function (req, res) {
        //req.body.result内に、リストのIDが上から順番にコンマ区切りの文字列で格納されています。
        var result = req.body.result;
        console.log('取得した配列:'+result);

        //配列に格納
        var result_array = result.split(',');

        /* ここからDBに格納する処理 */
        //1_casmデータを全て取得し、引数recordsに格納しておく。
        Casm.find().exec(function (err, records){
        //2_casmデータを全て削除する。
        Casm.destroy({}).exec(function createCB(err){
                //3_recordsを元に、casmデータを再作成する。
                var insertNum = 0;//idに対して番号を1からふる
                for(var num = 0 ; num < result_array.length ;num++){
                    //3-1_ログ書き出し
                    console.log(records[result_array[num]-1].id + ':' + records[result_array[num]-1].casm +
                        '→' + records[num].id + ':' + records[result_array[num]-1].casm);
                    //3-2_データ追加処理
                    Casm.create({
                        id  :records[num].id,
                        casm:records[result_array[num]-1].casm
                    }).exec(function insertRow(err){
                        insertNum++;
                        if(insertNum  == result_array.length){
                            //4_casmへリダイレクト
                            res.redirect('/casm');
                        }
                    });
                }
            });
        });
 },
 _config:{}
};

動かしてみる。

これですべての実装が完了したので、「sails lift」コマンドでサーバーをリスタートさせて/Casm/indexにアクセスします。

初期表示

これは完全に順番が間違っているので、並び変えます!

ドラッグ

ドラッグして、、、

ならべ替え後

並べ変え完了!これは正しい!

ログ

ログも正しく出力されているようです。

mysql> select * from casley.casm;
+----+--------------------------+
| id | casm                     |
+----+--------------------------+
|  1 | CSVで発想する!          |
|  2 | 仕事に誇りをもつ!       |
|  3 | 良いものは良いと言える! |
+----+--------------------------+
3 rows in set (0.00 sec)

DBも書き換わっていることを確認して終了です!

お疲れさまでした!!

Sails.jsを使用してみた感想

実際に使ってみたところ、情報通り、私のような初心者でも簡単に環境構築をすることができました。モック作成などに重宝しそうですねー。

今回はcssまで手が回りませんでしたが、画面作成好きな自分にとっては手っ取り早く画面作成に取り掛かれるというのは、とてもワクワクします。
一方で、バージョンアップの速度が速いのか、参考資料やネットの情報通りに実装していても動かない事が多く、大分苦労しました。

もうちょい情報が(日本語で)探しやすいと嬉しいですねー。