How can I bind an editable ko.observableArray of observable strings?
Asked Answered
H

4

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>
Hausmann answered 1/4, 2013 at 18:47 Comment(3)
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?Snowonthemountain
@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". :)Hausmann
Apparently it is a logged issue, I updated my answerSnowonthemountain
H
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());
Hausmann answered 1/4, 2013 at 22:40 Comment(0)
S
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

Snowonthemountain answered 1/4, 2013 at 19:6 Comment(4)
Thanks, those (the bugs) are exactly what I was looking for. The workaround is nice too!Hausmann
BTW, looks like the easiest fix is using value: $parent.list[$index()] instead of value: $data.Hausmann
That definitely looks cleaner, you should put it as an answerSnowonthemountain
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 @Hausmann in my scenario. Both are nice!Genovese
M
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

Meningitis answered 27/1, 2016 at 17:32 Comment(1)
Excellent! This is the correct answer, and worked perfectly.Harbour
J
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>
Johann answered 2/4, 2013 at 0:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.