使用Meteor配合Node.js编写实时聊天应用的范例


Posted in Javascript onJune 23, 2015

我经常见到被拿来与Derby.js做比较的框架是Meteor.js. 与Derby相似的是,它也能在多个客户端下实时更新views, 尽管做法上可能跟Derby有点不同. Derby可以较容易的使用多种数据库, 而Meteor则只亲近于MongoDB. 事实上, 通过如Mongoose客户端接入数据库的API与你在服务端所期望的已经非常接近了.

虽然现在meteor是个有一些缺点和争议的框架, 但Meteor看起来是非常有趣的选择用来建立有实时需求的应用. 个人还是喜欢Derby基于传统回调的编程形式更吸引我, 但在Derby的强大背后,却缺乏健壮的文档和一个大的开发者社区, 这无疑是个很大的打击. 或许这会随着时间推移而有所改变吧, 但比起Meteor来说还是会慢很多, 因为后者最近获得了1100万美元的资金. 这笔财政资金确保了Meteor的存在以及得到持续的支持. 对于那些需要财政与发展稳定的框架的开发者而言, 这笔资金只会让Meteor更加优胜. <!-- more --> 今天,让我们一起来看看如何新建一个真实的但又简单的Meteor应用. 本质上说, 这是基于Tom的 Vimeo screencast的一个新手指引. 与Tom的 Vimeo screencast最大的不同是处理事件的方式. 比起复制粘贴一个Meteor示例的代码, 我会一步一步的通过自己的方式来处理使用Enter键来提交一则讯息. 让我们开始吧.

创建一个 Meteor应用

Derby和Meteor 他们共有的一个大加分是他们各自的命令行工具. 与Derby使用Node的内置的 npm 工具所不同的是, Meteor使用的是它自己的.

在终端(Mac OS X 和 Linux),执行如下的命令. (在这之前请确保你已经安装了Node)

$curl https://install.meteor.com | /bin/sh

Metror会自己搞定,并安装命令行工具.

要新建一个项目, 先转到你的工作目录然后运行下边的代码. 这会创建一个目录, 里边包括有Meteor和一个最基本模板程序.

$meteor create chat

现在, 你可以转到该目录并运行下面的代码让它跑起来

$cdchat$meteor
Running on: http://localhost:3000/

想要看到这个最基础的应用程序, 你只需要在任意一款不过时的浏览器下打开http://localhost:3000/

只要你想, 你就可以使用Meteor内置的meteor deploy命令来部署你的应用到Meteor自己的服务器上

$meteor deploy my-app-name.meteor.com

只要你更新保存了你的代码, 所有连接上的浏览器都会实时更新其页面.

开发聊天应用

在Meteor Create指令产生的文件夹中,你可以看见不同的文件。如果你知道怎么查看隐藏文件的话,你还可以看见个.meteor这个文件夹。这个文件夹包含了Meteor它本身,以及MongoDB的数据文件。

在你App的根目录文件夹下,你应该可以看到这三个文件:chat.html, chat.css和chat.js。这三个文件都是自带说明部分的。HTML文件包含了App的模型以及外观,他们都是被chat.css定义的。Javascript文件包含了在client和server端要执行的脚本。有一点很重要,不要把任何东西放进这个脚本文件,比如说配置参数和密码,因为任何人都可以通过查看你应用程序的代码看到这些。

用你喜欢的文本编辑软件打开chat.js这个文件。就个人而言,我喜欢用Sublime Text2,因为这个工具简洁还有多种鼠标状态提示。

你可以在chat.js文件中查看到下面这样一段代码:
 

