I am creating a loading indicator on my submit button and attaching the "start" procedure to the beforeSubmit event using the registerJs function.
It works properly the first time, but after the Pjax container is reloaded, the event won't fire when the form is submitted again.
I am enclosing the entire view file in the Pjax container so that the flash messages are properly displayed.
Here is the main.php layout file snippet:
<?php Pjax::begin([
'id' => 'pjax-container',
'enablePushState' => false,
]); ?>
<?= Alert::widget(); ?>
<?= $content; ?>
<?php Pjax::end(); ?>
And here is the registerJs function, located in main.php in the section:
<?php
$js = <<< 'SCRIPT'
$('#form').on('beforeSubmit', function(){
alert('Submitted...');
//$("#spinner").fadeIn();
});
SCRIPT;
$this->registerJs($js);
?>
And here is the activeForm:
<?php $form = ActiveForm::begin(['id' => 'form', 'options' => ['data-pjax' => true]]); ?>
<?= $form->field($model, 'name')->textInput(['type' => 'text']) ?>
<?= $form->field($model, 'email')->textInput(['type' => 'email']) ?>
<?= $form->field($model, 'subject')->textInput(['type' => 'text']) ?>
<?php
echo $form->field($model, 'message')->textArea(['rows' => 6]);
echo $form->field($model, 'verify_code')->widget(Captcha::className(), [
'captchaAction' => 'site/captcha',
'template' => '<div class="row"><div class="col-lg-4">{image}</div><div class="col-lg-6">{input}</div></div>',
]);
?>
<button class="btn btn-success">
<span id="spinner" style="display: none; float:left; margin-right: 26px;">
<?php
echo Spinner::widget([
'pluginOptions' => [
'top' => '50%',
'left' => '10px',
'lines' => 11,
'length' => 5,
'width' => 3,
'corners' => 1,
'trail' => 100,
'speed' => 1.25,
'radius' => 4,
],
]);
?>
</span>
<?php echo $model->isNewRecord ? 'Send Message' : 'Update'; ?>
</button>
<?php ActiveForm::end(); ?>
I am using the Kartik Spinner Widget (Details and Demo)
Any ideas on why the beforeSubmit event won't fire after Pjax has loaded the data container after a successful for submit?
Thanks.
I am creating a loading indicator on my submit button and attaching the "start" procedure to the beforeSubmit event using the registerJs function.
It works properly the first time, but after the Pjax container is reloaded, the event won't fire when the form is submitted again.
I am enclosing the entire view file in the Pjax container so that the flash messages are properly displayed.
Here is the main.php layout file snippet:
<?php Pjax::begin([
'id' => 'pjax-container',
'enablePushState' => false,
]); ?>
<?= Alert::widget(); ?>
<?= $content; ?>
<?php Pjax::end(); ?>
And here is the registerJs function, located in main.php in the section:
<?php
$js = <<< 'SCRIPT'
$('#form').on('beforeSubmit', function(){
alert('Submitted...');
//$("#spinner").fadeIn();
});
SCRIPT;
$this->registerJs($js);
?>
And here is the activeForm:
<?php $form = ActiveForm::begin(['id' => 'form', 'options' => ['data-pjax' => true]]); ?>
<?= $form->field($model, 'name')->textInput(['type' => 'text']) ?>
<?= $form->field($model, 'email')->textInput(['type' => 'email']) ?>
<?= $form->field($model, 'subject')->textInput(['type' => 'text']) ?>
<?php
echo $form->field($model, 'message')->textArea(['rows' => 6]);
echo $form->field($model, 'verify_code')->widget(Captcha::className(), [
'captchaAction' => 'site/captcha',
'template' => '<div class="row"><div class="col-lg-4">{image}</div><div class="col-lg-6">{input}</div></div>',
]);
?>
<button class="btn btn-success">
<span id="spinner" style="display: none; float:left; margin-right: 26px;">
<?php
echo Spinner::widget([
'pluginOptions' => [
'top' => '50%',
'left' => '10px',
'lines' => 11,
'length' => 5,
'width' => 3,
'corners' => 1,
'trail' => 100,
'speed' => 1.25,
'radius' => 4,
],
]);
?>
</span>
<?php echo $model->isNewRecord ? 'Send Message' : 'Update'; ?>
</button>
<?php ActiveForm::end(); ?>
I am using the Kartik Spinner Widget (Details and Demo)
Any ideas on why the beforeSubmit event won't fire after Pjax has loaded the data container after a successful for submit?
Thanks.
Share Improve this question asked Apr 8, 2015 at 20:43 Mike PearsonMike Pearson 5051 gold badge6 silver badges18 bronze badges 4- 1 Which render method are you using in your controller? just render()? or renderAjax()? – Pomme.Verte Commented Apr 8, 2015 at 22:37
- I was using the render() but did a quick test with renderAjax() and that seemed to work. I had to move the registerJs() function to the actual view file though. And, now the flash messages to show up. Any ideas? – Mike Pearson Commented Apr 9, 2015 at 0:51
- Sorry, the I meant to say the flash messages don't show up now on Ajax load. – Mike Pearson Commented Apr 9, 2015 at 1:06
- So, it was an issue with the render function. I now use renderAjax() and it works just fine. I did have to move the Pjax and alert::widget to the view file as the renderAjax function doesn't include a layout and the flash messages wouldn't show. – Mike Pearson Commented Apr 9, 2015 at 1:56
2 Answers
Reset to default 5This problem occurs for several reasons.
Firstly, Any code placed within $(document).ready();
and loaded on the ajax called page (contained page) will not be executed after it is loaded.
Secondly, any references to external scripts (<script src>
) aren't guarantied to load before inline scripts. In the case of Yii2 these inline scripts would be registered with registerJs()
Getting Pjax to play nice with plex script dependent pages to load is not trivial.
A few things to solve your issues:
1) Inline scripts loaded with registerJs()
and set with a position of POS_READY
(default?) will be contained within a $(document).ready()
if the view is rendered via render()
.
Thus it is important to render via renderAjax()
. This will ensure that the inline scripts will run.
Sometimes (most times) you will need to use render()
when statically loaded and renderAjax()
when pjax loads a page. In your instance this is required in order to place the Pjax widget in the layout. You can use the following code for these situations:
if(Yii::$app->request->getHeaders()->has('X-PJAX'))
{
return $this->renderAjax('myview');
}
else
{
return $this->render('myview');
}
2) When writing external scripts that you know will load within the context of a pjax call. Use:
$(document).on('ready pjax:success', function(){
// ...
});
This will load the script correctly. But keep in mind that it will reload/rebind the script every time a Pjax call succeeds. That might not be desired unfortunately.
Also keep in mind that the pjax:success
event does not necessarily fire after the external scripts from the contained page are loaded. See 3)
3) To solve script order issues you will need to remove the following line from the pjax.js script:
obj.scripts = findAll(obj.contents, 'script[src]').remove()
This will however create issues with browsers that are set to disable eval()
(hence why the line exists in the first place)
Pjax will eventualy have this sorted. You can keep track of the progress here
If anything is unclear let me know and I'll rephrase.
UPDATE: Yii 2.0.7 has introduced changes. One of which is updating the bower\yii2-pjax
to 2.0.6
. This makes point 3) no longer necessary and makes point 2) less important, although it's still a good thing to know.
So. I have a solution, but it contradicts the documentation...
If you look at the actual redirect function (source):
public function redirect($url, $statusCode = 302, $checkAjax = true)
743 {
744 if (is_array($url) && isset($url[0])) {
745 // ensure the route is absolute
746 $url[0] = '/' . ltrim($url[0], '/');
747 }
748 $url = Url::to($url);
749 if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) {
750 $url = Yii::$app->getRequest()->getHostInfo() . $url;
751 }
752
753 if ($checkAjax) {
754 if (Yii::$app->getRequest()->getIsPjax()) {
755 $this->getHeaders()->set('X-Pjax-Url', $url);
756 } elseif (Yii::$app->getRequest()->getIsAjax()) {
757 $this->getHeaders()->set('X-Redirect', $url);
758 } else {
759 $this->getHeaders()->set('Location', $url);
760 }
761 } else {
762 $this->getHeaders()->set('Location', $url);
763 }
764
765 $this->setStatusCode($statusCode);
766
767 return $this;
768 }
You can see that it checks for Pjax before Ajax and has two different headers as such. the X-Redirect header that is mentioned all over the web and multiple sites, does NOT work for a Pjax call. You must check for X-Pjax-Url for a Pjax call.
I register my own javascript function to handle the redirect like below:
$js = <<< 'SCRIPT'
$(document).on('pjax:plete', function (event, xhr, textStatus, options) {
//$(document).ajaxComplete(function (event, xhr, settings) {
var url = xhr.getResponseHeader('X-Pjax-Url');
if (url) {
window.location = url;
}
});
SCRIPT;
$this->registerJs($js);
And for some reason, this on its own wasn't enough. The source function, by default runs the checkAjax code, but until I explicitly called it from my redirect code, it wouldn't work.
So here is what I use in my controller:
Yii::$app->response->redirect(Yii::getAlias('@web') . '/secure/dashboard/', true)->send();
return;
This has been discussed many, many times and I hope this helps others.