Here is how to deploy the following example, step by step:

  1. Deploy to Heroku by clicking the button deploy
    This requires a verified Heroku account—you will have to add a payment method to Heroku even though you won't be charged

  2. Install the Heroku CLI and open up a terminal window.
  3. Run this command in the terminal window:

    heroku run -a sh-example-simple soulheart load https://raw.githubusercontent.com/sethherr/soulheart/master/examples/manufacturers_simple.tsv
    Replace sh-example-simple with the name of your app on Heroku
  4. Add Select2 by loading the source from a CDN:

    <link href="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.min.css" rel="stylesheet" />
    <script src="//cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.min.js"></script>
    

    ... and a select box to your html page:

    <select id="basic-example-select"></select>
    
  5. Use this javascript to hook up the select box with select2:

    $('#sh-example-simple-select').select2({
      allowClear: true,
      placeholder: "Choose a manufacturer",
      ajax: {
        url: "http://sh-example-simple.herokuapp.com",
        dataType: "json",
        width: 'style',
        delay: 250,
        data: function(params) {
          return {
            q: params.term,
            page: params.page,
            per_page: 10
          };
        },
        processResults: function(data, page) {
          return {
            // Select2 requires an id, so we need to map the results and add an ID
            // You could instead include an id in the tsv you add to soulheart ;)
            results: data.matches.map(function(item) {
              return {
                id: item.text,
                text: item.text
              };
            }),
            pagination: {
              // If there are 10 matches, there's at least another page
              more: data.matches.length === 10
            }
          };
        },
        cache: true
      }
    });
    
    Replace sh-example-simple in http://sh-example-simple.herokuapp.com with the name of your app on Heroku

So, for example, let’s say you want to select bicycle manufacturers using select2 - but you want to prioritize more common manufacturers.

Some manufacturers are more popular than others - and since we expect people to be searching for more popular ones more frequently, it’s important to make them show up first — for example, if you input a “t”, the first manufacturer to select should be “Trek”, not “Tacx”.


Items with equal scores are ordered alphanumerically. So in the manufacturers example above, manufacturers are grouped in broad levels of popularity - 10, 100, 250 & 500 - higher numbers show up first. This example uses manufacturers.tsv

Set a priority to organize the way items are ordered.

$('#sh-example-priority-select').select2({
  allowClear: true,
  width: 'style',
  placeholder: "Choose manufacturers",
  multiple: true,
  ajax: {
    url: "https://sh-example-priority.herokuapp.com",
    dataType: 'json',
    delay: 250,
    data: function(params) {
      var result;
      return result = {
        q: params.term,
        page: params.page,
        per_page: 10
      };
    },
    processResults: function(data, page) {
      return {
        results: data.matches,
        pagination: {
          more: data.matches.length === 10
        }
      };
    },
    cache: true
  }
});

Search for items in only one category by adding a category parameter.


This example uses categories.json, which includes a whole host of options from the Bike Index API. Say that, instead of searching the full list, you only want to view options for handlebar types.

All categories are available at the Heroku app’s url + /categories. So start by pulling options into the category select box from:

https://sh-example-categories.herokuapp.com/categories

When the category changes, you want to grab the value and add it to your url as a query. This becomes the source for the items select box:

https://sh-example-categories.herokuapp.com?categories=handlebar%20types

Search for items in multiple categories by separating them with commas:

https://sh-example-categories.herokuapp.com?categories=colors,component%20types

$('#sh-example-categories-select-category').select2({
  allowClear: true,
  width: 'style',
  placeholder: "Choose a category",
  multiple: true,
  ajax: {
    url: "https://sh-example-categories.herokuapp.com/categories",
    dataType: 'json',
    delay: 250,
    data: function(params) {
      return {
        q: params.term,
        page: params.page,
        per_page: 10
      };
    },
    processResults: function(data, page) {
      return {
        results: data.categories.map(function(item) {
          return {
            id: item,
            text: item
          };
        })
      };
    },
    cache: true
  }
});

