How do I use JQuery in a Phoenix app?

What do I have to do to use JQuery in a Phoenix app? Suppose I create my Phoenix app like this:

/phoenix_apps% mix new --umbrella a_umbrella
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating apps
* creating config
* creating config/config.exs

Your umbrella project was created successfully.
Inside your project, you will find an apps/ directory
where you can create and host many apps:

    cd a_umbrella
    cd apps
    mix new my_app

Commands like "mix compile" and "mix test" when executed
in the umbrella project root will automatically run
for each application in the apps/ directory.


~/phoenix_apps% cd a_umbrella/apps
~/phoenix_apps/a_umbrella/apps% mix new my_app --sup
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/my_app.ex
* creating lib/my_app/application.ex
* creating test
* creating test/test_helper.exs
* creating test/my_app_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd my_app
    mix test

Run "mix help" for more commands.

That gives me this directory structure:

~/phoenix_apps/a_umbrella% tree .
~/phoenix_apps/a_umbrella% tree .
.
β”œβ”€β”€ README.md
β”œβ”€β”€ apps
β”‚   └── my_app
β”‚       β”œβ”€β”€ README.md
β”‚       β”œβ”€β”€ lib
β”‚       β”‚   β”œβ”€β”€ my_app
β”‚       β”‚   β”‚   └── application.ex
β”‚       β”‚   └── my_app.ex
β”‚       β”œβ”€β”€ mix.exs
β”‚       └── test
β”‚           β”œβ”€β”€ my_app_test.exs
β”‚           └── test_helper.exs
β”œβ”€β”€ config
β”‚   └── config.exs
└── mix.exs

6 directories, 9 files

What do I need to do to use JQuery in a template?

First, do you really need an umbrella app ?
Second, you just generate a template for an umbrella app, please follow the steps in Overview β€” Phoenix v1.7.2, how to create a phoenix app and how to integrate third party JS libs

3 Likes

What is the use case for JQuery?

2 Likes

First, do you really need an umbrella app ?

I don’t know. I’m following the directions in β€œPhoenix in Action”, Section 5.1.2.

Now, I’ve done this:

~/phoenix_apps/a_umbrella/apps% mix phx.new.web my_web --no-ecto

giving me this directory structure:

~/phoenix_apps/a_umbrella/apps/my_web% tree -I "outline|solid"
.
β”œβ”€β”€ README.md
β”œβ”€β”€ assets
β”‚   β”œβ”€β”€ css
β”‚   β”‚   └── app.css
β”‚   β”œβ”€β”€ js
β”‚   β”‚   β”œβ”€β”€ app.js
β”‚   β”‚   └── my_setup.js
β”‚   β”œβ”€β”€ tailwind.config.js
β”‚   └── vendor
β”‚       β”œβ”€β”€ heroicons
β”‚       β”‚   β”œβ”€β”€ LICENSE.md
β”‚       β”‚   β”œβ”€β”€ UPGRADE.md
β”‚       β”‚   └── optimized
β”‚       β”‚       β”œβ”€β”€ 20
β”‚       β”‚       └── 24
β”‚       β”œβ”€β”€ jquery.js
β”‚       └── topbar.js
β”œβ”€β”€ lib
β”‚   β”œβ”€β”€ my_web
β”‚   β”‚   β”œβ”€β”€ application.ex
β”‚   β”‚   β”œβ”€β”€ components
β”‚   β”‚   β”‚   β”œβ”€β”€ core_components.ex
β”‚   β”‚   β”‚   β”œβ”€β”€ layouts
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ app.html.heex
β”‚   β”‚   β”‚   β”‚   └── root.html.heex
β”‚   β”‚   β”‚   └── layouts.ex
β”‚   β”‚   β”œβ”€β”€ controllers
β”‚   β”‚   β”‚   β”œβ”€β”€ error_html.ex
β”‚   β”‚   β”‚   β”œβ”€β”€ error_json.ex
β”‚   β”‚   β”‚   β”œβ”€β”€ page_controller.ex
β”‚   β”‚   β”‚   β”œβ”€β”€ page_html
β”‚   β”‚   β”‚   β”‚   └── home.html.heex
β”‚   β”‚   β”‚   └── page_html.ex
β”‚   β”‚   β”œβ”€β”€ endpoint.ex
β”‚   β”‚   β”œβ”€β”€ gettext.ex
β”‚   β”‚   β”œβ”€β”€ router.ex
β”‚   β”‚   └── telemetry.ex
β”‚   └── my_web.ex
β”œβ”€β”€ mix.exs
β”œβ”€β”€ priv
β”‚   β”œβ”€β”€ gettext
β”‚   β”‚   β”œβ”€β”€ en
β”‚   β”‚   β”‚   └── LC_MESSAGES
β”‚   β”‚   β”‚       └── errors.po
β”‚   β”‚   └── errors.pot
β”‚   └── static
β”‚       β”œβ”€β”€ favicon.ico
β”‚       └── robots.txt
└── test
    β”œβ”€β”€ my_web
    β”‚   β”œβ”€β”€ channels
    β”‚   └── controllers
    β”‚       β”œβ”€β”€ error_html_test.exs
    β”‚       β”œβ”€β”€ error_json_test.exs
    β”‚       └── page_controller_test.exs
    β”œβ”€β”€ support
    β”‚   └── conn_case.ex
    └── test_helper.exs

24 directories, 34 files

I tried adding the file:

// ...assets/js/jquery_setup.js

import jQuery from "../vendor/jquery"
window.jQuery = jQuery
window.$ = jQuery

I downloaded jQuery, and I put it here:

//…assets/vendor/jquery.js

My ../assets/js/app.js file looks like this:

// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"

// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
//     import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
//     import "some-package"
//

//Setup jQuery:
import "./jquery_setup.js"

// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})

// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())

// connect if there are any LiveViews on the page
liveSocket.connect()

// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000)  // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket

Second, you just generate a template for an umbrella app, please follow the steps in Overview β€” Phoenix v1.7.2 1, how to create a phoenix app and how to integrate third party JS libs

I read that before I posted, and I tried this:

Asset Management

Beside producing HTML, most web applications have various assets (JavaScript, CSS, images, fonts and so on).

From Phoenix v1.7, new applications use esbuild to prepare assets via the Elixir esbuild wrapper, and tailwindcss via the Elixir tailwindcss wrapper for CSS. The direct integration with esbuild and tailwind means that newly generated applications do not have dependencies on Node.js or an external build system (e.g. Webpack).

Your JavaScript is typically placed at β€œassets/js/app.js” and esbuild will extract it to β€œpriv/static/assets/app.js”. In development, this is done automatically via the esbuild watcher. In production, this is done by running mix assets.deploy.

esbuild can also handle your CSS files, but by default tailwind handles all CSS building.

Finally, all other assets, that usually don’t have to be preprocessed, go directly to β€œpriv/static”.

third-party-js-packages Third-party JS packages
If you want to import JavaScript dependencies, you have two options to add them to your application:

Vendor those dependencies inside your project and import them in your β€œassets/js/app.js” using a relative path:

import topbar from β€œβ€¦/vendor/topbar”

In my browser’s javascript console, I keep getting the error:

ReferenceError: Can’t find variable: $

What is the use case for JQuery?

The last time I wrote javascript I used jQuery, which was what everyone was using. Before that, I wrote all my js by hand.

I just want to use a couple of the jQuery functions. But, I think jQuery is largely irrelevant to my question: suppose I have 50 lines of js code which consist of functions I’ve written, and I want to use them in my templates. Where do I put my js file, and what do I have to do so that my js functions can be called in my templates?

These days you would want to create a js module to store all your plain js functions, or third-party functionality from npm modules for example, then export them to be imported from app.js, or if the functions add behaviour that you want to apply to a LiveView component/element, import them into a LiveView Client hook with the phx-hook attribute.

1 Like

