Ask a Jedi: Example of onMissingTemplate to handle dynamic city/state pages

This post is more than 2 years old.

Dave asks:

I have a lot of city pages for my site similar to this folder structure: example.com/colorado/denver.cfm

What I have been doing is to create a template called template-city.cfm that gets included in to each city page such as denver.cfm. So basically denver.cfm is just a tiny shell file and the real guts of the page is located in the template-city.cfm, so the thought is that I only have to change the template-city.cfm once and all the others pages like denver.cfm get updated at once.

But in order to do this I had to create tons of city pages within each state (which I automated with CFfile) but a red flag is going off in my brain telling me this is the wrong way to do it. Is there some virtual way to create these city pages and even state folders?

There are two ways of doing this. The first would be with a server side URL rewriter. Apache has this built in, and you can find options for IIS as well. The URL rewriter would simply map example.com/louisiana/lafayette.cfm to example.com/dyncity.cfm?state=louisiana&city=lafayette.cfm.

If you don't have access to the web server and you are using ColdFusion 8, there is no reason not to use onMissingTemplate in Application.cfc. Here is a simple example:

<cffunction name="onMissingTemplate" access="public" returnType="boolean" output="true"> <cfargument name="pageRequested" type="string" required="true"> <cfset var city = listLast(arguments.pageRequested, "/")> <cfset var state = listGetAt(arguments.pageRequested, listLen(arguments.pageRequested,"/")-1, "/")> <cfset city = replaceNoCase(city, ".cfm", "")>
&lt;cfinclude template="dyncity.cfm"&gt;
&lt;cfreturn true&gt;

</cffunction>

Given a request of some.com/louisiana/lafayette.cfm, the value passed to pageRequested will be a string I can parse using list functions. I grab the city at the end, and the state at the second to last position. At that point, what you do is up to your model. You would probably call a CFC method that would translate a state/city string to a particular ID value of a city in the database. In my example I just include a file that displays it:

<cfoutput><h2>#city#, #state#</h2></cfoutput>

I've included the application as a zip to this blog entry. You should be able to extract it to your web server, and then hit any URL. The only drawback is that .cfm must be in the URL.

If you wanted to support example.com/louisiana/lafayette, then you would need to use the URL rewriters I mentioned above.

Download attached file.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Dave Dugdale posted on 12/9/2008 at 11:56 PM

Ray,

Thanks for answering my question!

I really like your idea of using the onMissingTemplate method.

I have my own dedicated IIS box (with CF8) so the URL re-writer idea gets really complicated since I am not very good at writing regular expressions.

Thanks again.

Comment 2 by Michael posted on 12/10/2008 at 12:41 AM

I actually do this method in another way using

www.example.com/c.cfm/State...

with c.cfm parsing out State and City off of it

The following function will get the State/City and then I can convert a List with the delimiter of / to an array and get the data off of this.

<cffunction name="getRHS">
<cfset path = cgi.PATH_INFO>
<cfset filename = cgi.script_name>

<cfif not findnocase(filename,path) >
<cfif len(path) gt 1>
<cfset path = right(path,len(path)-1)>
<cfelse>
<cfset path = " ">
</cfif>
<cfelse>
<cfif len(path) gt len(filename)>
<cfset path = right(path,len(path)-len(filename))>
<cfelse>
<cfset path = "">
</cfif>
</cfif>
<cfreturn path>
</cffunction>

Comment 3 by TJ Downes posted on 12/10/2008 at 12:47 AM

Nice post Ray!

Also, IIS7 has a built-in URL Rewrite. Finally.

Comment 4 by Dave Dugdale posted on 12/10/2008 at 3:20 AM

Ray,

I just added the files to my server and it worked perfectly.

Now I have to remember how the hierarchy works for the Application.cfc since i placed your files in a sub folder.

Comment 5 by ajm posted on 12/10/2008 at 4:15 AM

So is this a reasonable alternative to URL Rewrite? Could use use this to handle SES URLs on your site, say if you are on shared hosting?

Comment 6 by Raymond Camden posted on 12/10/2008 at 4:18 AM

Yes, if you are ok with .cfm being in the url someplace. If not, you need to get a rewriter.

Comment 7 by Matt Ondrey posted on 12/10/2008 at 6:22 AM

