8

I am trying to create a reusable widget in ASP.Net MVC. This is a widget that concatenates 2 dates into a string. The widget contains 3 files: Widget.cshtml, Widget.css, and Widget.js and dependencies to several jquery libraries.

Widget.cshtml

<h2>Create a date range</h2>

<div>
    <label for="startDate">Start:</label>
    <input id="startDate" class="date" name="startDate" type="text" />
</div>

<div>
    <label for="endDate">End:</label>
    <input id="endDate" class="date" name="endDate" type="text" />
</div>

<p id="output"></p>

<button id="createDateRange">Create</button>

Widget.css

label { color: #555;}

Widget.js

$(function () {
    $(".date").datepicker();

    $("#createDateRange").click(function () {
        var start = $("#startDate").val();
        var end = $("#endDate").val();
        $("#output").html(start + " to " + end);
    });
});

I want to make this widget reusable in my MVC pages. I have come up with several options, including:

  1. Create an HTML Helper
  2. RenderPartial
  3. RenderAction
  4. Remove all css and javascript dependencies from the widget file. Manually Copy-And-Paste into every view that I'm using it in the correct place.

I was about to use a helper, when I remembered Steve Souders rules for web site performance:

Rule 5: Put stylesheets at the top

Rule 6: Put scripts at the bottom

Options 1), 2) and 3) all seem like good ideas, but they all write the code inline and violate the performance rules. Option 4) allows me to manually put the javascript and css files where they belong, but copy-and-paste is prone to mistakes. A potential idea would be write a scaffolder that puts everything where it belongs, but that seems too much work for right now.

What is the best (hopefully simple) way to do widgets and still follow performance rules 5 & 6?

John
  • 3,332
  • 5
  • 33
  • 55

2 Answers2

2
  1. Change your widget to use unobtrusive javascript techniques so that the entire DOM can be scanned for instances of your widget. Rely on classes instead of IDs.
  2. Use a tool like Chirpy to package all your CSS files and Javascript files into a single library file (one .js and one .css) that you can append at the top and bottom (respectively) of your Master Page/Layout.
  3. Use an HTML Helper to produce your widget HTML. If you like, the HTML helper can render a partial view to keep the HTML view code in a view file. Because you're using unobtrusive javascript, there is no need to produce any script inline. Just let the widget's javascript code scan the DOM for instances of your widget.

Step 1

The original example already uses some unobtrusive javascript techniques, but here's how you could extend that pattern even further, as demonstrated in this jsfiddle.

Widget.cshtml

<div class="date-range-widget">
    <h2>Create a date range</h2>
    
    <div>
        <label for="startDate">Start:</label>
        <input id="startDate" class="date start-date" name="startDate" type="text" />
    </div>
    
    <div>
        <label for="endDate">End:</label>
        <input id="endDate" class="date end-date" name="endDate" type="text" />
    </div>
    
    <p class="output"></p>
    
    <button class="create-date-range">Create</button>
</div>

Widget.js

$(function () {
    // This line could probably be done generally, regardless of whether you're
    // in the widget.
    $("input.date").datepicker();

    $("div.date-range-widget").each(function(){
        var $t = $(this);
        var startBox = $t.find("input.start-date");
        var endBox = $t.find("input.end-date");
        var output = $t.find("p.output");
        $t.find("button.create-date-range").click(function () {
            output.html(startBox.val() + " to " + endBox.val());
        });
    });    
});

Note how you can now have multiple instances of this widget present on your page, with different id and name values, and they would function independently of one another.

When no data-range-widget is present, the javascript will still search the DOM, fail to find any, and move on. However, the cost of this operation is usually pretty lightweight compared to making a separate round-trip for a different javascript file. Keeping the CSS and javascript in separate, static files makes it so the browser can cache those files, which means it's pulling less data across the wire on average (since only the HTML changes from one request to the next).

Step 2

I've personally found it worthwhile to combine my javascript and CSS into one file, so the browser only has to pull one javascript and one css file in for a given page load (and both will be cached after the first page load). If you have a very large javascript file that is only used on certain pages, you may want to keep it separate and only load it on certain pages.

Regardless, your javascript should be written in such a way that you could have all the javascript files on your site loaded at once without stepping on each others' toes.

Step 3

Should be pretty self-explanatory.

Community
  • 1
  • 1
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • I looked up the term "Unobtrusive Javascript," and the whole concept is a bit vague. Can you be a little more specific about how these techniques could be used to solve the OP's problem from an ASP.NET MVC perspective, perhaps utilizing a small code sample to illustrate? – Robert Harvey Nov 16 '11 at 17:13
  • Perhaps I'm misunderstanding. Do you mean to combine all Javscript from your entire site, including script blocks, and put it into a single file that's loaded with every page that uses the Master?Suppose two pages are using different datepickers. Page one has $(".date").datepicker() and page2 has $(".date").simpledatepicker(); Running both scripts could cause both to blow up, couldn't it? – John Nov 16 '11 at 17:17
  • 1
    I've come to the same conclusion, though it's not as convenient for the widget user compared to pre-MVC webforms. Use HTML helpers to render the markup. They can be placed in a reusable library. Include the client script in layout pages, even if it's not necessary on every page. In the case of two datepicker types, you can distinguish them with distinct classes. $(".date-simple").simpledatepicker() and $(".date-complex").complexdatepicker(). Or you could use data- attributes for parameters that the .datepicker() jQuery widget can act on. – Carl Raymond Nov 16 '11 at 19:01
  • What about using ViewContext or ViewBag to register the script and style references? Such as this? http://stackoverflow.com/questions/5433531/using-sections-in-editor-display-templates/5433722#5433722 – John Nov 16 '11 at 19:04
  • @John: Yes, that's what I mean. Regardless of whether you do this, you shouldn't be writing two sets of javascript that would interfere with one another: you never know when you might want a simple and a complex date picker on the same page. Carl shows how you can avoid conflicts. Using ViewData to register the references and output them at the end of the page can work for javascripts, but the CSS should be included in the document's `head`. As I recall, CSS in the body isn't standards-compliant. – StriplingWarrior Nov 16 '11 at 19:51
2

You could use RenderSection() which is similar to ContentPlaceHolder's of old and put them in your _Layout.cshtml

   <head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
   @RenderSection("head");
</head>

Then in your View put:

@{
    ViewBag.Title = "Home Page";
}
@section head
{
<link href="widget-style.css" type="text/css" rel="stylesheet" /><
}

<h2>@ViewBag.Message</h2>
<p>
    To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
</p>

@section footer{
 <script type="text/javascript" src="wiget.js"></script>  
}

It's a simple alternative if you don't want to use Combress or Chirpy, you'll still have to add the code into the pages you use the control on, but it'll appear in the right places.

I also found a handy blog post on registering scripts from within a partial view that might be worth a read.

danswain
  • 4,171
  • 5
  • 37
  • 43
  • Have a look at the link I added to my answer, might be of help. – danswain Nov 16 '11 at 20:49
  • Excellent link. It even checks to make sure that it's not an Ajax request before writing it. – John Nov 16 '11 at 21:37
  • Good link. I've done something similar here - http://forloop.co.uk/managing-scripts-for-razor-partial-views-and-templates-in-asp.net-mvc – Russ Cam Nov 16 '11 at 21:59