As you mentioned, the site https://speakernet.co.uk manages filters using the session, and I recently refactored it to use ajax to change the filter. Its only a small application (about 1700 records in the largest table) so in the main talks listing, changing the filter causes a complete page reload.
On the map page its slightly different as it uses ajax to refetch the markers and replots them on the map already in view.
The filter mechanism is the same though and there is a couple of routes;
Route::post('/filter','FiltersController@create')->name('filter.create');
Route::delete('/removefilter/{filter}','FiltersController@destroy')->name('filter.destroy');
which lead to the FiltersController. This is responsible for setting the filter state in session, then when the map pins are requested, or the grid reloaded, the controller will use any filters present.
The filtersController;
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Traits\TalksHelpers;
use Illuminate\Http\Request;
class FiltersController extends Controller
{
use TalksHelpers;
public function create(Request $request)
{
$this->initialiseFilters();
if($request->has('cat')){
$this->filters['category'] = $request->cat;
}
if($request->has('fee')){
$this->filters['fee'] = $request->fee;
}
if($request->has('region')){
$this->filters['region'] = $request->region;
}
if($request->has('recency')){
$this->filters['recency'] = $request->recency;
}
if($request->has('notice')){
$this->filters['notice'] = $request->notice;
}
$this->setFilteredFlag();
$this->saveFilters();
return enumerate_filters($this->filters);
}
public function destroy(Request $request, $filter)
{
$this->unsetFilter($filter);
$this->setFilteredFlag();
return enumerate_filters($this->filters);
}
}
This calls on a Trait which is also shared by the grid controller
<?php
namespace App\Http\Controllers\Traits;
use App\Fee;
use App\Category;
use App\Region;
use App\Tag;
trait TalksHelpers
{
protected $filters;
public function prepareDropdowns()
{
view()->share('feelist', Fee::all());
view()->share('categoryList',Category::orderBy('name','ASC')->get());
view()->share('regionList',Region::orderBy('sort','ASC')->get());
}
public function preparePotLuck()
{
view()->share('potLuck', Tag::inRandomOrder()->take(25)->pluck('name', 'slug'));
}
public function initialiseFilters()
{
//ensure that the filters are setup in session and hydrated into controller
$this->filters = session('filters');
if(!isset($this->filters)) {
$this->resetFilters();
}
}
private function resetFilters()
{
// create empty filters
$this->filters = [
'category' => '',
'fee' => '',
'region' => '',
'recency' => '',
'notice' => '',
'tagged' => '',
'filtered' => false]
;
session(['filters' => $this->filters]);
}
private function saveFilters()
{
// save updated filters to the session
session(['filters' => $this->filters]);
}
private function unsetFilter($filter)
{
$this->initialiseFilters();
$this->filters[$filter] = '';
$this->setFilteredFlag();
return $this->saveFilters();
}
private function setFilteredFlag()
{
$f = $this->filters;
if (!
empty($f['region'])
|| !empty($f['category'])
|| !empty($f['fee'])
|| !empty($f['recency'])
|| !empty($f['notice'])
|| !empty($f['tagged']))
{
return $this->filters['filtered']= true;
}
return $this->filters['filtered']= false;
}
}
Javascript from the grid view;
@section('page-js')
<script>
window.onload = function () {
$('#category').on('change', function(){
applyFilter('cat=' + this.value);
});
$('#fee').on('change', function(){
applyFilter('fee=' + this.value);
});
$('#region').on('change', function(){
applyFilter('region=' + this.value);
});
$('#recency').on('change', function(){
applyFilter('recency=' + this.value);
});
$('#notice').on('change', function(){
applyFilter('notice=' + this.value);
});
}
function applyFilter(filter)
{
axios.post('/filter?' + filter)
.then(function(response){
location.reload();
});
}
function removeFilter(filter)
{
axios.delete('/removefilter/' + filter)
.then(function(response){
if(filter == 'category') {
location.replace('{{ route('talks.index') }}');
} else {
location.reload();
}
});
}
</script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
@endsection
and then the grid TalksController (just the relevant bits)
use TalksHelpers;
protected $status;
//list all the available talks (topics)
public function index(Request $request)
{
$this->initialiseFilters();
return $this->talks();
}
/**
* gather details for the main listing view
* @return view
*/
public function talks()
{
$filters = $this->filters;
// get topics, filtered by adding local scopes through the Topic class
$talks = Topic::with('speaker.region','tagged','category')
->regions($filters['region'])
->category($filters['category'])
->fee($filters['fee'])
->recency($filters['recency'])
->tagged($filters['tagged'])
->published()
->notice($filters['notice'])
->orderBy('subject')
->paginate(15);
$this->prepareDropdowns();
$this->preparePotLuck();
if(isset($filters['tagged'])){
view()->share('siteTitle', "SpeakerNet - index of talks tagged {$filters['tagged']}");
} else {
view()->share('siteTitle', 'SpeakerNet - your source for interesting speakers');
}
return view('guest.talks')->with(compact('talks','region'))
->withFilters($filters)
->withEnumeratedFilters(enumerate_filters($filters));
}
edit: Some of the simpler model scopes
public function scopeCategory($query,$category)
{
if($category!=""){
return $query->where('category_id',$category);
}
return $query;
}
public function scopeFee($query,$fee)
{
if(!Empty($fee)){
return $query->where('fee_id',$fee);
}
return $query;
}
public function scopeRecency($query,$recency)
{
if(!Empty($recency)){
$cutoff = Carbon::now()->subDays($recency)->format('Y-m-d');
return $query->where('updated_at','>',$cutoff);
}
return $query;
}
Sorry, a lot of code. Hopefully you will pick up some ideas