Binding a value to a specific index of an observable array?

5,039 views
Skip to first unread message

Brian Vallelunga

unread,
May 29, 2012, 9:29:35 AM5/29/12
to knock...@googlegroups.com
Is it possible to bind a value to a specific index of an observable array? I tried the following, which didn't work:

var viewModel = {
values: ko.observableArray()
}

<input data-bind="value: values[0]" />
<input data-bind="value: values[1]" />

rpn

unread,
May 29, 2012, 9:33:34 AM5/29/12
to knock...@googlegroups.com
Hello-
You would need to get to the underlying array, by calling it as a function like:

data-bind="value: values()[0]"

st...@media-phile.com

unread,
May 29, 2012, 11:44:01 PM5/29/12
to knock...@googlegroups.com
Why not extend your ViewModel to expose the item you're looking for as a computed property?  I bet there is some kind of logic behind why you are interested in showing a particular item in the list, and good separation of logic and presentaiton would suggest you ought to implement that in your ViewModel.
 
var viewModel = {
self: this,
values: ko.observableArray(),
 
interestingIndex: 0, //This value can be set to whatever index you're interested in displaying
 
interestingValue: ko.computed(function() {
 
    return values()[self.interestingIndex];  //May want to return null if the list is empty
 
}
}

Brian Vallelunga

unread,
May 31, 2012, 9:28:01 AM5/31/12
to knock...@googlegroups.com
It's not the showing that I was interested in, as much as the saving. I am building a query editor and one of the structures we have is a row, which is defined as:

field: ko.observable(),
operator: ko.observable(),
values: ko.observableArray()

This works great 99% of the time. Here are a couple of typical examples that fits into the 99%:

field = "Tags"
operator = "contains"
values = ["volunteer", "promoted", "special"]

field = "LastLoginDate"
operator = "eq"
values = ["ThisMonth"]

However, there are a few instances where I need extended values, such as:

field = "LastLoginDate"
operator = "eq"
values = ["CustomRange"]

I need somewhere to store the custom values passed in. I was thinking it might be possible to bind the custom range values to the second and third values in the array like:
values = ["CustomRange", "2012-04-01", "2012-05-01"] 

Unfortunately, this didn't work with the syntax exposed above, at least not when also using the Knockout Kendo UI bindings. I may see if I can write my own binding to accomplish this, but I believe it may be more trouble than it's worth. My current plan is to simply add values to the view model as you suggested. Something like:

field: ko.observable(),
operator: ko.observable(),
values: ko.observableArray(),
customRangeValueStart: ko.observable(),
customRangeValueEnd: ko.observable()

While this will obviously work, it just isn't very flexible.

Steve Gordon

unread,
May 31, 2012, 1:46:44 PM5/31/12
to knock...@googlegroups.com
Ah, gotcha.  Have you considered using different types of objects to represent different kinds of values?  For example, in some cases you want to model a scalar value, in other cases you want to model a range.  You can have two different object prototypes for those cases.  Each one can have a toValueArray() method that will return the corresponding array representation that your save operation is expecting.  Then you can bind your view to the specific value object and apply a view template that is appropriate for the given value type.
 
function ScalarValue() {
    var self = this;
 
    this.val = ko.observable(0);
 
    this.toValueArray = function() {
        return [ self.val() ];
    }
}
 
function RangeValue() {
    var self = this;
 
    this.lowerBound = ko.observable(0);
    this.upperBound = ko.observable(0);
 
    this.toValueArray = function() {
        return [ self.lowerBound(), self.upperBound() ];
    }
}
 
// and more types to model things like tags, last login date, and anything else
 
//This VM represents the filter expression
BinaryExpression = function()
{
    this.field = ko.observable();
    this.operator = ko.observable();
    this.value = ko.observable(new ScalarValue()); //this field can hold any of the value types
 
    this.save = function() {
        callSaveService({field: self.field, operator: self.operator, values: self.value.toValueArray() });
    }
}
 
 
In this way, you can create various ViewModels that naturally model the type of value you are trying to manipulate, you can bind different templates to different view types so that you can keep your presentations separate and clean, and when you're ready to save, you can "serialize" the value information into a flattened array by polymorphically calling the toValueArray method of the given value object. 

Steve Gordon

unread,
May 31, 2012, 1:49:29 PM5/31/12
to knock...@googlegroups.com
One small correction... The properties referenced in the save method need parentheses to access their values:
 
        callSaveService({field: self.field(), operator: self.operator(), values: self.value().toValueArray() });

Brian Vallelunga

unread,
May 31, 2012, 8:32:34 PM5/31/12
to knock...@googlegroups.com
Thanks for the great idea! Each of the fields in a row has some metadata associated with it already provides the type of its value (date, string, number, collection, etc) and I'm already swapping out the editing template, so this worked nicely. Here's what I'm doing now:

I have the ScalarValue, DateValue, ArrayValue, etc, set up as you describe. My row is now defined as having

field: ko.observable()
operator: ko.observable()
_value: ko.observable()
value: ko.computed(function() {
    return _value().toValueArray();
})

In my view model, I'm using the convention of a leading _ to filter things out that I don't want serialized to JSON, so the end result is exactly what I was looking for originally. My template binding is a little strange, in that I now have to do:

<input data-bind="_value().startDate" />
<input data-bind="_value().endDate" /> 

The one trick, is that since a user can select and change the field in a row, I'm using Knockout's manual subscription feature to subscribe to the field's change event and then swapping the value observable's inner type. Overall, it seems to be working great.
Reply all
Reply to author
Forward
0 new messages