Select2: Preselected item and correct rendering through templateSelection
Asked Answered
S

4

9

When using Select2 v4, the suggested (actually the right) way to set a selected value programmatically, is to manipulate the underlying select element, add the desired <option> elements you want, set the value with $selectElement.val(<value>) and trigger a .trigger('change') event so that select2 plugin will update itself from the underlying select element. You can see this here.

The problem with this is when you have defined a templateSelection, that returns not just text for the selected value, but a whole HTML DOM (for instance, you may apply styling, add images, icons etc etc and return that as the rendered selection).

templateSelection accepts input as it comes from either an array datasource, or an ajax datasource etc. Each item represents one option but it may have more than just and id and a text. templateSelection may take into account many of the item's properties to produce the rendered output.

You can only define value and text in an <option> element. You cannot define the actual object (item) you want to set as selected, so that the templateSelection may produce the right output.

Is there a way to achieve that using only select2's API?

If not, is there any other workaround to have the value selected from code rendered correctly through templateSelection?

Syllabary answered 13/7, 2017 at 12:15 Comment(0)
R
5

Just in case someone like me comes across this, the Select2 v4 manual provides an answer for this: https://select2.org/programmatic-control/add-select-clear-items#preselecting-options-in-an-remotely-sourced-ajax-select2

The following snippet is from the documentation. Essentially, you get the initial value via an ajax call, and set the initial value programmatically via the select2:select event.

// Set up the Select2 control
$('#mySelect2').select2({
    ajax: {
        url: '/api/students'
    }
});

// Fetch the preselected item, and add to the control
var studentSelect = $('#mySelect2');
$.ajax({
    type: 'GET',
    url: '/api/students/s/' + studentId
}).then(function (data) {
    // create the option and append to Select2
    var option = new Option(data.full_name, data.id, true, true);
    studentSelect.append(option).trigger('change');

    // manually trigger the `select2:select` event
    studentSelect.trigger({
        type: 'select2:select',
        params: {
            data: data
        }
    });
});

However, if you're using the templateSelection option, and are returning a jQuery object to provide a fancy one line formatted selection result, this solution will not work. In fact, triggering select2:select appears to do nothing.

Using jQuery, I found I could attach the data to the option, and from the templateSelection function, use this data as a fall back.

// create the option and append to Select2
var option = new Option(data.full_name, data.id, true, true);
$(option).data('raw', data);
studentSelect.append(option).trigger('change')

And in the templateSelection function:

templateSelection: function (result) {
    if (!result.id || result.loading) {
        return result.text
    }
    var $container = $(
        '<div class="clearfix">' +
            '<div class="pull-left select2-result__name"></div>' +
            '<div class="pull-right select2-result__finding_name"></div>' +
        '</div>'
    );

    var raw = $(result.element).data('raw')
    $container.find('.select2-result__name').text(result.name || raw.name || result.text)
    $container.find('.select2-result__finding_name').text(result.finding_name_1 || raw.finding_name_1)

    return $container
}

This example uses multiple fallbacks. We always try using what came in the results, as is provided in the normal ajax calls, but we fall back to the raw data from the initial option, and if that is lacking, fall back to the text field.

Radarscope answered 10/7, 2020 at 10:53 Comment(1)
Thanks for mentioning this, my problem was in the templateSelection function.Ingamar
T
2

templateSelection is callback that builds the object based on the text content of the option. The object that is returned and displayed is not the same thing as the content of the option. Options remain simple id and text content. The styling is handled separately.

When you define templateSelection you define a function that will have the option as parameter, so you have access to the text and the id of the option as available variables. This function will return the object built according to your template.

See this example:

$(document).ready(function() {
  $('.js-example-basic-single').select2({
    width: 'width: 100%',
    templateSelection: formatState
  });
});

$('.js-example-basic-single').val('WY');
$('.js-example-basic-single').trigger('change');

function formatState(state) {
  console.log(state);
  var $state = $(
    '<span>Content from the template: ' + state.element.text + ' ' + state.element.value + '</span>'
  );

  return $state;
}
<script src="https://code.jquery.com/jquery-2.2.4.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<select class="js-example-basic-single" name="state">
  <option value="AL">Alabama</option>
  <option value="WY">Wyoming</option>
</select>

EDIT:

Since you have access to the state, you have access to any property you define with dynamic data. And you have access to the element, so you could also have data attributes. This allows you to do something more elaborate:

$(document).ready(function() {
  $('.js-example-basic-single').select2({
    width: 'width: 100%',
    templateSelection: formatState,
    data: [{
      id: 0,
      text: "Wyoming"
    }, {
      id: 1,
      text: "Alabama",
      selected: true,
      disabled: true,
    }, {
      id: 2,
      text: "Washington",
      selected: true,
      customProp: 'something'
    }]
  });
});



function formatState(state) {
  console.log(state);
  var $state 
  if (state.customProp) {
    $state = $(
      '<span>Content from the template: ' + state.element.text + ' ' + state.element.value + ' and my custom property is <strong>' + state.customProp + '</strong></span>'
    );
  } else {
  $state = $(
      '<span>Content from the template: ' + state.element.text + ' ' + state.element.value + '</span>'
       );
  }

  return $state;
}
<script src="https://code.jquery.com/jquery-2.2.4.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js"></script>
<select class="js-example-basic-single" name="state">
</select>
Thorley answered 3/5, 2018 at 14:10 Comment(3)
that's the point, I need more that the text and value to render. My item is a JSON object with many properties (active, disabled, sold, available etc). I need access to all of this to render the appropriate htmlSyllabary
My question is about pre-selected values at the initial rendering of the page. templateSelection works very well with the actual item state when the user picks an options through the UI. It also works very well when the datasource is a js array.Syllabary
But when the datasource is an ajax remote api, I don't have any js items at all during the first render of the page. A user must click on the select element and then data comes from the server and options are rendered correctly. But if I must render the select box with an already selected item from the begining, I don't have access to the state. I have only an id and a text from the option I rendered inside the select element.Syllabary
C
1

I have solve this in another way my first select is icon_list when the value change I reinitiase the select with the same templateResult and templateSelection function

jQuery('#icons').select2({
  templateResult: formatSelectIcon,
  templateSelection: formatDisplayIcon
});

// value[3] is the value associated with the #icons select2

jQuery('#icon_list').on ('change', function() {
  jQuery('#icons').val(value[3]);
  jQuery('#icon').select2({
    templateResult: formatSelectIcon,
    templateSelection: formatDisplayIcon
  });
});

Stéphane

Crown answered 12/4, 2023 at 13:24 Comment(0)
E
0

I faced the same issue with Select2 v4.0.3. Solved it by adding necessary properties to target items from current selection items array:

var $widget = jQuery('#mySelect2Widget');

// add new option to widget
var newOption = new Option('New option text', 'New option id', false, true);
$widget.append(newOption);

// get current selection items
var items = $widget.select2('data');
var targetItem = null;

// find newly added item in the array by id or smth else
// and set it to the "targetItem" variable

targetItem.prop1 = 'abc';
targetItem.prop2 = 1;

// trigger "change" event
$widget.trigger('change');
Evangelical answered 24/7, 2018 at 14:41 Comment(1)
I don't have a .select2('data') array. My object comes from an ajax source.Syllabary

© 2022 - 2024 — McMap. All rights reserved.