Select2: DataAdapter (InitSelection deprecation) documentation

Created on 6 Mar 2015  ·  63Comments  ·  Source: select2/select2

I am attempting to migrate from Select2 3.5.2 to Select2 4.0.0rc1

I have current code which uses InitSelection to prepopulate values

But i see that this is being deprecated which makes sense, initselection focused on input val, where 4.0.0 will be migrating to the select element

The point is, I can find no documentation on how this is accomplished

4.x documentation

Most helpful comment

Couldn't agree more with @yellow1912's first comment. What's wrong with this picture:

OLD way:

initSelection : function (element, callback) {
  var data = [];
  $(element.val()).each(function () {
    data.push({id: this, text: this});
  });
  callback(data);
}

NEW way:

$.fn.select2.amd.require(
['select2/data/array', 'select2/utils'],
function (ArrayData, Utils) {
  function CustomData ($element, options) {
    CustomData.__super__.constructor.call(this, $element, options);
  }

  Utils.Extend(CustomData, ArrayData);

  CustomData.prototype.current = function (callback) {
    var data = [];
    var currentVal = this.$element.val();

    if (!this.$element.prop('multiple')) {
      currentVal = [currentVal];
    }

    for (var v = 0; v < currentVal.length; v++) {
      data.push({
        id: currentVal[v],
        text: currentVal[v]
      });
    }

    callback(data);
  };

  $("#select").select2({
    dataAdapter: CustomData
  });
}

We go from 7 lines of code to 30 lines of code to achieve the same functionality? The old way was much cleaner and easier to understand. The new way introduces new concepts (amd) that seems overly complex and confusing.

All 63 comments

I think you can check the small piece of code included in the Announcement link

http://select2.github.io/announcements-4.0.html

Personally I really hate this new way of defining custom data loader. It makes the process of defining loader extremely complex especially when you use it with other libs such as angularjs.

What with the $.fn.select2.amd.require? Now suddenly we have to learn about AMD as well.

CustomData.prototype.current can't replace initSelection, because you set value only with id prorerty and don't have text. You must manualy create options. (if you use ajax, data is empty)

Easier manualy create options with attribute selected.

P.S. plz add back initSelection

@Jrizzi1

I'm not sure if you have a similar case, but do look into the source code to see how each Adapter works. For example I think select2 SelectionAdapter will scan your selected options and auto add them.

This change is really unlucky & developers unfriendly. Previous case when using initSelection was significantly less effort-demanding implementation for developers. There is no documentation with real-world examples (there's only 1 simple example in announcement) and it's really a nightmare to study details of implementation, quite a time loss.

Please provide at least 2-3 more examples on how to initiate dropdown lists for existing values when loading dropdown from remote sources via ajax.

Couldn't agree more with @yellow1912's first comment. What's wrong with this picture:

OLD way:

initSelection : function (element, callback) {
  var data = [];
  $(element.val()).each(function () {
    data.push({id: this, text: this});
  });
  callback(data);
}

NEW way:

$.fn.select2.amd.require(
['select2/data/array', 'select2/utils'],
function (ArrayData, Utils) {
  function CustomData ($element, options) {
    CustomData.__super__.constructor.call(this, $element, options);
  }

  Utils.Extend(CustomData, ArrayData);

  CustomData.prototype.current = function (callback) {
    var data = [];
    var currentVal = this.$element.val();

    if (!this.$element.prop('multiple')) {
      currentVal = [currentVal];
    }

    for (var v = 0; v < currentVal.length; v++) {
      data.push({
        id: currentVal[v],
        text: currentVal[v]
      });
    }

    callback(data);
  };

  $("#select").select2({
    dataAdapter: CustomData
  });
}

We go from 7 lines of code to 30 lines of code to achieve the same functionality? The old way was much cleaner and easier to understand. The new way introduces new concepts (amd) that seems overly complex and confusing.

I think people are missing the point here (as I was initially). Although 'current' is the replacement for 'initSelection', it is rarely needed at all. From what I understand (and how I've implemented it), the typical way to initialize selections is to set the options elements to selected prior to calling select2. This is easily done in either HTML or AJAX.

It's actually quite easy and much more logical than it used to be. I think the documentation could make this point a bit more clear though because I was also confused at first. You only need this CustomData stuff if you want to override the default behavior of select2, which most people don't.

@pjclas, so based on the example in the release nodes, how would/did you go about easily implementing the solution in 4.0? Can you provide examples? Maybe it's something we all missed...

Sure, here is a simple ajax example:

http://jsfiddle.net/o45d0td6/

If you aren't using ajax, you can just add the option elements directly to the select tag in the html.

so based on the example in the release nodes, how would/did you go about easily implementing the solution in 4.0? Can you provide examples? Maybe it's something we all missed...

It sounds like @pjclas is referring to the second example in the release notes, which is roughly equivalent to the example in the jsfiddle.

And I agree, the second example is much closer to what most people need to use when migrating. If someone wants to create a pull request that either clarifies that or switches the example, that'd be great.

I believe that all API select2 v4 was very complicated.

Mainly working with ajax.

The InitSelection for me is very important. I understand that even with the tag "select" it must exist.

Imagine the situation. I have only the "id" of the object in my javascript and i need assigns it to "select" to display the information in the version 3.x enough to do that

$ ('#selectIdPessoa').val('BFDC6247-8FCD-4689-A69E-1C7625409924').trigger('change')

Then the ajax (InitSelection) was shot and the component would go to the server and search the "Text".

But in 4.X version nothing happens, even rewriting the code using adapters

I believe that if the API remain so complicated, there will be an exodus of developers for other plugins. We need to simplify things, and quite

The Example here https://select2.github.io/announcements-4.0.html on section "Removed the requirement of initSelection" show how to do without ajax (both examples).

We need a sample of replacement to "InitSelection" with remote data (ajax) on documentation

See http://jsfiddle.net/s8s2ho28/17/

I finally managed to solve my problem in migrating to version 4.

In my scenario all my "select2" are linked to a customBinding the knockoutjs. So I took my customBinding to solve the problem that was previously done by select2 internally on the "InitSelection"

So I'm posting here to help someone who has the same problem.

In the current solution, not yet tested it with "multiple". But soon I will test

Yeah I'm thinking about migrating from selectize.js to select2 4.0 but the new API looks extremely overengineered...

I figure I might as well reiterate that most use cases do not need a custom data adapter, including pre-selecting options using AJAX.

For those few that do, we need some actual documentation. I've been looking into Docco for generating some more direct documentation automatically, but there are plans to overhaul the documentation.

We need a sample of replacement to "InitSelection" with remote data (ajax) on documentation

The second example under that same section is designed for AJAX use cases, which was mentioned above.

Well the data adapter issue is obvious to solve and people crying for initSelection to be brought back really should just RTFM. However I'm referring to the general idea with adapters/decorators and wrapping that in this AMD thing. It really makes integration between select2 and Angular for example that much harder when you have to deal with another layer of dependency injection stuff.

Anyway, what's done is done, perhaps we'll get used to it.

Data Adapter is stressful solution, spend hours to understand it plus trials-errors. Well my monkey patch isnt good, but at least it works without bleeding to Data Adapter

 $.each $(el).find('.select2-ajax'), (i, item)->
      $(item).select2
        ajax:
          url: $(item).data('url')
          dataType: 'json'
          delay: 250
          data: (params)->
            return {q: params.term}
          processResults: (data, page)->
            return {results: data}
          cache: true
        minimumInputLength: 3
        templateResult: (data)->
          return data.label
        templateSelection: (data)->
          return data.label
      ajax_selected = $(item).parent().find('.select2-selection__rendered')
      if ajax_selected.length
        ajax_selected.text(ajax_selected.attr('title'))
        $(item).parent().find('.select2-container').css('width', 'auto')

@jackbit If you used text for your template (you should be - it's _mostly_ required) you likely wouldn't need to fiddle with how it is automatically rendered.

Alternatively, if you don't feel like doing the re-mapping, you should fall back to data.text in your templateSelection.

templateSelection: (data) ->
  return data.label or data.text
templateSelection: function (data) {
  return data.label || data.text
}

And you can set the width to auto by default when initializing Select2.

width: null // or 'auto', both are equivalent

@kevin-brown I have been using templateSelection on my snipped code above. My issue was when I want edit selection that i have already set or save, it doesn't display default value that i have made before. But in original select tag, it has my selected value.

In my opinion. We already use the "selec2" one day we will get used to the new API and everything will work out.

But my concern is that the use of a plugin should be as simple as possible. And definitely this is not happening in the new API, and the consequence of this is that new users do not opt ​​for our plugin, and over time the "select2" come into disuse.

I think we should simplify much the API plugin. Mainly in integration with AJAX.

The end of InitSelection brings disastrous consequences for those who used the plug connected to a MVVM framework (knockoutjs, angularjs and etc ...). For a task that "select2" made automatically, should now be made in advance by the application code itself.

See my example: http://jsfiddle.net/s8s2ho28/17/

In Version 3.x I used the InitSelection and voila, problem solved by integrating with knockoutjs I just attributing the value of the element and the "select2" resolved to me how to display it properly (searching via ajax text)

In 4.X version now after a long time to understand where it should be done to change (trying with or without DataAdapters) I managed to get a solution. But now my application via ajax search text, and then everything is already in HTML ready, oh then I apply the plugin.

It works, but makes using more painful, and it does not attract users, and it amazes beginners programmers.

I love the plugin, do not want to change it, so I think we should prioritize better documentation, especially a simpler API.

but, this is only my opinion, and not based on technical arguments, but rather on the user experience (developer)

@penihel, many share the same opinion and to your point about being turned off with the new api, I ended up downgrading back to 3.5.x.

It's not a matter of being able to figure out how to make it work, it goes back to the beginning of why we choose to use the library in the first place.... Ease of use and a simple, not overly complex, api that new developers can pick up without having to look at code and scratch their heads.

I'm also running into a problem with pre-setting selections via ajax but also using templateSelection & templateResult based on ab object returned via ajax.

How would I preset options in a multiselect with a known data object ahead of time that filters into the templateSelection?

I'm seriously confused about why everyone is having problems here. Kevin made the initSelection process way simpler and more logical with select2 4.0. He has two examples that show how to do it with or without ajax as he's mentioned several times. I think anyone who thinks they need a custom DataAdapter probably doesn't understand the simplicity of the new design...

Step 1: Statically or dynamically create/change options in a select element.
Step 2: Trigger a change on the select2 interface.
Step 3: Realize how easy this was and stop complaining! :-P

Friend @pjclas.

You do these three steps is not so simple, especially for those who are now beginning to use the select2.

And in the previous version, these three steps were all made using settings when applying the "select2" to the element.

Now this version you have to do some steps before applying the "select2". And some scenarios it sharply affects the quantity and complexity of code.

In my scenario I have a system with a 40 "select2" different, I have to do a treatment on each to ensure that there is the element "option" within the element "select" before applying the plugin "select2". (I do this for an ajax request and apply the "select2" in callback)

Even with a seemingly simple step, in various scenarios it is a painful step.

As you said, to use the select2 now we need (at least) the steps 1,2 and 3.

In the previous version it was solved in just one step. And lose this characteristic is not advantageous for the "select2" in my opinion

Another example, show me an example of how to change the CSS class Dropdown element without using Adapter? In the previous version there was a property, now only using the full version.

You don't have to have everything created prior to creating the select2, you can modify it any time you want, and then simply trigger a change event by calling "$select2_element.trigger('change');". As far as the CSS goes, you can change that at any time like you can with any element...

I'd think there would be a more difficult time converting the <input /> to a <select>, and maybe that's inspiring some of the issues here, as that is what requires the most changes in the long run. I wouldn't mind finding possible solutions to making migration easier, but stopping the deprecation is not a realistic long-term solution.

You do these three steps is not so simple, especially for those who are now beginning to use the select2.

Admittedly, that's my current issue with the solutions that are out there: It's no longer a "try initSelection and hope it works" kind of situation. So far we've solved the "hope it works" part of that, now it's just time to make it easier.

In my scenario I have a system with a 40 "select2" different, I have to do a treatment on each to ensure that there is the element "option" within the element "select" before applying the plugin "select2". (I do this for an ajax request and apply the "select2" in callback)

I'd hate to suggest "wrap it up in a helper function" as a possible solution here, as that adds an additional (albeit small) step to initializing Select2. But it might not be that much code, considering the small amount of code currently in there for compatibility that handles the job (somewhat) well.

The end of InitSelection brings disastrous consequences for those who used the plug connected to a MVVM framework (knockoutjs, angularjs and etc ...). For a task that "select2" made automatically, should now be made in advance by the application code itself.

I think it might just be sheer luck, but for some reason I haven't heard of a ton of issues converting Angular and Ember apps. Perhaps it's because Angular has other (better suited, imo) alternatives and Ember actually has a distinct "set everything up ahead of time" step that people are used to.

Part of my issue with Knockout, which is why I can't help a ton when converting it, is that I'm not at all familiar with what common conventions are. I can't speak for where application preparation happens with it, but I do know that it has the ability to synchronize data through bindings. And that should automatically create a new <option>, which Select2 should be able to pick up on.

But again, it's unfamiliar territory for me.

show me an example of how to change the CSS class Dropdown element without using Adapter?

The dropdownCssClass option still works in the full builds of 4.0.0, it's not yet documented though.

Hi @kevin-brown you're the best.

I think you understand my point.

I also think that the direction is not returning to the depreciated methods.

I believe that the direction is to facilitate as possible adding simpler options such as the placeholder (example: css changes, container)

and greatly improve the documentation. We need to understand that most people who use select2 not know concepts such as "decorator", "adapter" and "amd"

Thanks for listening, and sorry the heated argument. My intention has always been to contribute to the plugin, not just criticize negatively.

@kevin-brown I also agree with not moving backwards, and I can see wanting to use methods on the original select element when setting the values of said select element as it's more intuitive, but with all the examples I've seen so far, none of them deal with handling setting an option via an object and letting templateSelection display it like it should.

Example http://jsfiddle.net/q1q00hnm/3/ (ajax example from options page modified to show image in selection)
How would you pre-set, or set after intializing, a value in that select2 with a known repo object contacting the data for the selection? {id: 1, full_name: 'test repo', owner: { avatar_url: 'test_image.jpg' } }

We did it !. We came up with this solution for the AJAX problem ( Init with default value without < option > html tags (just < select > < /select > in DOM) and this data adapter: (select2 4.0))
This is done with Typescript.

!! Key point ONE: don't save the current values on the select element with $.val() as the options come from the ajax call. Val() does not work on non existing dom options - we save and read current selected value with:
LINE: (set value) ($(this.el)).data('selectedValues', [options.value]).trigger("change");
LINE (read value) : var currentVal = this.$element.data('selectedValues');

!! Key point TWO: The result data from the ajax request musst be returned as a collection data.results property:
LINE: callback({ results: mapped });

///

class Select2View {

el:any;

constructor(options?) {
    console.log('constructor of select2wrapper');
}

showselect2(options?) {

    this.el = options.el;

    console.log('init with value:', options.value);


    (<any>$).fn.select2.amd.require(['select2/data/array', 'select2/utils'], function(ArrayData, Utils) {
        function CustomData($element, options) {
            (<any>CustomData).__super__.constructor.call(this, $element, options);
        }

        Utils.Extend(CustomData, ArrayData);

        CustomData.prototype.current = function (callback) {

            var data = [];                
            var currentVal = this.$element.data('selectedValues');

            console.log('current',currentVal);

            if (currentVal == null) {
                callback([]);
                return;
            }

            if (!this.$element.prop('multiple')) {
                currentVal = [currentVal];
            }

            for (var v = 0; v < currentVal.length; v++) {
                data.push({
                    id: currentVal[v],
                    text: currentVal[v]
                });
            }

            callback(data);
        };

        CustomData.prototype.query = function (params, callback) {
            console.log('query',params);                

            (<any>$).ajax({
                url: "/myapi/layouts/5"                    
            }).done(function (data) {

                var mapped = $.map(data.Layout, function(obj) {
                    return { id: obj.Key, text: obj.Key };
                });

                callback({ results: mapped });
            });
        };

        (<any>$(options.el)).select2({
            dataAdapter: CustomData           
        });
    });

    (<any>$(this.el)).data('selectedValues', [options.value]).trigger("change");      
}

}

export = Select2View;

---------------------- snip --------------------
ps: remove the <....> typings from typescript to get javascript
ps2: the options Methode parameter is just used to configure this class from the outside

Another one of my near-future goals is to get a pull request going that switches the style of the documentation, so it focuses more on working examples and less on explanations about them.

none of them deal with handling setting an option via an object and letting templateSelection display it like it should.

One thing I've considered off and on for the past 21 days is the idea of converting the data attributes on the <option> into properties on the data object. That could mean that an <option> like the following

<option data-test="this" value="1">Something</option>

would automatically be converted into a data attribute with the test property

{
  "id": "1",
  "text": "Something",
  "test": "this"
}

Which is something we already do for data attributes on a <select>, and most likely would not be difficult to bring over as well.

The other alternative is to just allow an "extra data" property on an <option> and automatically combine it with the generated data object, which would allow for freeform JSON as long as it can be parsed by jQuery.

I like your last idea. the "extra data"

Today we have a problem. When you start ajax select2 with a default initial option tag and seek method

$ ('select'). select2 ('data')
// output: {id: xxx, text: yyyy}

After I seek any other item via ajax, and call the same method:

$ ('select'). select2 ('data')
// output: {id: xxx, text: yyyy, myprop1: zzz, myprop2: 2334}

I (and I think everyone else) need to always return the object always as shown in the second example

// output: {id: xxx, text: yyyy, myprop1: zzz, myprop2: 2334}

Because we need the object with all properties completed for use elsewhere in the html page.

This is one reason that the late "InitSelecion" was useful, he guarantee that every time I call the "data" would return me whenever my entire object received from the ajax request.

Now including a initial default select tag, to call the "data" will return me a different object.

You could understand the problem?

converting the data attributes on the

I would love to see that implemented, as it seems it could solve peoples' problems with initSelection's demise, as well as allowing more flexibility in handling events and to make use of richer or more complex data.

@kevin-brown, The data-* options are a possibility but you'd have to have a syntactically safe way of describing nested objects within the data attributes

Example: how would {id: 1, full_name: 'test repo', owner: { avatar_url: 'test_image.jpg' } } be described in multiple data-attribute fields.

One option would be to just fit the entire JSON (escaped) into a single data field on the option, and have it be parsed by the templateSelection option when initially rendering

    <select>
        <option data-select2-option="{id:1,full_name:\"test repo\",owner:{avatar_url:\"test_image.jpg\"}}" selected></option>
    </select>

However neither option I think solves the underlying issue, or an associated issue I just thought of.
My thinking is this, if there's already a method templateSelection built into select2 to take a selection object (that being clicked on in the dropdown) and return a jQuery object or string to populate the select2 element itself, why couldn't that be called by itself instead of only being able to be called from clicking an item on the dropdown list. Something as simple as $(element).select2('manual-select', obj) or similar that would basically be the same as if an ajax search had occured and a user selected a returned data object.

The associated issue is this, say I have two dropdowns, (forked from first demo: http://jsfiddle.net/zvgjLLkh/2/ ). And I wanted to programatically set the second select based on picking an option from the first one (or vice versa). There's no way, (and if I'm wrong please let me know), to pass an object to either select that would pass it through the templateSelection before rendering it.

Both would be solved by having a manual selection method.

I've put a really ugly hack together for manual selection.
On the current v4.0.0 build of select2.full.js starting at around line ~5841

      } else if (typeof options === 'string') {
        var instance = this.data('select2');
        console.log(instance);

        if (instance == null && window.console && console.error) {
          console.error(
            'The select2(\'' + options + '\') method was called on an ' +
            'element that is not using Select2.'
          );
        }

        var args = Array.prototype.slice.call(arguments, 1);

        /**
         * Added by RG for temporary bandaid to manually select select2 options
         */
        if (options == 'manualSelect') {
            instance.trigger('select', {
                data: args[0] 
            });
        }

        var ret = instance[options](args);

        // Check if we should be returning `this`
        if ($.inArray(options, thisMethods) > -1) {
          return this;
        }

        return ret;
      } else {

usage is just $(element).select2('manualSelection', obj) where obj is the object data that you want to select / add to the multi-select.

I agree with others, the new dataAdapter way is over-design and really horrible. The docs are really unclear (it's deprecated, but still moved? It seems a breaking change, which is deprecated...) and this is really annoying.

We hope this will be improved in a new version. For now we reverted the upgrading process (moved back to 3.5)

I was able to preset original multiselect values this way:

  1. set up data attributes on option
<select id="subjects" name="subjects" multiple="multiple">
  <option value="62501" selected="selected" data-display-name="Ask your colleagues" data-image-url="#{image_url}"Ask you colleagues</option>
</select>

and in the templateSelection template function:

var attrData = {};

$(item.element.attributes).each(function() {
  if ('data' === this.nodeName.substring(0, 4)) {
    // remove "data-" prefix and convert dashes to underscores
    return attrData[this.nodeName.substring(5).replace("-", "_")] = this.nodeValue;
  }
});

_.extend(item, attrData);

This way all the option attributes are available for formatting selection item.

I faced the same problem: Pre-selecting some value when using a multiple tag, Ajax query select.

What I did was, write the option tag to the select,

<select id="mySelect">
           <option value="1">Pre-selected tag</option>
</select>

and then after building the Select2, call the trigger('change') like this:

$j('#mySelect').select2({
            ajax: {
                ....      
              }
        });
$("#mySelect").val("1").trigger("change");

This added the tag to my select

Ridiculous! How come I need to spend hours understanding how to preselect value

Will this point be improved in the future?

Hi juanitoddd, thank you, but this only select 1 value (even if I call this many times for different options), because in the select2.js file, it clear the "ul" on update (by calling $this.clear(), which get the "ul" and .empty() it). BUT! I see that when we manually select, it's not disappearing because it actually recreate all the "li" using the attribute "data" (which must be empty when we call .trigger("update") like you prescribed.

By the way, I can bypass all that by changing "MultipleSelection.prototype.clear" in the select2.js directly (changing the clear to NOT remove the initial options), but as it is "unclean" and a bypass of the natural behavior of this function, I would like to be legit (and now those options are immortal and have to be handled manually... it's a mess).

Also, saying .select2('data', anArrayWithMyValues) does not work... Maybe I do THAT wrong..

So.

Do you know how to populate "data" with the function .trigger("update"), or do you know of a way to simply have more than one pre-selected option in a multiple select2?

Thanks!

Hello everybody, I initiated an activity to migrate Select2 to version 4.0, but it's hard just to contribute in conversation, I use to AngularJS and we have some complementary implementations because the needs of our systems, and use this new version is not giving right. I am unable to apply all current resources, not to mention some limitations on the types mentioned above.

We love this feature and use it to its maximum power, but the new version is killing us. We are going back to 3.5, let's wait a while and see how the new updates will walk and try again in the future.

Thanks guys for conversation.

Also rolling back to 3.5

@juanitoddd : Doesn't your ajax stop working on that step ?

+1

I'm also creating an AngularJS wrapper for this control and was extremely happy with it until I realized there was absolutely no easy way to programmatically control the data - which should be the most basic operation on the API. (due to the DOM searching of the API)

I'm finding using a data adapter isn't working with Ajax because it's searching through the DOM for results..... which aren't there because they're remotely queried.... seems kinda basic? C'mon guys

(to be more specific the initial value won't set because the DataAdapter.current method is searching through the DOM for a value that isn't there yet)

+1 in the hopes this gets better in the next version.

In my case, I am using the DataAdapter method because I'm dealing with a large data set that I need to do paging/filtering on. However I've spent hours trying to figure out how to get the initial selection to populate correctly (still with no luck).

That said, I realize this is free software and I am not contributing to it so keep up the good work :)

will this ever get fixed?

This is so confusing. This is my fifth time reading this page in the past month.

And it doesn't look like this will ever get improved...

My major problem with this API is there is no way to access the data context. You are expected to construct it entirely from within the data adapter. Which means it is not a true adapter. It is a one-direction plug.

The most immediate fix would be to allow the constructor for this object to take two additional, optional, parameters:

  • data, either an array or function
  • currentData, either an array or function. If an array and data argument is not supplied, will automatically walk the data argument and search for matches. If a function, would allow some way to do stuff.

But the problem with even this is there is no way to handle asynchronous data loads using this paradigm, which is EXACTLY WHY all the docs basically tell you to work around anything ajax-related by first inserting your options elements using AJAX, and THEN calling select2. But that defeats the purpose of a modern data-bound UI where you don't know the ID of the element, and are only using select2 to add a little bit of jazz to the UI.

Just my 2 cents after spending all day at work on a Friday trying to figure out a solution to this.

Here is the exact problem: https://github.com/select2/select2/blob/4.0.3/src/js/select2/core.js#L31

I think we may be able to build a proxy for initSelection and make a backward compatible fix for 3.x users if we just pretend that initSelection was never removed from the options object. Consumers may have to slightly modify their initSelection code, and we'd have to provide a BackCompatInjectedAdapter extension that drives this logic, but the wonky idea would be you would call it like this:

$("#example).select2({
  dataadapter: BackCompatInjectedAdapter,
  current: [this.KnockoutObservableStoringCurrentOptionId()];
});

Going to try that next... there HAS to be a way as part of the options object to specify a current value.

OK, here is where I am at with my attempt at a workaround. I tried writing my own custom data adapter, and gave up after TypeScript kept complaining. The whole way Select2 does modules is too advanced for me to figure out how to integrate into TypeScript.

So, here is what I did instead to progress my idea. I edited select2/data/select.js, as that is where current prototype function is defined.

I changed the function to the following, but I am still not getting select2 to set the current element (obviously, this is bad code, but I am trying to understand what 'current' is really doing for me that needs so many layers of indirection:

  SelectAdapter.prototype.current = function (callback) {
    var data = [];
    var self = this;

    var ic = this.options.get('initCurrent');
    console.log(ic);
    if (typeof ic !== "undefined")
    {
        data.push(new Option(ic, ic, true, true));
    }
    else
    {
        this.$element.find(':selected').each(function () {
            var $option = $(this);

            var option = self.item($option);

            data.push(option);
        });
    }


    callback(data);
  };

@jzabroski I like where you're goin' with that.. I actually went back and implemented a wrapper for 3.x in my solution. And was able to frame the problem like this:

1) Data binding involves initSelection
2) During initSelection the ng-model value is used to call the server for the key / value pair (using the key if you've bound it that way or value if you bound it that way)
3) When the data is returned it's passed into the callback of initSelection
4) During paging of the control the results are cached in the directive so they can be used to set ng-model when an option is selected.

So... This is essentially 2-way data binding. The problem my team ran into with 4.x is that it didn't give full control over this... And, for some reason, it's difficult to frame this problem which is why this discussion is so long..

I hope this helps... basically it's to make sure: 1) the DOM is separate from the data binding 2) no DOM manipulation is required when initializing the control, and 3) the "current" and "initial" values can be set and reference the ACTUAL DATA. not some hacked in option in the DOM.

Uhm, so that's my perspective on the problem. I was able to do this properly in 3.x.. in 4.x it's a lot more complicated i think because the DOM implementation is close to the data adapter.. so it's harder to parse out of the API in order to create the desired "data context" binding.. if that makes any sense?

But why isn't my hack working? When I click in the ui and select the object, it says 17. When I set it through val or current, it doesn't update the ui even after calling trigger ('change')

Will try to create a fiddle tomorrow.

@kevin-brown I've been studying this code for days now and trying to 'get inside your head' as the lead designer, so I can contribute something useful. I'm trying to get to the essence of a good design, since that seems to be your focus for 4.0, and I want to help you achieve that. Here are my thoughts:

  • DataAdapter.prototype.current is not intended to allow setting the current value. It is supposed to be a pure function that reads the current DOM state of the underlying select element. Ergo, @glowysourworm saying "the DOM implementation is close to the data adapter". In a reactive implementation like Knockout that uses data binding, I don't think we care what the DOM state is, because we are telling the DOM to react to us, rather than the other way around.

    • The fact the default implementation allows/encourages mixing DOM-side events and reactive events ($.ajax.done) causes subtle problems with composing DataAdapter extensions from the ground-up.

    • The other downside to this DOM requirement is the performance of DataAdapter.prototype.current is bound to the performance of jQuery's performance on this.$element.find(':selected').each - this could explain #4407.

  • When using AJAX configuration, the underlying <select> element only adds options that have been previously selected. Presumably, you coded it this way to minimize the number of HTMLElement objects in a single browser tab.

    • But this "previously selected" condition doesn't handle the recursive base case of initialization, unless the <option selected> is present. (I am about to test this in more detail...)

  • When using AJAX configuration, there is no way to call AJAX on initialization. This is somewhat implicit in the documentation, but not spelled out anywhere. For example, see No more hidden input tags and Removed the requirement of initSelection in the Select2 4.0 announcement. Combined, these two notes form a better implicit picture than either alone.

    • This causes several odd scenarios, that can only be resolved by pushing <option> elements into the underlying <select> control.

    • This is perhaps why the very bottom of the Removed the requirement of initSelection section says "If you only need to load in the initial options once, and otherwise will be letting Select2 handle the state of the selections, you don't need to use a custom data adapter. You can just create the

    • It's simply undefined/under-specified - everywhere in the documentation - what select2 should do if the programmer sets val to anything not in the DataAdapter.prototype.query and/or $.select2.options.data. Question: Going with the approach it is under-specified, we can say that $.select2.val() has to match at least $("select").val(), right?

  • Minor detail: $.select2.val(...) takes a string as a value, but $.select2.dataadapter.current returns an Array. I'm not sure why this asymmetry exists, but I thought I would point it out and get your thoughts on it.

@kevin-brown Correct, and sorry about the confusing wording... I should've just cited my use case - which was to create an AngularJS wrapper - so, yes, a data-binding scenario - to provide the functionality of the select 4.x control within an Angular app.

If you were to try and implement the 4.x control as a directive in Angular you'd see where the problems creep in. Specifically, there's no way to initialize the value of the control - which is why i think so many of us are complaining about the upgrade.

Put simply, if 4.x were capable of creating an interface similar to 3.x there wouldn't be this problem. So, once that interface is in place I think most of us will have our answer.

I think, studying the code, that the documentation for the dataadapter needs some changing. It doesn't update the elements, but rather gets the updated elements. Something else still needs to tell select2 there is an updated list to retrieve.

From options-old.html#dataAdapter (for some reason, only options-old has the API documentation):

// Get the currently selected options. This is called when trying to get the
// initial selection for Select2, as well as when Select2 needs to determine
// what options within the results are selected.
//
// @param callback A function that should be called when the current selection
//   has been retrieved. The first parameter to the function should be an array
//   of data objects.
DataAdapter.current = function (callback) {
  callback(currentData);
}

// Get a set of options that are filtered based on the parameters that have
// been passed on in.
//
// @param params An object containing any number of parameters that the query
//   could be affected by. Only the core parameters will be documented.
// @param params.term A user-supplied term. This is typically the value of the
//   search box, if one exists, but can also be an empty string or null value.
// @param params.page The specific page that should be loaded. This is typically
//   provided when working with remote data sets, which rely on pagination to
//   determine what objects should be displayed.
// @param callback The function that should be called with the queried results.
DataAdapter.query = function (params, callback) {
  callback(queryiedData);
}

and options-old.html#selectionAdapter:

// Update the selected data.
//
// @param data An array of data objects that have been generated by the data
//   adapter. If no objects should be selected, an empty array will be passed.
//
// Note: An array will always be passed into this method, even if Select2 is
// attached to a source which only accepts a single selection.
SelectionAdapter.update = function (data) { };

Let me know your thoughts, @kevin-brown

if I want to autocomplete a value from a local array that changes all the time (no AJAX), select2 4 makes it very hard. It was much simpler before. I think a higher-level api is really missing around the dataAdapter concept

here is how I do it for now:

RefAdapter = function(){}
$.fn.select2.amd.require(['select2/data/array', 'select2/utils'], function (ArrayData, Utils) {
  RefAdapter = function($element, options) {
    RefAdapter.__super__.constructor.call(this, $element, options);
  }
  Utils.Extend(RefAdapter, ArrayData);
  RefAdapter.prototype.query = function (params, callback) {
    var data = {results: [] };
    r = $("...")
    for (var i = 0; i < r.length; i++) {
      data.results.push({
        id: ...
        text: ...
      });
    }
    callback(data);
  };
})

//later
x.select2({dataAdapter: RefAdapter})

documentation is not great

I don't know if this is helpful but I'll post it here anyway.

The way I see it that if you have a static list that will never need refreshing you use:

{
data: [{text: "Something", id: "123"}, ... , {n} ]
}

And you use .val("stuff").change() to set the initial value.

If you need to retreive data dynamically you use one of these options

  1. The build in ajax option (which I have not used as if yet) for basic behaviour.
  2. The dataAdapter option, if you want to do some fancy stuff.

As for troubles with current values etc. I've wrapped Select2 in a class/object that handles Select2's behaviour, such as when to refresh etc. in it's most simple form it looks like this:

MySelect2Wrapper = function(initValue, $domElement){
    this.$domElement = $domElement;
    this.value = initValue;
   ...

   this.select2Options = {
         dataAdapter: $.fn.select2.amd.require('select2/data/customAdapter'),
          ...
     }
}

The way I enforce refreshing or not refresing (or any behaviour for that matter) is by passing an extra property to the select2Options object.
This property is a function which is bound to MySelect2Wrapper

So using the code from above and altering this block:

   this.select2Options = {
       dataAdapter: $.fn.select2.amd.require('select2/data/customAdapter'),
        ...
       }

To this:

   var self = this;
   this.select2Options = {
         dataAdapter: $.fn.select2.amd.require('select2/data/customAdapter'),
         myRefreshingProperty: function(){return self.doStuff();},
         myWhatEverElseProperty: ... ,
          ...
       }

and adding

MySelect2Wrapper.prototype = {
    doStuff: function(){
       var result = true;
       // Some logic here.
       ...

      return result;
    }
}

Then in the dataAdapter you can use this.options and read the propertyValues for w/e you want to do.
I realize that if Select2 decides to add a property with the same name you could run into a problem. So I would suggest giving the properties a unique prefix. Something like MyCompanyNamePropertyName: ... or w/e.

If you would want to pass the initialValue to the dataAdapter you just pass along self.value using a different property/function.

Another way
You could als add more parameters to the dataAdapter constructor. Though I feel this solution is quite elegant and not obtrusive.

Why I wrapped Select2
I feel it is good practice to wrap third party libraries in your own Classes/Objects in case the behaviour you want is implemented differently in newer versions. Or if you want to swap out the library in it's entirty.

That way you can still call the functions with the behaviour you want and change nothing in your code except the function blocks in your wrapper. Just throwing it out there in case someone has to re-write large parts of their code base because of a swap to Select2 4.0.

Side note
One thing I would like to comment is that I believe "query" and "current" get called in the wrong order. That is if you use the dataAdapter as a means to retreive remote data.

I would expect to use "query" to query my remote, and "current" to select the initial value for that query. Though again, this is only the case if you use (or abuse, depending on "normal" dataAdapter usage) dataAdapters to retreive remote data.

Hope this helps someone, Robert.

Beautiful!
So much easier to implement than that old bulky query:

All I need is for my select box to show 3 options when one criteria is met and show one less when another criteria is met. Do I need to do all this adapter stuff just for that? The adapter seems like overkill. And plugins like this are so we don't need to mess around with DOM elements.

The only way I can find is to empty it and reinitialize it, which seems horribly inefficient.

This thread is so long to read all of them.

All I can say, is that, next time.. I think before implementing such APIs.. consider RFC(request for comments) and maybe have a votation on how and what the api should look like.

By the way, I am having problem on how can I display default selection with the same format or template defined in my templateSelection.

@ajcastro agreed. The tragic thing is that AMD is largely falling out of favor these days, with technology like Webpack on the rise.

I'm going to say that this issue has gotten too lengthy and off-topic to still be useful at this point. Initial options for remotely sourced data is now documented: https://select2.org/data-sources/ajax#default-pre-selected-values

As a preemptive note to anyone who wants to give me a hard time about this...I'm a relatively new contributor/maintainer and was not part of this decision when it was made. So, please don't yell at me about this 😛

Further discussion on the overall trajectory of Select2, and the whole UMD adapter/decorator/whatever architecture, can be continued on the new forums.

There are other issues open about documenting the adapter/decorator system in general - contributions would be greatly appreciated as I don't really understand it myself at this time.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mauroschiappo picture mauroschiappo  ·  3Comments

jiny90 picture jiny90  ·  3Comments

fr0z3nfyr picture fr0z3nfyr  ·  3Comments

ghost picture ghost  ·  3Comments

dereuromark picture dereuromark  ·  3Comments