views:

752

answers:

2

I have been using ASP.NET MVC for six months or so and have been checking out the Nerd Dinner example created by those Microsoft guys. One thing I noticed they did when enabling AJAX to RSVP for a dinner, is put the JavaScript references in the User Control being used for RSVPing.
(FILE: RSVPStatus.ascx)

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NerdDinner.Models.Dinner>" %>

<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

This doesn't seem right to me, as there is a really good chance I would be using these same libraries elsewhere, like logon authentication. Plus if I change script versions, I need to hunt down all the references to the libraries.

So I ask if my thinking is correct and these references should actually be in a more central location like the master page?

Please let me know what the best practice is for this and pro's and cons if any.

A: 

In my website, www.trailbehind.com, we have a set of javascript files that belong on all pages. And then some pages include additional libraries.

For the JS files that all pages uses (there are a couple dozen files), we concatenate them and minify them on build.

There is a flag in our settings file that says whether to use the concatenated javascript or the separate files on build. This is critical so that you can debug the javascript on dev, but use the small, single-file javascript on production.

Here is our python code to combine and minify:

import os
import thetrailbehind.lib.jsmin as jsmin

JS_FILES = [  'lib/json2.js',
              'lib/markermanager.js',
              'lib/labeledmarker.js',
              'lib/rsh/rsh.js', 
              'lib/showdown.js',
              'lib/yui.js',
              'lib/dragzoom.js',
              'gen/attribute_data.js', 
              'gen/national-parks.js', 
              'Widgets/CommentsWidget.js', 
              'Widgets/Search.js', 
              'Widgets/MenuWidget.js', 
              'Widgets/PhotoWidget.js', 
              'Widgets/ReportList.js', 
              'Widgets/help.js', 
              'attributes.js', 
              'rsh.js', 
              'map.js', 
              'mapcontrols.js',
              'markers.js',
              'amazon.js',
              'plan_trip.js', 
              'init.js',]


def concat(files, base_path, all_file, all_file_min):
  if os.path.exists(base_path + all_file):
    lasttime = os.path.getmtime(base_path + all_file)
  else:
    lasttime = 0
  out_of_date = False
  for file in files:
    if os.path.getmtime(base_path + file) > lasttime:
      out_of_date = True
      break
  if out_of_date:
    outfile = open(base_path + all_file, 'w')
    for file in files:
      outfile.write(open(base_path + file).read())
      outfile.write("\n")
    outfile.close()

    alljs = open(base_path + all_file)
    allminjs = open(base_path + all_file_min, "w+")
    jsmin.JavascriptMinify().minify(alljs, allminjs)
    alljs.close()
    allminjs.close()



def main():
  concat(JS_FILES, '/home/wibge/thetrailbehind/media/javascript/', 'gen/all.js', 'gen/all.min.js')


if __name__ == "__main__":
  main()

And here is the Django/HTML template where we switch:

{% if use_all_js %}
  script type=text/javascript src=/site_media/javascript/gen/all.min.js> 
{% else %}
  script type="text/javascript" src="/site_media/javascript/rsh.js">
  script type="text/javascript" src="/site_media/javascript/amazon.js">
  script type="text/javascript" src="/site_media/javascript/map.js">  
  A BUNCH OF SEPARATE INCLUDES...etc
{% endif %}
Andrew Johnson
+5  A: 

I would definitely advise against putting them inside partials for exactly the reason you mention. There is a high chance that one view could pull in two partials that both have references to the same js file. You've also got the performance hit of loading js before loading the rest of the html.

I don't know about best practice but I choose to include any common js files inside the masterpage and then define a separate ContentPlaceHolder for some additional js files that are specific to a particular or small number of views.

Here's an example master page - it's pretty self explanatory.

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<head runat="server">
    ... BLAH ...
    <asp:ContentPlaceHolder ID="AdditionalHead" runat="server" />
    ... BLAH ...
    <%= Html.CSSBlock("/styles/site.css") %>
    <%= Html.CSSBlock("/styles/ie6.css", 6) %>
    <%= Html.CSSBlock("/styles/ie7.css", 7) %>
    <asp:ContentPlaceHolder ID="AdditionalCSS" runat="server" />
</head>
<body>
    ... BLAH ...
    <%= Html.JSBlock("/scripts/jquery-1.3.2.js", "/scripts/jquery-1.3.2.min.js") %>
    <%= Html.JSBlock("/scripts/global.js", "/scripts/global.min.js") %>
    <asp:ContentPlaceHolder ID="AdditionalJS" runat="server" />
</body>

Html.CSSBlock & Html.JSBlock are obviously my own extensions but again, they are self explanatory in what they do.

Then in say a SignUp.aspx view I would have

<asp:Content ID="signUpContent" ContentPlaceHolderID="AdditionalJS" runat="server">
    <%= Html.JSBlock("/scripts/pages/account.signup.js", "/scripts/pages/account.signup.min.js") %>
</asp:Content>

HTHs, Charles

Ps. I would agree with Andrew in saying that any common JS that is defined directly inside the master page should be concatenated and minified.

EDIT: My implementation of .JSBlock(a, b) as requested

public static MvcHtmlString JSBlock(this HtmlHelper html, string fileName)
{
    return html.JSBlock(fileName, string.Empty);
}

public static MvcHtmlString JSBlock(this HtmlHelper html, string fileName, string releaseFileName)
{
    if (string.IsNullOrEmpty(fileName))
        throw new ArgumentNullException("fileName");

    string jsTag = string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>",
                                 html.MEDebugReleaseString(fileName, releaseFileName));

    return MvcHtmlString.Create(jsTag);
}

And then where the magic happens...

    public static MvcHtmlString MEDebugReleaseString(this HtmlHelper html, string debugString, string releaseString)
    {
        string toReturn = debugString;
#if DEBUG
#else
        if (!string.IsNullOrEmpty(releaseString))
            toReturn = releaseString;
#endif
        return MvcHtmlString.Create(toReturn);
    }
Charlino
Great, thank your for the response, what is a good approach to minimizing js files in the ASP.NET MVC world?
Brettski
Good question... I'm a little lazy at the moment and I just run a bat file as part of the build that minifies all my js. The jsmin.exe can be found here http://www.crockford.com/javascript/jsmin.html - there is also C# code there so you can roll your own minifiy application. I would be interested in hearing how anyone concatenates AND/OR minifies them on the fly OR at build time.
Charlino
Charlino
It'd be cool to see your implementation of `Html.JSBlock(a,b)`.
Drew Noakes
Consider it added :-)
Charlino