Generating list with dividers dynamically

I’m just getting started with AngularJS and ionic. Is there a way to generate a list with dividers dynamically from an array of objects, each having a boolean to denote if that item is a divider or not ?

At this time no, but that’s not a bad idea. I’ll add it to our issues: https://github.com/driftyco/ionic/issues/348

Thanks Adam. Appreciate it.

Hey guys,

ive been testing this all morning and I cant figure out how to combine this technique with a ng-repeat. Adding the class doesn’t override the href. So this technique clashes with dynamic population of lists. In the github issue it states that the issue is closed.

Instead of trying to get the GUI portion of your app to do this, why not use JavaScript to accomplish it? I have a similar scenario in my app. I display a user’s entire Address Book in a list. I needed the first letter dividers as you see in iOS address book.

So, I collect all the contacts (using PhoneGap API’s), then I iterate through them adding them to an object that has the first letters as properties.

Then, I use 2 ng-repeats. One repeats for the first letters in the contacts object. Inside there, I repeat for each actual contact.

Example :

var contacts = [ 
	{'givenName' : 'Lisa',	'familyName' : 'Adams'	},
	{'givenName' : 'Bob',	'familyName' : 'Arnet'	},
	{'givenName' : 'Frank',	'familyName' : 'Able'	},
	{'givenName' : 'John',	'familyName' : 'Crow'	},
	{'givenName' : 'Bill',	'familyName' : 'Ward'	}
];

var $scope.contacts = {};

var contactsLength = contacts;
var firstLetter;

for(var i = 0; i < contactsLength; i++) {
	firstLetter = contacts[i].familyName.substring(0,1).toUpperCase();

	if(!$scope.contacts[firstLetter]) $scope.contacts[firstLetter] = [];

	$scope.contacts[firstLetter].push ( contacts[i].givenName + ' ' + contacts[i].familyName );
}

Then, I end up with something that looks like this:

$scope.contacts = {
	'A' : [
		'Lisa Adams',
		'Bob Arnet',
		'Frank Able'
	],
	'C' : [
		'John Crow'
	],
	'W' : [
		'Bill Ward'
	]
}

In your HTML, do something like this:

<div class="list">

    <div class="item item-divider"
        ng-repeat-start="(firstLetter, contacts) in fc.contacts">
        {{firstLetter}}
    </div>

    <div class="item"
         ng-repeat="contact in contacts"
         bindonce
         ng-click="contactSelected(contact.id)">{{ contact }}
    </div>

    <div ng-repeat-end></div>

</div>

You end up with this:

7 Likes

That looks totally feasible, our data structure already has the items listed with a character so I didnt consider this option. Thanks!

Thanks for the example! But I was wondering how to make the items link to another page? Because of the structure of $scope.contacts, I am unable to retrieve the id. Thanks to the push command, room only exists with no properties:

href="#/tab/room/{{room.id}}">{{room}}

Thanks to the push command, room only exists and represents the name-property we pushed in the controller-section.
Is it possible to redirect using ng-click like in your example?

Thanks

I’m not sure I’m understanding room vs contact and whether or not you have an id, but you can still link as much as you like.

This was written a good while ago. I don’t think I was using ui-router when I show this example. I’m assuming you are using a more recent version of Ionic.

So, you can do this two ways:

ng-click method :

<div class="list">
   <a class="item" ng-repeat="room in rooms" ng-click="goToRoom(room)">{{room}}</a>
</div>

ui-router method : See : http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-sref

<div class="list">
   <a class="item" ng-repeat="room in rooms" ui-sref="rooms({ "roomName" : room})">{{room}}</a>
</div>
1 Like

Thanks for the reply! I’m using rooms instead of contacts but the principle remains the same.

The problem I have is that there is no way I can access the room id; before I used your code example, I just used $scope.rooms = rooms;
in my controller so I could access all the properties of a room (id, name, capacity).

But when I use your code example to generate my lists with dividers dynamically, I am unable to access this properties. Because of the command $scope.rooms[firstLetter].push(rooms[i].name)
the room property in my html directly refers to the name which means room.id doesn’t exist.

I need the room.id to redirect to a specific page when a user click on a room.
I hope I was able to explain my problem.

You just need to change this:

$scope.rooms[firstLetter].push(rooms[i].name)

to this:

$scope.rooms[firstLetter].push(rooms[i])

et voilà there you have your room with all it’s properties.

2 Likes

Hi all!..

I’m resurrecting the post to ask you if someone have an idea about how to apply list divider based in months/year…

for example, I have a payment list, and want to use the dividers to separate the payments by month/year:


june 2014

customer 1 06/01/2014


may 2014

customer 1 05/25/2014

customer 2 05/20/2014


april 2014


customer 1 04/22/2014

customer 2 04/19/2014


I would like to thank you since now for your attention…

SOLVED!

Just used the solution given by @Calendee … thank you dude!..

    var payments = [ 
        		{'name' : 'Lisa Simpson',	'id' : '81', 'value' : '$ 25,00', 'date' : '25/03/2014', 'month' : '03', 'year' : '2014'	},
        		{'name' : 'John Kennedy',	'id' : '83', 'value' : '$ 25,00', 'date' : '15/03/2014', 'month' : '03', 'year' : '2014'	},
        		{'name' : 'Michael J. Fox',	'id' : '77', 'value' : '$ 25,00', 'date' : '12/04/2014', 'month' : '04', 'year' : '2014'	},
        		{'name' : 'Aaron Dobson',	'id' : '65', 'value' : '$ 25,00', 'date' : '10/02/2014', 'month' : '02', 'year' : '2014'	},
        		{'name' : 'Julian Edelman',	'id' : '88', 'value' : '$ 25,00', 'date' : '07/02/2014', 'month' : '02', 'year' : '2014'	}
        			];
