meteor-autocomplete icon indicating copy to clipboard operation
meteor-autocomplete copied to clipboard

Supporting multiple collections?

Open andreimcristof opened this issue 10 years ago • 15 comments

Hi, I implemented this autocomplete and I love it, however it seems to only work with one collection - can it work with multiple at the same time?

So if in settings I limit to 10, and the first collection delivers 7 results and 2nd collection delivers 3, I should see all results. But I only see the results of whichever collection is first declared in the settings. Example:

if (Meteor.isClient) {

Template.autoComplete.settings = function() {
  return {
   position: "bottom",
   limit: 10,
   rules: [
       {
         token: '',
         collection: Customers, 
         field: "name",
         matchAll: true,
         template: Template.autocompleteCustomer
       },
       {
         token: '',
         collection: Clients,
         field: "title",
         matchAll: true,
         template: Template.autocompleteClient
       }
    ]
    }
  };

}

Problem: When rules array contains 2 separate collections, and are declared together as shown above, only the first one declared in the array will deliver results. So in the example above only the customers will deliver results.

How can I use both collections at the same time?

Thanks,

andreimcristof avatar Aug 24 '14 18:08 andreimcristof

We currently don't support using the same token to match multiple collections (in your case, the token is the null string). I don't think this is necessary to do in our code.

The best way to approach this is probably not by using this library directly, but by using Meteor's publication API. Either publish both collections into the same client collection so they are merged on the client, and you can use a single search above, or create a server publication that searches both collections and publishes the combined result. Because we support autocomplete from both client-side collections and server-side publications, either way works - the first method is probably simpler to implement because Meteor will do the merging for you.

See http://stackoverflow.com/a/18880927/586086 for the basics on how to do this.

mizzao avatar Aug 28 '14 15:08 mizzao

thank you for your detailed reply!

i made it work with two separate tokens, that's also perfectly fine for what I need. Best,

andreimcristof avatar Aug 30 '14 09:08 andreimcristof

@andreimcristof I think you should consider the merging-via-publications approach; it's a lot cleaner and will not require the user to know about tokens.

mizzao avatar Aug 30 '14 18:08 mizzao

thank you for your suggestion! will give this a try

andreimcristof avatar Sep 06 '14 02:09 andreimcristof

Same problem here. :-) Would be great to search in more than 1 collection without token, providing a different template for each collection. Would be a lot cleaner without workarounds. Please let me know, if you implement this issue. Thank you, Bernhard

berfer avatar Sep 11 '14 15:09 berfer

@berfer The logic to implement this would be much more complicated than just to use Meteor's publication API to do the trick. It's not a workaround, it's the better solution.

I don't think I'll be handling this here.

mizzao avatar Sep 11 '14 16:09 mizzao

So I am working with a similar issue, but here is my issue. I want one collection to use a token and the other to not use a token.

My Code:

'autofillInput': function() {
  return {
    position: "bottom",
    limit: 1000,
    rules: [
      {
        callback: function(doc, element) { $("#ingredientId").val(doc._id); },
        collection: Ingredients,
        field: "ingredient",
        filter: { deleted: false },
        selector: function(match) { return getIngredientExpression($("#ingredient").val()); },
        template: Template.ingredientDropdown
      },
      {
        token: '@',
        callback: function(doc, element) { $("#recipeId").val(doc._id); },
        collection: Recipes,
        field: "recipe",
        filter: { deleted: false },
        selector: function(match) { return getRecipeExpression($("#ingredient").val()); },
        template: Template.recipeDropdown
      }
    ]
  }
},

In this scenario only the first collection is being searched. Is this possible to do without the fix talked about further up?

tgeene avatar Jan 15 '15 17:01 tgeene

@tgeene try putting the token rule first, before the non-token rule. I didn't have this use case in mind when I started this project, but I think it would work.

mizzao avatar Jan 15 '15 17:01 mizzao

Sad to say, this did not work. I was hopeful that the fix would be that easy. I guess I will work on implementing the fix from further up.

tgeene avatar Jan 15 '15 17:01 tgeene

Ok, so I am trying to implement the fix using the information from this link: http://stackoverflow.com/a/18880927/586086

When I do this:

'autofillInput': function() {
  return {
    position: "bottom",
    limit: 1000,
    rules: [
      {
        callback: function(doc, element) { $("#ingredientId").val(doc._id); },
        subscription: "ingredientsXrecipes",
        field: "ingredient",
        filter: { deleted: false },
        selector: function(match) { return getIngredientExpression($("#ingredient").val()); },
        template: Template.ingredientDropdown
      }
    ]
  }
},

Or I do this:

'autofillInput': function() {
  return {
    position: "bottom",
    limit: 1000,
    rules: [
      {
        callback: function(doc, element) { $("#ingredientId").val(doc._id); },
        collection: Ingredients,
        subscription: "ingredientsXrecipes",
        field: "ingredient",
        filter: { deleted: false },
        selector: function(match) { return getIngredientExpression($("#ingredient").val()); },
        template: Template.ingredientDropdown
      }
    ]
  }
},

I get this error Uncaught Error: Collection name must be specified as string for server-side search.

What I really don't understand from that link is how I access the information from the combined subscription on the client side so that I can implement it into my code.

Sorry if I am wasting your time, this one is just going right over my head.

tgeene avatar Jan 15 '15 18:01 tgeene

I haven't worked on this for a while, but I imagine it involves removing the collection field altogether, and using the ingredientsXrecipes publication to push combined data into the autocompleteRecords collection, which you can then search all at once.

Take a look at https://github.com/mizzao/meteor-autocomplete/blob/master/autocomplete-server.coffee to see what that publication would look like.

mizzao avatar Jan 15 '15 18:01 mizzao

I get how to publish the data, the problem I am having is pulling the data.

Example:

Meteor.publish("ingredientsXrecipes", function () {
    return [
      Ingredients.find({}, { fields: { ingredient: 1, deleted: 1 }, $sort: { ingredient: 1 } }),
      Recipes.find({}, { fields: { recipe: 1, deleted: 1 }, $sort: { recipe: 1 } })
    ];
  });

But I can't just call ingredientsXrecipes.find(). How do I call the data being joined in the publish?

-- Edit--

Sorry if I came off rude, I am just rather confused as to what to do.

tgeene avatar Jan 15 '15 20:01 tgeene

You have to publish the cursors to autocompleteRecords. Try doing the following:

Autocomplete.publishCursor(Ingredients.find( ... ), this);
Autocomplete.publishCursor(Recipes.find( ... ), this);
this.ready();

Instead of the return cursors you have there.

You can choose whether to filter on the client or the server. I think that if you are using a subscription, you will need to make sure you are sending an appropriate selector to the server (right now your selector function isn't returning an object even, just a string.)

mizzao avatar Jan 16 '15 04:01 mizzao

Ok so I did this:

Server

Meteor.publish("ingredientsXrecipes", function () {
  Autocomplete.publishCursor(Ingredients.find({}, { fields: { ingredient: 1, deleted: 1 }, $sort: { ingredient: 1 } }), this);
  Autocomplete.publishCursor(Recipes.find({}, { fields: { recipe: 1, deleted: 1 }, $sort: { recipe: 1 } }), this);
  this.ready();
});

Client:

'autofillInput': function() {
  return {
    position: "bottom",
    limit: 1000,
    rules: [
      {
        callback: function(doc, element) { $("#ingredientId").val(doc._id); },
        collection: Autocomplete,
        field: "ingredient",
        filter: { deleted: false },
        selector: function(match) { return getIngredientExpression($("#ingredient").val()); },
        template: Template.ingredientDropdown
      }
    ]
  }
},

And the error I got: Uncaught ReferenceError: Autocomplete is not defined.

tgeene avatar Jan 16 '15 19:01 tgeene

tgeene, if you did not fix it yet, you need to change to: collection: ingredientsXrecipes in the rules array

vicusbass avatar Apr 29 '15 09:04 vicusbass