JavaScript: pass deeply nested field name as parameter



  • I am using Knockout.js to do stuff, and I built a little object to encapsulate some Knockout fields and stateful behavior. But the loadNext function isn't working for me, and I don't understand how to fix it. Here is the object definition:

    function ItemState(key) {
      var self = this;
      self.item = ko.observable();
      self.items = ko.observable();
      self.loadNext = function () {
        var i = _.findIndex(self.items()(), function (obj) {
          return self.item()[key] == obj[key];
        });
        self.item(self.items()()[i + 1]);
      }
    }
    

    This seems to work sometimes. But I am trying to pass in a key = client.id and it seems to fail. Also, I know that even if I get past this issue, I'll have a further one, since client.id is a Knockout observable (so it has to be executed, like obj.client.id()).

    How can I handle this generically? Ideally, I'd like to just pass in "client.id()" as an argument to the constructor ItemsState. Or do something totally different that lets me compare objects by arbitrary fields.



  • General item.... consistently pass functions not values.....



  • @captain Like @TheCPUWizard said, passing a "comparable value extractor" function instead of a string representation would be more flexible. Sticking to traditional ES5 mode, it could look like this:

    function ItemState(accessor) {
      var getter;
      if (typeof accessor === 'function') {
        getter = accessor;
      } else {
        getter = function (item) { return item[accessor]; }
      }
      var self = this;
      self.item = ko.observable();
      self.items = ko.observable(); // based on the usage below, this seems to store a collection getter function, not a collection
                                    // might want to rename it to itemsProvider or something like that
      self.loadNext = function () {
        var items = self.items()(); // fetch the items only once, or some bastard could pass a getter function that returns different results with each call
        var target = getter(self.item());
        var i = _.findIndex(items, function (obj) {
          return target == getter(obj); // do you want == or === ?
        });
        // TODO: what if i === -1?
        self.item(items[i + 1]);
      }
    }
    
    new ItemState('someProperty')
    new ItemState(function(item) { return item.client.id(); })
    


  • @Captain I'm not sure if this search logic makes sense though. The logic is finding "whatever item is right after the first matching item in the whole list".

    You may instead want to find "the next matching item after the current one":

      self.loadNext = function () {
        var items = self.items()();
        var currentItem = self.item();
        var startIndex = _.indexOf(items, currentItem) + 1;
        var target = getter(currentItem);
        var i = _.findIndex(items, function (obj) {
          return target == getter(obj); // do you want == or === ?
        }, startIndex);
        // TODO: what if i === -1?
        self.item(items[i]);
      }
    


  • @dcoder This makes a whole lot of sense. I'm having to squash a few bugs now that this part is working, but it's working well. Thanks! I learned something new about writing JS.


Log in to reply