ColdFusion Function: character counting textarea with limit

Here is a cute CF Function that generates a textarea with a counter and limiter. The default look and function are pretty decent, with 255 characters perfect for varchar.

Here is the Function:

<cffunction name="textarea">

    <cfargument name="name" default="textarea">
    <cfargument name="id" default="#name#">
    <cfargument name="maxlength" default="255">
    <cfargument name="value" default="">
    <cfargument name="cols" default="">
    <cfargument name="rows" default="7">
    <cfargument name="css_width" default="">
    <cfargument name="css_height" default="">
    <cfargument name="css_float" default="">
    <cfargument name="css_margin" default="0px">
    <cfargument name="readonly" default="0">
    <cfargument name="css_clear" default="">
    <cfargument name="loadbase" default="1">

    <cfoutput>
    <cfif arguments.loadbase eq 1>
        <script type="text/javascript">
            function textCounter(field, countfield, maxlimit) {
                if (field.value.length > maxlimit) {
                    field.value = field.value.substring(0, maxlimit);
                    document.getElementById(countfield).innerHTML = field.value.length;
                    alert('You have exceded your character limit');
                } else {
                    document.getElementById(countfield).innerHTML = field.value.length;
                }
            }
        </script>
    </cfif>
    <textarea cols="#cols#" rows="#rows#"
        style="<cfif css_float is not ''>float:#css_float#;</cfif> margin:#css_margin#; <cfif css_width is not ''>width:#css_width#;</cfif> <cfif css_height is not ''>height:#css_height#;</cfif>"
        wrap="physical" name="#name#" id="#id#"
        onKeyDown="textCounter(this.form.#name#,'counter#name#',#maxlength#);"
        onKeyUp="textCounter(this.form.#name#,'counter#name#',#maxlength#);" <cfif readonly is 1>readonly</cfif>>#value#</textarea>
    <div style="width:#css_width#; text-align:right; clear:#css_clear#;"><span id="counter#name#">#len(value)#</span> / #maxlength# chars</div>
    </cfoutput>

</cffunction>

And to invoke it for display in your form:

#session.utils.textarea(name="metadesc", maxlength="500", value=read.metadesc, css_width="100%")#

This assumes you loaded the CFC into a session variable called ‘utils’. The value is preloaded from a CFQuery called ‘read’.

And if you want two textareas on the same page? No problem. Just tell the second one not to load the javascript, with the ‘loadbase=0’ attribute:

		#session.utils.textarea(name="metadesc", maxlength="500", value=read.metadesc, css_width="100%")#

		#session.utils.textarea(name="metakeyw", maxlength="500", value=read.metakeyw, css_width="100%", loadbase=0)#

I see now after posting this that there is room for improvement in the javascript. Especially if I consider scriptaculous. So look for a version 2 in the coming days (or weeks).

ISAPI_Rewrite 3 – ignore existing files and directories

In a recent project I needed to use search engine friendly urls. ISAPI_Rewrite 3 was on the IIS Windows server so I thought I’d use that instead of the crazy 404 custom cfm trick.

And it did work. But it also mapped the url for directories that actually existed, which is no good, it makes those dirs useless. So I had to put in conditions and rules to bypass that. I struggled with this problem for over two hours. So many examples and solutions online did not work.

After much trial and error, here is the resulting .htaccess file:

RewriteEngine on
RewriteBase /

#  Block external access to the Helper ISAPI Extension
RewriteRule ^.*.isrwhlp$ / [NC,F,O]

# 301 Redirect all requests that don't contain a dot or trailing slash to include a trailing slash
RewriteCond %{REQUEST_URI} !/$
RewriteCond %{REQUEST_URI} !.
RewriteRule ^(.*) %{REQUEST_URI}/ [R=301,L]

# Rewrites urls in the form of /parent/child/child/
# but only rewrites if the requested URL is not a file or directory
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [S=3]
RewriteRule ^(.*)/(.*)/(.*)/$ /includes/cfm/sefurl.cfm?sec=$1&amp;top=$2&amp;pag=$3 [QSA]
RewriteRule ^(.*)/(.*)/$ /includes/cfm/sefurl.cfm?sec=$1&amp;top=$2 [QSA]
RewriteRule ^(.*)/$ /includes/cfm/sefurl.cfm?sec=$1 [QSA]

There might be a better way to do this, as this was my first experience with rewrite.

MySQL and ColdFusion Pagination – version 2

After writing Pagination with ColdFusion and MySQL I tightened up the script some. And with Hatem’s suggestions, made it better. So, here is the new version. It’s a different application, so the query is slightly different.

Hatem’s added/modified:

  • <cftransaction>
  • #iif(url.page is 1,DE(firstperpage),DE(allperpage))#

I added/modified:

  • <cfparam name=”variables.firstperpage” default=”#variables.allperpage#”>
  • <cfparam name=”url.where” default=”ImportDownloaded IS NULL”>
  • WHERE #preserveSingleQuotes(url.where)#
<!--- CURRENT PAGE --->
<cfparam name="url.page" default="1">
<!--- RECORDS PER PAGE (ON ALL PAGES) --->
<cfparam name="variables.allperpage" default="15">
<!--- RECORDS PER PAGE (ON FIRST PAGE) --->
<cfparam name="variables.firstperpage" default="#variables.allperpage#">
<!--- NUMBER OF PAGE LINKS AT A TIME --->
<cfparam name="variables.blocksof" default="10"><cfparam name="url.where" default="ImportDownloaded IS NULL">

<cftransaction>
	<cfquery name="orders" datasource="#session.datasource#">
	SELECT SQL_CALC_FOUND_ROWS orders.*, orderitems.itemname
	FROM orders LEFT JOIN orderItems ON orders.id = orderItems.orderid
	WHERE #preserveSingleQuotes(url.where)#
	ORDER by orders.datetime DESC, orderitems.itemname
	LIMIT #firstperpage+(allperpage*(page-1))-allperpage#, #iif(url.page is 1,DE(firstperpage),DE(allperpage))#
	</cfquery>
	<cfquery name="result_count" datasource="#session.datasource#">
	SELECT FOUND_ROWS() as howmany
	</cfquery>
</cftransaction>

<!--- 'TOP' IS THE LAST PAGE NUMBER LINK --->
<cfset top = blocksof * ceiling((url.page)/blocksof)>

<cfoutput>

<!--- IF WE'RE PAST THE FIRST BLOCK OF PAGES, SHOW THE PREV LINK --->
<cfif url.page gt blocksof>
	<a href="?page=#top-blocksof#&where=#url.where#">« Prev</a>
</cfif>

