Meteor-Community-Packages / meteor-autocomplete

Client/server autocompletion designed for Meteor's collections and reactivity.
https://atmospherejs.com/mizzao/autocomplete
MIT License
350 stars 109 forks source link

Supporting multiple collections? #57

Open andreimcristof opened 10 years ago

andreimcristof commented 10 years ago

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,

mizzao commented 10 years ago

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.

andreimcristof commented 10 years ago

thank you for your detailed reply!

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

mizzao commented 10 years ago

@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.

andreimcristof commented 10 years ago

thank you for your suggestion! will give this a try

berfer commented 10 years ago

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

mizzao commented 10 years ago

@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.

tgeene commented 9 years ago

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?

mizzao commented 9 years ago

@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.

tgeene commented 9 years ago

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 commented 9 years ago

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.

mizzao commented 9 years ago

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.

tgeene commented 9 years ago

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.

mizzao commented 9 years ago

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.)

tgeene commented 9 years ago

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.

vicusbass commented 9 years ago

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