使用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 相关文章推荐
基于JQuery的抓取博客园首页RSS的代码
Dec 01 Javascript
ajax页面无刷新 IE下遭遇Ajax缓存导致数据不更新的问题
Dec 11 Javascript
js图片闪动特效可以控制间隔时间如几分钟闪动一下
Aug 12 Javascript
JS Attribute属性操作详解
May 19 Javascript
EasyUI中在表单提交之前进行验证
Jul 19 Javascript
工作中比较实用的JavaScript验证和数据处理的干货(经典)
Aug 03 Javascript
node.js发送邮件email的方法详解
Jan 06 Javascript
Node.js 使用request模块下载文件的实例
Sep 05 Javascript
微信小程序学习笔记之函数定义、页面渲染图文详解
Mar 28 Javascript
vue基于better-scroll仿京东分类列表
Jun 30 Javascript
微信小程序实现列表滚动头部吸顶的示例代码
Jul 12 Javascript
nuxt 每个页面head标签内容设置方式
Nov 05 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
substr()函数中文版
2006/10/09 PHP
可定制的PHP缩略图生成程式(需要GD库支持)
2007/03/06 PHP
PHP网站基础优化方法小结
2008/09/29 PHP
php excel类 phpExcel使用方法介绍
2010/08/21 PHP
PHP数组生成XML格式数据的封装类实例
2016/11/10 PHP
thinkPHP显示不出验证码的原因与解决方法分析
2017/05/20 PHP
php使用pthreads v3多线程实现抓取新浪新闻信息操作示例
2020/02/21 PHP
自动生成文章摘要的代码[JavaScript 版本]
2007/03/20 Javascript
js 未结束的字符串常量错误解决方法
2010/06/13 Javascript
javascript面向对象入门基础详细介绍
2012/09/05 Javascript
JavaScript设置首页和收藏页面的小例子
2013/11/11 Javascript
关闭浏览器时提示onbeforeunload事件
2013/12/25 Javascript
JS判断、校验MAC地址的2个实例
2014/05/05 Javascript
通过jquery 获取URL参数并进行转码
2014/08/18 Javascript
jQuery使用hide方法隐藏页面上指定元素的方法
2015/03/30 Javascript
Bootstrap3使用typeahead插件实现自动补全功能
2016/07/07 Javascript
jQuery悬停文字提示框插件jquery.tooltipster.js用法示例【附demo源码下载】
2016/07/19 Javascript
js querySelector() 使用方法
2016/12/21 Javascript
微信小程序实现图片上传、删除和预览功能的方法
2017/12/18 Javascript
微信小程序实现传参数的几种方法示例
2018/01/10 Javascript
详解vue中axios的封装
2018/07/18 Javascript
Vue创建头部组件示例代码详解
2018/10/23 Javascript
javascript实现5秒倒计时并跳转功能
2019/06/20 Javascript
使用apifm-wxapi模块中的问题及解决方法
2019/08/05 Javascript
javascript 使用sleep函数的常见方法详解
2020/04/26 Javascript
微信小程序:报错(in promise) MiniProgramError
2020/10/30 Javascript
[55:45]DOTA2上海特级锦标赛D组败者赛 Liquid VS COL第一局
2016/02/28 DOTA
Java中重定向输出流实现用文件记录程序日志
2015/06/12 Python
python爬虫容易学吗
2020/06/02 Python
读取nii或nii.gz文件中的信息即输出图像操作
2020/07/01 Python
英国领先的独立酒精饮料零售商:DrinkSupermarket
2021/01/13 全球购物
高中地理教学反思
2014/01/29 职场文书
管理提升方案
2014/06/04 职场文书
幼儿园师德师风心得体会
2016/01/12 职场文书
初中信息技术教学反思
2016/02/16 职场文书
高中数学教学反思范文
2016/02/18 职场文书