Voyager: [Feature] filtration and exporting feature

Created on 21 Jun 2019  路  8Comments  路  Source: the-control-group/voyager

Version information

  • Laravel: v5.8.15
  • Voyager: v1.2.6
  • PHP: 7.3.1
  • Database: . MySQL 5.7.24

Description of feature

datatables provide a multiple filtration to the table which cannot be done in voyager.
filtration could re-draw tables as the user need.(like the images attached)
adding printing to tables and exporting as excel or pdf or word

Proposed solution

add support for multiple filtration and advanced search
add exporting feature to the tables

Alternatives considered

providing a clear way to do it manually

Additional context

maxresdefault (1)
maxresdefault

feature help wanted

Most helpful comment

BelongsTo ServerSide Search #3595
filtration #4216

Version information

  • Laravel: v7.28
  • Voyager: v1.4

@vyyytenis Thanks a lot for the well explained code! It was a great help so i made a few changes in my own project. It's an ugly code I KNOW but it Works with server side pagination without breaking non-server side pagination, & no changes needed in the browse blade. Hope it helps in some way..!
Steps :

  1. Use a custum controller like in Voyager-docs : Using custom Controllers

  2. In the new controller created copy index method from VoyagerBaseController

  3. In line 95 make these changes

if ($search->value != '' && $search->key && $search->filter) {
$search_filter = ($search->filter == 'equals') ? '=' : 'LIKE';
$search_value = ($search->filter == 'equals') ? $search->value : '%' . $search->value . '%';
// changes 
$searchKeyType = $dataRow->where('display_name', '=', $searchNames[$search->key])->pluck('type')->toArray();

if (in_array('relationship', $searchKeyType)) {
    $infoAboutColumn = $dataRow->where('type', '=', 'relationship')->where('display_name', '=', $searchNames[$search->key])->first();

    $relationshipTabledetails = $infoAboutColumn->details;

    $relationshipTableModel = $relationshipTabledetails->model;

    $relationshipTableLabel = $relationshipTabledetails->label;

    $relatedmodel = app($relationshipTableModel);

    $additional_attributes = $relatedmodel->additional_attributes ?? [];

    $value = $search->value;

    if (in_array($relationshipTableLabel, $additional_attributes)) {
        $idFromRelationTable = $relatedmodel->all();
        $idFromRelationTable = $idFromRelationTable->filter(function ($relatedmodel) use ($value, $relationshipTabledetails) {
            return stripos($relatedmodel->{$relationshipTabledetails->label}, $value) !== false;
        });
        $idFromRelationTable = $idFromRelationTable->pluck('id')->toArray();
    } else {
        $idFromRelationTable = $relationshipTableModel::where($relationshipTableLabel, 'LIKE', '%' . $search->value . '%')->pluck('id');
    }

    $columnNameInYourTable = $infoAboutColumn->details->column;

    $query->whereIn($columnNameInYourTable, $idFromRelationTable);
}
else{
    $query->where($search->key, $search_filter, $search_value);

 }
}
// end changes 

All 8 comments

PRs welcome

as for me iam not really good enough to pull this myself so if any one else would help that would be great

any update regarding this feature of filtration? @fletch3555

No update. Nobody has built it yet

It is a good thing that there are now thoughts in this direction, because it was an absolute no no when I brought it up in the slack channel

I hope this comes up soon. I wish to have some more time to do a PR for this.

However, maybe a palliative I created for myself may be useful to someone in the mean time: https://github.com/damms005/laravel-model-export

Is there any update on having associated data being searchable?
And should this be merged with #3595 ?

Hi, I made a quick fix for multiple field search, It does work, but it needs work. I'll put a bit more code that it would be easier to see where what goes.
_WORKS ONLY WITH SERVER SIDE PAGINATION, AND BRAKES NON SERVER SIDE PAGINATION_
Look for: ------->>>>>

