A reader sent me a question earlier today about handling a slow process. He had a page where a particular CFC call was taking 2-3 seconds to run. He needed the output of the CFC to be in the middle of the page. He used cfflush above the call to at least present something to the user, but this wasn't optimal. They saw half a page load, then a delay, and finally the rest of the page. I suggested simply switching to an Ajax based solution and offered to create a quick demo so he could see it in action. As I'm an expert at writing slow code, I thought this would be a fun way to spend lunch. Let's start with the non-Ajax version first.
<h2>This is a test page</h2>
<p>
This is some content that is above the slow part. I'm going to use cfflush to expose the content earlier.
You should see it while CF then works on the slow part.
</p>
<cfflush>
<cfset ob = createObject("component","foo")>
<cfoutput>
<p>
#ob.doSomethingSlow()#
</p>
</cfoutput>
<p>
This is the end of the file. Nothing dynamic, nothing really interesting.
</p>
As you can see, the page contains a header and footer that is simple HTML. In the middle is a cfflush and then a call to my component. Let's take a look at the component.
<cfcomponent output="false">
<cffunction name="doSomethingSlow" output="false" returnType="any">
<cfset sleep(5000)>
<cfreturn "I'm down with the slow process. The result was foo.">
</cffunction>
</cfcomponent>
Yeah, I won't bother going over that. As you can see, it just uses the sleep function to pause for 5 seconds. (I was going to link to this demo. It works fine locally. Oddly it did not on production. I wonder if IIS7 is doing something to block the flush action? Actually it may be working - my connection seems slow enough today where the 5 seconds may be too short.) If you run this code you should see the first half of the page, a delay, and then the rest of the page.
Ok, so how can we switch this to an Ajax-based solution? You guys know I'm somewhat of a jQuery Fanboy now, but the simplest solution is to just use what's built in to ColdFusion 8. Consider:
<h2>This is a test page</h2>
<p>
This is some content that is above the slow part. I'm going to use cfflush to expose the content earlier.
You should see it while CF then works on the slow part.
</p>
<cfdiv bind="url:slow.cfm" />
<p>
This is the end of the file. Nothing dynamic, nothing really interesting.
</p>
I've gotten rid of the flush, and just moved the createObject/function call to slow.cfm. You can see this in action here.
Pretty simple? I couldn't not write a jQuery example, so here it is, one more time, with jQuery:
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$('#slowdiv').html('Loading...')
$('#slowdiv').load('slow.cfm')
})
</script>
</head>
<body>
<h2>This is a test page</h2>
<p>
This is some content that is above the slow part. I'm going to use cfflush to expose the content earlier.
You should see it while CF then works on the slow part.
</p>
<div id="slowdiv"></div>
<p>
This is the end of the file. Nothing dynamic, nothing really interesting.
</p>
</body>
</html>
Just a slight bit more complex. 4 or so lines more. Not bad though. Also, you have more control over the loading message. You can see this in action here.
Hope this helps!
Archived Comments
Thank you very much Ray...this is exactly what I needed. I really like the jQuery example. I think I will use that one to solve my issue.
Again...thank you.
Ha-ha, I just implimented the jQuery solution a couple of days ago. A query was taking 3 secs to return from the db so now the user experience is significantly better. 3 secs felt like an enternitity!
The only minor downside doing this to every slow page is it takes 2 server requests instead of 1, but it's a price well worth paying for.
Of course - and "Bad Ray" for not making this clear, stuff like this should ONLY be done after you are SURE there is nothing that can be done server-side.
@Ray,
You probably already know this but I'll say it anyway because you mentioned it in this post. I'm not a jQuery-er so for those of you who use the CFAjax stuff you too can control your loading message like so...
<script type="text/javascript">
_cf_loadingtexthtml="Loading...jQuery is NOT the only AJAX platform around - it just seems to be very popular - but I am not sold because dollar signs in code just remind me too much of qBasic";
</script>
@Andy. Most people love dollar signs, particularly followed by several digits. ;-) You don't have to use $ to use a jQuery function, you can change it to anything you like. Or simply replace it with the full object name "jquery".
If jQuery becomes too popular I'm just gonna have to find some other little known js framework to become a fan of. ;-)
"As I'm an expert at writing slow code"...
In addition to the knowledge, thanks for the morning chuckle.
We are still using CF7 so I have to go with the jQuery example (unless there is an easy way to do the Ajax sample in CF7).
Using the jQuery method, how would I pass a url variable to slow.cfm? Is it as simple as saying:
$('#slowdiv').load('slow.cfm?<cfoutput>#somevariable#</cfoutput>')
I have tried this but can't seem to get it to work.
@WF: Your URL isn't valid. If somevariable is 4, your URL is
slow.cfcm?4
Get it? You need to do
'slow.cfm?<cfoutput>x=#somevariable#</cfoutput>'
Of course, to be safe, you need to wrap somevariable in a urlEncodedFormat call.
What's nice is jQuery also lets you pass parameters in an object as the second argument to load:
.load('slow.cfm',{x:#somevariable#})
This will pass X as a form variable. You'd want to wrap that in jsStringFormat though.
sorry Ray, I left off the 'x=' when I did the post. My code actually does have the x=. Here is the exact jQuery header from my page called requestdetails.cfm:
<script>
$(document).ready(function() {
$('#moratdiv').html('Loading...')
$('#moratdiv').load('moratorium_check.cfm?<cfoutput>request_id=#url.request_id#</cfoutput>')
})
</script>
Here is the div code line:
<div id="moratdiv"></div>
In the moratorium_check.cfm page I have this (I actually have a cfc on this page but I just want to test if the url is being passed so I have this one line so I can see the output):
<cfoutput>#url.request_id#</cfoutput>
but on requestdetails.cfm the url.request_id value is not being displayed in the div.
jQUery may be switching to POST. Check the form scope. Firebug would tell you too.
Not sure what is wrong...but when I run your jQuery example on my local machine it works. However, when I run it in our prod environment it does not. It just displays the Loading... text and it never goes away.
My local machine is running CF8 but the prod machine is running CF7.
Are there any settings that I should check in our prod environment?
What does Firebug tell you?
I have never used Firebug...I just installed it for firefox but I am not sure what I am looking for.
You caaaaan do it @Ray - you must resist the urge to strike him down and instead you must convert him to the light side of the force!!!
The firebug is strong with this one!
@Willis: Firebug can be a bit overwhelming at first. I'm planning a video (have been for about 2 months, need ot find time) that focuses on just this issue.
Ok, so you should have a Firebug icon in the bottom right corner of your browser. Click on it, and the FB panel loads in the bottom. You may be asked to enable 3 things, net, console, and debugger I think. Just enable them all and the page reloads.
Click on the Net tab. Then click XHR. This will now show all your ajax requests. When you run the test, you should see the HTTP call, and probably, an error. You can then click on the event to see the response and the error.
Hope this helps
Got it. Interestingly Firebug says there is a 500 Internal Server Error when trying to GET slow.cfm. The file is there in the same folder, however it can't call it...hmmmm
This is only happening on our prod server (CF7) and not my local machine (CF8).
GOT IT. I dug a little deeper in firebug and its throwing an error in your cfc...apparently you are using the sleep function which is only available in CF8 and not CF7.
That's why it would not work in our prod environment but worked on my local machine.
And now you know why Firebug is the bee's knees. :)
If you really care, there is a Sleep UDF at CFLib I believe.
Just to set the record straight; it is official, I love you Ray!
I'm a little bit scared now, but thanks. ;)
@WF: Might want to take a look at this "Intro to Firebug" video: http://css-tricks.com/video...
Any web developer worth his salt will have this in his tool belt along with the Web Developer Firefox plugin.
Hi Ray, I must be doing something wrong. Copying your simple cfdiv example, I have:
(some text)
<cfdiv bind="url:slow.cfm" />
(some text)
and my slow.cfm looks like this:
<cfscript>
objWP = createObject("component", "com.mpi.mps.foo");
</cfscript>
<cfoutput>#objWP.doSomethingSlow()#</cfoutput>
My doSomethingSlow() is written the same as yours, but nothing is displaying in that cfdiv. Am I missing something in slow.cfm?
Not sure. Is this online where I can see? Your best bet is to look in the CHrome Dev Tools?
No, unfortunately - it's for an in-house app. Definitely nothing wrong with slow.cfm; I reduced it to nothing but a line of text and still got nothing in the cfdiv. Thanks, I'll check it out.
Figured it out - based on the js error I just noticed ('ColdFusion' is not defined), looks like there's no mapping to the CFIDE directory. Ref - http://forums.adobe.com/thr....
Cool, glad you got it.