Estou usando a ligação knockout select onde um objeto inteiro é ligado como o valor ... não apenas uma string.
bootstrap-multiselect não gostou disso porque usa o "valor" para relacionar seus itens da lista suspensa de volta à opção de seleção correspondente. Para fazer isso, você deve definir o atributo "optionsValue" em sua ligação knockout para que a opção tenha um atributo "value". No entanto, definir o atributo "optionsValue" tem o resultado esperado de definir apenas esse valor em meu selectedOptions observableArray ... não no objeto inteiro.
É bem possível que eu seja a única pessoa que usa a funcionalidade do knockout que permite vincular as opções a objetos inteiros, mas caso eu não seja ... aqui está a correção do hack:
Criou duas novas opções de bootstrap-multiselect chamadas "optionsKey" e "observableKey" para saber onde ir para obter um valor com o qual possa se relacionar:
<select id="mySelect" data-bind="options: myOptions, selectedOptions: mySelectedOptions, optionsText: 'myDisplayText', optionsAfterRender: SetOptionKey, multiselect: { optionsKey: 'data-key', observableKey: 'Key' }" multiple="multiple"></select>
Em meu modelo de visão nocauteada, defino o atributo referenciado por "optionsKey" em cada opção:
myVM.SetOptionKey = function ( option, item ) {
ko.applyBindingsToNode( option, { attr: { "data-key": item.Key } }, item );
};
Agora mergulho no arquivo bootstrap-multiselect.js. A primeira coisa que tive que fazer aqui foi na parte superior, onde está adicionando o manipulador de inicialização do knockout.
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
...
if (allBindings.has('selectedOptions')) {
var selectedOptions = allBindings.get('selectedOptions');
if (ko.isObservable(selectedOptions)) {
ko.computed({
read: function () {
selectedOptions();
// Added to handle knockout binding to an object...not just a value
// multiselect needs the selected property on the options, else it wont show them on refresh
if ((config.optionsKey !== undefined) &&
(config.optionsKey !== null) &&
(config.optionsKey.length > 0) &&
(config.observableKey !== undefined) &&
(config.observableKey !== null) &&
(config.observableKey.length > 0)) {
var _optionsKey = config.optionsKey;
var _observableKey = config.observableKey;
ko.utils.arrayForEach(selectedOptions(), function (selectedOption) {
$("option[" + _optionsKey + "='" + selectedOption[_observableKey]() + "']", $element).prop('selected', true);
})
}
setTimeout(function () {
$element.multiselect('refresh');
}, 1);
},
disposeWhenNodeIsRemoved: element
}).extend({ rateLimit: 100, notifyWhenChangesStop: true });
}
}
...
Em seguida, tive que modificar a função createOptionValue em bootstrap-multiselect.js para primeiro usar essa chave como o valor que ela define em seus itens de lista:
createOptionValue: function (element) {
...
var value = $element.val();
// Added to handle knockout binding to an object...not just a value
if ((this.options.optionsKey !== undefined) && (this.options.optionsKey !== null) && (this.options.optionsKey.length > 0)) {
var key = $element.attr(this.options.optionsKey);
if ((key !== undefined) && (key !== null) && (key.length > 0)) {
value = key;
}
}
...
}
Por último, tive que modificar a função getOptionByValue em bootstrap-multiselect.js para encontrar elementos de opção com base neste atributo em vez de valor:
getOptionByValue: function (value) {
var valueToCompare = value.toString();
// Added to handle knockout binding to an object...not just a value
if ((this.options.optionsKey !== undefined) && (this.options.optionsKey !== null) && (this.options.optionsKey.length > 0)) {
var opt = $("option[" + this.options.optionsKey + "='" + valueToCompare + "']", this.$select).first();
if( (opt !== undefined) && (opt !== null) ) {
return $(opt);
}
}
var options = $('option', this.$select);
for (var i = 0; i < options.length; i = i + 1) {
var option = options[i];
if (option.value === valueToCompare) {
return $(option);
}
}
},
Obrigado, incluirá isso no FAQ! Tenho certeza de que isso também é útil para outros usuários.
Obrigado por esta dica - eu estava tentando realizar a ligação para selecionar objetos também e este artigo foi muito útil. A única coisa que tive que fazer além do código de @ APM3 também foi preencher o atributo "valor" das minhas opções na função SetOptionKey:
self.SetOptionKey = function (option, item) {
ko.applyBindingsToNode(option, { attr: { "data-key": item.Value } }, item);
ko.applyBindingsToNode(option, { attr: { "value": item.Value } }, item);
}
Obrigado novamente por este artigo.
Consulte a essência para vinculação nocaute desse objeto com suporte. Basta substituir a ligação real do arquivo bootstrap-multiselect.js. Neste código, você não precisa colocar em sua VM para definir a opção.
//Just like this one
myVM.SetOptionKey = function ( option, item ) {
ko.applyBindingsToNode( option, { attr: { "data-key": item.Key } }, item );
};
Eu adicionei o pré-processo às ligações para adicionar implicitamente optionsAfterRender
.