简体   繁体   中英

Knockout: observableArray of arrays of observable inputs

I am having problems with route.html 's foreach binding in the project Flight Management Computer.


Problem 1: value bindings in observableArray all update simultaneously

For 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


Problem 2 (or rather, a Question)

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. IE

// 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.

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.

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.

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();

};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM