Mr Goobri
Mr Goobri

Reputation: 1469

Yii2: GridView for one-to-many relational data

I have an object, Object1, that references Object2 in a one-to-many relationship: one Object1 to many Object2.

In my Object1 view, I am trying to include a yii\grid\GridView of the related Object2 items only. Not in addition to the Object1 data. Just a GridView of Object2 items only.

Using code like that below, I believe I have to set both $dataProvider and $searchModel in my controller but I'm not sure how to link Yii::$app->request->queryParams with $id.

My code just returns all Object2 items, regardless of their relationship with Object1, which although makes perfect sense to me, is not what I'm looking for.

I am not even sure that this is the correct approach. Does anyone know of a solution? Thanks in advance.

/* Object1 model */
public function getRelations() {
    return $this->hasMany(Object2::className(), ['relation' => 'id']);
}

/* Object1 view */
<?= GridView::widget([
    'dataProvider' => $dataProvider,
    'filterModel' => $searchModel,
    'columns' => [
        ['class' => 'yii\grid\SerialColumn'],
        'id',
        'attr1',
        'attr2',
        'attr3',
        'attr4',
        ['class' => 'yii\grid\ActionColumn'],
    ],
]);

/* Object1 controller */
public function actionView($id){
    $searchModel = new Object2Search();
    $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

    return $this->render('view', [
        'model' => $this->findModel($id),
        'searchModel' => $searchModel,
        'dataProvider' => $dataProvider,
    ]);
}

Upvotes: 1

Views: 1559

Answers (1)

arogachev
arogachev

Reputation: 33548

Instead of rendering GridView in detail view I can suggest alternative approach.

Create a separate regular GridView widgets for both Object1 and Object2 items.

Extend ActionColumn of Object1 GridView with additional link to related items. I already explained it here, but I also include code here for better understanding:

[
    'class' => 'yii\grid\ActionColumn',
    'template' => '{objects2} {view} {update} {delete}',
    'buttons' => [
        'objects2' => function ($url, $model, $key) {
            /* @var $model common\models\Object1 */
            return Html::a(
                '<span class="glyphicon glyphicon-arrow-down"></span>',
                ['objects2/index', 'object1_id' => $model->id],
                [
                    'title' => 'Objects 2',
                    'aria-label' => 'Objects 2',
                    'data-pjax' => '0',
                ]
            );
        },        
    ],
],

Modify index action of Object2Controller to accept additional parameter (Object 1 id):

/**
 * @param integer $object1_id
 * @return string
 * @throws \yii\web\NotFoundHttpException
 */
public function actionIndex($object1_id)
{
    $searchModel = new Object2Search;
    $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

    return $this->render('index', [
        'searchModel' => $searchModel,
        'dataProvider' => $dataProvider,
    ]);
}

search() method of Object2Search should also handle additional parameter:

/**
 * @param array $params
 * @return ActiveDataProvider
 */
public function search($params)
{
    $query = Object2::find()->where(['object1_id' => $params['object1_id']]);

    $dataProvider = new ActiveDataProvider([
        'query' => $query,            
    ]);

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

    // Additional filters

    return $dataProvider;
}

Make sure to not include object1_id in rules() and additional filters.

As for display of Object2 items, simply exclude object1_id column and you are done.

Obviously this can be customized more, it's just a basic example.

The main advantage of this approach is code for models of different type is separated and easier to maintain.

As for using it in detail view, modify search() method of Object2Search model to apply initial filter by object1_id as I described above, but in this case pass id parameter from query params (which comes from view action):

$query = Object2::find()->where(['object1_id' => $params['id']]);

Use partials for GridView for better views organization.

Upvotes: 1

Related Questions