Results for tag "bootstrap"

YEOMANを使ってMEAN(MongoDB+Express+AngularJS+Node.js)を作成しよう

YEOMANを使ってMEANを作成する手順と開発チュートリアルを説明する。
MEANとはMongoDB+Express+AngularJS+Node.jsを組み合わせたWebアプリケーションである。
YEOMANとはフレームワークを簡単に作成できるツールで、node.jsのパッケージとして提供されている。
MEANのフレームワークにはangular-fullstakを使う。
※Yeomanで用意されているジェネレーターは沢山有るがMEANを作成できるジェネレーターで一番人気がangular-fullstak
 
ほとんどの手順は下記URLに従う。
YEOMANを使ってMEAN(MongoDB + Express.js + Angular.js + Node.js)のWebアプリケーションを作る
しかし、上記URLはYEOMANのバージョンが古いためか、チュートリアルの内容が現状のバージョンに沿わないため、下記であらためて説明する。
 
なお、ここで想定している開発環境は下記の通り。
Lubuntu(Ubuntu) 14.04
Node.js v4.2.3
yo 1.6.0
MongoDB 3.2.0
※ 上記以外のバージョンではチュートリアルの内容が沿わない場合がある
 

0. 事前準備

まずはangular-fullstakに必要なアプリをインストールする。
 

0.1. Node.jsのインストール

下記URL参照
Ubuntu 14.04 に Node.jsをインストールする
 

0.2. MongoDBのインストール

下記URL参照
Install MongoDB Community Edition on Ubuntu
 

0.3. Rubyとgemのインストール

下記URL参照
UbuntuにRubyGemsをインストールする
※なお、フレームワークにSassを使わない場合はRubyとgemをインストールする必要はない。
 

0.4. Sassのインストール

端末から下記コマンドを実行する

gem install sass

 

0.5. Yeomanのインストール

npm install -g yo grunt-cli bower

※Yeomanとはyo, grunt, bowerを組み合わせたもの
 

0.6. angular-fullstakのインストール

npm install -g generator-angular-fullstack
npm update -g

※yoが1.4.1以上でないとangular-fullstackが動かないのでアップデートすること
 

0.7. node-inspectorのインストール

デバッガを使う場合は下記コマンドを実行する。

npm install -g node-inspector

以上で事前準備は完了した。
次にMEANアプリの開発チュートリアルを説明する。
 

1. MEANアプリの雛形を作成する

yoを使ってMEANアプリを作成する。
アプリを作る場所はユーザーディレクトリの配下にする。
※ちなみにVMWareなどのバーチャルPCでの共有フォルダにはアプリは作れない。アプリ作成時にシンボリックリンクを貼るが、共有フォルダにはシンボリックリンクが貼れないため。
なお、アプリ名はsampleとする。
 

1.1. sampleディレクトリの作成

mkdir sample
cd sample

※ディレクトリ名がそのままアプリ名になる。
 

1.2. MEANアプリを作成する

yo angular-fullstack

なお、アプリの作成はウィザード方式になっており、各設定をダイアログで設定する。
各項目と選択すべき項目を列挙する。(▼が選択すべき項目 ●はチェックボックスの選択状態 ○はチェックボックスの非選択状態)

[?] What would you like to write scripts with? 
▼Babel
 TypeScript

 

[?] What would you like to write makeup with?
▼HTML
 Jade

 

[?] What would you like to write stylesheets with?
 CSS
▼Sass
 Stylus
 Less

※今回のチュートリアルではスタイルシートは弄らないのでSass以外を選んでも問題ない
 

[?] What Angular router would you like to use?
 ngRoute
▼uiRouter

 

[?] Would you like to include Bootstrap? Yes

 

[?] Would you like to include UI Bootstrap? Yes

 

[?] What would you like to use for data modeling?
●Mongoose (MongoDB)
○Sequelize (MySQL, SQLite, MariaDB, PostgreSQL)

 

[?] Would you scaffold out an authentication boilerplate? Yes

 

[?] Would you like to include additional oAuth strategies?
●Google
●Facebook
●Twitter

※今回のチュートリアルでは関係ないので何を選んでも良い
 

[?] Would you like to use socket.io? Yes

 

[?] What would you like to write tests with?
▼Jasmine
 Mocha + Chai + Sinon

 

1.3. 動作確認する

grunt serve

ブラウザが自動的に起動して下図の画面が表示されれば成功である。
yeoman.01.01
 

2. routerを作成する

