Search code examples
javascriptasp.net-mvcrazorasp.net-mvc-4razor-2

How can I avoid putting javascript in this EditorTemplate?


I wrote the following EditorTemplate that's loosely based off of some other SO questions and Google results:

@model Nullable<DateTime>
@{
    var metadata = ModelMetadata.FromStringExpression("", ViewData);
    string value;
    if(!String.IsNullOrEmpty(metadata.EditFormatString)) {
        if(Model.HasValue) {
            value = String.Format(metadata.EditFormatString, Model.Value);
        }
        else {
            value = metadata.NullDisplayText;
        }
    }
    else {
        value = Model.ToString();
    }
}
@Html.TextBox("", value, new { @class = "textBoxDate" })
<script type ="text/javascript">
    $(document).ready(function () {
        $('.textBoxDate').datepicker();
    });
</script>

The thing I don't like is that the script gets written below every textBoxDate. I understand why and I know that one possible solution would be to drop the script into a .js file and reference it on my page. That's not terribly difficult or anything but I was hoping there'd be a solution what would be a little bit more... seamless/magic (Why? Fun, it'd be neat, just because...). Any ideas?


Solution

  • I think dropping the script into a .js file and referencing it on your page is the most fun and neat approach to this problem.

    If you were looking for a less neat solution, you could add a couple HtmlHelper extensions to add register dependencies as the templating engine encounters them, then print out a bunch of script tags at the end of the page.

    private const string requiredJavascriptIncludesContextItemsKey = "requiredJavascriptIncludesContextItemsKey";
    
    public static void Require(this HtmlHelper html, string src)
    {
        var collection = (HashSet<string>) html.ViewContext.HttpContext.Items[requiredJavascriptIncludesContextItemsKey] ?? new HashSet<string>();
        collection.Add(src);
        html.ViewContext.HttpContext.Items[requiredJavascriptIncludesContextItemsKey] = collection;
    }
    
    public static HtmlString RequiredJavascriptIncludes(this HtmlHelper html)
    {
        var sb = new StringBuilder();
        foreach (var src in (HashSet<string>) html.ViewContext.HttpContext.Items[requiredJavascriptIncludesContextItemsKey] ?? new HashSet<string>())
        {
            sb.Append(string.Format("<script type='text/javascript' src='{0}></script>", src));
        }
    
        return new HtmlString(sb.ToString());
    }
    

    You could call the @Html.Require method within any template. In your case, it'd be like this:

    @model Nullable<DateTime>
    @{
        var metadata = ModelMetadata.FromStringExpression("", ViewData);
        string value;
        if(!String.IsNullOrEmpty(metadata.EditFormatString)) {
            if(Model.HasValue) {
                value = String.Format(metadata.EditFormatString, Model.Value);
            }
            else {
                value = metadata.NullDisplayText;
            }
        }
        else {
            value = Model.ToString();
        }
    }
    @Html.TextBox("", value, new { @class = "textBoxDate" })
    @Html.Require(Html.Content("scripts/datepicker.js"))
    

    Then, at the bottom of your base template page, you'd make the call to @Html.RequiredJavascriptIncludes() and all your js dependencies you've registered will be rendered as script tags at the end of the html document.