Today there was a thread on cf-talk about how to make a "Loading" or "Please Wait" style page while ColdFusion is doing something slow. Most of the answers talked about AJAX but I thought I'd show a simpler version that just used a bit of JavaScript.
First I'll create my loading message:
<p id="loading">
Please stand by while we do something.
<cfoutput>#repeatString(" ", 250)#</cfoutput>
</p>
Note that I gave an ID to my loading block. This will be used later. Also note the repeatString. Why do I have that? One of the "features" of IE is that it will not render any content until it gets "enough" content. I use this block of spaces simply to slap IE around and force it to render the content. My next line of code is a simple CFFLUSH:
<cfflush>
This is what tells ColdFusion to send the current output back to the browser. Now for the slow code. Obviously this will be custom for your application, but for my test I just used some Java:
<!--- slow process --->
<cfscript>
go_to = createObject("java", "java.lang.Thread");
go_to.sleep(3000); //sleep time in milliseconds
</cfscript>
You can find this code on the ColdFusion Cookbook entry.
Now I just need to clean up the loading text. I used this simple JavaScript:
<script language="javaScript">
loadingBlock = document.getElementById('loading');
loadingBlock.style.display='none';
</script>
And then I wrapped with a message to the user:
<p>
Thanks for waiting. Here is your important information.
</p>
Archived Comments
Very nice Ray. Do you have a piece of code using that anywhere?
Jay
No, because my code is never slow.
(Do you hear thunder?)
HAHAHA
I do not usually have the problem but I like the idea of a loading page sometimes. we pull huge quantities of data from databases and that can be slow at times so I appreciate the code.
Jay
I do something similar to this using a DIV tag and an animated GIF (a spinning hour glass). It gets the point across effectively because, unlike Ray, my code is dreadfully slow. I've found that the spinning hour glass actually hypnotizes the user into a completey passive state so they don't even notice the wait (I wish).
M. McConnell
Seriously - I don't remember where I've used that before. I'm sure I did - or something close to it in the past. Or maybe I didn't. :)
Mike would it be possible to get an example of it??
hour glass and all??
Jay
@Ray, - you can simplify the javascript into just one line:
<script language="javaScript">
document.getElementById('loading').style.display='none';
</script>
@jay
You can get some nice animated waiting style images from
http://www.ajaxload.info
Using one of those, your page could be this:
<span id="loading">
<img src="ajax-loader.gif" alt="please wait">
</span>
<cfflush>
page stuff here
<script language="javaScript">
document.getElementById('loading').style.display='none';
</script>
Haha! I love it Ray. I was thinking "Why would I ever want to have a please loading screen? If it takes that long maybe I should be finding a faster way to do it.". Then your comment of "No, because my code is never slow." made me die laughing.
I actually did something quite similar a couple years ago, moving and cropping a ton of images. I've actually noticed a bit of a problem with IE7 and <cfflush>, sometimes IE7 doesn't finish drawing the page. I haven't had the problem in other browsers, but it doesn't happen consistently so it might very well be the code.
Que serrar!
Ray, I think it might be very useful when exporting data from the database to spreadsheets. One of my clients here have this kind of requests very often. I was using some javascript and cfflush to do the job, but it wasn't as elegant as yours. Well done ;-)
We do something like that with our hotel reservation system. Searching through multiple hotels with multiple room types and date ranges can take some time.
Is there a good way to add a counter or timer to this to see how long the load is taking??
Jay
I just have a form display at the beginning, cfflush like you have, then inside my processing loop I use javascript and a flush to set a form field to whatever percentage my process is at. Makes long reports (when the user requests 3 years of data) at least appear to be doing something for the user. =)
we can add a javascript progress bar to the "loading" div:
<head>
<script language="JavaScript" src="jscripts/xp_progress.js"></script>
</head>
<p id="loading">Page Loading - Please Wait...</p>
<script type="text/javascript">
var bar1= createBar (300,15,'white',1,'black','blue',85,7,3,"");
</script>
<cflush>
<script type="text/javascript">
setTimeout("document.getElementById('loading').style.display = 'none'", 2000);
setTimeout("bar1.hideBar()", 2000);
</script>
You can find more about xp_progress.js here:
http://www.dynamicdrive.com...
That is pretty slick
When I redid our picture-heavy greyhounds site (greyhounds.swartzfager.org) this past summer, I used a similar technique to display "Loading page..." when the page first loaded, then created two JavaScript functions that would cause the text to fade in and out until the page finished loading (to indicate activity).
I long for the day when all of my family members have broadband. :)
I've used that in the past to send a bunch of script blocks, where each one increments a counter or sets the size of progress bar image. Dynamically generate the script block with the increment code, flush that, process a bit more, flush another script block, etc.
You could also use the onload event, which would kick in once the entire page was loaded.
<script language="javascript">
window.onload = function(){
document.getElementById('loading').style.display='none';
}
</script>
I would really like to either do a bar that progresses across as the page builds and/or have some time of timer showing how ling it has been. Just not sure how to do that.
What has been posted here has been great though.
Jay, see Tuyen's comment. If folks think it is worthwhile. I can make a simple example as well.
Believe it or not I did this myself as well... in Perl... in 93 or so. ;)
OK, Got the bar working and I like that. Now is there any way to do the timer type display to show how long the wait has been? Not a must but I have a few pages I want to track and this might be a good way to prove to some of the unbelievers I have here that say it takes longer than i know it does to pull data....
Jay,
Would using two GetTickCount functions to get the time it took to process the page do the trick? You could do it like this:
1. Put the first GetTickCount statement right after the <cfflush> and assign its value to a variable (say startTime).
2. Put the second GetTickCount statement at the end of your code and assign its value to a variable (endTime).
3. Subtract startTime from endTime and divide by 1000 to get the number of seconds it took to process the page.
4. Put a JavaScript block at the end of the code that writes that result to a <span> or <div> element at the top of the page (like “Load time: 83 seconds”).
Jay, all you need to do is run a JS function every second. You can do that with setTimeout(). Then you just update some text showing the # of seconds. Shouldn't be too hard.
OK that sounds good. I am not very good with JavaScript but I am ready to learn. I will try what you guys suggest.. Thanks Much
I don't believe the users want to know how long the page took to load, they want to know how much longer they have to wait until it finally does load! Since that is programatically impossible (depends on complexity of query and how busy your db is) it may be possible in certain circumstances to give them a rough indication of the percentage of completion.
Taking an easy example, if you have 4 queries to perform just CFLUSH some javascript after each CFQUERY to knock a visual counter on the page up by 25%. It will only be accurate if all queries take the same amount of time to process, so in reality the progress bar will show the percentage of CFML processed rather than a percentage of time to wait. If you only have 1 big query to do then obviously this idea won't help you.
If you can assume each load will take approximately same time you could save couple of previous page load times to application scope, take average of them and send that to JS functions that will show progress bar.
The way our data is I have to process one fairly large query and then in the loop of that I use the retrieved data to run many smaller queries so it can run hundreds of queries at a time. the average time for a page to run is about 10 - 15 seconds if you just watch and time it
or run a time and display it upon completion.
I have still not gotten the hang of making javascript show time as it loads so if anyone has a simple example it would be great.
Ray,
I'd like to inject a twist into this whole issue, not specifically related to displaying loading messages, but rather to the underlying issue of long-running processes, especially as applied to Model-Glue. I've tossed this out on the MG list a couple of times, but without much response.
So -- you have a Model-Glue event that sets off a long-running process. One that runs long enough that it could possibly run into the default CF timeout (and you don't want to reset the default timeout on the installation just for this one process).
Where in the sequence of MG actions would you/could you place a CFSETTING timeout value that would prevent the process from timing out? And how would you set up an event structure that would allow for a "loading" message of some sort, as well as for some sort of check to see if the process were complete. Then, of course, the results would need to be displayed.
I'm well aware that I just massively overcomplicated this whole blog entry. But since you've been working with Model-Glue a lot lately, I'm hoping you (or someone else listening) might be able to provide a little guidance.
I don't have proof - but I seem to remember that you could connect Sean's Concurrency library with Model-Glue, which would let you fire the events concurrently.
http://cfconcurrency.riafor...
I don't think you could do the "please wait" as we did though. You could tell the view to reload using JS, after like 3 seconds, and have the controllers check and see if the concurrent event is done yet.
Hi Ray,
Thanks. But unfortunately, the site in question is running CF Standard, so the Asynchronous Gateway isn't available.
How about something like this (not my idea, suggested by someone smarter):
The originating event simply contains two result statements in its event-handler tag. One kicks off the long-running process. The second simply returns a view, which could contain a loading message.
The first event goes on its merry way -- can you put a CFSETTING tag in a CFC method? -- and when it's complete, sets a session variable.
In the view produced from the second event, there's an iframe or a refresh, or an AJAX call, or something, which fires an event which checks the status of that session variable. If the process is complete, a result tag fires a final event to show the results.
This sounds pretty close. But I think I'm missing something. And of course, I've gone way far afield from the original subject.
I don't think that is going to work - as you won't see the view from the second result till the first one is also complete. Maybe consider using CFSCHEDULE to add an event?
What if the order of events were reversed, with the "loading" view firing first, followed by the event that starts the long process? Would they both execute then?
As far as the CFSCHEDULE suggestion goes: do you mean, use a CFSCHEDULE tag, scheduled to Now(), to call one of the events?
And -- one of the things I'm most confused about: where would I place a CFSETTING tag to avoid the long process from timing out? Can I simply place it in the method of the controller that's fired for the process, or perhaps in a global OnRequestStart method, applying the CFSETTING based on the name of the event?
I bring up the latter idea because in Mach-II, Peter Farrell has written a filter that can be applied to long-running events. If you apply the filter to an event and specify a timeout param, the CFSETTING appears to work throughout the event - or I guess, throughout the request. So I'm wondering if you could do much the same thing in MG,using an OnRequestStart method? Does any of that make sense?
In the MG context, no, you won't see view2 till view1 is done rendering.
Yes to your cfschedule thing.
CFSETTING: I'd place it in Application.cfm and make it global. You could also do it in the controller for the event in question.
Hi Ray,
Thanks for all your help.
As far as the sequence thing goes, what I was really asking (and I'll experiment to see the results on my own) was whether you could a) fire an event that simply announced two result; then b)the first resulting event would deliver some sort of "loading" view; and c) the second resulting event would actually call the long-running process. In that case, would MG render the loading view, then go on and start the process? If so, then it might be possible to check the progress of the event and deal with it.
Anyway, I'll play with it and see what happens. If I come up with anything useful, I'll share it.
Thanks again for your generous assistance.
What about when IIS compression is turned on? I read that will essentially flunk cfflush's ability to output data since IIS is holding onto the file/process until it's finished in order to compress it and send it to the browser. I have CF9 and IIS6 and can't get cfflush to work in even the most simple cases, and I have IIS compression turned on. Is there a way around? I guess doing something with threads and AJAX is probably the best way. Thanks.
Yes, I'd switch to an Ajax-based system. I've got a blog post on that here.
Ray,
I put this:
<span id="loading">
<img src="ajax-loader.gif" alt="please wait">
</span> <cfflush>
above all my long running queries to make it work. This puts it above my <html> tags. It seems to work just fine, but is this a bad idea?
Saw you at CFUnited last year. I hate that is is the last one. I can't come!
Browsers tend to be pretty forgiving, as you have seen. So this "works". Id consider moving it into the <body> block though. Not knowing the rest of your layout though I'm not sure if there is a reason you didn't want to in the first place.
Thanks Ray - I did move it into the body and all works perfectly in my test environment, but it seems I have the same problem you did above. It does not work in production (Windows 2008, CF8 Ent). Strange thing is I've got another page with CFFlush in production that works fine. I guess it has something to do with the amount of content and IIS, but the repeat function trick didn't work. Oh well, I guess I need to move to the AJAX or JSON solution.
I've seen oddities with cfflush myself lately as well. I agree though - an Ajax approach is definitely going to be better.
Ray, it is not working for me at all. All I see is the last line which is thanks for waiting line.
Well, first off, this post is a bit old. ;) Modern CF servers can just use the sleep() command. That being said, please note some browsers will not work with this. The whole repeatString is a hack to try to force it, but at the end of the day, it's up to the browser to decide when it will render.
Raymond, I tried your code, but did not work for me. So, I changed the code to render more white spaces. Then it worked.
Raymond,Thanks for your prompt reply.
Great to hear!