Three problems of constructing loader in Yii / jQuery

Hi there,

This is rather theoretical question, so in the first line I’m looking for the answer if this is even possible to implement and only then - how to implement it in Yii.

I have a bunch of very time consuming operations in my project, for which I designed a simple jQuery loader. It is fired in on click of element, shows jQuery UI dialog (CJuiDialog) with “x” button hidden and no other buttons for closing it. Right after showing this dialog, JS function redirects to destination URL. I’m using a little trick here - since redirection is in progress, there is no need to hide dialog - destination page won’t show it.

The problem I have is that I keep forgetting to attach this loader to link executing such time-consuming operation, as the same operation might be called from many sources - menu, sidebar, footer, header, links in page, etc. So I was thinking is there any idea or way to implement such solution (OnBeginRequest?) that would try to detect if destination URL is among array holding all such time-consuming URLs and if so, would display that loader before. If destination URL wouldn’t be on so called black list, a normal redirection, without progress dialog would be done.

I wrote OnBeginRequest before just as an example, as I don’t see no way implementing such thing in Yii. It must have be done completely in JavaScript / jQuery, but maybe you have other opinion.

Another problem is, how to block user from refreshing the page? Since “x” is hidden there is no way to manually close that dialog, but user can hit F5 to refresh page during long time-consuming operation an all the beautiful idea goes to hell. I heard that jQuery plugin called Block UI can prevent this and solve this problem, but I wasn’t able to get it work in Yii (was working fine in my old, pure-PHP project) and heard that this blocking is not working to well in such dumb browsers like IE.

Third problem is, how to implement Cancel button functionality, but I fear that this wouldn’t be possible at all, since redirection to a destination pages is done right after displaying the dialog, and source page code has no way how to break it, since it lost all the control in favor of a new page, being displayed as a result of redirection.

Thank you in advance for all ideas here.

Hmmm… I don’t see why would IE be dumb… for not letting you block the F5…

My opinion is that this is a browser operation (F5 - refresh) and if a user wants to refresh the page… you need to let him do that… on the other side… even if you block this… the user can close the browser… would you block even this…

How I see this… it would be better to inform the user that this operation will need time and he needs to wait (confirm dialog)… and only then if he choose so… you go to the ajax call… this way you informed the user that he has to wait and there is no reason why he would do a refresh… on the other side… if you don’t inform the user… he can think that the browser/application has blocked… and try to solve it by refreshing the page,…

As for the "onBeginRequest" there is $.ajaxSend - http://api.jquery.com/ajaxSend/

This will be executed before any ajax request - in this function you can for example open an dialog that say "ajax in progres"…

And there is $.ajaxComplete - http://api.jquery.com/ajaxComplete/

That will be executed after the ajax call - here you can close the dialog…

Well, to put two to two, you’re right. Blocking refreshing has no sense. User must be informed that should not hit Refhresh doring operation, because this will break current “session” and make him wait even longer.

Thanks for the tips on AJAX events I’ll investigate it further.

Let me reopen the discussion…

OK. But even if I agree with you, I still have no answer for most base problem - how to attach such event to every request that matches some address - no matter if this even will display a dialog waiting or information about up comming time consuming operation.

Your ideas with .ajaxSend and ajaxComplete sound interesting, but what about request that are not going through ajax, but a normal GET / POST request?

The main problem is that dialog displaying is done i JavaScript while clicks handling in Yii.

The way I see it - although I have no bloody idea, if this has any sense - is to write own onBeginRequest, analize there request URL (i.e. obtain controller and action) and if it falls into array holding time consuming operations, then block current execute of current request (if it is possible), render some small view that will contain only dialog and JS code displaying it along with a JS function that will do redirect to actual (requested) URL after displaying dialog and gaining YES from the user.

Sounds like a little bit madness! :] What do you think about it?

If the link is a normal link (non-ajax) than you cannot do anything… the new page will be opened in the current or new tab… if it’s a long operation than the browser status will be “loading”… you cannot do here anything…

if it’s an ajax request… that means that the current page (portion of page) will be updated… and if the operation is long enough a user can think that nothing is happening, that the browser is blocked…

to attach an event you need an ID or a CLASS… for example you can assign a certain class to any link that will do a "slow" load… and than attach a "click" event to all links with that class…

Are you sure, I can’t do nothing, if it isn’t AJAX call? :] What about my idea of checking in OnBeginRequest what controller/action was requested, and if it is on so called black list (of long operations) not to allow process this request, but instead call another action (let’s call it middle step or sub-request), pass destination controller/action to it as a parameter and let it render and display dialog and after that will use JS window.location.href to redirect to (time consuming) controller/action that was initially requested? What do you think about that? Madness? :]

First of all let’s distinct JS/jQuery and PHP… jQuery sees just a HREF link… it does not know what is an controller what an action…

Two options…

First option - jQuery solution… add certain class to those links… and with jQuery you can add an alert or confirm dialog to those links… that would say something like "This will take time, are you sure you want it"…

Second option - PHP solution… in the “slow” controller action you can check for some GET variable that would not be sent the first time… if it’s not there render another view that would say “This will take time, are you sure”… if the user confirms… call again the same URL with that GET parameter so that the next call will make the action…

I think we’re talking about two different cases. I’m not mixing jQuery and PHP I’m using one solution to support another one. Let me explain my idea on example. This will be partially done on pseudo-code as I wasn’t able (didn’t get enough time) to check, if this can be coded using Yii. Maybe then you’ll be able to point flaws in my solution.


onBeginRequest handler()

{

   	1. Get current controller/view

   	2. Check if it is in global array holding "slow" operations.

   	3. If not -> let Yii handle request normally (internally).

   	4. if yes -> 

   	4a. break current request handling done by Yii.

   	4b. render specific view, passing current controller/view as a $url variable to it.

   	4c. view contains only jQuery definition of a dialog and JS function redirecting to provided url (see below).

   	4d. view will display dialog with information that current operation is in progress and then begin redirection to provided url.

}

Example wait-view code could look like this:


<?php

    	$this->beginWidget('zii.widgets.jui.CJuiDialog', array

    	(

            	'id'=>'long-operation-wait-dialog',

    	));


    	echo(CHtml::openTag('div', array('style'=>'padding-top: 11px')));


    	$content = '<img src="'.Yii::app()->request->baseUrl.'/gfx/stopwatch.gif" width="139" height="150" alt="Loader" border="0"><br /><br />';

    	echo(CHtml::tag('div', array('style'=>'text-align: center', 'id'=>'ajax-loader'), $content));

    	echo(CHtml::tag('div', array('id'=>'loader-dialog-contents', 'style'=>'text-align: center')));

    	

    	echo(CHtml::closeTag('div'));

?>


<?php

    	$this->endWidget('zii.widgets.jui.CJuiDialog');

?>


<?php

    	Yii::app()->clientScript->registerScript('long-operation-wait-dialog-show-up',

    	'

            	function showLoaderDialog(title, contents, url)

            	{

                    	$("#loader-dialog-contents").html(contents);

                    	$("#long-operation-wait-dialog").dialog("option" , "title", title);

                    	$("a.ui-dialog-titlebar-close").remove();


                    	$("#long-operation-wait-dialog").dialog("open");


                    	if(url != "") window.location.href = url;

            	}

		     

            	showLoaderDialog(title, contents, '.$url.')

    	'

    	, CClientScript::POS_HEAD);

?>

If I’m not mistaken, my solution only differs from your second one that information is displayed as jQuery dialog, not rendered as a normal view.

Yes it is :D

You just need a way to "know" in the action if the view was already rendered…