Discover Meteor笔记

meteor和我在RIA的轮回说的理念很像,一看就爱上了,终于不用那么变态的所谓bs结构了.

资料

官方

https://www.meteor.com/

中文书

网友翻译的 http://zh.discovermeteor.com/

开始

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

Meteorite

安好 node.js 后

Meteorite不是必装的包,是Tom Coleman带头开发的第三方包,包装了meteor,可以实现安装一些第三方包.

sudo npm install -g meteorite

建立项目

官方是这样

meteor create myapp

这里可以这样,用mrt来代替meteor

mrt create microscope

smart package

meteor自带了常用的包,可以用mrt list来看

我喜欢用 coffeescript , less 和 bootstrap

mrt add coffeescript
mrt add less
mrt add bootstrap

生成的项目里的js和less直改了后缀就可以了,不用做什么

mv myapp.js myapp.coffee
mv myapp.css myapp.less

目录结构

直接抄书上的 * 在 /server 文件夹中的代码只会在服务器端运行。 * 在 /client 文件夹中的代码只会在客户端运行。 * 其它代码则将同时运行于服务器端和客户端上。 * 在 /lib 文件夹中的文件将被优先载入。 * 所有以 main.* 命名的文件将在其他文件载入后载入。 * 请将所有的静态文件(字体,图片等)放置在 /public 文件夹中。

以前记录的,有些罗嗦了,不要看,跳过宽宏即可

### private
用来放一些只能被server访问,不能给client看到的内容(不是代码),比如一些数据文件.
用[Assets](Assets.md)来访问
### client/compatibility
放一些其它的,和meteor不兼容的js文件
### public
images, favicon.ico, robots.txt 等要访问的文件放这里.和web.py的static一样
### lib
* Files in the lib directory at the root of your application are loaded first.
* Files that match main.* are loaded after everything else.
* Files in subdirectories are loaded before files in parent directories, so that files in the deepest subdirectory are loaded first (after lib), and files in the root directory are loaded last (other than main.*).
* Within a directory, files are loaded in alphabetical order by filename.
### client
### server
不能发过去给client看到的代码,比如登录什么的,就放这里
### test
### ./
根目录下的文件,同时会运行在client和server端,通过isClient和isServer来判断什么时候运行什么内容

模板

meteor的模板用的是Handlebars,还是有必要好好学一下:Handlebars模板学习

模板的定位

meteor有个好处,会自动找模板,不用指定模板位置.放在/client里的哪里都可以.

模板是这样一个html

