使用Node Webkit,Socket.io和MEAN建立实时聊天室

总览 (Overview)

Development folks work tirelessly to make building programs as easy as possible. The JavaScript, Web and Mobile app developers communities increased drastically since Node and Cordova were introduced. Developers who had web design skills could, with less effort, roll out a server using JavaScript for their applications, through the help of Node.js.

开发人员孜孜不倦地工作,以使构建程序尽可能容易。 自从引入Node和Cordova以来,JavaScript,Web和移动应用程序开发人员社区急剧增加。 拥有Web设计技能的开发人员可以在Node.js的帮助下,用较少的精力为应用程序使用JavaScript推出服务器。

Mobile lovers can with the help of Cordova now build rich hybrid apps using just JavaScript. Today, although it is old news, I am excited to share the ability of using JavaScript to build desktop standalone applications.

移动爱好者现在可以在Cordova的帮助下仅使用JavaScript来构建丰富的混合应用程序。 今天,尽管这是个老新闻,但我很高兴分享使用JavaScript构建桌面独立应用程序的功能

Node Webkit normally written "node-webkit" or "NW.js" is an app runtime based on Node.js and Chromium and enables us to develop OS native apps using just HTML, CSS and JavaScript.

通常写为“ node-webkit”或“ NW.js”的Node Webkit是基于Node.js和Chromium的应用程序运行时,使我们能够仅使用HTML,CSS和JavaScript来开发OS本机应用程序。

Simply put, Node Webkit just helps you utilize your skill as a web developer to build native application that runs comfortably on Mac, Windows and Linux with just a grunt/gulp (if preferred) build command.

简而言之,Node Webkit可以帮助您利用Web开发人员的技能来构建本机应用程序,只需使用grunt / gulp(如果需要)构建命令即可在Mac,Windows和Linux上舒适地运行。

This article concentrates a lot more on using Node Webkit, but in order to make things more interesting, we will be including other amazing solutions and they will include:

本文将重点放在使用Node Webkit上,但是为了使事情变得更加有趣,我们将包括其他出色的解决方案,其中包括:

  • Socket.io A realtime library for Node.js

    Socket.io一个Node.js的实时库
  • Angular Material: Angular’s implementation of Google’s Material Design

    Angular Material :Angular对Google的Material Design的实施
  • MEAN: MEAN is just a concept of combining the features of Mongo, Express, Angular and Node to build powerful apps

    MEAN:MEAN只是结合Mongo,Express,Angular和Node的功能来构建功能强大的应用程序的概念

Furthermore, the application has three sections:

此外,该应用程序包含三个部分:

  • The server

    服务器
  • The desktop (client)

    桌面(客户端)
  • The web (client)

    网络(客户端)

The web section will not be covered here, but it will serve as a test platform but don't worry, the code will be provided.

Web部分将不在此处讨论,但它将用作测试平台,但是请放心,我们将提供代码。

先决条件 (Prerequisites)

Level: Intermediate (Knowledge of MEAN is required)

级别 :中级(需要MEAN知识)

安装 (Installation)

We need to grab node-webkit and every other dependencies for our application. Fortunately, there are frameworks that make workflow easy and we will be using one of them to scaffold our application and concentrate more on the implementation.

我们需要获取应用程序的node-webkit和所有其他依赖项。 幸运的是,有一些框架可以简化工作流程,我们将使用其中一种框架来支持我们的应用程序,并更加专注于实现。

Yo and Slush are popular generators and any of these will work. I am going to be using Slush, but feel free to use Yo if you prefer to. To install Slush make sure you have node and npm installed and run

Yo和Slush是受欢迎的生成器,任何这些都可以使用。 我将使用Slush ,但如果愿意,可以随时使用Yo。 要安装Slush,请确保已安装并运行node和npm

$ npm install -g slush gulp bower slush-wean

The command will install the following globally on our system.

该命令将在我们的系统上全局安装以下内容。

  • slush: a scaffolding tool

    雪泥 :脚手架工具
  • slush-wean: the generator for Node Webkit

    slush-wean :Node Webkit的生成器
  • gulp: our task runner

    gulp :我们的任务执行者
  • bower: for frontend dependencies

    bower :用于前端依赖项

Just like YO, make your directory and scaffold your app using:

与YO一样,使用以下命令创建目录并搭建应用程序:

$ mkdir scotch-chat
$ cd scotch-chat
$ slush wean

Running the below command will give us a glance of what we have been waiting for:

运行以下命令将使我们了解正在等待的内容:

$ gulp run

img1

The image shows our app loading. The author of the generator was generous enough to provide a nice template with simple loading animation. To look cooler, I replaced the loading text with Scotch's logo.

