如何轻松实现选择器:搜索 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 应用程序中使用它。注意事项:

  1. 游标 MyIndex.search(searchTerm) 是一种反应性数据源 - 您不能仅将其用作 return 值。当使用 MiniMongo 在客户端搜索时,这不是问题,但当您使用 MongoDBEngine 在服务器上搜索时,这很重要,因为它是异步的。在 React 中,您可以将光标包裹在 withTracker 中,以响应方式将数据传递给组件。在 Blaze 中,您将使用 autorun.tracker。这在文档中显示但没有解释,我花了一段时间才明白发生了什么。

  2. 文档在选择器示例中有一个错误,很容易更正,但如果您的代码中有其他问题,它会令人困惑。

  3. 对于 MongoDBEngine,必须指定 'permission' - 它不会默认为 'true'。没有它,您将看不到任何结果。

  4. 将默认选择器对象写到控制台让我看看它是如何构造的,然后创建一个新的选择器 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);