当前位置:K88软件开发文章中心编程语言JavaScriptMeteor → 文章内容

Meteor 投票

减小字体 增大字体 作者:佚名  来源:网上搜集  发布时间:2019-1-15 15:29:04

s="post"> <a href="#" class="upvote btn btn-default {{upvotedClass}}">?</a> <div class="post-content"> //... </div></template>Template.postItem.helpers({ ownPost: function() { //... }, domain: function() { //... }, upvotedClass: function() { var userId = Meteor.userId(); if (userId && !_.include(this.upvoters, userId)) { return 'btn-primary upvotable'; } else { return 'disabled'; } }});Template.postItem.events({ 'click .upvotable': function(e) { e.preventDefault(); Meteor.call('upvote', this._id); }});我们将 css class 从 .upvote 变成 .upvotable,别忘了修改 click 事件处理函数。接下来,你会发现被投过一票的帖子会显示 "1 votes", 下面让我们花点时间来处理单复数形式。处理单复数是个复杂的事,但在这里我们会用一个非常简单的方法。我们建一个通用的 Spacebars helper 方法来处理他们:UI.registerHelper('pluralize', function(n, thing) { // fairly stupid pluralizer if (n === 1) { return '1 ' + thing; } else { return n + ' ' + thing + 's'; }});之前我们创建的 helper 方法都是绑定到某个模板的。但是现在我们用 Template.registerHelper 创建一个全局的 helper 方法,我们可以在任何模板中使用它:<template name="postItem">//...<p> {{pluralize votes "Vote"}}, submitted by {{author}}, <a href="{{pathFor 'postPage'}}">{{pluralize commentsCount "comment"}}</a> {{#if ownPost}}<a href="{{pathFor 'postEdit'}}">Edit</a>{{/if}}</p>//...</template>现在我们看到的是 "1 vote"。更智能的投票机制我们的投票代码看起来还行,但是我们能做的更好。在 upvote 方法,我们两次调用 Mongo: 第一次找到帖子,第二次更新它。这里有两个问题。首先,两次调用数据库效率会有点低。但是更重要的是,这里引入了一个竞速状态。我们的逻辑是这样的:从数据库中找到帖子。检查用户是否已经投票。如果没有,用户可以投一票。如果同一个用户在步骤 1 和 3 之间两次投票会如何?我们现在的代码会让用户给同一个帖子投票两次。幸好,Mongo 允许我们将步骤 1-3 合成一个 Mongo 命令://...Meteor.methods({ post: function(postAttributes) { //... }, upvote: function(postId) { check(this.userId, String); check(postId, String); var affected = Posts.update({ _id: postId, upvoters: {$ne: this.userId} }, { $addToSet: {upvoters: this.userId}, $inc: {votes: 1} }); if (! affected) throw new Meteor.Error('invalid', "You weren't able to upvote that post"); }});//...我们的代码是说“找到 id 是这个并且用户没有投票的帖子,并更新他们为投票”。如果用户还没有投票,就会找到这个 id 的帖子。如果用户已经投过票了,就不会有结果返回。Latency Compensation假定你想作弊通过修改帖子投票数量来让一个帖子排到榜单的第一名:> Posts.update(postId, {$set: {votes: 10000}});(postId 是你某个帖子的 id)这个无耻的企图将会被我们系统的 deny() 回调函数捕获(collections/posts.js 记得么?)并且立刻取消。但是如果你仔细看,你可能会发现系统的延迟补偿 (latency compensation)。它可能一闪而过, 会看到帖子现在第一位闪了一下,然后回到原来的位置。发生了什么? 在客户端的 Posts 集合,update 方法会被执行。这会立刻发生,因此帖子会来到列表第一的位置。同时,在服务器端 update 方法会被拒绝。过了一会 (如果你在本地运行 Meteor 这个时间间隔会是毫秒级的), 服务器端返回一个错误,告诉客户端 Posts 集合恢复到原来状态。最终的结果是: 在等待服务器端返回的过程中,UI 只能相信客户端本地集合数据。当服务器端一返回拒绝了修改,UI 就会使用服务器端数据。排列首页的帖子现在每个帖子都有一个基于投票数的分数,让我们显示一个最佳帖子的列表。这样,我们将看到如何管理对于帖子集合的两个不同的订阅,并将我们的 postsList 模板变得更通用一些。首先,我们需要两个订阅,分别用来排序。这里的技巧是两个订阅同时订阅同一个 posts 发布,只是参数不同!我们还需要新建两个路由 newPosts 和 bestPosts,分别通过 URL /new 和 /best 访问(当然,使用 /new/5 和 /best/5 进行分页)。我们将继承 PostsListController 来生成两个独立的 NewPostsListController 和 BestPostsListController 控制器。对于 home 和 newPosts 路由,我们可以使用完全一致的路由选项,通过继承同一个 NewPostsListController 控制器。另外,这是一个很好的例子说明 Iron Router 的灵活性。让我们用 NewPostsListController 和 BestPostsListController 提供的 this.sort 替换 PostsListController 的排序属性 {submitted: -1}://...PostsListController = RouteController.extend({ template: 'postsList', increment: 5, postsLimit: function() { return parseInt(this.params.postsLimit) || this.increment; }, findOptions: function() { return {sort: this.sort, limit: this.postsLimit()}; }, subscriptions: function() { this.postsSub = Meteor.subscribe('posts', this.findOptions()); }, posts: function() { return Posts.find({}, this.findOptions()); }, data: function() { var hasMore = this.posts().count() === this.postsLimit(); return { posts: this.posts(), ready: this.postsSub.ready, nextPath: hasMore ? this.nextPath() : null }; }});NewPostsController = PostsListController.extend({ sort: {submitted: -1, _id: -1}, nextPath: function() { return Router.routes.newPosts.path({postsLimit: this.postsLimit() + this.increment}) }});BestPostsController = PostsListController.extend({ sort: {votes: -1, submitted: -1, _id: -1}, nextPath: function() { return Router.routes.bestPosts.path({postsLimit: this.postsLimit() + this.increment}) }});Router.ro

上一页  [1] [2] [3]  下一页


Meteor 投票