Skip to content

Instantly share code, notes, and snippets.

@DrSammyD
Last active July 6, 2016 13:07
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save DrSammyD/3ae055ca1280ccd9a4ae to your computer and use it in GitHub Desktop.
Save DrSammyD/3ae055ca1280ccd9a4ae to your computer and use it in GitHub Desktop.
Selectize Binding Handler
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define(['knockout','jquery','selectize'], factory);
} else {
var selConstructor = (typeof selectize != "undefined")?selectize:$('<select>').selectize().data('selectize').constructor;
factory(ko,$,selConstructor);
}
})(function (ko,$,selectize) {
var _key= null;
for(_key in ko.bindingHandlers.options){
if(typeof ko.bindingHandlers.options[_key] =='string' && ko.bindingHandlers.options[_key].match('__ko__'))
break;
}
var inject_binding = function (allBindings, key, value) {
//https://github.com/knockout/knockout/pull/932#issuecomment-26547528
var has = allBindings.has;
var get = allBindings.get;
return {
has: function (bindingKey) {
return (bindingKey == key) || has.call(allBindings,bindingKey);
},
get: function (bindingKey) {
var binding = get.call(allBindings,bindingKey);
if (bindingKey == key) {
binding = binding ? [].concat(binding, value) : value;
}
return binding;
}
};
}
var setOptionNodeSelectionState =function (optionNode, isSelected) {
optionNode.selected = isSelected;
}
selectize.prototype.updateOriginalInput=function(){
var i, n, options, self = this;
if (self.$input[0].tagName.toLowerCase() === 'select') {
options = [];
ko.utils.arrayForEach(self.$input.get(0).getElementsByTagName("option"),function(node){
var isSelected = self.items.indexOf(node[ko.bindingHandlers.options[_key].slice(1)]||node['value']) !=-1
setOptionNodeSelectionState(node, isSelected);
})
} else {
self.$input.val(self.getValue());
}
self.$input.trigger('propertychange');
if (self.isSetup) {
self.trigger('change', self.$input.val());
}
}
selectize.prototype.destroy=function(){
var self = this;
var eventNS = self.eventNS;
self.trigger('destroy');
self.off();
self.$wrapper.remove();
self.$dropdown.remove();
$(window).off(eventNS);
$(document).off(eventNS);
$(document.body).off(eventNS);
delete self.$input[0].selectize;
}
ko.bindingHandlers.selectizeOptions={
init:function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var $el= $(element);
if(allBindingsAccessor.get('selectizeMultiple'))
$(element).prop('multiple',true);
$.extend(allBindingsAccessor, inject_binding(allBindingsAccessor,'optionsAfterRender',function(el){
$(el).attr('value', el[ko.bindingHandlers.options[_key].slice(1)]||el['value'])
}));
var ret= ko.bindingHandlers.options.init(element,valueAccessor, allBindingsAccessor, viewModel, bindingContext);
$el.selectize({
placeholder: ko.unwrap(allBindingsAccessor.get('optionsCaption'))
});
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$el.data('selectize').destroy();
});
return ret;
},
update:function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var ret= ko.bindingHandlers.options.update(element,valueAccessor, allBindingsAccessor, viewModel, bindingContext);
var $el= $(element);
$el.data('selectize').destroy();
var $chi=$el.children();
$el.selectize({
placeholder: ko.unwrap(allBindingsAccessor.get('optionsCaption'))
});
$el.append($chi);
ret= ko.bindingHandlers.options.update(element,valueAccessor, allBindingsAccessor, viewModel, bindingContext);
return ret;
}
}
ko.bindingHandlers.selectizeCaption = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.bindingHandlers.optionsCaption.update(element,valueAccessor, allBindingsAccessor, viewModel, bindingContext);
$(element).data('selectize').settings.placeholder= ko.unwrap(valueAccessor());
$(element).data('selectize').updatePlaceholder();
}
}
ko.bindingHandlers.selectize = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.bindingHandlers.value.init(element,valueAccessor, allBindingsAccessor, viewModel, bindingContext)
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.unwrap(valueAccessor());
ko.bindingHandlers.value.update(element,valueAccessor, allBindingsAccessor, viewModel, bindingContext);
$el=$(element);
$el.data('selectize').setValue(
$el.find(":selected")
.map(function(i,el){
return el[ko.bindingHandlers.options[_key].slice(1)]||el['value']
}).get()[0]
);
},
after: ["selectizeOptions"]
}
ko.bindingHandlers.selectizeMultiple = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
$(element).prop('multiple',true);
ko.bindingHandlers.selectedOptions.init(element,valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.bindingHandlers.selectedOptions.update(element,valueAccessor, allBindingsAccessor, viewModel, bindingContext);
$el=$(element);
$el.data('selectize').setValue(
$el.find(":selected")
.map(function(i,el){
return el[ko.bindingHandlers.options[_key].slice(1)]||el['value']
}).get()
);
},
after: ["selectizeOptions"]
}
});
@anishpateluk
Copy link

nice

@thj-dk
Copy link

thj-dk commented Sep 1, 2014

Do you have any usage samples, please? Thank you!

@DrSammyD
Copy link
Author

DrSammyD commented Sep 3, 2014

