Issues with "Self-Closing" Tags

by Dave MichelFebruary 25, 2013

I was bit by this again a couple of weeks ago when I went on autopilot and did something like this:

<script type="text/javascript" src="/Scripts/Header/01-jquery.js" />
<script type="text/javascript" src="/Scripts/Header/blog.js" />
<script type="text/javascript" src="/Scripts/Header/jquery.colorbox-min.js" />
<script type="text/javascript" src="/Scripts/02-jquery.cookie.js" />
<script type="text/javascript" src="/Scripts/04-jquery-jtemplates.js"" />

The 01-jquery.js file loaded fine, but all the scripts below it were ignored. After fighting with this for a while, I remembered that HTML and XML are parsed differently, Everything worked great after I explicitly put in closing tags like this:


<script type="text/javascript" src="/Scripts/Header/01-jquery.js"></script>
<script type="text/javascript" src="/Scripts/Header/blog.js"></script>
<script type="text/javascript" src="/Scripts/Header/jquery.colorbox-min.js"></script>
<script type="text/javascript" src="/Scripts/02-jquery.cookie.js"></script>
<script type="text/javascript" src="/Scripts/04-jquery-jtemplates.js"></script>

When writing HTML, bear in mind that the nice XML shortcuts we take for granted may not parse properly under certain circumstances. HTML != XML, and HTML != XHTML, as far as a browser's parser goes. There's some good info concerning this at http://stackoverflow.com/questions/69913/why-dont-self-closing-script-tags-work.

CSS/JS file order in BlogEngine.NET

by Dave MichelFebruary 6, 2013

One of the new, cool capabilities of .Net is built-in compression and bundling of CSS and javascript (Gunner Peipman has written an excellent introductory article here). BlogEngine.NET 2.7 uses this infrastructure. However, there are a couple of gotchas when integrating an existing site's styles and scripts into BlogEngine.NET.

What Gets Bundled in BlogEngine.NET?

BlogEngine.NET sets up its bundles in the Global.asax.cs file, like this:

static void Init_BundleTable()
{
  BundleTable.Bundles.EnableDefaultBundles();
  BundleTable.Bundles.Clear();

  var jsTransform = new JsMinify();
  var cssTransform = new CssMinify();

  // scripts added to header and NOT deferred
  var hdrjs = new Bundle("~/Scripts/Header/js", jsTransform);
  hdrjs.AddDirectory("~/Scripts/Header", "*.js", false);
  BundleTable.Bundles.Add(hdrjs);

  // for anonymous users
  var css = new Bundle("~/Styles/css", cssTransform);
  css.AddDirectory("~/Styles", "*.css", false);
  BundleTable.Bundles.Add(css);

  var js = new Bundle("~/Scripts/js", jsTransform);
  js.AddDirectory("~/Scripts", "*.js", false);
  BundleTable.Bundles.Add(js);

...

The AddDirectory() method will add every file that matches the specified pattern in the directory to the bundle in alphabetical order. For the fragment above, all the *.css files in the Styles directory will be minified and bundled into a "/Styles/css" link at the top of each post or BlogEngine.NET page, assuming "Enable optimization" is checked in BlogEngine.NET's Advanced Settings.

Managing File Order

You'll want to control the order of files in the bundle, especially for CSS. There are two main ways to do this.

  • Adjust the filenames in your directory so that they get loaded in the order you choose.
  • Use AddFile() for each file (in order) instead of AddDirectory() for the whole directory

The BlogEngine.NET developers use the first option, adding a two digit integer to the beginning of the filename. Now you have complete control over the order of your files in a bundle.

Original Order

  • modern-responsive.css
  • modern.css
  • OrchardHouse.css
  • site.css

New Order

  • 01modern.css
  • 02modern-responsive.css
  • 03site.css
  • 04OrchardHouse.css

What About Running Unoptimized/Unbundled?

If you're not bundling, what you want is exactly the same order with the CSS and Javascript files as you have in the bundles. If "Enable optimization" in unchecked, BlogEngine.NET reads in the files using the regular DirectoryInfo().GetFiles() method, which reads in the files in alphabetical order. Then the list is used to make a link tag for each file:

public static void AddStyle(System.Web.UI.Page page, string lnk)
{

 // global styles on top, before theme specific styles
 if(lnk.Contains("Global.css") || lnk.Contains("Styles/css"))
   page.Header.Controls.AddAt(0, new LiteralControl(
     string.Format("\n", lnk)));
 else
   page.Header.Controls.Add(new LiteralControl(
     string.Format("\n", lnk)));

}

AddStyle() puts Global.css at the top of the page header (when unoptimized), and it does the same for the style bundle (when optimized, since all the style bundles begin with "Styles/css"). When you are unoptimized, you are adding the list of files to the header, but you'll either get them in reverse order (if they match the if, they are added at the 0 index of the page header, reversing the order) or you'll get them at the bottom of the page header (if they match the else. The theme style will be in the middle of the header, above the dynamically loaded styles, so your theme is generally quite boogered.

My solution was to modify Helpers in BlogEngine.Core, using Linq to parse the integer I put at the beginning of each filename:

public static void AddStyle(System.Web.UI.Page page, string lnk)
{
  // global styles on top, before theme specific styles
  int index = GetIndexFromFilename(lnk);

  if(lnk.Contains("Global.css") || lnk.Contains("Styles/css") || index > 0)
    page.Header.Controls.AddAt(index, new LiteralControl(
      string.Format("\n", lnk)));
  else
     page.Header.Controls.Add(new LiteralControl(
       string.Format("\n", lnk)));
}

/// 
/// Get an index number for ordering the file from the filename
/// 
/// Added by DTM 2/6/2012
/// 
/// 
/// 0 if no number at beginning of filename, 
/// otherwise returns the number
/// 
private static int GetIndexFromFilename(string lnk)
{
  int beginIndex = lnk.LastIndexOf("/");
  string filename;

  if (beginIndex > 0)
    filename = lnk.Substring(beginIndex + 1);
  else
    filename = lnk;

  char[] charArray = filename.Trim().TakeWhile(c => char.IsDigit(c)).ToArray();

  if (charArray.Length > 0)
    return Int32.Parse(new string(charArray));
  else
    return 0;
}

Now BlogEngine.NET will put CSS and javascript files in the order I choose, whether bundled or not.

About the author

Dave

Dave is a recording engineer, musician and software developer in Minneapolis.

Month List