Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(list): allow setting a custom trackBy function for the underlying ngFor directive #435

Merged
merged 6 commits into from Aug 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/app/list/basic-list/example/list-example.component.html
Expand Up @@ -18,5 +18,8 @@ <h4>List Component Example</h4>
<tab heading="Heading" (select)="tabSelected($event)">
<list-heading-example *ngIf="activeTab === 'Heading'"></list-heading-example>
</tab>
<tab heading="Polling" (select)="tabSelected($event)">
<list-polling-example *ngIf="activeTab === 'Polling'"></list-polling-example>
</tab>
</tabset>
</div>
2 changes: 2 additions & 0 deletions src/app/list/basic-list/example/list-example.module.ts
Expand Up @@ -16,6 +16,7 @@ import { ListModule } from '../list.module';
import { ListBasicExampleComponent } from './list-basic-example.component';
import { ListCompoundExampleComponent } from './list-compound-example.component';
import { ListHeadingExampleComponent } from './list-heading-example.component';
import { ListPollingExampleComponent } from './list-polling-example.component';
import { ListExampleComponent } from './list-example.component';
import { ListPinExampleComponent } from './list-pin-example.component';
import { NodesContentComponent } from './content/nodes-content.component';
Expand All @@ -30,6 +31,7 @@ import { SortArrayPipeModule } from '../../../pipe/sort-array';
ListBasicExampleComponent,
ListCompoundExampleComponent,
ListHeadingExampleComponent,
ListPollingExampleComponent,
ListExampleComponent,
ListPinExampleComponent,
NodesContentComponent
Expand Down
159 changes: 159 additions & 0 deletions src/app/list/basic-list/example/list-polling-example.component.html
@@ -0,0 +1,159 @@
<div class="padding-15">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<pfng-list id="myList"
[actionTemplate]="actionTemplate"
[config]="listConfig"
[expandTemplate]="expandTemplate"
[items]="items"
[itemTemplate]="itemTemplate"
[trackBy]="trackByIndex"
(onActionSelect)="handleAction($event, null)"
(onClick)="handleClick($event)"
(onDblClick)="handleDblClick($event)"
(onSelectionChange)="handleSelectionChange($event)">
<ng-template #itemTemplate let-item="item" let-index="index">
<div class="list-pf-left">
<span class="fa {{item.typeIcon}} list-pf-icon list-pf-icon-bordered list-pf-icon-small"></span>
</div>
<div class="list-pf-content-wrapper">
<div class="list-pf-main-content">
<div class="list-pf-title">{{item.name}}</div>
<div class="list-pf-description text-overflow-pf">{{item.address}}</div>
</div>
<div class="list-pf-additional-content">
<div>
<span class="pficon pficon-screen"></span>
<strong>{{item.hostCount}}</strong> Hosts
</div>
<div>
<span class="pficon pficon-cluster"></span>
<strong>{{item.clusterCount}}</strong> Clusters
</div>
<div>
<span class="pficon pficon-container-node"></span>
<strong>{{item.nodeCount}}</strong> Nodes
</div>
<div>
<span class="pficon pficon-image"></span>
<strong>{{item.imageCount}}</strong> Images
</div>
</div>
</div>
</ng-template>
<ng-template #actionTemplate let-item="item" let-index="index">
<pfng-action class="list-pf-actions"
[config]="actionConfig"
(onActionSelect)="handleAction($event, item)">
</pfng-action>
</ng-template>
<ng-template #expandTemplate let-item="item" let-index="index">
<p>This should stay open while the list updates.</p>
<basic-content [item]="item"></basic-content>
</ng-template>
</pfng-list>
</div>
</div>
</div>
<div class="row padding-top-10">
<div class="col-sm-12">
<h4 class="actions-label">Settings</h4>
<hr/>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<form role="form">
<div class="form-group">
<label class="radio-inline">
<input id="selectType1" name="selectType" type="radio"
[(ngModel)]="selectType" value="checkbox" (ngModelChange)="updateSelectionType()">Checkbox
</label>
<label class="radio-inline">
<input id="selectType2" name="selectType" type="radio"
[(ngModel)]="selectType" value="radio" (ngModelChange)="updateSelectionType()">Radio Button
</label>
<label class="radio-inline">
<input id="selectType3" name="selectType" type="radio"
[(ngModel)]="selectType" value="row" (ngModelChange)="updateSelectionType()">Row
</label>
<label class="radio-inline">
<input id="selectType4" name="selectType" type="radio"
[(ngModel)]="selectType" value="none" (ngModelChange)="updateSelectionType()">None
</label>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<form role="form">
<div class="form-group">
<label class="checkbox-inline">
<input id="dblClick" name="dblClick" type="checkbox"
[(ngModel)]="listConfig.dblClick"
(ngModelChange)="listConfig.multiSelect = false">Double Click
</label>
<label class="checkbox-inline">
<input id="multiSelect" name="multiSelect" type="checkbox"
[(ngModel)]="listConfig.multiSelect"
[disabled]="listConfig.dblClick">Multi Select
</label>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<form role="form">
<div class="form-group">
<label class="checkbox-inline">
<input id="useExpandingRows" name="useExpandingRows" type="checkbox"
[(ngModel)]="listConfig.useExpandItems">Simple Expansion
</label>
<label class="checkbox-inline">
<input id="itemsAvailable" name="itemsAvailable" type="checkbox"
[(ngModel)]="itemsAvailable"
(ngModelChange)="updateItemsAvailable()">Items Available
</label>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<button (click)="resetItems()">Reset items</button>
</div>
</div>
<div class="row padding-top-10">
<div class="col-sm-12">
<h4 class="actions-label">Actions</h4>
<hr/>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<textarea rows="3" class="col-sm-12">{{actionsText}}</textarea>
</div>
</div>
<div class="row padding-top-10">
<div class="col-sm-12">
<h4>Code</h4>
<hr/>
</div>
</div>
<div>
<tabset>
<tab heading="api">
<iframe class="demoframe" src="docs/classes/listcomponent.html"></iframe>
</tab>
<tab heading="html">
<include-content src="src/app/list/basic-list/example/list-polling-example.component.html"></include-content>
</tab>
<tab heading="typescript">
<include-content src="src/app/list/basic-list/example/list-polling-example.component.ts"></include-content>
</tab>
</tabset>
</div>
</div>
194 changes: 194 additions & 0 deletions src/app/list/basic-list/example/list-polling-example.component.ts
@@ -0,0 +1,194 @@
import {
Component,
OnInit,
TemplateRef,
ViewEncapsulation
} from '@angular/core';