if (Meteor.is_client) { Template.hello.greeting = function () { return "Welcome to chat."; }; Template.hello.events = { 'click input' : function () { // template data, if any, is available in 'this' if (typeof console !== 'undefined') console.log("You pressed the button"); } }; } if (Meteor.is_server) { Meteor.startup(function () { // code to run on server at startup }); }

在Meteor.js中注意if段落中Meteor.is_client和Meteor.is_server的两个部分。在这些区块中的代码会分开执行,当运行这段代码的机器是client端则只运行clint块中的代码,server同理。这就说明了Meteor在实际运用中的代码共享能力。

删除掉if中所有Meteor.is_client和Meteor.is_server段的代码,最后只剩下一段:
 

if (Meteor.is_client) { }

注意,当你保存了 脚本文件之后,你的浏览器会立刻刷新加载这段新的代码。

创建视图(View)

在我们正式对这个脚本文件动工之前, 我们需要先新建一个视图用来展示聊天记录. 在编辑器里打开chat.html并删除body标签里边的代码. 包括名为hello的template标签.只留如下部分

<head>
 <title>chat</title>
</head>

<body>

</body>

接着在body标签里添加下面这句

{{> entryfield}}

Meteor使用的模板系统与Mustache很相似.大括号{% raw %}{{}}{% endraw %}表示要呈现的内容. 通过简单地在两对大括号里添加内容如{% raw %}{{hello}}{% endraw %}, 模板系统会用hello这个变量的值来替换它. 后面会更详细的介绍.

注意到了在entryfield这个词前面有个大于号>了吗? 使用该符号来指定渲染哪一个模板.

<template name="entryfield">
  <input type="text" id="name" placeholder="Name" /> <input type="text" id="message" placeholder="Your Message" />
</template>

在这个例子中,template标签有单个属性, 即模板的名字, 这就是我们要渲染的模板, 注意, 模板的名字要和body里的代码指定的模板名字一样 ({{> entryfield}})

查看浏览器, 你会发现页面已经刷新了, 输入框已经呈现出来了.

接下来, 在body里边添加另外的一个mutache标签用以渲染讯息列表

{{> messages}}

最后, 我们还需要新建一个名叫messages的模板. 在entryfield模板下面添加下面这段代码

<template name="messages">
  <p>
    {{#each messages}}
      <strong>{{name}}</strong>- {{message}}
    {{/each}}
  </p>
</template>

注意到each子句. 在Meteor中你可以使用如下的语法来遍历一个数组模板

{{#each [name of array]}}
{{/each}}

使用each循环时,上下文会有所改变. 当引用变量的时候, 实际上你引用的是每一个数组元素的值.

 例如,在我们的chat应用中, 我们遍历了数组模板"messages"里边的每个元素, 该数组可以像下面这样,

[
  {
    "name": "Andrew",
    "message": "Hello world!"
  },
  {
    "name": "Bob",
    "message": "Hey, Andrew!""
  }
]

然后, 在each循环中, 你可以看到{% raw %}{{message}}{% endraw %}{% raw %}{{name}}{% endraw %}, 这会引用 每一个数组元素的值来替代(Andrew 和 Bob 替换 name, 以及各自的问候信息.)

当返回到你的浏览器, 你还看不到任何的改变. 因为讯息数组还没被传送到模板, 所以Meteor遍历不到任何东西来呈现.

你的chat.html最后应该是这样的

<head>
 <title>chat</title>
</head>

<body>
 {{> entryfield}}

 {{> messages}}
</body>

<template name="entryfield">
  <input type="text" id="name" placeholder="Name" /> <input type="text" id="message" placeholder="Your Message" />
</template>

<template name="messages">
  <p>
    {{#each messages}}
      <strong>{{name}}</strong>- {{message}}<br/>
    {{/each}}
  </p>
</template>

Javascript

从现在开始, 我们处理的大部分代码都是客户端代码, 所以, 除非特别说明, 以下的代码都是在if (Meteor.is_client)代码块中.

在我们编写展示讯息的代码之前,让我们先新建一个Collection. 从本质上讲, 这是一组Models. 换句话说, 在这个chat应用的环境下, Messages collection保存着整个聊天记录, 而每条讯息记录是一个Model.

在if语句前, 添加如下代码来初始化Collection:

Messages = new Meteor.Collection('messages');

因为我们希望这个Collection可以在客户端和服务端被创建, 所以我们把它写在了客户端代码块之外.

由于Meteor为我们做了大部分的工作, 要展示聊天记录是非常容易的. 只需要把下面的代码添加进if语句里边.

Template.messages.messages = function(){
  return Messages.find({}, { sort: { time: -1 }});
}

让我们拆开来分析这段代码:

Template.messages.messages = function(){ … }

第一部分Template表示我们正在修改一个模板的行为.

Template.messages.messages = function(){ … }

第二部分messages是模板的名字, 表示是在修改哪一个模板. 例如,如果我们想要对"entryfield"模板做些什么, 只需把代码改成

Template.entryfields.variable = function(){ … }

(在这里, 请别这么做)

Template.messages.messages = function(){ … }

第三部分的这个messages代表的是一个这个模板里的一个变量. 还记得我们的each循环遍历messages吗? 这就是那个mesaages.

当你打开浏览器时, 页面还是没有什么改变. 这是意料之中的事, 因为我们只抓取的讯息, 而没有展示出来.

此时,你的chat.js应该是这样的. 是否很惊讶就只需在服务器写这么些代码我们就能展示一个实时的聊天记录应用.

Messages = new Meteor.Collection('messages');

if (Meteor.is_client) {
 Template.messages.messages = function(){
  return Messages.find({}, { sort: { time: -1 }});
 }
}

在console里添加Message

这部分的内容是可选的, 当然它有助于你调试程序. 你可以直接跳过往下学习建立form来响应键盘事件(key press).

如果你想要测试你的讯息显示代码, 你可以手动插入一条记录到数据库. 打开你的浏览器控制台, 并输入如下:

Messages.insert({ name: 'Andrew', message: 'Hello world!', time: 0 })

这将会在数据库中新建一条记录, 如果正确的操作了的话,那浏览器就会即刻更新这条讯息在页面上.

消息表单

回到chat.js文件当中,我们会将供输入的form和数据库链接起来以接收用户聊天数据的提交。在底部添加下面的代码,不过注意要在if语句块中。

 

Template.entryfield.events = {
 "keydown #message": function(event){
  if(event.which == 13){
   // Submit the form
   var name = document.getElementById('name');
   var message = document.getElementById('message');
 
   if(name.value != '' && message.value != ''){
    Messages.insert({
     name: name.value,
     message: message.value,
     time: Date.now()
    });
 
    name.value = '';
    message.value = '';
   }
  }
 }
}

代码有点多,让我们再回顾一遍。你也许还记得,在Template后面的第二个单词决定了我们正在修改的是哪个模板。不过跟之前不同的是,我们写的代码是用来绑定数据库和messages模板的,我们正在修改的模板是entryfield。

这个模板中events的属性包含了一个object,events的属性按照下面的格式呈现:

 
"[eventname] [selector]"
例如,如果我们想为一个ID为hello的button绑定一个点击事件的话,我们会把下面的代码加入到events的个结构体当中。
 
"click #hello": function(event){ … }
在我们的例子当中,我们是将一个函数绑定到了ID为“message”的一个keydown事件当中。如果你还记得,这段代码早在我们在chat.html文件中建立模板的时候就已经设定好了。

在事件对象中,每个key都有一个函数作为它的值。这个函数在事件被调用时执行,其中事件对象作为第一个参数传递给该函数。在我们的app里,每当ID带有“message”的输入栏中有任意键被按下(keydown)时,该函数就被调用了。

函数内的代码相当简单。首先,我们检查回车键是否被按下(输入中有13的关键代码)。第二,我们通过ID取得两个输入栏的DOM元素。第三,我们检查并确保输入值不为空,以防止用户提交一个空的名字或信息(name or message)。

注意下面的代码很重要。这段代码是将message插入数据库。

Messages.insert({
 name: name.value,
 message: message.value,
 time: Date.now()
});

正如你看到的,这和我们插入到控制台的代码类似,但不是硬编码的数值,我们用的是DOM元素的值。此外,我们加入了当前时间,以保证聊天日志被正确的按时间排序。

最后,我们将两个输入的值简单的设为''以清空输入栏。

现在,如果你进入浏览器,你可以试着输入一个名字与信息到两个输入栏。按下回车以后,输入栏将被清除,一个新的消息会出现在你的输入字段的正下方。打开另一个浏览器窗口,导航到同一个URL(http://localhost:3000/)。试着键入另一个信息,而

正如你看到的,Meteor非常强大。不需要写一行明确更新消息日志的代码,新的信息显示出来并同步到多个浏览器和客户端。

总结

虽然Meteor工作起来非常酷,而且也有一些非常有用的应用支持它,比如Derby.js,但它是不成熟的。一个说明这一点例子就是,浏览文档并找找红色的引文。例如,关于MongoDB集合该文档做了如下陈述:

    目前客户端被给予集合的完全写访问权限。它们可以执行任意的更新命令。一旦我们建立鉴权认证,你将能够限制客户端的直接插入,更新和删除。我们也在考虑校验器或者其他类似ORM的功能。

任何用户拥有完全的写访问权限是一个非常大的问题,因为对任何一个app产品——如果一个用户对你的整个数据库有写访问权限,这是一个相当大的安全问题。

看到Meteor(和Derby.js!)在像哪个方向前进是令人激动的,但是除非它成熟一点,它可能不是一个产品级应用的最好选择。期待那1100万美元资金能很好的利用起来。

Javascript 相关文章推荐
关于JavaScript定义类和对象的几种方式
Nov 09 Javascript
使用javascript获取flash加载的百分比的实现代码
May 25 Javascript
Javascript数组的排序 sort()方法和reverse()方法
Jun 04 Javascript
javascript 弹出的窗口返回值给父窗口具体实现
Nov 23 Javascript
javascript使用正则获取url上的某个参数
Sep 04 Javascript
用AngularJS的指令实现tabs切换效果
Aug 31 Javascript
浅谈JS中String()与 .toString()的区别
Oct 20 Javascript
浅谈React中的元素、组件、实例和节点
Feb 27 Javascript
VUE Error: getaddrinfo ENOTFOUND localhost
May 03 Javascript
Vue事件修饰符native、self示例详解
Jul 09 Javascript
Vue.js实现大屏数字滚动翻转效果
Nov 29 Javascript
JavaScript实现手机号码 3-4-4格式并控制新增和删除时光标的位置
Jun 02 Javascript
使用Node.js为其他程序编写扩展的基本方法
Jun 23 #Javascript
Windows系统下Node.js的简单入门教程
Jun 23 #Javascript
jQuery实现判断滚动条到底部
Jun 23 #Javascript
jQuery实现新消息在网页标题闪烁提示
Jun 23 #Javascript
使用Raygun对Node.js应用进行错误处理的方法
Jun 23 #Javascript
javascript创建函数的20种方式汇总
Jun 23 #Javascript
使用Node.js实现HTTP 206内容分片的教程
Jun 23 #Javascript
You might like
php sprintf()函数让你的sql操作更安全
2008/07/23 PHP
PHPwind整合最土系统用户同步登录实现方法
2010/12/08 PHP
批量去除PHP文件中bom的PHP代码
2012/03/13 PHP
jQuery弹出层插件简化版代码下载
2008/10/16 Javascript
讨论javascript(一)工厂方式 js面象对象的定义方法
2009/12/15 Javascript
20个最新的jQuery插件
2012/01/13 Javascript
Js日期选择自动填充到输入框(界面漂亮兼容火狐)
2013/08/02 Javascript
使用jQuery异步加载 JavaScript脚本解决方案
2014/04/20 Javascript
js动态控制table的tr、td增加及删除的具体实现
2014/04/30 Javascript
关于js函数解释(包括内嵌,对象等)
2016/11/20 Javascript
vue在使用ECharts时的异步更新和数据加载详解
2017/11/22 Javascript
如何将你的AngularJS1.x应用迁移至React的方法
2018/02/01 Javascript
Vue.js构建你的第一个包并在NPM上发布的方法步骤
2019/05/01 Javascript
JavaScript面试中常考的字符串操作方法大全(包含ES6)
2020/05/10 Javascript
vue cli4.0项目引入typescript的方法
2020/07/17 Javascript
[36:37]2014 DOTA2华西杯精英邀请赛5 24 VG VS iG
2014/05/25 DOTA
Python数组定义方法
2016/04/13 Python
Python的collections模块中namedtuple结构使用示例
2016/07/07 Python
Python的SQLalchemy模块连接与操作MySQL的基础示例
2016/07/11 Python
Django ORM框架的定时任务如何使用详解
2017/10/19 Python
安装python3的时候就是输入python3死活没有反应的解决方法
2018/01/24 Python
Python中sort和sorted函数代码解析
2018/01/25 Python
python画图系列之个性化显示x轴区段文字的实例
2018/12/13 Python
Python3实现从排序数组中删除重复项算法分析
2019/04/03 Python
linux环境下Django的安装配置详解
2019/07/22 Python
Django之使用内置函数和celery发邮件的方法示例
2019/09/16 Python
python 字段拆分详解
2019/12/17 Python
Python 实现向word(docx)中输出
2020/02/13 Python
Python绘图之二维图与三维图详解
2020/08/04 Python
CSS3 3D位移translate效果实例介绍
2016/05/03 HTML / CSS
搞笑的获奖感言
2014/08/16 职场文书
北京青年观后感
2015/06/15 职场文书
css3实现背景图片颜色修改的多种方式
2021/04/13 HTML / CSS
浅谈Redis主从复制以及主从复制原理
2021/05/29 Redis
Java实现学生管理系统(IO版)
2022/02/24 Java/Android
Golang连接并操作MySQL
2022/04/14 MySQL