Yii 2.0: Render Form in popup via AJAX (Create and Update) with ajax validation & Also load any page via ajax Yii 2.0 (2.3)

24 followers

There are a few issues with the other solutions I originally used that I found from other wikis. I address the issues I had in this much simpler and shorter way. I am also going to explain what is going into way more detail than others to help people understand what's going on.

This will allow you to create, update, or load ANY page in your project using the same modal so you aren't creating a lot of modals in all of your pages; just in the layout main.php.

This will also let you open a modal and reload the modal with other content without being closed and reopened.

You could technically use this to load any div not just a modal. The only difference is the js load div classes and you could make a whole site load via ajax. I will show you at the end of this how to load anything via ajax.

You must have bootstrap and jQuery or the will not work.

How to use a modal with ajax (below is any item via ajax)

So we are going to do 5-6 steps depending on what route you choose.

  1. Include JS

  2. Add Modal to your main.php layout

  3. Update controller actions to use Ajax

  4. Update form to use Ajax

  5. Add buttons to views to load content.

First we will create the JS you can either add it to your main layout as script tags or add it to your App Assets

To add it to App Assets you will create a file in your web directory ie. frontend/web/js/ajax-modal-popup.js. If you want to include your script inline the skip two code blocks.

$(function(){
    //get the click of modal button to create / update item
    //we get the button by class not by ID because you can only have one id on a page and you can
    //have multiple classes therefore you can have multiple open modal buttons on a page all with or without
    //the same link.
//we use on so the dom element can be called again if they are nested, otherwise when we load the content once it kills the dom element and wont let you load anther modal on click without a page refresh
      $(document).on('click', '.showModalButton', function(){
         //check if the modal is open. if it's open just reload content not whole modal
        //also this allows you to nest buttons inside of modals to reload the content it is in
        //the if else are intentionally separated instead of put into a function to get the 
        //button since it is using a class not an #id so there are many of them and we need
        //to ensure we get the right button and content. 
        if ($('#modal').data('bs.modal').isShown) {
            $('#modal').find('#modalContent')
                    .load($(this).attr('value'));
            //dynamiclly set the header for the modal
            document.getElementById('modalHeader').innerHTML = '<h4>' + $(this).attr('title') + '</h4>';
        } else {
            //if modal isn't open; open it and load content
            $('#modal').modal('show')
                    .find('#modalContent')
                    .load($(this).attr('value'));
             //dynamiclly set the header for the modal
            document.getElementById('modalHeader').innerHTML = '<h4>' + $(this).attr('title') + '</h4>';
        }
    });
});

register the file in your appassets i.e. frontend/assets/AppAsset.php

class AppAsset extends AssetBundle {
 
    public $basePath = '@webroot';
    public $baseUrl = '@web';
    public $css = [
    ];
    public $js = [
        'js/ajax-modal-popup.js',
    ];
    public $depends = [
        'yii\web\YiiAsset',
    ];
 
}

To include the script without using app assets you can do the following however, it is not recommenced.

It is a little bit different and would be placed inline with your other js files at the end of your layout main.php

<script type="text/javascript">
    //get the click of modal button to create / update item
    //we get the button by class not by ID because you can only have one id on a page and you can
    //have multiple classes therefore you can have multiple open modal buttons on a page all with or without
    //the same link.
//we use on so the dom element can be called again if they are nested, otherwise when we load the content once it kills the dom element and wont let you load anther modal on click without a page refresh
      $(document).on('click', '.showModalButton', function(){
        //check if the modal is open. if it's open just reload content not whole modal
        //also this allows you to nest buttons inside of modals to reload the content it is in
        //the if else are intentionally separated instead of put into a function to get the 
        //button since it is using a class not an #id so there are many of them and we need
        //to ensure we get the right button and content. 
        if ($('#modal').data('bs.modal').isShown) {
            $('#modal').find('#modalContent')
                    .load($(this).attr('value'));
            //dynamiclly set the header for the modal via title tag
            document.getElementById('modalHeader').innerHTML = '<h4>' + $(this).attr('title') + '</h4>';
        } else {
            //if modal isn't open; open it and load content
            $('#modal').modal('show')
                    .find('#modalContent')
                    .load($(this).attr('value'));
             //dynamiclly set the header for the modal via title tag
            document.getElementById('modalHeader').innerHTML = '<h4>' + $(this).attr('title') + '</h4>';
        }
    });
