Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filters on lists #175

Closed
alexgmin opened this issue Oct 11, 2016 · 18 comments
Closed

Filters on lists #175

alexgmin opened this issue Oct 11, 2016 · 18 comments
Assignees
Labels

Comments

@alexgmin
Copy link

alexgmin commented Oct 11, 2016

This type of filters
Image
(This screenshot is from https://github.com/zofe/rapyd-laravel)

The code to do this on rapyd

$filter->add('name', 'Name', 'text');

$filter->add('featured','Featured', 'select')
    ->options([null => 'Enabled', 1 => 'Enabled: Yes', 0 => 'Enabled: No']);

$filter->add('state','State', 'select')->options(
    Item::getStates()
);
$filter->add('created_at','Created', 'daterange')
    ->format('m/d/Y', 'en');

$filter->add('brand.name', 'Brand', 'select')
    ->options(
        Brands::getBrands()
    );

$filter->add('user.id', 'User ID', 'text')->clause('where')->operator('=');

What would be the best way to implement something like this on backpack?
Are there plans to do something similar?

@OwenMelbz
Copy link
Contributor

This is a feature that's in development/planning

However if you add your own UI code into the template, you can utilise the "addClause()" method described here -> https://laravel-backpack.readme.io/docs/crud-full-api#usage-level-2-often

an example could be -> #83

@tabacitu
Copy link
Member

Hi @alexgmin ,

There's no one-line way to do it right now. It's a know feature request and one of the 2 core features that are urgent (multi-language CRUDs, filters). I like how it's done in rapyd, thank you, we should take a closer look before we implement it ourselves and learn from it.

For now, you can "fake" the filter functionality by adding new buttons on top of the form (next to the Add button). You can do that by adding buttons that, instead of being actual buttons, will be dropdowns or inputs.

So something like this should work:

public function setup()
{
  // add a button whose HTML is in a view placed at resources\views\vendor\backpack\crud\buttons
  $this->crud->addButtonFromView('top', 'featured_filter', 'featured_filter', 'end');

  if (Input::get('featured_filter')) {
    $this->crud->addClause(Input::get('featured_filter'));
  }
}

resources/views/vendor/backpack/crud/buttons/featured_filter.blade.php:

<select name="featured_filter">
  <option value="featured">Featured</option>
  <option value="not_featured">Not Featured</option>
  <option value="">Whatever</option>
</select>

<script>
jQuery(document).ready(function($) {
        $("select[name=featured_filter]").change(function() {
                     window.location.href = "{{ url($crud->route) }}?featured_filter="+$(this).val();
                 });
    });
</script>

Please note I haven't tested the code above, it's more like pseudocode :-)

@alexgmin
Copy link
Author

Yeah, that should work for now. Thanks, @tabacitu . Backpack is awesome, btw, we love it.

@tabacitu
Copy link
Member

tabacitu commented Oct 11, 2016

I think we could implement something very similar to how fields, buttons and columns work: filter types, each in their own blade file in /src/resources/views/filters/. That would help in terms of familiarity and extensibility.

$this->crud->addFilter([
  'name' => 'featured',
  'type' => 'dropdown',
  'values' => [
               'featured' => 'Featured',
               'not_featured' => 'Not featured',
               '' => 'Whatever',
              ],
], function($value) {
  if ($value != '') {
    $this->crud->addClause($value);
  }
});

The filters would come with some pre-built logic (probably) and you can pass a closure as an optional second parameter to overwrite it with your own.


What WOULD be very useful, and I hope it's ok if I hijack this thread for it, would be to make a list of all possible filter types. So that we know if this implementation is ok. //TODO

Off the top of my head:

  • select (dropdown) - keep the same name from field and column;
  • text (input type text; write and click submit; only useful if it searches something that isn't also a column, otherwise they could use the datatable search);
  • forumula: x > 0, x < y, x = z, y != z - would have:
    • [x dropdown] [operator dropdown] [z dropdown]
    • or
    • [x dropdown] [operator dropdown] [z input]

@tabacitu tabacitu self-assigned this Oct 11, 2016
@tabacitu tabacitu added this to the 3.2 - maturity milestone Oct 11, 2016
@tabacitu
Copy link
Member

Also, I think we should allow some flexibility to where those filters show up. Just like you can add buttons in different stacks, we might create special stacks for filters (or create more stacks for both buttons and filters).

I think useful places where we could add filters would be:
screen_shot_2016-10-25_at_09_52_04

Thoughts?

@mpixelz
Copy link

mpixelz commented Nov 3, 2016

just my 2cents.. what about having a separate new row at the top after the title and before the actual table row?
something like this:
screenshot-filters

@tabacitu
Copy link
Member

tabacitu commented Nov 3, 2016

@mpixelz yep, that could be an option too. So 4 stacks should do:

  • header
  • top_right
  • center
  • left
  • right

@OwenMelbz
Copy link
Contributor

@tabacitu I vaguely remember you saying you're working on this? is that still the case?

@tabacitu
Copy link
Member

tabacitu commented Nov 3, 2016

Yup. WIll be done by Nov 17th, the latest. Currently my thinking is to have these field types:

@tabacitu
Copy link
Member

tabacitu commented Nov 3, 2016

Currently my thinking is to first develop these most important filter types:

  • select
  • select2
  • date
  • daterange
  • formula

The general syntax would be:

$this->crud->addFilter($options, $values, $filter_logic);

I'll be releasing the select and select2 in about two weeks, and their syntax should be something like this:

$this->crud->addFilter([ // select from enum column values
          'name' => 'published',
          'type' => 'select',
          'label'=> 'Published'
        ],
        function() {
            return App\Models\Article::getPossibleEnumValues('published');
        }
        // , function($value) {} // optional filter logic overwrite; by default it makes sure that {name} == $value
        );

$this->crud->addFilter([ // select_from_array
          'name' => 'featured',
          'type' => 'select_from_array',
        ], [ // values
           'featured' => 'Featured',
           'not_featured' => 'Not featured',
           '' => 'Whatever',
          ]
        // , function($value) {} // optional filter logic overwrite; by default it makes sure that {name} == $value
        });

$this->crud->addFilter([ // select_from_array
          'name' => 'featured',
          'type' => 'select_from_array',
        ], function() {
            // get all Users and create an associative array with ['id' => 'name']
            return App\Models\User::all()->keyBy('id')->pluck('name');
        }
        // , function($value) {} // optional filter logic overwrite; by default it makes sure that {name} == $value
        });

This would eliminate the need for a bunch of filter types for different purposes. What we'll do instead is give a number of examples, to match most common uses:

  • enum
  • select_from_array
  • select_from_model
  • select_from_ajax

What do you guys think?

@OwenMelbz
Copy link
Contributor

Not sure if this is simplifying it too much, but could we utilise model scopes? for the actual filtering? maybe just define a scope, then internally handle the rest?

what would the select from ajax do? would it not be better to just do that by default?

additionally I've found https://github.com/dangrossman/bootstrap-daterangepicker to be good plugin for date ranges, specifically the "last 30 days" etc type fields.

but apart from that all happy here :)

@tabacitu
Copy link
Member

tabacitu commented Nov 3, 2016

Yup, you could move the filtering logic entirely into scopes with something like this:

$this->crud->addFilter([ // select from enum column values
          'name' => 'published',
          'type' => 'select',
          'label'=> 'Published'
        ],
        function() {
            return App\Models\Article::getPossibleEnumValues('published');
        },
        function($value) {
            if ($value != '') {
              $this->crud->addClause($value); // adds the name of the variable as a scope
            }
        }
        );

Oooor... we could use the third parameter as either a closure or string. If you pass a closure, it will run the closure. If you pass a string, it will run a pre-defined operation. We could use these strings (similar to the PHP comparison operators):

  • "==" - db_value == selected_value [this would be the default]
  • ">" - db_value > selected_value
  • ">="
  • "<"
  • "<="
  • "<>"
  • "!="
  • "like" -> db_value like %selected_value%
  • "scope" - add the value as a scope
  • anything else?

Not 100% sure this is achievable though.

Definitely agree to dangrossman's daterangepicker. It's awesome.

The select_from_ajax would load the select2 values with ajax. For when you have 1mil users and you want to filter the ones with the first_name==Owen. We can't do them all like this, because it would be too inconvenient to insert a filter then. In order to have an ajax filter you need to:

  • add the filter (similar syntax, but you also give out the route to the ajax results)
  • add a route (ex: /product/ajax-names)
  • create a method ( ex: ProductCrudController@ajaxNames)

@tabacitu
Copy link
Member

tabacitu commented Nov 8, 2016

Guys,

I've been working on filters and I'm really happy with the progress. The syntax is very similar to the above: very powerful and it's very easily customizable. The prototype is ready on a feature branch (only select and select2 right now) and I'm pretty sure you'll love it.

