序章

開発担当者は、プログラムの構築を可能な限り簡単にするためにたゆまぬ努力をしています。 NodeとCordovaが導入されて以来、JavaScript、Web、およびモバイルアプリの開発者コミュニティは劇的に増加しました。 Webデザインのスキルを持っている開発者は、Node.jsの助けを借りて、少ない労力で、アプリケーションにJavaScriptを使用してサーバーを展開できます。

モバイル愛好家は、Cordovaの助けを借りて、JavaScriptだけを使用してリッチなハイブリッドアプリを構築できるようになりました。 今日、それは古いニュースですが、JavaScriptを使用してデスクトップスタンドアロンアプリケーションを構築する機能を共有できることに興奮しています

Node WebKit 通常の記述:「node-webkit」または「NW.js」は、Node.jsとChromiumに基づくアプリランタイムであり、HTML、CSS、JavaScriptのみを使用してOSネイティブアプリを開発できます。

簡単に言えば、Node WebKitは、Web開発者としてのスキルを活用して、Mac、Windows、およびLinuxでgrunt / gulp(必要に応じて)ビルドコマンドだけで快適に実行されるネイティブアプリケーションを構築するのに役立ちます。

この記事では、Node WebKitの使用に重点を置いていますが、物事をより面白くするために、他のすばらしいソリューションを追加します。

  • Socket.ioNode.js用のリアルタイムライブラリ
  • Angular Material :AngularによるGoogleのマテリアルデザインの実装
  • MEAN:MEANは、Mongo、Express、Angular、Nodeの機能を組み合わせて強力なアプリを構築するという概念にすぎません。

さらに、アプリケーションには3つのセクションがあります。

  • サーバー
  • デスクトップ(クライアント)
  • ウェブ(クライアント)

ここではWebセクションについては説明しませんが、テストプラットフォームとして機能しますが、コードが提供されますのでご安心ください。

前提条件

レベル:中級( MEAN の知識が必要です)

インストール

アプリケーションのnode-webkitおよびその他の依存関係を取得する必要があります。 幸い、ワークフローを簡単にするフレームワークがあり、そのうちの1つを使用してアプリケーションの足場を作り、実装にさらに集中します。

YoとSlushは人気のあるジェネレーターであり、これらのいずれも機能します。 Slush を使用しますが、必要に応じてYoを使用してください。 Slushをインストールするには、ノードとnpmがインストールされて実行されていることを確認してください

  1. npm install -g slush gulp bower slush-wean

このコマンドは、次のものをシステムにグローバルにインストールします。

  • スラッシュ:足場ツール
  • slush-wean:ノードWebKitのジェネレーター
  • gulp :タスクランナー
  • bower:フロントエンドの依存関係用

YOと同じように、次を使用してディレクトリとスキャフォールドをアプリにします。

  1. mkdir scotch-chat
  2. cd scotch-chat
  3. slush wean

以下のコマンドを実行すると、私たちが待ち望んでいたものが一目でわかります。

  1. gulp run

画像はアプリの読み込みを示しています。 ジェネレーターの作成者は、単純な読み込みアニメーションを備えた素敵なテンプレートを提供するのに十分寛大でした。 見栄えを良くするために、読み込み中のテキストをScotchのロゴに置き換えました。

Slushの自動化に慣れていない場合は、GitHubのNodeWebKitに直接アクセスできます。

アプリをセットアップしたので、空ですが、休憩を取り、サーバーを準備します。

サーバー

サーバーは基本的に、モデルルート、およびソケットイベントで構成されています。 可能な限りシンプルに保ち、記事の最後にある指示に従ってアプリを自由に拡張できます。

ディレクトリ構造

PCのお気に入りのディレクトリにフォルダを設定しますが、フォルダの内容が次のようになっていることを確認してください。

    |- public
        |- index.html
    |- server.js
    |- package.json

依存関係

ルートディレクトリにあるpackage.jsonファイルで、アプリケーションを記述し、アプリケーションの依存関係を含めるJSONファイルを作成します。

    {
      "name": "scotch-chat",
      "main": "server.js",
      "dependencies": {
        "mongoose": "latest",
        "morgan": "latest",
        "socket.io": "latest"
      }
    }

それで十分です。 これは最小限の設定であり、シンプルで短くしています。 ディレクトリルートでnpm installを実行して、指定した依存関係をインストールします。

  1. npm install

サーバーセットアップの開始

手を汚す時が来ました! まず、server.jsにグローバル変数を設定します。これにより、既にインストールされているアプリケーションの依存関係が保持されます。

server.js
    // Import all our dependencies
    var express  = require('express');
    var mongoose = require('mongoose');
    var app      = express();
    var server   = require('http').Server(app);
    var io       = require('socket.io')(server);

わかりました、私は私の言葉を守りませんでした。 変数は依存関係を保持しているだけでなく、使用できるように構成しているものもあります。

静的ファイルを提供するために、expressは静的ファイルフォルダーの構成に役立つメソッドを公開します。 簡単です:

server.js
    ...

    // tell express where to serve static files from
    app.use(express.static(__dirname + '/public'));

次は、データベースへの接続を作成します。 私はローカルのMongoDBを使用していますが、これはMongoデータベースによってホストされていることがわかるため、明らかにオプションです。 Mongoose は、MongoDBでの作業をはるかに簡単にする素晴らしいAPIを公開するノードモジュールです。

server.js
    ...

    mongoose.connect("mongodb://127.0.0.1:27017/scotch-chat");

Mongooseを使用して、データベーススキーマとモデルを作成できるようになりました。 また、別のドメインからアクセスするため、アプリケーションでCORSを許可する必要があります。

server.js
    ...

    // create a schema for chat
    var ChatSchema = mongoose.Schema({
      created: Date,
      content: String,
      username: String,
      room: String
    });

    // create a model from the chat schema
    var Chat = mongoose.model('Chat', ChatSchema);

    // allow CORS
    app.all('*', function(req, res, next) {
      res.header("Access-Control-Allow-Origin", "*");
      res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
      res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key');
      if (req.method == 'OPTIONS') {
        res.status(200).end();
      } else {
        next();
      }
    });

サーバーには3つのルートがあります。 インデックスファイルを提供するルート、チャットデータを設定するルート、および部屋名でフィルタリングされたチャットメッセージを提供する最後のルート:

server.js
    /*||||||||||||||||||||||ROUTES|||||||||||||||||||||||||*/
    // route for our index file
    app.get('/', function(req, res) {
      //send the index.html in our public directory
      res.sendfile('index.html');
    });

    //This route is simply run only on first launch just to generate some chat history
    app.post('/setup', function(req, res) {
      //Array of chat data. Each object properties must match the schema object properties
      var chatData = [{
        created: new Date(),
        content: 'Hi',
        username: 'Chris',
        room: 'php'
      }, {
        created: new Date(),
        content: 'Hello',
        username: 'Obinna',
        room: 'laravel'
      }, {
        created: new Date(),
        content: 'Ait',
        username: 'Bill',
        room: 'angular'
      }, {
        created: new Date(),
        content: 'Amazing room',
        username: 'Patience',
        room: 'socet.io'
      }];

      //Loop through each of the chat data and insert into the database
      for (var c = 0; c < chatData.length; c++) {
        //Create an instance of the chat model
        var newChat = new Chat(chatData[c]);
        //Call save to insert the chat
        newChat.save(function(err, savedChat) {
          console.log(savedChat);
        });
      }
      //Send a resoponse so the serve would not get stuck
      res.send('created');
    });

    //This route produces a list of chat as filterd by 'room' query
    app.get('/msg', function(req, res) {
      //Find
      Chat.find({
        'room': req.query.room.toLowerCase()
      }).exec(function(err, msgs) {
        //Send
        res.json(msgs);
      });
    });

    /*||||||||||||||||||END ROUTES|||||||||||||||||||||*/

私が信じる最初のルートは十分に簡単です。 index.htmlファイルをユーザーに送信するだけです。

2番目の/setupは、アプリケーションの最初の起動時に1回だけヒットすることを目的としています。 テストデータが必要ない場合はオプションです。 基本的に、チャットメッセージの配列(スキーマに一致する)を作成し、それらをループして、データベースに挿入します。

3番目のルート/msgは、部屋名でフィルタリングされ、JSONオブジェクトの配列として返されるチャット履歴をフェッチする役割を果たします。

サーバーの最も重要な部分は、リアルタイムロジックです。 単純なアプリケーションの作成に取り組んでいることを念頭に置いて、ロジックは包括的に最小限に抑えられます。 続いて、次のことを行う必要があります。

  • アプリケーションがいつ起動されるかを知る
  • 接続時に利用可能なすべての部屋を送信する
  • ユーザーが接続するのを聞いて、デフォルトの部屋に割り当てます
  • 彼らが部屋を切り替えるときに耳を傾ける
  • そして最後に、新しいメッセージを聞いて、それが作成された部屋の人にのみメッセージを送信します

したがって:

server.js
    /*||||||||||||||||SOCKET|||||||||||||||||||||||*/
    //Listen for connection
    io.on('connection', function(socket) {
      //Globals
      var defaultRoom = 'general';
      var rooms = ["General", "angular", "socket.io", "express", "node", "mongo", "PHP", "laravel"];

      //Emit the rooms array
      socket.emit('setup', {
        rooms: rooms
      });

      //Listens for new user
      socket.on('new user', function(data) {
        data.room = defaultRoom;
        //New user joins the default room
        socket.join(defaultRoom);
        //Tell all those in the room that a new user joined
        io.in(defaultRoom).emit('user joined', data);
      });

      //Listens for switch room
      socket.on('switch room', function(data) {
        //Handles joining and leaving rooms
        //console.log(data);
        socket.leave(data.oldRoom);
        socket.join(data.newRoom);
        io.in(data.oldRoom).emit('user left', data);
        io.in(data.newRoom).emit('user joined', data);

      });

      //Listens for a new chat message
      socket.on('new message', function(data) {
        //Create message
        var newMsg = new Chat({
          username: data.username,
          content: data.message,
          room: data.room.toLowerCase(),
          created: new Date()
        });
        //Save it to database
        newMsg.save(function(err, msg){
          //Send message to those connected in the room
          io.in(msg.room).emit('message created', msg);
        });
      });
    });
    /*||||||||||||||||||||END SOCKETS||||||||||||||||||*/

次に、従来のサーバーが起動します。

server.js
    server.listen(2015);
    console.log('It\'s going down in 2015');

index.htmlに適切なHTMLを入力して、node server.jsを実行します。 localhost:2015は、HTMLのコンテンツを提供します。

ノードWebKitクライアント

現在実行中のサーバーを作成するために残したものを掘り下げる時間です。 このセクションは、HTML、CSS、JS、およびAngularの日常的な知識が必要なだけなので非常に簡単です。

–>

ディレクトリ構造

作成する必要はありません! それが発電機のインスピレーションだったと思います。 調べたい最初のファイルはpackage.jsonです。

Node WebKitを実行するには、基本的に2つの主要なファイルが必要です。

  1. エントリポイント(index.html
  2. package.jsonは、エントリポイントがどこにあるかを示します

package.jsonには、メインがindex.htmlの場所であり、"window":の下に一連の構成があり、そこからすべてを定義することを除いて、私たちが慣れている基本的なコンテンツがありますアイコン、サイズ、ツールバー、フレームなどを含むアプリのウィンドウのプロパティ。

依存関係

サーバーとは異なり、bowerを使用して依存関係をロードします。これは、クライアントアプリケーションであるためです。 bower.jsonの依存関係を次のように更新します。

    "dependencies": {
      "angular": "^1.3.13",
      "angular-material" : "^0.10.0",
      "angular-socket-io" : "^0.7.0",
      "angular-material-icons":"^0.5.0",
      "animate.css":"^3.0.0"
    }

ショートカットの場合は、次のコマンドを実行するだけです。

  1. bower install --save angular angular-material angular-socket-io angular-material-icons animate.css

フロントエンドの依存関係ができたので、views/index.ejsを次のように更新できます。

index.ejs
    <html><head>
        <title>scotch-chat</title>
        <link rel="stylesheet" href="css/app.css">
        <link rel="stylesheet" href="css/animate.css">
        <link rel="stylesheet" href="libs/angular-material/angular-material.css">

        <script src="libs/angular/angular.js"></script>
        <script src="http://localhost:2015/socket.io/socket.io.js"></script>
        <script type="text/javascript" src="libs/angular-animate/angular-animate.js"></script>
        <script type="text/javascript" src="libs/angular-aria/angular-aria.js"></script>
        <script type="text/javascript" src="libs/angular-material/angular-material.js"></script>
        <script type="text/javascript" src="libs/angular-socket-io/socket.js"></script>
        <script type="text/javascript" src="libs/angular-material-icons/angular-material-icons.js"></script>

        <script src="js/app.js"></script>
    </head>
    <body ng-controller="MainCtrl" ng-init="usernameModal()">
        <md-content>
            <section>
                <md-list>
                    <md-subheader class="md-primary header">Room: {{room}} <span align="right">Userame: {{username}} </span> </md-subheader>

                    <md-whiteframe ng-repeat="m in messages" class="md-whiteframe-z2 message" layout layout-align="center center">
                        <md-list-item class="md-3-line">
                            <img ng-src="img/user.png" class="md-avatar" alt="User" />
                            <div class="md-list-item-text">
                                <h3>{{ m.username }}</h3>
                                <p>{{m.content}}</p>
                            </div>
                        </md-list-item>
                    </md-whiteframe>

                </md-list>
            </section>

            <div class="footer">


                <md-input-container>
                    <label>Message</label>
                    <textarea ng-model="message" columns="1" md-maxlength="100" ng-enter="send(message)"></textarea>
                </md-input-container>


            </div>

        </md-content>
    </body>
    </html>

すべての依存関係とカスタムファイル(app.cssとapp.js)を含めました。 注意事項:

  • 角度のある素材を使用しており、そのディレクティブによってコードが「HTML6」のように見えます。
  • ng-repeatを使用してメッセージスコープをループし、その値をブラウザにレンダリングしています
  • 後で見るディレクティブは、ENTERキーが押されたときにメッセージを送信するのに役立ちます
  • initでは、ユーザーは優先ユーザー名を求められます
  • AngularでSocket.ioを簡単に操作できるように含まれているAngularライブラリがあります。

アプリケーション

このセクションの主要部分は、app.jsファイルです。 これは、Node WebKit GUIと対話するためのサービス、ENTERキー押下およびコントローラー(メインおよびダイアログ)を処理するためのディレクティブを作成します。

app.js
    //Load angular
    var app = angular.module('scotch-chat', ['ngMaterial', 'ngAnimate', 'ngMdIcons', 'btford.socket-io']);

    //Set our server url
    var serverBaseUrl = 'http://localhost:2015';

    //Services to interact with nodewebkit GUI and Window
    app.factory('GUI', function () {
        //Return nw.gui
        return require('nw.gui');
    });
    app.factory('Window', function (GUI) {
        return GUI.Window.get();
    });

    //Service to interact with the socket library
    app.factory('socket', function (socketFactory) {
        var myIoSocket = io.connect(serverBaseUrl);

        var socket = socketFactory({
            ioSocket: myIoSocket
        });

        return socket;
    });

次に、3つのAngularサービスを作成します。 最初のサービスはそのNodeWebKitGUIオブジェクトを取得するのに役立ち、2番目のサービスはそのWindowプロパティを返し、3番目のサービスはベースURLを使用してSocket.ioをブートストラップします。

app.js
    //ng-enter directive
    app.directive('ngEnter', function () {
        return function (scope, element, attrs) {
            element.bind("keydown keypress", function (event) {
                if (event.which === 13) {
                    scope.$apply(function () {
                        scope.$eval(attrs.ngEnter);
                    });

                    event.preventDefault();
                }
            });
        };
    });

上記のスニペットは、Angularを使用して以来、私のお気に入りの1つです。 イベントをENTERキーにバインドします。これにより、キーが押されたときにイベントをトリガーできます。

最後に、app.jsには全能のコントローラーがあります。 server.jsで行ったように、理解を容易にするために物事を分解する必要があります。 コントローラは次のことを期待されています。

  1. サーバーから放出された部屋を使用して、ウィンドウメニューのリストを作成します。
  2. 参加するユーザーは、ユーザー名を提供する必要があります。
  3. サーバーからの新しいメッセージをリッスンします。
  4. ENTERキーを入力して押すことにより、新しいメッセージが作成されたときにサーバーに通知します。

部屋のリストを作成する

目標を定義したら、次のようにコーディングします。

app.js
    //Our Controller
    app.controller('MainCtrl', function ($scope, Window, GUI, $mdDialog, socket, $http){

      //Menu setup

      //Modal setup

      //listen for new message

      //Notify server of the new message

    });

これが、すべての依存関係を持つコントローラーのスケルトンです。 ご覧のとおり、目標で定義されているコードのプレースホルダーとして機能する4つの内部コメントがあります。 それでは、メニューを選びましょう。

app.js
    //Global Scope
    $scope.messages = [];
    $scope.room     = "";

    //Build the window menu for our app using the GUI and Window service
    var windowMenu = new GUI.Menu({
        type: 'menubar'
    });
    var roomsMenu = new GUI.Menu();

    windowMenu.append(new GUI.MenuItem({
        label: 'Rooms',
        submenu: roomsMenu
    }));

    windowMenu.append(new GUI.MenuItem({
        label: 'Exit',
        click: function () {
            Window.close()
        }
    }));

メニューのインスタンスを作成し、それにいくつかのメニュー(RoomsとExit)を追加しただけです。 部屋メニューはドロップダウンとして機能することが期待されているため、サーバーに利用可能な部屋を要求し、それを部屋メニューに追加する必要があります。

app.js
    //Listen for the setup event and create rooms
    socket.on('setup', function (data) {
        var rooms = data.rooms;

        for (var r = 0; r < rooms.length; r++) {
            //Loop and append room to the window room menu
            handleRoomSubMenu(r);
        }

        //Handle creation of room
        function handleRoomSubMenu(r) {
            var clickedRoom = rooms[r];
            //Append each room to the menu
            roomsMenu.append(new GUI.MenuItem({
                label: clickedRoom.toUpperCase(),
                click: function () {
                    //What happens on clicking the rooms? Swtich room.
                    $scope.room = clickedRoom.toUpperCase();
                    //Notify the server that the user changed his room
                    socket.emit('switch room', {
                        newRoom: clickedRoom,
                        username: $scope.username
                    });
                    //Fetch the new rooms messages
                    $http.get(serverBaseUrl + '/msg?room=' + clickedRoom).success(function (msgs) {
                        $scope.messages = msgs;
                    });
                }
            }));
        }
        //Attach menu
        GUI.Window.get().menu = windowMenu;
    });

上記のコードは、関数を使用して、サーバーから利用可能な部屋の配列をループし、部屋のメニューに追加します。 これで、目的#1は完了です。

ユーザー名を尋ねる

2番目の目的は、角度のあるマテリアルモーダルを使用してユーザーにユーザー名を尋ねることです。

app.js
    $scope.usernameModal = function (ev) {
        //Launch Modal to get username
        $mdDialog.show({
            controller: UsernameDialogController,
            templateUrl: 'partials/username.tmpl.html',
            parent: angular.element(document.body),
            targetEvent: ev,
        })
        .then(function (answer) {
            //Set username with the value returned from the modal
            $scope.username = answer;
            //Tell the server there is a new user
            socket.emit('new user', {
                username: answer
            });
            //Set room to general;
            $scope.room = 'GENERAL';
            //Fetch chat messages in GENERAL
            $http.get(serverBaseUrl + '/msg?room=' + $scope.room).success(function (msgs) {
                $scope.messages = msgs;
            });
        }, function () {
            Window.close();
        });
    };

HTMLで指定されているように、initで、usernameModalが呼び出されます。 mdDialogサービスを使用して、参加しているユーザーのユーザー名を取得します。これが成功すると、入力したユーザー名がバインディングスコープに割り当てられ、そのアクティビティについてサーバーに通知してから、ユーザーをデフォルト(GENERAL)にプッシュします。部屋。 成功しなかった場合は、アプリを閉じます。 目標#2が完了しました!

    //Listen for new messages (Objective 3)
        socket.on('message created', function (data) {
            //Push to new message to our $scope.messages
            $scope.messages.push(data);
            //Empty the textarea
            $scope.message = "";
        });
        //Send a new message (Objective 4)
        $scope.send = function (msg) {
            //Notify the server that there is a new message with the message as packet
            socket.emit('new message', {
                room: $scope.room,
                message: msg,
                username: $scope.username
            });

        };

メッセージを聞く

3番目で最後の目的は単純です。 #3はメッセージをリッスンし、存在する場合は既存のメッセージの配列にプッシュし、#4は新しいメッセージが作成されたときにサーバーに通知します。 app.jsの最後に、モーダルのコントローラーとして機能する関数を作成します。

app.js
    //Dialog controller
    function UsernameDialogController($scope, $mdDialog) {
        $scope.answer = function (answer) {
            $mdDialog.hide(answer);
        };
    }

CSSとアニメーション

見苦しい外観を修正するには、app.cssを更新してください。

    body {
        background: #fafafa !important;
    }

    .footer {
        background: #fff;
        position: fixed;
        left: 0px;
        bottom: 0px;
        width: 100%;
    }

    .message.ng-enter {
        -webkit-animation: zoomIn 1s;
        -ms-animation: zoomIn 1s;
        animation: zoomIn 1s;
    }

最後のスタイルに注意してください。 ngAnimateanimate.cssを使用して、メッセージのきれいなアニメーションを作成しています。

このコンセプトここでどのように遊ぶことができるかについてはすでに書きました。

締めくくり

画像を見れば何が気になるのかわかります! アドレスバーですね。 これが、package.jsonwindow構成の出番です。 "toolbar": true"toolbar": falseに変更するだけです。

また、アイコンを"icon": "app/public/img/scotch.png"に設定して、ウィンドウアイコンをスコッチロゴに変更しました。 新しいメッセージが届いたら、通知を追加することもできます。

    var options = {
        body: data.content
    };
    var notification = new Notification("Message from: "+data.username, options);

    notification.onshow = function () {

      // auto close after 1 second
      setTimeout(function () {
          notification.close();
      }, 2000);
    }

そしてさらに楽しい…

テスト

GitHubからWebクライアントをダウンロードしてアプリケーションをテストすることをお勧めします。 サーバー、Webクライアント、アプリの順に実行します。 アプリとWebクライアントの両方からメッセージの送信を開始し、同じ部屋でメッセージを送信している場合は、それらがリアルタイムで表示されるのを確認します。

さらに進む

さらに挑戦したい場合は、私たちのアプリに以下を追加してみてください

  1. Facebookによる認証。
  2. 部屋を更新するための管理セクション。
  3. 実際のユーザーアバターを使用します。
  4. gulp deploy --{{platform}}を使用してアプリをデプロイします。例:gulp deploy --mac。 *など…

結論

最後までやり遂げてよかったです。 NodeWebKitは素晴らしいコンセプトです。 コミュニティに参加して、アプリの作成を簡単にします。 今日はスコッチがたくさんあり、誰かを笑顔にしたことを願っています…