@melck
Copy link

melck commented Sep 5, 2014

Can u give more example plz ?

@melck
Copy link

melck commented Sep 5, 2014

How can i use a object like with optionValue and optionText ?

@DrSammyD
Copy link
Author

@melck in exactly the same way you would normally use them

@lelelic
Copy link

lelelic commented Nov 6, 2014

hi @DrSammyD!
I'm using your plugin to integrate selectize with knockoutjs and it's works perfectly!
I've a suggestion: create a binding option called selectizeOptions with all selectize support options and use it when you call $(element).selectize({ ...})... so the plugin will be more extended!

@ShaneCoufreur
Copy link

Hi @DrSammyD.

First of all, thanks for this awesome binding!

I have a question regarding the use of this binding together with the value binding provided by knockout.

This jsfiddle ( http://jsfiddle.net/Lgs2fx90/1/ ) shows a default knockout binding on a select, which dynamically adds items when the button is pushed. The selected value is outputted next to the button.

If I then add your binding, and update my markup to use selectizeOptions instead of options, I only get an update to my value property on the very first time I add an item. (example: http://jsfiddle.net/5pxa5rft/)

Do you have an explanation for why this is happening or is perhaps my expectation to see the values updated wrong?

Thanks again!
// Shane

@DrSammyD
Copy link
Author

@ShaneCoufreur you need selectize or selectizeMultiple and selectizeOptions. selectize and selectizeMultiple replace the value binding.

here's a working jsfiddle of what you're trying to do
http://jsfiddle.net/fm19zxj8/16/

@ShaneCoufreur
Copy link

@DrSammyD Thanks for the response! I didn't know selectize replaced the value binding.

What I'm seeing now is that if i put a non-integer value in the optionValue property, it stops working.

http://jsfiddle.net/fm19zxj8/17/ (The new values work, because they're integers, the values in model upon creation don't, because they're string. The binding outputs the value for the strings, and something different for the int ones

@DrSammyD
Copy link
Author

@ShaneCoufreur yes, well that would be because the binding is broken 😀
Fixed
http://jsfiddle.net/fm19zxj8/23/
I think it has to do with the options binding not using the #__ko__### for a lookup of option values if the value is a string instead of an int, which doesn't make any sense to me, but what ever.

@lelelic
Copy link

lelelic commented Nov 19, 2014

Hi @DrSammyD,
i think there is an error in your last commit.

Infact, in your last fiddle you wrote this line in selectizeOptions init binding:

$.extend(allBindingsAccessor, inject_binding(allBindingsAccessor,'optionsAfterRender',function(el){
     $(el).attr('value', el[ko.bindingHandlers.options[_key].slice(1)]||el['value'])
}));

but in your last git you committed this:

$.extend(allBindingsAccessor, inject_binding(allBindingsAccessor,'optionsAfterRender',function(el){
                $el.attr('value', el[ko.bindingHandlers.options[_key].slice(1)]||el['value'])
            }));

and the options selection doesn't work.

Daniele

@DrSammyD
Copy link
Author

DrSammyD commented Feb 7, 2015

@lelelic Thanks

@lelelic
Copy link

lelelic commented Apr 1, 2015

Hi DrSammyD :)
I think there is an issue with the subscribe event.. How you can see in this fiddle (http://jsfiddle.net/p3y05ukc/9/), ko-selectize is firing twice subscribe event while the "classic" option of knockout once. I've tried to investigate the code but i didn't found the codeline that generate the trouble..

Thanks,
Daniele

@ehartford
Copy link

Can I please see an example that uses selectizeMultiple? I would like to have an observable that contains the currently selected options.

@jacobmhulse
Copy link

Excellent work Dr Sammy D! This is exactly what I needed!

@ehartford: Take a look at this: http://jsfiddle.net/fm19zxj8/25/

@jacobmhulse
Copy link

@DrSammyD
I'm not sure if you are working on this still or not but I ran into an issue which I think would be easy to solve (for you).

The call to $(element).selectize({ ... });' is only made in theselectizeOptions` bindingHandler, which cannot be used if the options are hard-coded into the html. It might be a good idea to check if the element has been initialized and if not, go ahead and do so in each bindingHandler. Thoughts?

@lelelic
Copy link

lelelic commented Nov 3, 2015

Hi @DrSammyD,
I've a simple question: how can I pre-select an option if I use optionValue and OptionText?
In this fiddle I try to get it but without results: http://jsfiddle.net/bqgd81jz/1/ . If you notice the value observable is initialized with 3.

Thanks in advance.
Daniele

@krnlde
Copy link

krnlde commented Nov 28, 2015

@DrSammyD do you see a possibility to hand over selectize constructor options? For example plugins or render: {option: ...} would be very handy and it feels weird to put them in the code directly.

@lelelic
Copy link

lelelic commented Feb 16, 2016

Hey @krnlde looks at my gist https://gist.github.com/lelelic/114a286649438b6ef365. I've added the option "selectizeSettings" where you can specify all the options of selectize constructor.
For example:
<select name="sel1" class="form-control" data-bind='selectize: values, selectizeOptions: myValues, optionsText: "Description", optionsValue: "Id", selectizeSettings: {onDelete: function() { return false }}'></select>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment