3

I've implemented a variant of the very cunning answer here for generating unique IDs for inputs and corresponding labels in my Knockout markup. My end goal is clickable labels, not unique IDs per se - I had previously been using a click binding on each label which navigated the DOM to select its input, but that seemed breaky and inefficient.

However the solution linked doesn't work for radio button groups where each radio in the group binds back to the same observable. In my case my radio groups are either true / false (binding back to a boolean observable) or represent an enum, in which case the observable holds an integer value. The solution as provided results in all of the radios in a group (and their corresponding label for attributes) getting the same ID.

To make matters more complex, these radio button sets themselves appear multiple times. For example, there might be 10 templated divs each with a set of 3 radio buttons: Red, Green, Blue - each radio having a label which I want to activate the corresponding radio on click.

I've been struggling to modify this solution to suit, but my brain has now locked up. Any ideas welcome!

Community
  • 1
  • 1
Tom W Hall
  • 5,273
  • 4
  • 29
  • 35
  • My interim solution is to just rely on implicit association by putting the radio inputs _inside_ the labels - but I don't like that approach because this site is going to be adapted by a graphic designer who may change the structure (yes, I'm scared too) and I don't like that use of labels anyway because of CSS styling issues. – Tom W Hall Jul 18 '12 at 03:41
  • Can you post a fiddle with an example of your viewmodel. Its not clear how you are reusing the radios. – Kyeotic Jul 18 '12 at 04:26
  • Yep, here's a simplified example: http://jsfiddle.net/qeQTd/17/ Cheers. – Tom W Hall Jul 18 '12 at 05:07
  • I'm thinking now that I could generate the ID with a binding in a similar fashion to the name, and reuse that on the label for - but the markup is going to get pretty heinous. – Tom W Hall Jul 18 '12 at 05:20
  • Works fine for me: http://jsfiddle.net/tyrsius/52xDw/1/. Also, the mapping plugin actually makes your example longer and more complicated than it would be if you just used `arrayMap`. – Kyeotic Jul 18 '12 at 05:34
  • Thanks. Yeah, the mapping plugin stuff is only there because I quickly ripped out an example from my real context and simplified it a bit. I can see why your fiddle works, because you've specified "name" as the observable to add the id property to for the Male option, and "gender" for the Female option. Was that a conscious thing? I guess the id property which the UniqueId / UniqueFor binders add could be added to any of my PersonVM's observables, I just need to do that in a clear logical way. – Tom W Hall Jul 18 '12 at 05:50
  • No that was the result of my inattentiveness. Fail. – Kyeotic Jul 18 '12 at 06:02
  • Does this work: http://jsfiddle.net/tyrsius/52xDw/3/ ? Its a bit awkward to mix that info in, but its in the view and its view info, so its not against the MVVM pattern... I think. – Kyeotic Jul 18 '12 at 06:28
  • Yes it does - awesome! That's the basic concept I was trying to achieve, using the one relevant observable (in this case gender) but with different "sub ids" - the mod parameter. It is debatable whether this approach or just putting the input inside a label and hoping for the best re CSS is the appropriate decision, but in any case I think this is a pretty nifty binding technique in general to have on hand, so cheers. – Tom W Hall Jul 18 '12 at 07:10

1 Answers1

7

From my comment, a fiddle using RP's binding with a modification

ko.bindingHandlers.uniqueId = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var value = valueAccessor();
        var idMod = 'id-' + (allBindingsAccessor().uniqueMod || 0);
        value[idMod] = value[idMod] || ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter);

        element.id= value[idMod];
    },
    counter: 0,
    prefix: "unique"
};

ko.bindingHandlers.uniqueFor = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var value = valueAccessor();
        var idMod = 'id-' + (allBindingsAccessor().uniqueMod || 0);
        value[idMod] = value[idMod] || ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter);

        element.setAttribute("for", value[idMod]);
    } 
};

Example use:

<input type="radio" data-bind="checked: gender, attr: { name: 'gender-' + id() },
     uniqueId: gender, uniqueMod: 'male'" value="1" />
<label data-bind="uniqueFor: gender, uniqueMod: 'male' ">Male</label>

<input type="radio" data-bind="checked: gender, attr: { name: 'gender-' + id() },
    uniqueId: gender, uniqueMod: 'female'" value="2" />
<label data-bind="uniqueFor: gender, uniqueMod: 'female '">Female</label>
Kyeotic
  • 19,697
  • 10
  • 71
  • 128