JavaScript inaccessible from Phoenix subview

I’m trying to call some JavaScript which listens for a click event on a button. However, I am piping the subview through

|> put_layout(false)

in the controller to avoid view inception, which is stripping out the JavaScript call in the layout view. When I add the JavaScript call helper back in I get a different sort of infinite loop which results in various visual glitches and a whole bunch of websockets calls…

I can likely solve this by adding the JavaScript inline within the subview, but I wondered if that’s the best way to do it.

The JavaScript code I want in the subview is currently:

if(window.location.pathname.match("/tutorials/section1/")) {
  $(".button").click(function() {
      $(".result").append("<p class='success'>Success</p>")
    } else {
      $(".result").append("<p class='error'>Error</p>")

But I’m also wondering if it might be possible to do this in the controller in Elixir and not involve JavaScript at all.

p.s. you guys and your community are frickin’ awesome and are restoring my faith in code-humanity.

1 Like

Can you share some more code showing your current approach?

What are you trying to achieve? Are you loading a part of the application (the subview) using ajax and inserting via javascript? If that is the case and the click handler you’re showing should work on a button inside the subview, then what you’re struggling with ( I think ) is the fact that the button doesn’t exist when you’re trying to hook it up to the event handler.
What you could do is use .on ( instead of .click to bind the event. It supports delegated events which is what you’re probably looking for.

1 Like

Hi jdk and thanks for replying. I’ll try with the .on event handler. Sorry for the late reply but I got a bit overwhelmed with coding recently and had to take a bit of a break. I’ll let you know if it works.

1 Like

Unfortunately $(".button").on("click", "tr", function() does the same thing. The problem I think is that I’m calling the app.js file in the layout view like this: <script src="<%= static_path(@conn, "/js/app.js") %>"></script>. However, in my subview I have to strip out the layout view to stop the elements in the layout view I don’t want from appearing in the subview. I’m doing this using the following Elixir code in the controller:

def linux3(conn, _params) do
    |> put_layout(false)
    |> render("section1/linux/3.html")

The problem is that because the layout view calls the JavaScript, when the subview is piped through |>put_layout(false) the JavaScript is also stripped out.

I have isolated this is the case, because when I add the script inline into the subview it works correctly; however, this violates separation of concerns and – more importantly in my view – makes it difficult to share app-wide functionality with the subview, potentially adding large amounts of boilerplate, which would likely lead to substantial bloat as the application scales. However, if I call in app.js from the subview I get all of the general code called for the layout view, which among other things, leads to it repeatedly drawing some aspects of the page that are dynamically controlled, repeatedly.

The ideal solution, then, would be to call in each individual JavaScript file I need for this particular view, but I can’t get Phoenix to directly access any internal JavaScript file but app.js without throwing an error.

1 Like

How about a additional layout template, which does not render any html chrome, but does add the javascript file? Then you can do |> put_layout("plain.html").


That does sound like a potential solution. I wasn’t yet aware the pipe works like that :). I’ve a couple of things I need to finish off and then I’ll check and make sure it works, but it certainly sounds like it will.

1 Like

Ok, I’ve had a look and it seems the appropriate solution is a combination of your suggestion about possibly using another layout view (very useful) and changing the brunch file; specifically this section:

files: {
javascripts: {
  joinTo: "js/app.js"

  // To use a separate vendor.js bundle, specify two files path
  // joinTo: {
  //   "js/app.js": /^js/,
  //   "js/vendor.js": /^(?!js)/
  // }
  // To change the order of concatenation of files, explicitly mention here
  // order: {
  //   before: [
  //     "vendor/js/jquery-2.1.1.js",
  //     "vendor/js/bootstrap.min.js"
  //   ]
  // }

gives some very strong hints on how to enable concatenation into multiple JavaScript files, allowing for per-page JavaScript loading. Something that’s pretty much an absolute necessity for larger applications.

1 Like