'Trying to use select2 with Importmaps on Rails 7

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:[email protected]/dist/js/select2.js"
pin "jquery", to: "https://ga.jspm.io/npm:[email protected]/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:[email protected]/dist and npm:[email protected]/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...



Solution 1:[1]

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:[email protected]/dist/jquery.js", preload: true
pin "select2", to: "https://cdn.jsdelivr.net/npm/[email protected]/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:[email protected]/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/[email protected]/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

Solution 2:[2]

Thanks to Lam Phan. But it is not enough. For me, loading of select2 was resolved simple, then i'm expected. It resolved select2 initialization for development and production enviroments both.

See the vendor/assets/javascripts folder inside select2-rails gem. That folder contains the file select2.js

Add to your app/assets/config/manifest.js lines in such order:

//= link jquery.min.js
//= link select2.js

That is enough for me and pins and import working by default:

# config/importmap.rb

pin 'jquery', to: 'jquery.min.js'
...
pin 'select2'
pin 'application'
pin_all_from 'app/javascript', under: 'application'
// app/javascript/application.js
import 'jquery'
...
import 'select2'
# app/views/layout/application.html.haml
...
  %head
    = javascript_importmap_tags 'application'
// app/javascript/utils/select2_init.js
export class Select2Init {
  start() {
    $(function() {
      $('.select2').select2()
    })
  }
}

and run initializer in start point:

import { Select2Init } from 'application/utils/select2_init'

new Select2Init().start()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Sergio Belevskij