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?
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.
The way I do it is generate the URL server-side and store in the generated HTML using an HTML5 data attribute, eg: (Razor syntax)
<li class='customClass' data-url='@Url.Action("DisplayItems", "Home", new { id = Model.Id })'>...</li>
Then you can use the jQuery attr() function to pick up the url, eg:
$(".customClass").click(function () {
$.ajax({
url: $(this).attr("data-url"),
success: function (data) {
// do stuff
}
});
});
If you're generating HTML client-side in response to AJAX calls, you can include the relevant URLs in your JSON payload and populate the data- attribute the same way.
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.
Use the module pattern.
// separate js file
var PAGE_MODULE = (function () {
var url = {},
init = function(url) { ... },
load = function() {
$.ajax({
url: url,
...
});
}
return { init: init };
})();
// calling init goes on the page itself
PAGE_MODULE.init(" %: Url.Action(...) %>");
In general the inline onclick handler is not good javascript as you are using a global function.
onclick='doAjax(
I recommend reading http://jqfundamentals.com/book/index.html#N20D82 to get a better handle on the module pattern.
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'),
...
});