Indexed list and collection-repeat

Hey guys,

I am trying to get an indexed list working (https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/TableViewStyles/TableViewCharacteristics.html#//apple_ref/doc/uid/TP40007451-CH3-SW3). I had something similar working with ng-repeat, where i had every item have an id, and then the indexed list would just refer to this id and jump to the position on the list. Because of performance problems, I decided to jump to collection-repeat. Since the DOM is not created for all items, neither are the ids for these rows. What’s a good way to implement these jumps? Any ideas?

Andi

Did you try to use $index from collection-repeat scope?

I’m not sure what you mean. I don’t have a problem indexing the values, the problem is that with collection-repeat the values that are not loaded on the current scroll (eg. items that start with letter Z that arte at the bottom of the list) will not be addressable. At least to my knowledge. Is there a way to tell the collection-repeat to scroll to a certain element?

My base problem is that I’m not getting a proper scroll to the each item.

<div collection-repeat="recipee in recipees | filter:{category:filter.category, authorId:filter.authorId} | groupByDateOrName:(grouping=='Date') track by recipee.id" item-height="getItemHeight(recipee, $index)" class="alpha_list" id="index_{{recipee.id}}">
...
</div>

<ul class="alpha_sidebar" ng-show="mode == 'list'">
  <li ng-click="scrollTo('index_{{g.recipeeId}}')" ng-repeat="g in swipeGroup">{{g.name}}</li>
</ul>

$scope.scrollTo = function(location) {
  $location.hash(location);
  $ionicScrollDelegate.anchorScroll();
};

The location changes, but the scroll bar always scrolls to the top, ignoring the actual #location i’m sending. Switching to ng-repeat makes this work.

It looks like there was an issue opened for this and the Ionic team removed the ability to use anchorScroll with collectionRepeat:

Maybe you could submit a feature request? http://ionicframework.com/submit-issue/

Added it here:

I’m thinking of implementing an index search with a scrollTo function. I’ll post results.

Well, the problem now resides that I can’t really do aliasing for the collection-repeat. Any ideas?

<div collection-repeat="recipee in recipees | filter:{category:filter.category, authorId:filter.authorId} | groupByDateOrName:(grouping=='Date') track by recipee.id as filteredItems" item-height="getItemHeight(recipee, $index)" class="alpha_list" id="index_{{recipee.id}}">
...
</div>

<ul class="alpha_sidebar" ng-show="mode == 'list'">
  <li ng-click="scrollTo({{g.recipeeId}})" ng-repeat="g in swipeGroup">{{g.name}}</li>
</ul>

$scope.scrollTo = function(recipeeId) {
  // The following code works with ng-repeat but not with collection-repeat
  //$location.hash(location);
  //$ionicScrollDelegate.anchorScroll();

  // collection-repeat workaround
  var scrollHeight = 0;
  for (var i = 0; i < $scope.filteredItems.length; i++) {
    if ($scope.filteredItems[i].id == recipeeId) {
      return scrollHeight;
    }
    scrollHeight += $scope.getItemHeight($scope.filteredItems[i], i);
  }
};

I can’t use the “recipe in recipees | filter1 | filter2 as filteredItems”. Is there an alternate way to declare filters? Can I refrence the filtered list from my controller???

You can do the filtering in the javascript, here is one example: http://codepen.io/brandyshea/pen/gbZrNQ?editors=101

If you create a codepen with some sample data I could help more.

Ok. Had to hack my way through.

Step 1. Add alias to ionic’s collection repeat. Go to ionic.bundle.js on the CollectionRepeatDirective and add this to the regex.

var match = repeatExpr.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);

After this, add:

var aliasAs = match[3]; // AC: Added this
// Alias test
if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||        /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
  throw Error("alias " + aliasAs +" is invalid --- must be a valid JS identifier which is not a reserved name.");
}

And below on the scope.$watchCollection call, add the following before the timeout function:

scope.$watchCollection(listGetter, function(newValue) {
  newValue || (newValue = []);

  if (!angular.isArray(newValue)) {
    throw new Error("collection-repeat expected an array for '" + listExpr + "', " +
      "but got a " + typeof value);
  }

  // AC: Adding alias as from angular.js' ng-repeat
  if (aliasAs) {
    scope[aliasAs] = newValue;
  }

  // Wait for this digest to end before refreshing everything.
  $timeout(function() {
    getRepeatManager().refreshData(newValue);
    if (newValue.length) refreshDimensions();
  }, 0, false);
});

Step 2. On the scroll function on your controller, add the following:

$scope.scrollTo = function(recipeeId) {
  // The following code works with ng-repeat but not with collection-repeat
  //$location.hash(location);
  //$ionicScrollDelegate.anchorScroll();
  
  // collection-repeat workaround
  var items = $scope.$$childHead.filteredItems;
  var scrollHeight = 0;
  for (var i = 0; i < items.length; i++) {
    if (items[i].id == recipeeId) {
      $ionicScrollDelegate.scrollTo(0, scrollHeight, true);
    }
    scrollHeight += $scope.getItemHeight(items[i], i);
  }
};

Still not the best way to do it, and I have to reference the $$childHead which is suboptimal. If any of you Ionic devs have any comments or possible inclusions to collection-repeat, please let me know.

thx for your solution, I will try later.

@andiCR - I am trying to get indexed list working with collection-repeat as well. I wanted to know if $scope.getItemHeight in the controller that you defined is in the ionic.bundle.js or a custom function that you wrote.

Do you have a codepen or can you share the controller code?