Skip to content
This repository has been archived by the owner on May 17, 2018. It is now read-only.

[Feature Request] How can filters be applied to PageableCollection? #75

Open
twalker opened this issue Apr 18, 2013 · 10 comments
Open

[Feature Request] How can filters be applied to PageableCollection? #75

twalker opened this issue Apr 18, 2013 · 10 comments

Comments

@twalker
Copy link

twalker commented Apr 18, 2013

In the FAQ, it states, "...you can do filtering fairly easily with Backbone's built-in support for Underscore.js methods."

I am looking to do just that, but am not sure of a good approach that maintains accurate pagination state and model references across other views.

Is there an example of applying filters to a PageableCollection?

Here's the api I'm shooting for:

var Desert = Backbone.Model.extend({
  defaults: {
    flavor: null,
    timesEaten: null
  }
})
var Deserts = Backbone.PageableCollection.extend({
  model: Desert
});

// Deserts will be a collection shared by other views.
var deserts = new Deserts([
  {flavor: 'chocolate', timesEaten: 100},
  {flavor: 'strawberry', timesEaten: 1},
  {flavor: 'vanilla', timesEaten: 999},
]);

deserts would have a _filters object that could be
modified through addFilter, removeFilter, clearFilters methods

deserts.addFilter('flavor', function(model){
  return model.get('flavor') === 'vanilla';
});

deserts.addFilter('favorite', function(model){
  return model.get('timesEaten') > 100;
});

A bound backgrid and/or paginator would reflect state with the filters applied.
Probably through incorporating an interal _applyFilters, something like:

_applyFilters: function (models) {
  _.each(this._filters, function (fnValue, key, list) {
    models = models.filter(fnValue);
  }, this);
  return models;
},

Return to the full collection:

deserts.clearFilters();

It's not clear to me how I can extend BackbonePageable to provide these filtering methods without overriding internal methods.

An example or suggestion in the documentation would be great.

Thank you for such a great project.

@twalker
Copy link
Author

twalker commented Apr 20, 2013

My working solution is to not use Backbone.PageableCollection for my full collections,
but instead provide it with a pre-filtered collection and reset Pageable#fullCollection when filters change. e.g.

var Deserts = Backbone.Collection.extend({
  model: Desert
});
// mixin filtering functionality
lodash.extend(Deserts.prototype, filterable);

var deserts = new Deserts(boostrapped.deserts)

var pageableDeserts = new BackbonePageable(deserts.clone().models, {
  state: { pageSize: 15},
  mode: 'client'
});

// ... 
// User selects filters from views, 
// the filters are added/removed on the base colleciton
// ...

// Reset the full collection with a filtered subset of models  
pageableTerritories
    .getFirstPage()
    .fullCollection
    .reset(deserts.filtered());

It'd be good to know if there is a better approach. Or, would a PageableCollection.Extension.Filterable be a welcome addition to the project?

@wyuenho
Copy link
Member

wyuenho commented Apr 22, 2013

There's a much simplified ClientSideFilter in Backgrid's master now, maybe you can take a look to get some ideas. It's very simple.

As to adding view/in-place filtering capabilities to backbone-pageable, I think the answer will probably be a no. I consider backbone-pageable is now feature-complete and in maintenance mode. There are already 28 Underscore methods attached to both PageableCollection and PageableCollection.fullCollection. That's already an overabundance.

As you may have discovered already, resetting fullCollection is the easiest way to have all the pagination state recalculated automagically. If you are trying to filter a pageable collection under server side mode, the pagination state should be managed on the server instead.

@wyuenho wyuenho closed this as completed Apr 22, 2013
@wyuenho
Copy link
Member

wyuenho commented May 6, 2013

BTW, you can attach extra parameters to pageableCollection.queryParams, and the value can be a function. This way you can have the filter persist across pagination under server mode.

@twalker
Copy link
Author

twalker commented May 6, 2013

Great to know, thanks, I'll be using that soon. I'm using ClientSideFilter and PageableCollection along with my own collection filters. It's working splendidly.

FWIW: Pre-filtering was the key for me, instead of basing my collections off of PageableCollection. The pattern I'm using is a collection "blessed" with a filters object composed of N filtering functions that I can add and remove by key. Collection.filtered() is what I provide to PageableCollection.

    onFilterChange: function(){
        //...add/remove filters to this.collection....
        this.pageableCollection
            .getFirstPage()
            .fullCollection
            .reset(this.collection.filtered());
        return this;
    }

@wyuenho
Copy link
Member

wyuenho commented May 6, 2013

a collection "blessed" with a filters object composed of N filtering functions that I can add and remove by key.

This sounds interesting. I'm curious does your implementation work for all 3 modes? Can I add a couple of filters and have a server mode PageableCollection send out extra query params?

@wyuenho wyuenho reopened this May 6, 2013
@twalker
Copy link
Author

twalker commented May 6, 2013

No. If only I had such foresight.
I'm only accounting for the 'client' mode with bootstrapped collections, and each 'filter' is just a filtering function that's directly applied to the collection.
https://github.com/twalker/eggshell/blob/gridable/public/js/src/collections/mixins/filterable.js

If you're feeling brave, here's more context in how I'm using it with the PageableCollection:
https://github.com/twalker/eggshell/blob/gridable/public/js/src/main.js

I haven't tried it, yet, but I imagine it wouldn't take much for the filters to include something like 'toQueryParams' to use in the server and infinite modes. But as it stands, PageableCollection knows nothing of my pre-filters.

@josx
Copy link
Contributor

josx commented Jul 27, 2013

In my case I was needing server search thru backbone-pagebale. Maybe someone could use it:

Add query as state:

    state: {
...
        query: {}
...
    },

add query to queryParams:

    queryParams: {
...
       query: function() { return this.state.query; },
...
    }

add method to set query:

    setQuery: function (query, page_size) {
         var state = this.state;
        if(query != state.query) {
             state = _.clone(this._initState)
             state.pageSize = page_size;
        }
         state = this.state = this._checkState(_.extend({}, state, {
             query: query,
         }));
     },

@Artforge
Copy link

Artforge commented Aug 8, 2013

josx, that's exactly what I'm looking for - but It's not quite working for me - where are you calling setQuery from? Any chance you could share a little more of your implementation code with me?

@josx
Copy link
Contributor

josx commented Aug 16, 2013

@toekneestuck
Copy link

For what it's worth, I needed search/filter functionality for my Pageable collections as well and started out with extending the PageableCollection, but recently just forked and committed my own additions. There's still some work to do, but it's a good start for what I need, and might help some others.

I merged in the filtering style of Backbone.Paginator so you can do things like:

collection.addFilter({
    field : 'my_attr',
    type: 'contains',
    value : 'some value'
})

I added built in filter types for client collections, and then for server/infinite mode I map the filter types to query params, so the above code might make a query like this:

/your/endpoint?filter=my_attr&filter_operation=contains&filter_value=some%20value&page=1

It's setup to play well with multiple filters as well, and will turn the query params into arrays like:

/your/endpoint?filter[]=my_attr&filter[]=other_attr&filter_operation[]=contains&filter_operation[]=equals&filter_value[]=some%20value&filter_value[]=something&page=1

Searching can also pretty simple and is configurable with default fields to search on:

collection.defaultSearchField = 'my_attr';
collection.search('my query');

In server/infinite mode it would make a call something like this:

/your/endpoint?query=my%20query&query_field=my_attr

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants