2

I am loading .aspx and .ascx files as StreamReader.

I want each file to register it's javascript and stylesheet dependencies in some declaration like a <%@ ClientDependency path="~/Scripts/jquery-1.4.1.min.js" %>.

Is there an existing convention for doing such a thing? I don't need an implementation, but I don't want to create a new syntax if there is already a way to do it.

Also, what are the guidelines for custom <%@ blocks in ASP.NET?

Also, please retag this question if you can think of a more appropriate description.

rick schott
  • 21,012
  • 5
  • 52
  • 81
smartcaveman
  • 41,281
  • 29
  • 127
  • 212
  • Please provide more detail on what you're doing and why? What's the point of reading in the .aspx files? – John Saunders Mar 10 '11 at 03:49
  • I am using ASP.NET MVC and I am keeping my client-side includes in a compressed, compiled format. I am saving the files and their necessary includes in a database. I don't want to use – smartcaveman Mar 10 '11 at 03:53
  • Consider that every view may have a dependency on jquery, but only certain widgets will have a dependency on jquery ui. If I am not displaying one of those jqueryui-dependent widgets on my page, then I do not want to incur the overhead for including that script. Please let me know if this is still unclear. – smartcaveman Mar 10 '11 at 03:57
  • @John Saunders, do you have a better way to address this? – smartcaveman Mar 10 '11 at 18:38
  • @John Saunders, this isn't MVC specific. I'm just looking for a client-side include registration convention. Do you see any problem with the approach I've described? – smartcaveman Mar 10 '11 at 18:48
  • @smart: I suppose the .ascx files could use [RegisterClientScriptBlock](http://msdn.microsoft.com/en-us/library/system.web.ui.clientscriptmanager.registerclientscriptblock.aspx). I don't know if the .aspx pages could do the same. – John Saunders Mar 10 '11 at 19:00

3 Answers3

1

Have you considered Google Loader or something like this: Enabling the ASP.NET Ajax script loader for your own scripts

rick schott
  • 21,012
  • 5
  • 52
  • 81
  • Thanks for the resource. I am using something similar. However, this doesn't help with the registration. – smartcaveman Mar 10 '11 at 04:23
  • +1 - The script loader is certainly a useful tool. The problem is that it still requires inline javascript which isn't acceptable. – smartcaveman Mar 10 '11 at 04:32
0

Basically...

Here's what I'm doing in my current MVC project:

  • Using Telerik's Web Asset Manager to register all of my common scripts and CSS in the Master Page that I know will be used on every page (There's an open source download, if like me, you didn't know).

  • I encapsulate all JS functionality (beyond a few lines) into functions stored in appropriate external JS files.

  • I encapsulate all of my extended UI functionality into HTML Helpers. They then use the StyleSheetRegistrar and ScriptsRegistrars to dynamically load any extra CSS or JS that are required for that particular helper. And rely on the Telerik manager not to load them more than once.

  • If I ever use inline styles, I specify the style tag manually inside the Helper method via the htmlAttributes object, but its rare.

  • In order to call my javascript functionality needed on the HTML element, I use ScriptRegistrar().OnDocumentReady()'s string overload to specify a javascript function to call for that element.

  • I've also defined a simple extension method for ScriptRegistrarBuilder in my HtmlHelpers class that allows me to provide a parameter list of arguments to pass to my javascript method call, when calling OnDocumentReady(), without a heap of string concatenation.

    public static void OnDocumentReady(this ScriptRegistrarBuilder builder, string format, params string[] args)
    {
        builder.OnDocumentReady(String.Format(format, args));
    }
    
  • And of course, I render all the JS at the bottom of the Master page.

You don't avoid all uses of inline JS here, but it keeps it pretty thin, if you do it right. Honestly, I can't really think of another way that wouldn't start to get pretty over complicated.

In the end I think you get something pretty flexible which at the same time allows you to be fairly specific about loading what you do and do not need on any given page.

The only caveat I've found with this method is in terms of CSS. In order to be able to dynamically load CSS files into the page when a helper needs them, I've had to render all the CSS includes at the bottom of the Master page, past any possible calls to add new files.

Haven't found a way around this yet, unfortunately.

A small example

Perhaps needlessly detailed for you... but this way someone searching can hopefully find a useful solution too! :)

