Indexed list and collection-repeat


#1

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


#2

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


#3

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?


#4

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.


#5

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/


#6

Added it here:

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


#7

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???


#8

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.


#9

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.


#10

thx for your solution, I will try later.


#11

@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?