如何轻松实现选择器:搜索 Meteor,使用 React 而不是 Blaze
How to implement a selector in easy: search for Meteor, using React instead of Blaze
我正在尝试使用 documentation and examples to add a server-side selector to a search function in my Meteor app, implemented using the Easy Search 插件。最终目标是确保通过搜索只返回用户有权查看的文档。
我可以在 Leaderboard 示例中看到一个选择器,但我无法在我的代码中使用它。
版本:
Meteor 1.7.0.1
easy:search@2.2.1
easysearch:components@2.2.2
easysearch:core@2.2.2
我修改了 Meteor 'todos' example app to demonstrate the problem, and my demo code is in a repo。
注意!为了演示这个问题,您需要在演示应用程序中创建一个帐户,然后创建一个列表并将其设为私有。这会将 'userId' 字段添加到列表中。
然后您可以在靠近主要部分顶部的搜索框中键入搜索列表的名称;搜索结果写入浏览器控制台。
第一个问题是,如果我从 example in the documentation 复制代码,我会看到服务器错误“searchObject is not defined:
从文档复制,导致错误:imports/api/lists/lists.js
export const MyIndex = new Index({
'collection': Lists,
'fields': ['name'],
engine: new MongoDBEngine({
selector(searchDefinition, options, aggregation) {
// retrieve the default selector
const selector = this.defaultConfiguration()
.selector(searchObject, options, aggregation)
// options.search.userId contains the userId of the logged in user
selector.userId = options.search.userId
return selector
},
}),
});
文档中似乎有错误。
从排行榜示例开始,下面的代码运行但间歇性地 returns 没有结果。例如,如果我有一个名为 "My list" 的列表,并且我键入搜索词 's',有时该列表会从搜索中返回,有时则不会。如果我使用 MiniMongo 引擎,一切都会完美无缺。
index selector {"$or":[{"name":{"$regex":".*my.*","$options":"i"}}],"userId":"Wtrr5FRHhkKuAcrLZ"}
客户端和服务器:imports/api/lists/lists.js
export const MyIndex = new Index({
'collection': Lists,
'fields': ['name'],
'engine': new MongoDBEngine({
selector: function (searchObject, options, aggregation) {
let selector = this.defaultConfiguration().selector(searchObject, options, aggregation);
selector.userId = options.search.userId;
console.log('index selector', JSON.stringify(selector));
return selector;
}
}),
permission: () => {
return true;
},
});
客户端:imports/ui/components/lists-show.js
Template.Lists_show.events({
'keyup #search'(event) {
console.log('search for ', event.target.value);
const cursor = MyIndex.search(event.target.value);
console.log('count',cursor.count());
console.log('results', cursor.fetch());
},
});
客户:imports/ui/components/lists-show.html
<input id="search" type="text" placeholder="search..." />
编辑:我认为问题是当 Minimongo 引擎在客户端运行时,MongoDBEngine 在服务器上运行并且结果存在时间问题。文档显示使用 Tracker.autorun,但这不适合我的 React / Redux 应用程序。如果我设法解决问题,我会 post 一个答案 - 我不可能是唯一一个尝试做这样的事情的人。
我在我的 React / Redux / Meteor 应用程序中使用它。注意事项:
游标 MyIndex.search(searchTerm) 是一种反应性数据源 - 您不能仅将其用作 return 值。当使用 MiniMongo 在客户端搜索时,这不是问题,但当您使用 MongoDBEngine 在服务器上搜索时,这很重要,因为它是异步的。在 React 中,您可以将光标包裹在 withTracker 中,以响应方式将数据传递给组件。在 Blaze 中,您将使用 autorun.tracker。这在文档中显示但没有解释,我花了一段时间才明白发生了什么。
文档在选择器示例中有一个错误,很容易更正,但如果您的代码中有其他问题,它会令人困惑。
对于 MongoDBEngine,必须指定 'permission' - 它不会默认为 'true'。没有它,您将看不到任何结果。
将默认选择器对象写到控制台让我看看它是如何构造的,然后创建一个新的选择器 returns MyDocs 是 public 或创建的由用户。
我的代码如下。如果它对其他人有帮助,我还展示了如何搜索标签,这些标签是存储在集合标签中的名称为 属性 的对象。每个 MyDoc 都有一个 'tags' 属性,这是一个标签 ID 数组。选择器首先搜索 Tags 集合以查找名称与搜索词匹配的标签,然后在 MyDocs 中选择文档,这些标签的 ID 在 doc.tags 数组中。
可能有更好的方法来查找搜索词或构建标签搜索,但这就是我可以开始工作的方法。
在服务器和客户端上:
import { Index, MongoDBEngine } from 'meteor/easy:search';
export const MyDocs = new Mongo.Collection('mydocs');
export const Tags = new Mongo.Collection('tags');
export const MyIndex = new Index({
'collection': MyDocs,
'fields': ['name'],
'engine': new MongoDBEngine({
'selector': function (searchObject, options, aggregation) {
const selector = this.defaultConfiguration().selector(searchObject, options, aggregation);
console.log('default selector', selector); // this searches on name only
// find docs by tag as well as by name
const searchTerm = searchObject.name;
const matchingTags = Tags.find({ 'name': { '$regex': searchTerm } }).fetch();
const matchingTagIds = matchingTags.map((tag) => tag._id);
selector.$or.push({ 'tags': { '$in': matchingTagIds } });
const newSelector = {
'$and': [
{
'$or': [
{ 'isPublic': { '$eq': true } },
{ 'createdBy': options.search.userId },
],
},
{
'$or': selector.$or,
},
],
};
return newSelector;
},
'fields': (searchObject, options) => ({
'_id': 1,
'createdBy': 1,
'name': 1,
}),
'sort': () => ({ 'name': 1 }),
}),
'permission': () => true,
});
仅客户端代码中的 React 组件:
import React from 'react';
import { connect } from 'react-redux';
import { withTracker } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
import store from '../modules/store';
import {
getSearchTerm,
searchStart,
} from '../modules/search'; // contains Redux actions and partial store for search
import { MyIndex } from '../../modules/collection';
function Search(props) {
// functional React component that contains the search box
...
const onChange = (value) => {
clearTimeout(global.searchTimeout);
if (value.length >= 2) {
// user has entered a search term
// of at least 2 characters
// wait until they stop typing
global.searchTimeout = setTimeout(() => {
dispatch(searchStart(value)); // Redux action which sets the searchTerm in Redux state
}, 500);
}
};
...
// the component returns html which calls onChange when the user types in the search input
// and a list which displays the search results, accessed in props.searchResults
}
const Tracker = withTracker(({ dispatch }) => {
// searchTerm is saved in Redux state.
const state = store.getState();
const searchTerm = getSearchTerm(state); // Redux function to get searchTerm out of Redux state
let results = [];
if (searchTerm) {
const cursor = MyIndex.search(searchTerm); // search is a reactive data source
results = cursor.fetch();
console.log('*** cursor count', cursor.count());
return {
'searchResults': results,
};
})(Search);
export default connect()(Tracker);
我正在尝试使用 documentation and examples to add a server-side selector to a search function in my Meteor app, implemented using the Easy Search 插件。最终目标是确保通过搜索只返回用户有权查看的文档。
我可以在 Leaderboard 示例中看到一个选择器,但我无法在我的代码中使用它。
版本:
Meteor 1.7.0.1
easy:search@2.2.1
easysearch:components@2.2.2
easysearch:core@2.2.2
我修改了 Meteor 'todos' example app to demonstrate the problem, and my demo code is in a repo。
注意!为了演示这个问题,您需要在演示应用程序中创建一个帐户,然后创建一个列表并将其设为私有。这会将 'userId' 字段添加到列表中。
然后您可以在靠近主要部分顶部的搜索框中键入搜索列表的名称;搜索结果写入浏览器控制台。
第一个问题是,如果我从 example in the documentation 复制代码,我会看到服务器错误“searchObject is not defined:
从文档复制,导致错误:imports/api/lists/lists.js
export const MyIndex = new Index({
'collection': Lists,
'fields': ['name'],
engine: new MongoDBEngine({
selector(searchDefinition, options, aggregation) {
// retrieve the default selector
const selector = this.defaultConfiguration()
.selector(searchObject, options, aggregation)
// options.search.userId contains the userId of the logged in user
selector.userId = options.search.userId
return selector
},
}),
});
文档中似乎有错误。
从排行榜示例开始,下面的代码运行但间歇性地 returns 没有结果。例如,如果我有一个名为 "My list" 的列表,并且我键入搜索词 's',有时该列表会从搜索中返回,有时则不会。如果我使用 MiniMongo 引擎,一切都会完美无缺。
index selector {"$or":[{"name":{"$regex":".*my.*","$options":"i"}}],"userId":"Wtrr5FRHhkKuAcrLZ"}
客户端和服务器:imports/api/lists/lists.js
export const MyIndex = new Index({
'collection': Lists,
'fields': ['name'],
'engine': new MongoDBEngine({
selector: function (searchObject, options, aggregation) {
let selector = this.defaultConfiguration().selector(searchObject, options, aggregation);
selector.userId = options.search.userId;
console.log('index selector', JSON.stringify(selector));
return selector;
}
}),
permission: () => {
return true;
},
});
客户端:imports/ui/components/lists-show.js
Template.Lists_show.events({
'keyup #search'(event) {
console.log('search for ', event.target.value);
const cursor = MyIndex.search(event.target.value);
console.log('count',cursor.count());
console.log('results', cursor.fetch());
},
});
客户:imports/ui/components/lists-show.html
<input id="search" type="text" placeholder="search..." />
编辑:我认为问题是当 Minimongo 引擎在客户端运行时,MongoDBEngine 在服务器上运行并且结果存在时间问题。文档显示使用 Tracker.autorun,但这不适合我的 React / Redux 应用程序。如果我设法解决问题,我会 post 一个答案 - 我不可能是唯一一个尝试做这样的事情的人。
我在我的 React / Redux / Meteor 应用程序中使用它。注意事项:
游标 MyIndex.search(searchTerm) 是一种反应性数据源 - 您不能仅将其用作 return 值。当使用 MiniMongo 在客户端搜索时,这不是问题,但当您使用 MongoDBEngine 在服务器上搜索时,这很重要,因为它是异步的。在 React 中,您可以将光标包裹在 withTracker 中,以响应方式将数据传递给组件。在 Blaze 中,您将使用 autorun.tracker。这在文档中显示但没有解释,我花了一段时间才明白发生了什么。
文档在选择器示例中有一个错误,很容易更正,但如果您的代码中有其他问题,它会令人困惑。
对于 MongoDBEngine,必须指定 'permission' - 它不会默认为 'true'。没有它,您将看不到任何结果。
将默认选择器对象写到控制台让我看看它是如何构造的,然后创建一个新的选择器 returns MyDocs 是 public 或创建的由用户。
我的代码如下。如果它对其他人有帮助,我还展示了如何搜索标签,这些标签是存储在集合标签中的名称为 属性 的对象。每个 MyDoc 都有一个 'tags' 属性,这是一个标签 ID 数组。选择器首先搜索 Tags 集合以查找名称与搜索词匹配的标签,然后在 MyDocs 中选择文档,这些标签的 ID 在 doc.tags 数组中。
可能有更好的方法来查找搜索词或构建标签搜索,但这就是我可以开始工作的方法。
在服务器和客户端上:
import { Index, MongoDBEngine } from 'meteor/easy:search';
export const MyDocs = new Mongo.Collection('mydocs');
export const Tags = new Mongo.Collection('tags');
export const MyIndex = new Index({
'collection': MyDocs,
'fields': ['name'],
'engine': new MongoDBEngine({
'selector': function (searchObject, options, aggregation) {
const selector = this.defaultConfiguration().selector(searchObject, options, aggregation);
console.log('default selector', selector); // this searches on name only
// find docs by tag as well as by name
const searchTerm = searchObject.name;
const matchingTags = Tags.find({ 'name': { '$regex': searchTerm } }).fetch();
const matchingTagIds = matchingTags.map((tag) => tag._id);
selector.$or.push({ 'tags': { '$in': matchingTagIds } });
const newSelector = {
'$and': [
{
'$or': [
{ 'isPublic': { '$eq': true } },
{ 'createdBy': options.search.userId },
],
},
{
'$or': selector.$or,
},
],
};
return newSelector;
},
'fields': (searchObject, options) => ({
'_id': 1,
'createdBy': 1,
'name': 1,
}),
'sort': () => ({ 'name': 1 }),
}),
'permission': () => true,
});
仅客户端代码中的 React 组件:
import React from 'react';
import { connect } from 'react-redux';
import { withTracker } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
import store from '../modules/store';
import {
getSearchTerm,
searchStart,
} from '../modules/search'; // contains Redux actions and partial store for search
import { MyIndex } from '../../modules/collection';
function Search(props) {
// functional React component that contains the search box
...
const onChange = (value) => {
clearTimeout(global.searchTimeout);
if (value.length >= 2) {
// user has entered a search term
// of at least 2 characters
// wait until they stop typing
global.searchTimeout = setTimeout(() => {
dispatch(searchStart(value)); // Redux action which sets the searchTerm in Redux state
}, 500);
}
};
...
// the component returns html which calls onChange when the user types in the search input
// and a list which displays the search results, accessed in props.searchResults
}
const Tracker = withTracker(({ dispatch }) => {
// searchTerm is saved in Redux state.
const state = store.getState();
const searchTerm = getSearchTerm(state); // Redux function to get searchTerm out of Redux state
let results = [];
if (searchTerm) {
const cursor = MyIndex.search(searchTerm); // search is a reactive data source
results = cursor.fetch();
console.log('*** cursor count', cursor.count());
return {
'searchResults': results,
};
})(Search);
export default connect()(Tracker);