Wow, that's a horribly complex title. Hopefully it makes sense. Anyway, here is the question from reader Doug:
You've covered using custom tags for layout in a couple posts. My struggle is with the head tags.
Say for example, I have 10 different forms in my app, all requiring different jQuery validation scripts. In what manner should I build the head portion of a template to only pull in the specific .js I need?
My current approach just doesn't seem as clean as it could be... I'm cfimporting in a directory of tags, and then including only the ones needed in my head custom tag:
<cf_head>
<js:jquery>
<js:validate>
<js:jqSubmitForm form="verticalForm">
<js:jqResetForm>
</cf_head>
It works, but each of the 10 form validation scripts is stored separately and then called just for the one form that needs it. I guess since the script truly is unique to that form, there's no way around it.
But figured I'd inquire... do you recommend a better approach?
So first off - let me begin by saying that my "custom tag for layout" approach is something I've generally moved away from. Not because I don't like the approach (I think 'wrapping' is one of the few places where custom tags make sense in a modern ColdFusion application), but because I'm generally using a framework that has it's own way of handling layout and page content. (As an aside, I am finding myself using the approach in CFBuilder extensions - but more on that later.) Despite that, there is still a need for this. You could simply include every library your site needs within your main template. But doing so adds unnecessary load and processing to your pages. Does it really matter? Probably not for 99% of our sites, but if we can easily manage what resources (JS frameworks, CSS files) get loaded in a template than there is no reason not too.
The approach I've taken is actually pretty similar to Doug's. I update my template to allow for flags that indicate which libraries to load. As an example, here is a snippet of code from Adobe Groups (built using ColdFusion 9 and Model-Glue).
<cfset loadmce = event.getValue("loadmce",false)>
<cfset loadrsvp = event.getValue("loadrsvp", false)>
For those who don't know Model-Glue, this code allows views to set a value, loadmce, or loadesvp, to indicate JavaScript libraries and code that need to be loaded. In this specific case one is for tinyMCE and one is for RSVP code. I felt that both code blocks were large enough that I did not want to include them in every request. In a custom tag approach, this could simply be:
<cfparam name="attributes.loadmce" default="false">
<cfparam name="attributes.loadrsvp" default="false">
I kept the flags/names a bit generic. I didn't want my views specifying a particular file to load, like tinymce.1.9.js. I felt that by keeping it generic it would both be easier to read and easier to update behind the scenes.
On another note - I've also made use of JavaScript in my views as well. As much as I'd like to keep everything with head blocks, I don't always particularly worry about it. If I know that I'm always loading jQuery for example, I've got no qualms about doing this in a view:
<script>
$(document).ready(function() {
stuff
});
</script>
JQuery has no issues with multiple blocks like this. I also feel it's better to keep code like this closer to the view. It's worth it to break it out of the head so I can see it closer to the rest of my page layout. (If that doesn't make sense, let me know.)
So - does this seem like a sensible approach? It's worked for me - but how about others?
Archived Comments
For performance reasons, it's best to avoid putting JavaScript inside the HEAD section because it prevents the page's contents from loading/rendering until all of those scripts have been downloaded. The best practice is to load JavaScript right before the end of your BODY tag.
I find that this usually makes it easier to dynamically load scripts depending on which view/template you're rendering as you can usually just put the necessary scripts at the end of your view/template, or set the necessary flags that are checked by some other code that renders the final "footer" portion of the page.
I'm sure someone will mention it, so I might as well...
Your last approach could be modified to use <cfhtmlhead>:
<cfsavecontent variable="js">
<script>
$(document).ready(function() {
stuff
});
</script>
</cfsavecontent>
<cfhtmlhead text="#js#" />
Which would automagically inject the script into the head. People tend to go bonkers about this, so YMMV... (personally i do it all the time).
Also - FWIW - I tend to include the core jQuery library on every single page in an app. It's so small that it's practically unnoticeable and it's just nice to know that it's always there when I need it.
@todd -Yes to including jQuery on every page, and no to cfhtmlhead. I'll use that tag when they force me to at gunpoint.
Okay, now I'm curious... what are the arguments against cfhtmlhead?
I will always disagree to not putting JS in the head. If your JS is so big that it slows your page load you really need to reevaluate your JS. Main problem with having JS be the last thing to load, if you have a decent sized form and all your form validation is in that JS if your JS validation isn't loaded a user will be able to click around, maybe submit the form, maybe breaking the page, or else screw up any sort of JS animations that you might use. Minimize, minimize, minimize.
@Ryan,
If you're relying on JS form validation and not doing server side validation you've got bigger problems to worry about than performance and JS code bloat.
There will always be exceptions to the rule, but it's still best practice in most cases: http://developer.yahoo.com/...
Of course it's not the sole form of validation, but avoiding the JS errors is always a personal preference.
@Ryan
You can keep your JS at the bottom and not have the user be able to submit the form until it is loaded by disabling the submit button in your html and then running a $('#submit').attr("disabled","") in your .ready().
@Ray - gunpoint... why? I love cfhtmlhead.
It just feels messy. I'd rather put stuff in the head myself.
I'm still trying different approaches on this front. I can't put my finger on why, but I just don't like using CFIMPORT and CFHTMLHEAD.
Anyways, my latest approach is to use a CT for layout that accepts a couple params like so:
<cf_layout title="#appname# Login"
head="css_common,css_tables,css_login,anticache,js_login">
The CT takes the head param and spins out corresponding CFMODULES within it:
<cfloop index="h" list="#attributes.head#" delimiters=",">
<cfmodule name="layout.#h#">
</cfloop>
What I find fascinating is how many different ways this issue can be solved. I suppose I should look into using something like FW/1 and not reinvent the wheel, but for now I'm having fun exploring options.