</sript>

Add the bootstrap modal to your layouts main.php file. I put it at the very end after all of my scripts.

<?php
yii\bootstrap\Modal::begin([
    'headerOptions' => ['id' => 'modalHeader'],
    'id' => 'modal',
    'size' => 'modal-lg',
    //keeps from closing modal with esc key or by clicking out of the modal.
    // user must click cancel or X to close
    'clientOptions' => ['backdrop' => 'static', 'keyboard' => FALSE]
]);
echo "<div id='modalContent'></div>";
yii\bootstrap\Modal::end();
?>

You can also put a loading Gif in the content show until the content is loaded

<?php
yii\bootstrap\Modal::begin([
    'headerOptions' => ['id' => 'modalHeader'],
    'id' => 'modal',
    'size' => 'modal-lg',
    //keeps from closing modal with esc key or by clicking out of the modal.
    // user must click cancel or X to close
    'clientOptions' => ['backdrop' => 'static', 'keyboard' => FALSE]
]);
echo "<div id='modalContent'><div style="text-align:center"><img src="my/path/to/loader.gif"></div></div>";
yii\bootstrap\Modal::end();
?>

set up the each and every actions to you want to use ajax on to handel the ajax by using renderAjax() instead of render(). The only difference is that renderajax will only load the page content and render will load the page content and the layout (i.e. menus, sidebars, footers etc.).

If you want be able to view a page with either ajax or direct URL (going to yoursite.com/youraction in the url) you must put a check in the controller to use either ajax or standard render.

public function actionSomeAction($id) {
        $model = $this->findModel($id);
        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => (string) $model->_id]);
        }elseif (Yii::$app->request->isAjax) {
            return $this->renderAjax('_form', [
                        'model' => $model
            ]);
        } else {
            return $this->render('_form', [
                        'model' => $model
            ]);
        }
    }

The same action as above with just ajax and would never use direct access via url would be

public function actionSomeAction($id) {
        $model = $this->findModel($id);
        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => (string) $model->_id]);
        }else (Yii::$app->request->isAjax) {
            return $this->renderAjax('_form', [
                        'model' => $model
            ]);
        }
    }

Next you need to tell each and every form to still use inline validation. Things you must do here!

1. All forms MUST have an ID

if both of those are not set the form will NOT do client/ajax validation.

<?php
    $form = ActiveForm::begin([
                'options' => [
                    'id' => 'create-product-form'
                ]
    ]);
    ?>

Now just create your buttons to load into the modal!

Things you must have on the button is one class showModalButton because that is what we told our JS function in the begining of this wiki to look out for.

Title tag because we told the JS to use the title tag for the modal title. If you want an empty title use 'title'=>'' but don't leave it out our you will have a js error.

Don't use href because we told our JS to look for attribute 'value' and use it to load the content so you must use the 'value' attribute.

here are examples of both loading content with or without variables

use yii\helpers\Html;
use yii\helpers\Url;
 
    <?= Html::button('Create New Company', ['value' => Url::to(['companies/create']), 'title' => 'Creating New Company', 'class' => 'showModalButton btn btn-success']); ?>
 
//load existing content from db or pass varibles
 
 
    <?= Html::button('Update Company', ['value' => Url::to(['companies/update', 'id'=>1]), 'title' => 'Updating Company', 'class' => 'showModalButton btn btn-success']); ?>
 
//could even load a view in it
 
    <?= Html::button('Create New Company', ['value' => Url::to(['companies/view']), 'title' => 'Viewing Company', 'class' => 'showModalButton btn btn-success']); ?>

You can nest them too. with other solutions I found on here you couldn't. I.e.

If you have a create page in a modal you can also put another modal button in that modal and it will reload the modal.

Note: if you modal opens and it has no content that means you have an error on your page/form that you are tying to render in the modal. it will not show an error it will just be empty!!! Also, if its not working use firebug and inspect the console errors.

How to load any view via ajax

Add the js you can use the same way I listed above.

$(function(){
//load the current page with the conten indicated by 'value' attribute for a given button.
   $(document).on('click', '.loadMainContent', function(){
            $('#main-content').load($(this).attr('value'));
    });
});

in your layout main.js or whatever layout you use wrap your content in a div and give it the id of main-content (if you change this show change it in the js script too). This won't effect anything that's not being used via ajax.

