$or
通过使用每个集合的单个发布/订阅来解决问题,并在find
查询中加以利用,我已经设法取得了一些有希望的初步结果。
这个想法是提供一个包装器Meteor.Collection
,允许您添加“视图”,它们基本上是命名的游标。但真正发生的是这些游标不是单独运行的......它们的选择器被提取,$or'd 一起并作为单个查询运行并运行到单个 pub-sub。
它并不完美,因为偏移量/限制不适用于这种技术,但目前 minimongo 无论如何都不支持它。
但最终它允许你做的是声明看起来像同一个集合的不同子集,但在引擎盖下它们是同一个子集。前面只有一点抽象,让它们感觉干净利落。
例子:
// Place this code in a file read by both client and server:
var Users = new Collection("users");
Users.view("enabledUsers", function (collection) {
return collection.find({ enabled: true }, { sort: { name: 1 } });
});
或者如果你想传递参数:
Users.view("filteredUsers", function (collection) {
return collection.find({ enabled: true, name: this.search, { sort: { name: 1 } });
}, function () {
return { search: Session.get("searchterms"); };
});
参数作为对象给出,因为它是一个单一的发布/订阅 $or'd 在一起,我需要一种方法来获取正确的参数,因为它们混合在一起。
并在模板中实际使用它:
Template.main.enabledUsers = function () {
return Users.get("enabledUsers");
};
Template.main.filteredUsers = function () {
return Users.get("filteredUsers");
};
简而言之,我利用在服务器和客户端中运行相同的代码的优势,如果服务器没有做某事,客户端会做,反之亦然。
最重要的是,只有您感兴趣的记录才会发送给客户。这一切都可以在没有抽象层的情况下通过简单地自己执行 $or 来实现,但是随着更多子集的添加,$or 会变得非常难看。这只是帮助用最少的代码管理它。
我很快写了这个来测试它,对篇幅和缺乏文档表示歉意:
测试.js
// Shared (client and server)
var Collection = function () {
var SimulatedCollection = function () {
var collections = {};
return function (name) {
var captured = {
find: [],
findOne: []
};
collections[name] = {
find: function () {
captured.find.push(([]).slice.call(arguments));
return collections[name];
},
findOne: function () {
captured.findOne.push(([]).slice.call(arguments));
return collections[name];
},
captured: function () {
return captured;
}
};
return collections[name];
};
}();
return function (collectionName) {
var collection = new Meteor.Collection(collectionName);
var views = {};
Meteor.startup(function () {
var viewName, view, pubName, viewNames = [];
for (viewName in views) {
view = views[viewName];
viewNames.push(viewName);
}
pubName = viewNames.join("__");
if (Meteor.publish) {
Meteor.publish(pubName, function (params) {
var viewName, view, selectors = [], simulated, captured;
for (viewName in views) {
view = views[viewName];
// Run the query callback but provide a SimulatedCollection
// to capture what is attempted on the collection. Also provide
// the parameters we would be passing as the context:
if (_.isFunction(view.query)) {
simulated = view.query.call(params, SimulatedCollection(collectionName));
}
if (simulated) {
captured = simulated.captured();
if (captured.find) {
selectors.push(captured.find[0][0]);
}
}
}
if (selectors.length > 0) {
return collection.find({ $or: selectors });
}
});
}
if (Meteor.subscribe) {
Meteor.autosubscribe(function () {
var viewName, view, params = {};
for (viewName in views) {
view = views[viewName];
params = _.extend(params, view.params.call(this, viewName));
}
Meteor.subscribe.call(this, pubName, params);
});
}
});
collection.view = function (viewName, query, params) {
// Store in views object -- we will iterate over it on startup
views[viewName] = {
collectionName: collectionName,
query: query,
params: params
};
return views[viewName];
};
collection.get = function (viewName, optQuery) {
var query = views[viewName].query;
var params = views[viewName].params.call(this, viewName);
if (_.isFunction(optQuery)) {
// Optional alternate query provided, use it instead
return optQuery.call(params, collection);
} else {
if (_.isFunction(query)) {
// In most cases, run default query
return query.call(params, collection);
}
}
};
return collection;
};
}();
var Items = new Collection("items");
if (Meteor.isServer) {
// Bootstrap data -- server only
Meteor.startup(function () {
if (Items.find().count() === 0) {
Items.insert({title: "item #01", enabled: true, processed: true});
Items.insert({title: "item #02", enabled: false, processed: false});
Items.insert({title: "item #03", enabled: false, processed: false});
Items.insert({title: "item #04", enabled: false, processed: false});
Items.insert({title: "item #05", enabled: false, processed: true});
Items.insert({title: "item #06", enabled: true, processed: true});
Items.insert({title: "item #07", enabled: false, processed: true});
Items.insert({title: "item #08", enabled: true, processed: false});
Items.insert({title: "item #09", enabled: false, processed: true});
Items.insert({title: "item #10", enabled: true, processed: true});
Items.insert({title: "item #11", enabled: true, processed: true});
Items.insert({title: "item #12", enabled: true, processed: false});
Items.insert({title: "item #13", enabled: false, processed: true});
Items.insert({title: "item #14", enabled: true, processed: true});
Items.insert({title: "item #15", enabled: false, processed: false});
}
});
}
Items.view("enabledItems", function (collection) {
return collection.find({
enabled: true,
title: new RegExp(RegExp.escape(this.search1 || ""), "i")
}, {
sort: { title: 1 }
});
}, function () {
return {
search1: Session.get("search1")
};
});
Items.view("processedItems", function (collection) {
return collection.find({
processed: true,
title: new RegExp(RegExp.escape(this.search2 || ""), "i")
}, {
sort: { title: 1 }
});
}, function () {
return {
search2: Session.get("search2")
};
});
if (Meteor.isClient) {
// Client-only templating code
Template.main.enabledItems = function () {
return Items.get("enabledItems");
};
Template.main.processedItems = function () {
return Items.get("processedItems");
};
// Basic search filtering
Session.get("search1", "");
Session.get("search2", "");
Template.main.search1 = function () {
return Session.get("search1");
};
Template.main.search2 = function () {
return Session.get("search2");
};
Template.main.events({
"keyup [name='search1']": function (event, template) {
Session.set("search1", $(template.find("[name='search1']")).val());
},
"keyup [name='search2']": function (event, template) {
Session.set("search2", $(template.find("[name='search2']")).val());
}
});
Template.main.preserve([
"[name='search1']",
"[name='search2']"
]);
}
// Utility, shared across client/server, used for search
if (!RegExp.escape) {
RegExp.escape = function (text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
};
}
测试.html
<head>
<title>Collection View Test</title>
</head>
<body>
{{> main}}
</body>
<template name="main">
<h1>Collection View Test</h1>
<div style="float: left; border-right: 3px double #000; margin-right: 10px; padding-right: 10px;">
<h2>Enabled Items</h2>
<input type="text" name="search1" value="{{search1}}" placeholder="search this column" />
<ul>
{{#each enabledItems}}
<li>{{title}}</li>
{{/each}}
</ul>
</div>
<div style="float: left;">
<h2>Processed Items</h2>
<input type="text" name="search2" value="{{search2}}" placeholder="search this column" />
<ul>
{{#each processedItems}}
<li>{{title}}</li>
{{/each}}
</ul>
</div>
</template>