The Loader module provides a block displaying a page loading progress indicator as percentage or progress bar. I'll just post important parts of it as reference here, but the loader module now has its project page so the code here might get outdated. It's here to show the basic principle only.
It can also trigger configurable jQuery callbacks at each loading stage if necessary (25%, 50%, 75% and 100%). It is a simple jQuery plugin that counts visible img tags on the page and attaches .load() events to them to update the counter, and fire callbacks accordingly. It has very little overhead and a $(window).load() event makes sure nothing gets left behind. You can check my initial jQuery plugin for further information and mostly if you want such a feature on a Drupal 5 website. If so, simply use the jQuery version there or use the D5 progress bar in your theming functions.
Let's take a look at the module.
/** * Implementation of hook_init(). */ function loader_init() { $settings = array( 'minimum' => variable_get('loader_minimum', 5), 'usepb' => variable_get('loader_usepb', 1), 'animations' => variable_get('loader_animations', 1), 'wrapper' => variable_get('loader_wrapper', '#wrapper'), 'container' => variable_get('loader_container', '#container'), 'loadingclass' => variable_get('loader_loadingclass', 'loading'), 'callback' => variable_get('loader_callback', ''), 'callback_tw' => variable_get('loader_callback_25', ''), 'callback_fi' => variable_get('loader_callback_50', ''), 'callback_se' => variable_get('loader_callback_75', ''), // TODO allow passing callback arguments? ); drupal_add_js(array('loader' => $settings), 'setting'); if (variable_get('loader_usepb', 1)) { drupal_add_js('misc/progress.js', 'module', 'header'); } $path = drupal_get_path('module', 'loader'); drupal_add_js($path .'/loader.js', 'module', 'header'); }
The block simply contains <div id="loader_loader"></div> to attach our progress bar or percentage to.
(function($) { /** * A page loading progress object. Initialized by passing a "loader" element in * which the loading progress will be displayed by a Drupal.progressBar (or not) * * Usage: * $('#loader').loader({options}); * * Options: * 'wrapper': $(document.body), * The element in which we check for the images to load * 'container': $('#container'), * Container for your content which will get hidden then faded-in when * the page has finished loading (if animations are enabled in your * settings * 'loadingClass': 'loading', * CSS class added to the loader and container for extra theming * 'useDrupalProgress': true * Use Drupal.progressBar or not, in which case a simple * <div><span>0</span></div> will be inserted in $loader and the * span element will get updated with the progress in percentage * 'callback': function(){} * Callback function that gets called after $('#container') finished * loading * * NOTE: The module already attaches itself to the document (or element) you * set in the settings with Drupal.behaviours (see bottom of this file). * * DO NOT EXECUTE $.loader.init() again! * */ $.loader = { init: function(options) { if (!Drupal.jsEnabled) return false; $loader = $('#loader_loader'); if ($($loader).length <= 0) return false; $settings = Drupal.settings.loader; if ($($settings).length <= 0) return false; var settings = { 'minimum': $settings.minimum, 'useDrupalProgress': $settings.usepb, 'wrapper': $settings.wrapper, 'container': $settings.container, 'loadingClass': $settings.loadingclass, 'animations': $settings.animations, 'callback': $settings.callback, 'callbackTw': $settings.callback_tw, 'callbackFi': $settings.callback_fi, 'callbackSe': $settings.callback_se, }; if (options) $.extend(settings, options); var $wrapper = $(settings.wrapper); var $container = $(settings.container); var lastPercentLoaded = 0; var imgLoaded = 0; var hasLoaded = 0; var gotFeedback = false; var imgTotal = $('img:visible', $wrapper).length; var do_loaded = function() { if (!hasLoaded) { $loader.removeClass(settings.loadingClass).stop().fadeOut('normal').remove(); $container.removeClass(settings.loadingClass); if ((typeof(settings.callback) == 'string')) { try { eval(settings.callback + '(true)'); } catch(err) { try { console.log('Error: ' + err + '\nTrying to call: ' + settings.callback);} catch(nofirebugsucka) {} } } if (!gotFeedback) { $.loader.callbacks(settings,0,100); } hasLoaded = 1; } }; if ((imgTotal <= 0) || imgTotal < $settings.minimum) { $(window).load( function() { do_loaded(); }); return false; } // We can now show the loading progress, minimum has been met if (settings.useDrupalProgress) { var pb = new Drupal.progressBar('loader-progress'); var $pb = $(pb.element); $loader.hide().append($pb).fadeIn('slow'); } else { var $percent = $('<span>0</span>'); var $loadel = $('<div></div>').text('%').prepend($percent); $loader.hide().append($loadel).fadeIn('slow'); } // We can go ahead now, hide the content in $container if animations // are on and attach behaviors if (settings.animations) { $container.hide(); } $loader.addClass(settings.loadingClass); $container.addClass(settings.loadingClass); var loaded = function() { if (settings.animations) { $container.fadeIn('normal', function() { do_loaded(); if ($.browser.msie) { $.loader.fixIEfilter(this); } }); } else { do_loaded(); } }; $('img:visible', $wrapper).each( function(img) { $(this).load( function(e) { imgLoaded++; var percentLoaded = parseInt((parseInt(imgLoaded) * 100) / parseInt(imgTotal)); if (settings.useDrupalProgress) { pb.setProgress(percentLoaded); } else { $percent.text(percentLoaded); } $.loader.callbacks(settings,lastPercentLoaded,PercentLoaded); lastPercentLoaded = percentLoaded; if (imgLoaded == imgTotal) { loaded(); } }); }); $(window).load( function() { // Force initLoaded() when images don't fire .load() if (settings.useDrupalProgress) { pb.setProgress(100); } else { $percent.text('100'); } loaded(); }); return $loader; }, fixIEfilter: function(els) { $(els).each(function(i){ if ($(this).length > 0) { if (this.style.filter && this.style.removeAttribute) { this.style.removeAttribute('filter'); } } }); }, callbacks: function(settings,lastPercentLoaded,percentLoaded) { if (lastPercentLoaded < 25 && percentLoaded >= 25 && (typeof(settings.callbackTw) == 'string')) { try { eval(settings.callbackTw + '(' + percentLoaded + ')'); } catch(err) { try { console.log('Error: ' + err + '\nTrying to call: ' + settings.callbackTw);} catch(nofirebugsucka) {} } } if (lastPercentLoaded < 50 && percentLoaded >= 50 && (typeof(settings.callbackFi) == 'string')) { try { eval(settings.callbackFi + '(' + percentLoaded + ')'); } catch(err) { try { console.log('Error: ' + err + '\nTrying to call: ' + settings.callbackFi);} catch(nofirebugsucka) {} } } if (lastPercentLoaded < 75 && percentLoaded >= 75 && (typeof(settings.callbackSe) == 'string')) { try { eval(settings.callbackSe + '(' + percentLoaded + ')'); gotFeedback = true; } catch(err) { try { console.log('Error: ' + err + '\nTrying to call: ' + settings.callbackSe);} catch(nofirebugsucka) {} } } } }; Drupal.behaviors.loader = function() { $.loader.init(); } })(jQuery);
Copyright (c) 2009 Vincent Gariepy
Licensed under the GPL license
(en.wikipedia.org/wiki/GNU_General_Public_License)
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.