Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ionic Dynamic List Divider

I have been stumped on this problem for a while now, so I am hoping you can get me in the right direction.

My angular factory returns an object which looks like this

[{
    name:"Fall",
    year:"20xx",
    id: some_id_#
}, ....]

This is a list of semesters with an object for each semester containing the name, year, and semester id. I am using ionic for my UI framework, and I would like to set up my HTML output to look like this...

2012
  Fall
  Spring
2013
  Winter
  Spring
2014
  etc
  etc

Where each year is a list divider. My HTML currently looks like this

<ion-list show-delete="data.showDelete">
    <!-- I WAN TO CHANGE THIS TO BE A DYNAMIC HEADER ADDED FOR EACH NEAR YEAR-->
    <ion-item class="item-divider">
        Semesters
    </ion-item>

    <ion-item ng-show="semesters.length == 0">
        No semesters yet!
    </ion-item>
    <ion-item class="item-dark item-icon-right" href="#/app/class-list/{{semester.id}}/{{semester.name}}/{{semester.year}}" ng-repeat="semester in semesters">
        <ion-delete-button class="ion-ios7-trash-outline"
                   ng-click="deleteSemester(semester)">
        </ion-delete-button>
        {{semester.name}} {{semester.year}}
        <i class="icon ion-ios7-arrow-forward"></i>
    </ion-item>
</ion-list>

I don't like cluttering my view with logic, because that belongs in the controller, but I am not sure how to go about it, aha.

Thanks guys!

like image 657
Panda4Man Avatar asked Nov 01 '14 22:11

Panda4Man


3 Answers

I'm not sure if this is the best way of doing it, but I built a CodePen that does this:

  • Take an original list (this would be your real data)
  • Modify the list by creating additional items for unique starting letters
  • In the view, we see if our data is a letter, and if so, treat it as a list divider

This is kind of like how one of their pens work (http://codepen.io/ionic/pen/uJkCz). It feels slightly wrong to me, but it seems to work well. Here is the controller portion:

.controller('RootCtrl', function($scope) {

  //orig data
  var list = [];
  list.push({name:"Gary"});
  list.push({name:"Gosh"});
  list.push({name:"Ray"});
  list.push({name:"Sam"});
  list.push({name:"Sandy"});

  $scope.list = [];
  var lastChar = '';
  for(var i=0,len=list.length; i<len; i++) {
    var item = list[i];

    if(item.name.charAt(0) != lastChar) {
      $scope.list.push({name:item.name.charAt(0),letter:true});
      lastChar = item.name.charAt(0);
    }
    $scope.list.push(item);

  }
})

And then the view checks to see if the data is a person vs a letter. Again, this feels a bit lame, but...

<ion-list type="list-inset">
  <ion-item ng-repeat="person in list" ng-class="person.letter? 'item-divider':''">
    {{person.name}}
  </ion-item>
</ion-list>

You can run this here: http://codepen.io/cfjedimaster/pen/HqrBf

like image 106
Raymond Camden Avatar answered Oct 01 '22 21:10

Raymond Camden


Another solution, using UnderscoreJS, would have you "group" your list by the property you're interested in (here, the year), and then you can iterate over those groups in your template.

Short Version

in your controller:

$scope.groupedPairs = _.chain(list).groupBy('year').pairs().sortBy(0).value();

in your template:

<ion-list>
    <div ng-repeat="groupedPair in groupedPairs track by groupedPair[0]">
        <ion-item class="item-divider">
            {{groupedPair[0]}}
        </ion-item>
        <ion-item ng-repeat="item in groupedPair[1] track by item.id">
            {{item.name}}
        </ion-item>
    </div>
</ion-list>

Long Version

Start by grouping your list by the 'year' property.

var list = [{
    name:"Fall",
    year:"2014"
}, {
    name:"Fall",
    year:"2012"
}, {
    name:"Spring",
    year:"2012"
}, {
    name:"Spring",
    year:"2013"
}, {
    name:"Spring",
    year:"2014"
}, {
    name:"Winter",
    year:"2013"
}];
var grouped = _(list).groupBy('year');

grouped is now:

{
    "2014": [{
        name:"Fall",
        year:"2014"
    }, {
        name:"Spring",
        year:"2014"
    }],
    "2012": [{
        name:"Fall",
        year:"2012"
    }, {
        name:"Spring",
        year:"2012"
    }],
    "2013": [{
        name:"Spring",
        year:"2013"
    }, {
        name:"Winter",
        year:"2013"
    }]
}

But now you want it sorted by the year, so we need two more operations to do that.

First, transform the grouped object into an array of key/value pairs.

var pairs = _(grouped).pairs();

And pairs is now:

[
    [
        "2014",
        [{
            name:"Fall",
            year:"2014"
        }, {
            name:"Spring",
            year:"2014"
        }]
    ],
    [
        "2012",
        [{
            name:"Fall",
            year:"2012"
        }, {
            name:"Spring",
            year:"2012"
        }]
    ],
    [
        "2013",
        [{
            name:"Spring",
            year:"2013"
        }, {
            name:"Winter",
            year:"2013"
        }]
    ]
]

Now we just need to sort by the "key" in each key/value pair. In other words, the [0] property of each pair (which contains the year it was grouped into, e.g. "2013"):

var sortedPairs = _(pairs).sortBy(0);

And sortedPairs is now:

[
    [
        "2012",
        [{
            name:"Fall",
            year:"2012"
        }, {
            name:"Spring",
            year:"2012"
        }]
    ],
    [
        "2013",
        [{
            name:"Spring",
            year:"2013"
        }, {
            name:"Winter",
            year:"2013"
        }]
    ],
    [
        "2014",
        [{
            name:"Fall",
            year:"2014"
        }, {
            name:"Spring",
            year:"2014"
        }]
    ]
]

... which can be easily iterated over in your template. Given $scope.groupedPairs = sortedPairs;:

<ion-list>
    <div ng-repeat="groupedPair in groupedPairs track by groupedPair[0]">
        <ion-item class="item-divider">
            {{groupedPair[0]}}
        </ion-item>
        <ion-item ng-repeat="item in groupedPair[1] track by item.id">
            {{item.name}}
        </ion-item>
    </div>
</ion-list>

and the full controller code to convert var list to $scope.groupedPairs, without all the explanation:

$scope.groupedPairs = _.chain(list).groupBy('year').pairs().sortBy(0).value();
like image 39
colllin Avatar answered Oct 01 '22 20:10

colllin


This solution uses the ng-repeat alias feature to show a grouped date divider from a date object. The key to this solution is the ng-if on the divider. Note the parentheses around the date:'yyyy' filtered comparison, which only adds the divider to the DOM if the previous result year does not equal the current result year. It displays a grouped divider.

<ion-list show-delete="data.showDelete"
          ng-repeat="semester in semesters as results">

    <ion-item class="item-divider"
              ng-if="(results[$index-1].year|date:'yyyy') !== (results[$index].year|date:'yyyy')">
        {{results[$index].year|date:'yyyy'}}
    </ion-item>

    <ion-item ng-show="results.length == 0">
        No semesters yet!
    </ion-item>
    <ion-item class="item-dark item-icon-right" href="#/app/class-list/{{semester.id}}/{{semester.name}}/{{semester.year}}">
        <ion-delete-button class="ion-ios7-trash-outline"
                   ng-click="deleteSemester(semester)">
        </ion-delete-button>
        {{semester.name}} {{semester.year}}
        <i class="icon ion-ios7-arrow-forward"></i>
    </ion-item>
</ion-list>
like image 24
deinerson1 Avatar answered Oct 01 '22 20:10

deinerson1