How to generate the right URLs in javascript when externalizing scripts - javascript

I am working on an ASP.NET MVC3 app. I was reading about good Javascript practices in various places and decided it would be best if I externalized the javascript and jQuery calls that were sitting in a <script/> tag at the bottom of my views.
I put each view's Javascript in a separate file with a standardized naming convention so I could generate the <script/> tag to include the appropriate file in my layout view.
But it's on the verge of being too complicated to be maintainable, and I have run into one problem that breaks the whole scheme, namely the need to occasionally insert URLs into the javascript.
For example: if I'm doing a $.getJSON call, I need to provide a URL. I've been generating them on the server side, in the views, using the UrlHelper class, like so:
var _RegistrationSelectClassUrl = '#Url.Action("SelectClass", "Student", new { area = "Registration", id = Model.Person.PersonID })';
And this worked nicely ... up to the point where I externalized the javascript. No longer being part of views, calls to UrlHelper were just strings again.
So my question is this: if I stick with the externalized javascript, I need a way to insert or generate the appropriate URLs. Other than sticking a script section in the layout file that uses UrlHelper to define a constant for every URL I might want to use, I can't think of a good one.
Any suggestions?

External JS:
var namespace = {
init: function (url) {
this._RegistrationSelectClassUrl = url;
},
_RegistrationSelectClassUrl: ''
};
View:
<script src="external/js.js" />
<script>
namespace.init('#Url.Action("SelectClass", "Student", new { area = "Registration", id = Model.Person.PersonID })');
</script>
This is a basic example.

You can't. The moment the script leaves the parsed page it loses all access to the model. If you added this code into a PartialView, you would be able to parse things with the Helper classes, but since your link is dependent on the model, your scripts will have to be generated in the page itself.
A way to externalize it might be to have a mapped array of links that is populated by the model view itself. You could then add link text into this array mapping based on some key that would then be accessible by the external script after load. I would recommend not using the global variables for this and prototype an object for this purpose.
Some documentation for this: http://www.javascripttoolbox.com/bestpractices/#namespace

Related

Best way to remove hardcoded string in javascript with Asp.Net MVC

In my javascript files, I have too much hardcorded url that references controllers actions. Sometime, I also have messages displayed directly from my javascript.
What is the best way to remove all these hardcoded strings from javascript files?
Step one, use T4MVC to automatically generate a structured set
of .NET classes that describe your ASP.NET MVC's application
structure
Step two, create a new partial view that defines server-side
information described by T4MVC as a set of Javascript constants.
<script type="text/javascript">
var SHOPPING_CART_DETAIL_URL = '#Url.Action(MVC.ShoppingCart.Detail(Model.ShoppingCartId))';
var CLIENT_DETAIL_URL = '#Url.Action(MVC.Client.Detail(Model.ClientId))';
var USER_IS_ADMIN = #(User.IsInRole(Roles.Admin) ? "true" : "false");
</script>
Step three, include this partial view in the head of whatever page
you need it. You could also include it in the head of your general
page layout. Make sure this loads before the rest of your JavaScript files.
Step four, use your newly defined JavaScript constants throughout your JavaScript files.
For that purpose I use T4MVC. It will allow you to use strongly typed objects in place of literal strings. You will need to initialize your javascript in your views, but other than that it works great.
For messages etc. here are some ideas: what are the different approaches to multilingual javascript applications
If you want to avoid using T4MVC, you could make a Controller that parses JS files. Just configure a route in Global.asax that catches all the javascript urls, and that action will ready the JS file, parse it, return a result with the URLs.
routes.MapRoute(
"Javascript",
"{url}.js",
new { controller = "Javascript", action = "Parse" }
);
Then write an action that reads the requested URL, finds the JS file, replaces values based on a Key/Value dictionnary. Perhaps even externalize that Key/Value dictionnary if could need that. The rest is up to you.
Another option would be to use a controller action that returns Javascript code. That javascript code would be a list of variables filled with URLs that are MVC generated.

How to namespace our JS for use with the Rails asset pipeline

