iFinity Blogs 

Oct 25

Written by: Bruce Chapman
Sunday, October 25, 2009 3:33 PM 

Count me amongst the legions of jQuery fans.  I might be a bit later to the party than others, but to my defence I really like writing server based code, so I do as much of that as possible.   It’s only when faced with the need to ship code that the UI starts getting the attention it deserves.

Adding jQuery into DNN is an obvious choice, although made somewhat problematic with the standard Prototype library inclusion, and the fact that DNN modules are built with ASP User Controls rather than pages.   You’ll see plenty of advice on the internet to either use the built-in DNN 5 jQuery inclusion method, or to just add the includes into the page where your jQuery code is.   The first is problematic if you’re building for a broad range of DNN versions, the second is problematic if you want to distribute your code without having a long list of post-install steps to get it working. Another issue is that DNN modules can (and will) be copied to the page more than once, so your code has to be careful of including some sort of duplication check, so that you don't end up with 5 references to the jQuery library next time someone gets a little click-happy with the 'Add Module' button.

With that background, I set about developing a small routine which can be used with any DNN Version, which would provide version-independent, one-line inclusion of the jQuery libaries.

Programmatically Including JavaScript and CSS files in ASP.NET

The first thing to solve in this task is a simple inclusion of javascript includes in the header of the page.  This is one of those things that you would think is really easy in ASP.NET, but unfortunately it can be a bit of a trick for new players.  Most people start looking around the ClientScriptManager object, but in my opinion it's overblown and messy.  You'll often see people using the ClientScriptManager to do the duplication check by adding in an empty bit of javascript - a brutal way to save effort. The easiest (and cleanest) way to do this is to create a specific html tag, and then actually add that to the header file, like this:

      HtmlGenericControl scriptInclude = (HtmlGenericControl)page.Header.FindControl(id);
      if (scriptInclude == null)
      {
           scriptInclude = new HtmlGenericControl("script");
           scriptInclude.Attributes.Add("src", src);
           scriptInclude.Attributes.Add("type", "text/javascript");
           scriptInclude.ID = id;
           page.Header.Controls.Add(scriptInclude);
      }

This code looks for a specific html script include tag in the page header, and if not found, creates the tag and adds it in. Couldn't be simpler really - and no messy, empty <script/> tags lying around in your Html.

Determining the DotNetNuke Version

Why determine the DotNetNuke version? Well, because if you're using DNN 5 and better, then DNN has a built in function for including the jQuery libraries. No need to reinvent the wheel, so if this function is around, might as well use it. However, if you're like me and building modules for a massive variety in DNN versions, there's no telling if the DNN 5 method will exist or not. So you need to determine the DotNetNuke version.

You might head for the object browser and start looking for the version method or property. That's where more young players will dash themselves upon the rocky shores of DNN history. You see, somewhere along the line (about 4.9, I believe) the location of the version property actually moved within the object hierarchy. Thus, in order to determine the version, you first have to know the version to know where to look for it. Trying to use this circular logic will only lead to tears and torn-out tufts of hair.

The only avenue left is to bypass all the built-in methods and just go straight for the assembly version. That's always in the right spot, and thankfully, always correct. Here's my version-independent version checking code:

     System.Version ver = System.Reflection.Assembly.GetAssembly(typeof(DotNetNuke.Common.Globals))
			.GetName().Version;
     if (ver != null)
     {
         major = ver.Major;
         minor = ver.Minor;
         build = ver.Build;
         revision = ver.Revision;
         return true;
     }
     else
     {
         major = 0; minor = 0; build = 0; revision = 0;
         return false;
     }

Incidentally, that method is so version-safe it can be used on any asp.net project, not just DNN. Incidentally, just because you've determined the version, you're not out of the woods yet. Because you can't just compile against a method in a later version library and expect it to all work with an earlier version. No, instead we must fall back to the all-purpose tool of the .NET developers toolbox : reflection:

     Type jQueryType = Type.GetType("DotNetNuke.Framework.jQuery, DotNetNuke");
     if (jQueryType != null)
     {
         //run the DNN 5.0 specific jQuery registration code
        jQueryType.InvokeMember("RequestRegistration", 
                     System.Reflection.BindingFlags.Static, null, jQueryType, null);
     }

This code will run the DNN jQuery 'RequestRegistration' method if it's available (ie, installed in a DNN 5 environment), but will still compile against earlier versions of DNN libraries. For more information on this call, see Joe Brinkman's informative post on jQuery and DotNetNuke 5

Including the jQuery Libaries

So, equipped with code to work out what version we're using, and armed with code to insert in a javascript include file, there's nothing left to do but stitch it all together. Just one last thing : Google, in their infinite bandwidth and hey-we'll-get-your-data generosity, provide a hosted version of the jQuery libraries. I've long ago given up on trying to hide anything from the big G, so I'm happy to just mooch off their site and use the hosted version. Thus the example code contains references to the Google libraries. If you're of a more suspicious and distrusting nature, then by all means include the jQuery library with your app and change the code.

Full Code Listing

	/// <summary>
        /// Includes the jQuery libraries onto the page
        /// </summary>
        /// <param name="page" />Page object from calling page/control</param>
        /// <param name="includejQueryUI" />if true, includes the jQuery UI libraries</param>
        /// <param name="uncompressed" />if true, includes the uncompressed libraries</param>
	/// <param name="includeNoConflict" />if true, includes the uncompressed libraries</param>
        internal static void InjectjQueryLibary(System.Web.UI.Page page, bool includejQueryUI, 
						bool uncompressed, bool includeNoConflict)
        {
            int major, minor, build, revision;
            bool injectLib = false;
            if (SafeDNNVersion(out major, out minor, out revision, out build))
            {
                switch (major)
                {
                    case 5: 
                        //todo: all versions of 5?
                        injectLib = false;
                        break;
                    default:
                        injectLib = true;
                        break;
                }
            }
            else
                injectLib = true;

            if (injectLib)
            {
                //no in-built jQuery libraries into the framework, so include the google version
                string lib = null;
                if (uncompressed)
                    lib = "http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js";
                else
                    lib = "http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js";

                if (page.Header.FindControl("jquery") == null)
                {
                    System.Web.UI.HtmlControls.HtmlGenericControl jQueryLib 
				= new System.Web.UI.HtmlControls.HtmlGenericControl("script");
                    jQueryLib.Attributes.Add("src", lib);
                    jQueryLib.Attributes.Add("type", "text/javascript");
                    jQueryLib.ID = "jquery";
                    page.Header.Controls.Add(jQueryLib);

                    if (includeNoConflict)
                    {
                        // use the noConflict (stops use of $) due to the use of prototype 
			// with a standard DNN distro
                        System.Web.UI.HtmlControls.HtmlGenericControl noConflictScript = 
				new System.Web.UI.HtmlControls.HtmlGenericControl("script");
                        noConflictScript.InnerText = " jQuery.noConflict(); ";
                        noConflictScript.Attributes.Add("type", "text/javascript");
                        page.Header.Controls.Add(noConflictScript);
                    }
                }
            }
            else
            {
                //call DotNetNuke.Framework.jQuery.RequestRegistration();
                Type jQueryType = Type.GetType("DotNetNuke.Framework.jQuery, DotNetNuke");
                if (jQueryType != null)
                {
                    //run the DNN 5.0 specific jQuery registration code
                    jQueryType.InvokeMember("RequestRegistration", 
                            System.Reflection.BindingFlags.Static, null, jQueryType, null);
                }
            }

            //include the UI libraries??
            if (includejQueryUI)
            {
                string lib = null;
                if (uncompressed)
                    lib = "http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.js";
                    
                else
                    lib = "http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js";
                page.ClientScript.RegisterClientScriptInclude("jqueryUI", lib);
            }
        }
        /// <summary>
        /// Helper function to insert a given javascript file into the page header
        /// </summary>
        /// <param name="page" />Current Page object</param>
        /// <param name="id" />Unique (for the page) id of the file</param>
        /// <param name="src" />Qualified location of the script file</param>
        internal static void IncludeScriptFile(Page page, string id, string src)
        {
            HtmlGenericControl scriptInclude = (HtmlGenericControl)page.Header.FindControl(id);
            if (scriptInclude == null)
            {
                scriptInclude = new HtmlGenericControl("script");
                scriptInclude.Attributes.Add("src", src);
                scriptInclude.Attributes.Add("type", "text/javascript");
                scriptInclude.ID = id;
                page.Header.Controls.Add(scriptInclude);
            }
        }
        /// <summary>
        /// Helper function to insert a given CSS file into the page header
        /// </summary>
        /// <param name="page" />Current Page Object</param>
        /// <param name="id" />Unique (for the page) id of the file</param>
        /// <param name="href" />Qualified location of the CSS file</param>
        internal static void IncludeCSSFile(Page page, string id,string href)
        {
            HtmlLink cssLink = (HtmlLink)page.Header.FindControl(id);
            if (cssLink == null)
            {
                cssLink = new HtmlLink();
                cssLink.Href = href;
                cssLink.Attributes.Add("rel", "stylesheet");
                cssLink.Attributes.Add("type", "text/css");
                cssLink.ID = id;
                page.Header.Controls.Add(cssLink);
            }
        }
        /// <summary>
        /// Returns a version-safe set of version numbers for DNN
        /// </summary>
        /// <param name="major" />out value of major version (ie, 4,5 etc)</param>
        /// <param name="minor" />out value of minor version (ie 1 in 5.1 etc)</param>
        /// <param name="revision" />out value of revision (ie 3 in 5.1.3)</param>
        /// <param name="build" />out value of build number</param>
        /// <remarks>Dnn moved the version number during about the 4.9 version, 
        /// which to me was a bit frustrating and caused the need for this reflection method call</remarks>
        /// <returns>true if successful, false if not</returns>
        internal static bool SafeDNNVersion(out int major, out int minor, out int revision, out int build)
        {
            System.Version ver = System.Reflection.Assembly.GetAssembly(
			typeof(DotNetNuke.Common.Globals)).GetName().Version;
            if (ver != null)
            {
                major = ver.Major;
                minor = ver.Minor;
                build = ver.Build;
                revision = ver.Revision;
                return true;
            }
            else
            {
                major = 0; minor = 0; build = 0; revision = 0;
                return false;
            }
        }

Code Licensing on this snippet

You're free to take the code on this page and use it how you will, commercial or non-commercial, attributed or not. By all means link back and admit where you pinched it from, or you can try and be sneaky and pass it off as your own work to appear productive to your boss. It's not going to worry me. Just don't try and get me to fix your site if something is wrong!  Oh, and as always, please don’t ask for a VB version : there are oodles of converters out there.

Using the jQuery Include Code in a DNN Module

You’ll see that I have included a couple of options in the main method.  These are:

- includejQueryUI : this is for including the standard jQueryUI library from google.  Note that this occurs even if you’re using DNN 5 or better.

- uncompressed: when set to true, the jQuery code will be downloaded in the uncompressed format.  If ‘false’, then the jQuery code will be downloaded in ‘minified’ format.  Minified decreases the download payload and speeds up page load time, but you wouldn’t try and debug it.

- includeNoConflict : when set to true, the jQuery.noConflict()  call will be included.  This can be used to stop the ‘$’ namespace clash between the prototype code library in DNN and jQuery.   Personally, I just write all my jQuery code using jQuery(‘’) instead of $(‘’)

Here’s how you would use this code.  Let’s assume you keep it in a static class called ‘IncludeFunctions’.  In the Page_Load event of your DNN Module User Control, this is the code to include:

IncludeFunctions.InjectjQueryLibary(Page, false, false, false);

That’s it!  The code will automagically do all the rest for you.

