Skip to content

Commit 1e1b1d5

Browse files
committed
Add filters example + AJAX based <Dropdown> element
1 parent 9a7e7cd commit 1e1b1d5

File tree

19 files changed

+277
-40
lines changed

19 files changed

+277
-40
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ yarn-error.log
1818
/public/css/*
1919
/public/js/*
2020
composer.lock
21+
composer.phar
2122

2223
.phpstorm.meta.php
2324
_ide_helper.php

app/Http/Requests/StoreUserRequest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function rules()
1818
'last_name' => 'required|string|max:100',
1919
'middle_name' => 'nullable|string|max:100',
2020
'email' => 'required|email|unique:users',
21-
'roles' => 'required|array|exists:roles,id',
21+
'roles' => 'required|array|exists:roles,name',
2222
'avatar' => $this->request->has('avatar') ? 'nullable' : 'image',
2323
'password' => 'required|min:6',
2424
];

app/Http/Requests/UpdateUserRequest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function rules()
1818
'last_name' => 'required|string|max:100',
1919
'middle_name' => 'nullable|string|max:100',
2020
'email' => 'required|email|max:200|unique:users,email,'.$this->user->id.',id',
21-
'roles' => 'required|array|exists:roles,id',
21+
'roles' => 'required|array|exists:roles,name',
2222
'avatar' => 'nullable|image',
2323
'password' => 'nullable|min:6'
2424
];

app/Http/Resources/RoleResource.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class RoleResource extends JsonResource
1515
*/
1616
public function toArray($request)
1717
{
18-
return $this->resource->toArray();
18+
$data = $this->resource->toArray();
19+
20+
return ['id' => $data['name'], 'title' => $data['title']];
1921
}
2022
}

app/Http/Resources/UserResource.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function toArray($request)
2727
$data['abilities'] = $this->getAbilities();
2828
$data['created_at'] = !empty($this->resource->created_at) ? $this->resource->created_at->diffForHumans() : null;
2929
$data['updated_at'] = !empty($this->resource->updated_at) ? $this->resource->updated_at->diffForHumans() : null;
30-
$data['roles'] = Data::formatCollectionForSelect($this->roles);
30+
$data['roles'] = Data::formatCollectionForSelect($this->roles, 'name');
3131
if (isset($data['avatar'])) {
3232
unset($data['avatar']);
3333
}

app/Models/User.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Models;
44

5+
use App\Traits\Filterable;
56
use App\Traits\Searchable;
67
use Illuminate\Contracts\Auth\MustVerifyEmail;
78
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -17,7 +18,7 @@ class User extends Authenticatable implements MustVerifyEmail
1718

1819
use HasRolesAndAbilities;
1920

20-
use Searchable;
21+
use Searchable, Filterable;
2122

2223
/**
2324
* ALlowed search fields

app/Services/User/UserService.php

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
namespace App\Services\User;
44

55
use App\Http\Resources\UserResource;
6-
use App\Models\Role;
76
use App\Models\User;
87
use App\Services\Media\MediaService;
8+
use App\Traits\Filterable;
99
use App\Utilities\Data;
1010
use Bouncer;
11+
use Illuminate\Database\Eloquent\Builder;
1112
use Illuminate\Database\Eloquent\Model;
13+
use Illuminate\Support\Arr;
1214
use Illuminate\Support\Carbon;
1315

1416
class UserService
@@ -49,16 +51,22 @@ public function index($data)
4951
if (!empty($data['search'])) {
5052
$query = $query->search($data['search']);
5153
}
54+
if (!empty($data['filters'])) {
55+
$this->filter($query, $data['filters']);
56+
}
5257
if (!empty($data['sort_by']) && !empty($data['sort'])) {
5358
$query = $query->orderBy($data['sort_by'], $data['sort']);
5459
}
60+
61+
// dd(vsprintf(str_replace('?', '%s', str_replace('?', "'?'", $query->toSql())), $query->getBindings()));
62+
5563
return UserResource::collection($query->paginate(10));
5664
}
5765

5866
/**
5967
* Creates resource in the database
6068
* @param array $data
61-
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|null
69+
* @return Builder|\Illuminate\Database\Eloquent\Model|null
6270
*/
6371
public function create(array $data)
6472
{
@@ -177,4 +185,25 @@ private function clean(array $data)
177185
}
178186
return $data;
179187
}
188+
189+
/**
190+
* Filter resources
191+
* @return void
192+
*/
193+
private function filter(Builder &$query, $filters)
194+
{
195+
$query->filter(Arr::except($filters, ['role']));
196+
197+
if (!empty($filters['role'])) {
198+
$roleFilter = Filterable::parseFilter($filters['role']);
199+
if (!empty($roleFilter)) {
200+
if (is_array($roleFilter[2])) {
201+
$query->whereIs(...$roleFilter[2]);
202+
} else {
203+
$query->whereIs($roleFilter[2]);
204+
}
205+
}
206+
}
207+
208+
}
180209
}

app/Traits/Filterable.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace App\Traits;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
7+
trait Filterable
8+
{
9+
10+
/**
11+
* Filters a query by key:value parameters
12+
*
13+
* @param Builder $query
14+
* @param array $data
15+
* @param string $operator
16+
* @return Builder
17+
*/
18+
public function scopeFilter(Builder $query, array $data, string $operator = 'AND')
19+
{
20+
$operator = strtoupper($operator);
21+
$callbacks = [
22+
'AND_WHERE' => 'where',
23+
'AND_WHERE_IN' => 'whereIn',
24+
'OR_WHERE' => 'orWhere',
25+
'OR_WHERE_IN' => 'orWhereIn',
26+
];
27+
foreach ($data as $column => $value) {
28+
$filterParts = self::parseFilter($value);
29+
if (empty($filterParts)) {
30+
continue;
31+
}
32+
$comparison = $filterParts[1];
33+
$inputVal = $filterParts[2];
34+
if (is_array($inputVal)) {
35+
call_user_func_array([$query, $callbacks[$operator.'_WHERE_IN']], [$column, $inputVal]);
36+
} else {
37+
if (!is_numeric($inputVal)) {
38+
$comparison = 'LIKE';
39+
$inputVal = "%".$inputVal."%";
40+
}
41+
call_user_func_array([$query, $callbacks[$operator.'_WHERE']], [$column, $comparison, $inputVal]);
42+
}
43+
}
44+
45+
return $query;
46+
}
47+
48+
/**
49+
* Parse the filter
50+
* @param $value
51+
* @return array|string|string[]
52+
*/
53+
public static function parseFilter($value)
54+
{
55+
$filterParts = explode(';', $value);
56+
$totalFilterParts = count($filterParts);
57+
if ($totalFilterParts !== 3) {
58+
return [];
59+
}
60+
$value = $filterParts[$totalFilterParts - 1];
61+
if (strpos($value, '|') !== false) {
62+
$filterParts[$totalFilterParts - 1] = explode('|', $value);
63+
}
64+
return $filterParts;
65+
}
66+
}

lang/en/frontend.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"reset_password" => "Reset Password"
6363
],
6464
"phrases" => [
65+
'clear_filters' => 'Clear all',
6566
"loading" => "Loading...",
6667
"sign_out" => "Sign Out",
6768
'all_records' => 'All Records',

resources/js/helpers/api.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export function prepareQuery(args) {
3737
let page = args.hasOwnProperty('page') ? args.page : null
3838
let search = args.hasOwnProperty('search') ? args.search : null;
3939
let sort = args.hasOwnProperty('sort') ? args.sort : null;
40+
let filters = args.hasOwnProperty('filters') ? args.filters : null;
4041
let params = {page: page}
4142
if (search) {
4243
params.search = search;
@@ -47,5 +48,28 @@ export function prepareQuery(args) {
4748
params.sort = sort.direction;
4849
}
4950
}
51+
if (filters) {
52+
for (let key in filters) {
53+
if (!filters[key] || (filters[key].hasOwnProperty('value') && !filters[key].value)) {
54+
continue;
55+
}
56+
let comparison = filters[key].hasOwnProperty('comparison') ? filters[key].comparison : '=';
57+
let values = Array.isArray(filters[key].value) ? filters[key].value : [filters[key].value];
58+
let cleanValues = [];
59+
for(let i in values) {
60+
if('object' === (typeof values[i])) {
61+
if(values[i].hasOwnProperty('id')) {
62+
cleanValues.push(values[i].id);
63+
}
64+
} else {
65+
cleanValues.push(values[i]);
66+
}
67+
}
68+
if(cleanValues.length > 0) {
69+
params['filters[' + key + ']'] = key + ';' + comparison + ';' + cleanValues.join('|');
70+
}
71+
}
72+
}
73+
5074
return params;
5175
}

0 commit comments

Comments
 (0)