up vote 2 down vote favorite
5

When doing a Ajax call to an MVC action currently I have my javascript inside the View, not inside its own JS file.

It is then very easy to do this:

var xhr = $.ajax({
     url: '<%= Url.Action("DisplayItem","Home") %>/' + el1.siblings("input:hidden").val(),
     data: { ajax: "Y" },
     cache: false,
     success: function(response) { displayMore(response, el1, xhr) }
});

...then including the URL in the ajax call using Url.Action() inside the JS is pretty easy. How could i move this do its own JS file when without hard coding the URL?

flag

55% accept rate

3 Answers

up vote 1 down vote accepted

Here's another way:

In your master page, include an area for inline scripts:

<head>
  ...
  <asp:ContentPlaceHolder runat="server" ID="_inlineScripts" />
  ...
</head>

Then in the Page_Load, create a utility function:

protected void Page_Load( object sender, EventArgs e )
{
  AddInlineScript( string.Format( "$.url=function(url){{return '{0}'+url;}}", GetBaseUri() ) );
  ...
}

private Uri GetBaseUri()
{
  var requestUrl = Request.Url.AbsoluteUri;
  var i = requestUrl.IndexOf( request.Path );

  return new Uri( requestUrl.Substring( 0, i ) );
}

private void AddInlineScript( string content )
{
  var script = new HtmlGenericControl( "script" );

  script.Attributes.Add( "type", "text/javascript" );
  script.InnerHtml = content;

  _inlineScripts.Controls.Add( script );
}

Now you can use this function in your ajax:

$.ajax({
  url: $.url('path/to/my-handler'),
  ...
});
link|flag
up vote 2 down vote

This way fully uses MVC Routing so you can fully take advantage of the MVC framework. Inspired by stusmith's answer.

Here I have an action in ApplicationController for dynamic javascript for this URL :

 /application/js

I'm including static files here because I want just one master javascript file to download. You can choose to just return the dynamic stuff if you want:

     /// <summary>
    /// Renders out javascript
    /// </summary>
    /// <returns></returns>
    [OutputCache(CacheProfile = "Script")]
    [ActionName("js")]
    public ContentResult RenderJavascript()
    {
        StringBuilder js = new StringBuilder();

        // load all my static javascript files                    
        js.AppendLine(IO.File.ReadAllText(Request.MapPath("~/Scripts/rr/cart.js")));
        js.AppendLine(";");

        // dynamic javascript for lookup tables
        js.AppendLine(GetLookupTables());
        js.AppendLine(";");

        return new ContentResult()
        {
            Content = js.ToString(),
            ContentType = "application/x-javascript"
        };
    }

This is the helper function that creates our lookup table. Just add in a line for each RouteUrl you want to use.

    [NonAction]
    private string GetLookupTables() 
    {
        StringBuilder js = new StringBuilder();

        // list of keys that correspond to route URLS
        var urls = new[] {
            new { key = "updateCart", url = Url.RouteUrl("cart-route", new { action = "updatecart" }) },
            new { key = "removeItem", url = Url.RouteUrl("cart-route", new { action = "removeitem" }) }
        };

        // lookup table function
        js.AppendLine("// URL Lookuptable");
        js.AppendLine("$.url=function(url) {");
        js.AppendLine("var lookupTable = " + new JavaScriptSerializer().Serialize(urls.ToDictionary(x=>x.key, x=>x.url)) + ";");
        js.AppendLine("return lookupTable[url];");
        js.AppendLine("}");

        return js.ToString();
    }

This generates the following dynamic javascript, which is basically just a lookup table from an arbitrary key to the URL I need for my action method :

// URL Lookuptable
$.url=function(url) {
var lookupTable = {"updateCart":"/rrmvc/store/cart/updatecart","removeItem":"/rrmvc/store/cart/removeitem"};
return lookupTable[url];
}

In cart.js I can have a function like this. Note that the url parameter is taken from the lookup table :

 var RRStore = {};
 RRStore.updateCart = function(sku, qty) {

    $.ajax({

        type: "POST",
        url: $.url("updateCart"),
        data: "sku=" + sku + "&qty=" + qty,
        dataType: "json"

        // beforeSend: function (){},
        // success: function (){},
        // error: function (){},
        // complete: function (){},
    });

    return false;

};

I can call it from anywhere with just :

 RRStore.updateCart(1001, 5);

This seemed to be the only way I could come up with that would allow me to use routing in a clean way. Dynamically creating URLS in javascript is icky and hard to test. Testing types can add in a layer somewhere in here to easily facilitate testing.

link|flag
up vote 1 down vote

Wrap the AJAX call in a function that takes the URL (and any other data) as a parameter(s) and returns the response. Then in your view, call the function instead of calling the AJAX call directly.

function doAjax( url, data, elem, callback )
{
    return $.ajax({
        url: url,
        data: { ajax: data },
        cache: false,
        success: function(response) { callback(response, elem, xhr); }
    });
}

...

<input type='button' value='Go get it' onclick='doAjax( <%= Url.Action ...

I'm not sure that this is any better than having the Ajax call on the page instead of in a JS file, unless you use the exact same pattern frequently.

link|flag
1  
isn't this the inline JS we are trying to remove by hooking up the events on document.ready??? – Schotime Dec 18 '08 at 2:00
Your solution is technically right, but I agree with you that this is not really better. – BobbyShaftoe Dec 18 '08 at 3:21
it depends what you're trying to abstract away into the library function. in this case you're not abstracting any business logic away, just hiding the fact that you're using jquery – Simon_Weaver Jul 16 '09 at 18:37

Your Answer

get an OpenID
or
never shown

Not the answer you're looking for? Browse other questions tagged or ask your own question.