import { cloneDeep } from 'lodash';

import { Action } from '../../../action/action';
import { ActionConfig } from '../../../action/action-config';
import { EmptyStateConfig } from '../../../empty-state/empty-state-config';
import { ListEvent } from '../../list-event';
import { ListConfig } from '../list-config';

@Component({
encapsulation: ViewEncapsulation.None,
selector: 'list-polling-example',
templateUrl: './list-polling-example.component.html'
})
export class ListPollingExampleComponent implements OnInit {
actionsText: string = '';
allItems: any[];
emptyStateConfig: EmptyStateConfig;
items: any[];
itemsAvailable: boolean = true;
listConfig: ListConfig;
actionConfig: ActionConfig;
selectType: string = 'checkbox';
updateItemsInterval: number;

constructor() {
}

ngOnInit(): void {
this.allItems = [
this.makeRandomItem(),
this.makeRandomItem(),
this.makeRandomItem(),
];
this.allItems[0].expanded = true;
this.items = cloneDeep(this.allItems);

this.emptyStateConfig = {
actions: {
primaryActions: [{
id: 'action1',
title: 'Main Action',
tooltip: 'Start the server'
}],
moreActions: [{
id: 'action2',
title: 'Secondary Action 1',
tooltip: 'Do the first thing'
}, {
id: 'action3',
title: 'Secondary Action 2',
tooltip: 'Do something else'
}, {
id: 'action4',
title: 'Secondary Action 3',
tooltip: 'Do something special'
}]
} as ActionConfig,
iconStyleClass: 'pficon-warning-triangle-o',
title: 'No Items Available',
info: 'This is the Empty State component. The goal of a empty state pattern is to provide a good first ' +
'impression that helps users to achieve their goals. It should be used when a list is empty because no ' +
'objects exists and you want to guide the user to perform specific actions.',
helpLink: {
hypertext: 'List example',
text: 'For more information please see the',
url: '#/list'
}
} as EmptyStateConfig;

this.actionConfig = {
primaryActions: [],
moreActions: [{
id: 'hint',
title: 'This menu should stay open while the list updates',
tooltip: 'This menu should stay open while the list updates'
}],
moreActionsDisabled: false,
moreActionsVisible: true
} as ActionConfig;

this.listConfig = {
dblClick: false,
emptyStateConfig: this.emptyStateConfig,
multiSelect: false,
selectItems: false,
selectionMatchProp: 'name',
showCheckbox: true,
showRadioButton: false,
useExpandItems: true
} as ListConfig;

this.updateItemsInterval = <any>setInterval(() => this.updateItems(), 2500);
}

ngDoCheck(): void {
}

ngOnDestroy(): void {
clearInterval(this.updateItemsInterval);
}

updateItems(): void {
if (this.items.length < 20) {
this.items = [...this.items, this.makeRandomItem()];
}
}

resetItems(): void {
this.items = cloneDeep(this.allItems);
}

makeRandomItem(): any {
return {
name: `Random ${getRandomArbitrary(0, 20)}`,
address: `Some Address ${getRandomArbitrary(1, 100)}`,
city: 'Bedrock',
state: 'Washingstone',
typeIcon: 'fa-plane',
clusterCount: getRandomArbitrary(1, 6),
hostCount: getRandomArbitrary(1, 8),
imageCount: getRandomArbitrary(1, 8),
nodeCount: getRandomArbitrary(1, 10)
};
}

/**
* Get the tracking id to use for each row
*
* @param index The current row index
* @param item The current row item
* @returns number
*/
trackByIndex(index: number, item: any): any {
return index;
}

// Actions

handleAction($event: Action, item: any): void {
if ($event.id === 'start' && item !== null) {
item.started = true;
}
this.actionsText = $event.title + ' selected\r\n' + this.actionsText;
}

handleSelectionChange($event: ListEvent): void {
this.actionsText = $event.selectedItems.length + ' items selected\r\n' + this.actionsText;
}

handleClick($event: ListEvent): void {
this.actionsText = $event.item.name + ' clicked\r\n' + this.actionsText;
}

handleDblClick($event: ListEvent): void {
this.actionsText = $event.item.name + ' double clicked\r\n' + this.actionsText;
}

// Row selection

updateItemsAvailable(): void {
this.items = (this.itemsAvailable) ? cloneDeep(this.allItems) : [];
}

updateSelectionType(): void {
if (this.selectType === 'checkbox') {
this.listConfig.selectItems = false;
this.listConfig.showCheckbox = true;
this.listConfig.showRadioButton = false;
} else if (this.selectType === 'radio') {
this.listConfig.selectItems = false;
this.listConfig.showCheckbox = false;
this.listConfig.showRadioButton = true;
} else if (this.selectType === 'row') {
this.listConfig.selectItems = true;
this.listConfig.showCheckbox = false;
this.listConfig.showRadioButton = false;
} else {
this.listConfig.selectItems = false;
this.listConfig.showCheckbox = false;
this.listConfig.showRadioButton = false;
}
}
}

function getRandomArbitrary(min: number, max: number): number {
return Math.floor(Math.random() * (max - min) + min);
}
2 changes: 1 addition & 1 deletion src/app/list/basic-list/list.component.html
Expand Up @@ -34,7 +34,7 @@
<!-- items -->
<div class="list-pf-item {{item?.itemStyleClass}}"
[ngClass]="{'active': item.selected || item.expanded}"
*ngFor="let item of (config.usePinItems ? (items | sortArray: 'showPin': true) : items); let i = index">
*ngFor="let item of (config.usePinItems ? (items | sortArray: 'showPin': true) : items); let i = index; trackBy: trackBy">
<div class="list-pf-container" [id]="getId('item', i)" (click)="toggleExpandItem($event, item)">
<!-- pin -->
<div class="pfng-list-pin-container" *ngIf="config.usePinItems">
Expand Down