19

Following on from this:

Yii2 how does search() in SearchModel work?

I would like to be able to filter a GridView column of relational data. This is what I mean:

I have two tables, TableA and TableB. Both have corresponding models generated using Gii. TableA has a foreign key to a value in TableB, like this:

TableA
attrA1, attrA2, attrA3, TableB.attrB1

TableB
attrB1, attrB2, attrB3

attrA1 and attrB1 are the primary keys of their corresponding tables.

Now, I have a Yii2 GridView of attrA2, attrA3 and attrB2. I have a working filter on attrA2 and attrA3 so that I can search on column values. I also have a working sort for these two columns too - by just clicking on the column header. I would like to be able to add this filtering and sorting on attrB2 too.

My TableASearch model looks like this:

public function search($params){
    $query = TableA::find();
    $dataProvider = new ActiveDataProvider([
        'query' => $query,
    ]);

    if (!($this->load($params) && $this->validate())) {
        return $dataProvider;
    }

    $this->addCondition($query, 'attrA2');
    $this->addCondition($query, 'attrA2', true);
    $this->addCondition($query, 'attrA3');
    $this->addCondition($query, 'attrA3', true);

    return $dataProvider;
}

In my TableA model, I set the related value like this

    public $relationalValue;

public function afterFind(){
    $b = TableB::find(['attrB1' => $this->attrB1]);
    $this->relationalValue = $b->relationalValue;
}

Although it is probably not the best way of doing this. I think I have to use $relationalValue somewhere in my search function but I'm not sure how. Similarly, I would like to be able to sort by this column too - just like I can for attrA2 and AttrA3 by clicking on the header link`. Any help would be appreciated. Thanks.

Community
  • 1
  • 1
Mr Goobri
  • 1,449
  • 5
  • 19
  • 42

3 Answers3

24

This is based on the description in the guide. The base code for the SearchModel comes from the Gii code generator. This is also assuming that $this->TableB has been setup using hasOne() or hasMany() relation. See this doc.

1. Setup search model

In TableASearch model add:

public function attributes()
{
    // add related fields to searchable attributes
    return array_merge(parent::attributes(), ['TableB.attrB1']);
}

public function rules() 
{
    return [
        /* your other rules */
        [['TableB.attrB1'], 'safe']
    ];
}

Then in TableASearch->search() add (before $this->load()):

$dataProvider->sort->attributes['TableB.attrB1'] = [
      'asc' => ['TableB.attrB1' => SORT_ASC],
      'desc' => ['TableB.attrB1' => SORT_DESC],
 ];

$query->joinWith(['TableB']); 

Then the actual search of your data (below $this->load()):

$query->andFilterWhere([
    'like',
    'TableB.attrB1',
     $this->getAttribute('TableB.attrB1')
]);

2. Configure GridView

Add to your view:

echo GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        /* Other columns */
       'TableB1.attrB1',
        /* Other columns */        
     ]
]);
Ahmed Ismail
  • 912
  • 11
  • 21
Paul van Schayck
  • 517
  • 7
  • 10
  • `$query->joinWith(['companyPosition']);` should be `$query->joinWith(['TableB']);` – pedak Nov 27 '14 at 15:04
  • Great answer, thank you but over the time Yii2 has evolved and I've reallocated the "solution" to the simpler of the answers. Cheers. – Mr Goobri Aug 01 '16 at 20:53
  • I added this to ignore character case: ```$query->andFilterWhere([ 'like', 'LOWER(TableB.attrB1)', strtolower($this->getAttribute('TableB.attrB1')) ]); ``` – Ahmed Ismail Jul 07 '19 at 20:25
5

Filtering a gridview by a column is damn easy in Yii 2.0. Please add the filter attribute to a gridview column having lookup values, as under:

[
        "class" => yii\grid\DataColumn::className(),
        "attribute" => "status_id",
        'filter' => ArrayHelper::map(Status::find()->orderBy('name')->asArray()->all(), 'id', 'name'),
        "value" => function($model){
            if ($rel = $model->getStatus()->one()) {
                return yii\helpers\Html::a($rel->name,["crud/status/view", 'id' => $rel->id,],["data-pjax"=>0]);
            } else {
                return '';
            }
        },
        "format" => "raw",
], 
Alireza Fallah
  • 4,609
  • 3
  • 31
  • 57
iltaf khalid
  • 9,928
  • 5
  • 30
  • 34
  • Only just seen this, a year on and 2 years since I asked it. This is a neat little answer and by far the easiest solution. Thank you. – Mr Goobri Aug 01 '16 at 20:52
  • its good and work for me but all result in DropDownList , how to change ajax textbox like more filter textbox??? – user3770797 Jul 24 '17 at 08:51
3

I'm stuck with this problem too, and my solution is rather different. I have two simple models:

Book:

class Book extends ActiveRecord
{
    ....

    public static function tableName()
    {
        return 'books';
    }

    public function getAuthor()
    {
        return $this->hasOne(Author::className(), ['id' => 'author_id']);
    }

And Author:

class Author extends ActiveRecord
{

    public static function tableName()
    {
        return 'authors';
    }

    public function getBooks()
    {
        return $this->hasMany(Book::className(), ['author_id' => 'id']);
    }

But my search logic is in different model. And i didn't find how can i implement search without creating additional field author_first_name. So this is my solution:

class BookSearch extends Model
{
    public $id;
    public $title;
    public $author_first_name;

    public function rules()
    {
        return [
            [['id', 'author_id'], 'integer'],
            [['title', 'author_first_name'], 'safe'],
        ];
    }

    public function search($params)
    {
        $query = Book::find()->joinWith(['author' => function($query) { $query->from(['author' => 'authors']);}]);
        $dataProvider = new ActiveDataProvider([
            'query' => $query,
            'pagination' => array('pageSize' => 50),
            'sort'=>[
                'attributes'=>[
                    'author_first_name'=>[
                        'asc' => ['author.first_name' => SORT_ASC],
                        'desc' => ['author.first_name' => SORT_DESC],
                    ]
                ]
            ]
        ]);

        if (!($this->load($params) && $this->validate())) {
            return $dataProvider;
        }
        ....
        $query->andWhere(['like', 'author.first_name', $this->author_first_name]);
        return $dataProvider;
    }
}

This is for creating table alias: function($query) { $query->from(['author' => 'authors']);}

And GridView code is:

<?php echo GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        [
            'attribute' => 'id',
            'filter' => false,
        ],
        [
            'attribute' => 'title',
        ],
        [
            'attribute' => 'author_first_name',
            'value' => function ($model) {
                    if ($model->author) {
                        $model->author->getFullName();
                    } else {
                        return '';
                    }
                },
            'filter' => true,
        ],
        ['class' => 'yii\grid\ActionColumn'],
    ],
]); ?>

I will appreciate any critiques and advice.

Alex
  • 8,055
  • 7
  • 39
  • 61