yoで作られたMEANアプリにメンバー一覧ページを追加するというチュートリアルを説明する。
処理の流れとしては、
1.yoコマンドでrouterの雛形を作成する
2.routerのhtmlを編集する
3.routerのcontrolerを編集する
という流れになる。
 

2.1. ページを追加する

Ctrl + Cでgruntを止めて、下記のコマンドを実行する。

yo angular-fullstack:route member

下記のダイアログは全てデフォルトにする。
[?] Where would you like to create this route? client/app/
[?] What will the url of your route be? member
すると下記のファイルが作成される。
create client/app/member/member.js
create client/app/member/member.controller.js
create client/app/member/member.controller.spec.js
create client/app/member/member.html
create client/app/member/member.scss
 
動作確認する。

grunt serve

http://localhost:9000/memberにアクセスして、下図が表示されれば成功である。
yeoman.01.02
 

2.2. htmlを編集する

client/app/member/member.htmlを下記の通り編集する。

編集前
<div class="col-md-12">
This is the member view.
</div>

 

編集後
<h2>メンバー一覧</h2>
<ul>
  <li ng:repeat="member in members">
    {{member.name}}
  </li>
</ul>

 

2.3. ダミーデータを追加する

メンバーが正しく表示されるかを確認するためにダミーデータを追加する。(ダミーデータを後からmongoDBのデータに置き換える)
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope) {
    $scope.message = 'Hello';
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope) {
    $scope.members = [{name:'田中'},{name:'鈴木'}];
  });

 

2.4. 動作確認する

http://localhost:9000/memberにアクセスして、下図の通り表示されれば成功である。
yeoman.01.03
 

3. clientからserverをGETする

処理の流れとしては、
1.clientからserverの/api/membersをGETで呼び出し
2.serverはcontroller.index関数へルーティング
3.controller.index関数でMongoDBにアクセス
4.MongoDBがデータを返す
という流れになる。
 

3.1. APIを追加する

Ctrl + Cでgruntを止めて、下記のコマンドを実行する。

yo angular-fullstack:endpoint member

下記のダイアログは全てデフォルトにする。
[?] What will the url of your endponit to be? /api/members
すると下記のファイルが作成される。
create server/api/member/index.js
create server/api/member/member.controller.js
create server/api/member/member.model.js
create server/api/member/member.socket.js
create server/api/member/member.spec.js
 

3.2. スキーマを定義する

メンバー一覧に表示する名前を定義する。
server/api/member/member.model.jsを下記の通り編集する。

編集前
'use strict';

var mongoose = require('bluebird').promisifyAll(require('mongoose'));

var MemberSchema = new mongoose.Schema({
  name: String,
  info: String,
  active: Boolean
});

export default mongoose.model('Member', MemberSchema);

 

編集後
'use strict';

var mongoose = require('bluebird').promisifyAll(require('mongoose'));

var MemberSchema = new mongoose.Schema({
  name: String,
});

export default mongoose.model('Member', MemberSchema);

 

3.3. MongoDBにデータを追加する

MongoDBに初期データを追加する。
server/api/member/member.controller.jsのMember変数定義以降に下記を追加する。

編集前
'use strict';

import _ from 'lodash';
import Member from './member.model';

 

編集後
'use strict';

import _ from 'lodash';
import Member from './member.model';

Member.find({}).remove(function() {
  Member.create({
    name: '田中_2'
  }, {
    name: '鈴木_2'
  }, function(err) {
    console.log('finished populating Members');
  });
});

 

3.4. サーバーにGETが定義されていることを確認する

server/api/member/index.jsに下記の通りgetが定義されていることを確認する。

router.get('/', controller.index);

 
server/api/member/member.controller.jsに下記の通りindex関数が定義されていることを確認する。

// Gets a list of Members
export function index(req, res) {
  Member.findAsync()
    .then(respondWithResult(res))
    .catch(handleError(res));
}

※上記のコードはangular-fullstackによって自動生成される。
 

3.5. APIに接続する

clientからserverのapiを参照する。
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope) {
    $scope.members = [{name:'田中'},{name:'鈴木'}];
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });
  });

 

3.6. 動作確認する

http://localhost:9000/memberにアクセスして、下図の通り表示されれば成功である。
yeoman.01.04
 

4. clientからserverへPOSTする

処理の流れとしては、
1.clientからserverの/api/membersをPOSTで呼び出し
2.serverはcontroller.create関数へルーティング
3.controller.create関数でMongoDBにアクセス
4.MongoDBへデータを保存する
という流れになる。
 

