Jiang qinghua пре 6 година
родитељ
комит
9389dac573

+ 2 - 1
resources/lang/en/admin.php

@@ -43,7 +43,6 @@ return [
     'name'                  => 'Name',
     'username'              => 'Username',
     'user'                  => 'User',
-    'uri'                   => 'Uri',
     'alias'                 => 'Alias',
     'route_action'          => 'Route Action',
     'middleware'            => 'Middleware',
@@ -142,6 +141,8 @@ return [
     'new_folder'            => 'New folder',
     'time'                  => 'Time',
     'size'                  => 'Size',
+    'between_start'         => 'Start',
+    'between_end'           => 'End',
     'listbox'               => [
         'text_total'         => 'Showing all {0}',
         'text_empty'         => 'Empty list',

+ 2 - 0
resources/lang/zh-CN/admin.php

@@ -144,6 +144,8 @@ return [
     'new_folder'            => '新建文件夹',
     'time'                  => '时间',
     'size'                  => '大小',
+    'between_start'         => '起始',
+    'between_end'           => '结束',
     'create_extension'      => '创建扩展',
     'listbox'               => [
         'text_total'         => '总共 {0} 项',

+ 1 - 1
resources/views/grid/table.blade.php

@@ -37,7 +37,7 @@
         @endif
         <tr>
             @foreach($grid->getColumns() as $column)
-                <th {!! $column->formatTitleAttributes() !!}>{!! $column->getLabel() !!}{!! $column->sorter() !!}</th>
+                <th {!! $column->formatTitleAttributes() !!}>{!! $column->getLabel() !!}{!! $column->renderHeader() !!}</th>
             @endforeach
         </tr>
         </thead>

+ 9 - 0
src/Grid.php

@@ -629,6 +629,14 @@ HTML
         $this->columns->prepend($column, Column::SELECT_COLUMN_NAME);
     }
 
+    /**
+     * Apply column filter to grid query.
+     */
+    protected function applyColumnFilter()
+    {
+        $this->columns->each->bindFilterQuery($this->model());
+    }
+
     /**
      * Build the grid.
      *
@@ -641,6 +649,7 @@ HTML
         }
 
         $this->applyQuickSearch();
+        $this->applyColumnFilter();
 
         $collection = $this->processFilter(false);
 

+ 31 - 51
src/Grid/Column.php

@@ -4,7 +4,6 @@ namespace Dcat\Admin\Grid;
 
 use Closure;
 use Dcat\Admin\Grid;
-use Dcat\Admin\Grid\Concerns;
 use Dcat\Admin\Grid\Displayers\AbstractDisplayer;
 use Dcat\Admin\Traits\BuilderEvents;
 use Dcat\Admin\Traits\Definitions;
@@ -54,7 +53,8 @@ class Column
 {
     use BuilderEvents,
         Definitions,
-        Concerns\Displayers;
+        Grid\Column\HasHeader,
+        Grid\Column\Displayers;
 
     const SELECT_COLUMN_NAME = '__row_selector__';
 
@@ -128,13 +128,6 @@ class Column
      */
     protected $value;
 
-    /**
-     * Is column sortable.
-     *
-     * @var bool
-     */
-    protected $sortable = false;
-
     /**
      * Sort arguments.
      *
@@ -211,6 +204,14 @@ class Column
         $this->grid = $grid;
     }
 
+    /**
+     * @return Grid
+     */
+    public function grid()
+    {
+        return $this->grid;
+    }
+
     /**
      * Set original data for column.
      *
@@ -368,27 +369,38 @@ class Column
     }
 
     /**
-     * Set sort value.
-     *
-     * @param bool $sort
+     * Mark this column as sortable.
      *
-     * @return Column
+     * @param string $cast
+     * @return Column|string
      */
-    public function sort(bool $sort)
+    public function sortable($cast = null)
     {
-        $this->sortable = $sort;
+        return $this->addSorter($cast);
+    }
 
-        return $this;
+    /**
+     * Set help message for column.
+     *
+     * @param string $help
+     * @param null $style
+     * @return $this
+     */
+    public function help($help = '', $style = null)
+    {
+        return $this->addHelp($help, $style);
     }
 
     /**
-     * Mark this column as sortable.
+     * Set column filter.
+     *
+     * @param Grid\Column\Filter $builder
      *
      * @return $this
      */
-    public function sortable()
+    public function filter(Grid\Column\Filter $filter)
     {
-        return $this->sort(true);
+        return $this->addFilter($filter);
     }
 
     /**
@@ -543,38 +555,6 @@ class Column
         return $item;
     }
 
-
-    /**
-     * Create the column sorter.
-     *
-     * @return string
-     */
-    public function sorter()
-    {
-        if (!$this->sortable) {
-            return '';
-        }
-
-        $icon  = '';
-        $color = 'text-70';
-        $type  = 'desc';
-
-        if ($this->isSorted()) {
-            $type = $this->sort['type'] == 'desc' ? 'asc' : 'desc';
-            if ($this->sort['type']) {
-                $icon .= $this->sort['type'] == 'desc' ? '-by-attributes-alt' : '-by-attributes';
-
-                $color = 'text-80';
-            }
-        }
-
-        $url = request()->fullUrlWithQuery([
-            $this->grid->model()->getSortName() => ['column' => $this->name, 'type' => $type]
-        ]);
-
-        return " <a class=' glyphicon glyphicon-sort{$icon} $color' href='$url'></a>";
-    }
-
     /**
      * Determine if this column is currently sorted.
      *

+ 22 - 1
src/Grid/Concerns/Displayers.php → src/Grid/Column/Displayers.php

@@ -1,6 +1,6 @@
 <?php
 
-namespace Dcat\Admin\Grid\Concerns;
+namespace Dcat\Admin\Grid\Column;
 
 use Dcat\Admin\Grid\Column;
 use Dcat\Admin\Grid\Displayers\AbstractDisplayer;
@@ -151,4 +151,25 @@ trait Displayers
             return $v ? explode($d, $v) : [];
         });
     }
+
+    /**
+     * Display the fields in the email format as gavatar.
+     *
+     * @param int $size
+     *
+     * @return $this
+     */
+    public function gravatar($size = 30)
+    {
+        return $this->display(function ($value) use ($size) {
+            $src = sprintf(
+                'https://www.gravatar.com/avatar/%s?s=%d',
+                md5(strtolower($value)),
+                $size
+            );
+
+            return "<img src='$src' class='img img-circle'/>";
+        });
+    }
+
 }

+ 131 - 0
src/Grid/Column/Filter.php

@@ -0,0 +1,131 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column;
+
+use Dcat\Admin\Grid\Column;
+use Dcat\Admin\Grid\Model;
+use Dcat\Admin\Support\Helper;
+use Illuminate\Contracts\Support\Renderable;
+use Illuminate\Support\Arr;
+
+abstract class Filter implements Renderable
+{
+    /**
+     * @var string|array
+     */
+    protected $class;
+
+    /**
+     * @var Column
+     */
+    protected $parent;
+
+    /**
+     * @param Column $column
+     */
+    public function setParent(Column $column)
+    {
+        $this->parent = $column;
+    }
+
+    /**
+     * Get column name.
+     *
+     * @return string
+     */
+    public function getColumnName()
+    {
+        return $this->parent->getName();
+    }
+
+    /**
+     * @return string
+     */
+    public function getFormName()
+    {
+        return $this->parent->grid()->getName().
+            '_filter_'.
+            $this->getColumnName();
+    }
+
+    /**
+     * Get filter value of this column.
+     *
+     * @param string $default
+     *
+     * @return array|\Illuminate\Http\Request|string
+     */
+    public function getFilterValue($default = '')
+    {
+        return request($this->getFormName(), $default);
+    }
+
+    /**
+     * Get form action url.
+     *
+     * @return string
+     */
+    public function getFormAction()
+    {
+        $request = request();
+
+        $query = $request->query();
+        Arr::forget($query, [$this->getColumnName(), '_pjax']);
+
+        $question = $request->getBaseUrl().$request->getPathInfo() == '/' ? '/?' : '?';
+
+        return count($request->query()) > 0
+            ? $request->url().$question.http_build_query($query)
+            : $request->fullUrl();
+    }
+
+    /**
+     * @return string
+     */
+    protected function urlWithoutFilter()
+    {
+        $query = app('request')->all();
+        unset($query[$this->getFormName()]);
+
+        return Helper::urlWithQuery(url()->current(), $query);
+    }
+
+    /**
+     * @param string $key
+     *
+     * @return array|null|string
+     */
+    protected function trans($key)
+    {
+        return __("admin.{$key}");
+    }
+
+    /**
+     * Add a query binding.
+     *
+     * @param mixed $value
+     * @param Model $model
+     */
+    public function addBinding($value, Model $model)
+    {
+        //
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function render()
+    {
+        //
+    }
+
+    /**
+     * @param array ...$params
+     * @return static
+     */
+    public static function create(...$params)
+    {
+        return new static(...$params);
+    }
+
+}

+ 167 - 0
src/Grid/Column/Filter/Between.php

@@ -0,0 +1,167 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column\Filter;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Grid\Model;
+use Dcat\Admin\Grid\Column\Filter;
+
+class Between extends Filter
+{
+    protected $dateFormat = null;
+
+    public function __construct()
+    {
+        $this->class = [
+            'start' => uniqid('column-filter-start-'),
+            'end'   => uniqid('column-filter-end-'),
+        ];
+    }
+
+    /**
+     * Date filter.
+     *
+     * @return $this
+     */
+    public function date()
+    {
+        return $this->setDateFormat('YYYY-MM-DD');
+    }
+
+    /**
+     * Time filter.
+     *
+     * @return $this
+     */
+    public function time()
+    {
+        return $this->setDateFormat('HH:mm:ss');
+    }
+
+    /**
+     * Datetime filter.
+     *
+     * @return $this
+     */
+    public function datetime()
+    {
+        return $this->setDateFormat('YYYY-MM-DD HH:mm:ss');
+    }
+
+    /**
+     * @param $format
+     * @return $this
+     */
+    protected function setDateFormat($format)
+    {
+        $this->dateFormat = $format;
+
+        $this->collectAssets();
+
+        return $this;
+    }
+
+    /**
+     * Add a binding to the query.
+     *
+     * @param mixed $value
+     * @param Model $model
+     */
+    public function addBinding($value, Model $model)
+    {
+        $value = array_filter((array) $value);
+
+        if (empty($value)) {
+            return;
+        }
+
+        if (!isset($value['start'])) {
+            return $model->where($this->getColumnName(), '<', $value['end']);
+        }
+
+        if (!isset($value['end'])) {
+            return $model->where($this->getColumnName(), '>', $value['start']);
+        }
+
+        return $model->whereBetween($this->getColumnName(), array_values($value));
+    }
+
+    protected function addScript()
+    {
+        if (!$this->dateFormat) {
+            return;
+        }
+
+        $options = [
+            'locale'           => config('app.locale'),
+            'allowInputToggle' => true,
+            'format'           => $this->dateFormat,
+        ];
+
+        $options = json_encode($options);
+
+        Admin::script("$('.{$this->class['start']},.{$this->class['end']}').datetimepicker($options);");
+    }
+
+    /**
+     * Render this filter.
+     *
+     * @return string
+     */
+    public function render()
+    {
+        $script = <<<'JS'
+$('.dropdown-menu input').click(function(e) {
+    e.stopPropagation();
+});
+JS;
+
+        Admin::script($script);
+
+        $this->addScript();
+
+        $value = $this->getFilterValue(['start' => '', 'end' => '']);
+        $active = empty(array_filter($value)) ? '' : 'text-yellow';
+
+        return <<<EOT
+&nbsp;<span class="dropdown" style="position:absolute">
+<form action="{$this->getFormAction()}" pjax-container style="display: inline-block;">
+    <a href="javascript:void(0);" class="dropdown-toggle {$active}" data-toggle="dropdown">
+        <i class="fa fa-filter"></i>
+    </a>
+    <ul class="dropdown-menu" role="menu" style="padding: 10px;box-shadow: 0 2px 3px 0 rgba(0,0,0,.2);left: -70px;border-radius: 0;font-weight:normal;background:#fff">
+        <li>
+            <input type="text" 
+                class="form-control input-sm {$this->class['start']}" 
+                name="{$this->getFormName()}[start]" 
+                placeholder="{$this->trans('between_start')}" 
+                value="{$value['start']}" 
+                autocomplete="off" />
+        </li>
+        <li style="margin: 5px;"></li>
+        <li>
+            <input type="text" 
+                class="form-control input-sm {$this->class['start']}" 
+                name="{$this->getFormName()}[end]"  
+                placeholder="{$this->trans('between_end')}" 
+                value="{$value['end']}" 
+                autocomplete="off"/>
+        </li>
+        <li class="divider"></li>
+        <li class="">
+            <button class="btn btn-sm btn-primary column-filter-submit "><i class="fa fa-search"></i></button>
+            <span onclick="LA.reload('{$this->urlWithoutFilter()}')" class="btn btn-sm btn-default column-filter-all"><i class="fa fa-undo"></i></span>
+        </li>
+    </ul>
+    </form>
+</span>
+EOT;
+    }
+
+    protected function collectAssets()
+    {
+        Admin::collectComponentAssets('moment');
+        Admin::collectComponentAssets('bootstrap-datetimepicker');
+    }
+
+}

+ 93 - 0
src/Grid/Column/Filter/Checkbox.php

@@ -0,0 +1,93 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column\Filter;
+
+use Dcat\Admin\Admin;
+use Illuminate\Support\Str;
+
+trait Checkbox
+{
+    /**
+     * Add script to page.
+     *
+     * @return void
+     */
+    protected function addScript()
+    {
+        $script = <<<JS
+$('.{$this->class['all']}').on('change', function () {
+    if (this.checked) {
+        $('.{$this->class['item']}').prop('checked', true);
+    } else {
+        $('.{$this->class['item']}').prop('checked', false);
+    }
+    return false;
+});
+
+JS;
+
+        Admin::script($script);
+    }
+
+    protected function renderCheckbox()
+    {
+        $value = $this->getFilterValue([]);
+
+        $this->addScript();
+
+        $allCheck = (count($value) == count($this->options)) ? 'checked' : '';
+        $active = empty($value) ? '' : 'text-yellow';
+
+        $allId = 'filter-all-'.Str::random(5);
+
+        return <<<EOT
+&nbsp;<span class="dropdown" style="position:absolute;">
+<form action="{$this->getFormAction()}" pjax-container style="display: inline-block;">
+    <a href="javascript:void(0);" class="dropdown-toggle {$active}" data-toggle="dropdown">
+        <i class="fa fa-filter"></i>
+    </a>
+    <ul class="dropdown-menu" role="menu" style="padding: 10px;box-shadow: 0 2px 3px 0 rgba(0,0,0,.2);left: -70px;border-radius: 0;font-weight:normal;background:#fff">
+        
+        <li>
+            <ul style='padding: 0;'>
+            <li style="margin: 0;padding-left:5px">
+                <div class="checkbox checkbox-primary checkbox-inline ">
+                    <input class="{$this->class['all']}" id="{$allId}" type="checkbox" {$allCheck} />
+                    <label for="{$allId}">&nbsp;{$this->trans('all')}</label>
+                </div>
+            </li>
+                <li class="divider"></li>
+                {$this->renderOptions($value)}
+            </ul>
+        </li>
+        <li class="divider"></li>
+       <li class="">
+            <button class="btn btn-sm btn-primary column-filter-submit "><i class="fa fa-search"></i></button>
+            <span onclick="LA.reload('{$this->urlWithoutFilter()}')" class="btn btn-sm btn-default column-filter-all"><i class="fa fa-undo"></i></span>
+        </li>
+    </ul>
+</form>
+</span>
+EOT;
+    }
+
+    protected function renderOptions($value)
+    {
+        return collect($this->options)->map(function ($label, $key) use ($value) {
+            $checked = in_array($key, $value) ? 'checked' : '';
+
+            $id = 'filter-ckb-'.Str::random(5);
+
+            return <<<HTML
+<li style="margin: 0;padding-left:5px">
+    <div class="checkbox checkbox-primary checkbox-inline ">
+        <input id="$id" type="checkbox" class="{$this->class['item']}" name="{$this->getFormName()}[]" value="{$key}" {$checked}/>
+        <label for="$id">&nbsp;{$label}</label>
+    </div>
+</li>
+HTML;
+        })->implode("\r\n");
+
+    }
+
+}

+ 120 - 0
src/Grid/Column/Filter/Equal.php

@@ -0,0 +1,120 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column\Filter;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Grid\Model;
+use Dcat\Admin\Grid\Column\Filter;
+
+class Equal extends Filter
+{
+    use Input;
+
+    /**
+     * @var string
+     */
+    protected $dateFormat;
+
+    /**
+     * InputFilter constructor.
+     *
+     * @param string $type
+     */
+    public function __construct(?string $placeholder = null)
+    {
+        $this->placeholder($placeholder ?: $this->trans('search'));
+
+        $this->class = uniqid('column-filter-');
+    }
+
+    /**
+     * Date filter.
+     *
+     * @return $this
+     */
+    public function date()
+    {
+        return $this->setDateFormat('YYYY-MM-DD');
+    }
+
+    /**
+     * Time filter.
+     *
+     * @return $this
+     */
+    public function time()
+    {
+        return $this->setDateFormat('HH:mm:ss');
+    }
+
+    /**
+     * Datetime filter.
+     *
+     * @return $this
+     */
+    public function datetime()
+    {
+        return $this->setDateFormat('YYYY-MM-DD HH:mm:ss');
+    }
+
+    /**
+     * @param $format
+     * @return $this
+     */
+    protected function setDateFormat($format)
+    {
+        $this->dateFormat = $format;
+
+        $this->collectAssets();
+        $this->addDateScript();
+
+        return $this;
+    }
+
+    protected function addDateScript()
+    {
+        $options = [
+            'locale'           => config('app.locale'),
+            'allowInputToggle' => true,
+            'format'           => $this->dateFormat,
+        ];
+
+        $options = json_encode($options);
+
+        Admin::script("$('.{$this->class}').datetimepicker($options);");
+    }
+
+    /**
+     * Add a binding to the query.
+     *
+     * @param string     $value
+     * @param Model|null $model
+     */
+    public function addBinding($value, Model $model)
+    {
+        $value = trim($value);
+        if (empty($value)) {
+            return;
+        }
+
+        $model->where($this->getColumnName(), $value);
+    }
+
+    /**
+     * Render this filter.
+     *
+     * @return string
+     */
+    public function render()
+    {
+        return $this->renderInput();
+    }
+
+    protected function collectAssets()
+    {
+        Admin::collectComponentAssets('moment');
+        Admin::collectComponentAssets('bootstrap-datetimepicker');
+    }
+
+
+}

+ 56 - 0
src/Grid/Column/Filter/In.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column\Filter;
+
+use Dcat\Admin\Grid\Model;
+use Dcat\Admin\Grid\Column\Filter;
+
+class In extends Filter
+{
+    use Checkbox;
+
+    /**
+     * @var array
+     */
+    protected $options = [];
+
+    /**
+     * CheckFilter constructor.
+     *
+     * @param array $options
+     */
+    public function __construct(array $options)
+    {
+        $this->options = $options;
+
+        $this->class = [
+            'all'  => uniqid('column-filter-all-'),
+            'item' => uniqid('column-filter-item-'),
+        ];
+    }
+
+    /**
+     * Add a binding to the query.
+     *
+     * @param array $value
+     * @param Model $model
+     */
+    public function addBinding($value, Model $model)
+    {
+        if (empty($value)) {
+            return;
+        }
+
+        $model->whereIn($this->getColumnName(), $value);
+    }
+
+    /**
+     * Render this filter.
+     *
+     * @return string
+     */
+    public function render()
+    {
+        return $this->renderCheckbox();
+    }
+}

+ 65 - 0
src/Grid/Column/Filter/Input.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column\Filter;
+
+use Dcat\Admin\Admin;
+
+trait Input
+{
+    protected $placeholder = null;
+
+    /**
+     * Add script.
+     *
+     * @return void
+     */
+    protected function addScript()
+    {
+        $script = <<<'JS'
+$('.dropdown-menu input').click(function(e) {
+    e.stopPropagation();
+});
+JS;
+        Admin::script($script);
+    }
+
+    /**
+     * Set input placeholder.
+     *
+     * @param null|string $placeholder
+     * @return $this
+     */
+    public function placeholder(?string $placeholder)
+    {
+        $this->placeholder = $placeholder;
+
+        return $this;
+    }
+
+    protected function renderInput()
+    {
+        $this->addScript();
+
+        $active = empty($value) ? '' : 'text-yellow';
+
+        return <<<EOT
+&nbsp;<span class="dropdown" style="position: absolute">
+    <form action="{$this->getFormAction()}" pjax-container style="display: inline-block;">
+    <a href="javascript:void(0);" class="dropdown-toggle {$active}" data-toggle="dropdown">
+        <i class="fa fa-filter"></i>
+    </a>
+    <ul class="dropdown-menu" role="menu" style="padding: 10px;box-shadow: 0 2px 3px 0 rgba(0,0,0,.2);left: -70px;border-radius: 0;font-weight:normal;background:#fff;">
+        <li>
+            <input placeholder="{$this->placeholder}" type="text" name="{$this->getFormName()}" value="{$this->getFilterValue()}" class="form-control input-sm {$this->class}" autocomplete="off"/>
+        </li>
+        <li class="divider"></li>
+        <li class="">
+            <button class="btn btn-sm btn-primary column-filter-submit "><i class="fa fa-search"></i></button>
+            <span onclick="LA.reload('{$this->urlWithoutFilter()}')" class="btn btn-sm btn-default column-filter-all"><i class="fa fa-undo"></i></span>
+        </li>
+    </ul>
+    </form>
+</span>
+EOT;
+    }
+}

+ 25 - 0
src/Grid/Column/Filter/Like.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column\Filter;
+
+use Dcat\Admin\Grid\Model;
+
+class Like extends Equal
+{
+    /**
+     * Add a binding to the query.
+     *
+     * @param string     $value
+     * @param Model|null $model
+     */
+    public function addBinding($value, Model $model)
+    {
+        $value = trim($value);
+        if (empty($value)) {
+            return;
+        }
+
+        $model->where($this->getColumnName(), 'like', "%{$value}%");
+    }
+
+}

+ 25 - 0
src/Grid/Column/Filter/StartWith.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column\Filter;
+
+use Dcat\Admin\Grid\Model;
+
+class StartWith extends Equal
+{
+    /**
+     * Add a binding to the query.
+     *
+     * @param string     $value
+     * @param Model|null $model
+     */
+    public function addBinding($value, Model $model)
+    {
+        $value = trim($value);
+        if (empty($value)) {
+            return;
+        }
+
+        $model->where($this->getColumnName(), 'like', "{$value}%");
+    }
+
+}

+ 115 - 0
src/Grid/Column/HasHeader.php

@@ -0,0 +1,115 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column;
+
+use Dcat\Admin\Grid;
+use Dcat\Admin\Grid\Column;
+use Dcat\Admin\Grid\Model;
+use Illuminate\Contracts\Support\Htmlable;
+use Illuminate\Contracts\Support\Renderable;
+
+/**
+ * @property Grid $grid
+ */
+trait HasHeader
+{
+    /**
+     * @var Filter
+     */
+    public $filter;
+
+    /**
+     * @var array
+     */
+    protected $headers = [];
+
+    /**
+     * Add contents to column header.
+     *
+     * @param string|Renderable|Htmlable $header
+     *
+     * @return $this
+     */
+    public function addHeader($header)
+    {
+        if ($header instanceof Filter) {
+            $header->setParent($this);
+            $this->filter = $header;
+        }
+
+        $this->headers[] = $header;
+
+        return $this;
+    }
+
+    /**
+     * Add a column sortable to column header.
+     *
+     * @param string $cast
+     *
+     * @return Column|string
+     */
+    protected function addSorter($cast = null)
+    {
+        $sortName = $this->grid->model()->getSortName();
+
+        $sorter = new Sorter($sortName, $this->getName(), $cast);
+
+        return $this->addHeader($sorter);
+    }
+
+    /**
+     * Add a help tooltip to column header.
+     *
+     * @param $message
+     * @param null $style
+     * @return $this
+     */
+    protected function addHelp($message, $style = null)
+    {
+        return $this->addHeader(new Help($message, $style));
+    }
+
+    /**
+     * Add a filter to column header.
+     *
+     * @param \Closure $builder
+     * @return $this
+     */
+    protected function addFilter(Filter $filter)
+    {
+        return $this->addHeader($filter);
+    }
+
+    /**
+     * Add a binding based on filter to the model query.
+     *
+     * @param Model $model
+     */
+    public function bindFilterQuery(Model $model)
+    {
+        if ($this->filter) {
+            $this->filter->addBinding($this->filter->getFilterValue(), $model);
+        }
+    }
+
+    /**
+     * Render Column header.
+     *
+     * @return string
+     */
+    public function renderHeader()
+    {
+        return collect($this->headers)->map(function ($item) {
+            if ($item instanceof Renderable) {
+                return $item->render();
+            }
+
+            if ($item instanceof Htmlable) {
+                return $item->toHtml();
+            }
+
+            return (string) $item;
+        })->implode('');
+    }
+}

+ 55 - 0
src/Grid/Column/Help.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column;
+
+use Dcat\Admin\Widgets\Tooltip;
+use Illuminate\Contracts\Support\Renderable;
+
+class Help implements Renderable
+{
+    /**
+     * @var string
+     */
+    protected $message = '';
+
+    /**
+     * @var string
+     */
+    protected $style;
+
+    /**
+     * Help constructor.
+     *
+     * @param string $message
+     */
+    public function __construct($message = '', $style = null)
+    {
+        $this->message = $message;
+        $this->style = null;
+    }
+
+    /**
+     * Render help  header.
+     *
+     * @return string
+     */
+    public function render()
+    {
+        $tooltip = Tooltip::make('.grid-column-help');
+
+        if ($this->style && method_exists($tooltip, $this->style)) {
+            $tooltip->{$this->style};
+        }
+
+        $tooltip->content($this->message)
+            ->top()
+            ->render();
+
+        return <<<HELP
+<a href="javascript:void(0);" class="grid-column-help" >
+    <i class="fa fa-question-circle"></i>
+</a>
+HELP;
+    }
+
+}

+ 93 - 0
src/Grid/Column/Sorter.php

@@ -0,0 +1,93 @@
+<?php
+
+namespace Dcat\Admin\Grid\Column;
+
+use Illuminate\Contracts\Support\Renderable;
+
+class Sorter implements Renderable
+{
+    /**
+     * Sort arguments.
+     *
+     * @var array
+     */
+    protected $sort;
+
+    /**
+     * Cast Name.
+     *
+     * @var array
+     */
+    protected $cast;
+
+    /**
+     * @var string
+     */
+    protected $sortName;
+
+    /**
+     * @var string
+     */
+    protected $columnName;
+
+    /**
+     * Sorter constructor.
+     *
+     * @param string $sortName
+     * @param string $columnName
+     * @param string $cast
+     */
+    public function __construct($sortName, $columnName, $cast)
+    {
+        $this->sortName = $sortName;
+        $this->columnName = $columnName;
+        $this->cast = $cast;
+    }
+
+    /**
+     * Determine if this column is currently sorted.
+     *
+     * @return bool
+     */
+    protected function isSorted()
+    {
+        $this->sort = app('request')->get($this->sortName);
+
+        if (empty($this->sort)) {
+            return false;
+        }
+
+        return isset($this->sort['column']) && $this->sort['column'] == $this->columnName;
+    }
+
+    /**
+     * @return string
+     */
+    public function render()
+    {
+        $icon  = '';
+        $color = 'text-70';
+        $type  = 'desc';
+
+        if ($this->isSorted()) {
+            $type = $this->sort['type'] == 'desc' ? 'asc' : 'desc';
+            if ($this->sort['type']) {
+                $icon .= $this->sort['type'] == 'desc' ? '-by-attributes-alt' : '-by-attributes';
+
+                $color = 'text-80';
+            }
+        }
+
+        $sort = ['column' => $this->columnName, 'type' => $type];
+
+        if ($this->cast) {
+            $sort['cast'] = $this->cast;
+        }
+
+        $url = request()->fullUrlWithQuery([
+            $this->sortName => $sort
+        ]);
+
+        return " <a class=' glyphicon glyphicon-sort{$icon} $color' href='$url'></a>";
+    }
+}