itemsapi / itemsjs

Extremely fast faceted search engine in JavaScript - lightweight, flexible, and simple to use
Apache License 2.0
346 stars 41 forks source link

Supporting filtering for a value range #9

Closed mkgn closed 6 years ago

mkgn commented 6 years ago

Hi,

I would like to know whether it's possible to configure the library to filter for min-max value. For example, a list of items that has a price attribute where I can show a slider with a min/max value that gets filtered.

As of now it works great for item lists.

cigolpl commented 6 years ago

Hi,

In terms of filters I think it would be good to create some hook which allows developers modify data before search so then developers will have unlimited flexibility (i.e. with using lodash library). I will think more about how to create such a interface.

If you also mean creating facets like that:

image

Development is quite complex, not sure if that is really needed for small datasets and I am afraid it can slow down the system (I can be wrong) but I am considering this in the future though

Btw Is it not a problem for you that ItemsJS works fast only up to 1000 items ?

mkgn commented 6 years ago

ItemJS being able to handle ~1000 items is more than enough for small to medium projects. The suggestion was made because that will provide some completeness to the library in my opinion. As you have explained, I was thinking about some kind of a hook with a facet type (min/max) if specified at the time of initializing will fireup a function for the developers to do whatever they want.?

Initially can be some kind of a quick fix, before thinking about a full blown solution.

cigolpl commented 6 years ago

Yeah, that might be very useful and make it more complete. I think initially about something like this before making a real search:

itemsjs.prefilter(function(items) {
  // make operations on items i.e. min / max using lodash
  return items;
})
itemsjs.search()

or

itemsjs.search({
  query: 'shoes',
  prefilter: function(items) {
    return items;
  }
})

The second solution should be simplier as I guess there will be a few new lines of code and it's also quite flexible in terms of API changes in the future. What is your opinion ?

cigolpl commented 6 years ago

Hey, I've added the second solution to the code https://github.com/itemsapi/itemsjs/commit/c6578afa0e96768677b2889236d13435a7ec2ba5. Now it should be more customizable I hope

mkgn commented 6 years ago

Great... let me play around and see. What I am trying to do is have a range slider to work with this

cigolpl commented 6 years ago

@mkgn I hope this problem is at least temporarily solved so I am closing this ticket. Thx for all feedback

ghost commented 6 years ago

Hello, I'm sorry for opening this ticket again, but I still don't understand how can prefilter function will help me to create a price range filter.

Can you please explain how can I link the prefilter with the configuration.aggregators filters in order to add the price range?

Thank you very much! Andrew

cigolpl commented 6 years ago

Hello Andrew,

You can use prefilter option for narrowing items based on price i.e.:

prefilter: function(items) {
  return items.filter(item => {
    if (item.price > 6) {
      return true;
    } 
    return false;
  });
}

but it will not generate nice aggregation / facet ranges like in the example in the top. Number ranges are not supported right now.

If you want more powerful ranges you can try https://github.com/itemsapi/elasticitems (if you use Node.js) or Elasticsearch

ghost commented 6 years ago

Hello @cigolpl

Thank you very much for the info.

I manage to create a filter option with the prefilter function. https://gyazo.com/fb1658cf95ad25d77d598a21513e420e

Best Regards and thank you very much again for the help.

cigolpl commented 6 years ago

Wow, it looks really nice! Could I use your animation for a demo purposes ? (i.e. probably I'll convert to gif and include in documentation)

ghost commented 6 years ago

Thank you very much, Sure, you can include it in the documentation. I'm working on the design so I will have a better version soon enough.

ghost commented 6 years ago

Hi @cigolpl,

Here is an updated version of my demo if you still need it: https://www.dropbox.com/s/3vwocwoudbsz2p1/filter.mp4?dl=0

Here is the prefilter code I used to create the range filter

prefilter: function(items) {

                  var items = items.filter(product => {
                    if (product.price >= vm.minPrice && product.price <= vm.maxPrice) {
                      return product;
                    }
                  });
                  return items;
}

I also have few questions: I'm using Vue Js on this project and I'm getting all data async using ajax and I used $(document).ajaxStop to wait until all products are loaded, do you recommend a better way to do this?

It is possible to hide facets if there are only 0 or 1 buckets inside? I used v-if="buckets.length > 1" but when I sort or apply filters, other filters will become inactive and they will be hidden automatically.

I want to make the aggregators something like this: https://gyazo.com/a868aeac2cb04d4f256233b78d76ebce

Kind Regards Andrew

cigolpl commented 6 years ago

Hello Andrew, thanks for your use case. The demo and its aesthetic design looks really amazing!

In terms of your question personally instead of .ajaxStop I would use for specific ajax request (if this is what you mean):

$.ajax({
  url: url,
  success: function() {
    alert('loading finished'); 
  }
});

For ajax spinner I usually use sth like this:

$.ajaxSetup({
  beforeSend:function() {
    $('.ajax-loader').show();
  },
  complete:function() {
    $('.ajax-loader').hide();
  }
});

I am not so skilled in Vue but I think you can hide facets if there are 0 or 1. Not sure exactly how but I think you are close to the solution. Maybe using "v-show" or another logical conditions

cigolpl commented 6 years ago

Btw how is the sorting working in your demo - is it using fully itemsjs or making ajax request to the server ? It's a bit slowier when you click "best selling" than in the rest demo ;)

ghost commented 6 years ago

Thank you very much for the kind words about the design, for the big help and fast response. :)

The server responds with only 50 products per request and has multiple pages so I iterate through all pages and save all products in the allProducts array. Here is an example:

var allProducts = []
var loadaAll = function(url, sortBy, totalProducts) {
        var vm = this;
        allProducts = [];
        var productDeferreds = [];

        var pageStartNum = 1;
        for (var i = 0; i < totalProducts / 50; i++) {
          var pageNumber = pageStartNum + i;
          var $productRequest = $.get(url + '?sort_by=' + sortBy + '&page=' + pageNumber, function(response) {
            allProducts.push.apply(allProducts, response.products);
          });
          productDeferreds.push($productRequest);
        }
      };

So this is how I get all products async and I'm using $(document).ajaxStop to find out if all products are loaded.

Regarding the sort function, I'm using ItemsJs to sort by Title, Price (asc / desc) and Categories), for best-selling and featured I don't have access in the JSON so I have to set allProducts to empty and run the loadAll function again with the proper sortBy property and get all best-selling or featured products. I'm not sure if this is the best approach, it takes few seconds to load 500 - 1000 products.

cigolpl commented 6 years ago

Sounds good. In terms of sorting best-selling and featured maybe it would be better if you add fields to the json best-selling, featured as true / false or 1 / 0 or "1" / "0" and then sort in the same way as by title, price, categories. Not sure if it is possible in your case but it should be much faster than making requests to the server and reindexing

Kugeleis commented 1 year ago

Is there a working solution for a range filter of analog values around? The links above are dead.

japo32 commented 1 month ago

Hello @cigolpl ! when do you think this feature will be available on the packaged release?

japo32 commented 1 month ago

as a workaround I've filtered the items before recreating a new instance. Not ideal but works in the short term.

const handleFilterChange = (values) => {
    const filteredItems = applyRangeFilters(values, items);

    const itemsjsInstance = ItemsJS(filteredItems, itemsJsConfig);
    ...
cigolpl commented 1 month ago

Hey @japo32, filtering through filter function should be much better than recreating instance every time (you save re-indexing time):

var result = itemsjs.search({
  filter: function(item) {
    return item.rating >= 8 && item.reviews_count >= 200;
  }
});
japo32 commented 1 month ago

Hey @japo32, filtering through filter function should be much better than recreating instance every time (you save re-indexing time):

var result = itemsjs.search({
  filter: function(item) {
    return item.rating >= 8 && item.reviews_count >= 200;
  }
});

Ok thanks. I've updated my code to use that.