4.1. メンバー登録フォームを追加する

メンバーページからメンバーを登録できるようにする。
client/app/member/member.htmlを下記の通り編集する。

編集前
<h2>メンバー一覧</h2>
<ul>
  <li ng:repeat="member in members">
    {{member.name}}
  </li>
</ul>

 

編集後
<div ng-controller="MemberCtrl">
  <h2>メンバー登録</h2>
  <form >
    <legend>メンバー登録</legend>
    <label>名前</label> 
    <input type="text" id="name" name="name" ng-model="member.name"> 
    <button class="btn btn-primary" ng-click="createMember()">登録</button>
  </form>

  <h2>メンバー一覧</h2>
  <ul>
    <li ng:repeat="member in members">
      {{member.name}}
    </li>
  </ul>
</div>

 

4.2. 登録ボタンのイベントハンドラーを追加する

ボタンが動作するように、イベントハンドラーを追加する。
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $scope.members.push({
          name: $scope.member.name
        });
        $scope.member.name = '';
      }
    };
  });

 

4.3. 動作確認する

http://localhost:9000/memberにアクセスして、任意の名前(例では佐藤)を入力して登録ボタンを押して、下図の通り表示されれば成功である。
yeoman.01.05
 

4.4. 登録データをPOSTする

このままではサーバーにデータが保存されていないので、データをサーバーへPOSTする。
client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $scope.members.push({
          name: $scope.member.name
        });
        $scope.member.name = '';
      }
    };
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $http.post('/api/members', $scope.member).success(function() {
          $scope.members.push({
            name: $scope.member.name
          });
          $scope.member.name = '';
        });
      }
    };
  });

 

4.5. サーバーにPOSTが定義されていることを確認する

server/api/member/index.jsに下記の通りpostが定義されていることを確認する。

router.post('/', controller.create);

 
server/api/member/member.controller.jsに下記の通りcreate関数が定義されていることを確認する。

// Creates a new Member in the DB
export function create(req, res) {
  Member.createAsync(req.body)
    .then(respondWithResult(res, 201))
    .catch(handleError(res));
}

※上記のコードはangular-fullstackによって自動生成される。
 

4.6. 動作確認する

http://localhost:9000/memberにアクセスして、任意の名前(例では佐藤)を入力して登録ボタンを押して、さらにページ更新して、下図の通り表示されれば成功である。
yeoman.01.05
 

5. socket.ioでページを同期させる

angular-fullstackではsocket.ioをページの同期に使っているようなので、それについても説明する。
処理の流れとしては、
1.clientでページ読み込み時(/api/memberをGETする時)にsocket通知によるデータ配列追加/削除処理を登録しておく。(syncUpdates関数)
2.serverではMongoDBにデータを追加した際にMongoDBのポストホック機能(Member.schema.post関数)からホックしている全てのclientへsocket通知を送信する。
3.clientではsocket通知を受け取ったら、データ配列に追加する。(データは$scope内に定義しているので、angularJSにより自動的にページ更新がかかる。)
という流れになる。
 

5.1. socket.syncUpdates()を呼び出す

client/app/member/member.controller.jsを下記の通り編集する。

編集前
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $http.post('/api/members', $scope.member).success(function() {
          $scope.members.push({
            name: $scope.member.name
          });
          $scope.member.name = '';
        });
      }
    };
  });

 

編集後
'use strict';

angular.module('sampleApp')
  .controller('MemberCtrl', function ($scope, $http, socket) {
    $scope.members = [];

    $http.get('/api/members').success(function(members) {
      $scope.members = members;
      socket.syncUpdates('member', $scope.members);
    });

    $scope.createMember = function() {
      if ($scope.member && $scope.member.name) {
        $http.post('/api/members', $scope.member).success(function() {
          $scope.member.name = '';
        });
      }
    };
  });

変更点は、
・/api/membersのGET時にsocket.syncUpdates呼び出し
・socket.syncUpdatesでメンバー一覧の更新が行われるので、createMember関数でのメンバー追加処理は削除する
 

5.2. socket.syncUpdates()の定義を確認する

