1

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.

Incerteza
  • 32,326
  • 47
  • 154
  • 261

1 Answers1

6

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.

Community
  • 1
  • 1
Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
  • `Add a separate JSON endpoint that returns the required data as JSON which can then be requested from your JavaScript` -- could you elaborate on that? – Incerteza Dec 14 '16 at 08:07
  • You can just add a route to your API pipeline, which will return a JSON response that includes the URL you need. For example you could return `{"createUserUrl": "..."}` here. Then, you can make an async request from JavaScript to this JSON endpoint (with jQuery, or XMLHttpRequest, or whatever you have at your disposal to make HTTP requests from JS) and extract the value from the response (I believe jQuery will do it for you, with plain JS you can use `JSON.parse`). BTW I think this might be complete overkill for your case, which is why I didn't write more about this option. – Patrick Oscity Dec 14 '16 at 11:32
  • that'll be 2 requests. – Incerteza Dec 14 '16 at 12:50
  • Yes, one for the app.js, and another one for the JSON containing the data. – Patrick Oscity Dec 14 '16 at 13:44