Reputation: 13
I am having problems with route.html's
foreach
binding in the project Flight Management Computer.
value
bindings in observableArray
all update simultaneouslyFor the route in javascript, I set up a ko.observableArray
of an Array
of ko.observable
(sounds very confusing, but code attached below nonetheless):
/* All bindings applied to viewmodel already */
var route = ko.observableArray();
var DEFAULT_ROUTE = [
ko.observable(), // Waypoint Name
ko.observable(), // Lat.
ko.observable(), // Lon.
ko.observable(), // Altitude Restriction
ko.observable(false), // Waypoint isValid
ko.observable('') // Waypoint information
];
Clicking a specific button adds the DEFAULT_ROUTE
with no problem, as it calls
route.push(DEFAULT_ROUTE);
The HTML Code looks roughly like this and have no UI issues:
<tbody data-bind="foreach: route">
<tr class="wpt-row">
<td><input data-bind="value: $data[0]"></td> <!--waypoint input-->
<td><input data-bind="value: $data[1]"></td> <!--lat. input-->
<td><input data-bind="value: $data[2]"></td> <!--lon. input-->
<td><input data-bind="value: $data[3]"></td> <!--alt. input-->
</tr>
</tbody>
However, problems arise when there are multiple arrays in the outer ko.observableArray
, as changing one input value both in the UI and in javascript will update ALL values in each array. Example:
var route = ko.observableArray([DEFAULT_ROUTE, DEFAULT_ROUTE, DEFAULT_ROUTE]);
// Then, outside viewmodel (in javascript console)
route()[0][0]('WPT'); // Sets the waypoint of the first input field to be 'WPT'
// Later
route()[0][0](); // 'WPT', correct
route()[1][0](); // 'WPT', incorrect, should be undefined
route()[2][0](); // 'WPT', incorrect, should be undefined
I set up a similar foreach
in a different file, but with <input>
simply as <span>
, and data-bind
as text: $data[x]
instead of value
. That different file works fine with no problems. The different file is log.html
After the route
problem is fixed, I wish to update some specific values in a single array (one waypoint input field) when another value in that same array changes. I.E.
// Scenario 1, waypoint is a valid waypoint with proper coords
var waypoint = 'WAATR';
var coords = getWaypoint(waypoint); // [42.1234, -70.9876]
route()[0][0](waypoint);
// route()[0][0]() is now 'WAATR'
// route()[0][1] and route()[0][2] should automatically update with value `coords[0]` and `coords[1]`
// route()[0][4] should be set to true (valid waypoint)
// Scenario 2, waypoint is NOT a valid waypoint
var waypoint = 'IDK';
var coords = getWaypoint(waypoint); // []
route()[0][0](waypoint);
// route()[0][0]() is now 'IDK'
// route()[0][1] and route()[0][2] should remain undefined, waiting for users to manually input coordinates
// route()[0][4] should be false (invalid waypoint)
I read the documentation and there is an extend
function, but I don't really understand it. The challenge right now is how to limit those automatic fill-in functions to a specific array (waypoint input field) instead of (like Problem #1) to the entire data table of input.
I would greatly appreciate if anybody could help, since the route
is the most important feature of the entire project.
Upvotes: 1
Views: 761
Reputation: 13
Hi user3297291, thank you for your kind help! Based on your suggestion, I was able to complete the function:
var Route = function () {
var self = this;
// Waypoint name
var _fix = ko.observable();
self.fix = ko.pureComputed({
read: function () {
return _fix();
},
write: function (val) {
_fix(val);
var coords = get.waypoint(val);
var isValid = coords[0] && coords[1];
self.lat(coords[0], isValid);
self.lon(coords[1], isValid);
self.info(coords[2]);
}
});
// Latitude
var _lat = ko.observable();
self.lat = ko.pureComputed({
read: function () {
return _lat();
},
write: function (val, isValid) {
_lat(val);
self.valid(isValid ? true : false);
}
});
// longitude
var _lon = ko.observable();
self.lon = ko.pureComputed({
read: function () {
return _lon();
},
write: function (val, isValid) {
_lon(val);
self.valid(isValid ? true : false);
}
});
// Is waypoint valid
self.valid = ko.observable(false);
// Waypoint info
self.info = ko.observable();
};
Upvotes: 0
Reputation: 23372
You should really use objects rather than arrays. It makes everything much easier to read and understand, and will greatly help debugging.
var Route = function() {
this.waypointName = ko.observable();
this.lat = ko.observable();
this.lon = ko.observable();
this.altitudeRestriction = ko.observable();
this.isValid = ko.observable(false);
this.waypointInfo = ko.observable('');
};
Like you already figured out, you can now use this by calling new Route()
. You'll solve issue 1 and have code that's easier to read and mantain. The right foundation to solve issue 2:
Because you now have a clearly defined model, you can start defining relations between the properties by using subscribe
or computed
. You want to change the waypointName
property and have other properties automatically update:
var Route = function() {
this.waypointName = ko.observable();
// Automatically updates when you set a new waypoint name
var coords = ko.pureComputed(function() {
return getWaypoint(this.waypointName());
}, this);
// Check if we got correct coords
this.isValid = ko.pureComputed(function() {
return coords().length === 2;
}, this);
// Auto-extract lat from coords, null if invalid
this.lat = ko.pureComputed(function() {
return this.isValid()
? coords()[0]
: null;
}, this);
// Auto-extract lat from coords, null if invalid
this.lon = ko.pureComputed(function() {
return this.isValid()
? coords()[1]
: null;
}, this);
};
You now have a default Route
with isValid: false
, lat: null
, lon:null
and when you set the waypointName
to a string value, like route.waypointName("WAATR")
, all properties will automatically update.
Upvotes: 1
Reputation: 895
Question 1: This is rather an javascript related problem, than a knockoutjs. You are push-ing a reference to the same object again and again, and thereby making your observableArray contain multiple references to same object. You should change your code, to use a factory function instead:
var DEFAULT_ROUTE = function(){
return [
ko.observable(), // Waypoint Name
ko.observable(), // Lat.
ko.observable(), // Lon.
ko.observable(), // Altitude Restriction
ko.observable(false), // Waypoint isValid
ko.observable('') // Waypoint information
];
};
And then pushing:
route.push(DEFAULT_ROUTE());
This way you add a new object each time.
Upvotes: 0