/client/components/socket/socket.service.jsにsyncUpdates関数が定義されていることを確認する。

      /**
       * Register listeners to sync an array with updates on a model
       *
       * Takes the array we want to sync, the model name that socket updates are sent from,
       * and an optional callback function after new items are updated.
       *
       * @param {String} modelName
       * @param {Array} array
       * @param {Function} cb
       */
      syncUpdates: function (modelName, array, cb) {
        cb = cb || angular.noop;

        /**
         * Syncs item creation/updates on 'model:save'
         */
        socket.on(modelName + ':save', function (item) {
          var oldItem = _.find(array, {_id: item._id});
          var index = array.indexOf(oldItem);
          var event = 'created';

          // replace oldItem if it exists
          // otherwise just add item to the collection
          if (oldItem) {
            array.splice(index, 1, item);
            event = 'updated';
          } else {
            array.push(item);
          }

          cb(event, item, array);
        });

        /**
         * Syncs removed items on 'model:remove'
         */
        socket.on(modelName + ':remove', function (item) {
          var event = 'deleted';
          _.remove(array, {_id: item._id});
          cb(event, item, array);
        });
      },

※上記のコードはangular-fullstackによって自動生成される。
 

5.3. MongoDBのポストフック機能を使ってsocket通知が呼び出されていることを確認する

server/api/member/member.socket.jsにMongoDBへのデータ追加時にsocketからmember:saveが通知されることを確認する。

/**
 * Broadcast updates to client when the model changes
 */

'use strict';

var MemberEvents = require('./member.events');

// Model events to emit
var events = ['save', 'remove'];

export function register(socket) {
  // Bind model events to socket events
  for (var i = 0, eventsLength = events.length; i < eventsLength; i++) {
    var event = events[i];
    var listener = createListener('member:' + event, socket);

    MemberEvents.on(event, listener);
    socket.on('disconnect', removeListener(event, listener));
  }
}


function createListener(event, socket) {
  return function(doc) {
    socket.emit(event, doc);
  };
}

function removeListener(event, listener) {
  return function() {
    MemberEvents.removeListener(event, listener);
  };
}

※上記のコードはangular-fullstackによって自動生成される。
 

5.4. socket通知の登録処理が呼び出されていることを確認する

server/config/socketio.jsでregistar関数が呼ばれていること確認する。

// When the user connects.. perform this
function onConnect(socket) {
  // When the client emits 'info', this listens and executes
  socket.on('info', data => {
    socket.log(JSON.stringify(data, null, 2));
  });

  // Insert sockets below
  require('../api/member/member.socket').register(socket);
  require('../api/thing/thing.socket').register(socket);

}

※上記のコードはangular-fullstackによって自動生成される。
 

5.5. 動作確認する

ブラウザを2ウィンドウ開き、http://localhost:9000/memberにアクセスして、一方に任意の名前(例では藤原)を入力して登録ボタンを押して、もう一方に登録内容が反映されれば成功である。
yeoman.01.06
 

6. デバッガ(node-inspector)を起動する

デバッガの起動方法を説明しておく。
下記のコマンドを実行する。

grunt serve:debug

するとブラウザが自動起動してnode-inspectorが開く。
‘–debug-brk’オプション付きで起動されるので、必ずサーバー側のソースコードの先頭でブレイクする。

7. gruntでブラウザ指定する

起動するブラウザを変えたい場合、Gruntfile.jsを下記の通り変更する。

