views:

6127

answers:

3

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?

+1  A: 

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.

tvanfosson
isn't this the inline JS we are trying to remove by hooking up the events on document.ready???
Schotime
Your solution is technically right, but I agree with you that this is not really better.
BobbyShaftoe
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
A: 

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'),
  ...
});
stusmith
+7  A: 

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.

Simon_Weaver
this is so nice and very clean indeed!
mare