$scope.payments = {};
var month;

for(var i = 0; i < payments.length; i++) {
	month = payments[i].month +'/'+ payments[i].year;
				

	if(!$scope.payments[month]) $scope.payments[month] = [];

	$scope.payments[month].push ( payments[i].name + ' ' + payments[i].date );
}

the view:

<div class="list">

	<div class="item item-divider"
		ng-repeat-start="(month, payments) in payments">
					{{month}}
	</div>

	<div class="item"
		ng-repeat="payment in payments"
		ng-click="contactSelected(contact.id)">{{ payment }}
	</div>

	<div ng-repeat-end></div>
</div>

This worked great for me, the only difference is that I took a number from the array and made it my key like so:

{ '1': 
 [ { name: 'BCS McZainasheff\'s Wee', id: '71' },
 { name: 'Slurry Fluerious', id: '72' },
 { name: 'English Mild Brown Ale', id: '73' },
 { name: 'Sparkling Cider', id: '74' },
 { name: 'Unicorn Blood', id: '75' },
 { name: 'ZK experiments 1, 2, 3', id: '76' } ],
 '2': 
 [ { name: '10w30', id: '85' },
 { name: 'Not Old Chubb', id: '86' },
 { name: 'Lupulin XX', id: '87' } ],
'11': 
 [ { name: 'Buddy\'s Award Winning Pumpkin Spice Ale', id: '118' },
 { name: 'No Passport Needed', id: '119' },
 { name: 'White IPA', id: '120' } ],

Unfortunately it ng-repeat puts these out of order and I cannot for the life of me figure out how to fix it. Any help?

(ng-repeat puts it in this order: 1,11,12, etc)

1 Like

hello sir what is fc.contacts ?

Wow, very well explained! Thanks

Ignore “fc” it will be only contacts…

same here as well !!

Does @Calendee solution still work?

splitAlphabetically() {

    var firstLetter;

    for (var i = 0; i < this.activeUsersCount; i++) 
    {
      firstLetter = this.activeUsers[i].user_email.substring(0, 1).toUpperCase();

      if (!this.filteredContacts[firstLetter])
      {
        this.filteredContacts[firstLetter] = [];
      }

      this.filteredContacts[firstLetter].push(this.activeUsers[i]);
    }
  }

which displays my filteredContacts array alphabetically as below,

Object {A: Array(2), B: Array(1), D: Array(1)}
A : Array(2)
  0 : Object
    user_email  : "admin@gmail.com"
  1 : Object
    user_email  : "admintwo@gmail.com"
B :  Array(1)
  0 : Object
    user_email  : "bob@gmail.com"
D  : Array(1)
  0 : Object
    user_email  : "dave@gmail.com"

But when I try to populate on the html side, it can’t find user_email?

<ion-list-header ng-repeat-start="(firstLetter, contacts) in filteredContacts">
  {{firstLetter}}
</ion-list-header>
<ion-item-sliding ng-repeat="contact in contacts">
	<ion-item>
		<h2>{{contact.user_email}}</h2>
	</ion-item>

Can anyone help?

Okay so I have a solution that works with a slightly different data structure. Using the examples above here is how I worked it out:

var contacts = [
    {'givenName':'Lisa', 'familyName':'Adams', 'groupName':'Group 1'},
    {'givenName':'Bob', 'familyName':'Arnet', 'groupName':'Group 1'},
    {'givenName':'Frank', 'familyName':'Able', 'groupName':'Group 1'},
    {'givenName':'John', 'familyName':'Crow', 'groupName':'Group 2'},
    {'givenName':'Bill', 'familyName':'Ward', 'groupName':'Group 3'}
];

In order to group and sort the array I run it through these two functions:

let sContacts = sortArray(contacts, 'groupName', 1);
this.contacts = groupBy(sContacts, 'groupName', 'familyName');

var groupBy = function groupByArray(xs, key, sortKey) {
    return xs.reduce(function (rv, x) {
        let v = key instanceof Function ? key(x) : x[key];
        let el = rv.find(r => r && r.key === v);

        if (el) {
            el.values.push(x);
            el.values.sort(function (a, b) {
                return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
            });
        } else {
            rv.push({ key: v, values: [x] });
        }

        return rv;
    }, []);
};

function sortArray(array, property, direction) {
    direction = direction || 1;
    array.sort(function compare(a, b) {
        let comparison = 0;
        if (a[property] > b[property]) {
            comparison = 1 * direction;
        } else if (a[property] < b[property]) {
            comparison = -1 * direction;
        }
        return comparison;
    });
    return array;
}

Now we have something like:

{
    'key' : 'Group 1',
    'values' : [
		['givenName': 'Lisa Adams', 'familyName': 'Adams'], 
		['givenName': 'Bob', 'familyName': 'Arnet'],
		['givenName': 'Frank', 'familyName': 'Able']
    ],
    'key' : 'Group 2',
    'values' : [
        ['givenName': 'John', 'familyName': 'Crow']
    ],
    'key' : 'Group 3',
    'values' : [
        ['givenName': 'Bill', 'familyName': 'Ward']
    ]
}

And the html file simply looks like this:

    <ion-item-group *ngFor="let group of contacts">
      <ion-item-divider color="light">{{group.key}}</ion-item-divider>
      <button ion-item (click)="itemSelected(contact)" *ngFor="let contact of group.values">
        {{ contact.givenName }} {{ contact.familyName }}
      </button>
    </ion-item-group>