<div class="content" id="main-content">;
<?= $content ?>
</div>

use the buttons just like i did above just change the class to loadMainContent

<?= Html::button('Create New Company', ['value' => Url::to(['companies/create']), 'title' => 'Creating New Company', 'class' => 'loadMainContent btn btn-success']); ?>

Some more examples of buttons are above.

That's it... If you are using a form you must set your controllers to validate via ajax and forms like i showed in the bootstrap modal portion.

Also, you can switch any button to "a" link and use "FALSE" as the url.

<?= Html::a('title', FALSE, ['value' => Url::to(['create/company', 'type' => 'company']), 'class' => 'loadMainContent']); ?>

You can also alter the js to use the url instead of value like

$('#main-content').load($(this).attr('href'));

but I'd recommend using pjax for page content. I'll write one of these using pjax instead of ajax. The main benefit is that pjax will keep intact back buttons / forward buttons/ allows for links to be properly booked marked etc.

if you use this please up vote or if you have questions/ comments/ improvements please post a comment! that's it and i hope you find this useful!

Total 18 comments

#20104 report it
Fábio Sales at 2017/07/06 06:47am
re: skworden

Hello skworden,

Thank you! You helped me a lot!

Best regards

#20102 report it
skworden at 2017/07/05 04:37pm
re: fabio

You would need to tell the action to return json so the submitting jQuery function can interpret the response from the server since it's coming from php and jQuery / javascript can't read PHP.

public function actionSomeAction($id) {
        $model = $this->findModel($id);
        if ($model->load(Yii::$app->request->post()) && $model->save()) {
             \Yii::$app->response->format = Response::FORMAT_JSON;
            return['id'=>$id, 'someOtherData'=>true];
        }else (Yii::$app->request->isAjax) {
            return $this->renderAjax('_form', [
                        'model' => $model
            ]);
        }
    }

You would also need to ajax submit the form which is beyond the scope of this wiki. Maybe I can add ajax submission to the new version. With that said I won't be answering a lot of questions here but here is an example of how you could ajax submit the form

var form =jQuery('#formID');
form.on('beforeSubmit', function(e) {
    e.preventDefault();
    jQuery.ajax({
        url: form.attr('action'),
        type: form.attr('method'),
        data: new FormData(form[0]),
        mimeType: 'multipart/form-data',
        contentType: false,
        cache: false,
        processData: false,
        dataType: 'json',
        success: function (data) {
            alert('ID: '+data.id + ' someOtherData:' + data.someOtherData);
        }
    });
     return false;
});

Hope that helps

#20101 report it
skworden at 2017/07/05 04:24pm
All

I haven't looked at this in awhile but there are a lot of different ways to do this and a lot of different things you can do with this. What type of new guide would you want?

#20100 report it
Fábio Sales at 2017/07/04 03:05pm
Submit a form

Hello,

How can i submit a form after the action from modal executes successfully (without redirecting to another action, like in this wiki)?

#20080 report it
paskuale at 2017/05/19 05:31am
dead or alive ?

But this thread is stopped? Does anyone know some evolution of this fantastic guide?

thanks

#20042 report it
paskuale at 2017/03/09 06:06am
widget

Can you achieve a widget with this code ? Js code in vendor/appAsset ? Thanks

#20008 report it
paskuale at 2017/01/05 12:44pm
open modal from other modal (stackable mode)

How to ? Any examples ? thanks

#20007 report it
paskuale at 2017/01/05 12:31pm
layout refresh

Why when I call modal first time ... I see a strange layout refresh in background ?

thanks

#20006 report it
paskuale at 2017/01/05 12:24pm
small upgrade in js code

thanks for this article ;)

$('#modal').find('#modalContent')

correct it with:

$('#modal').find('.modalContent')

because it was a class not a id !

#19992 report it
Vaso Zonjic at 2016/12/02 02:04pm
Closing modal dialog on custom button

I want to add custom button on modal form content. I want to close modal dialog on button click. How can i do that because i cant see X for close?

SOLVED: just put onclick event in button or <a tag ahd write $('#modal').modal('hide');

#19912 report it
Muhammad Shahzad at 2016/07/23 03:33am
Open modal inside modal

Hi, I have load login page in modal, now I want to show another modal on link reset. How it is possible?

