Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Select2 with ajax gets initialized several times with Rails turbolinks events

I am a developing a Ruby On Rails app using Rails 4.2.6. I am using Turbolinks alongside jquery.turbolinks (sorry I could'nt post the links to those elements as I am a newbie on the site). My problem is very simple but I just can't solve it. Here it is: I have a form fetched through AJAX

<div class="card-footer">
  <a class="btn btn-sm btn-primary-outline" data-remote="true"  href="/profiles/Mke5kA/positions/new"><i class="fa fa-plus"></i> Nouvelle expérience professionnelle</a>
  <div id="new_position_form"></div>
</div>

The form contains Select2 elements that get their data through AJAX

= simple_form_for [profile, position], remote: true, html: {id: 'positionForm', class: 'm-b-1'} do |f|
  = f.input :company_id, as: :select, input_html: {:'data-behaviour' => 'company-select2', :'data-kind' => 'company'}
  = f.input :title
  = f.input :summary
  - location = f.object.build_location
  = f.simple_fields_for :location do |l|
    = render 'locations/fields', l: l, city: position.city
  = render "profiles/shared/date_fields", f: f, model: position
  = f.input :skill_list, as: :select, input_html: {multiple: true, :data => {:behaviour => 'acts-as-taggable', :'taggable-context' => 'skills'}}
  %button.btn.btn-primary{:type => "submit"}= icon('check-square-o', 'Enregistrer')
  = link_to icon('remove', 'Annuler'), 'javascript:void(0)', 
        data: {:'lgnk-behaviour' => "remove-form", :'lgnk-target' => "#positionForm" }, class: 'btn btn-secondary'
  • The Select2 elements are "activated" currently upon Rails Trubolinks events "page:load page:update", but I have also tried "page:change"
  • When the form is fetched: the select2 elements are fine (activated correctly):

Initial form (after AJAX fetch

My problem appears when I try typing in the Select2 that are using AJAX to get the data: all the select2s are duplicated:

enter image description here

Here is how I get the Select2 initialized:

var loc_tag = function() {
  $('[data-behaviour="acts-as-taggable"]').not('.select2-hidden-accessible').each (function (index, element) {
    if ($(element).data('value')) {
      var options = $(element).data('value').split(', ');
      $.each(options, function(key, tag){
        $(element).append($('<option selected></option>').val(tag).text(tag));
      });
    }

    $(element).select2({
      ajax: {
        url: "/tags?context="+$(element).data('taggable-context'),
        dataType: 'json',
        headers: {
         "Accept": "application/json"
        },
       delay: 250,
       data: function (params) {
         return {
           q: params.term, // search term
           page: params.page
         };
       },
       processResults: function (data, page) {
         return {
           results: data
         };
      },
      cache: true
    },
    escapeMarkup: function (markup) { return markup; }, // let our custom formatter work
    minimumInputLength: 2,
    tags: true,
    language: "fr",
    theme: "bootstrap",
    width: "100%",
    placeholder: 'Mots clés...'
    });
  });

};
$(document).on('page:load page:update', loc_tag);

I want the Select2 elements to get initialized only once (when the form is fetched) and not upon AJAX responses on them getting their data. I have tried jQuery.not(".select2-hiden-accessible") on the elements unsing Select2 (select2-hidden-accessible being the class Select2 adds to an initialized Select2 element) but it does not work.

Many thanks for your kind help!

like image 259
morismael Avatar asked Apr 08 '16 10:04

morismael


4 Answers

When using Turbolinks 5 and select2, the select2 object is no longer attached (see below for test) to the <select> when using the back button to return to a page. A new select2 object is created and attached after going back but it was unusable.

jack's answer didn't work for me because when the new select2 object is added, the <select> still has class='select2-hidden-accessible' which, among other things, sets width: 1px !important. When the new select2 object is created it's basically invisible.

The key for me was to destroy all select2 objects before TL caches the page. Here is the solution that worked for me:

$(document).on("turbolinks:before-cache", function() {
  $('.select2-input').select2('destroy');
});

$(document).on('turbolinks:load', function() {
  $('.select2-input').select2();
});

More Detail

I believe this is the correct approach given the Turbolinks documentation (emphasis mine):

Preparing the Page to be Cached

Listen for the turbolinks:before-cache event if you need to prepare the document before Turbolinks caches it. You can use this event to reset forms, collapse expanded UI elements, or tear down any third-party widgets so the page is ready to be displayed again.

Testing select2 Existance

To test if the select2 object is attached to the <select> you can execute the following in the console:

('.select2-input').first().data('select2')
like image 157
Matt Avatar answered Nov 17 '22 09:11

Matt


I have the same problem and I found that when you press the back button both the select and the select2 elements are rendered but they are not bound together so when you re-initialize it with $('select).select2() it creates another brand new select2 element next to it.

So here's what I did before initializing the select2:

If the select is not a select2 (i.e. $(el).data('select2') == undefined) but there is already a select2 element next to it, then remove it.

if ($(el).data('select2') == undefined && $(el).next().hasClass('select2-container')) {
  $(el).next().remove();
}
$(el).select2();
like image 29
Jack Avatar answered Nov 17 '22 11:11

Jack


2021 UPDATE:

Using the turbolinks:before-cache:

document.addEventListener('turbolinks:before-cache', function () {
  // removing the select2 from all selects
  $("select").select2('destroy');
});

And loading the select2 when turbolinks:load:

document.addEventListener("turbolinks:load", function () {
  // applying select2 to all selects
  $("select").select2({
    theme: 'bootstrap4' // if you want to pass some custom config
  });
});

Of course, you can create a custom .select2-binder class or a data-select2-binder to choose which select will be affected.


I had the same issue and I solved by doing:

// init.js (you can pass an container instead of using document

$( document ).on('turbolinks:load', function() {
  $( document ).find('select').not('.select2-hidden-accessible').select2();
});

And now I don't have those duplicated selects :)

like image 5
dpedoneze Avatar answered Nov 17 '22 11:11

dpedoneze


Nothing here worked for me.

I was able to get around this altogether by not caching the page with the select.

I use this on the top of the page with select: <% provide(:no_cache, true) %>

I use this on application.html.erb:

<% if yield(:no_cache) %>
  <meta name="turbolinks-cache-control" content="no-cache">
<% end %>
like image 1
Sidney Avatar answered Nov 17 '22 11:11

Sidney