Why do I get an error when trying to use the Meteor search package?

advertisements

I followed this tutorial on meteor search-source and modified the example so it would fit my current needs. This is my collections.js which is located in my lib directory

Guides = new Mongo.Collection("guides");

I have following code in my client side controller.

var options = {
    keepHistory: 1000 * 60 * 5,
    localSearch: true
};

var fields = ['title'];

GuideSearch = new SearchSource('guides', fields, options);

Template.guide_list.helpers({
    getGuides: function () {
        return GuideSearch.getData({
            transform: function (matchText, regExp) {
                return matchText.replace(regExp, "<b>$&</b>")
            }
        });
    },

    isLoading: function () {
        return GuideSearch.getStatus().loading;
    }
});

Template.guide_list.events({
    "keyup #title": _.throttle(function(e) {
        var text = $(e.target).val().trim();
        GuideSearch.search(text);
    }, 200)
});

And this as my server side code

SearchSource.defineSource('guides', function(searchText, options) {
  if(searchText) {
    var regExp = buildRegExp(searchText);
    var selector = {title: regExp}
    return Guides.find(selector, options).fetch();
  } else {
    return Guides.find({}, options).fetch();
  }
});

function buildRegExp(searchText) {
  // this is a dumb implementation
  var parts = searchText.trim().split(/[ \-\:]+/);
  return new RegExp("(" + parts.join('|') + ")", "ig");
}

For some reason, I receive the following error message when typing something into my input field

Exception in delivering result of invoking 'search.source': Meteor.makeErrorType/[email protected]://10.0.3.162:3000/packages/meteor.js?9730f4ff059088b3f7f14c0672d155218a1802d4:525:15
[email protected]://10.0.3.162:3000/packages/ddp-client.js?250b63e6c919c5383a0511ee4efbf42bb70a650f:4625:23
Connection/[email protected]://10.0.3.162:3000/packages/ddp-client.js?250b63e6c919c5383a0511ee4efbf42bb70a650f:3365:7
._launchConnection/self.socket.onmessage/<@http://10.0.3.162:3000/packages/ddp-client.js?250b63e6c919c5383a0511ee4efbf42bb70a650f:2734:11
[email protected]://10.0.3.162:3000/packages/underscore.js?46eaedbdeb6e71c82af1b16f51c7da4127d6f285:149:7
._launchConnection/[email protected]://10.0.3.162:3000/packages/ddp-client.js?250b63e6c919c5383a0511ee4efbf42bb70a650f:2733:9
[email protected]://10.0.3.162:3000/packages/ddp-client.js?250b63e6c919c5383a0511ee4efbf42bb70a650f:173:9
[email protected]://10.0.3.162:3000/packages/ddp-client.js?250b63e6c919c5383a0511ee4efbf42bb70a650f:1158:5
[email protected]://10.0.3.162:3000/packages/ddp-client.js?250b63e6c919c5383a0511ee4efbf42bb70a650f:1216:13
SockJS.websocket/[email protected]://10.0.3.162:3000/packages/ddp-client.js?250b63e6c919c5383a0511ee4efbf42bb70a650f:1363:9

This is my template code:

