I have a js "my_js_file.js" file which I include only into a single page. In that js file I need to get an url by calling a phoenix helper:
# my_js_file.js
function func1(userLogin) {
var createUserUlr = "<%= user_home_path(#conn, :create, userLogin) %>";
//...........
But it doesn't work.
When I rename it to "my_js_file.js.eex", it still doesn't work -- the value between "<%= %>" isn't getting evaluated. Why not? And what are the options?
Note that I'm passing a variable to the url.
I don't think this is possible, because the assets in the static/ directory are not processed by Elixir, but by your asset pipeline (Brunch by default). They are pre-compiled so even if it worked, you would not have access to the #conn variable, since this is not available at the time when static assets are compiled.
Keep in mind this is a good thing! The server does not need to re-render your Javascript on every page request.
You have several options to get the desired result:
Hard coding
Might seem hacky, but works well enough for simple cases. I recommend hard coding just a path, using // at the start to preserve the current protocol (http/https). If you need to resolve this to a full URL, you can use this trick.
Data attributes
For one-off usage, you can add a data attribute to a related portion of your markup, for example for a login form:
<div id="create-user" data-url="<%= user_home_path(#conn, :create, userLogin) %>">
<!-- ... -->
</div>
Then in your JavaScript, you can access the data attribute. Note that depending on your use case, there might be a better way to retrieve the DOM node containing the data attribute than using document.querySelector, this is just an example:
let createUserUrl = document.querySelector("#create-user").getAttribute("data-url");
If you only need to target IE11+ or willing to use a polyfill, you can also use dataset
let createUserUrl = document.querySelector("#create-user").dataset.url;
Or if you're using jQuery:
let createUserUrl = $("#create-user").data("url");
Maybe you'll be able to extract the URL from a different attribute such as the url of a form, should you be overriding the onclick handler of the submit button, for example.
Add property on window object
For truly global values, such as authentication tokens etc. you can set a property on the window object, for example in web/templates/layout/app.html.eex
<script>window.userToken = "<%= assigns[:user_token] %>";</script>
then in your JavaScript, you can simply access
window.userToken
Other solutions
There are also more advanced solutions available for this problem, including:
Add a separate JSON endpoint that returns the required data as JSON which can then be requested from your JavaScript
If you use a JavaScript framework such as React.js, Vue.js, etc. you might be able to leverage routing logic from the framework or auxiliary JavaScript packages.
I'm sure there's even more options I didn't think of right now.
Related
I am using a ASP.NET route (to intercept the call to the .js) and controller to generate some JS I want to use on my client. The reason I'm doing this is so as to not have to duplicate id's or constants on the client. Here's the output of my JS:
app.serviceRootURL = 'http://localhost:65211/'; // set in my web.config
app.ajaxResponseStatuses = [
{ "status":"Success", "id":0 }, // set in my C# DTO
{ "status":"Failure", "id":1 },
];
First of all, I am not sure if this is the best approach, so other suggestions to doing this would be beneficial.
More importantly though, I'm wondering how I can bundle and minify this. As I understand it, even if I could minify the JS at compile or run-time, minification will change the names of my variables. So in the above JS, app.ajaxResponseStatuses could get changed to a.bc, and then in the actual JS files where I'm trying to access that variable, they could be looking for x.yz.
Can I minify this code and get it to the server?
Will I still be able to use the above properties in other minified files?
(bonus points) Is this a good aproach to pass server-side-only values to be used on the client?
Part 1
If you are generating the js at runtime, bundling isn't possible (at least not efficiently). You would have to create a new bundle for every request which isn't terribly quick. Plus, you wouldn't be able to cache the regular, constant script bundle.
EDIT: While bundling server-generated js isn't practical, rendering the values into a script tag in the page can achieve the same benefit of bundling, fewer HTTP calls. See the edit in Part 3 for more.
Minifying the server generated js however, is totally possible. This question should have the answer you're looking for. However, I'd recommend you cache this on the server if possible, as the minification process itself could take longer than simply sending down the extra bits.
Part 2
In most minifiers, global variables (those accessible on the window object) are skipped during the name mangling. With the same respect, variables that are accessed in other files that are not defined within that file are not renamed.
For example, if you have the following file...
// outside of a closure, so globally accessible
var foo = 1;
function bar() {
// within a closure, and defined with `var`, not globally accessible
var bar;
// reference to variable declared in another file
baz = null;
}
it would be minified as follows (with whitespace included for readability
var foo = 1;
function bar() {
var b;
baz = null;
}
This is one reason it is important to always declare your variables using the var keyword, otherwise they are assumed to be references to global variables and will not be minified.
Also, JSON (not Javascript object literals!!!) will never be distorted by minifiers, because it consists of string literals for all keys, and all values that aren't of another literal type.
Part 3
Not a bad way, and at my job we do use this approach. For small files though, or simple config values, we have transitioned to rendering server values in a script tag using ASP.NET in the actual view. i.e.
Default.aspx
<script> window.globals = <%= JsonConvert.SerializeObject(new AppGlobals(currentUser)) %>; </script>
We rip this out into a code behind, but the premise is the same.
EDIT:
Server-Generated JS (at it's own uri)
Pros
Cacheable by browser (if fresh values aren't needed on every request)
Cons
Extra round trip
Use when:
Your generated files are large, but rarely change or are the same for multiple users. These scripts can be treated the same as other static assets. To give an example, we serve a js file containing all the text in our app for localization purposes. We can serve a different js file based on the language set in the user's settings, but these values only change once at most with every release, so we can set aggressive cache headers and use a hash in the uri, along with a query string for the locale, to leverage browser caching and download each language file only once per client. Plus, if this file is going to be the same for every user accessing the same uri, you can cache it at the web server (IIS, Apache, etc.).
Ex: /api/language.v1-0-0.js?locale=en
Your js is independent from the rest of your app and not having it won't delay rendering. In this case, you can add the async attribute to your script tag, and this file will be downloaded asynchronously and executed when it is received without preventing the execution of other javascript.
Server-Rendered JS (within the page in a script tag)
Pros
No extra HTTP calls
Cons
Can add extra weight to your HTML, which may not be cacheable or minified depending on your circumstances
Use when:
Your values change often. The weight added to the page should be negligible unless you have a huge number of values (in that case, you might consider splitting them up and adding API endpoints for these values, and only getting them when you need them). With this, you can cut out the extra HTTP call as the js is injected into a script tag on a page the user would already have to retrieve.
But...
Don't waste too much time worrying about it. The differences in these two approaches is almost always negligible. If it becomes a problem, try both and use the better option for your case.
I'd like to use a little bit of simple AJAX in my Rails application. The main problem I have is that inside a script I need to specify the URL where the request will go, which in most cases is a path to some Rails controller action. Anywhere else I'm advised not to write paths explicitly by hand (i.e. not do ugly stuff like '/my_resources/' + resource_id) but use helpers like my_resource_path instead.
But this approach doesn't work well with javascript, since these helpers don't work inside assets/javascripts. I can think of some ugly ways I can bypass the problem, currently I've implemented an extremely ugly workaround which is basically putting something like this inside my view:
<%= javascript_tag "onSubmitQuotePage('#{j escape_javascript(autocomplete_authors_url(''))}');"%>
But I can't imagine Rails developers didn't think of some prettier solution, some right way of doing AJAX.
I can't say I like the way I do this so very happy to hear better ways of doing this.
But I either include the path in a data attribute on a relevant DOM element, or for some static routes I include a <script> block in the layout file that contains relevant paths.
<script>
(function() {
"use strict";
window.myapp || {};
window.myapp.new_order_path = '<%= new_order_path %>';
window.myapp.orders_path = '<%= orders_path %>';
...
}());
</script>
It's by no means a pretty solution, but the cases where I need a route in my JS are rather rare and that way I can then use myapp.new_order_path in my JS when I need it.
Just to add my two cents here: you can use Rails URI helpers to generate some sort of URI templates. E.g., if you have this route defined:
edit_user GET /users/:id/edit(.:format) users#edit
And you call it like edit_user_path(':user_id:'), it will return /users/:user_id:/edit. This way you can generate URI templates to be compiled by javascript. As #Tigraine said, you can include this in a data attribute of some top element, and use it from the client side.
It comes really in situations where you need to generate URIs for AJAX fetched resources: you just serve the resource id and let the javascript compile the URI from the template with a simple string.replace(':user_id:', user_id).
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.
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
I observed chunks like below sometimes on web pages. So i am curious to know what this really does? or why its written in such a way?
<script src="somefile.js?param1=one¶m2=two" />
i can only make out following few intentions behind it
Its not page URL (i mean .aspx/.php/.jsp etc.) so its not hacking kind of code where user can add code like this to pass data without getting users attention as its tag which does not render on UI OR implementing old type of AJAX alternative
This kind of URL param are useful if user do not wish the JS file (any other resource like image) to get cached. This can be quick way to manage caching
But i am unable to figure out following
Looks like page URL parameters but are these parameters anyway readable in JavaScript file and have some additional utility?
Do these parameters have any extra role to play here ?
What are the other possible practical scenarios where code like this can be/is used?
So please provide some inputs related with the same
Thanks,
Running Non-JS Code within .js Extensions
In cases like this, that source .js file might (given proper server-configurations) actually have PHP/.NET code within it, which can read those appended values.
As you said, Avoiding Cache...
Additionally, people will at times append a random string at the end of their referenced elements to avoid loading cached data.
The URL having '.js' means nothing. It could still be handled by a server-side script like an ASP or PHP.
Either the javascript file is not static (it is generated by the server based on the parameters in its querystring)
OR
In the JavaScript file itself, you can have it check its own querystring parameters (not just that of the page, but that of the javascript source url).
OR
(This doesn't exactly match your scenario, but) you can also add parameters at the end of image and script urls as a way of versioning. The version with the url="somescript.js?V=3" will be cached by the user until the page then changes and the url is not="somescript.js?V=4". The file will be replaced by the version on the server no matter what the browser setting may be.
My guess (without looking at this specific case) is that the javascript file is reading its own querystring. I have done this, and its very helpful.
Looks like page URL parameters but are these parameters anyway readable in JavaScript file and have some additional utility?
Yes you can read them in JavaScript, Scriptaculous uses that approach for loading modules, eg:
<script type="text/javascript" src="scriptaculous.js?load=effects,dragdrop">
</script>
Do these parameters have any extra role to play here ?
What are the other possible practical scenarios where code like this can be/is used?
That can be also used for server-side script joining and minifying, of course using some url rewriting technique to have the .js extension, and as you say, it's a common technique to add timestamp parameters to break the browser cache.
It can be used for three different reasons:
1) To generate the JavaScript file in the server depending on the parameters;
2) To avoid caching;
3) To pass parameters to JavaScript itself
An example of this in practice would be a server side handler for somefile.js that uses the parameters (names of other scripts) to determine which scripts are actually required and combine/minify them, returning them as a single somefile.js script file.