I am trying to use Select2 on a new Rails 7 app and am struggling as follows:
I have pinned it into my import maps and imported it like so:
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "trix"
pin "@rails/actiontext", to: "actiontext.js"
pin "select2", to: "https://ga.jspm.io/npm:select2@4.1.0-rc.0/dist/js/select2.js"
pin "jquery", to: "https://ga.jspm.io/npm:jquery@3.6.0/dist/jquery.js"
(the two last lines were added when I run bin/importmap pin select2)
import "jquery";
import "select2";
import "@hotwired/turbo-rails";
import "controllers";
import "trix";
import "@rails/actiontext";
(have moved both jquery and select2 to the end as well as to the beginning - didn't change a thing).
When I am in a form, I can access an element with $ like so:
$('#book_genre_ids');
...(returns the element)
But when I manually try - in the console - to run select2() on an element, here's what happens:
$('#book_genre_ids').select2();
VM574:1 Uncaught TypeError: $(...).select2 is not a function
at <anonymous>:1:22
I did check the network sources (chrome console), and I could find npm:jquery@3.6.0/dist and npm:select2@4.1.0-rc.0/dist from gap.jspm.io. I found some resources that pointed at multiple jquery libraries being loaded, but I didn't find more than the above in the network sources in the console...
Select2 will register itself as a jQuery function .select2()
, so in order to using this method, the select2
lib must be loaded after the jquery
lib, otherwise it could not find jquery
reference, hence it could not register jquery
function, hence the error $(...).select2 is not a function
will be throw.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js" />
However, the import
is asynchronous loading so it's not guarantee that when select2
look for jquery
, this lib is loaded. So although you setup import them in order as below:
import "jquery";
import "select2";
The select2
is still not found the jquery
when it needed.
Fortunately, the gem importmap-rails support preloading pin modules (modulepreload), so base on that i come up with a solution: preload jquery
before select2
# config/importmap.rb
pin "jquery", to: "https://ga.jspm.io/npm:jquery@3.6.0/dist/jquery.js", preload: true
pin "select2", to: "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"
Then i follow the way Rails7 setup "@hotwired/stimulus" to setup "jquery"
// app/javascript/controllers/application.js
import { Application } from "@hotwired/stimulus"
...
import jQuery from "jquery"
window.jQuery = jQuery // <- "select2" will check this
window.$ = jQuery
...
now on stimulus controllers where "select2" is needed, you could load "select2"
// app/javascript/controllers/demo_controller.js
import { Controller } from "@hotwired/stimulus"
import "select2"
export default class extends Controller {
initialize() {
$('.js-example-basic-multiple').select2();
}
// ...
Note: your "select2" CDN "https://ga.jspm.io/npm:select2@4.1.0-rc.0/dist/js/select2.js" source contain import e from"jquery";
at the first line hence it'll not work in this solution, so i recommend using the official cdn link: "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js" instead.
update
In case you don't want to use stimulus, you could pin another js file to setup "select2" and load it in your layout views
// app/javascript/utils.js
import "select2"
$(document).ready(function () {
$('.js-example-basic-multiple').select2();
});
# config/importmap.rb
...
pin "utils"
# app/views/products/show.html.erb
<%= javascript_import_module_tag("utils") %>
...
Note: you have to use javascript_import_module_tag
, not javascript_importmap_tags
, reference