该图显示了我们的应用程序加载。 生成器的作者足够慷慨地提供带有简单加载动画的漂亮模板。 为了看起来更酷,我将加载文本替换为Scotch的徽标。

If you are not comfortable with Slush automating things you can head right to node webkit on GitHub or watch the beginners video series.

如果您对Slush自动化不满意,可以直接访问GitHub上的node webkit或观看初学者视频系列。

Now that we have setup our app, though empty, we will give it a break and prepare our server now.

现在我们已经设置了应用程序,尽管它是空的,但我们要休息一下并准备服务器。

服务器 (The Server)

The server basically consists of our model, routes and socket events. We will keep it as simple as possible and you can feel free to extend the app as instructed at the end of the article.

服务器基本上由我们的模型路由套接字事件组成 。 我们将使其保持尽可能的简单,您可以按照本文结尾处的说明随意扩展应用程序。

目录结构 (Directory Structure)

Setup a folder in your PC at your favorite directory, but make sure the folder content looks like the below:

在PC上您喜欢的目录中设置一个文件夹,但请确保该文件夹的内容如下所示:

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

依存关系 (Dependencies)

In the package.json file located on your root directory, create a JSON file to describe your application and include the application's dependencies.

在根目录下的package.json文件中,创建一个JSON文件来描述您的应用程序并包括应用程序的依赖项。

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

That will do. It is just a minimal setup and we are keeping things simple and short. Run npm install on the directory root to install the specified dependencies.

这样就可以了。 这只是一个最小的设置,我们使事情变得简单而简短。 在目录根目录上运行npm install以安装指定的依赖项。

$ npm install

开始我们的服务器设置 (Starting Our Server Setup)

Huuugh. It is time to get our hands dirty! The first thing is to setup global variables in server.js which will hold the applications dependencies that are already installed.

呵呵 现在该弄脏我们的双手了! 第一件事是在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);

Ok, I didn't keep to my word. The variables are not only holding the dependencies, but some are configuring it for use.

好吧,我没有信守诺言。 这些变量不仅保存了依赖关系,而且还在配置它们以供使用。

To serve static files, express exposes a method to help configure the static files folder. It is simple:

为了提供静态文件,express公开了一种方法来帮助配置静态文件文件夹。 很简单:

// server.js

...

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

Next up is to create a connection to our database. I am working with a local Mongo DB which obviously is optional as you can find it's hosted by Mongo databases. Mongoose is a node module that exposes amazing API which makes working with Mongo DB a lot much easier.

接下来是创建到我们数据库的连接。 我正在使用本地Mongo数据库,该数据库显然是可选的,因为您可以发现它由Mongo数据库托管。 Mongoose是一个节点模块,它公开了令人惊叹的API,这使与Mongo DB的工作变得容易得多。

// server.js

...

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

With Mongoose we can now create our database schema and model. We also need to allow CORS in the application as we will be accessing it from a different domain.

现在,借助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();
  }
});

Our server will have three routes in it. A route to serve the index file, another to setup chat data and the last to serve chat messages filtered by room names:

我们的服务器将包含三个路由。 用于提供索引文件的路由,用于设置聊天数据的路由,以及用于提供通过会议室名称过滤的聊天消息的最后一条路由:

// 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|||||||||||||||||||||*/

The first route I believe is easy enough. It will just send our index.html file to our users.

我认为第一条路线很容易。 它只会将我们的index.html文件发送给我们的用户。

The second /setup is meant to be hit just once and at the initial launch of the application. It is optional if you don't need some test data. It basically creates an array of chat messages (which matches the schema), loops through them and inserts them into the database.

第二个/setup只在应用程序首次启动时被点击一次。 如果不需要一些测试数据,则为可选。 基本上,它创建一个聊天消息数组(与模式匹配),循环遍历并将它们插入数据库。

The third route /msg is responsible for fetching chat history filtered with room names and returned as an array of JSON objects.

第三个路由/msg负责获取用房间名称过滤并作为JSON对象数组返回的聊天记录。

The most important part of our server is the realtime logic. Keeping in mind that we are working towards producing a simple application, our logic will be comprehensively minimal. Sequentially, we need to:

我们服务器最重要的部分是实时逻辑。 请记住,我们正在努力开发一个简单的应用程序,因此我们的逻辑将在总体上最小化。 因此,我们需要:

  • Know when our application is launched

    知道何时启动我们的应用程序
  • Send all the available rooms on connection

    发送所有可用的房间连接
  • Listen for a user to connect and assign him/her to a default room

    聆听用户进行连接并将其分配给默认房间
  • Listen for when he/she switches room

    换房间时听
  • And, finally, listen for a new message and only send the message to those in the room at which it was created

    最后,收听新消息并将其仅发送给创建它的房间中的人

Therefore:

因此:

// 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||||||||||||||||||*/

Then the traditional server start:

然后,传统服务器启动:

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

Fill the index.html with any HTML that suites you and run node server.js. localhost:2015 will give you the content of your HTML.

用适合您的任何HTML填充index.html并运行node server.jslocalhost:2015将为您提供HTML的内容。

节点Webkit客户端 (The Node Webkit Client)

Time to dig up what we left to create our server which is running currently. This section is quite easy as it just requires your everyday knowledge of HTML, CSS, JS and Angular.

是时候挖掘剩下的内容来创建当前正在运行的服务器了。 本部分非常简单,因为它只需要您HTML,CSS,JS和Angular的日常知识。

img2

目录结构 (Directory Structure)

We don't need to create any! I guess that was the inspiration of generators. The first file you might want to inspect is the package.json.

我们不需要创建任何内容! 我想那是发电机的灵感。 您可能要检查的第一个文件是package.json

Node Webkit requires, basically, two major files to run:

Node Webkit基本上需要运行两个主要文件:

  1. an entry point (index.html)

    入口点( index.html
  2. a package.json to tell it where the entry point is located

    一个package.json来告诉它入口点在哪里

package.json has the basic content we are used to, except that it's main is the location of the index.html, and it has a set of configuration under "window": from which we define all the properties of the app's window including icons, sizes, toolbar, frame, etc.

package.json具有我们惯用的基本内容,不同之处在于它主要是index.html的位置,并且在"window":下具有一组配置"window":从中我们定义应用程序窗口的所有属性,包括图标,大小,工具栏,框架等。

依存关系 (Dependencies)

Unlike the server, we will be using bower to load our dependencies as it is a client application. Update your bower.json dependencies to:

与服务器不同,我们将使用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"
}

For a shortcut, just run the following command:

要获取快捷方式,只需运行以下命令:

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

Now that we have our frontend dependencies, we can update our views/index.ejs to:

现在我们有了前端依赖项,我们可以将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>

We included all our dependencies and custom files (app.css and app.js). Things to note:

我们包含了所有依赖项和自定义文件(app.css和app.js)。 注意事项:

  • We are using angular material and it's directives are making our code look like "HTML 6".

    我们使用的是有角度的材质 ,它的指令使我们的代码看起来像“ HTML 6”。
  • We are looping through our messages scope using ng-repeat and rendering it's values to the browser

    我们正在使用ng-repeat遍历消息范围,并将其值呈现给浏览器
  • A directive which we shall see later helps us to send the message when the enter key is pressed

    稍后将看到的指令将帮助我们在按Enter键时发送消息
  • On init, the user is asked for a preferred username

    init ,要求用户提供首选用户名
  • There is an Angular library that is included to help working with socket.io in Angular easier.

    包含一个Angular库,以帮助在Angular中更轻松地使用socket.io。

应用程序 (The Application)

The main part of this section is the app.js file. It creates services to interact with the Node Webkit GUI, a directive to handle the enter keypress and the controllers (main and dialog).

本节的主要部分是app.js文件。 它创建与Node Webkit GUI交互的服务,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;
});

Next up, we create three angular services. The first service helps us get that Node Webkit GUI object, the second returns it's Window property and the third bootstraps Socket.io with the base url.

接下来,我们创建三个角度服务。 第一个服务帮助我们获取该Node Webkit GUI对象,第二个服务返回它的Window属性,第三个服务使用基本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();
            }
        });
    };
});

The above snippet is one of my favorites ever since I have been using Angular. It binds an event to the enter key, which thereby an event can be triggered when the key is pressed.

自从我一直使用Angular以来,上面的片段就是我的最爱之一。 它将事件绑定到Enter键,从而可以在按下该键时触发事件。

Finally, with the app.js is the almighty controller. We need to break things down to ease understanding as we did in our server.js. The controller is expected to:

最后, app.js是万能的控制器。 我们需要分解内容以简化理解,就像我们在server.js所做的那样。 控制器应:

  1. Create a list of window menus from using the rooms emitted from the server.

    通过使用服务器发出的房间来创建窗口菜单列表。
  2. The user on joining is expected to provided his/her username.

    加入的用户应提供其用户名。
  3. Listen for a new message from the server.

    侦听来自服务器的新消息。
  4. Notify the server of new messages when they are created by typing and hitting the enter key.

    在创建新消息时,通过键入并单击Enter键通知服务器。

创建房间列表 (Create a List of Rooms)

With our objectives defined let us code:

在定义了目标之后,让我们编写代码:

// 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

});

That is our controller's skeleton with all of it dependencies. As you can see, it has four internal comments which is serving as a placeholder for our codes as defined in the objectives. So let's pick on the menu.

那就是我们控制器的所有依赖项的骨架。 如您所见,它有四个内部注释,它们用作目标中定义的代码的占位符。 因此,让我们在菜单上进行选择。

// 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()
    }
}));

We simply created instances of the menu and appended some menu (Rooms and Exit) to it. The rooms menu is expected to serve as a drop-down and so we have to ask the server for available rooms and append it to the rooms menu:

我们只是创建了菜单的实例,并向其中添加了一些菜单(“房间”和“退出”)。 房间菜单应该用作下拉菜单,因此我们必须向服务器询问可用的房间,并将其附加到房间菜单中:

// 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;
});

The above code with the help of a function, loops through an array of rooms when they are available from the server and then append them to the rooms menu. With that, Objective #1 is completed.

上面的代码在一个函数的帮助下,从服务器可用时循环遍历一系列房间,然后将它们附加到房间菜单。 这样,目标#1完成。

询问用户名 (Asking for a Username)

Our second objective is to ask the user for username using angular material modal.

我们的第二个目标是使用角度材料模态向用户询问用户名。

// 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();
    });
};

As specified in the html, on init, the usernameModal is called. It uses the mdDialog service to get username of a joining user and if that is successful it will assign the username entered to a binding scope, notify the server about that activity and then push the user to the default (GENERAL) room. If it is not successful we close the app. Objective #2 completed!

如html中所指定,在初始化时,将调用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
        });

    };

收听消息 (Listening For Messages)

The third, and the last, objective is simple. #3 just listens for messages and if any push it to the array of existing messages and #4 notifies the server of new messages when they are created. At the end of app.js, we create a function to serve as the controller for the Modal:

第三个也是最后一个目标很简单。 #3仅侦听消息,如果有消息则将其推送到现有消息的数组中,并且#4在创建新消息时通知服务器。 在app.js的最后,我们创建一个函数用作Modal的控制器:

// app.js

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

CSS和动画 (CSS and Animation)

To fix some ugly looks, update the app.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;
}

Note the last style. We are using ngAnimate and animate.css to create a pretty animation for our messages.

注意最后的样式。 我们正在使用ngAnimate和animate.css为我们的消息创建漂亮的动画。

I already wrote on how you can play with this concept here.

我已经在这里写了如何使用这个概念。

img3

闭幕 (Closing Up)

I can guess what you are worried about after looking at the image! The address bar, right? This is were the window configuration in the package.json comes in. Just change "toolbar": true to "toolbar": false.

看完图片我就能猜出您担心的是什么! 地址栏,对不对? 这是package.json中的window配置。只需将"toolbar": true更改为"toolbar": false

I also set my icon to "icon": "app/public/img/scotch.png" to change the window icon to Scotch logo. We can also add notification once there is a new message:

我还将图标设置为"icon": "app/public/img/scotch.png" ,将窗口图标更改为Scotch徽标。 一旦有新消息,我们还可以添加通知:

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);
}

img4

img5

And even more fun...

还有更多的乐趣...

测试中 (Testing)

I suggest you test the application by downloading the web client from Git Hub. Run the server, then the web client and then the app. Start sending messages from both the app and the web client and watch them appear in realtime if you are sending in the same room.

我建议您通过从Git Hub下载Web客户端来测试应用程序。 运行服务器,然后运行Web客户端,然后运行应用程序。 开始从应用程序和Web客户端发送消息,如果您在同一个房间中发送消息,请观看它们实时显示。

The demo server is on here and the web client here.

演示服务器在这里 ,Web客户端在这里

走得更远 (Go Further)

If you want to challenge yourself further, you can try to add the following to our app

如果您想进一步挑战自己,可以尝试将以下内容添加到我们的应用中

  1. Authentication with Facebook.

    使用Facebook进行身份验证。
  2. Admin section to update rooms.

    管理部分以更新房间。
  3. Use a real user avatar.

    使用真实的用户头像。
  4. Deploy the app using gulp deploy --{{platform}} eg: gulp deploy --mac. * etc...

    使用gulp deploy --{{platform}}部署应用程序,例如: gulp deploy --mac 。 *等...

结论 (Conclusion)

I am glad we made it to the end. Node Webkit is an amazing concept. Join the community and make building apps easier. Hope you had a lot of scotch today and that I made someone smile...

我很高兴我们做到了最后。 Node Webkit是一个了不起的概念。 加入社区,使构建应用程序更加容易。 希望你今天有很多苏格兰威士忌,我让某人微笑...

翻译自: https://scotch.io/tutorials/a-realtime-room-chat-app-using-node-webkit-socket-io-and-mean

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值