template(name="guide_list")
    .format-properly
        .container-fluid
            .input-group#adv-search
                .form-horizontal(role="form" method="POST" action="#")
                    .col-md-6
                        .form-group
                            label(for="contain") Guide title
                            input.form-control(type="text" id="title")
                    .col-md-6
                        .form-group
                            label(for="contain") Author
                            input.form-control(type="text" name="author")
                    .col-md-6
                        .form-group
                            label(for="hero") Select a hero
                            select.form-control(name="hero")
                                option(value="all" selected) All Heroes
                                option(value="Druid") Druid
                                option(value="Hunter") Hunter
                                option(value="Mage") Mage
                                option(value="Paladin") Paladin
                                option(value="Priest") Priest
                                option(value="Rogue") Rogue
                                option(value="Shaman") Shaman
                                option(value="Warlock") Warlock
                                option(value="Warrior") Warrior
                    .col-md-6
                        .form-group
                            label(for="filter") Filter by
                            select.form-control(name="filterBy")
                               option(value="0" selected) All guides
                               option(value="most_viewed") Most viewed
                               option(value="top_rated") Top rated
                               option(value="most_commented") Most commented

        .container-fluid
            .table-responsive
                table.table.table-hover
                    thead
                        tr
                            th hero
                            th title
                            th author
                            th updated
                            th dust
                            th
                                span.glyphicon.glyphicon-eye-open
                            th
                                span.glyphicon.glyphicon-heart
                            th
                                span.glyphicon.glyphicon-comment
                    tbody
                        each guides
                            tr
                                td {{hero}}
                                td
                                    a(href="/guide/{{formatId _id}}") {{title}}
                                td {{authorUsername}}
                                td {{moFormat modifiedAt 'YYYY-MM-DD'}}
                                td {{dust}}
                                td {{hitCount}}
                                td {{rating}}
                                td {{commentCount}}

                    tbody
                        each getGuides
                            tr
                                td {{hero}}
                                td
                                    a(href="/guide/{{formatId _id}}") {{title}}
                                td {{authorUsername}}
                                td {{moFormat modifiedAt 'YYYY-MM-DD'}}
                                td {{dust}}
                                td {{hitCount}}
                                td {{rating}}
                                td {{commentCount}}

Any help or suggestions are highly appreciated!

Edit: I updated the search-source package to 1.4.2


I believe that the issue is the lack of options passed to the GuideSearch.search method.

This leads to the search definition handler getting null for options.

SearchSource.defineSource('guides', function(searchText, options) {
  if(searchText) {
    var regExp = buildRegExp(searchText);
    var selector = {title: regExp}
    return Guides.find(selector, options).fetch(); //illegal
  } else {
    return Guides.find({}, options).fetch();
  }
});

This results in the collection's find() method getting a null options arguments, that, if supplied, must be an object (and not null).

Therefore, either check for a null options in the data source and pass an empty object (or nothing) to the find() method, or pass an empty object to the GuideSearch.search() method:

Template.guide_list.events({
    "keyup #title": _.throttle(function(e) {
        var text = $(e.target).val().trim();
        GuideSearch.search(text, {}); // here
    }, 200)
});

You should probably make sure that the options are not null in the server method as in @webdeb's answer in any case.

Additionally, at the moment some packages (check and ejson) have to be added as dependencies to your app as the meteorhacks:search-source package uses them without declaring a dependency. This is due to change introduced in v1.2.0. (before then those symbols were automatically available to packages).

In order to get have all of the results available initially, you can trigger a search when the template is first created. Note that this may be fairly costly when you have a lot of data, so you should probably limit the results returned from the search definition handler on the server.

Template.guide_list.onCreated(function () {
  GuideSearch.search('', {});
});

In order to display the title correctly in the search results, you could use Spacebars.SafeString() so that blaze knows to render it as HTML.

Template.guide_list.helpers({
    getGuides: function () {
        return GuideSearch.getData({
            transform: function (matchText, regExp) {
              return Spacebars.SafeString(matchText.replace(regExp, "<b>$&</b>"))
            }
        });
    },
    formatId: function(id) {
      return id;
    },
    moFormat: function(date, format) {
        return moment(date).format(format);
    },
    isLoading: function () {
        return GuideSearch.getStatus().loading;
    }
});

Template.guide_list.events({
    "keyup #title": _.throttle(function(e) {
        var text = $(e.target).val().trim();
        GuideSearch.search(text, {/* your options here */});
    }, 200)
});

or, alternatively, use the triple brace notation:

a(href="/guide/{{formatId _id}}") {{{title}}}

Warning: be sure to sanitize your matchText when doing this.

Publications should have nothing to do with the results, as the package uses its own collection.