1

I've created a custom binding to use as a color picker.

<ul data-bind="colorPicker: selcol"></ul>

It creates 10 inline divs each representing other color. When I click a div the color is selected. I have a problem with assigning the 'selected; css class to selected div. I try to use css binding inside custom binding but this doesn't work. It only selects initial div which stays selected even after selecting other div.

Please check the example: http://jsfiddle.net/zbkkzdsp/Jbvvq/

Thanks for any help. If you have any hints or comments to my code, please let me know. I'm quite new with knockout and will take any chance to learn more.

JotaBe
  • 38,030
  • 8
  • 98
  • 117
Eori
  • 67
  • 1
  • 7

1 Answers1

3

It seems like you're doing a lot of fancy stuff with computed values in your custom binding, so I would recommend creating view models for your colors.

First define a view model for each individual color:

var ColorModel = function(options) {
  var self = this;

  // Keep a reference to the parent picker for selection
  self.picker = options.picker;

  // The CSS value of the color
  self.color = ko.observable(options.color || 'transparent');

  // A flag denoting whether this color is selected
  self.selected = ko.observable(options.selected || false);

  // This will be called when the corresponding color link is clicked
  // Note that we're not doing any event binding with jQuery as with your custom binder
  self.select = function() {
    self.picker.selectColor(self); 
  };
};

Then a view model for the color picker itself:

var ColorPickerModel = function() {
  var self = this;

  // The list of all colors
  self.colors = ko.observableArray([]);

  self.addColor = function(color) {
    var newColor = new ColorModel({
        picker: self,
        color: color
    });

    self.colors.push(newColor);
    return newColor;
  };

  // Called by individual colors
  self.selectColor = function(colorModel) {
    // Deselect the current color so we don't select two
    var current = self.selected();
    if (current) {
      current.selected(false);
    }

    // Mark the color as selected - KO will do the rest
    colorModel.selected(true);

    // Remember this color as the selected one to deselect later
    self.selected(colorModel);
  };

  // Create at least one default color
  var transparent = self.addColor('transparent');

  // Keep track of the selected color - set to transparent by default
  self.selected = ko.observable(transparent);
  transparent.select();
};

Then bind your HTML view to your picker view model:

<div id="color-picker">
  <div data-bind="foreach: colors">
    <a href="#" data-bind="
       style: { 'background-color': $data.color }, 
       css: { 'selected': selected }, 
       click: select"></a>
  </div>
  <div>
    Selected color: <span data-bind="text: selected().color"></span>
  </div>    
</div>

And tie it all together:

var pickerDiv = document.getElementById('color-picker'),
    picker = new ColorPickerModel();

// Add some colors
picker.addColor('red');
picker.addColor('blue');
picker.addColor('green');
picker.addColor('orange');
picker.addColor('purple');
picker.addColor('black');

// Bind Knockout
ko.applyBindings(picker, pickerDiv);

// Add more colors - the view will update automatically
picker.addColor('pink');

Here's a working example: http://jsbin.com/izarik/1/edit

Vlad Magdalin
  • 1,692
  • 14
  • 17
  • Thanks. Your solution works really good but my goal was to pack as much as it is possible to custom binding for later reuse. I wanted to place many of such color pickers on different sites so it would be very easy to achieve with a single binding handler. – Eori Jan 06 '13 at 13:29
  • In that case, if you're already using jQuery events inside of your custom binding to handle the click, why not just use that to assign a 'selected' class? E.g. on every click, addClass('selected'). That is, why do you need KO bindings in there if you're already not using KO as intended? – Vlad Magdalin Jan 07 '13 at 00:26
  • I managed to prepare working example: http://jsfiddle.net/uvc2b/2/ I'm not sure though if it doesn't have any bugs that could cause performance issues or other. @Vlad Why using custom binding is not using KO as intended? – Eori Jan 07 '13 at 08:33
  • @Eori Sorry, I didn't mean to imply that using custom bindings is not "kosher KO," I meant that using jQuery events inside of your custom binding is not doing things "the KO way" as I understand it. For example, in your latest fiddle, there's no need for you to add any KO bindings to each color since you're already managing their classes with jQuery. My (personal) rule is that if I'm using a custom binding (which I do extensively), the binding should only add behavior, but not inject new DOM elements (since the binding has already taken place for the handler's init method to even execute). – Vlad Magdalin Jan 08 '13 at 00:10
  • It's ok to make child elements in your binding handler if you tell KO from your binding handler that your binding handler is in control of descendant bindings. See http://knockoutjs.com/documentation/custom-bindings-controlling-descendant-bindings.html. If you really do still need some binding to occur within your child elements then you can turn around and use ko.applyBindingsToDescendants (see same KO page for documentation) from within your binding handler. – Ian Yates Nov 15 '15 at 04:42