Trying to use select2 with Importmaps on Rails 7
Asked Answered
T

2

10

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...

Talkington answered 8/2, 2022 at 13:36 Comment(0)
S
17

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

Stomachache answered 10/2, 2022 at 10:37 Comment(5)
thanks for the exhaustive answer! I did change the importmap for jquery to preload, and I also changed the source for select2. However I am now a bit confused. I am simply trying to call select2 on a form.... in Rails 6, I would have used a special class and then via javascript used something like $('.class-name').select2() .... why would I need to load select2 on a stimulus controller?Talkington
@Talkington no need to use stimulus, take a look at my update.Stomachache
ok thanks - this worked. I now have a problem that in my form I have TWO select boxes (one triggered by select2 and one other one) but I think this is a stylesheet problem... I think I now need to import the corresponding select2 stylesheet.Talkington
Two more things worth noting: The npm version of select2 does not seem to work. As @LamPhan correctly stated, the official CDN version of select2 has to be referenced in the importmap. Also, I'm not sure whether the module preload is really required - the importmap thing may figure out that jQuery has to be loaded before select2 (for me, it works without preload: true).Kurgan
To make things work even better, I want to remind you to add the CSS file of select2Biondo
E
1

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()
Elkin answered 5/5, 2022 at 16:32 Comment(1)
Thank you Sergio. I actually run into so many other issues with importmaps, that I switched to jsbundling.Talkington

© 2022 - 2024 — McMap. All rights reserved.