<!--- LOOP THROUGH THE PAGES IN THIS BLOCK --->
<cfloop from="#top-blocksof+1#" to="#top#" index="a">
	<cfif url.page is not a>
		<a href="?page=#a#&where=#url.where#">#a#</a>
	<cfelse>
	    <b>[#a#]</b>
	</cfif>
<!--- IF WE'RE ON THE LAST BLOCK AND DON'T HAVE ENOUGH RECORDS TO COMPLETE THE BLOCK,
	STOP CREATING THE LINKS FOR MORE PAGES, AND SET A FLAG TO NOT SHOW THE LINK FOR THE NEXT BLOCK --->
<cfif ((a-1) * allperpage) + firstperpage gte result_count.howmany>
	<cfset noNext=1>
	<cfbreak>
</cfif>
</cfloop>

<!--- IF WE RAN OUT OF RECORDS, DO NOT SHOW THE LINK FOR THE NEXT BLOCK OF PAGES --->
<cfif not isdefined("noNext")>
	<a href="?page=#top+1#&where=#url.where#">Next »</a>
</cfif>

</cfoutput>

Some pretty good stuff I’d say 🙂

Thanks Hatem.

Serving gzip compressed Scriptaculous and Prototype with ColdFusion

gzip compressed javascript libraries I love Scriptaculous, and mixing it with ColdFusion to create some very nice Rich Internet Applications. But at nearly 300KB, loading Prototype+Scriptaculous is kinda heavy. The workaround for that is shrinking the javascript libraries.

There are a few utilities around the net that will ‘compact’ or ‘shrink’ javascript. Variables are renamed, and all white space is removed – including comments and credits. Not too cool.

But here I’ll show you how to take it a step further and serve javascript compressed with coldFusion. I am able to get Prototype v1601 and Scriptaculous v181 download size down from 288KB to 42KB – with credits intact. ColdFusion 8 is not required – I believe CF5 or better will do (maybe even earlier!). This is a great way to serve gzip compressed content when not being able to control the web server and you don’t have CF8.

  1. Pack/shrink/compact all the javascript files into one. Make sure Prototype is at the top. That work has been done already, easily found on google. That’s how I got mine, it was 148KB. 2:1? Pretty darn good for no compression.
  2. I added credits for each library at the top of that file.
  3. Gzip the file.

So, great. We have a gzip compressed 42KB web 2.0 javascript. Thats almost 7:1 compression – not too shabby.  And with credits intact, thank you very much. Now we have to serve it as such. But you cannot just link to the gz in a javascript src attribute. Most browsers will not know what to do with it. So it has to be served smartly, and correctly. You need the compressed and uncompressed version both handy since older browsers cannot decompress GZ – in case someone tries to surf your site from 1997. And for the compressed version, you need to set the correct content-encoding and content-type headers.

Here is how to do it with ColdFusion. For this demonstration, the files are named:

  • prototype1601scriptaculous181.cfm
  • prototype1601scriptaculous181.js
  • prototype1601scriptaculous181.gz

In your javascript tag, make the src point to the CFM, such as

<script
   src="/includes/js/scriptaculous/prototype1601scriptaculous181.cfm"
   type="text/javascript"
   charset="ISO-8859-1"/>

It’s important that your charset is ISO-8859-1.

This is the contents of prototype1601scriptaculous181.cfm:

<cfif cgi.HTTP_ACCEPT_ENCODING contains "gzip">
<cfheader name="Content-Encoding" value="gzip">
<cfcontent
type="application/x-javascript"
deletefile="no"
file="#expandpath('./prototype1601scriptaculous181.gz')#">
<cfelse>
<cfinclude template="prototype1601scriptaculous181.js">
</cfif>

gzip compressed javascript librariesThis beauty first checks to make sure the browser can handle gzip.

  • If so, it sets the correct Content-Encoding for the file. With cfcontent it sends the compressed data to the browser as type application/x-javascript.
  • If not, it sends the uncompressed js data.

As you can see on the right, the credits are still in the compressed javascript when decompressed by the browser.

In my free time (HA) I’ll create and post PHP, ASP, Ruby, etc version as well. Or if someone else would like to do it I’d gladly post them here.

Other Resources:
http://compressorrater.thruhere.net/
http://shrinksafe.dojotoolkit.org/
http://www.bananascript.com/?home
http://blog.mohanjith.net/2008/03/recipe-for-compressed-scriptaculous.html
http://cfspaghetti.blogspot.com/2007/07/how-to-gzip-compress-coldfusion-8.html

Random SQL Server results – a one word command

I learned a great way to randomize SQL results.

In the past, I would have followed this routine:

Run a query pulling all of the results (tens? hundreds? thousands?).
Create an array.
Loop until 5 good random results are made:
Get 1 random query result.
If it’s not in the array, put it in.
If it is in the array, ignore it and try again.
Then I’d output the data from the array, rather than the query.

I suppose I could simply fill an array then randomize it. But that’s not really the point.

As you see, with this version we have to pull back so many more results than we’re actually going to use. That’s wasteful – especially if you want to pull back lots of data in each row.

So what’s this great super duper new way I’ve discovered to simplify code, only pull back the 5 results from SQL Server, and run fast? It’s really simple: tell SQL Server to do the work for you, with one word. No post parsing needed!

Just order the results by NewID(). Yeah, that’s it.

SELECT TOP 5
	id, name, description
FROM
	companies
WHERE
	active=1
ORDER BY
	NewId()

This query will only return 5 random results – even if there are 1000 companies that are active – and in random order.  So you can simply output your query like normal with your server language. Sweet.

Emergent Success Launches ES-Extranet, an Innovative Online Collaboration Tool

SOURCE: Emergent Success, Inc.

Sep 26, 2007 14:32 ET

MOUNTAIN VIEW, CA–(Marketwire – September 26, 2007 ) – Emergent Success Inc. today announced the
launch of ES-Extranet a proprietary web-based collaboration tool for use by
their clients, partners and consultants. Since the email "inbox" was not
designed as a collaborative work environment, this technological solution
creates the open space where discussion, calendaring, asset sharing and
other collaborative activities are easily practiced and navigated.

Emergent Success, Inc. is a collaborative consulting company that
facilitates dialogues to assist clients in solving their real-time
problems. Even though the preference is to do this work in vivo, the
addition of the ES-Extranet will allow anyone involved in a current
collaboration to participate in an asynchronous manner. The principals at
Emergent Success believe that in the same way that there is enormous value
in gathering people together for in-person dialogues, there is also
significant value to "virtual" dialogues. Principal Kevin Buck explains
"Experience has shown that to engage people with an online tool once you
have engaged them in person maximizes any collective effort — it is not an
either/or, but a both/and."

With an eye to collaborative integrity, Emergent Success engaged with David
Muro, designer, and Jules Gravinese, web developer, to co-create this new
tool. Each brought the best of their knowledge and experience to bear as
they developed the intuitive feel and ease of use of this online workspace.
Since collaborations are an iterative process, we look forward to the
ongoing learning for our company, consultants and clients.

About Emergent Success, Inc.

Emergent Success assists clients to solve their real-time issues by
liberating the collective wisdom, talent and energy from within their
organization for the emergence of strategic success. Its senior
consultants facilitate collaborative dialogues that create systemic
integration amongst the unintended silos present in most organizations.
The Company is headquartered in Mountain View, CA with consultants located
across the United States. www.emergentsuccess.com

Pagination with ColdFusion and MySQL

I find that web development with MySQL to be so much more pleasurable than with MS SQL Server. Here is a great example to prove my point. In another post I showed how to pull off pagination with MS SQL and ColdFusion. Lots of SQL code. And it required running the query twice. Once to count the entire result set, and another time for the results to display.
This version however, is very slim. It makes use of MySQL’s SQL_CALC_FOUND_ROWS, FOUND_ROWS(), and LIMIT.


SQL_CALC_FOUND_ROWS():

SQL_CALC_FOUND_ROWS and FOUND_ROWS() can be useful in situations when you want to restrict the number of rows that a query returns, but also determine the number of rows in the full result set without running the query again. An example is a Web script that presents a paged display containing links to the pages that show other sections of a search result. Using FOUND_ROWS() allows you to determine how many other pages are needed for the rest of the result.

FOUND_ROWS():

A SELECT statement may include a LIMIT clause to restrict the number of rows the server returns to the client. In some cases, it is desirable to know how many rows the statement would have returned without the LIMIT, but without running the statement again. To obtain this row count, include a SQL_CALC_FOUND_ROWS option in the SELECTFOUND_ROWS() afterward.

LIMIT:

The LIMIT clause can be used to constrain the number of rows returned by the SELECT statement. LIMIT takes one or two numeric arguments. With two arguments, the first argument specifies the offset of the first row to return, and the second specifies the maximum number of rows to return.

So let’s go take a look at this beauty…

<!--- CURRENT PAGE --->
<cfparam name="url.page" default="1">
<!--- RECORDS PER PAGE (ON ALL PAGES) --->
<cfparam name="variables.allperpage" default="6">
<!--- RECORDS PER PAGE (ON FIRST PAGE) --->
<cfparam name="variables.firstperpage" default="3">
<!--- NUMBER OF PAGE LINKS AT A TIME --->
<cfparam name="variables.blocksof" default="4">

<!--- WE RUN THE QUERY WITH SQL_CALC_FOUND_ROWS --->
<cfquery name="latest" datasource="MagLibrary">
SELECT SQL_CALC_FOUND_ROWS articles.title, articles.articleid,
	publications.title as pubtitle, publications.rank
FROM articles INNER JOIN publications ON
	articles.publicationID = publications.publicationID
WHERE articles.active = 1
ORDER BY publications.rank, pubtitle, articles.title, articles.articleid
<!--- ONLY RETURN THE AMOUNT OF RECORDS WE NEED FOR THIS PAGE --->
LIMIT #firstperpage+(allperpage*(page-1))-allperpage#,
<cfif url.page is 1>
	#firstperpage#
<cfelse>
	#allperpage#
</cfif>
</cfquery>

<!--- NOW WE QUERY HOW MANY ROWS WERE FOUND --->
<Cfquery name="articlecount" datasource="#session.datasource#">
SELECT FOUND_ROWS() AS articlecount
</cfquery>

<!--- 'TOP' IS THE LAST PAGE NUMBER LINK --->
<Cfset top = blocksof * ceiling((url.page)/blocksof)>

<cfoutput>

<!--- IF WE'RE PAST THE FIRST BLOCK OF PAGES, SHOW THE PREV LINK --->
<cfif url.page gt blocksof>
	<a href="?page=#top-blocksof#" class="fullstorylink" style="text-decoration:underline;">« Prev</a>
</cfif>

<!--- LOOP THROUGH THE PAGES IN THIS BLOCK --->
<cfloop from="#top-blocksof+1#" to="#top#" index="a">
<cfif url.page is not a>
	<a href="?page=#a#" class="fullstorylink" style="text-decoration:underline;">#a#</a>
<Cfelse>
	<b>[#a#]</b>
</cfif>
<!--- IF WE'RE ON THE LAST BLOCK AND DON'T HAVE ENOUGH RECORDS TO COMPLETE THE BLOCK,
	STOP CREATING THE LINKS FOR MORE PAGES, AND SET A FLAG TO NOT SHOW THE LINK FOR THE NEXT BLOCK --->
<cfif ((a-1) * allperpage) + firstperpage gte articlecount.articlecount>
	<Cfset noNext=1><cfbreak>
</cfif>
</cfloop>

<!--- IF WE RAN OUT OF RECORDS, DO NOT SHOW THE LINK FOR THE NEXT BLOCK OF PAGES --->
<cfif not isdefined("noNext")>
	<a href="?page=#top+1#" class="fullstorylink" style="text-decoration:underline;">Next »</a>
</cfif>

</cfoutput>

<!--- NOW IT'S A SIMPLE MATTER OF SHOWING THE RECORDS --->
<br><br>

<!--- WE'LL GROUP THE ARTICLES UNDER THEIR PUBLICATION --->
<cfoutput query="latest" group="pubtitle">
<h2>#pubtitle#</h2>
<cfoutput>
<a href="details.cfm?articleid=#articleid#">#title#</a>
</cfoutput>
</cfoutput>

For those of you counting lines, we went from 94 (MS SQL) down to 70 (MySQL). That’s 24 less lines of SQL code. Not too shabby. And just knowing your programming is more efficient is a good feeling =)

Internet Explorer 7 breaks CFInput validation

Redmond does it again. Thanks folks.

If you use CFinput validation on a text field with a style of display:none, you’ll run into problems with IE7.

Original Code:

<cfinput type="text" name="#attributes.name#"	required="#attributes.required#"
validate="#attributes.validate#"
message="#attributes.message#"	value="#attributes.value#"
style="display:none;">

After an invalid entry of a form field is made, ColdFusion displays the error message of your choice with generated JavaScript. Upon dismissing the alert, that JavaScript then places the system carat focus on the form field in question.

In a client’s form I had a hidden form field that would hold generated values. If that form field is blank upon submission, the page shows the validation error. And it works in Mac:Safari/Firefox and PC:IE5.5/IE6. What it does NOT work in is IE7. What happens is IE chokes on the fact that form field is hidden, so it freaks out about it and shows it’s own error, which stops your ColdFusion generated JavaScript, which contains the ‘return false’, which is supposed to stop the form from being processed.

IE7 breaks CFInput

Get the picture? Your form will be submitted even after validation fails. Nice.

Onto the fix
In order to not have CF give focus to this hidden form element, we have to change the default behavior of the JavaScript. So take out the message attribute, and replace it with onError, like so:

<cfinput type="text" name="#attributes.name#"
	required="#attributes.required#"
	validate="#attributes.validate#"
	onError="alert(unescape('#urlencodedformat(attributes.message)#')); return false;"
	value="#attributes.value#"
	style="display:none;"
>

Now if the field is invalid an alert will be shown, but no focus() command will get executed. The return false simply hualts the form submission.

The only downside to this is that you’ll get an alert box for each invalid item (only one at a time though), whereas CFInput’s default action puts them all together into one alert box.
Expanding on this, you can create a JavaScript to compile all error messages… but jeez… what’s the point of CFInput then?

IE. It’s almost as bad as Safari.

Referer Cloud

Using ColdFusion I look for the referring web page that sent a viewer to my site. Then I split that traffic up into two categories: 1) Searches 2) Links. If a similar search term was seen before, I incriment it’s weight. Same goes for domains of links. The result is a Referer cloud, very similar in look to a Tag Cloud:

Referer Cloud

But I find this MUCH more interested than a tag cloud…
This shows me what terms people are searching for in order to find me. Also lets me know who is linking to me (if the link was clicked on). I find it REALLY damn intersting and fun to watch. And since it’s sorted by the last hit time descending, I get up to the instant information.

Look at how weighted ‘php reference pdf’ is… and I only have two pages on my site that mention php. It just goes to show you how popular php is, and the need for the decent PDF reference that I posted.

And notice how many of those search term referrals are from Google (mouse over the terms for info). It shows how dominant they really are.

Comparison

I did a little poking around online and found other search/referer clouds. But they didn’t split the search terms out. And they counted www.domain.com as sepearate from domain.com, whereas my program knows to combine them as domain.com.

I’ve looked at the source code of other referer cloud programs… they are up in 300 lines of code, and use server log files (not real time). My program is only 150 lines of code and uses real time cgi variables and a MySQL database.

Others: Google only. Mine: All search engines.

Less code, more functionality, better performance. =)

Pagination with ColdFusion and MSSQL (faux MySQL’s LIMIT X,Y)

Pagination is something that has always been somewhat difficult for
me. Especially optimizing it for large sites. To make it even harder,
the customer wants the first page to show three items, and every
subsequent page to show ten. Not hard enough? OK, they want page number
links instead of simple left/right arrow links, such as:

