How to create/update a model with its related items using Listbox or CheckboxList

  1. Scenario
  2. Models
  3. Controller Actions and Views

Scenario ¶

Assume we have many categories and many posts.

A post can belongs to multiple categories, and a category can have many posts. We define posts' categories using a junction table.

Now, we want to develop a page to create/update a post with a list box (or a check box list) that enables the user to define the categories of the post.

Models ¶

Here are 4 models involved in the page we want to develop.

  1. Post extends ActiveRecord (representing post table)
    • id
    • title
    • body
    • ... etc.
  2. Category extends ActiveRecord (representing category table)
    • id
    • name
    • ... etc.
  3. PostCategory extends ActiveRecord (representing post_category junction table)
    • post_id
    • category_id
  4. PostWithCategories extends Post
    • (all the inherited attributes of Post)
    • category_ids

As for the first 3 models, you can create them easily with the help of Gii.

PostWithCategories model ¶

And the last one is extended from Post. It has an additional attribute called 'category_ids' that will be used to handle the categories of the post. Note that 'category_ids' attribute is an array of category ids.

class PostWithCategories extends Post
{
    /**
     * @var array IDs of the categories
     */
    $category_ids = [];
    
    /**
     * @return array the validation rules.
     */
    public function rules()
    {
        return ArrayHelper::merge(parent::rules(), [
            // each category_id must exist in category table (*1)
            ['category_ids', 'each', 'rule' => [
                    'exist', 'targetClass' => Category::className(), 'targetAttribute' => 'id'
                ]
            ],
        ]);
    }

    /**
     * @return array customized attribute labels
     */
    public function attributeLabels()
    {
        return ArrayHelper::merge(parent::attributeLabels(), [
            'category_ids' => 'Categories',
        ]);
    }

    /**
     * load the post's categories (*2)
     */
    public function loadCategories()
    {
        $this->category_ids = [];
        if (!empty($this->id)) {
            $rows = PostCategory::find()
                ->select(['category_id'])
                ->where(['post_id' => $this->id])
                ->asArray()
                ->all();
            foreach($rows as $row) {
               $this->category_ids[] = $row['category_id'];
            }
        }
    }

    /**
     * save the post's categories (*3)
     */
    public function saveCategories()
    {
        /* clear the categories of the post before saving */
        PostCategory::deleteAll(['post_id' => $this->id]);
        if (is_array($this->category_ids)) {
            foreach($this->category_ids as $category_id) {
                $pc = new PostCategory();
                $pc->post_id = $this->id;
                $pc->category_id = $category_id;
                $pc->save();
            }
        }
        /* Be careful, $this->category_ids can be empty */
    }
}

(*1) In the rules for the validation, we use EachValidator to validate the array of category_ids attribute.

(*2) loadCategories method loads the IDs of the post's categories into this model instance.

(*3) saveCategories method saves the post's categories specified in category_ids attribute.

Category model ¶

We want to add a small utility method to Category model.

class Category extends ActiveRecord
{
    ...

    /**
     * Get all the available categories (*4)
     * @return array available categories
     */
    public static function getAvailableCategories()
    {
        $categories = self::find()->order('name')->asArray()->all();
        $items = ArrayHelper::map($categories, 'id', 'name');
        return $items;
    }
}

(*4) getAvailableCategories method is a static utility function to get the list of available categories. In the returned array, the keys are 'id' and the values are 'name' of the categories.

Controller Actions and Views ¶

Since we already have all the necessary logic in models, the controller actions can be as simple as the following examples.

Create action ¶
/**
 * Create Post with its categories
 */
public function actionCreate()
{
    $model = new PostWithCategories();
    
    if ($model->load(Yii::$app->request->post()) {
        if ($model->save()) {
            $model->saveCategories();
            return $this->redirect(['index']);
        }
    }

    return $this->render('create', [
        'model' => $model,
        'categories' => Category::getAvailableCategories(),
    ]);
}
create.php view script ¶

In the view script, we can use a listBox with multiple selection or a checkboxList to select the categories.

<?php $form = ActiveForm::begin([
    'id' => 'post-form',
    'enableAjaxValidation' => false,
]); ?>

<?= $form->field($model, 'title')->textInput(); ?>

<?= $form->field($model, 'body')->textArea(); ?>

...

<?= $form->field($model, 'category_ids')
    ->listBox($categories, ['multiple' => true])
    /* or, you may use a checkbox list instead */
    /* ->checkboxList($categories) */
    ->hint('Select the categories of the post.');
?>

<div class="form-group">
    <?= Html::submitButton('Create', [
        'class' => 'btn btn-primary'
    ]) ?>
</div>

<?php ActiveForm::end(); ?>
Update action ¶

It's almost the same with Create action:

/**
 * Update a post with its categories
 * @param integer $id the post's ID
 */
public function actionUpdate($id)
{
    $model = PostWithCategories::findOne($id);
    $model->loadCategories();
    
    if ($model->load(Yii::$app->request->post()) {
        if ($model->save()) {
            $model->saveCategories();
            return $this->redirect(['index']);
        }
    }

    return $this->render('update', [
        'model' => $model,
        'categories' => Category::getAvailableCategories(),
    ]);
}
update.php view script ¶

It's different from create.php only in the text of the submit button:

...
    <?= Html::submitButton('Update', [
        'class' => 'btn btn-primary'
    ]) ?>
...
5 0
6 followers
Viewed: 91 319 times
Version: 2.0
Category: Tutorials
Written by: softark softark
Last updated by: softark softark
Created on: Jan 27, 2016
Last updated: 7 years ago
Update Article

Revisions

View all history