On my old IIS sites I remap the 404 pages to a template that handles these types of requests since at the time I didn't know about any rewrite options.

When the template can't find any relevant content it sends a 404 via cfheader statuscode so that the search engines don't think I'm running a spam doorway site.

Comment 8 by Steve Withington posted on 12/10/2008 at 10:48 AM

is this information being stored/pulled from any database on the back end? if so, why not simply use URL variables along the lines of "/index.cfm?state=louisiana&city=lafayette" which would still be pretty seo-friendly too.

Comment 9 by Anthony posted on 12/10/2008 at 2:00 PM

I wonder what the performance implications are of using onMissingTemplate over using URLRewrite? Speed, throughput, etc? Anyone out there able to speak to this?

Comment 10 by Raymond Camden posted on 12/10/2008 at 4:53 PM

@SW: Yes, the idea is that the city info is stored in the db, and you take the url params and look them up.

So sure, the non short version of the URL isn't so bad. I'm no SEO expert of course. I will say that it is less typing to do /state/city and it is something a non-techy would easier be able to remember.

Comment 11 by Beth posted on 12/10/2008 at 6:10 PM

What happens if there isn't a template for the city?

Comment 12 by Raymond Camden posted on 12/10/2008 at 6:16 PM

Well, now we are getting into something specific to his application, and less about the concept in general. The original author would have to say what to do. I'd guess though that you could simply forward to badcity.cfm, a page that says 'The city you wanted doesn't exist.'

Comment 13 by Dave Dugdale posted on 12/11/2008 at 11:21 AM

Ray,

For some reason I am having a tough time transferring my old Application.cfm to the new Application.cfc so I can use your cool onMissingTemplate idea for my URL structure.

The first thing I am trying to transfer over is the variables that are shared among all pages and all visitors of my site.

So I thought onApplicationStart function would be the best use for my global variables such as company name, but I can't get it to work.

Application.cfc file:

<cfcomponent output="false">

<cffunction name="onApplicationStart" returnType="boolean" output="true">
<cfset Application.companyname = "Joes Diner">

<cfreturn true>
</cffunction>

</cfcomponent>

Normal template:

<cfoutput> #Application.companyname#</cfoutput>

But I get this error when running the page:
Element COMPANYNAME is undefined in APPLICATION.

Sorry I am so confused on this, I have used CFC before in the past with much success.

Dave

Comment 14 by Raymond Camden posted on 12/11/2008 at 5:57 PM

You forgot to name your application. Use <cfthis.name="rayrocks"> (or somesuch) above the onApplicationStart method.

You should read the docs on Application.cfc. It will definitely help.

Comment 15 by Raymond Camden posted on 12/11/2008 at 6:02 PM
Comment 16 by Dave Dugdale posted on 12/12/2008 at 1:53 AM

Wow, that applicationTimeout had me all screwed up, I would keep changing variables in application.cfc thinking they would change instantly - but instead the hung on for 20 minutes! I have less hair now that I pulled most of it out.

For testing I set it to 5 seconds and now I see how it is working.

Question do I have to use the "Application" prefix on all my Application variables such as <cfset Application.coname = "Daves Diner"> or can I do it like <cfset coname = "Daves Diner">?

Comment 17 by Raymond Camden posted on 12/12/2008 at 1:54 AM

By definition, an Application scoped variable will be application.something.

Comment 18 by Dave Dugdale posted on 12/12/2008 at 2:06 AM

Ray thanks for the quick reply, that is what I thought, now I have to go and change a ton of variables using find replace.

I am using your standard application file that you pointed me to earlier and finding very useful.

Comment 19 by Eric Roberts posted on 9/24/2010 at 2:33 AM

Any ideas on using IIS7's rewite with a dynamic CF page? Wehave a 404 handler that has been hadling this...basically the user friendly url comes in, the 404 handler looks up the name in the db and returns an ID in a url that the site understands. For some reason, IIS7 doesn't seem to like this...it returns a 404.0 not found error(404, 404.2 and 404.3 are defined). So I came up with the idea of taking advantage of IIS7's rewrite. This seems to use the web.config file. Is there a way to dynmically do this with data from the DB? Thanks! I'll also post this on CF-talk ;-)

Comment 20 by Raymond Camden posted on 9/26/2010 at 12:04 AM

Sorry - I've never used IIS7's rewrite.