1 2 3 [4] Next »

Still Easy?

  • Only show 4 page links at a time
  • When you click ‘next’, show the next block of 4 pages
  • When you click ‘prev’, show the previous block of 4 pages
  • If you are on the first block of links, do not show the ‘prev’ link
  • If you run out of records, only show the correct number of page links in that block
  • If you run out of records, do not show the ‘next’ link

Remember, page 1 has 3 items. Page 2 has 10. So the math gets kinda
tricky when you don’t want to do ‘what page are we on’ statements all
over the place.

Here is my solution. All you have to do is enter your records per
page (“allperpage”), number of page links at a time (“blocksof”), and
the number of items on the first page (“firstperpage”).

The SQL query has been optimized to get as close to MySQL’s ‘LIMIT’
function as possible. We’re doing an INNER JOIN here, and sorting on
columns from both tables. I haven’t seen any examples of this
optimization technique with an inner join, or even sorting on more than
one column. We pull back the max rows we’d need, then trim the top with
a reverse sort, finally un-reverse the sort for the web page. WHEW.

Onto the code…

In this example, we’re going to show page blocks of 4. The first page has 3 items. Every other page has 6. The example uses articles which are in publications.
It looks like a monster. But half of it is comments.

<!--- CURRENT PAGE --->
<cfparam name="url.page" default="1">
<!--- RECORDS PER PAGE (ON ALL PAGES) --->
<cfparam name="variables.allperpage" default="6">
<!--- RECORDS PER PAGE (ON FIRST PAGE) --->
<cfparam name="variables.firstperpage" default="3">
<!--- NUMBER OF PAGE LINKS AT A TIME --->
<cfparam name="variables.blocksof" default="4">