<template name="postsList">
  <div class="posts">
    {{#each posts}}
      {{> postItem}}
    {{/each}}
  </div>
</template>

<template>包起来的一个html,根据name来定位,所以放的位罝不重要.

模板的三种语法

  • Partials :通过 {{> templateName}} 标记,简单直接地告诉 Meteor 这部分需要用相同名称的模板来取代(在我们的例子中就是 postItem )。
  • Expressions :比如 {{title}} 标记,它要么是调用当前对象的属性,要么就是对应到当前模板管理器中定义的 helper 方法,并返回其方法值(后面会详细讨论)。
  • Block Helpers :在模板中控制流程的特殊标签,如 {{#each}}…{{/each}}{{#if}}…{{/if}}

    数据传递

    貌似是用这样的方式来传递数据

    Template.postsList.helpers({
    posts: postsData
    })
    

    用Template找到要传入的template(这里是postsList),再调用helpers方法,传入一个字典.

模板里就能使用传入的变量了.

管理器里的this

上面的数据传递是直接给一个list

更常见的是给一个函数.

Template.postItem.helpers({
  domain: function() {
    var a = document.createElement('a');
    a.href = this.url;
    return a.hostname;
  }
});

这里的this不好理解,回过头去看前面的.

    {{#each posts}}
      {{> postItem}}
    {{/each}}

这里把循环出来的post的属性挂到了postItem里,所以在postItem.helpers里可以用this来取到挂过去的属性.

ps:这里还用了一个javascript很巧妙的技巧来取hostname,自行体会吧.

集合

Collection是meteor设定的一个特殊的数据结构,实现了同步.(db->client或者client->db)

如果是有js来定义Collection,不能加var.因为要用作全局的.我用coffeescript,看这里来处理: meteor 使用 coffeescript 定义全局变量的注意事项

数据操作

服务器

用Meteor.Collection的相关方法来操作即可,可以看作是对Mongodb作了一层封装: API

直接操作

到app目录

mrt mongo

这时可以输入标准mongodb

db.posts.insert({title: "A new post"});

客户端

依托于MiniMongo技术,在client上实现了一个mongodb的子集.

这里不能用标准的mongodb,只能用meteor封装好的.

Posts.insert({title: "A second post"});

语法都差不多,只是这里直接操作Collection对像.

不用fetch

Posts.find()

真正返回的的游标,在client console取一下就知道了.但是在meteor代码里直接就可以当数组用,不用反复的调用fetch()

meteor会智能的做好这事.

autopublish

正常的情况server上的数据是不应该全部同步到client端的.多了还不搞死client. meteor默认开了这个包autopublish,是为了开发方便.用于生产要关了

mrt remove autopublish

还是指定哪些数据子集要传到client

Meteor.publish('posts' function() {
  return Posts.find();
});

要给发布的数据起个名字,这里叫posts.(必须是Collection的名字么?)

订阅

Meteor.subscribe('posts')

发布与订阅

发布与订阅机制就是为了数据子集的过滤器.

发布与订阅时的参数

很多时候,过滤条件是只有client才知道的,而publish又是只能在server端(为了数据安全).所以必须要client来告诉publish条件是什么.

因而,publish和subscribe间是可以做参数传递的

比如,publish如下:

Meteor.publish('posts', function(author) {
  return Posts.find({flagged: false, author: author});
});

subscribe可以这样

Meteor.subscribe('posts', 'bob-smith');

和函数调用也差不多了.

数据定位

发布与订阅时要起一个名字,名字和内容是没有关系的,叫什么都可以,保持唯一就可以.重复了也不会出错的,只是订阅时会订阅到第一个以那个名子发布的数据,第二个同名的发布的数据就消逝在风中了.

再次强调,发布与订阅不过是个过滤器,是以Collection为数据粒度的.

对一个Collection可以有多对发布订阅

Meteor.publish('posts_bigzhu', ->
  return Posts.find({title:'Meteor'})
)
Meteor.publish('posts_bigzhu2', ->
  return Posts.find({title:'The Meteor Book'})
)

client:

Meteor.subscribe('posts_bigzhu')
Meteor.subscribe('posts_bigzhu2')

这时client的Posts里会是两个的数据合集(不会重复)

不挂到Collection

Meteor.publish('posts_bigzhu', ->
  return Posts.find({title:'Meteor'})
)
Meteor.publish('posts_bigzhu2', ->
  #return Posts.find({title:'The Meteor Book'})
  return {
    title: 'Meteor',
    author: 'Tom Coleman',
    url: 'http://meteor.com'
  }
)

不会报错,但是client没有地方来取posts_bigzhu2的数据.

数据和安全

client和server使用同一个数据库API,好颠覆常识.

不过同样的代码,在server端是直接访问数据库.client端的不是(这样才对嘛),这是数据安全的说明

By default, a new Meteor app includes the autopublish and insecure packages, 
which together mimic the effect of each client having full read/write access to the server's database. 
These are useful prototyping tools, 
but typically not appropriate for production applications. 
When you're ready, just remove the packages.

meteor client 本地有一个cache的db,一但改后,改变本地的db,引起本地页面的变化,同时向server提交.

如果server拒绝该数据,就从server重新取一份db的copy到本地.页面还原.

如果server接受,由server引发其它client的改变.

这样就不用和server的roundtrip了

路由

meteor似乎没有官方的route方案. iron-router是这本书的作者开发的第三方包. 要这来

mrt add iron-router

来安装.

把main.html再分层,和jinja2比较像.

route的配置要放在lib里,因为route要首先加载,lib是确保被首先加载的文件夹.

书里的代码有错,写成{{yield}}了,应为{{>yield}}

route会自动查找与route名一样的模板,还会自动定义与route名一样的path,也就是

this.route('postsList', {path: '/postsList'});
this.route('postsList')

是一样的.

会有人说,还要定个route名干什么? * path,模板 可能会有不一样的情况 * url带参数的问题 * 定个route名,才知道你要走route * 可以用route名来取path:{{pathFor 'postsList'}}

带参数的路由

  this.route('postPage', {
    path: '/posts/:_id'
  });

:_id匹配到的东西放到了this.params._id里,自行取用吧.

session

Session是一个全局的,别滥用. 全局一时爽,调试火葬厂

用Session.get()和Session.set(‘xx’,‘??’)来取和设就可以了.

改动代码引起的页面功态加载并不会清空Session,但是用户手动刷新就会让Seesion消失了.

用户

有做了现成的包

mrt add accounts-ui-bootstrap-dropdown
mrt add accounts-password

{{loginButtons}}就可以显示,控制下拉方向:{{loginButtons align="right"}}

用户包注意学习 安全的数据库数据子集

响应式

数据集变动时会触发observe()方法,用这个再来改变界面.

如果用的是meteor的tempate生成的界面,那么这个事meteor自已帮你做了.

但是如果用了一些其它的js库,就要用observe()的added和removed的回调方法去调用第三方库来改变.

computations

建立一个computations,只要在Deps.autorun 方法中加上需要的代码块,和observe()有点反过来.

Deps.autorun(function() {
  console.log('There are ' + Posts.find().count() + ' posts');
});

代码里面要有一个响应式数据源,Posts.find()就是一个响应式数据源.改变时,会导致这个代码调用.

创建帖子

注意,有个html写错了

<textarea name="message" type="text" value=""/>

ATTENTION: default value of option force_s3tc_enable overridden by environment. Created new window in existing browser session.

<textarea name="message"></textarea>

开启安全插入

mrt remove insecure

就是删除insecure的包,又逆向思维.而后,无法在client 向collection insert数据了.

要指定

@Posts.allow({
  insert: (userId, doc)->
    return !! userId
})

不想让没登录的用户看到form,用route的before hook过滤器(书上的代码太老了,已经改了好多) 老的代码:

var requireLogin = function() {
  if (! Meteor.user()) {
    this.render('accessDenied');
    this.stop();
  }
}
Router.before(requireLogin, {only: 'postSubmit'});

新的可以用的:

requireLogin = (pause)->
  if ! Meteor.user()
    this.render('accessDenied')
    pause()
Router.onBeforeAction(requireLogin, {only: 'postSubmit'})

登录后刷新,会发现accessDenied的页面还是会闪现,书上也没有说清楚为什么会这样,但是可以这样来解决.

requireLogin = (pause)->
  if ! Meteor.user()
    if (Meteor.loggingIn())
      this.render(this.loadingTemplate)
    else
      this.render('accessDenied')
    pause()
Router.onBeforeAction(requireLogin, {only: 'postSubmit'})

accounts包提供了一个currentUser变量,可以直接在template中使用.等于Meteor.user().所以可以用这个来让未登录用户看不到一些内容.

<ul class="nav">
  {{#if currentUser}}<li><a href="{{pathFor 'postSubmit'}}">Submit Post</a></li>{{/if}}
</ul>

前面直接暴露了insert,虽然使用了allow来限定只有这个collection允许insert,但是还有很不安全.另外,还想在insert前做一些验证之类的操作就不好办了,放在client来做这些操作就更不安全了.所以还是回到老的模式,post请求在server端来验证和insert.利用meteor的call方法.传参,回调,没什么不同(感觉变得好平庸的样子). 所以我还是用神奇的client insert来做(喜欢书上的,就按书上的来) 不过想一想,有些还是不好做,比如要插入时间,client端时间是不可靠的,所以我屈服了.

  • allow要删了,别再开口子了.

帖子重复,302重定向的时候,报错了,不知为什么,先不管了.

延迟补偿

很牛的功能,cient模拟数据操作,让用户感觉不到延迟.server完成后,再同步.