If you don’t know if you need an umbrella application, I would propose you do not need one. Just start with a simple phoenix application using mix phx.new
I’ve the book too, besides others, a lot others ;-), but I’ve not read it through, especially not that mentioned part.
Personally, I would recommend reading β€œElixir in Action” by Sasa Juric, then you may read β€œPhoenix in Action” to understand the basics of phoenix or go directly to β€œProgramming Phoenix LiveView” by Bruce A. Tate and Sophie DeBenedetto.
The you may decide if you really need JQuery.
If you are an online learning guy, I would recommend the Elixir/OTP and Phoenix LiveView courses from ProgamticStudio and Grox.io Learning

2 Likes

For me one of the best courses and book that made me switch my brain from Object Orientated Programming to Functional Programming was the book and course from @pragdave:

Video Course:

Book:

I would highly recommend to read this book or take the course prior the other Elixir Resources, which I also highly recommend to read after this one.

In this case he his just learning from the book, but I agree with your affirmation.

An alternative to Umbrella apps are poncho projects that simply are a mix project in a parent folder, that you can then require as a dependence {:hangman, path: "../hangman"},:

When coding web aplications we may want to extract all the core functionality not directly related with serving the web or api requests to a poncho project to allow us for an easy decoupling and possibility of reusing it in other projects, like a CLI interface or whatever.

Using poncho projects also has the benefit of allowing us to move more easily to a microservices architecture if the need arises in the future.

I asked because if not was something very specific to it we could suggest you more modern approaches to achieve the same.

For example the moder way of using JQuery alike functionality is via AlpineJS:

https://fullstackphoenix.com/tutorials/combine-phoenix-liveview-with-alpine-js

The PETAL (Phoenix Elixir Tailwindcss Alpine LiveView) stack is nowadays very common in Phoenix projects.

I never tired to use JQuery in a Phoenix app, but you can try this answer from Stackoverflow:

import $ from 'jquery'
window.jQuery = $
window.$ = $

I tried creating a js module, but it doesn’t work for me. Here’s what I tried:

…/assets/vendor/my_functions.js


export function hello() {
  alert("My javacript functions got imported!")
};

…/assets/js/app.js

import {hello} from "../vendor/my_functions.js"  

Without the braces around hello, I got the following error:

No matching export in "vendor/my_functions.js" for import "default"

    js/app.js:21:7:
      21 β”‚ import hello from "../vendor/my_functions.js"
         β•΅        ~~~~~

Template:

<section>
  <div id="fade_target">Hello world, from Demo!</div>
</section>
<script>
  window.onload = function() {
      hello()
  } 
</script>

Error in the javascript console in my browser:

ReferenceError: Can’t find variable: hello

which points here:

            <section>
                <div id="fade_target">Hello world, from Demo!</div>
            </section>
            <script>
            window.onload = function() {
                hello()   <===============================
            }
            </script>

For me one of the best courses and book that made me switch my brain from Object Orientated Programming to Functional Programming.

I spent a few years learning erlang before elixir.

I’ve already read it.

1 Like

I purchased β€œElixir in Action” using a discount code on this forum. I’ve read part of it. It is very good. At the same time, I purchased β€œPhoenix in Action”, and I’m revisiting it now.

1 Like

Does your umbrella project happen to be created using an older version of the Phoenix generators?

% elixir --version
Erlang/OTP 24 [erts-12.3.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]

Elixir 1.14.4 (compiled with Erlang/OTP 24)

I’m using Phoenix 1.7.2, which I installed a few days ago with the command:

$ mix archive.install hex phx_new

mix.exs:

defmodule MyWeb.MixProject do
  use Mix.Project

  def project do
    [
      app: :my_web,
      version: "0.1.0",
      build_path: "../../_build",
      config_path: "../../config/config.exs",
      deps_path: "../../deps",
      lockfile: "../../mix.lock",
      elixir: "~> 1.14",
      elixirc_paths: elixirc_paths(Mix.env()),
      start_permanent: Mix.env() == :prod,
      aliases: aliases(),
      deps: deps()
    ]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [
      mod: {MyWeb.Application, []},
      extra_applications: [:logger, :runtime_tools]
    ]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [
      {:phoenix, "~> 1.7.2"},
      {:phoenix_html, "~> 3.3"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:phoenix_live_view, "~> 0.18.16"},
      {:floki, ">= 0.30.0", only: :test},
      {:phoenix_live_dashboard, "~> 0.7.2"},
      {:esbuild, "~> 0.7", runtime: Mix.env() == :dev},
      {:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev},
      {:telemetry_metrics, "~> 0.6"},
      {:telemetry_poller, "~> 1.0"},
      {:gettext, "~> 0.20"},
      {:jason, "~> 1.2"},
      {:plug_cowboy, "~> 2.5"}
    ]
  end

  # Aliases are shortcuts or tasks specific to the current project.
  #
  # See the documentation for `Mix` for more info on aliases.
  defp aliases do
    [
      setup: ["deps.get", "assets.setup", "assets.build"],
      "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
      "assets.build": ["tailwind default", "esbuild default"],
      "assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"]
    ]
  end
end

I’ll try adding my js file to a non umbrella project and see if I get different results.

I also highly recommend Elixir in action, but PragDave is the only one that spends the time to make your mind shift from OOP to FP and thats why I always recommend his book first.

Another excellent book is Real-Time Phoenix by @sb8244:

This will import hello into the scope of app.js, but will not make it available to the HTML document automatically unless you assign the function to the global window element.

// file: assets/js/app.js
import {hello} from "../vendor/my_functions.js" 
window.hello = hello

A good next step would be to namespace your exported functions, then assign that to the global element:

// file: assets/vendor/my_functions.js
export function hello () {...}
export function etc () {...}

// file: assets/js/app.js
import * as MyFunctions from "../vendor/my_functions.js" 
window.MyFunctions = MyFunctions

// in HTML <script>
window.onload = function () {
  MyFunctions.hello()
  MyFunctions.etc()
}

This can start to get unwieldy when you have a lot of functionality in multiple HTML scripts.

I would instead move them to app.js and set up your code from there:

// file: assets/js/app.js
import * as MyFunctions from "../vendor/my_functions.js" 

window.onload = function () {
    MyFunctions.hello()
}

// now this is only necessary if you also want to access them directly from the dev console
window.MyFunctions = MyFunctions

Lastly, if app.js also gets too large for your liking, or you only want to have certain features running in specific pages, you can move the relevant code to its own module, have esbuild compile it separately, then load it into your page.

// file: assets/js/my_feature.js
import { hello } from  "../vendor/my_functions.js" 

button = document.querySelector("#someButton")
button.addEventListener("click", () => { hello() }

add js/my_feature.js to the esbuild args string (and restart the server to apply changes to config files)

# file: config/config.exs
config :esbuild,
  version: "0.17.11",
  default: [
    args:
      ~w(js/app.js js/my_feature.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]
<!--- my_page.html.heex or inside render/function component ~H template -->
<script defer phx-track-static type="text/javascript" src={~p"/assets/my_feature.js"}>
</script>

<button id="someButton" type="button">Run hello()</button>
6 Likes

Hurray! Clap, clap! I got all the methods you suggested to work. Somebody understands scope in javascript!

Out of curiosity, why didn’t my attempts with JQuery work? I clicked the link:

Download the uncompressed, development jQuery 3.6.4

and I copied the code and put it in ..assets/vendor/jquery.js.

In ../assets/js/jquery_setup.js, I have:

import jQuery from "../vendor/jquery"
window.jQuery = jQuery
window.$ = jQuery

And, in app.js I have:

import "./jquery_setup.js"

But, in my browser I get an error in the js console that says $ isn’t recognized. I looked through the jQuery code, and I don’t think it exports β€œjQuery” or β€œ$”. If it would take too much time to figure out, don’t bother.

1 Like

That’s a very good question. This style is a side effect only type of import that should run the script’s global code. :thinking:

Maybe try to compile it separately in the esbuild args string and import it as a separate asset in the HTML head, and skip the extra step through app.js?

Or just straight-up inline the jquery_setup.js code in app.js?

1 Like

I couldn’t get jQuery to work. Thanks for your help.