URL variables are a common way to pass information from one template to another. You see them all the time, in the form of http://www.foo.com/bar.cfm?boofar=farboo. In this example, the page bar will have access to the URL variable boofar whose value is farboo. In the real world, bar.cfm might be a category listing page on an ecommerce site, where the URL variable acts as a database parameter to determine which category to return.
Every time a page is requested, the web server returns a string containing the URL variables in the form of the querystring. In ColdFusion, you can access it by referencing the CGI scope like this: cgi.querystring Coldfusion also makes a Structure of all URL variables for you. This means you can get at the value of boofar by referencing url.boofar (it will return farboo in the example above).
Another bonus to having URL variables available as a Structure, is you can dump them all with a loop:
<cfoutput>
<cfloop collection="#url#" item="i">
<br />url name/value : #i# = #url[i]#
</cfloop>
</cfoutput>
The downside to URL variables is that they live short lives. By the time the web server has delivered the page to the browser, it has forgotten all about any URLs. For this reason, data that needs to be available from page to page in a site is usually set as a session variable, or in a cookie. Session variables either require cookies, or are tracked by URLs.
Newsflash : Cookies are Bad for You
It is amazing how interconnected the universe we live in is. Cookies are bad for you in real life, and they're bad for you on the internet. Yet people love to eat them, don't they. Web programmers are indulgent people. They know the cookies are bad, they know they shouldn't be stuffing them down people's throats....but....once you've had 2 or 3, well, you might as well have more, right?
WRONG!
I think it's downright rude to stuff your visitors pockets full of cookies for fizz-bang-bum-shwa-shwa like setting color preferences. Don't agree? Imagine if everytime you stepped into an elevator, everyone started secretly trying to put little notes in each others pockets. Whoa, are you happy to see me? Or is that a cookie in your pocket...
Everything will be all right though, because we don't need cookies. Not even one.
What if you could pick and choose which URL variables persisted, and which didn't? For instance, if you had a mechanism that allowed the user to select a stylesheet, you would want that stylesheet variable to stay alive from page to page. However, you will likely have other URL variables around that you would NOT want to persist. A page that displays categories for instance, may use a URL variable to determine which category to fetch and display from the database. You wouldn't want ?catid=5 to follow your visitors around endlessly, but you do want to remember that they like the "red snowflakes" stylesheet.
When Cookies are Good for You
There are of course times when cookies should be used. Client-server authentication is a perfect example.
We do have to protect people from pasting links to their financial portfolio in an IM for instance.
This is not good: "Look honey, here's a link to all our money along with our personal info! Isn't it great that the session infor is stored in the URL? You don't even have to login to see it! I sure hope nobody is snooping or monitoring this chat session!"
This is good: "Honey, please login to our financial site and see how much money we have. Use the login and password I whispered in your ear last night."
The Solution in a Nutshell
In Coldfusion version 5 and higher, the server creates a structure out of the URL scope. By simply declaring a list of the URLs that we want to persist, we can make our own token to append to links, forms, and redirects. The only downside to this technique is that you must be careful to include this token when building your site. The payout, however, makes it absolutely worth the hassle.
We'll also encounter times when we want to add the tokens, but not include some of our "sticky" variables. For instance, the form that changes this site's colors, uses a GET operation. Including all the URL variables as hidden fields would result in our new values being doubled. We'll get around this with a helper function.
Bare Bones Example
Put two templates in a ColdFusion powered web-accessible folder. Name one Application.cfm, and the other index.cfm. Insert this code into Application.cfm:
<!--- Application.cfm --->
<!--- param each sticky url --->
<cfparam name="url.cfid" default="">
<cfparam name="url.cftoken" default="">
<cfparam name="url.debug" default="">
<cfparam name="url.colordark" default="">
<cfparam name="url.colorlight" default="">
<cfparam name="url.bstyle" default="">
<cfscript>
app = StructNew();
app.stickyList = 'style,debug,colorlight,colordark,bstyle,foo,cfid,cftoken';
app.tokens = StructNew();
app.tokens['?'] = '';
app.tokens['&'] = '';
app.tokens['inputs'] = '';
app.stickyURLs = ArrayNew(1);
app.stickyURLs = ListToArray(app.stickyList);
for(i=1;i LTE ArrayLen(app.stickyURLs);i=i+1)
{
key = app.stickyURLs[i];
if(StructKeyExists(url,key) AND Len(url[key]))
{
if(Len(app.tokens['?']))
{
temptoken = '&';
} else {
temptoken = '?';
}
app.tokens['?'] = app.tokens['?'] & temptoken & LCase(key) & '=' & url[key];
app.tokens['&'] = app.tokens['&'] & '&' & LCase(key) & '=' & url[key];
app.tokens['inputs'] = app.tokens['inputs'] & '<input type="hidden" name="' & LCase(key) & '" value="' & url[key] & '">';
}
}
// SET ALL DEFAULT VALUES FOR STICKY URLs HERE
if(Len(url.colordark) EQ 0)
url.colordark = '990000';
if(Len(url.colorlight) EQ 0)
url.colorlight = 'eeeeee';
if(Len(url.bstyle) EQ 0)
url.bstyle = 'solid';
</cfscript>
Now, put this in the index.cfm file:
<!--- index.cfm --->
<!--- Bare-bones sticky URL example --->
<cfdump var="#app#">
Browse to index.cfm
Glue ?colordark=123456 onto the end of the URL in your address bar, and hit return. You should see your colordark value in the dump of app.tokens.
Append Your Tokens
Now we have created three keys in our app.tokens Structure, which will be available to all pages:
- app.tokens['?']
- app.tokens['&']
- app.tokens['inputs']
We will need to append our tokens to: links, forms, meta refreshes, cflocation tags, and (shame on you) javascript-generated links.
If you're playing along with your own templates, try pasting the following code blocks into index.cfm to see how they work. Once you have your page set-up, try adding and taking away URL variables in the address bar.
app.tokens['?']
This one is for addresses that have no other URL variables.
<cfoutput>
<a href="/code/#app.tokens['?']#" title="the code section">visit the code section</a>
</cfoutput>
app.tokens['&']
Remember our ?catid=5 example from above? If the link already has some URL variables, we want to use app.tokens['&'] at the end of the link.
<cfoutput>
<a href="/store/?catid=5#app.tokens['&']#" title="view category 5">category 5</a>
</cfoutput>
app.tokens['inputs']
The final key, app.tokens['inputs'] is for forms in GET mode. Notice it contains our sticky list formatted as hidden input fields.
For a form that doesn't change any sticky URLs (such as a site's search box):
<cfoutput>
<form action="#cgi.script_name#" method="get" name="search" id="search">
<fieldset>
<legend>Search Site</legend>
<label for="criteria">search for</label>
<input type="text" name="criteria" id="criteria" size="10" />
#app.tokens['inputs']#
<input type="submit" name="submit_search" value="go"/>
</fieldset>
</form>
</cfoutput>
Now we need to be careful with this one, especially in forms that change anything in our sticky list. For that, we need the helper function, getTokensMinusArg, coming up next:
getTokensMinusArg(token,dropme)
When honking our app.tokens['inputs'] value into a form that changes one or more of the URLs in the sticky list, we need to drop the changing variables from app.tokens['inputs'] temporarily. If we don't, the sticky value will be passed in the form twice. First, as the new value, and then again as the sticky value. The sticky value will take precedence, and no changes will occur.
<cfscript>
/*
function to return app.tokens, minus a token
arguments:
token ?,& or inputs
dropme = token to drop
*/
function getTokensMinusArg(token,dropme)
{
returnvalue='';
if(isArray(dropme))
dropme = ArrayToList(dropme);
for(i=1;i LTE ArrayLen(app.stickyURLs);i=i+1)
{
key = app.stickyURLs[i];
if(StructKeyExists(url,key) AND Len(url[key]) AND ListFind(dropme,key) EQ 0)
{
if(token EQ 'inputs') // hidden form fields
{
returnvalue = returnvalue & '<input type="hidden" name="' & key & '" value="' & url[key] & '">';
} else { // else its app.tokens['?'] or app.tokens['&']
delim = '&';
if(i EQ 1 AND token EQ '?') // if ['?'] start querystring with a question mark
{
delim = '?';
}
returnvalue = returnvalue & delim & LCase(key) & '=' & url[key];
} // close token EQ inputs
} // close if structkeyexists
} // close i loop return returnvalue;
} // end function
</cfscript>
You can include the function, or add it to your Application.cfm template.
Here now, is an example of how app.tokens['inputs'] is used with the getTokensMinusArg function in the colorpicker form. We will make a list of the two variables we want to temporarily suppress, called droplist, and pass it to the getTokensMinusArg function:
<cfset droplist = "colorlight,colordark">
<cfoutput>
<form action="#cgi.script_name#"method="get"name="choosecolors"id="choosecolors">
<fieldset>
<legend>Customize Site</legend>
<label for="colorlight">light color</label>
<input type="text" name="colorlight" id="colorlight" size="10" value="#url.colorlight#" /><br />
<label for="colordark">dark color</label><input type="text" name="colordark" id="colordark" size="10" value="#url.colordark#" />
<!--- write all the sticky urls as hidden form fields, EXCEPT for the ones in the "droplist" --->
#getTokensMinusArg('inputs',droplist)#
<input type="submit" name="changecolors" value="Reload" />
</fieldset>
</form>
</cfoutput>
Conclusion
You now have the power to make whichever variables you wish persist in the URL. Use this power wisely, and cut down on your cookie intake.
A parting word on querystrings.
So as not to confuse search spiders, be careful not to serve up pages by default that have identical content, but different querystrings. Search engines will generally discard such pages from their index. As I'm sure you don't want them to be so polite, an important fact to remember about spiders is that they do not submit forms. If you are setting URL variables for trivial things (like a site's colors), be sure to do so with a form, or some other non-spiderable means.
2 Comments
Betty wrote on 06/04/10 1:37 AM
thanks
maliby45 wrote on 05/15/09 12:01 AM
To me the benefit for us CFML developers is in the greater adoption (and the attendant increase in numbers of developers using the language) that Railo and OpenBD can bring to CFML as a language and community. For Adobe — http://rapid4me.com/?q=Adobe, I think the profit lies in Bolt (or whatever it will be called). Assuming a greater number of CF users (sites, developers, etc.), the need for a standard IDE increases (no knock on CFEclipse...I love and use it daily). I, personally, think this is where Adobe sees the dollars (see the opening of Flex as a server versus the incredible sales of Flex Builder since version 2 as a model).