I ran into a situation this week while working on a MachII project I’m doing with my brother that I’m sure many of you have encountered before. We have our view pages broken up into small chunks that we render inside a “pod-like” layout on the screen. Many of these view pages are using a one or more jQuery plugins (including cfUniform, DataTables and FancyBox) which need the main jQuery library to be included in the header to make them work correctly. We had been doing something like this in the top of each view that needed jQuery:

1
2
3
4
5
6
<cfsavecontent variable="jQueryInit">
     <script type="text/javascript" language="javascript"
         src="/javascript/jquery-1.3.2.min.js"></script>
</cfsavecontent>
 
<cfhtmlhead text="#jQueryInit#"></cfhtmlhead>

This worked perfectly fine for a while, right up until the point where we had two views in the same request that needed jQuery. If you put the above code in each of the view pages, then only some of your jQuery plugins work because the jQuery javascript file was getting loaded and instantiated multiple times–wiping out everything that had been configured previously.

The natural first “fix” for this problem was to only include the above code in the view that would be used first on the page. While that would work in theory, it seriously breaks the notion of encapsulation that we’re trying to achieve by separating all these views into individual files. Essentially, you could only ever use a view reliably on one page because you never knew if jQuery had been loaded or not.

I had started to contemplate building some kind of custom listener object and accompanying view to do be able to manage this problem when I remembered reading about the HTMLHelperProperty CFC included in MachII 1.8. After doing some reading, I realized that the solution to my problem was already built right into MachII.

The HTMLHelperProperty is designed to be a “holding place” of sorts for anything that you need to dynamically add to the section as you progress through the various steps configured by your for the requested event. It allows you to call different functions to add javascript files, css files, and meta properties to the “pile” of things that will be inserted into the section just before the request ends. For example, to include the jQuery library that we need from the above example, all you have to do is add this bit of code:

1
<cfset getProperty("html").addJavascript("jquery-1.3.2.min.js") />

The best part is, you can add that same line to EVERY view that needs jQuery and the HTMLHelperProperty CFC will only add it to the tag once. Yep, you read that right! It intelligently manages duplicate additions so you don’t wind up clobbering your javascript objects with multiple invocations like we talked about above.

Now, the HTMLHelperProperty CFC isn’t confined to doing javascript files. It also can be used to manage CSS files. Say we have a view that needs to use the DataTables and FancyBox plugins. Each of those plugins ships with a CSS file that it needs to make things look right. That’s no problem either, you can pass in a comma-delimited list of javascript and CSS files (or pass in an array of values as well) like this (extra line breaks added for readability–you’ll not want to use them in your code):

1
2
3
4
5
6
7
8
9
<cfset getProperty("html").addJavascript("
     jquery-1.3.2.min.js, 
     jquery.dataTables.min.js, 
     jquery.fancybox-1.2.1.js
     ") />
<cfset getProperty("html").addStylesheet("
     datatables.css, 
     jquery.fancybox.css
     ") />

The other use is to dynamically add tags to your document. These are pretty self-explanatory so I’m not going to go into them here, but refer you to the wiki page instead.

You may have noticed that there I’ve not included any file system paths to the javascript and CSS files in the examples above. When you configure the property in your MachII config file, you can set the default “home” directories for your javascript and CSS files. In the following example, the path to CSS files is set to “/css” and the path to javascript files is set to “/javascript”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- HTMLHelper Property -->
<property name="html" type="MachII.properties.HtmlHelperProperty">
    <parameters>
	<parameter name="metaTitleSuffix" value=" | MyCompany, Inc." />
	<parameter name="cacheAssetPaths" value="true" />
 
	<!-- Defaults to ExpandPath(".") -->
	<!-- <parameter name="webrootBasePath" value="/" /> -->
 
	<!-- Defaults to webroot base path + "/js" -->
	<parameter name="jsBasePath" value="/javascript" />
 
	<!-- Defaults to webroot base path + "/css" -->
	<parameter name="cssBasePath" value="/css" />
 
    </parameters>
</property>

Two quick notes on this block of code:

  1. You’ll notice that I’ve commented out the parameter named “webrootBasePath”. It defaults to the filesystem path equivalent to where your index.cfm page is running which is what I wanted, so I commented it out so as to use the default.
  2. You’ll also notice a parameter named “cacheAssetPaths”. There’s quite a bit behind this one, but the short version of the story is that, used correctly, this can help your visitors not have to download your javascript and css files every time (using them from the browser cache instead), while still forcing the browser to download the file if you have made any changes. Refer to the wiki page listed above for a complete description on this feature of the HTMLHelperProperty CFC.

One thing that the HTMLHelperProperty doesn’t do at the moment is allow you to store “ad-hoc” javascript code for inclusion in the section. This would be especially helpful in dealing with jQuery plugins as many of them need to be initialized in script once the document is loaded. For example, the following script is required to trigger the FancyBox plugin on an image named “preview”:

 $(document).ready(function() {
	$("#preview").fancybox();
});

It would be really nice to be able to do something like the code below to have your own custom scripts included in the header after the other javascript files are loaded:

1
2
3
4
5
6
7
<cfsavecontent variable="fancyBoxInit">
     $(document).ready(function() {
	$("#preview").fancybox();
      });
</cfsavecontent>
 
<cfset getProperty("html").addScript( fancyBoxInit, "javascript") />

The only issue I had with the property is that I got an error under under Railo 3.1.015 if I left the .js extension off of each javascript file I wanted to include. The wiki page examples say that you can just put the name of the javascript and css files (minus the extension) into the function, but I wasn’t able to get it to work unless I included the extensions. I’m still experimenting with this to see if it’s a bug in the property when running under Railo or if it’s something that I did.

All in all I’m very impressed with the feature and REALLY happy that I found it before tearing off and creating my own solution.

UPDATE: After looking through the code, I determined that I was getting an error when leaving off the extension of the files because my files had periods in their names other than between the filename and extension. I filed this bug and submitted a patch. The MachII team reviewed the patch, integrated it into the source and closed the ticket in the space of about 90 minutes. So, this bug has been resolved in the latest BER version in Subversion.

5 thoughts on “Using the new HTMLHelperProperty in MachII 1.8

  1. Great post Dan! Glad you like everything, let us know if the leaving off the .js extensions is a bug of ours. It works on Adobe CF8 and OpenBD just fine so I think there might be a little buglet in Railo maybe. For things that use multiple libraries of JS / CSS (one example is Lightwindow or Lightbox on Prototype that uses prototype / scriptaculous / css files) is to use the asset packages which you say all the files you need for that asset package as metadata package and use addAsset(“nameOfPackage”). Any js files you that have already been included in the head won’t be added twice. Also, it makes it easier because you change versions of a library (and the file name changes), you make the change in one place versus in multiple view files. This would be great for your example that uses jquery, datatables and fancybox.

    For ad-hoc js code, take a peek at the script tag in the View library:

    ad-hoc code here

    By default the script tag in the view custom tag library puts stuff in the HTML head area just like the HTMLHelper. There is also a “style” tag that does similar stuff. I don’t know your blog accepts links, but here is the link to the doco:
    http://greatbiztoolsllc.trac.cvsdude.com/mach-ii/wiki/MachII1.8SpecificationViewTagLib#ThescriptTag

  2. @Peter J. Farrell
    Thanks for the feedback Peter…always nice to know someone is reading this.

    I took a look at the script tag you mentioned. That’s a much nicer solution than having your scripts sprinkled through the body of the rendered HTML document and for someone that’s not using the HTMLHelperProperty in their application, that would be the way to go.

    However, I still feel like there would be some benefit to allowing the HTMLHelperProperty to do something similar, but combine all the ad-hoc scripts that are set into it through the request into one <script> tag in the <head> section of the HTML document.

    With the current M2 script view, you’ll wind up with separate <script> tags in the header for each time you call the tag. Some would say that this is nitpicky and I’d probably agree, but the functionality is so close to being there as it is. For those using the HTMLHelperProperty in their applications, it would be nice to do everything in one place rather than using 2 different mechanisms.

  3. Peter J. Farrell

    Dan, yeah we thought about combining all ad-hoc code into one script tag however it gets pretty complicated when you mix in M2 caching and subroutines. We weren’t ready to tackle this in 1.8 and something I’m sure we’ll think about for 1.9.

  4. Dan, FYI don’t use cfhtmlhead if you are ever going to use Mach-II caching as the cfhtmlhead tags won’t get replayed when an item is gotten from the cache. Use the addHtmlHeadElement() method available in the ViewContext to add an element to the head. The CacheHandler uses this as an observer point so they can get replayed when using cached data. We do the same for cfheader tag — use addHTTPHeader() method instead. Probably should highlight this more in the docs.

  5. Thanks Peter…I’ll keep that in mind. We’re not using any caching at the moment but that will be something good to remember when we get to that point.

Leave a Reply

Your email address will not be published. Required fields are marked *

*