Skip to content

Commit 65dd906

Browse files
author
Ben Grynhaus
committed
CommandBar - declarative syntax. WIP 1
1 parent 12331c6 commit 65dd906

File tree

10 files changed

+321
-17
lines changed

10 files changed

+321
-17
lines changed

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,18 @@ <h2>Getting up and running...</h2>
1616
</div>
1717

1818
<div>
19-
<fab-command-bar #commandBar [items]="commandBarItems" [farItems]="commandBarFarItems">
19+
<fab-command-bar>
20+
<items>
21+
<fab-command-bar-item key="run" text="Run" [iconProps]="{ iconName: 'CaretRight' }" [disabled]="runDisabled"></fab-command-bar-item>
22+
<fab-command-bar-item key="new" text="New" [iconProps]="{ iconName: 'Add' }" (onClick)="onNewClicked()"></fab-command-bar-item>
23+
<fab-command-bar-item key="save" text="Save" [iconProps]="{ iconName: 'Save' }" [subMenuProps]="saveSubMenuProps"></fab-command-bar-item>
24+
<fab-command-bar-item key="copy" text="Copy" [iconProps]="{ iconName: 'Copy' }" (onClick)="onCopyClicked()"></fab-command-bar-item>
25+
</items>
26+
27+
<far-items>
28+
<fab-command-bar-item key="help" text="Help" [iconProps]="{ iconName: 'Help' }"></fab-command-bar-item>
29+
<fab-command-bar-item key="full-screen" [iconOnly]="true" [iconProps]="{ iconName: fullScreenIcon }" (onClick)="toggleFullScreen()"></fab-command-bar-item>
30+
</far-items>
2031
</fab-command-bar>
2132

2233
<fab-default-button (onClick)="toggleRun()" text="Toggle run"></fab-default-button>

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

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,87 @@
1-
import { ChangeDetectorRef, ViewEncapsulation, Component, ComponentFactoryResolver, Injector, Input, ComponentRef, TemplateRef, ViewChild, AfterViewInit } from '@angular/core';
2-
import { DialogType, ITheme, IChoiceGroupProps, SpinnerSize, PersonaSize, PersonaPresence, PivotLinkSize, SelectableOptionMenuItemType, PanelType, ICommandBarItemProps, IBreadcrumbItem, IButtonProps, Button, MessageBarType, ShimmerElementType } from 'office-ui-fabric-react';
1+
import {
2+
ChangeDetectorRef,
3+
ViewEncapsulation,
4+
Component,
5+
ComponentFactoryResolver,
6+
Injector,
7+
Input,
8+
ComponentRef,
9+
TemplateRef,
10+
ViewChild,
11+
AfterViewInit,
12+
} from '@angular/core';
13+
import {
14+
DialogType,
15+
ITheme,
16+
IChoiceGroupProps,
17+
SpinnerSize,
18+
PersonaSize,
19+
PersonaPresence,
20+
PivotLinkSize,
21+
SelectableOptionMenuItemType,
22+
PanelType,
23+
ICommandBarItemProps,
24+
IBreadcrumbItem,
25+
IButtonProps,
26+
Button,
27+
MessageBarType,
28+
ShimmerElementType,
29+
} from 'office-ui-fabric-react';
330
import { IExpandingCardOptions } from '@angular-react/fabric/src/components/hover-card';
431
import { ICommandBarItemOptions, FabCommandBarComponent } from '@angular-react/fabric/src/components/command-bar';
532