#19911 report it
Muhammad Shahzad at 2016/07/23 03:33am
Open modal inside modal

Hi, I have load login page in modal, now I want to show another modal on link reset. How it is possible?

#19491 report it
Derek++ at 2015/07/31 04:36am
Close button gets deleted

The code you posted will overwrite the close button when the modal loads since the close button is contained within the #modalHeader element...

document.getElementById('modalHeader').innerHTML = '<h4>' + $(this).attr('title') + '</h4>';

Should change to something like this...

<?php
yii\bootstrap\Modal::begin([
    'header' => '<span id="modalHeaderTitle"></span>',
    'headerOptions' => ['id' => 'modalHeader'],
    'id' => 'modal',
    'size' => 'modal-lg',
    //keeps from closing modal with esc key or by clicking out of the modal.
    // user must click cancel or X to close
    'clientOptions' => ['backdrop' => 'static', 'keyboard' => FALSE]
]);
echo '<div id="modalContent"><div style="text-align:center"><img src="'.Url::to('/images/modal-loader.gif').'"></div></div>';
yii\bootstrap\Modal::end();
?>
(function($){
    $(document).on('click', '.showModalButton', function () {
        //check if the modal is open. if it's open just reload content not whole modal
        //also this allows you to nest buttons inside of modals to reload the content it is in
        //the if else are intentionally separated instead of put into a function to get the 
        //button since it is using a class not an #id so there are many of them and we need
        //to ensure we get the right button and content. 
        if ($('#modal').data('bs.modal').isShown) {
            $('#modal').find('#modalContent')
                    .load($(this).attr('value'));
            //dynamiclly set the header for the modal
            document.getElementById('modalHeaderTitle').innerHTML = '<h4>' + $(this).attr('title') + '</h4>';
        } else {
            //if modal isn't open; open it and load content
            $('#modal').modal('show')
                    .find('#modalContent')
                    .load($(this).attr('value'));
            //dynamiclly set the header for the modal
            document.getElementById('modalHeaderTitle').innerHTML = '<h4>' + $(this).attr('title') + '</h4>';
        }
    });
 })(jQuery);
#19438 report it
ric_ardo at 2015/07/08 10:45am
Update field

Congratulations, great post! This partially working for me. I have a form (freight / _form). In it there is a drop-down field called customer, and this field fetches the data from the database, the customer table. I am using the modal example above to add new customers. It works, but is not only upgrading the client field after saving and closing modal. I need after the closing of modal, he maintains the form shipping, while maintaining inserted data, just update customer field. I read about pjax, and I think it will solve, but I am a beginner yii2 and could not make it work for me. I am sorry my bad english. Can you help me please?

#19392 report it
skworden at 2015/06/15 09:26pm
answers

@rock cat

Just because it doesn't register layouts doesn't mean it won't register js/css when being called. It will load the js for the form because Yii registers it when the form is called. If you register you assets in the page it will include them in the form i.e. ajax/ client validation. If your client validation doesn't work that means you don't have something configured properly. I use a highly modified version of this everyday in my programs and client validation does work.

@aronbos

You really should register the assets via Yii's asset manager. If you do so it will automatilly place them in the

<?php $this->endBody() ?>

location. The reason the endBody() function is there, is to tell Yii where to place POS_END located assets & similar for the beginBody() function. As far as making it a elseif statement instead of an else... you shouldn't have to if your actions are set up correctly.

#19341 report it
aronbos at 2015/05/24 03:13pm
add if en after instruction

In

public function actionSomeAction($id) {
        $model = $this->findModel($id);
        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => (string) $model->_id]);
        }else  !->add IF <-! (Yii::$app->request->isAjax) {

add if.

Second tip, add after:

Add the bootstrap modal to your layouts main.php file. I put it at the very end after all of my scripts.

but before <?php $this->endBody() ?>

#19325 report it
Rock Cat at 2015/05/21 05:51am
How could I use Yii client validation

As you said, in actions we use $this->renderAjax() method, so this method can't load any layouts and js, it means can't process client valida form data, that default by Yii2 is yiiActiveForm. how do you resolve this?

#19276 report it
Alessandro Correia at 2015/05/07 10:54am
Not work

When I try do this the modal is show but not load the content and the page refresh with link target.

Leave a comment

Please to leave your comment.

Write new article