Returning Page Output Right Away with <cfflush>By default, the output of all ColdFusion templates is automatically buffered by the server, which means the HTML for the entire page is sent to the browser at once, after all processing has been completed. In general, this isn't a problem. In fact, it helps ColdFusion pull off a number of cool tricks internally. See the section "When You Can't Flush the Buffer," later in this chapter. That said, you will sometimes want ColdFusion to return the HTML it generates right away, while the template is executing. The browser can receive and display the HTML as it is generated, and meanwhile, ColdFusion can be finishing the rest of the template. The act of telling ColdFusion to send back the generated output right away is called clearing the page buffer. When to Clear the BufferYou might want to clear the page buffer in two basic situations:
In both of these situations, clearing the page buffer judiciously can make your applications appear to be more responsive because they give more feedback to the user sooner. NOTE Don't start clearing the page buffer regularly in all your ColdFusion templates, however. Instead, use it when you think there would be a problem for ColdFusion to send back a particular page all at once. In particular, don't place a <cfflush> tag in your Application.cfm file. See the section "When You Can't Flush the Buffer," later in this chapter. The Exception, Not the RuleMost ColdFusion pages don't fall into either of the categories we've discussed. That is, most of your application's templates won't generate tons and tons of HTML code, and most will execute normally in well under a second. So clearing the page buffer usually doesn't have much impact on the average ColdFusion template. And after the page buffer has been cleared, a number of features can no longer be used and will generate error messages (see the section "When You Can't Flush the Buffer," later in this chapter). In short, the capability to flush the page buffer is helpful for dealing with certain special situations, as outlined previously. Unless the template you are working on will produce a large amount of output or will take a long time to process, let ColdFusion buffer the page normally. Introducing the <cfflush> TagColdFusion provides a tag called <cfflush> that lets you clear the server's page buffer programmatically. As soon as ColdFusion encounters a <cfflush> tag, it sends anything the template has generated so far to the browser. If the browser is able to, it displays that content to the user while ColdFusion continues working on the template. The <cfflush> tag takes just one attributeintervalwhich is optional. You can use <cfflush> without interval; that simply causes the page buffer to be flushed at the moment the tag is encountered. If you provide a number to interval, ColdFusion continues to flush the page cache whenever that many bytes have been generated by your template. So, interval="1000" causes the page buffer to be cleared after every 1,000 bytes, which would usually mean after every 1,000th character. Flushing the Output Buffer for Large PagesLook at the Show All option in Listing 24.7, earlier in this chapter. Over time, hundreds or thousands of records could exist in the Expenses table, causing the Show All display to become extremely large. Therefore, it becomes a good candidate for the <cfflush> tag. For instance, near the top of Listing 24.7, you could change this code: <!--- Show all on page if ShowAll is passed in URL ---> <cfif URL.showAll> <cfset rowsPerPage = totalRows> </cfif> to this: <!--- Show all on page if ShowAll is passed in URL ---> <cfif URL.showAll> <cfset rowsPerPage = totalRows> <!--- Flush the page buffer after every 5,000 characters ---> <cfflush interval="5000"> </cfif> Now the page begins to be sent to the user's browser in 5,000-character chunks, instead of all at once. The result is that the page should display more quickly when the Show All option is used. Note, however, that the difference might not be particularly noticeable until the Expenses table gets quite large. Even then, the difference will likely be more noticeable for modem users. Flushing the Output Buffer for Long-Running ProcessesYou have seen how <cfflush> can help with templates that return large pages to the browser. You also can use <cfflush> to deal with situations in which a lengthy process needs to take place (such as verifying and charging a user's credit card or executing a particularly complex record-updating process). Simulating a Long-Running ProcessTo keep the examples in this chapter simple, the following code snippet is used to simulate some type of time-consuming process. Because this code should never be used in an actual, real-world application, it isn't explained in detail here. The basic idea is to create a <cfloop> that keeps looping over and over until a specified number of seconds have passed. For instance, this will force ColdFusion to spin its wheels for five seconds: <cfset initialTime = now()> <cfloop condition="dateDiff('s', initialTime, now()) lt 5"></cfloop>
See Appendix C for more information about the now() and dateDiff() functions. NOTE The previous code snippet is a very inefficient way to cause ColdFusion to pause for a specified amount of time, so don't use it in your own production code. It will cause ColdFusion to hog the CPU during the time period specified. It is used in this chapter only as a placeholder for whatever time-consuming process you might need to execute in your own templates. Displaying a Please-Wait Type of MessageThe FlushTest.cfm template shown in Listing 24.8 demonstrates how you can use the <cfflush> tag to output page content before and after a lengthy process. Here, the user is asked to wait while an order is processed. Listing 24.8. FlushTest.cfmDisplaying Messages<!--- Filename: FlushTest.cfm Created by: Nate Weiss (NMW) Purpose: Demonstrates use of <cfflush> for incremental page output ---> <html> <head><title><cfflush> Example</title></head> <body> <!--- Initial Message ---> <p><strong>Please Wait</strong><br> We are processing your order.<br> This process may take up to several minutes.<br> Please do not Reload or leave this page until the process is complete.<br> <!--- Flush the page output buffer ---> <!--- The above code is sent to the browser right now ---> <cfflush> <!--- Time-consuming process goes here ---> <!--- Here, ColdFusion is forced to wait for 5 seconds ---> <!--- Do not use this CFLOOP technique in actual code! ---> <cfset initialTime = now()> <cfloop condition="dateDiff('s', initialTime, now()) lt 5"></cfloop> <!--- Display "Success" message ---> <p><strong>Thank You.</strong><br> Your order has been processed.<br> </body> </html> As you can see, the code is very simple. First, a "please wait" message is displayed, using ordinary HTML tags. Then, the <cfflush> tag is used to flush the page buffer, which lets the user see the message immediately. Next, the time-consuming process is performed (you would replace the <cfloop> snippet with whatever is appropriate for your situation). The rest of the page can then be completed normally. If you visit this template with your browser, you should see the "please wait" message alone on the page at first. After about 5 seconds, the thank-you message will appear. This gives your application a more responsive feel. Displaying a Graphical Progress MeterWith the help of some simple JavaScript code, you can create a graphical progress meter while a particularly long process executes. The code in Listing 24.9 is similar to the previous listing, except that it assumes there are several steps in the time-consuming process that the template needs to accomplish. When the page first appears, it shows an image of a progress indicator that reads 0%. As each step of the lengthy process is completed, the image is updated so the indicator reads 25%, 50%, 75%, and finally 100%. Listing 24.9. FlushMeter.cfmDisplaying a Progress Meter<!--- Filename: FlushMeter.cfm Created by: Nate Weiss (NMW) Purpose: Diplays a progress meter as a lengthy task is completed ---> <html> <head><title><cfflush> Example</title></head> <body> <!--- Initial Message ---> <p><strong>Please Wait</strong><br> We are processing your order.<br> <!--- Create the "Meter" image object ---> <!--- Initially, it displays a blank GIF ---> <img name="Meter" src="/books/2/448/1/html/2/../images/PercentBlank.gif" width="200" height="16" alt="" border="0"> <!--- Flush the page buffer ---> <cfflush> <!--- Loop from 0 to 25 to 50 to 75 to 100 ---> <cfloop from="0" to="100" step="25" index="i"> <!--- Time-consuming process goes here ---> <!--- Here, ColdFusion waits for 5 seconds as an example ---> <!--- Do not use this technique in actual code! ---> <cfset initialTime = now()> <cfloop condition="dateDiff('s', initialTime, now()) lt 2"></cfloop> <!--- Change the SRC attribute of the Meter image ---> <cfoutput> <script language="JavaScript"> document.images["Meter"].src = '../images/Percent#i#.gif'; </script> </cfoutput> <cfflush> </cfloop> <!--- Display "Success" message ---> <p><strong>Thank You.</strong><br> Your order has been processed.<br> </body> </html> First, an ordinary <img> tag is used to put the progress indicator on the page. The image's src attribute is set to the PercentBlank.gif image, which is an empty, transparent (spacer) image that won't show up (except as empty space) on the page. The <cfflush> tag is used to ensure that the browser receives the <img> tag code and displays the placeholder image right away. Next, the <cfloop> tag is used to simulate some type of time-consuming, five-step process. Because of the step attribute, the value of i is 0 the first time through the loop, then 25, then 50, then 75, and then 100. Each time through the loop, a <script> tag is output that contains JavaScript code to change the src property of the meter <img>, which causes the meter effect. The buffer is flushed with <cfflush> after each <script> tag, so the browser can receive and execute the script right away. The first time through the loop, the <img> is set to display the Percent0.gif file, then Percent25.gif, and so on. The end result is a simple progress meter that helps users feel they are still connected during whatever time-consuming processes they initiated. Figure 24.5 shows what the meter looks like in a browser. Figure 24.5. The <CFFLUSH> tag enables you to display progress indicators during lengthy processes.NOTE There isn't room here to fully cover the use of an image's src property to swap the images it displays. For more information, consult a JavaScript reference book, or the scripting reference section under HTML Reference in ColdFusion Studio's online help. If the user's browser doesn't support JavaScript, the <img> tag will simply continue to display the PercentBlank.gif image (which the user won't notice because it is invisible). Flushing the Output Buffer Between Table RowsIn Listing 24.7, a Show All link was added to the next-n interface for browsing Orange Whip Studios' expenses. Depending on the number of rows in the Expenses table, that page could produce a lot of output. You might want to consider flushing the page output buffer after every few rows of data, so the user will start to see the rows while the page is being generated. Listing 24.10 shows how you can add a <cfflush> tag in the middle of an output loop, so that groups of rows are sent back to the browser right away. In this example, the rows are sent back in groups of five; in practice, you might want to choose a higher number of rows, such as 20 or 30. Listing 24.10. NextN5.cfmSending Content to the Browser<!--- Filename: NextN5.cfm Created by: Nate Weiss (NMW) Purpose: Displays Next N record-navigation interface Please Note Includes NextNIncludeBackNext.cfm and NextNIncludePageLinks ---> <!--- Retrieve expense records from database ---> <cfquery name="getExp" datasource="#APPLICATION.dataSource#"> SELECT f.FilmID, f.MovieTitle, e.Description, e.ExpenseAmount, e.ExpenseDate FROM Expenses e INNER JOIN Films f ON e.FilmID = f.FilmID ORDER BY e.ExpenseDate DESC </cfquery> <!--- Number of rows to display per Next/Back page ---> <cfset rowsPerPage = 10> <!--- What row to start at? Assume first by default ---> <cfparam name="URL.startRow" default="1" type="numeric"> <!--- Allow for Show All parameter in the URL ---> <cfparam name="URL.showAll" type="boolean" default="no"> <!--- We know the total number of rows from query ---> <cfset totalRows = getExp.recordCount> <!--- Show all on page if ShowAll passed in URL ---> <cfif URL.showAll> <cfset rowsPerPage = totalRows> </cfif> <!--- Last row is 10 rows past the starting row, or ---> <!--- total number of query rows, whichever is less ---> <cfset endRow = min(URL.startRow + rowsPerPage - 1, totalRows)> <!--- Next button goes to 1 past current end row ---> <cfset startRowNext = endRow + 1> <!--- Back button goes back N rows from start row ---> <cfset startRowBack = URL.startRow - rowsPerPage> <!--- Page Title ---> <html> <head><title>Expense Browser</title></head> <body> <cfoutput><h2>#APPLICATION.companyName# Expense Report</h2></cfoutput> <!--- simple style sheet for formatting ---> <style> th {font-family:sans-serif;font-size:smaller; background:navy;color:white} td {font-family:sans-serif;font-size:smaller} td.DataA {background:silver;color:black} td.DataB {background:lightgrey;color:black} </style> <table width="600" border="0" cellSpacing="0" cellPadding="1"> <!--- Row at top of table, above column headers ---> <tr> <td width="500" colSpan="3"> <!--- Message about which rows are being displayed ---> <cfoutput> Displaying <b>#URL.startRow#</b> to <b>#endRow#</b> of <b>#TotalRows#</b> Records<br> </cfoutput> </td> <td width="100" align="right"> <cfif not URL.showAll> <!--- Provide Next/Back links ---> <cfinclude template="NextNIncludeBackNext.cfm"> </cfif> </td> </tr> <!--- Row for column headers ---> <TR> <TH WIDTH="100">Date</TH> <TH WIDTH="250">Film</TH> <TH WIDTH="150">Expense</TH> <TH WIDTH="100">Amount</TH> </TR> <!--- For each query row that should be shown now ---> <cfloop query="getExp" startRow="#URL.startRow#" endRow="#endRow#"> <!--- Use class "DataA" or "DataB" for alternate rows ---> <cfset class = iif(getExp.currentRow mod 2 eq 0, "'DataA'", "'DataB'")> <!--- Actual data display ---> <cfoutput> <tr valign="baseline"> <td width="100">#lsDateFormat(ExpenseDate)#</td> <td width="250">#MovieTitle#</td> <td width="150"><i>#Description#</i></td> <td width="100">#lsCurrencyFormat(ExpenseAmount)#</td> </tr> </cfoutput> <!--- If showing all records, flush the page buffer after every 5th row ---> <cfif URL.showAll> <cfif getExp.currentRow mod 5 eq 0> <!--- End the current table ---> </table> <!--- Flush the page buffer ---> <cfflush> <!--- Start a new table ---> <table width="600" border="0" cellSpacing="0" cellPadding="1"> <!--- Simulate a time-intensive process ---> <cfset initialTime = now()> <cfloop condition="dateDiff('s', initialTime, now()) lt 1"></cfloop> </cfif> </cfif> </cfloop> <!--- Row at bottom of table, after rows of data ---> <tr> <td width="500" colSpan="3"> <cfif not URL.showAll and totalRows gt rowsPerPage> <!--- Shortcut links for "Pages" of search results ---> Page <cfinclude template="NextNIncludePageLinks.cfm"> <!--- Show All Link ---> <cfoutput> <a href="#CGI.script_name#?showAll=Yes">Show All</a> </cfoutput> </cfif> </td> <td width="100" align="right"> <cfif not URL.showAll> <!--- Provide Next/Back links ---> <cfinclude template="NextNIncludeBackNext.cfm"> </cfif> </td> </tr> </table> </body> </html> This code listing is mostly unchanged from an earlier version (see Listing 24.4). The only significant change is the addition of the <cfif> block at the end of the <cfloop> block. The code in this block executes only if the user has clicked the Show All link, and only if the current row number is evenly divisible by 5 (that is, every fifth row). If both of these conditions apply, the current <table> tag (the one opened near the top of the listing) is ended with a closing </table> tag. The page buffer is then flushed using <cfflush>, and a new table is started with an opening <table> tag that matches the one from the top of the listing. In other words, the expense records are shown as a series of five-row tables that are each sent to the browser individually, rather than as one long table that gets sent to the browser at once. Because each of these mini-tables is complete, with beginning and ending <table> tags, the browser can display them as it receives them (most browsers can't properly render a table until the closing </table> tag has been encountered). After the page buffer is cleared, this template waits for 1 second, using the same time-delay technique that was used in the progress meter example (see Listing 24.9). Again, never use this technique in your actual code templates. It is used here only as a simple way of causing ColdFusion to pause for a moment, so you can see the effect of the page flushes. If you visit Listing 24.10 with your Web browser and click the Show All link, you will see that the rows of data are presented to you in small groups, with a one-second pause between each group. This shows that the buffer is being cleared, and that a user accessing a very long page over a slow connection would at least be able to begin viewing records before the entire page had been received. NOTE Of course, in practice, you wouldn't have the time-delay loop at all. It's included here only to make the effect easier to see while developing. When You Can't Flush the BufferThis section has introduced the <cfflush> tag and pointed out several situations in which it can be helpful. However, because it causes the content your templates generate to be sent to the browser in pieces, rather than the whole page at once, certain ColdFusion tags and features that depend on being capable of manipulating the page as a whole can't be used after a <cfflush> tag. Restrictions on Cookie UseAfter the <cfflush> tag has been used on a page, you can no longer tell ColdFusion to set a cookie in the browser. This is because cookies are set by sending special HTTP headers to the browser, and all HTTP headers must be sent to the browser before any actual HTML content. So, after a <cfflush> tag has been used, you can't send additional headers, so it's too late for ColdFusion to set any cookies. If you really need to set a cookie after a <cfflush>, you can use JavaScript to do it. For your convenience, a custom tag called <cf_setCookieViaJS> has been included on the CD-ROM for this book. The custom tag supports three attributescookieName, cookieValue, and expireswhich correspond to the name, value, and expires attributes for the regular <cfcookie> tag. The expires attribute is optional. So, instead of <cfcookie name="MyCookie" value="My Value"> You would use <cf_setCookieViaJS cookieName="MyCookie" cookieValue="My Value"> Note that the cookie will be set only if the user's browser supports JavaScript and if JavaScript has not been disabled. NOTE This example custom tag is not supported. We are merely presenting it as a work-around for situations in which you must set a cookie after a <cfflush> tag. Whenever possible, set cookies using the usual <cfcookie> and <cfset> methods explained in Chapter 20. NOTE If you are familiar with JavaScript, you could study the SetCookieViaJS.cfm custom tag template (on the CD-ROM) as an example of how custom tags can be used to generate JavaScript code. NOTE The PATH, SECURE, and DOMAIN attributes from <cfcookie> aren't supported by this custom tag, but they could easily be added by editing the custom tag template. See Chapter 23 for information about building custom tags. Restrictions on <cflocation>After a <cfflush> tag has been encountered, you can no longer use the <cflocation> tag to redirect the user to another page. This is because <cflocation> works by sending a redirect header back to the browser. When the first <cfflush> tag is encountered on a page, the page's headers have already been sent to the browser, so it's too late to redirect the browser to another page using the usual methods provided by HTTP alone. There are two work-arounds to this problem. Both rely on the browser to interpret your document in a certain way, and they aren't part of the standard HTTP protocol. That said, these methods should work fine with most browsers. The first work-around is to include a <meta> tag in the document, with an HTTP-EQUIV attribute set to Refresh. Then, provide the URL for the next page in the CONTENT attribute, as shown below. Most browsers interpret this as an instruction to go to the specified page as soon as the tag is encountered. So, instead of this: <cflocation url="MyNextPage.cfm"> you would use this: <meta http-equiv="Refresh" content="0; URL=MyNextPage.cfm"> TIP If you want the redirect to occur after 5 seconds rather than right away, you could change the 0 in the previous snippet to 5. Consult an HTML reference for more information about this use of the <meta> tag. Another work-around is to use JavaScript. The following snippet could also be used in place of the <cflocation> shown previously. However, if JavaScript is disabled or not supported by the client, nothing will happen. See the scripting reference in Dreamweaver or HomeSite+ for more information about this use of the document.location object: <script language="JavaScript"> <!-- document.location.href ="MyNextPage.cfm"; //--> </script> Other RestrictionsSeveral other tags (and one function) can't be used after a <cfflush> tag has been encountered, for the same basic reasons the <cfcookie> and <cflocation> tags can't be used they all need to send special HTTP headers to the browser before your HTML code begins. These tags and functions can't be used after a <cfflush>:
|