Best way to attach parent object on can.List items

I’m not sure if there’s a better more CanJS-y way of doing this so pardon my ignorance if there is.

When I iterate through a can.List, I need to also be able to access the previous item I iterated on. The best way I could do this was by adding _parent and _prevItem properties to each item in the list. I add the _parent property on init of the can.List. The _prevItem is a getter that gets the current index and returns the previous item based on that.

This resulted in the creation of this:

var PenultimateList = can.List.extend({
  init: function () {}//Adding _parent to all children here
});
PenultimateList.Map = can.Map.extend({
  define: {
    _prevItem: {
      get: function () {}//Returns the previous item by using _parent
    }
  }
});

First Issue

The first issue I run into is with the define property:

import PenultimateList from 'PenultimateList';
var Todos = PenultimateList.extend({
  define: {
    name: {
      type: 'string'
    }
  }
});

Todos will not have the _prevItem property I created before. I created a define extender that helps with this but I wondering if I don’t necessarily have to do that.

Second Issue

If using can-connect, having the _parent property causes a circular reference error with JSON.stringify. I worked around this by making _parent a function instead of a reference. Kinda weird but it works.

Third Issue

In order for each child to have the correct _parent, I create an ‘add’ listener during init that adds the property. Doing this causes can-connect to stop updating the list. (I’m still debugging this so I’ll update post once I get my bearings).

More info…

Here’s a jsbin that illustrates what I have going now: http://jsbin.com/foxuva/edit?js,output

I have two implementations of this in two different projects:

  • Project A: can-connect 0.3.3, canjs 2.3.2
  • Project B: no can-connect, canjs 2.2.6

Obviously, only Project A has the can-connect issues.

I feel like I’m doing some crazy workaround for can.Map not having access to it’s parent List (if there is one) or the previous item. So, I’m hoping that maybe I’m just crazy and there’s an easier way to get to the parent.

Soooo… after posting that, I had a thought. I think someone mentioned it to me and it just didn’t register.

I created a block helper that caches the previous item. Doing this avoids all three issues I mentioned. Weeee!

      supahList: function (list, opts) {
          var prevItem = false;
          var nextItem = false;
          var resp = [];
          // Loop through each item, add a previous
          // item property and render all the things!
          can.each(list(), function (item) {
              prevItem = nextItem;
              nextItem = item;
              // not comfortable adding the _prevItem property
              // to the item itself but... at least it works.
              item.attr('_prevItem', prevItem);
              var frag = opts.fn(item);
              resp.push(frag.textContent);
          });
          return resp.join(" ");
      }

So, for this situation, the helper will suffice. I think I need to tweak it a bit but it should be fine.

Didn’t like that I was adding a property to the items. I didn’t want to do this but it was the better solution in the end:

groupedList: function (list, opts) {
  var prevItem = false;
  var resp = [];
  can.each(list(), function (item) {
    var frag = opts.fn({curItem: item, prevItem: prevItem});
    resp.push(frag);
    prevItem = item;
  });
  return resp;
}

Now, in the block, I have two properties, curItem and prevItem. I didn’t really like this but it seems to be the best overall solution I could come up with.

Now, in the template, I can do:

{{#groupedList myList}}
  {{#is prevItem.createDate curItem.createDate}}
      Do stuff here!
  {{/is}}
  {{#with curItem}}
    {{createDate}}
  {{/with}}
{{/groupedList}}
1 Like