When using a multiple select2, and ajax/initSelection for the select source, the filter option is disabled and the select2-search__field is not in the html on the inital load.
Since this selector does not have a minimumInputLength, options are displayed, and once one is selected this search field is created and can be used. If minimumInputLength is defined then the select2 is basically unusable.
This is using knockout so I've included the binding handler.
HTML:
...
<select class="form-control"
data-bind="selectedOptions: matchList,
select2: {
initSelection: $parents[1].initSupplier,
ajax: $parents[1].ajaxSuppliers,
escapeMarkup: function (markup) { return markup },
multiple: true,
maximumSelectionSize: 5,
placeholder: 'Select Supplier...',
width: '100%',
allowClear: true
}"></select>
...
Viewmodel:
self.initSupplier = function (element, callback) {
var options = $(element).children();
if (options.length > 0) {
var suppliers = [];
var ajax = options.map(function (opt) {
if (options[opt]) {
return $.ajax('api/supplier/' + options[opt].value).done(function (data) {
try {
suppliers.push({ id: data.identifierGuid, text: data.name });
} catch (err) {
if (system.debug()) {
console.error(err);
}
}
});
}
});
$.when.apply($, ajax).then(function () {
if (suppliers.length > 0) {
callback(suppliers);
}
});
}
};
self.supplierPageSize = ko.observable(25);
self.ajaxSuppliers = {
delay: 450,
url: 'api/suppliers',
type: 'GET',
dataType: 'json',
data: function (params) {
return {
skip: self.supplierPageSize() * ((params.page || 1) - 1),
take: self.supplierPageSize(),
code: isNaN(params.term) ? null : params.term,
name: isNaN(params.term) ? params.term : null,
showAll: true
};
},
processResults: function (data, params) {
if (data && data.list) {
data.list = data.list.map(function (supplier) {
return { id: supplier.identifierGuid || null, text: supplier.name || '' };
});
} else {
data = { list: [] };
}
return {
results: data.list,
pagination: {
more: (params.page * self.supplierPageSize()) < data.totalCount
}
};
}
}
Binding Handler:
ko.bindingHandlers.select2 = {
init: function (el, valueAccessor, allBindingsAccessor, viewModel) {
var allBindings = allBindingsAccessor();
var select2 = ko.unwrap(allBindings.select2);
if (select2.data) {
select2.data = ko.unwrap(select2.data);
}
if ("selectedOptions" in allBindings && !("options" in allBindings)) {//initalising select2 with selections correctly
var values = ko.unwrap(allBindings.selectedOptions);
if (values && values.length > 0) {
select2.data = values.map(function (opt) { return { id: opt, text: opt }; });
}
}
if (!select2.width) {
select2.width = '100%';
}
select2.formatInputTooShort = select2.formatInputTooShort || function (value, min) {
return "Please enter " + (min - value.length) + " or more characters";
};
$(el).select2(select2);
if (select2.selecting) {
$(el).on('select2:selecting', select2.selecting);
}
if (select2.removing) {
$(el).on('select2:unselecting', select2.removing);
}
ko.utils.domNodeDisposal.addDisposeCallback(el, function () {
$(el).select2('destroy');
});
$(el).trigger('change');
},
update: function (el, valueAccessor, allBindingsAccessor, viewModel) {
var allBindings = allBindingsAccessor();
var select2 = ko.unwrap(allBindings.select2);
if ("value" in allBindings) {
var value = ko.unwrap(allBindings.value);
if (ko.unwrap(select2.multiple) && value.constructor !== Array) {
value = String(value).split(',');
}
$(el).select2('val', value);
$(el).val(value).trigger('change');
} else if ("selectedOptions" in allBindings) {
var converted = [];
var textAccessor = function (value) { return value; };
if ("optionsText" in allBindings) {
textAccessor = function (value) {
var valueAccessor = function (item) {
return item;
};
if ("optionsValue" in allBindings) {
valueAccessor = function (item) {
return item[allBindings.optionsValue];
};
}
var items = $.grep(allBindings.options(), function (e) {
return valueAccessor(e) == value;
});
if (items.length == 0 || items.length > 1) {
return "UNKNOWN";
}
return items[0][allBindings.optionsText];
}
}
$.each(allBindings.selectedOptions(), function (key, value) {
converted.push({ id: value, text: textAccessor(value) });
});
$(el).select2("data", converted);
$(el).trigger('change');
}
}
};
Workaround to make this work. This is only for if you need it to be empty on load. Define data with id and text of ""
<select class="form-control" multiple
data-bind="selectedOptions: matchList,
select2: {
data: [{id: '', text: ''}],
initSelection: $parents[1].initPriceGroup,
ajax: $parents[1].ajaxPriceGroups,
escapeMarkup: function (markup) { return markup },
multiple: true,
maximumSelectionSize: 5,
placeholder: 'Select Price Group...',
width: '100%',
allowClear: true
}"></select>
Then the initialiser needs to ignore ids that are invalid and return callback with empty list.
This will force the select2 to believe it's been deselected and add the search input without the user seeing this and having to do it themselves.
self.initPriceGroup = function (element, callback) {
var options = $(element).children();
if (options.length > 0) {
var groups = [];
var ajax = options.map(function (opt) {
if (options[opt] && options[opt].value) {
return $.ajax('api/priceGroup/' + options[opt].value).done(function (data) {
try {
groups.push({ id: data.priceGroupGuid, text: data.name });
} catch (err) {
if (system.debug()) {
console.error(err);
}
}
});
}
});
$.when.apply($, ajax).then(function () {
if (groups.length > 0) {
callback(groups);
} else {
callback([]);
}
});
}
};