yiisoft / yii2

Yii 2: The Fast, Secure and Professional PHP Framework
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
14.24k stars 6.91k forks source link

view scripts not executing in bootstrap\modal for the first time #16867

Open buttflattery opened 5 years ago

buttflattery commented 5 years ago

If you are using bootstrap\Modal to load a form inside the modal window, and you show the modal window on the click of a button, the scripts are not executed the first modal window loads the view either they are inline using registerJs('alert("hello")') or registered via Asset::register($this), but if you close the modal window and click the button again then the scripts are executed as soon as the modal window opens and loads the view. Also the statement View::POS_READY has no effect when used with $this->registerJs('alert("hello")',\yii\web\View::POS_READY);, it is not wrapped inside the $(document).ready() block.

What steps will reproduce the problem?

Create a view views/site/modal.php and add the modal code like below with an extra button and js code to open the modal window on button click and bind event shown.bs.modal to load the view inside the modal content section once modal is shown

<?php

use yii\bootstrap\Modal;
use yii\helpers\Html;

Modal::begin([
    'id' => 'contact',
]);
?>

<div class="modal-header">
    <h4>Contact</h4>
</div>

<div class="modal-body">

</div>

<div class="modal-footer">
    <?php
    echo Html::submitButton('Add Folder Now', ['class' => 'btn btn-primary']);
    ?>

</div>

<?php Modal::end() ;

 echo Html::button('button', ['class'=>'btn btn-primary','id'=>'add-folder-button']);

 $js=<<< js
        $("#add-folder-button").on('click',function(){
            $( '#contact' ).modal( {show:true});
            $( '#contact' ).appendTo("body");
        });

         $( '#contact' ).on( 'shown.bs.modal', function ( event ) {
            // var button = $(event.relatedTarget); // Button that triggered the modal
            let modal = $( this );
            modal.find( '.modal-header > h4' ).text( 'Add Folder' );
            modal.find( '.modal-body' ).load( 'contact');
        } );
js;
$this->registerJs($js, \yii\web\View::POS_READY);

Create an action inside the SiteController to load the above view

public function actionModal(){
        return $this->render('modal');
    }

Now modify the actionContact inside the SiteController like below to be loaded inside the modal window

/**
     * Displays contact page.
     *
     * @return Response|string
     */
    public function actionContact()
    {
        $model = new ContactForm();
        if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) {
            Yii::$app->session->setFlash('contactFormSubmitted');
            return $this->refresh();
        }
        return $this->renderAjax('contact', [
            'model' => $model,
        ]);
    }

For the view, you can use the default contact.php view inside the views/site folder just add the below code on top of the view


$js=<<< js
        alert("hello");
js;
$this->registerJs($js, \yii\web\View::POS_READY);

What is the expected result?

on clicking the button the modal window should load and as soon the view is loaded it should show you the alert("hello") , and if I cycle through the fields it should run the ActiveForm client validation.

Note : i even tried providing 'enableClientValidation'=>true in the form options.

What do you get instead?

it Does nothing neither it shows the alert neither it runs the validation, instead if you close and then click the button again to open the modal window then everything works correctly. the Alert is shown and the client validation works. but if you refresh the page again it wont work again until you open the modal window once and close it and open it again.

The above scenario works perfectly in Yii 1.x versions and i have it working i am working on a yii1.x extension to transform it to yii2.x

Additional info

Q A
Yii version 2.0.15.1
PHP version 5.6
Operating system windows
yii-bot commented 5 years ago

Thank you for your question. In order for this issue tracker to be effective, it should only contain bug reports and feature requests.

We advise you to use our community driven resources:

If you are confident that there is a bug in the framework, feel free to provide information on how to reproduce it. This issue will be closed for now.

This is an automated comment, triggered by adding the label question.

buttflattery commented 5 years ago

@samdark sorry if this is being considered as a question as I did not intend to, the minimum code to regenerate the issue is already added, and it is the most basic way that is being used widely around for opening a view inside the modal window, but it simply does not work. I would take this question to StackOverflow although just to make sure there is something I am doing wrong. But can you just confirm if this works accurately in your case?

samdark commented 5 years ago

No, it won't work. POS_READY is fired once on loading a document. Loading model contents is not that.

buttflattery commented 5 years ago

@samdark i don't think so you are following me on this, i don't care if the POS_READY works or not, lets say there isn't any inline javascript code that is written using $this->registerJs() in the view that you are loading inside the modal, its just an ActiveForm. Now, it should trigger the validation as soon as you TAB through the fields.

If you agree on this prove me wrong when i say

"The ActiveForm validation will not work on the first modal load, BUT the second time, if you dont refresh the page and just close the modal window and load it again."

Why does it work on the second time then, either it should work on the first time or it should never work. And according to you the inline scripts also shouldnt work why does it displays the alert the second time modal window is loaded without refreshing the page.

the response has the following HTML

<div class="site-contact">
    <h1>Contact</h1>

        <p>
            If you have business inquiries or other questions, please fill out the following form to contact us.
            Thank you.
        </p>

        <div class="row">
            <div class="col-lg-5">

                <form id="contact-form" action="/my/Plugin-development/modal-test-app/web/index.php/site/contact" method="post">
<input type="hidden" name="_csrf" value="rNhcLMksQ2bndmA0oq6VoFcupc6UhOM373ySUCs8Pk-UiG1LvwErM49GA12RycfiAX7siPHuzlOgHfkWYlR4Lg==">
                    <div class="form-group field-contactform-name required">
<label class="control-label" for="contactform-name">Name</label>
<input type="text" id="contactform-name" class="form-control" name="ContactForm[name]" autofocus aria-required="true">

<p class="help-block help-block-error"></p>
</div>
                    <div class="form-group field-contactform-email required">
<label class="control-label" for="contactform-email">Email</label>
<input type="text" id="contactform-email" class="form-control" name="ContactForm[email]" aria-required="true">

<p class="help-block help-block-error"></p>
</div>
                    <div class="form-group field-contactform-subject required">
<label class="control-label" for="contactform-subject">Subject</label>
<input type="text" id="contactform-subject" class="form-control" name="ContactForm[subject]" aria-required="true">

<p class="help-block help-block-error"></p>
</div>
                    <div class="form-group field-contactform-body required">
<label class="control-label" for="contactform-body">Body</label>
<textarea id="contactform-body" class="form-control" name="ContactForm[body]" rows="6" aria-required="true"></textarea>

<p class="help-block help-block-error"></p>
</div>
                    <div class="form-group field-contactform-verifycode">
<label class="control-label" for="contactform-verifycode">Verification Code</label>
<div class="row"><div class="col-lg-3"><img id="contactform-verifycode-image" src="/my/Plugin-development/modal-test-app/web/index.php/site/captcha?v=5be1ef855b05f9.16889267" alt=""></div><div class="col-lg-6"><input type="text" id="contactform-verifycode" class="form-control" name="ContactForm[verifyCode]"></div></div>

<p class="help-block help-block-error"></p>
</div>
                    <div class="form-group">
                        <button type="submit" class="btn btn-primary" name="contact-button">Submit</button>                    </div>

                </form>
            </div>
        </div>

    </div>
<script src="/my/Plugin-development/modal-test-app/web/assets/84303e17/jquery.js"></script>
<script src="/my/Plugin-development/modal-test-app/web/assets/820a819f/yii.js"></script>
<script src="/my/Plugin-development/modal-test-app/web/assets/820a819f/yii.validation.js"></script>
<script src="/my/Plugin-development/modal-test-app/web/assets/820a819f/yii.captcha.js"></script>
<script src="/my/Plugin-development/modal-test-app/web/assets/820a819f/yii.activeForm.js"></script>
<script>        alert("hello");
jQuery('#contactform-verifycode-image').yiiCaptcha({"refreshUrl":"\/my\/Plugin-development\/modal-test-app\/web\/index.php\/site\/captcha?refresh=1","hashKey":"yiiCaptcha\/site\/captcha"});
jQuery('#contact-form').yiiActiveForm([{"id":"contactform-name","name":"name","container":".field-contactform-name","input":"#contactform-name","error":".help-block.help-block-error","validate":function (attribute, value, messages, deferred, $form) {yii.validation.required(value, messages, {"message":"Name cannot be blank."});}},{"id":"contactform-email","name":"email","container":".field-contactform-email","input":"#contactform-email","error":".help-block.help-block-error","validate":function (attribute, value, messages, deferred, $form) {yii.validation.required(value, messages, {"message":"Email cannot be blank."});yii.validation.email(value, messages, {"pattern":/^[a-zA-Z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/,"fullPattern":/^[^@]*<[a-zA-Z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/,"allowName":false,"message":"Email is not a valid email address.","enableIDN":false,"skipOnEmpty":1});}},{"id":"contactform-subject","name":"subject","container":".field-contactform-subject","input":"#contactform-subject","error":".help-block.help-block-error","validate":function (attribute, value, messages, deferred, $form) {yii.validation.required(value, messages, {"message":"Subject cannot be blank."});}},{"id":"contactform-body","name":"body","container":".field-contactform-body","input":"#contactform-body","error":".help-block.help-block-error","validate":function (attribute, value, messages, deferred, $form) {yii.validation.required(value, messages, {"message":"Body cannot be blank."});}},{"id":"contactform-verifycode","name":"verifyCode","container":".field-contactform-verifycode","input":"#contactform-verifycode","error":".help-block.help-block-error","validate":function (attribute, value, messages, deferred, $form) {yii.validation.captcha(value, messages, {"hash":642,"hashKey":"yiiCaptcha/site/captcha","caseSensitive":false,"message":"The verification code is incorrect."});}}], []);</script>
np25071984 commented 5 years ago

I have reproduced the situation and got the same result. I don't know why, but renaming modal-body class name solve it.

Modal::begin([
    'id' => 'contact',
]);
?>

    <div class="modal-header">
        <h4>Contact</h4>
    </div>

    <div class="modal-bodyy">

    </div>

    <div class="modal-footer">
        <?php
            echo Html::submitButton('Add Folder Now', ['class' => 'btn btn-primary']);
        ?>

    </div>

<?php Modal::end() ;

echo Html::button('Show', ['class'=>'btn btn-primary','id'=>'add-folder-button']);

$js = <<< js
    $("#add-folder-button").on('click',function(){
        $( '#contact' ).modal({show: true});
        $( '#contact' ).appendTo("body"); // do you really need this?!
    });

    $( '#contact' ).on( 'shown.bs.modal', function ( event ) {
        let modal = $( this );
        modal.find( '.modal-header > h4' ).text( 'Add Folder' );
        modal.find( '.modal-bodyy' ).load( 'contact'); 
    });
js;

$this->registerJs($js, \yii\web\View::POS_READY);

User case:

  1. refresh the page
  2. press Show button
  3. got Modal window
  4. Contact changes to Add Folder
  5. Alert "hello" appeared
  6. press Ok
  7. Contact view loaded to Div with modal-bodyy class name
  8. got validation errors after press Submit button
samdark commented 5 years ago

That means a handler is bound to .modal-body that results in this behavior.

np25071984 commented 5 years ago

The query modal.find( '.modal-body' ) returns collection of a few elements (two in my personal case). This it the source of the problem. So, we can solve it by:

    $( '#contact' ).on( 'shown.bs.modal', function ( event ) {
        let modal = $( this );
        let body = modal.find( '.modal-body' );
        if (body.length > 1) {
            body.splice(0,1);
        }
        body.load( 'contact');
    });

The only question - why it returns a few elements?

samdark commented 5 years ago

console.log() on that page and check what chrome/ff debugger shows.

np25071984 commented 5 years ago

here is my code:

    $( '#contact' ).on( 'shown.bs.modal', function ( event ) {
        let modal = $( this );
        let body = modal.find( '.modal-body' );
        console.dir(body);
        // if (body.length > 1) {
        //     body.splice(0,1);
        // }
        body.load( 'contact');
    });

On first button press I got: image

On second: image

'it works' indicates that js from modal view executed successfully.

samdark commented 5 years ago

Weird... no idea why there are two modal bodies...

np25071984 commented 5 years ago

I think they are widget itself, and its content. The code

Modal::begin([
    'id' => 'contact',
]);
...
Modal::end() ;

gives us one container. Than, content

<div class="modal-header">
    <h4>Contact</h4>
</div>

gives us another one. That is it.

And user's content overwritten after the first button click. That is why it works since second and further clicks.