I am, however, struggling with one interface dilemma and I'd like a second opinion. To recap, there are 4 places ("stacks") where you can place filters: top, right, bottom, left. I expect the most used to be "top" and "right", because that's where it makes most sense.

Here's how they all look:
screen shot 2016-11-08 at 18 57 23
screen shot 2016-11-08 at 18 57 39
screen shot 2016-11-08 at 18 57 51
screen shot 2016-11-08 at 18 57 05

I like how "right" and "left" look. But I'm not very happy with "top" and "bottom". I've also tried it this way, it looks cleaner, but it seems they take up too much space, if the label and select are on separate rows:
screen shot 2016-11-08 at 18 59 11

I've also thought about having them in a drawer menu (hidden, click "Filters" and a drawer opens up from the right), but it's a two-click process instead of a one-click process and that might be annoying. Plus, it opens up the issue of letting the user know somehow that some filters are applied.

Any ideas/suggestions? Do you think it's ok to leave "top" like that?

Thanks a lot. Cheers!

@borutjures
Copy link

I like AngelList's top filters for their jobs search (https://angel.co/jobs):

filters

User always knows which filters are applied. Even with a lot of filters selected they don't take up too much space.

There is also an option to save filters. Most filters have option to include or exclude values.

Drop-downs for filters are optimized based on their values:

filter1

filter2

filter3

filter4

filter5

Just an idea since you asked :-) Your design works too. Maybe for top/bottom the filters could be included in the same box as the table. Maybe below the top buttons.

@tabacitu
Copy link
Member

tabacitu commented Nov 9, 2016

@properius ... you rule, man. I can't thank you enough.

I really really like the AngelList filters, but I dismissed them at first. I thought it would make it far too difficult for developers to customize and make their own filters. But I played around for a few hours just to make sure and... I think I ended up with something excellent.

So the only thing that would make it very very difficult to implement in Backpack is the "tag" system - every filter gets added as a tag in that input. It would make it a nightmare to handle when building custom filters, which I imagine will happen a lot. Customization is a very important feature of Backpack. And it's also the one thing that makes the filtering a little bit confusing there: you activate the filter from one spot but then deactivate it from another spot.

BUT. I played around with different ways to show the "active filters" and I really think you'll like my solution. So without further ado, I present to you... (drums rolling): the bootstrap navbar.
screen shot 2016-11-09 at 11 29 06

I know, right?! It was literally in front of our eyes this whole time. I found it might be a perfect solution for our filters interface. I'm still testing its reliability, but right now I am very very happy with this solution and I wanted to show you some screenshots. Click to open them full-size for a closer-to-reality experience.

screen shot 2016-11-09 at 11 20 17
screen shot 2016-11-09 at 11 20 29
screen shot 2016-11-09 at 11 20 39
screen shot 2016-11-09 at 11 20 54
screen shot 2016-11-09 at 11 21 05

Here's what I think are the PROs and CONs of using the navbar vs using the filters above:

PROs CONs
prettier -
collapsed on mobile by default -
eliminates the need for multiple stacks (top, left, bottom, right) -
they fit nicely in the perfect spot for filters: right above the datatable -
are both very visible AND out-of-the-way -
active filters are easy to spot using the "active" state -
allows for very simple filters, using Eloquent scopes (Trashed, Published, etc) -
allows for very complex filters with no added space, because the actual filter will be inside a dropdown (think x > y OR z >= t ) -
will still look good with 20+ filters, on multiple rows -
coding a custom dropdown (filter) should be very familiar to developers, since it's just a bootstrap navbar coding a custom filter WILL have more complicated JS than before and will be pretty different from custom fields
- on the JS side, it will be kind of hacky to place values on <li> elements or <input type="hidden"> inside these elements

As you might notice, I am very very biased towards switching to this new system. If you see something against it (or an even better solution), please speak now. It will be very very difficult to change it after we launch it. Also, feedback please (of course).

And @properius , thanks again :-)

Cheers!

@mohammed-alsiddeeq
Copy link

You didn't left us something to say.
This is just awesome....

@tabacitu
Copy link
Member

tabacitu commented Nov 10, 2016

I'm done implementing the most important filters and will be launching the feature soon. Any feedback on the filter docs?

https://laravel-backpack.readme.io/v3.0/docs/filters

Cheers!

@tabacitu
Copy link
Member

Done, launched! Use it and abuse it :-)

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

No branches or pull requests

6 participants