class VoyagerBaseController extends BaseVoyagerBaseController
{
    public function index(Request $request)
    {
        // GET THE SLUG, ex. 'posts', 'pages', etc.
        $slug = $this->getSlug($request);

        // GET THE DataType based on the slug
        $dataType = Voyager::model('DataType')->where('slug', '=', $slug)->first();

        // Check permission
        $this->authorize('browse', app($dataType->model_name));

        $getter = $dataType->server_side ? 'paginate' : 'get';



//        $search = (object) ['value' => $request->get('s'), 'key' => $request->get('key'), 'filter' => $request->get('filter')];


        $searchNames = [];
        if ($dataType->server_side) {
            $searchable = SchemaManager::describeTable(app($dataType->model_name)->getTable())->pluck('name')->toArray();
            $dataRow = Voyager::model('DataRow')->whereDataTypeId($dataType->id)->get();
            foreach ($searchable as $key => $value) {
                $field = $dataRow->where('field', $value)->first();
                $displayName = ucwords(str_replace('_', ' ', $value));
                if ($field !== null) {
                    $displayName = $field->getTranslatedAttribute('display_name');
                }
                $searchNames[$value] = $displayName;
            }
        }

        $orderBy = $request->get('order_by', $dataType->order_column);
        $sortOrder = $request->get('sort_order', $dataType->order_direction);
        $usesSoftDeletes = false;
        $showSoftDeleted = false;

        // Next Get or Paginate the actual content from the MODEL that corresponds to the slug DataType
        if (strlen($dataType->model_name) != 0) {
            $model = app($dataType->model_name);

            if ($dataType->scope && $dataType->scope != '' && method_exists($model, 'scope'.ucfirst($dataType->scope))) {
                $query = $model->{$dataType->scope}();
            } else {
                $query = $model::select('*');
            }

            // Use withTrashed() if model uses SoftDeletes and if toggle is selected
            if ($model && in_array(SoftDeletes::class, class_uses_recursive($model)) && Auth::user()->can('delete', app($dataType->model_name))) {
                $usesSoftDeletes = true;

                if ($request->get('showSoftDeleted')) {
                    $showSoftDeleted = true;
                    $query = $query->withTrashed();
                }
            }

            // If a column has a relationship associated with it, we do not want to show that field
            $this->removeRelationshipField($dataType, 'browse');
//------->>>>>
//------->>>>>
            $search = $request->all();
            if (count($search) > 0) {
                foreach ($search as $key => $val) {
                    if (!strpos($key, 'relationship') && $val != null) $query->where($key, 'LIKE', '%' . $val . '%');
                }

//                $query->where($search->key, $search_filter, $search_value);

            }
//------->>>>>
//------->>>>>

image

This goes to bread browse.blade.php
Simple form with submit

<thead>
<tr>
    <form method="get" class="form-search">

        @if($showCheckboxColumn)
            <th>
                <input type="checkbox" class="select_all">
            </th>
        @endif
//------->>>>>
//------->>>>>
 @foreach($dataType->browseRows as $key => $row)
                                                <th>
                                                    @if ($isServerSide)
                                                        <a href="{{ $row->sortByUrl($orderBy, $sortOrder) }}">
                                                            @endif
                                                            {{ $row->getTranslatedAttribute('display_name') }}
                                                            @if ($isServerSide)
                                                                @if ($row->isCurrentSortField($orderBy))
                                                                    @if ($sortOrder == 'asc')
                                                                        <i class="voyager-angle-up pull-right"></i>
                                                                    @else
                                                                        <i class="voyager-angle-down pull-right"></i>
                                                                    @endif
                                                                @endif
                                                        </a>
                                                    @endif

                                                    @if ($isServerSide)
                                                        <input type="text"   name="{{$row->field}}" value="{{ !empty($search[$row->field ]) ? $search[$row->field ] : ''}}">
                                                    @endif
                                                </th>

                                            @endforeach
//------->>>>>
//------->>>>>


        <button class="btn btn-info btn-lg" type="submit">
            <i class="voyager-search"></i>
        </button>
    </form>
    <th class="actions text-right">{{ __('voyager::generic.actions') }}</th>
</tr>
</thead>

UPDATE pagination links

{{ $dataTypeContent->appends(
     array_merge(
         $search,
         [
             'order_by' => $orderBy,
             'sort_order' => $sortOrder
         ]
     )

)->links() }}

UPDATE I store the id of the relation, so it works for me. Right now I'm building a prototype of my app, so I didnt test it fully. I will test it on the go

 if (count($search) > 0) {
                foreach ($search as $key => $val) {

                    if (
                         $val != null
                        && $key != 'page'
                        && $key != 'sort_order'
                        && $key != 'order_by'
                        && strpos($key, 'relationship')
                    ) {
                        $infoAboutColumn = $dataRow->where('type', '=','relationship')->where('field', '=', $key)->first();
                        $relationshipTableModel = $infoAboutColumn->details->model; // Model to search in for the ralation
                        $relationshipTableLabel = $infoAboutColumn->details->label; // column name in the relation table
                        $itemFromRelation = $relationshipTableModel::where($relationshipTableLabel, 'LIKE', '%' . $val .'%')->first(); // item from relation table
                        $columnNameInYoureTable = $infoAboutColumn->details->column; // Name of the column, that you are ralationing with

                        $idFromRelationTable = $itemFromRelation->id;


                        $query->where($columnNameInYoureTable, 'LIKE', '%' . $idFromRelationTable . '%');
                    } else {
                        if (
                            !strpos($key, 'relationship')
                            && $val != null
                            && $key != 'page'
                            && $key != 'sort_order'
                            && $key != 'order_by'
                        ) {
                            $query->where($key, 'LIKE', '%' . $val . '%')->get();
                        }
                    }

                }
            }

BelongsTo ServerSide Search #3595
filtration #4216

Version information

  • Laravel: v7.28
  • Voyager: v1.4

@vyyytenis Thanks a lot for the well explained code! It was a great help so i made a few changes in my own project. It's an ugly code I KNOW but it Works with server side pagination without breaking non-server side pagination, & no changes needed in the browse blade. Hope it helps in some way..!
Steps :

  1. Use a custum controller like in Voyager-docs : Using custom Controllers

  2. In the new controller created copy index method from VoyagerBaseController

  3. In line 95 make these changes

if ($search->value != '' && $search->key && $search->filter) {
$search_filter = ($search->filter == 'equals') ? '=' : 'LIKE';
$search_value = ($search->filter == 'equals') ? $search->value : '%' . $search->value . '%';
// changes 
$searchKeyType = $dataRow->where('display_name', '=', $searchNames[$search->key])->pluck('type')->toArray();

if (in_array('relationship', $searchKeyType)) {
    $infoAboutColumn = $dataRow->where('type', '=', 'relationship')->where('display_name', '=', $searchNames[$search->key])->first();

    $relationshipTabledetails = $infoAboutColumn->details;

    $relationshipTableModel = $relationshipTabledetails->model;

    $relationshipTableLabel = $relationshipTabledetails->label;

    $relatedmodel = app($relationshipTableModel);

    $additional_attributes = $relatedmodel->additional_attributes ?? [];

    $value = $search->value;

    if (in_array($relationshipTableLabel, $additional_attributes)) {
        $idFromRelationTable = $relatedmodel->all();
        $idFromRelationTable = $idFromRelationTable->filter(function ($relatedmodel) use ($value, $relationshipTabledetails) {
            return stripos($relatedmodel->{$relationshipTabledetails->label}, $value) !== false;
        });
        $idFromRelationTable = $idFromRelationTable->pluck('id')->toArray();
    } else {
        $idFromRelationTable = $relationshipTableModel::where($relationshipTableLabel, 'LIKE', '%' . $search->value . '%')->pluck('id');
    }

    $columnNameInYourTable = $infoAboutColumn->details->column;

    $query->whereIn($columnNameInYourTable, $idFromRelationTable);
}
else{
    $query->where($search->key, $search_filter, $search_value);

 }
}
// end changes 
Was this page helpful?
0 / 5 - 0 ratings

Related issues

abacram picture abacram  路  3Comments

wp-src picture wp-src  路  3Comments

IvanBohonosiuk picture IvanBohonosiuk  路  4Comments

rayqiri picture rayqiri  路  3Comments

jeulun picture jeulun  路  3Comments