$('#sh-example-categories-select-category').on("change", function(e) {
  if ($(this).val() === null) {
    $('#sh-example-categories-select-item').select2('val', 'All');
  }
  window.categories = $(this).val();
  window.categories_url = toQueryString(window.categories);
  setItemsSelect(window.categories_url);
  return setLabelText(window.categories);
});

setItemsSelect = function(categories_url) {
  return $('#sh-example-categories-select-item').select2({
    allowClear: true,
    width: 'style',
    multiple: true,
    placeholder: "Choose items",
    ajax: {
      url: categories_url,
      dataType: 'json',
      delay: 250,
      data: function(params) {
        return {
          q: params.term,
          page: params.page,
          per_page: 10
        };
      },
      processResults: function(data, page) {
        return {
          results: data.matches.map(function(item) {
            return {
              id: item.text,
              text: item.text
            };
          }),
          pagination: {
            more: data.matches.length === 10
          }
        };
      },
      cache: true
    }
  });
};

Any column that isn’t category, text or priority will be returned as well.


Here, the emoticons.tsv example file includes id, image_url and source fields. These values are returned and available for incorporation into the select box.

Through the magic of select2’s templating options, emoticon images are displayed in the dropdown along with their text, category and source details:

formatEmoji = function(emoji) {
  if (emoji.category === "emoticon") {
    $emoji = $("<span><img src='" + emoji.image_url + "' class='img-emoji opt-emoji' />" + emoji.text + "</span><span class='emoji-type'>" + emoji.category + " from " + emoji.source + "</span>");
    return $emoji;
  }
};

Once an item is selected, we can opt to display only its image:

formatSelectedEmoji = function(emoji) {
  if (emoji.category === "emoticon") {
    $emoji = $("<span><img src='" + emoji.image_url + "' class='img-emoji' /></span>");
    return $emoji;
  }
};

To match the dongers and emoticons, Soulheart needs to not ignore symbols. Typically Soulheart strips out symbols to make matching easier, but you can change the normalizing function with a command

In this case, to load in the dongers and emoticons, the exact commands (after creating a Heroku instance named sh-example-arbitrary):

heroku run -a sh-example-arbitrary "soulheart -s normalize"
heroku run -a sh-example-arbitrary soulheart load https://raw.githubusercontent.com/sethherr/soulheart/master/examples/emoticons.tsv

$('#sh-example-arbitrary-select').select2({
  allowClear: true,
  width: 'style',
  placeholder: "Choose an emoticon",
  multiple: true,
  templateSelection: formatSelectedEmoji,
  templateResult: formatEmoji,
  ajax: {
    url: "https://sh-example-arbitrary.herokuapp.com",
    dataType: 'json',
    delay: 250,
    data: function(params) {
      return {
        q: params.term,
        page: params.page,
        per_page: 10
      };
    },
    processResults: function(data, page) {
      return {
        results: data.matches,
        pagination: {
          more: data.matches.length === 10
        }
      };
    },
    cache: true
  }
});

formatEmoji = function(emoji) {
  var $emoji;
  if (!emoji.id) {
    return emoji.text;
  }
  if (emoji.category === "emoticon") {
    $emoji = $("<span><img src='" + emoji.image_url + "' class='img-emoji opt-emoji' />" + emoji.text + "</span><span class='emoji-type'>" + emoji.category + " from " + emoji.source + "</span>");
    return $emoji;
  } else if (emoji.category === "donger") {
    $emoji = $("<span><span class='img-donger opt-emoji'>" + emoji.text + "</span>" + emoji.id + "</span><span class='emoji-type'>" + emoji.category + " from " + emoji.source + "</span>");
    return $emoji;
  }
};

formatSelectedEmoji = function(emoji) {
  var $emoji;
  if (!emoji.id) {
    return emoji.text;
  }
  if (emoji.category === "emoticon") {
    $emoji = $("<span><img src='" + emoji.image_url + "' class='img-emoji' /></span>");
    return $emoji;
  } else if (emoji.category === "donger") {
    $emoji = $("<span class='img-donger'>" + emoji.text + "</span>");
    return $emoji;
  }
};