Generating list with dividers dynamically

#1

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 ?

#2

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

#3

Thanks Adam. Appreciate it.

#4

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.

#5

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
Showing / Hiding List Dividers
#6

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

#7

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

#8

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
#9

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.

#10

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
#11

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>
#12

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
#13

hello sir what is fc.contacts ?

#14

Wow, very well explained! Thanks

#15

Ignore “fc” it will be only contacts…

#16

same here as well !!

#17

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?

#18

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>