Make form in ActiveForm way

Hello!

I’m going to make form like this:

6614

form.png

And I have the following table schema (model relations):

6615

schema.png

How can I implement it in ActiveForm style?

I tried to make it but I couldn’t due to lack of docs.

Any suggestions, please?

I’m afraid there wouldn’t be any out-of-the-box solution for this. Perhaps somebody would have a code for a similar form to share with you. Making the form will be the easy part if you have a good model.

You may also look at some existing projects for the inspiration. For example: http://easyiicms.com

Or some time ago I’ve posted a solution for a different type of form using many-to-many data. It’s not exactly what you need but it may give you some ideas:

http://www.yiiframework.com/forum/index.php/topic/62030-whats-the-simple-straightforward-way-to-save-a-many-to-many-relation/page__view__findpost__p__275073

I understand that there’s no any out-of-the-box solutions. I just need some reference, some kind of starting point.

Most of available open source projects on Yii2 are trivial and it’s hard to learn how to build a real-world application.

Okay, I’ll try to dig into Easyii CMS. Thank you, Vojtech!

Since skworden addressed only how to handle the form I’ll post here how I would handle the dynamic attributes.

I would generate Survey, Indicator, Response and Answer model using Gii and then proceed with following modifications:

  1. Extend the Response model to handle the related answers:

<?php


namespace frontend\models;


use Yii;

use common\models\Response;

use common\models\Survey;

use common\models\Answer;

use yii\base\Model;


class ResponseForm extends Response

{


    /**

     * Overrides parent method. Populates the Response model and related Answer models.

     *

     * @inheritdoc

     */

    public function load($data, $formName = null)

    {

        if (!parent::load($data, $formName)) {

            return false;

        }


        return Model::loadMultiple($this->answers, $data, $formName);

    }


    /**

     * Overrides parent method. Saves the Response model and related Answer models.

     *

     * @inheritdoc

     */

    public function save($runValidation = true, $attributeNames = null)

    {

        $transaction = Yii::$app->db->beginTransaction();

        try {

            if (!parent::save($runValidation, $attributeNames) || !$this->saveAnswers()) {

                $transaction->rollBack();

                return false;

            }


            $transaction->commit();

        } catch (\Exception $e) {

            $transaction->rollBack();

            throw $e;

        }


        return true;

    }


    /**

     * Binds the Survey model and creates empty answers for a new response

     *

     * @param Survey $survey Related Survey model

     */

    public function bindSurvey($survey)

    {

        $this->survey_id = $survey->id;


        $answers = [];


        foreach ($survey->indicators as $indicator) {

            $answers[$indicator->id] = new Answer(['indicator_id' => $indicator->id]);

        }


        $this->setAnswers($answers);

    }


    /**

     * Overrides parent method. Indexes answers by ID.

     *

     * @inheritdoc

     */

    public function getAnswers()

    {

        return parent::getAnswers()->indexBy('indicator_id');

    }


    /**

     * Setter for Answers

     *

     * @param Answers[] $answers

     */

    protected function setAnswers($answers)

    {

        $this->answers = $answers;

    }


    /**

     * Saves all answers. Sets the response_id for new records.

     *

     * @return boolean whether the saving succeeds

     */

    protected function saveAnswers()

    {

        foreach ($this->answers as $answer) {

            if ($answer->isNewRecord) {

                $answer->response_id = $this->id;

            }


            if (!$answer->save()) {

                return false;

            }

        }


        return true;

    }

}

  1. Change the attributeLabels() method of the Answer model to get the label based on the indicator text:

/**

 * @inheritdoc

 */

public function attributeLabels()

{

	return [

		'response_id' => Yii::t('app', 'Response ID'),

		'indicator_id' => Yii::t('app', 'Indicator ID'),

		'value' => $this->indicator->text,

	];

}

  1. Adjust the Create action in controller:

 /**

 * Creates a new Response model.

 * If creation is successful, the browser will be redirected to the 'view' page.

 * @param integer $surveyID Related Survey ID

 * @return mixed

 */

public function actionCreate($surveyID)

{

	if (($survey = Survey::findOne($surveyID)) === null) {

		throw new NotFoundHttpException('The requested page does not exist.');

	}


	$model = new ResponseForm();


	$model->bindSurvey($survey);


	if ($model->load(Yii::$app->request->post()) && $model->save()) {

		return $this->redirect(['view', 'id' => $model->id]);

	} else {

		return $this->render('create', [

					'model' => $model,

		]);

	}

}

  1. Create the form (this is just a hint without taking care of input types etc.):

<div class="response-form">


    <?php $form = ActiveForm::begin(); ?>


    <?php foreach ($model->answers as $id => $answer): ?>

        <?= $form->field($answer, "[$id]value")->textInput() ?>

    <?php endforeach; ?>


    <?= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>


    <?= $form->field($model, 'email')->textInput(['maxlength' => true]) ?>


    <?= $form->field($model, 'comment')->textInput(['maxlength' => true]) ?>


    <div class="form-group">

        <?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>

    </div>


    <?php ActiveForm::end(); ?>


</div>

What a good answer, Vojtech! Thank you a lot!

I bet that someday you’ll be able to just submit your answers from this forum to a publishing house and make the best Yii book ever. :)

I’m not really sure how you mean it. There is a standard validation of loaded data performed by the save() method of the Response and Answer models and there is also a client side validation of the ActiveForm. But perhaps you mean something else.

I’m glad you found my answer useful. I wouldn’t dare to assume it would be worthy a book. There are surely other approaches and some could be even more elegant. Perhaps it could be used as a contribution to samdark’s Yii 2.0 Cookbook - https://github.com/samdark/yii2-cookbook. I’ll ask samdark about that.