通常起動のブラウザを指定する方法(例ではchromium-browserを指定している)

  grunt.initConfig({
  //省略...
    open: {
      server: {
        url: '<%= express.options.domain %>:<%= express.options.port %>',
        app: 'chromium-browser'
      }
    },

デバッガのブラウザを指定する方法(例ではchromium-browserを指定している)

    nodemon: {
    //省略...
      debug: {
      //省略...
        options: {
       //省略...
          callback: function (nodemon) {
            // opens browser on initial server start
            nodemon.on('config:update', function () {
              setTimeout(function () {
                require('open')('http://localhost:8080/debug?port=5858', 'chromium-browser');
              }, 500);
            });
          }
        }
      }
    },

1

BootstrapのサンプルNestingのドロップダウンが開かない

BootstrapのサンプルNestingのドロップダウンが開かないので、その修正箇所を説明する。
 
【修正前】
http://getbootstrap.com/components/#btn-groups-nested

<div class="btn-group">
  <button type="button" class="btn btn-default">1</button>
  <button type="button" class="btn btn-default">2</button>

  <div class="btn-group">
    <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
      Dropdown
      <span class="caret"></span>
    </button>
    <ul class="dropdown-menu" role="menu">
      <li><a href="#">Dropdown link</a></li>
      <li><a href="#">Dropdown link</a></li>
    </ul>
  </div>
</div>

 
【修正後】

<div class="btn-group">
  <button type="button" class="btn btn-default">1</button>
  <button type="button" class="btn btn-default">2</button>

  <div class="btn-group">
    <div class="dropdown">
      <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
        Dropdown
        <span class="caret"></span>
      </button>
      <ul class="dropdown-menu" role="menu">
        <li><a href="#">Dropdown link</a></li>
        <li><a href="#">Dropdown link</a></li>
      </ul>
    </div>
  </div>
</div>

 
要は

<div class="btn-group">

の直下に

<div class="dropdown">

が必要。
 


0

node.jsのsocket.ioでIRCリレーサーバーを作ってherokuにデプロイしてみた

IRC Client
http://irc-client.herokuapp.com/
これはあくまでリレーサーバーであってIRCサーバーそのものではない。
図で説明するとこんな感じ。
irc.00.01
要はウェブブラウザから直接IRCサーバーには繋がらないので、両者を仲介するサーバーを作ったのだ。
 
ちなみにソースコードはgithubに置いた。
https://github.com/SimtterCom/irc-client
ソースコードのうち、リレーサーバーとIRCサーバーを繋ぐソースコード(irc.js)はKaedeさんのソースコードを使用したので、Kaedeさん作者への謝意とともにirc.jsの著作権はKaedeさん作者にあることを明記する
Node.jsで動くIRCBot、kaedeさんを作った
 
さらにちなみに開発はCloud9で開発した。
クラウドサービス使いまくって全部無料。
凄い時代だ。
 
では、IRC Clientの使い方を説明する。

画面説明

まずは、画面説明をする。
http://irc-client.herokuapp.com/にアクセスしたら下図の画面になる。
irc.01.01
 

テキスト入力欄

Host:アクセスするIRCサーバー名を指定する(デフォルトではWordPressのチャンネルがあるサーバーを設定している)
Port:IRCサーバーのポート番号(6667か6669)
User Nane:実際あまり使われることないが必要(実質どんな名前でも良い。スペースは使えない)
Real Name:これも使われることが無いが必要(スペースも使える)
Nick Name:IRCサーバーに繋がってるユーザー全てにユニークな名前でないといけない。チャットで表示されるのもこの名前
Channel Name:入りたいチャンネル名を指定する。指定したチャンネルが無い場合は自動で作られる。(デフォルトではWordPressの公式チャンネルを指定している)
Message:自分が発言したい内容を書き込む。
 

ボタン

Login:IRCサーバーにログインする。(ログインした後はボタンがLogoutに変更になる)
Logout:ログインしているIRCサーバーからログアウトする。
Join:チャンネルに参加する。(参加した後はボタンがPartに変更になる)
Part:参加しているチャンネルから離脱する。
Send:発言する。(Messageが空白だとボタンが無効になっている。Messageに何か文字を入れると有効になる)
 

表示

Channel Users:チャンネルに入っているユーザー一覧
Name:発言した人のNick Name
Message:発言内容
 

チュートリアル

次にチュートリアルを説明する。

IRCにログインする

irc.02.01
loginボタンを押してログインする
 
irc.02.02
Please Wait…ダイアログが表示されるのでしばらく待つ
 
irc.02.03
Successが表示されればログイン成功。
OKボタンを押す。
 
irc.02.11
Failedが表示されればログイン失敗。
Nicknameがすでに使われているので、違うNicknameに変更して再度Loginボタンを押す。
 

チャンネルに参加する

ログインが成功すれば次はチャンネルに参加する。
irc.02.04
Channel Nameに参加したいチャンネル名(デフォルトではWordpressの公式チャンネルになっている)を入力してJoinボタンを押す。
 
irc.02.05
Channel UsersとMessageに自分がジョインしたメッセージが表示されればジョイン成功。
 

発言する

次は実際発言してみよう。
irc.02.06
Messageに発言内容(この例ではHello!)を入力してSendボタンを押す。
 
irc.02.07
Nameに自分のニックネーム、Messageに発言内容が表示されれば発言成功。
 

チャンネルから離脱する

チャンネルからの離脱方法を説明する。
irc.02.08
Partボタンを押す。

IRCからログアウトする

IRCからのログアウト方法を説明する。
irc.02.09
チャンネルから離脱した状態でLogoutボタンを押す。
 
irc.02.10
ログイン前の状態に戻ればログアウト成功。


0