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.
Include JS
Add Modal to your main.php layout
Update controller actions to use Ajax
Update form to use Ajax
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>';
}
});
</script>
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!
Not work
When I try do this the modal is show but not load the content and the page refresh with link target.
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?
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:
but before <?php $this->endBody() ?>
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.
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?
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...
[javascript] 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(); ?>
[javascript] (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);
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?
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?
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');
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 !
layout refresh
Why when I call modal first time ... I see a strange layout refresh in background ?
thanks
open modal from other modal (stackable mode)
How to ? Any examples ? thanks
widget
Can you achieve a widget with this code ? Js code in vendor/appAsset ?
Thanks
dead or alive ?
But this thread is stopped? Does anyone know some evolution of this fantastic guide?
thanks
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)?
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?
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
re: skworden
Hello skworden,
Thank you! You helped me a lot!
Best regards
Unique validator not working in ajax pop up
Hai skworden
Thank you, your tutorial help me alot
I implement CRUD with pop up ajax, and all is running well
But when i use unique validator, it's not working, the validation is ignored in pop up create/update form
Can you help me ?
Hi @skworden a guide for manage nested modals ? Thanks
Hi @skworden ,
are you currently using it with the latest Yii2 version
2.0.15.1
i dont think so that the active form validation works using the above method you provided. With the latest version it works the second time you load the modal window not the FIRST time, and that too without refreshing the page, otherwise it never works the first time. i tried twice, once in my project where i am creating a extension and after facing this issue i tried using a fresh install of basic-app and it is the same you can try loading the contact form inside the modal window, just change the$this->render('contact')
to$this->renderAjax('contact')
in theSiteController
.This is wierd issue that i am facing currently with the latest Yii2 version, neither client validationof active form or any inline script like
$this->registerJs('alert("hello")')
works the first time when the modal window loads. BUT on the second attempt after closing the modal window and opening it again. strangely there arent any errors too.$(element).data('bs.modal')
will returnnull
if the modal is not opened rather than a boolean so update the code to compare the condition withnull
otherwise it wont work correctly.Hello,
thank you.
As @rock cat also I have some validation problem .
My scenario is an insert in modal form:
in role of model I set this to check unique email
`
['email', function($attribute, $params) {$value = $this->email; $idexcept=0; if (isset($this->id)){ $idexcept = $this->id; } $qry = \Yii::$app->db->createCommand("SELECT * FROM user WHERE email = '{$value}' and id<>{$idexcept}")->queryOne(); if ($qry) { $this->addError($attribute, 'This email is used. Please try another one.'); } }],
The validation run after the submit and the modal switch off. Ho can I fix this problem? Thanks in advance. Alessio
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.