Implementing menu items with progress (wait) dialog

You are viewing revision #1 of this wiki article.
This is the latest version of this article.

  1. Introduction
  2. The code
  3. Final words

This article shows one of possible ways to implement menu items (as well as links), which displays a progress / wait dialog and are redirecting browser to destination URL in the same time. This is especially useful, for links or routes that are know to be loaded for a prolonged period of time (i.e. getting a lot of data from database).

Note: This example comes from my very old project, so the code is not that perfect as I would wrote it now. Consider this as just a start point for you, for further reasarch. Feel free to introduce any corrections or enhancements, you would like to this article.

Introduction

The general idea is to display some dialog box, using JavaScript or jQuery and initiate browser redirection in the same time (in response to one click event). If you implement proposed solution, you don't have to take care about hiding already open wait-dialog, because when page is finally redirected to destination, wait-dialog is all gone (part of browser history! :]).

I'm showing an example based on menu. But since menus, CGridView's columns and buttons confguration are quite similar (based on associative arrays and well known attributes), this solution can be easily adapted for example to a link, button or grid view column.

The code

PHP code

Consider following array as one menu item:

array
(
	'label'=>'Results',
	'linkOptions'=>CFR::getLoaderArray
	(
		'Loading results module',
		'Getting data from database!',
		array('/results/index')
	),
	'active'=>(app()->controller->id == 'results')
)

For convinience (attaching to many menu items, links, buttons etc.) key configuration array is not expressed directly and is instead generated using following function:

/**
 * Builds params list (htmlOptions) used to call wait-loader.
 * 
 * @param string Window title.
 * @param string Message, that appears in a window.
 * @param string Destination URL, where page should be redirected after displaying wait-dialog.
 * @param array Additional htmlOptions merged with function ones.
 * 
 * @return array Associative array in htmlOptions format, ready to be binded to a menu, button etc.
 */
public static function getLoaderArray($title, $contents, $url = '', $htmlOptions = array())
{
	$url = CHtml::normalizeUrl($url);

	$onClick = 'showLoaderDialog(\''.$title.'\', \'Please wait...<br /><br /><strong>'.$contents.'</strong>\'';
	$onClick = ($url != '') ? $onClick.', \''.$url.'\')' : $onClick.')';
	
	$baseArray = array('onclick'=>$onClick, 'encode'=>false);
	$baseArray = array_merge($htmlOptions, $baseArray);

	return $baseArray;
}
Javascript code

Functions showLoaderDialog() and hideLoaderDialog() are Javascript functions defined this way:

<?php
	Yii::app()->clientScript->registerScript('long-operation-wait-dialog-open',
	'
		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 != "")
			{
				if(typeof intervalID !== "undefined") clearInterval(intervalID);
				
				window.location.href = url;
			}
		}
		
		function hideLoaderDialog()
		{
			$("#long-operation-wait-dialog").dialog("close");
		}'
			
	, CClientScript::POS_HEAD);
?>

As you may see, you may use this solution without passing URL, which will result in displaying wait dialog without actual browser redirection. But I can't think of many situations, where such feature would be usefull?

The view

Key part of entire solution is a partial view, which holds actual wait (progress) dialog (and above mentioned two Javascript functions). I used CJuiDialog for this purpose, but you can of course use anything you want or even design your wait dialog manually.

Here is wait dialog binded inside partial view:

<?php
	$this->beginWidget('zii.widgets.jui.CJuiDialog', array
	(
		'id'=>'long-operation-wait-dialog',
		'options'=>array
		(
			'title'=>'',
			'autoOpen'=>false,
			'closeOnEscape'=>false,
			'draggable'=>false,
			'resizable'=>false,
			'modal'=>true,
			'height'=>290,
			'width'=>400,
		),
		'htmlOptions'=>array('style'=>'display: none')
	));

	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');
?>

Notice, that this part: 'htmlOptions'=>array('style'=>'display: none') is necessary for some old versions of Internet Explorer, in which dialogs were not correctly hidden using CJuiDialog (jQuery) internals.

Now, because entire engine of this solution (both view and responsible Javascript functions) are stored in one partial view (stored in /protected/views/layouts/_wait.php), all I had to do, was to add renderPartial call to my main layout file (/protected/views/layouts/main.php):

<?php $this->renderPartial('//layouts/_wait'); ?>

just below <body> tag.

This ensured, that I'll have my wait-dialog always rendered, in any view, no matter, if I want it or not. This was also necessary, because my menu definition (where I mostly used my wait-dialogs) was also stored in main layout file. However, if you're planning to use this solution only in certain situations, it is better to use renderPartial directly in a view, where you want to use your wait-dialog. Do not add it to your layout file in this case.

Final words

As I mentioned in the beginning, this is quite old solution and it is not coded (or designed) with highest standards. But it can be a good start point for anyone wishing to develop something better or more professional.