<!--- COUNT NUMBER OF RECORDS TOTAL --->
<cfquery name="articlecount" datasource="MagLibrary">
SELECT count(articles.articleid) as articlecount
FROM articles INNER JOIN publications ON articles.publicationID = publications.publicationID
WHERE articles.active = 1
</cfquery>

<!--- RETURN ONLY THE NUMBER OF RECORDS WE NEED --->
<!--- THIS IS AS CLOSE TO MYSQL'S LIMIT X,Y AS WE'RE GONNA GET --->
<cfquery name="latest" datasource="#session.datasource#">
<!--- PAGE 1 IS SIMPLE. SUBSEQUENT PAGES GET COMPLICTED SINCE THE FIRST PAGE HAS A DIFFERENT NUMBER OF RECORDS --->
<!--- THIRD, ORDER OUR RESULTS --->
SELECT *
FROM (
	<!--- SECOND, ONLY RETURN THE AMOUNT OF RECORDS WE NEED FOR THIS PAGE --->
	SELECT TOP
	<!--- THIS STEP ENSURES THE LAST PAGE HAS THE CORRECT AMOUNT OF RECORDS.
		IGNOREING THIS CHECK ALWAYS GIVES THE LAST PAGE THE #PERPAGE# RECORDS
		SO YOU'D END UP WITH SOME OF THE PREVIOS PAGE'S RECORDS --->
	<cfif articlecount.articlecount lt (firstperpage + (allperpage * (url.page-1)))>
		#articlecount.articlecount - (firstperpage + (allperpage * (url.page-2)))#
	<Cfelse>
		<cfif url.page is 1>
			#firstperpage#
		<cfelse>
			#allperpage#
		</cfif>
	</cfif> *
	FROM (
		<!--- FIRST, GET UP TO THE AMOUNT WE NEED WITH CORRECT SORTING --->
		SELECT TOP #firstperpage + (allperpage * (url.page-1))#
			articles.title, articles.articleid, publications.title as pubtitle, publications.rank
		FROM articles INNER JOIN publications ON articles.publicationID = publications.publicationID
		WHERE articles.active = 1
		<!--- OUR PREFERED ORDERING --->
		ORDER BY publications.rank, pubtitle, articles.title, articles.articleid
	) as t1
	<!--- REVERSE THE ORDERING SINCE WE'RE SELECTING THE TOP --->
	ORDER BY rank DESC, pubtitle DESC, title DESC, articleid DESC
) as t2
<!--- AND REORDER FOR DISPLAYING --->
ORDER BY rank, pubtitle, title, articleid
</cfquery>

