jquery select2 (4.0) ajax with templateResult and templateSelection

16.6k views Asked by At

My Select2 was working on 3.5 correctly..

Since upgrading to v4.0 (not "full" - and changing keywords/functions as needed), I have a weird problem where there are extra AJAX calls being made. However, the URL is not defined, so they generate 404 Not Found errors. The URL is https://localhost:8443/myapp/undefined

They seem to be related to the templateResult and templateSelection being present. If I comment them out, the select2 works correctly (but my data is not formatted).

With them not commented out, I get the mysterious/undefined AJAX call once at initialization, then one call is made when I click on the select box, then once for every character I type (even if I set minimumInputLength). However, even with these bogus AJAX calls, my "real" ajax call will fire and return results (which are properly formatted by templateResult/templateSelection. I have tried with and without "escapeMarkup" with no difference in behavior.

What is triggering these bad AJAX calls and how can I stop them? (Because otherwise, it is working just fine)

Thanks in advance!

Edit Here is a full page that demonstrates the issue. The extra network calls are generated by the tag that I use in the formatResult function. But why is it returning the html when it should be in a "loading" state?

Well, it turns out that setting the "placeholder" causes the loading variable to not be set, thus the html is returned (with its ill-formed tag)

So if a placeholder is set, the templateResult and templateSelection should also check for an empty id..

if (result.id == "" || result.loading) return result.text;

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title></title>

<link rel="stylesheet" href="${pageContext.request.contextPath}/lib/select2/dist/css/select2.css" />
</head>
<body>

<form id="organization_lookup_form" class="form-horizontal" >
<div>
    Select2 using placeholder <select id="search1" style="width:300px"></select>
</div>
<div style="padding-top:250px">
    Select2 WITHOUT placeholder <select id="search2" style="width:300px"></select>  
</div>
</form>

<script src="${pageContext.request.contextPath}/lib/jquery/dist/jquery.min.js"></script>
<script src="${pageContext.request.contextPath}/lib/select2/dist/js/select2.min.js"></script>
<script>
$(document).ready(function () {
      function formatResult (result){
          console.log('%o', result);
          if (result.loading) return result.text;
          var html = '<div>'+
                '<img src="' + result.image + '">' +
                '<h4>'+result.label+'</h4></div>';
          return html;
      };

      $('#search1').select2({
          placeholder: "Search...",
          ajax: {
              url: '/search',
              dataType: 'json',
              data: function (params, page) {
                  return {
                      term: params.term, // search term
                      page: page
                  };
              },
              processResults: function (data, page) {
                  return {results: data.results};
              },
              cache: true
          },
          templateResult : formatResult,
          templateSelection : formatResult,
          escapeMarkup: function(m) {
              // Do not escape HTML in the select options text
              return m;
           },
          minimumInputLength: 3
      });
      $('#search2').select2({
//        placeholder: "Search...",
          ajax: {
              url: '/search',
              dataType: 'json',
              data: function (params, page) {
                  return {
                      term: params.term, // search term
                      page: page
                  };
              },
              processResults: function (data, page) {
                  return {results: data.results};
              },
              cache: true
          },
          templateResult : formatResult,
          templateSelection : formatResult,
          escapeMarkup: function(m) {
              // Do not escape HTML in the select options text
              return m;
           },
          minimumInputLength: 3
      });
});
</script>

</body>
</html>
2

There are 2 answers

1
Kevin Brown-Silva On BEST ANSWER

There are a couple of issues here that are probably leading to the requests that you are seeing.

  1. Select2 requires the text attribute on objects to point to a human-readable version of the option. This is used in the background for matching options as well as for accessibility purposes, so it's important to use it.
  2. You are only checking to see if the loading option is set, which works if you are only handling the "Searching..." text but doesn't work if anything else is being handled. I would recommend instead checking for the absence of the image property, which appears to be what is undefined in these cases.

And to answer the specific questions you've asked

What is triggering these bad AJAX calls and how can I stop them?

Select2 only triggers remote requests while searching, so any extra requests must be generated within your code. In your case, this was because you were trying to display an image in your results, but you weren't handling cases where the image doesn't actually exist on the object.

But why is it returning the html when it should be in a "loading" state?

The templateResult method is used when formatting many of the notices displayed in results, including the "Searching..." message. This allows you to apply special formatting to these messages, but it also means that you need to handle them in your templating methods. Normally this shouldn't be an issue, as it defines the text property that is typically used to display the human readable version of the option, but it causes problems when this property is not being used.

0
Bastien Ho On

Since version 4.0, the value of templateResult and templateSelection are just "appened". This behavior is not documented.

So if you pass html, all tags are displayed.

The workaround is to return a jQuery object :

 function formatResult (result){
      console.log('%o', result);
      if (result.loading) return result.text;
      var html = '<div>'+
            '<img src="' + result.image + '">' +
            '<h4>'+result.label+'</h4></div>';
      //return html;
      return $(html);
  };