I understand the reasoning behind the rails 3.1 asset pipeline: we compile all the JS in a neat, cacheable file to improve performance. Great we want that.
However, loading everything also means we've got to be very careful that we not use a certain ID or class on multiple pages, if we have some JS attached to it. Or else, the JS will fire on both pages, since its always loaded.
Now, what we want to achieve is the following:
* we want to keep everything in a single JS file (we know how we can load files separately, just don't want that)
* we want to namespace the JS in each controller_name.js so it's only loaded when the appropriate namespace is initialized
* we want to initialize the appropriate namespace by reading the current controller from say a data-attribute on the <body> tag of our layout
The problem is: we don't have a good idea how to implement in JS. Particularly, how should we namespace the JS and then dynamically initialize it based on the contents of a HTML tag..
Any help is greatly appreciated!
Here is a way to namespace everything on a controller/action level
http://www.viget.com/inspire/extending-paul-irishs-comprehensive-dom-ready-execution/
above was inspired by http://paulirish.com/2009/markup-based-unobtrusive-comprehensive-dom-ready-execution/
You basically declare you body as such
<body data-controller="<%= controller_name %>" data-action="<%= action_name %>">
And then these methods are called (which each have a series of methods -- so if you need something on every page, it's in common/init. Or on all users actions, that's on users/init. Or only the users show page? that's users/show.
SITENAME.common.init();
SITENAME.users.init();
SITENAME.users.show();
I've used this and it works very very well.
JsSpace.on('users', {
index: function(){
console.log('index action of users controller');
}
});
that pattern implemented by render controller and action into body attribute then
fetch them and execute the match function.
js-namespace-rails

MVC .NET 3 - Maintainable, Refactor-Friendly Javascript Files

I currently have page-specific javascript files that look something like:
$(document).ready(function ()
{
namespace.home.list = {};
namespace.home.list.Update = function ()
{
var element = $(namespace.ElementIds.LIST);
//some code that pulls a list from the server and dumps the data into our element
});
};
By my own convention, this .js file is meant to belong to Controller Home/List (and correspondingly the View Home/List.cshtml).
Furthermore, namespace.ElementIds has a list of Ids that are manually kept in sync with a (nearly) identical .cs file, so that server-generated html creates elements with the same Ids that I reference in my js files.
I am unhappy with this as a solution for keeping my elementId and js namespaces refactor-friendly. Is there a better way?
Currently my javascript is loaded via script tags with src attributes, but I've considered having them in .cshtml files that have nothing but a script tag with some razor markup that would let me define namespaces + elementIds using only my C# classes. I have a feeling this is not a good idea, but I don't actually know why. It would increase my ability to refactor and link my server code to my client code, but what are the drawbacks? Lack of caching? Decreased performance?
Generally you do not want to have that many javascript files linked to your document. Each uses up a round trip's worth of time and overhead in the HTTP Protocol.
What you should do is build/use a system that collects all your JS files in your project and crunches them into one minified js file (or 2-3 if you need lighter loads in places). This will make sure they load quickly and in one go.
Then you link the one file in your document and it doesn't matter what you rename the original js file to. Or where it lives.
As for namespacing, something you should note:
You should probably wrap up most of your modules in a scope to keep from polluting your global namespace, this has the addded benefit of giving you a shortcut for your namespacing mechanism:
;(function($, $namespace) {
var $module = $namespace.list = {};
$module.Update = function ()
{
var element = $($namespace.ElementIds.LIST);
//some code that pulls a list from the server and dumps the data into our element
};
})(jQuery, namespace.home);
This allows you to have private helper functions with useful (generic) names. Since they're only accessible from within that scope and won't conflict with other modules. Additionally this makes sure your jQuery never conflicts with another js library that may take the $ identifier in the global namespace.

Unique instances of Javascript self-executing anonymous functions

I have created the PHP side of a modular AJAX/PHP framework and now I am trying to implement the client side.
From my previous experience with modular web applications I know that sometimes multiple instances of one particular module are needed. For example, a web based two player game with page parts for each user.
On PHP side I have assigned a unque ID to each constructed instance of the module and I can pass this UID to the browser but I have no idea how to implement the Javascript side of this module instance.
Modules can be loaded all in one go or loaded separately through AJAX (I am using jQuery).
Now I am using a modular approach that I found in some article, but I can redesign it in some other way if that would help to solve this issue without sacrifising modularity and private/public code separation. For now let's say I have a js file with the following:
//Self-Executing Anonymous Func
(function( MyModule, $, undefined ) {
// My Uid
MyModule.UID = "";
//Public Method
MyModule.onLoad = function() {
alert("Hey, you loaded an instance of MyModule with UID " + MyModule.UID);
};
//Private Methods follow
function somethingPrivate( ) {
}
}( window.MyModule = window.MyModule|| {}, jQuery ));
I am using Smarty for templates. Let's say, I have a simple module template like this:
<div id="{$contents.moduleuid}">
here goes the contents of the module which can be accessed from MyModule Javascript code by using this unique moduleuid
</div>
I have set up the server side so each module automatically appends additional template with Javascript:
<script type="text/javascript">
/*
TODO: here I have access to the {$contents.moduleuid}
But I have no idea what to put here to create a unique instance of MyModule
(also it might need loading js file if it was not loaded yet) and I should also set for
this instance MyModule.UID to {$contents.moduleuid}
and also call MyModule.onLoad for this instance after it has loaded its Javascript.
*/
</script>
I am not experienced with advanced Javascript topics so it is unclear to me how I can create a separate instance of MyModule for each module which gets construced server-side? Is it possible at all to create instances of self-executing anonymous functions? If not, then how can I implement and clone Javascript objects with separated private/public code?
My recommendation is to keep the client side and server side loosely coupled. Try to build your modular client application completely with HTML/JS without PHP tricks on it. As I understand, each of your module (or UI component) need to be loosely coupled from the others. In such case there are several other concerns you might need to look for:
How to keep your UI component structure (html), presentation (css) and behavior (JS) self contained (for example in a single folder), so that it can live or die independently
How these self contained components interact with each other
How to manage the configurations/settings of your UI components
Should you be using MVVM or MVC pattern to organize and bind the view to your PHP model
Who decides when to create/show/hide your UI components (for example based on URL for bookmarking)
If your client is a large and complex application, you might need to look for other concerns such as JS optimization, unit testing, documentation, product sub modules, etc.
Have a look at the BoilerplateJS Javascript reference architecture we put forward at http://boilerplatejs.org. It suggests ways to address all concerns I discussed above.
Since you are already using jQuery, you could create a jQuery plugin. The plugin should behave the way you need, and I believe you won't even need a unique ID. Considering each of your module's instance is contained in a div with class module-container, your jQuery code for adding client-side behavior to the divs would be something like this:
$(function(){
// DOM content is loaded
$('.module-container').MyPluginName();
});
The minimal plugin code would be (considering it's in a separate .js file):
(function($){
$.fn.MyPluginName = function() {
// Return this.each to maintain chainability
return this.each(function() {
// Keep a reference to your unique div instance.
var $this = $(this);
// Plugin logic here
});
};
})(jQuery);
If you are using jQueryUI, I also recommend you also look into the "widget factory" (intro, docs), which serves as a base for building powerful, normalized jQuery plugins.

Ruby on Rails with Unobtrusive JavaScript - Managing URLs

I'm using a modular system of JavaScript files when working in Rails - basically each view will have its own module in a .js file. My issue comes when I need a dynamic, Rails generated string within my JavaScript, for example translation strings and URLs.
Translations are nicely solved using babilu but I'm still stuck on the generation of URLs. I could write something that looked at the routes in the application and generate JavaScript methods which I could pass stuff like IDs of objects.
An alternative would be to pass in the already-generated URL to any functions I was calling, which sounds messy but could be the most flexible alternative.
I don't know that there's any truly pleasing way to do this, but one possibility is to have your server-side code write a small <script> block into the page to declare some variables that your packaged Javascript can discover and use.
<script>
var pageGlobals = {
interestingURL: <% ... url %>,
...
};
</script>
I've done this to keep track of things like image subdirectories that are determined by customer "syndicate" affiliation. The Javascript code just knows that whenever it needs that for a URL, it can just go look in a global object and pick out a standardized variable.
In my experience there tend to be only a small number of such things to communicate to the canned Javascript, so the <script> block tends not to get out of hand. I've buried mine in a page template so I don't even have to think about it with new pages.
Oldish question, but here's another way. The HTML 5 spec allows for custom data- attributes.
In your view:
<button id="myButton" data-url="<%= my_resource_path %>">Click me</button>
Then in your packaged js:
var myurl = $("#myButton").data("url");
See here also.
I don't like this either. The ideal solution to me would be javascript templates. Imagine in the .js file you could do:
var users_path = '<%= users_path %>';
But that would mean the .js files would have to be regenerated in every request (well, one could use caching just like with rails html).
Anyway, what you can also do is put the dynamic stuff in data- attributes. So you can do for example
<%= select_tag :select_something, select_options, 'data-url' => users_url %>
And then read that attribute out in the javascript file. I prefer this over the solution suggested by Pointy.
Edit: Well actually someone implemented the dynamic .js file idea. Seems straight forward enough, just create a javascripts controller and link to its actions via javascript_include_tag: dynamic javascript files

Categories

Resources