davidhw
25 Nov 2008, 12:33 AM
My Ext.js based web application has two loading issues that I guess are fairly common in this community:
1. Long load times for JS files (around 700kb to load), users getting bored and not waiting...
2. Users' browsers not updating recent javascript and CSS, but using cached old versions - creating errors
This PHP function (below) helps to load Javascript files while showing a smooth-moving progress bar.
.js files are timestamped, based on their actual file modified time, so that the users' browser will always load the most recent file.
You can see it in action at www.oothanks.com
(http://www.oothanks.com)NB: In my set up I have divided my ext.js file (a full build, including prototype loader) into several (11) sections - simply by opening it in a textfile editor, and copying a page of lines at a time into new js files - named ext-d1.js to ext-d11.js. They load in order. I did this for a previous non-smooth version of this loader, and I still do it with this loader. I doubt that the rate calculation that smooths the progress bar would work well with a very large JS file. See the example below...
A function is also provided to load CSS files with timestamps.
Timestamps are hashed, because I felt like it.
Here's the PHP:
<?php
// JS and CSS with Timestamp #################################################
function show_stamped_JS($file) {
$t = filemtime($_SERVER['DOCUMENT_ROOT'] . preg_replace("/^(.*)\.js\??(.*)$/", '$1.js', $file));
$file = $file . ((strpos($file, ".js?") === false) ? "?" : "&");
echo "<script src='{$file}v=" . md5(date("YmdHis", $t)) . "' type='text/javascript' language='JavaScript'></script>";
}
function show_stamped_CSS($file) {
$t = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);
echo "<link rel='stylesheet' type='text/css' href='$file?v=" . md5(date("YmdHis", $t)) . "'></link>";
}
// JSLoader ##################################################################
function JSLoader($ar, $div) {
// $ar is an array of JS files to be loaded, JS Loader reports progress with a progress bar etc
// $div the id of the div where the progress will be reported
if (!defined('JSLOADER_JS_SET')) {
define('JSLOADER_JS_SET',true);
// load the JSloader javascript
?>
<!-- JSLoader Script -->
<script language='JavaScript' type='text/javascript'>
var JSLoader = function(){
// namespaced private variables & functions
var JSL = {
totalSize:0, sizeSoFar:0, currentTotal:0, div:"",
startTime:"", rate:0, timerOn:false, intrval:100,
drawBox: function(){
JSL.div.innerHTML = "<div class='JSLoader-barholder'>"
+ "<div class='JSLoader-bar' id='JSLoaderBar' style='width:0%;'>"
+ " Loading: <span id='JSLoaderText'>0</span>%"
+ "</div>"
+ "</div>";
JSL.bar = document.getElementById('JSLoaderBar');
JSL.barText = document.getElementById('JSLoaderText');
},
update: function(fr) {
// shows the box to fraction fr (of 1)
if (JSL.bar === undefined) {
JSL.drawBox();
}
pc = Math.floor(fr * 100);
JSL.bar.style.width = pc + "%";
JSL.barText.innerHTML = pc;
},
timeUpdate: function(){
if (JSL.timerOn) {
JSL.currentTotal = JSL.currentTotal + JSL.rate;
if (JSL.currentTotal <= JSL.totalSize) {
JSL.update(JSL.currentTotal / JSL.totalSize);
setTimeout(JSL.timeUpdate, JSL.intrval);
}
}
}
};
return {
init: function(tS, divId){
// sets up everything
JSL.totalSize = tS;
JSL.div = document.getElementById(divId);
JSL.startTime = new Date();
JSL.rate = 1000;
JSL.timerOn = false;
JSL.currentTotal = 0; // the virtual amount of bytes
JSL.sizeSoFar = 0; // the actual amount of bytes
},
startFile: function(fName, fSize){
// call this before a file loads, after the previous file has finished
if (JSL.sizeSoFar == 0) {
JSL.update(0);
JSL.timerOn = true;
setTimeout(JSL.timeUpdate, JSL.intrval);
} else {
// timer is on,
// recalculate the 'rate' - ie the increment each intrval
var t = new Date();
var currRate = (JSL.sizeSoFar == 0) ? 0 : JSL.sizeSoFar / (t.getTime() - JSL.startTime.getTime());
var timeRemaining = (JSL.totalSize - JSL.sizeSoFar) / currRate;
if (timeRemaining != 0) {
JSL.rate = JSL.intrval * ((JSL.totalSize - JSL.currentTotal) / timeRemaining);
}
}
JSL.sizeSoFar += fSize;
},
complete: function(){
// call this to end it all!
JSL.timerOn = false;
JSL.update(1);
}
}
}();
</script>
<?php
} // eof first run JS
// calculate total file size
// changing file names to get rid of any ?thing=wotsit
$t = 0;
$fName = "";
foreach ($ar as $f) {
$f1 = preg_replace("/^(.*)\.js\??(.*)$/", '$1.js', $f);
$fName[$f] = $f1;
$t = $t + filesize($_SERVER['DOCUMENT_ROOT'] . $f1);
}
// call the start function
echo "<script type='text/javascript' language='JavaScript'>"
. "JSLoader.init('$t','$div',$width);"
. "</script>";
// load each file, and report in between
foreach ($ar as $f) {
// report
echo "<script type='text/javascript' language='JavaScript'>"
. "JSLoader.startFile('$f'," . filesize($_SERVER['DOCUMENT_ROOT'] . $fName[$f]) . ");"
. "</script>";
show_stamped_JS($f);
}
// complete it
echo "<script type='text/javascript' language='JavaScript'>"
. "JSLoader.complete();"
. "</script>";
}
?>
You might need to alter the path to the files - I've used $_SERVER['DOCUMENT_ROOT'] in this example, I actually use a 'defined' DOC_ROOT constant in my setup.
It needs some STYLE:
.JSLoader-barholder {
border: 1px solid blue;
background: #a50000 url(/images/listitem_red1_center.png) repeat-x;
height: 20px;
width: 100%;
}
.JSLoader-bar {
border: none;
background: green url(/images/listitem_green1_center.png) repeat-x;
height: 100%;
line-height:20px;
}
Obviously replace my images with your own, or just use colours.
to call the function, you need an array of js files, and a div with an id in which to load the progress bar. You CAN have scriptaculous?load=effects in there (it slightly messes up the rate calculation, but with lots of other JS files loading, that gets evened out).
Here's my example of calling the function, CALL THIS IN THE BODY of the document, not the HEAD, while your loading screen is showing:
<?php
// organise the loading of JS scripts...
$JSLoaderAr = array(
'/lib/prototype/prototype.js',
'/lib/scriptaculous/scriptaculous.js?load=effects'
);
for ($n = 1; $n < 12; $n++) {
$JSLoaderAr[] = "/lib/ext2/ext-divide/ext-d$n.js";
}
$JSLoaderAr[] = '/lib/functions.js';
$JSLoaderAr[] = '/lib/fields.js';
$JSLoaderAr[] = '/lib/main.js';
JSLoader($JSLoaderAr, "loadingSpace");
?>
And, for the record, here's how I call the CSS function in the head section of the html document:
<?php show_stamped_CSS("/lib/ext2/resources/css/ext-all.css"); ?>
It all works in FF3 and IE7, don't know about others - sorry!
Anyway - hope this works for you, and you find it helpful.
1. Long load times for JS files (around 700kb to load), users getting bored and not waiting...
2. Users' browsers not updating recent javascript and CSS, but using cached old versions - creating errors
This PHP function (below) helps to load Javascript files while showing a smooth-moving progress bar.
.js files are timestamped, based on their actual file modified time, so that the users' browser will always load the most recent file.
You can see it in action at www.oothanks.com
(http://www.oothanks.com)NB: In my set up I have divided my ext.js file (a full build, including prototype loader) into several (11) sections - simply by opening it in a textfile editor, and copying a page of lines at a time into new js files - named ext-d1.js to ext-d11.js. They load in order. I did this for a previous non-smooth version of this loader, and I still do it with this loader. I doubt that the rate calculation that smooths the progress bar would work well with a very large JS file. See the example below...
A function is also provided to load CSS files with timestamps.
Timestamps are hashed, because I felt like it.
Here's the PHP:
<?php
// JS and CSS with Timestamp #################################################
function show_stamped_JS($file) {
$t = filemtime($_SERVER['DOCUMENT_ROOT'] . preg_replace("/^(.*)\.js\??(.*)$/", '$1.js', $file));
$file = $file . ((strpos($file, ".js?") === false) ? "?" : "&");
echo "<script src='{$file}v=" . md5(date("YmdHis", $t)) . "' type='text/javascript' language='JavaScript'></script>";
}
function show_stamped_CSS($file) {
$t = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);
echo "<link rel='stylesheet' type='text/css' href='$file?v=" . md5(date("YmdHis", $t)) . "'></link>";
}
// JSLoader ##################################################################
function JSLoader($ar, $div) {
// $ar is an array of JS files to be loaded, JS Loader reports progress with a progress bar etc
// $div the id of the div where the progress will be reported
if (!defined('JSLOADER_JS_SET')) {
define('JSLOADER_JS_SET',true);
// load the JSloader javascript
?>
<!-- JSLoader Script -->
<script language='JavaScript' type='text/javascript'>
var JSLoader = function(){
// namespaced private variables & functions
var JSL = {
totalSize:0, sizeSoFar:0, currentTotal:0, div:"",
startTime:"", rate:0, timerOn:false, intrval:100,
drawBox: function(){
JSL.div.innerHTML = "<div class='JSLoader-barholder'>"
+ "<div class='JSLoader-bar' id='JSLoaderBar' style='width:0%;'>"
+ " Loading: <span id='JSLoaderText'>0</span>%"
+ "</div>"
+ "</div>";
JSL.bar = document.getElementById('JSLoaderBar');
JSL.barText = document.getElementById('JSLoaderText');
},
update: function(fr) {
// shows the box to fraction fr (of 1)
if (JSL.bar === undefined) {
JSL.drawBox();
}
pc = Math.floor(fr * 100);
JSL.bar.style.width = pc + "%";
JSL.barText.innerHTML = pc;
},
timeUpdate: function(){
if (JSL.timerOn) {
JSL.currentTotal = JSL.currentTotal + JSL.rate;
if (JSL.currentTotal <= JSL.totalSize) {
JSL.update(JSL.currentTotal / JSL.totalSize);
setTimeout(JSL.timeUpdate, JSL.intrval);
}
}
}
};
return {
init: function(tS, divId){
// sets up everything
JSL.totalSize = tS;
JSL.div = document.getElementById(divId);
JSL.startTime = new Date();
JSL.rate = 1000;
JSL.timerOn = false;
JSL.currentTotal = 0; // the virtual amount of bytes
JSL.sizeSoFar = 0; // the actual amount of bytes
},
startFile: function(fName, fSize){
// call this before a file loads, after the previous file has finished
if (JSL.sizeSoFar == 0) {
JSL.update(0);
JSL.timerOn = true;
setTimeout(JSL.timeUpdate, JSL.intrval);
} else {
// timer is on,
// recalculate the 'rate' - ie the increment each intrval
var t = new Date();
var currRate = (JSL.sizeSoFar == 0) ? 0 : JSL.sizeSoFar / (t.getTime() - JSL.startTime.getTime());
var timeRemaining = (JSL.totalSize - JSL.sizeSoFar) / currRate;
if (timeRemaining != 0) {
JSL.rate = JSL.intrval * ((JSL.totalSize - JSL.currentTotal) / timeRemaining);
}
}
JSL.sizeSoFar += fSize;
},
complete: function(){
// call this to end it all!
JSL.timerOn = false;
JSL.update(1);
}
}
}();
</script>
<?php
} // eof first run JS
// calculate total file size
// changing file names to get rid of any ?thing=wotsit
$t = 0;
$fName = "";
foreach ($ar as $f) {
$f1 = preg_replace("/^(.*)\.js\??(.*)$/", '$1.js', $f);
$fName[$f] = $f1;
$t = $t + filesize($_SERVER['DOCUMENT_ROOT'] . $f1);
}
// call the start function
echo "<script type='text/javascript' language='JavaScript'>"
. "JSLoader.init('$t','$div',$width);"
. "</script>";
// load each file, and report in between
foreach ($ar as $f) {
// report
echo "<script type='text/javascript' language='JavaScript'>"
. "JSLoader.startFile('$f'," . filesize($_SERVER['DOCUMENT_ROOT'] . $fName[$f]) . ");"
. "</script>";
show_stamped_JS($f);
}
// complete it
echo "<script type='text/javascript' language='JavaScript'>"
. "JSLoader.complete();"
. "</script>";
}
?>
You might need to alter the path to the files - I've used $_SERVER['DOCUMENT_ROOT'] in this example, I actually use a 'defined' DOC_ROOT constant in my setup.
It needs some STYLE:
.JSLoader-barholder {
border: 1px solid blue;
background: #a50000 url(/images/listitem_red1_center.png) repeat-x;
height: 20px;
width: 100%;
}
.JSLoader-bar {
border: none;
background: green url(/images/listitem_green1_center.png) repeat-x;
height: 100%;
line-height:20px;
}
Obviously replace my images with your own, or just use colours.
to call the function, you need an array of js files, and a div with an id in which to load the progress bar. You CAN have scriptaculous?load=effects in there (it slightly messes up the rate calculation, but with lots of other JS files loading, that gets evened out).
Here's my example of calling the function, CALL THIS IN THE BODY of the document, not the HEAD, while your loading screen is showing:
<?php
// organise the loading of JS scripts...
$JSLoaderAr = array(
'/lib/prototype/prototype.js',
'/lib/scriptaculous/scriptaculous.js?load=effects'
);
for ($n = 1; $n < 12; $n++) {
$JSLoaderAr[] = "/lib/ext2/ext-divide/ext-d$n.js";
}
$JSLoaderAr[] = '/lib/functions.js';
$JSLoaderAr[] = '/lib/fields.js';
$JSLoaderAr[] = '/lib/main.js';
JSLoader($JSLoaderAr, "loadingSpace");
?>
And, for the record, here's how I call the CSS function in the head section of the html document:
<?php show_stamped_CSS("/lib/ext2/resources/css/ext-all.css"); ?>
It all works in FF3 and IE7, don't know about others - sorry!
Anyway - hope this works for you, and you find it helpful.