A further recommendation is to use a conditional compile so that your debug code gets the un-minified version of the jQuery library (while ensuring your RELEASE version uses the minified version:

#if (DEBUG)
  IncludeFunctions.InjectjQueryLibary(Page, false, true, false);
#else
  IncludeFunctions.InjectjQueryLibary(Page, false, false, false);
#endif

Bonus Code : Including CSS Files

If you're an eagle-eyed reader and actually looked through the code before copy/pasting with abandon, you'll notice that there is a method called 'IncludeCSSFile' in the code. This isn't actually used anywhere in the code to include the jQuery library, but generally with any bit of jQuery code you might care to include with your module, there's generally a couple of CSS files along for the ride. This method allows to to use the same 'no duplicate' principle and include these css files in the Html header, right where they are meant to go. Just use the code like this (again, assuming in the Page_Load event of a DNN Module User Control):

   IncludeFunctions.IncludeCSSFile(this.Page, "my-css-file", this.ModulePath + "css/my-css-file.css");

The above code will include the 'my-css-file.css' file (located in the /desktopModules/your-module-name/css path) into the page header.

Further, if you're writing jQuery, chances are you're following the design principle of unobtrusive javascript, so aren't going to include a big chunk of messy script code in the middle of your html. Are You? So you can use the include script function in the code in the same way:

   IncludeFunctions.IncludeScriptFile(this.Page, "my-js-file", this.ModulePath + "js/my-js-file.js");

This works in the same way and gives a nice, clean way of including the script files into your code

Note that you might have to remove some line breaks in the main code listing to get the code to work. 

If you’ve had success, or find a bug, please let me know via the comments.

Tags:

6 comment(s) so far...

Re: Include jQuery in a DotNetNuke Module with version independent code

Great article Bruce. Love to see new people jump into jQuery. Also, I'll look at the 5.0 internals and see if there are some useful bits here to extend our jQuery support.

By Joe Brinkman on   Monday, October 26, 2009 10:12 PM

Re: Include jQuery in a DotNetNuke Module with version independent code

Thanks Joe - it was your posts that got me started after all. Now I've even got a jQuery shelf on my coding library, so call me a convert! This code was actually written for a jQuery-enabled Google Maps/Streetview implementation I had to cook up.

I think the most useful thing for a DNN inclusion would be a one-line css/js duplicate-checked inclusion somewhere. Maybe in the PortalModuleBase class, ie this.IncludeJSfile(id, src) and this.IncludeCSSFile(id, src)

By Bruce Chapman on   Monday, October 26, 2009 10:19 PM

Re: Include jQuery in a DotNetNuke Module with version independent code

The trouble I've found with this sort of server-side approach is that as soon as you're sharing the page with a module that injects and old version of jQuery inside its container regardless of what else is on the page (and yes, they are out there!) you're sunk. That's why I always do my jQuery injection at the client side.

By Mark Allan on   Monday, October 26, 2009 10:36 PM

Re: Include jQuery in a DotNetNuke Module with version independent code

@mark - that's true, but I like to think that you see less of this problem nowadays with jQuery becoming much more prevalent. Really the main issue I'm trying to solve here is not just to get jQuery into the page, but to do it in a way with a module that will work with 4.x and 5.x versions of DNN, without having to package two separate module versions.

By Bruce Chapman on   Monday, October 26, 2009 10:45 PM

Re: Include jQuery in a DotNetNuke Module with version independent code

@Bruce - In fact, one of the main advantages of the client-side approach is that it's completely version-independent. Basically, with a couple of client-side "if" statements (if loaded at all + if version is high enough), you can make sure you've got the version of jQuery you need regardless of the version of DNN and any other modules that may be present. But yes, hopefully soon most jQuery modules will apply some sort of intelligence to including their resources!

By Mark Allan on   Monday, October 26, 2009 11:14 PM

Re: Include jQuery in a DotNetNuke Module with version independent code

Hi Bruce,

I had this same issue early on in DNN 5.

I use this check:-
IF( HttpContext.Current.Items("jquery_registered") Is Nothing )

I'll check out your solution Mark.

By Scott McCulloch on   Tuesday, October 27, 2009 12:40 PM

Your name:
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Add Comment   Cancel 
Bruce Chapman
Hi, I'm Bruce Chapman, and this is my blog. You'll find lots of information here - my thoughts about business and the internet, technical information, things I'm working on and the odd strange post or two.