6-
733
@Component({
834
selector: 'app-root',
935
templateUrl: './app.component.html',
1036
styleUrls: ['./app.component.css'],
1137
encapsulation: ViewEncapsulation.None,
1238
})
1339
export class AppComponent {
40+
@ViewChild('customRange')
41+
customRangeTemplate: TemplateRef<{ item: any; dismissMenu: (ev?: any, dismissAll?: boolean) => void }>;
42+
43+
runDisabled: boolean;
44+
45+
onNewClicked() {
46+
console.log('New clicked');
47+
}
1448

15-
@ViewChild(FabCommandBarComponent) commandBar: FabCommandBarComponent;
16-
@ViewChild('customRange') customRangeTemplate: TemplateRef<{ item: any, dismissMenu: (ev?: any, dismissAll?: boolean) => void }>;
49+
onCopyClicked() {
50+
console.log('Copy clicked');
51+
}
1752

18-
commandBarItems: ReadonlyArray<ICommandBarItemOptions> = [
53+
// FIXME: Allow declarative syntax too
54+
saveSubMenuProps = {
55+
items: [
56+
{
57+
key: 'save',
58+
text: 'Save',
59+
onClick: () => console.log('Save clicked'),
60+
},
61+
{
62+
key: 'save-as',
63+
text: 'Save as',
64+
subMenuProps: {
65+
onItemClick: (ev, item) => {
66+
console.log(`${item.text} clicked`);
67+
return true;
68+
},
69+
items: [
70+
{
71+
key: 'save-as-1',
72+
text: 'Save as 1',
73+
},
74+
{
75+
key: 'save-as-2',
76+
text: 'Save as 2',
77+
},
78+
],
79+
},
80+
},
81+
],
82+
};
83+
84+
/* commandBarItems: ReadonlyArray<ICommandBarItemOptions> = [
1985
{
2086
key: 'run',
2187
text: 'Run',
@@ -155,16 +221,17 @@ export class AppComponent {
155221
},
156222
onClick: () => console.log('Expand clicked'),
157223
}
158-
];
224+
]; */
159225

160226
isPanelOpen = false;
161227

162228
toggleRun() {
163-
this.commandBarItems = this.commandBarItems.map(item =>
164-
item.key === 'run'
165-
? { ...item, disabled: !item.disabled }
166-
: item
167-
);
229+
this.runDisabled = !this.runDisabled;
230+
}
231+
232+
toggleFullScreen() {
233+
this.fullScreenIcon = this.fullScreenIcon === 'BackToWindow' ? 'MiniExpand' : 'BackToWindow';
168234
}
169235

236+
fullScreenIcon = 'MiniExpand';
170237
}

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

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,23 @@ import {
99
ViewChild,
1010
ChangeDetectorRef,
1111
SimpleChanges,
12-
OnChanges,
12+
ContentChild,
13+
AfterContentInit,
14+
OnDestroy,
1315
} from '@angular/core';
1416
import { ICommandBarItemProps, ICommandBarProps } from 'office-ui-fabric-react/lib/CommandBar';
1517
import { IContextualMenuItemProps } from 'office-ui-fabric-react/lib/ContextualMenu';
1618
import omit from '../../utils/omit';
19+
import { CommandBarItemsDirective } from './directives/command-bar-items.directive';
20+
import { CommandBarFarItemsDirective } from './directives/command-bar-far-items.directive';
21+
import { CommandBarOverflowItemsDirective } from './directives/command-bar-overflow-items.directive';
22+
import { CommandBarItemsDirectiveBase } from './directives/command-bar-items-base.directive';
23+
import { TypedChanges, OnChanges } from '../../types/angular/typed-changes';
24+
import {
25+
CommandBarItemDirective,
26+
CommandBarItemPropertiesChangedPayload,
27+
} from '@angular-react/fabric/src/components/command-bar/directives/command-bar-item.directive';
28+
import { Subscription } from 'rxjs';
1729

1830
@Component({
1931
selector: 'fab-command-bar',
@@ -43,7 +55,11 @@ import omit from '../../utils/omit';
4355
styles: ['react-renderer'],
4456
changeDetection: ChangeDetectionStrategy.OnPush,
4557
})
46-
export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarProps> implements OnChanges {
58+
export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarProps>
59+
implements OnChanges<FabCommandBarComponent>, AfterContentInit, OnDestroy {
60+
@ContentChild(CommandBarItemsDirective) readonly itemsDirective: CommandBarItemsDirective;
61+
@ContentChild(CommandBarFarItemsDirective) readonly farItemsDirective: CommandBarItemsDirective;
62+
4763
@ViewChild('reactNode') protected reactNodeRef: ElementRef;
4864

4965
@Input() componentRef?: ICommandBarProps['componentRef'];
@@ -73,11 +89,13 @@ export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarPro
7389
/** @internal */
7490
transformedOverflowItems_: ReadonlyArray<ICommandBarItemProps>;
7591

92+
private readonly _subscriptions: Subscription[] = [];
93+
7694
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef) {
7795
super(elementRef, changeDetectorRef, true);
7896
}
7997

80-
ngOnChanges(changes: SimpleChanges) {
98+
ngOnChanges(changes: TypedChanges<this>) {
8199
if (
82100
changes['items'] &&
83101
changes['items'].previousValue !== changes['items'].currentValue &&
@@ -100,6 +118,60 @@ export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarPro
100118
super.ngOnChanges(changes);
101119
}
102120

121+
ngAfterContentInit() {
122+
if (this.itemsDirective) this._initDirective(this.itemsDirective, 'items');
123+
if (this.farItemsDirective) this._initDirective(this.farItemsDirective, 'farItems');
124+
}
125+
126+
ngOnDestroy() {
127+
this._subscriptions.forEach(subscription => subscription.unsubscribe());
128+
}
129+
130+
private _initDirective<TCommandBarItemsDirective extends CommandBarItemsDirectiveBase>(
131+
directive: TCommandBarItemsDirective,
132+
itemsPropertyKey: 'items' | 'farItems' | 'overflowItems'
133+
) {
134+
const transformItemsFunc =
135+
(directive instanceof CommandBarItemsDirective && (newItems => this._createTransformedItems(newItems))) ||
136+
(directive instanceof CommandBarFarItemsDirective && (newItems => this._createTransformedFarItems(newItems))) ||
137+
(directive instanceof CommandBarOverflowItemsDirective &&
138+
(newItems => this._createTransformedOverflowItems(newItems)));
139+
null;
140+
141+
if (!transformItemsFunc) {
142+
throw new Error('Invalid directive given');
143+
}
144+
145+
const setItems = (
146+
mapper: (items: ReadonlyArray<ICommandBarItemOptions>) => ReadonlyArray<ICommandBarItemOptions>
147+
) => {
148+
this[itemsPropertyKey] = mapper(this[itemsPropertyKey]);
149+
transformItemsFunc(this[itemsPropertyKey]);
150+
};
151+
152+
const mergeItemChanges = (item: ICommandBarItemOptions, changes: TypedChanges<ICommandBarItemOptions>) => {
153+
const itemChangesOverrides = Object.entries(changes).reduce<Partial<ICommandBarItemOptions>>(
154+
(acc, [propertyKey, change]) => ({
155+
[propertyKey]: change.currentValue,
156+
}),
157+
{}
158+
);
159+
160+
return Object.assign({}, item, itemChangesOverrides);
161+
};
162+
163+
// Initial items
164+
setItems(() => directive.items);
165+
166+
// Subscribe for existing items changes
167+
this._subscriptions.push(
168+
directive.onItemChanged.subscribe(({ key, changes }: CommandBarItemPropertiesChangedPayload) => {
169+
setItems(items => items.map(item => (item.key === key ? mergeItemChanges(item, changes) : item)));
170+
this.detectChanges();
171+
})
172+
);
173+
}
174+
103175
private _createTransformedItems(newItems: ReadonlyArray<ICommandBarItemOptions>) {
104176
this.transformedItems_ = newItems.map(item => this._transformCommandBarItemOptionsToProps(item));
105177
}

libs/fabric/src/components/command-bar/command-bar.module.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,20 @@ import { registerElement } from '@angular-react/core';
22
import { CommonModule } from '@angular/common';
33
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
44
import { CommandBar } from 'office-ui-fabric-react/lib/CommandBar';
5+
56
import { FabCommandBarComponent } from './command-bar.component';
7+
import { CommandBarItemDirective } from './directives/command-bar-item.directive';
8+
import { CommandBarItemsDirective } from './directives/command-bar-items.directive';
9+
import { CommandBarFarItemsDirective } from './directives/command-bar-far-items.directive';
10+
import { CommandBarOverflowItemsDirective } from './directives/command-bar-overflow-items.directive';
611

7-
const components = [FabCommandBarComponent];
12+
const components = [
13+
FabCommandBarComponent,
14+
CommandBarItemsDirective,
15+
CommandBarFarItemsDirective,
16+
CommandBarOverflowItemsDirective,
17+
CommandBarItemDirective,
18+
];
819

920
@NgModule({
1021
imports: [CommonModule],
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Directive, ContentChildren, QueryList } from '@angular/core';
2+
import { CommandBarItemDirective } from './command-bar-item.directive';
3+
import { CommandBarItemsDirectiveBase } from './command-bar-items-base.directive';
4+
import { ICommandBarItemOptions } from '../command-bar.component';
5+
6+
@Directive({
7+
selector: 'fab-command-bar > far-items',
8+
})
9+
export class CommandBarFarItemsDirective extends CommandBarItemsDirectiveBase {
10+
@ContentChildren(CommandBarItemDirective) readonly directiveItems: QueryList<CommandBarItemDirective>;
11+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Directive, Input, TemplateRef, ContentChild, Output, EventEmitter } from '@angular/core';
2+
3+
import { OnChanges, TypedChanges } from '../../../types/angular/typed-changes';
4+
import { ICommandBarItemOptions } from '../command-bar.component';
5+
import { IContextualMenuItem } from 'office-ui-fabric-react';
6+
7+
export interface CommandBarItemPropertiesChangedPayload {
8+
readonly key: ICommandBarItemOptions['key'];
9+
readonly changes: TypedChanges<ICommandBarItemOptions>;
10+
}
11+
12+
@Directive({ selector: 'fab-command-bar-item' })
13+
export class CommandBarItemDirective implements ICommandBarItemOptions, OnChanges<CommandBarItemDirective> {
14+
@ContentChild(TemplateRef) template: TemplateRef<any>;
15+
16+
@Input() key: ICommandBarItemOptions['key'];
17+
@Input() text?: ICommandBarItemOptions['text'];
18+
@Input() iconProps?: ICommandBarItemOptions['iconProps'];
19+
@Input() disabled?: ICommandBarItemOptions['disabled'];
20+
@Input() iconOnly?: ICommandBarItemOptions['iconOnly'];
21+
@Input() subMenuProps?: ICommandBarItemOptions['subMenuProps'];
22+
23+
@Output() readonly onClick = new EventEmitter<{ ev?: MouseEvent | KeyboardEvent; item?: IContextualMenuItem }>();
24+
25+
@Output() readonly onItemChanged = new EventEmitter<CommandBarItemPropertiesChangedPayload>();
26+
27+
ngOnChanges(changes: TypedChanges<this>) {
28+
this.onItemChanged.emit({
29+
key: this.key,
30+
changes: Object.assign<{}, TypedChanges<this>, Pick<TypedChanges<this>, 'onClick'>>(
31+
{},
32+
changes,
33+
changes.onClick && {
34+
onClick: {
35+
...changes.onClick,
36+
isFirstChange: changes.onClick.isFirstChange,
37+
},
38+
}
39+
),
40+
});
41+
}
42+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
Directive,
3+
ContentChildren,
4+
QueryList,
5+
OnDestroy,
6+
AfterContentInit,
7+
Output,
8+
EventEmitter,
9+
} from '@angular/core';
10+
import { CommandBarItemDirective, CommandBarItemPropertiesChangedPayload } from './command-bar-item.directive';
11+
import { ICommandBarItemOptions } from '../command-bar.component';
12+
import { Subscription } from 'rxjs';
13+
14+
export abstract class CommandBarItemsDirectiveBase implements AfterContentInit, OnDestroy {
15+
private readonly _subscriptions: Subscription[] = [];
16+
17+
abstract readonly directiveItems: QueryList<CommandBarItemDirective>;
18+
19+
@Output() readonly onItemChanged = new EventEmitter<CommandBarItemPropertiesChangedPayload>();
20+
21+
get items() {
22+
return (
23+
this.directiveItems &&
24+
this.directiveItems.map<ICommandBarItemOptions>(directiveItem => ({
25+
key: directiveItem.key,
26+
text: directiveItem.text,
27+
iconProps: directiveItem.iconProps,
28+
iconOnly: directiveItem.iconOnly,
29+
disabled: directiveItem.disabled,
30+
onClick: (ev, item) => {
31+
directiveItem.onClick.emit({
32+
ev: ev && ev.nativeEvent,
33+
item: item,
34+
});
35+
},
36+
}))
37+
);
38+
}
39+
40+
ngAfterContentInit() {
41+
this._subscriptions.push(
42+
...this.directiveItems.map(directiveItem =>
43+
directiveItem.onItemChanged.subscribe(changes => this.onItemChangedHandler(changes))
44+
)
45+
);
46+
}
47+
48+
ngOnDestroy() {
49+
this._subscriptions.forEach(subscription => subscription.unsubscribe());
50+
}
51+
52+
private onItemChangedHandler(payload: CommandBarItemPropertiesChangedPayload) {
53+
this.onItemChanged.emit(payload);
54+
}
55+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Directive, ContentChildren, QueryList } from '@angular/core';
2+
import { CommandBarItemDirective } from './command-bar-item.directive';
3+
import { ICommandBarItemOptions } from '../command-bar.component';
4+
import { CommandBarItemsDirectiveBase } from './command-bar-items-base.directive';
5+
6+
@Directive({
7+
selector: 'fab-command-bar > items',
8+
})
9+
export class CommandBarItemsDirective extends CommandBarItemsDirectiveBase {
10+
@ContentChildren(CommandBarItemDirective) readonly directiveItems: QueryList<CommandBarItemDirective>;
11+
}

0 commit comments

Comments
 (0)