6

This is a follow-up to How can I bind a ko.observableArray of strings?

How can I bind an editable observable array of observable strings to a set of input boxes? I don't want to bind to an array of objects, as my underlying JSON sent from the server is an array of strings.

The following example doesn't work (try it at http://jsfiddle.net/LDNeA/). Binding an array of objects with observable strings is OK, but binding the array of observable strings directly doesn't work, and the model is not updated.

The important thing is that the entries in the textboxes are mapped back into the model.

JS:

var ViewModel = function() {
    this.value = ko.observable("hi")
    this.array1 = ko.observableArray([ko.observable("hi"), ko.observable("there")]);
    this.array2 = ko.observableArray([{ data: ko.observable("hi") }, { data: ko.observable("there") }]);
};

ko.applyBindings(new ViewModel());

HTML:

<div class='liveExample'>   
    <p><input data-bind='value: value' /></p> 
    <div data-bind="foreach: array1">
        <p><input data-bind='value: $data' /></p> 
    </div>
    <div data-bind="foreach: array2">
        <p><input data-bind='value: data' /></p> 
    </div>
</div>

<pre data-bind="text: ko.toJSON($data)"></pre>
Community
  • 1
  • 1
slipheed
  • 7,170
  • 3
  • 27
  • 38
  • This is definitely an issue, but if the sending back to server JSON as a simple array is the only requirement, would you accapt an object that serialized itself as a single value so that it *looked* like just an array? – Kyeotic Apr 01 '13 at 18:57
  • @Tyrsius - I'd accept that answer if the answer is that "this is currently a bug in knockout.js", or "this is why this could never work". :) – slipheed Apr 01 '13 at 19:00
  • 1
    Apparently it is a logged issue, I updated my answer – Kyeotic Apr 01 '13 at 19:17

4 Answers4

14

As noted by links posted by @Tyrsius, this is a bug (?) in Knockout.

The easiest workaround is to use $parent.items()[$index()], as seen in this fiddle: http://jsfiddle.net/r8fSg/. Note that $parent.items() is the observableArray of items that is used in the foreach.

<div data-bind="foreach: items">
    <p><input data-bind='value: $parent.items()[$index()]' /></p> 
</div>

<pre data-bind="text: ko.toJSON($data)"></pre>

And the model:

var ViewModel = function() {
    this.items = ko.observableArray([ko.observable("hi"), ko.observable("hi")]);
};

ko.applyBindings(new ViewModel());
slipheed
  • 7,170
  • 3
  • 27
  • 38
3

The issue is that Knockout doesn't get a reference to the source when it isn't the property of something. Since you are just passing it a function, the two-way binding fails. This has been noted before, is logged as an issue here, and the behavior is expalined in this issue.

This is not an ideal solution, but you can control object serialization with toJSON methods. This will allow you to produce what appears to be an array of strings, while still being observable in your app.

var Item = function(name){
    this.name = ko.observable(name);
};

Item.prototype.toJSON = function(){
    //special note: knockout already unwraps the observable for you
    return this.name;
};

Here is the fiddle


Update

See slipheeds answer for a solution that just uses a binding, which I prefer to this method. Leaving this answer in case others prefer it

Community
  • 1
  • 1
Kyeotic
  • 19,697
  • 10
  • 71
  • 128
  • Thanks, those (the bugs) are exactly what I was looking for. The workaround is nice too! – slipheed Apr 01 '13 at 19:53
  • 1
    BTW, looks like the easiest fix is using `value: $parent.list[$index()]` instead of `value: $data`. – slipheed Apr 01 '13 at 19:53
  • That definitely looks cleaner, you should put it as an answer – Kyeotic Apr 01 '13 at 19:55
  • I have the same problem, but I use the mapping plugin so I don't have to define properties on my model. With this, I have to again. I'd go with the solution from @slipheed in my scenario. Both are nice! – Brandon Apr 11 '14 at 12:11
2

In Knockout 3.0 and above using $rawData instead of $data solves this problem. It is also mentioned as solution @ https://github.com/knockout/knockout/issues/708#issuecomment-27630842

Ramesh
  • 13,043
  • 3
  • 52
  • 88
1

Another solution is to use the Repeat binding (https://github.com/mbest/knockout-repeat), which does provide this functionality. Here's your example updated to use Repeat: http://jsfiddle.net/LDNeA/1/

<div data-bind="repeat: array1">
    <p><input data-bind='value: $item' /></p> 
</div>
Michael Best
  • 16,623
  • 1
  • 37
  • 70