JavaScript Assets in Phoenix

Hi Folks,

I am working on a personal app using Phoenix, and I had the need to add a library (via npm) in order to make my table sortable by headers. I took a look at the official guide on static assets, and after a read I did the following:

  • Install the library via npm: npm install tablesort --save
  • Created a new file: web/static/js/sort.js with the following code:
import tablesort from "tablesort"

export const Sort = {
  makeSortable: () => {
    const table = document.getElementById('sortable')
    tablesort(table)
  }
}
  • Then, I import that object into web/static/js/app.js, like so:
import { Sort } from "./sort"

export const App = {
  sortTable: Sort.makeSortable
}
  • Finally, in my app.html.eex layout template, I invoke that function:
<script>require("web/static/js/app").App.sortTable()</script>

The general idea is simple. I define a function that achieves my goals, and then I expose that function to my layout so that it can be invoked.

The reason I went into a bit of detail on what I did is because I wanted to ask two questions:

  1. Is this the idiomatic way of doing things?
  2. At the moment, I can use require only in the app layout, not in another template. What is special about the layout that it doesn’t raise an error when I use require, and can I (should I?) use require in a template in some way?

Thanks folks!

2 Likes

I’d honestly skip the adding it to the app and instead in the page I’d just do <script>require('web/static/js/sort')()</script> but otherwise it seems normal?

You should be able to use it anywhere in the HTML where your app script is loaded ‘before’ your app.js import. I actually load my app.js async and I send an event when it is loaded so I check if it exists and if not wait on the event:

<script>
// This is in the head, before anything else really in my layout template
function run(cb) {
  if (!require) window.addEventListener('AppLoaded', cb)
  else cb()
}
</script>

Then just use it like this or so:

run(function(){
  require('blah').doSomething();
})
2 Likes

@OvermindDL1 - thank you very much for your response.

The problem that I was having when trying to move

<script>require("web/static/js/app").App.sortTable()</script>

into my template (not the layout), was that the entire template (including that script element) is rendered before

<script src="<%= static_path(@conn, "/js/app.js") %>"></script>

I simply moved <script src="<%= static_path(@conn, "/js/app.js") %>"></script> right after the body tag and then I was able to use require... in my template. I guess the docs obscure the fact that require is made available by <script src="<%= static_path(@conn, "/js/app.js") %>"></script>, and that has to be loaded and executed before we can call require.

Thank you!

2 Likes

That is all standard javascript ecosystem stuff. :slight_smile:

Consequently, that is also why I put a global event out from the bottom of my app.js file, once it is loaded and that event is sent, then it calls my callbacks like in my above code, then not only does it not matter ‘where’ the script tag is, but you can even add the async keyword to it so the javascript downloads at the same time as everything else instead of being serialized. :slight_smile:

1 Like

@OvermindDL1 - Understood. Thanks again!

2 Likes

@StevenXL, I have a super 101 question. When you refer to the path web/static/js, what’s the path from your Phoenix root?

I have:

assets/js
priv/static/js

but no static/js at the root or in lib/project_web/

I had to do a lot of experimenting after reading the original reference document http://phoenixframework.org/blog/static-assets and this lovely post before I finally got things to work right for my own problem. I was struggling with calling a new JavaScript function in a new JavaScript file that takes arguments.

I decided to write up what I did. Is there a better way to handle the inline invocation in the template in my final step? All feedback greatly welcomed, as I’m still very new to Phoenix, Brunch and learning JavaScript properly after 20 years of stubbornly dodging it and mostly copying what I needed. :innocent: