Skip to content

Commit 000fa42

Browse files
authored
Merge pull request #37 from Microsoft/pr/37
[Fabric] Add DetailsList, GroupItem, and MarqueeSelection components (#37)
2 parents 2b7b9a9 + 047c023 commit 000fa42

25 files changed

+1090
-96
lines changed

apps/demo/src/app/app.component.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,34 @@ <h2>Getting up and running...</h2>
6464
</fab-command-bar>
6565

6666
<fab-calendar [strings]="strings" (onSelectDate)="onSelectDate($event)"></fab-calendar>
67+
68+
<fab-marquee-selection [isEnabled]="marqueeEnabled" [selection]="selection">
69+
<fab-details-list [selection]="selection" [items]="detailItems" (onColumnHeaderClick)="onColumnHeaderClicked($event)">
70+
<columns>
71+
<fab-details-list-column key="column-1" [minWidth]="150" name="Column #1" fieldName="field1" [isResizable]="true">
72+
</fab-details-list-column>
73+
<fab-details-list-column key="column-2" [minWidth]="150" name="Column #2" fieldName="field2" [isResizable]="true">
74+
</fab-details-list-column>
75+
<fab-details-list-column key="column-3" [minWidth]="150" name="Custom Column" fieldName="field3" [isResizable]="true">
76+
<render>
77+
<ng-template let-index="index">
78+
Custom content for index {{ index }}
79+
</ng-template>
80+
</render>
81+
</fab-details-list-column>
82+
</columns>
83+
<groups>
84+
<fab-group-item key="group-1" name="Group #1" [count]="3" [startIndex]="0">
85+
<fab-group-item [level]="1" key="group-1-1" name="Nested Group #1.1" [count]="1" [startIndex]="0"></fab-group-item>
86+
<fab-group-item [level]="1" key="group-1-2" name="Nested Group #1.2" [count]="2" [startIndex]="1"></fab-group-item>
87+
</fab-group-item>
88+
<fab-group-item key="group-2" name="Group #2" [count]="1" [startIndex]="3"></fab-group-item>
89+
<fab-group-item key="group-3" name="Group #3" [count]="1" [startIndex]="4"></fab-group-item>
90+
</groups>
91+
</fab-details-list>
92+
</fab-marquee-selection>
93+
<div>{{ selection.count || 0 }} item(s) selected</div>
94+
<fab-default-button (onClick)="marqueeEnabled = !marqueeEnabled">
95+
Marquee {{ marqueeEnabled === false ? 'Disabled' : 'Enabled' }}
96+
</fab-default-button>
6797
</div>

apps/demo/src/app/app.component.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ChangeDetectorRef, Component, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core';
2-
import { ICalendarStrings, IContextualMenuProps } from 'office-ui-fabric-react';
2+
import { ICalendarStrings, IContextualMenuProps, ISelection, Selection } from 'office-ui-fabric-react';
33

44
@Component({
55
selector: 'app-root',
@@ -11,7 +11,9 @@ export class AppComponent {
1111
@ViewChild('customRange')
1212
customRangeTemplate: TemplateRef<{ item: any; dismissMenu: (ev?: any, dismissAll?: boolean) => void }>;
1313

14+
marqueeEnabled: boolean;
1415
runDisabled: boolean;
16+
selection: ISelection;
1517

1618
strings: ICalendarStrings = {
1719
months: [
@@ -39,6 +41,14 @@ export class AppComponent {
3941
weekNumberFormatString: 'Week number {0}',
4042
};
4143

44+
detailItems = [
45+
{ field1: 'f1content1', field2: 'f2content1' },
46+
{ field1: 'f1content2', field2: 'f2content2' },
47+
{ field1: 'f1content3', field2: 'f2content3' },
48+
{ field1: 'f1content4' },
49+
{ field2: 'f2content5' },
50+
];
51+
4252
onNewClicked() {
4353
console.log('New clicked');
4454
}
@@ -68,7 +78,13 @@ export class AppComponent {
6878
console.log($event);
6979
}
7080

71-
constructor(private readonly cd: ChangeDetectorRef) {}
81+
onColumnHeaderClicked(event: any) {
82+
console.log('Column header clicked', event);
83+
}
84+
85+
constructor(private readonly cd: ChangeDetectorRef) {
86+
this.selection = new Selection();
87+
}
7288

7389
customItemCount = 1;
7490

apps/demo/src/app/app.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ import {
99
FabComboBoxModule,
1010
FabCommandBarModule,
1111
FabDatePickerModule,
12+
FabDetailsListModule,
1213
FabDialogModule,
1314
FabDividerModule,
1415
FabFabricModule,
16+
FabGroupModule,
1517
FabGroupedListModule,
1618
FabHoverCardModule,
1719
FabIconModule,
1820
FabImageModule,
1921
FabLinkModule,
22+
FabMarqueeSelectionModule,
2023
FabMessageBarModule,
2124
FabModalModule,
2225
FabPanelModule,
@@ -67,6 +70,9 @@ import { CounterComponent } from './counter/counter.component';
6770
FabSliderModule,
6871
FabSearchBoxModule,
6972
FabCalendarModule,
73+
FabDetailsListModule,
74+
FabGroupModule,
75+
FabMarqueeSelectionModule
7076
],
7177
declarations: [AppComponent, CounterComponent],
7278
bootstrap: [AppComponent],

libs/fabric/src/lib/components/command-bar/command-bar.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ import {
6262
export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarProps>
6363
implements OnChanges<FabCommandBarComponent>, AfterContentInit, OnDestroy {
6464
@ContentChild(CommandBarItemsDirective)
65-
readonly itemsDirective: CommandBarItemsDirective;
65+
readonly itemsDirective?: CommandBarItemsDirective;
6666
@ContentChild(CommandBarFarItemsDirective)
67-
readonly farItemsDirective: CommandBarFarItemsDirective;
67+
readonly farItemsDirective?: CommandBarFarItemsDirective;
6868
@ContentChild(CommandBarOverflowItemsDirective)
69-
readonly overflowItemsDirective: CommandBarOverflowItemsDirective;
69+
readonly overflowItemsDirective?: CommandBarOverflowItemsDirective;
7070

7171
@ViewChild('reactNode')
7272
protected reactNodeRef: ElementRef;
@@ -188,7 +188,7 @@ export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarPro
188188

189189
// Subscribe for existing items changes
190190
this._subscriptions.push(
191-
directive.onItemChanged.subscribe(({ key, changes }: CommandBarItemChangedPayload) => {
191+
directive.onChildItemChanged.subscribe(({ key, changes }: CommandBarItemChangedPayload) => {
192192
setItems(items => items.map(item => (item.key === key ? mergeItemChanges(item, changes) : item)));
193193
this.markForCheck();
194194
})

libs/fabric/src/lib/components/command-bar/directives/command-bar-items.directives.ts

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,15 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import {
5-
AfterContentInit,
6-
ContentChildren,
7-
Directive,
8-
EventEmitter,
9-
OnDestroy,
10-
Output,
11-
QueryList,
12-
} from '@angular/core';
13-
import { Subscription } from 'rxjs';
14-
import { ICommandBarItemOptions } from '../command-bar.component';
15-
import { CommandBarItemChangedPayload, CommandBarItemDirective } from './command-bar-item.directives';
4+
import { ContentChildren, Directive, QueryList } from '@angular/core';
165

17-
export abstract class CommandBarItemsDirectiveBase implements AfterContentInit, OnDestroy {
18-
private readonly _subscriptions: Subscription[] = [];
6+
import { ChangeableItemsDirective } from '../../core/shared/changeable-items.directive';
7+
import { ICommandBarItemOptions } from '../command-bar.component';
8+
import { CommandBarItemDirective } from './command-bar-item.directives';
199

10+
export abstract class CommandBarItemsDirectiveBase extends ChangeableItemsDirective<ICommandBarItemOptions> {
2011
abstract readonly directiveItems: QueryList<CommandBarItemDirective>;
2112

22-
@Output()
23-
readonly onItemChanged = new EventEmitter<CommandBarItemChangedPayload>();
24-
@Output()
25-
readonly onItemsChanged = new EventEmitter<QueryList<CommandBarItemDirective>>();
26-
2713
get items() {
2814
return (
2915
this.directiveItems &&
@@ -38,29 +24,6 @@ export abstract class CommandBarItemsDirectiveBase implements AfterContentInit,
3824
}))
3925
);
4026
}
41-
42-
ngAfterContentInit() {
43-
this._subscriptions.push(
44-
...this.directiveItems.map(directiveItem =>
45-
// Propagate the change to the parent CommandBarComponent
46-
directiveItem.onItemChanged.subscribe(changes => this.onItemChangedHandler(changes))
47-
)
48-
);
49-
50-
this._subscriptions.push(
51-
this.directiveItems.changes.subscribe((newValue: this['directiveItems']) => {
52-
this.onItemsChanged.emit(newValue);
53-
})
54-
);
55-
}
56-
57-
ngOnDestroy() {
58-
this._subscriptions.forEach(subscription => subscription.unsubscribe());
59-
}
60-
61-
private onItemChangedHandler(payload: CommandBarItemChangedPayload) {
62-
this.onItemChanged.emit(payload);
63-
}
6427
}
6528

6629
@Directive({ selector: 'fab-command-bar > items' })

libs/fabric/src/lib/components/contextual-menu/directives/contextual-menu-item.directive.ts

Lines changed: 28 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,23 @@ import {
1212
QueryList,
1313
} from '@angular/core';
1414
import { IContextualMenuItem } from 'office-ui-fabric-react';
15-
import { Subscription } from 'rxjs';
16-
import { OnChanges, TypedChanges } from '../../../declarations/angular/typed-changes';
17-
import { ItemChangedPayload } from '../../core/declarative/item-changed.payload';
1815

19-
export type ContextualMenuItemChangedPayload = ItemChangedPayload<IContextualMenuItem['key'], IContextualMenuItem>;
16+
import { OnChanges } from '../../../declarations/angular/typed-changes';
17+
import { ItemChangedPayload } from '../../core/declarative/item-changed.payload';
18+
import { ChangeableItemsHelper, IChangeableItemsContainer } from '../../core/shared/changeable-helper';
19+
import { ChangeableItemDirective } from '../../core/shared/changeable-item.directive';
2020

2121
@Directive({ selector: 'contextual-menu-item' })
22-
export class ContextualMenuItemDirective
23-
implements IContextualMenuItem, OnChanges<ContextualMenuItemDirective>, AfterContentInit, OnDestroy {
22+
export class ContextualMenuItemDirective extends ChangeableItemDirective<IContextualMenuItem>
23+
implements
24+
AfterContentInit,
25+
IChangeableItemsContainer<IContextualMenuItem>,
26+
IContextualMenuItem,
27+
OnChanges<ContextualMenuItemDirective>,
28+
OnDestroy {
2429
@ContentChildren(ContextualMenuItemDirective)
2530
readonly menuItemsDirectives: QueryList<ContextualMenuItemDirective>;
2631

27-
@Input()
28-
key: IContextualMenuItem['key'];
2932
@Input()
3033
componentRef?: IContextualMenuItem['componentRef'];
3134
@Input()
@@ -94,53 +97,30 @@ export class ContextualMenuItemDirective
9497
@Output()
9598
readonly click = new EventEmitter<{ ev?: MouseEvent | KeyboardEvent; item?: IContextualMenuItem }>();
9699

97-
// Directive properties
98100
@Output()
99-
readonly onItemChanged = new EventEmitter<ContextualMenuItemChangedPayload>();
100-
101-
private readonly _subscriptions: Subscription[] = [];
102-
103-
ngOnChanges(changes: TypedChanges<this>) {
104-
this.onItemChanged.emit({
105-
key: this.key,
106-
changes,
107-
});
101+
get onChildItemChanged(): EventEmitter<ItemChangedPayload<string, IContextualMenuItem>> {
102+
return this.changeableItemsHelper && this.changeableItemsHelper.onChildItemChanged;
103+
}
104+
@Input()
105+
get onItemsChanged(): EventEmitter<QueryList<ChangeableItemDirective<IContextualMenuItem>>> {
106+
return this.changeableItemsHelper && this.changeableItemsHelper.onItemsChanged;
108107
}
109108

110-
ngAfterContentInit() {
111-
// @ContentChildren selects host component as well.
112-
// Relevant GitHub issue: https://github.com/angular/angular/issues/10098
113-
const nonSelfMenuItemsDirectives = this.menuItemsDirectives.filter(directive => directive !== this);
114-
if (nonSelfMenuItemsDirectives.length === 0) {
115-
return;
116-
}
117-
118-
const items = nonSelfMenuItemsDirectives.map(directive => this._directiveToContextualMenuItem(directive));
119-
if (!this.subMenuProps) {
120-
this.subMenuProps = { items: items };
121-
} else {
122-
this.subMenuProps.items = items;
123-
}
109+
private changeableItemsHelper: ChangeableItemsHelper<IContextualMenuItem>;
124110

125-
this._subscriptions.push(
126-
this.menuItemsDirectives.changes.subscribe((newValue: this['menuItemsDirectives']) => {
127-
this.onItemChanged.emit({
128-
key: this.key,
129-
changes: {
130-
subMenuProps: {
131-
currentValue: {
132-
...this.subMenuProps,
133-
items: newValue.map(directive => this._directiveToContextualMenuItem(directive)),
134-
},
135-
},
136-
},
137-
});
138-
})
139-
);
111+
ngAfterContentInit() {
112+
this.changeableItemsHelper = new ChangeableItemsHelper(this.menuItemsDirectives, this, nonSelfDirective => {
113+
const items = nonSelfDirective.map(directive => this._directiveToContextualMenuItem(directive as any));
114+
if (!this.subMenuProps) {
115+
this.subMenuProps = { items: items };
116+
} else {
117+
this.subMenuProps.items = items;
118+
}
119+
});
140120
}
141121

142122
ngOnDestroy() {
143-
this._subscriptions.forEach(subscription => subscription.unsubscribe());
123+
this.changeableItemsHelper.destroy();
144124
}
145125

146126
private _directiveToContextualMenuItem(directive: ContextualMenuItemDirective): IContextualMenuItem {

0 commit comments

Comments
 (0)