Zlatan Omerovic
Zlatan Omerovic

Reputation: 4097

How to use ActiveForm instance across Ajax requests in Yii 2?

For example we have this ActiveForm implementation in a sample view:

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

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

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

    <div id="additional-form-fields"></div>

    <a href="#" id="load-additional-form-fields">
        Load more fields
    </a>

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

Now, I want to add more ActiveField / ActiveForm fields inside this form and place them in the #additional-form-fields element with Ajax, I'd do a simple jQuery callback:

$('#load-additional-form-fields').click(function() {
    $.get('/site/additional-fields', {}, function(data) {
        $('#additional-form-fields').html( data );
    });
});

And the action additional-fields inside SiteController would be something as:

public function actionAdditionalFields() {
    $model = new User;

    return $this->renderAjax('additional-fields', [
        'model' => $model,
        // I could pass a 'form' => new ActiveForm, here, but it's a big NO-NO!
    ]);
}

And this works perfectly, only if I don't use any other ActiveField fields inside this action's view:

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

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

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

Of course, I have to pass or instatiate $form somehow in this view, but it's NOT an option to use another ActiveForm::begin() / ActiveForm::end() anywhere inside this view since it will create another <form> tag and thus when I inject the Ajax response, I'll end up with with a <form> inside a <form> ...

Now, my question is as follows: Since I want to use ActiveForm, how can I share an instance of the ActiveForm through out multiple requests?

Is it doable / possible, if so, please help me realize how?

So far I have tried to put $form inside a session, but that's definitelly not working and not an option. Different than that, I've tried when passing parameters to renderAjax:

[
    'model' => $model,
    'form' => new ActiveForm,
]

In this case I get the following:

  1. Form fields are created as they should with appopriate names and id's.
  2. jQuery is loaded again (at the bottom of the response: <script src="..."> ... you get the idea)
  3. I don't get the generated JavaScript for validation.

Is there anyway to share an instance of $form?

Upvotes: 2

Views: 796

Answers (1)

Zlatan Omerovic
Zlatan Omerovic

Reputation: 4097

Okay, I have manage to do this, so I'll post the solution here and I'll open an issue on Github - might be useful in future versions.

1. Updates in yii2\widgets\ActiveForm.php

I've added a following property to the ActiveForm class:

/**
 * @var boolean whether to echo the form tag or not
 */
public $withFormTag = true;

And I've changed run() method into this (check for // <-- added):

public function run()
{
    if (!empty($this->_fields)) {
        throw new InvalidCallException('Each beginField() should have a matching endField() call.');
    }

    $content = ob_get_clean();

    if($this->withFormTag) { // <-- added
        echo Html::beginForm($this->action, $this->method, $this->options);
    } // <-- added

    echo $content;

    if ($this->enableClientScript) {
        $id = $this->options['id'];
        $options = Json::htmlEncode($this->getClientOptions());
        $attributes = Json::htmlEncode($this->attributes);
        $view = $this->getView();
        ActiveFormAsset::register($view);
        $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
    }

    if($this->withFormTag) { // <-- added
        echo Html::endForm();
    } // <-- added
}

Thus if we instantiate a form like this:

$form = ActiveForm::begin([
    'withFormTag' => false,
]);

It will not echo a <form> tag, but will render all ActiveField items and it will create their respective JavaScript/jQuery validators if $this->enableClientScript = true;.

2. Updates in my local view/file

After applying the previous fix in the base class, I needed to do the following in my view:

<?php $form = ActiveForm::begin([
    'withFormTag' => false,
    'id' => 'w0',
]); ?>

I had to pass the id parameter since every next instance of the ActiveForm class is incremented by 1, and I want my JavaScript/jQuery validators to be applied to the parent form, which by default starts from 0 -> w0.

And this is what did the trick!

Here's the Github issue as well: https://github.com/yiisoft/yii2/issues/12973

Upvotes: 1

Related Questions