<!--- 'TOP' IS THE LAST PAGE NUMBER LINK --->
<Cfset top = blocksof * ceiling((url.page+1)/blocksof)>

<cfoutput>

<!--- IF WE'RE PAST THE FIRST BLOCK OF PAGES, SHOW THE PREV LINK --->
<cfif url.page gt blocksof>
	<a href="?page=#top-blocksof#" class="fullstorylink" style="text-decoration:underline;">« Prev</a>
</cfif>

<!--- LOOP THROUGH THE PAGES IN THIS BLOCK --->
<cfloop from="#top-blocksof+1#" to="#top#" index="a">
<cfif url.page is not a>
	<a href="?page=#a#" class="fullstorylink" style="text-decoration:underline;">#a#</a>
<Cfelse>
	<b>[#a#]</b>
</cfif>
<!--- IF WE'RE ON THE LAST BLOCK AND DON'T HAVE ENOUGH RECORDS TO COMPLETE THE BLOCK,
	STOP CREATING THE LINKS FOR MORE PAGES, AND SET A FLAG TO NOT SHOW THE LINK FOR THE NEXT BLOCK --->
<cfif ((a-1) * allperpage) + firstperpage gte articlecount.articlecount>
	<Cfset noNext=1><cfbreak>
</cfif>
</cfloop>

<!--- IF WE RAN OUT OF RECORDS, DO NOT SHOW THE LINK FOR THE NEXT BLOCK OF PAGES --->
<cfif not isdefined("noNext")>
	<a href="?page=#top+1#" class="fullstorylink" style="text-decoration:underline;">Next »</a>
</cfif>

</cfoutput>

<!--- NOW IT'S A SIMPLE MATTER OF SHOWING THE RECORDS --->
<br><br>

<!--- WE'LL GROUP THE ARTICLES UNDER THEIR PUBLICATION--->
<cfoutput query="latest" group="pubtitle">
<h2>#pubtitle#</h2>
<cfoutput>
<a href="details.cfm?articleid=#articleid#">#title#</a>
</cfoutput>
</cfoutput>

Comments, suggestions, improvements are gladly welcomed.