In the master page:

<%
    Html.Telerik().StyleSheetRegistrar().DefaultGroup(styles => styles
       .Add("~/Content/Site.css")
       .Add("~/Content/core.css")
    );

    Html.Telerik().ScriptsRegistrar().DefaultGroup(scripts => scripts
        .Add("~/Scripts/common.js")
        .Combine(true)
        .Compress(true)
        .CacheDurationInDays(30)
    );
%>

Then somewhere just above the body closing tag:

<%:
   Html.Telerik().ScriptsRegistrar().Render();
   Html.Telerik().StyleSheetsRegistrar().Render();
%>

In your HTML helper:

    public static MvcHtmlString AutoCompleteComboListFor<TModel, TProperty>(this HtmlHelper<TModel> Helper, Expression<Func<TModel, TProperty>> expression,
        SelectList List, string DataSource)
    {
        MvcHtmlString dropDown = SelectExtensions.DropDownListFor<TModel, TProperty>(Helper, expression, List);

        string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
        string id = Helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName);

        //Do whatever you want

        helper.Telerik().ScriptRegistrar().DefaultGroup(scripts => scripts
            .Add("~/Scripts/extended.js")
            );
        helper.Telerik().StyleSheetRegistrar().DefaultGroup(styles => styles
            .Add("~/Content/extended.css")
            );

        helper.Telerik().ScriptRegistrar().OnDocumentReady(@"MyExtendedFunction('{0}');", id);

        return dropDown;
    }

Simply calling that helper (or any other) will end in a tidy block at the end of the page containing all JS code, files and CSS files.

<link type="text/css" href="/Content/Site.css" rel="stylesheet"/>
<script type="text/javascript" src="/asset.axd?id=kAAAAB-LCAAAAAAABADsvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee--997o7nU4n99__P1xmZAFs9s5K2smeIYCqyB8_fnwfPyJ-8Uezjx597xd_tPro0Uevp3WxapuPRh-d82dL-uynf9E6r6-3d8f79N9P47vqo0c7v2Qk3-bv2nw5y2f2m91f8v1f8v3RR9OWvmzp27s_nV1mDcOlBpf06d7O7s743nj33u4BfTKl3u99ep9--ehRW6_zX_L_BAAA___9S_3qkAAAAA%3d%3d"></script>

<script type="text/javascript"> 
//<![CDATA[
jQuery(document).ready(function(){
myFunction('Foo');});
//]]>
</script>
Geekman
  • 599
  • 7
  • 18
  • Use Combined(true) and Compress(true) on both registrations. This way you only have to send a single HTTP request for each include-collection instead of for each individual include. – smartcaveman Mar 10 '11 at 18:28
  • @smartcaveman Definitely on my list of things to do. Although as an aside, isn't it also true that just by combining resources, they are also compressed implicitly? Thought I read this on the Telerik site somewhere. In any case, agreed. – Geekman Mar 10 '11 at 18:35
  • No. Look at the generated code sample you posted. There are three script tags. Each one necessitates a separate HTTP request, as does each link tag – smartcaveman Mar 10 '11 at 18:37
  • @smartcaveman Edited for combined requests. I meant, though, that once you have your requests being combined properly, I believe you don't need to call Compress(), the combined call is automatically compressed. – Geekman Mar 10 '11 at 19:00
  • Just confirmed using firebug. At least it looks that way... so sleepy, I could be wrong. – Geekman Mar 10 '11 at 19:04
0

There is no existing convention for registering client includes with a directive. My solution was to create a custom server control instead and use the existing TemplateParser functionality to get the include data (which ended up being preferable to loading the file as a stream and parsing it myself).

bkaid
  • 51,465
  • 22
  • 112
  • 128
smartcaveman
  • 41,281
  • 29
  • 127
  • 212