Update combobox and calendar with directives (#95)

Add directives for the ComboBox and Calendar to enable the Angular-style template localization pattern.

Change wrapper to fix some timing bugs
This commit is contained in:
R.C. Shaw 2019-03-06 11:23:51 -08:00 коммит произвёл Ben Grynhaus
Родитель 87863d96b2
Коммит 8ccbaa5e3e
15 изменённых файлов: 255 добавлений и 17 удалений

Просмотреть файл

@ -142,6 +142,30 @@
<fab-calendar [strings]="strings" (onSelectDate)="onSelectDate($event)"></fab-calendar>
<fab-calendar (onSelectDate)="onSelectDate($event)">
<fab-calendar-strings
[months]="[
'January',
'Fabruary',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]"
[shortMonths]="['Jan', 'Fab', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']"
[days]="['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']"
[shortDays]="['S', 'M', 'T', 'W', 'T', 'F', 'S']"
goToToday="Go to tomorrow"
weekNumberFormatString="Week number {0}"
></fab-calendar-strings>
</fab-calendar>
<fab-marquee-selection [isEnabled]="marqueeEnabled" [selection]="selection">
<fab-details-list
[selection]="selection"

Просмотреть файл

@ -1,7 +1,7 @@
<h1>Microsoft Fabric [React] Components</h1>
<h2>Button</h2>
<fab-default-button text="Toggle Disabled" (onClick)="toggle()"></fab-default-button>
<fab-default-button text="Toggle Disabled" (onClick)="toggle()" contentStyle="margin-right: 10px;"></fab-default-button>
<fab-default-button
[disabled]="disabled"
(onClick)="click()"
@ -22,7 +22,46 @@
>
{{ sampleContent2 }} {{ sampleContent3 }}
<fab-dialog-footer>
<fab-default-button (onClick)="clickSave()" text="Save" contentStyle="margin-right: 10px;"></fab-default-button>
<fab-default-button (onClick)="clickSave()" text="Save"></fab-default-button>
<fab-default-button (onClick)="toggleDialog()" text="Cancel"></fab-default-button>
</fab-dialog-footer>
</fab-dialog>
<hr />
<h2>Combo box</h2>
<fab-combo-box contentStyle="width: 300px;" (onChange)="comboChange($event)">
<options>
<fab-combo-box-option optionKey="A" text="See option A"></fab-combo-box-option>
<fab-combo-box-option optionKey="B" text="See option B"></fab-combo-box-option>
</options>
</fab-combo-box>
{{ selectedComboBoxKey }}: {{ selectedComboBoxValue }}
<hr />
<h2>Directive Calendar</h2>
<fab-calendar (onSelectDate)="onSelectDate($event)">
<fab-calendar-strings
[months]="[
'January',
'Fabruary',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]"
[shortMonths]="['Jan', 'Fab', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']"
[days]="['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']"
[shortDays]="['S', 'M', 'T', 'W', 'T', 'F', 'S']"
goToToday="Go to tomorrow"
weekNumberFormatString="Week number {0}"
></fab-calendar-strings>
</fab-calendar>
{{ selectedDate }}

Просмотреть файл

@ -1,4 +1,5 @@
import { Component } from '@angular/core';
import { IComboBoxOption, ICalendarStrings } from 'office-ui-fabric-react';
@Component({
selector: 'app-fabric',
@ -12,6 +13,23 @@ export class FabricComponent {
secondsCounter = 0;
sampleContent2 = '0 Seconds Passed';
sampleContent3 = '';
selectedComboBoxKey: string = "None";
selectedComboBoxValue: string = "None";
selectedDate: Date;
comboBoxOptions: IComboBoxOption[] = [
{ key: 'A', text: 'See option A' },
{ key: 'B', text: 'See option B' },
];
onSelectDate(event) {
this.selectedDate = event.date;
}
comboChange(event) {
this.selectedComboBoxKey = event.option.key;
this.selectedComboBoxValue = event.option.text;
}
get sampleContent() {
return `Button clicked ${this.sampleContentCounter} times.`;

Просмотреть файл

@ -1,7 +1,17 @@
import { NgModule } from '@angular/core';
import { FabButtonModule, FabDialogModule } from '@angular-react/fabric';
import {
FabButtonModule,
FabDialogModule,
FabComboBoxModule,
FabCalendarModule
} from '@angular-react/fabric';
const componentModules = [FabButtonModule, FabDialogModule];
const componentModules = [
FabButtonModule,
FabDialogModule,
FabComboBoxModule,
FabCalendarModule
];
@NgModule({
imports: componentModules,
exports: componentModules,

Просмотреть файл

@ -97,7 +97,7 @@ export abstract class ReactWrapperComponent<TProps extends {}> implements AfterV
this._contentClass = value;
if (isReactNode(this.reactNodeRef.nativeElement)) {
this.reactNodeRef.nativeElement.setProperty('className', classnames(value));
this.changeDetectorRef.detectChanges();
this.markForCheck();
}
}
@ -118,7 +118,7 @@ export abstract class ReactWrapperComponent<TProps extends {}> implements AfterV
if (isReactNode(this.reactNodeRef.nativeElement)) {
const stringValue = typeof value === 'string' ? value : stylenames(value);
this.reactNodeRef.nativeElement.setProperty('style', toStyle(stringValue));
this.changeDetectorRef.detectChanges();
this.markForCheck();
}
}
@ -142,9 +142,11 @@ export abstract class ReactWrapperComponent<TProps extends {}> implements AfterV
this._shouldSetHostDisplay = setHostDisplay;
}
ngAfterViewInit() {
ngAfterContentInit() {
this._passAttributesAsProps();
}
ngAfterViewInit() {
if (this._shouldSetHostDisplay) {
this._setHostDisplay();
}

Просмотреть файл

@ -129,6 +129,7 @@ export abstract class FabBaseButtonComponent extends ReactWrapperComponent<IButt
setItems(this.menuItemsDirectives.toArray());
}
super.ngAfterContentInit();
}
ngOnDestroy() {

Просмотреть файл

@ -12,8 +12,11 @@ import {
Output,
Renderer2,
ViewChild,
ContentChild,
AfterContentInit,
} from '@angular/core';
import { ICalendarProps } from 'office-ui-fabric-react/lib/Calendar';
import { CalendarStringsDirective } from './directives/calendar-strings-directive.component';
@Component({
selector: 'fab-calendar',
@ -57,7 +60,7 @@ import { ICalendarProps } from 'office-ui-fabric-react/lib/Calendar';
styles: ['react-renderer'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FabCalendarComponent extends ReactWrapperComponent<ICalendarProps> {
export class FabCalendarComponent extends ReactWrapperComponent<ICalendarProps> implements AfterContentInit {
@ViewChild('reactNode') protected reactNodeRef: ElementRef;
@Input() componentRef?: ICalendarProps['componentRef'];
@ -91,14 +94,22 @@ export class FabCalendarComponent extends ReactWrapperComponent<ICalendarProps>
@Output() readonly onSelectDate = new EventEmitter<{ date: Date; selectedDateRangeArray?: Date[] }>();
@Output() readonly onDismiss = new EventEmitter<void>();
@ContentChild(CalendarStringsDirective) readonly calendarStringsDirective?: CalendarStringsDirective;
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, renderer: Renderer2) {
super(elementRef, changeDetectorRef, renderer);
// coming from React context - we need to bind to this so we can access the Angular Component properties
this.onSelectDateHandler = this.onSelectDateHandler.bind(this);
this.onDismissHandler = this.onDismissHandler.bind(this);
}
ngAfterContentInit() {
if (this.calendarStringsDirective) {
this._initDirective(this.calendarStringsDirective);
super.ngAfterContentInit();
}
}
onSelectDateHandler(date: Date, selectedDateRangeArray?: Date[]) {
this.onSelectDate.emit({
date,
@ -109,4 +120,8 @@ export class FabCalendarComponent extends ReactWrapperComponent<ICalendarProps>
onDismissHandler() {
this.onDismiss.emit();
}
private _initDirective(calendarStringsDirective: CalendarStringsDirective) {
this.strings = calendarStringsDirective.strings;
}
}

Просмотреть файл

@ -8,16 +8,17 @@ import * as CalendarCss from 'office-ui-fabric-react/lib-amd/components/Calendar
import { Calendar } from 'office-ui-fabric-react';
import { noop } from '../../utils/noop';
import { FabCalendarComponent } from './calendar.component';
import { CalendarStringsDirective } from './directives/calendar-strings-directive.component';
// Dummy action to force CalendarCss to load and not be tree-shaken away.
noop(CalendarCss);
const components = [FabCalendarComponent];
const declarations = [FabCalendarComponent, CalendarStringsDirective];
@NgModule({
imports: [CommonModule],
declarations: components,
exports: components,
declarations: declarations,
exports: declarations,
schemas: [NO_ERRORS_SCHEMA],
})
export class FabCalendarModule {

Просмотреть файл

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { Directive, Input } from '@angular/core';
import { ICalendarStrings } from 'office-ui-fabric-react';
/**
* Wrapper directive for calendar strings
*/
@Directive({ selector: 'fab-calendar > fab-calendar-strings' })
export class CalendarStringsDirective {
@Input() months: ICalendarStrings['months'];
@Input() shortMonths: ICalendarStrings['shortMonths'];
@Input() days: ICalendarStrings['days'];
@Input() shortDays: ICalendarStrings['shortDays'];
@Input() goToToday: ICalendarStrings['goToToday'];
@Input() weekNumberFormatString: ICalendarStrings['weekNumberFormatString'];
get strings(): ICalendarStrings {
return {
months: this.months,
shortMonths: this.shortMonths,
days: this.days,
shortDays: this.shortDays,
goToToday: this.goToToday,
weekNumberFormatString: this.weekNumberFormatString
}
}
}

Просмотреть файл

@ -2,10 +2,26 @@
// Licensed under the MIT License.
import { InputRendererOptions, JsxRenderFunc, ReactWrapperComponent } from '@angular-react/core';
import { ChangeDetectorRef, ElementRef, EventEmitter, Input, NgZone, OnInit, Output, Renderer2 } from '@angular/core';
import {
ChangeDetectorRef,
ElementRef,
EventEmitter,
Input,
NgZone,
OnInit,
Output,
Renderer2,
ContentChild,
AfterContentInit,
} from '@angular/core';
import { IComboBox, IComboBoxOption, IComboBoxProps } from 'office-ui-fabric-react/lib/ComboBox';
import { ComboBoxOptionDirective } from './directives/combo-box-option.directive';
import { ComboBoxOptionsDirective } from './directives/combo-box-options.directive';
export abstract class FabBaseComboBoxComponent extends ReactWrapperComponent<IComboBoxProps>
implements OnInit, AfterContentInit {
@ContentChild(ComboBoxOptionDirective) readonly optionsDirective?: ComboBoxOptionDirective;
export abstract class FabBaseComboBoxComponent extends ReactWrapperComponent<IComboBoxProps> implements OnInit {
@Input() componentRef?: IComboBoxProps['componentRef'];
@Input() options: IComboBoxProps['options'];
@Input() allowFreeform?: IComboBoxProps['allowFreeform'];
@ -49,6 +65,8 @@ export abstract class FabBaseComboBoxComponent extends ReactWrapperComponent<ICo
@Output() readonly onMenuDismissed = new EventEmitter<void>();
@Output() readonly onScrollToItem = new EventEmitter<{ itemIndex: number }>();
@ContentChild(ComboBoxOptionsDirective) readonly comboBoxOptionsDirective?: ComboBoxOptionsDirective;
onRenderLowerContent: (props?: IComboBoxProps, defaultRender?: JsxRenderFunc<IComboBoxProps>) => JSX.Element;
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, renderer: Renderer2, ngZone: NgZone) {
@ -65,6 +83,13 @@ export abstract class FabBaseComboBoxComponent extends ReactWrapperComponent<ICo
this.onRenderLowerContent = this.createRenderPropHandler(this.renderLowerContent);
}
ngAfterContentInit() {
if (this.comboBoxOptionsDirective) {
this._initDirective(this.comboBoxOptionsDirective);
}
super.ngAfterContentInit();
}
onItemClickHandler(event: React.FormEvent<IComboBox>, option?: IComboBoxOption, index?: number) {
this.onItemClick.emit({
event: event.nativeEvent,
@ -95,4 +120,9 @@ export abstract class FabBaseComboBoxComponent extends ReactWrapperComponent<ICo
itemIndex,
});
}
private _initDirective(directive: ComboBoxOptionsDirective) {
this.options = directive.items;
this.markForCheck();
}
}

Просмотреть файл

@ -7,13 +7,20 @@ import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComboBox, VirtualizedComboBox } from 'office-ui-fabric-react';
import { FabComboBoxComponent } from './combo-box.component';
import { FabVirtualizedComboBoxComponent } from './virtualized-combo-box.component';
import { ComboBoxOptionDirective } from './directives/combo-box-option.directive';
import { ComboBoxOptionsDirective } from './directives/combo-box-options.directive';
const components = [FabComboBoxComponent, FabVirtualizedComboBoxComponent];
const declarations = [
FabComboBoxComponent,
FabVirtualizedComboBoxComponent,
ComboBoxOptionDirective,
ComboBoxOptionsDirective
];
@NgModule({
imports: [CommonModule],
declarations: components,
exports: components,
declarations: declarations,
exports: declarations,
schemas: [NO_ERRORS_SCHEMA],
})
export class FabComboBoxModule {

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { Directive, Input } from '@angular/core';
import { IComboBoxOption } from 'office-ui-fabric-react';
/**
* Wrapper directive for creating a ComboBoxOption
* @paramName optionKey Binds to React 'key' property.
* Name change necessary because key is a reserved attribute in the wrapper component.
*/
@Directive({ selector: 'fab-combo-box-option' })
export class ComboBoxOptionDirective {
@Input() optionKey: IComboBoxOption['key'];
@Input() text: IComboBoxOption['text'];
@Input() title?: IComboBoxOption['title'];
@Input() itemType: IComboBoxOption['itemType'];
@Input() index?: IComboBoxOption['index'];
@Input() ariaLabel?: IComboBoxOption['ariaLabel'];
@Input() selected?: IComboBoxOption['selected'];
@Input() disabled?: IComboBoxOption['disabled'];
@Input() data?: IComboBoxOption['data'];
@Input() styles?: IComboBoxOption['styles'];
@Input() useAriaLabelAsText?: IComboBoxOption['useAriaLabelAsText'];
}

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { ContentChildren, Directive, QueryList } from '@angular/core';
import { IComboBoxOption } from 'office-ui-fabric-react';
import { ComboBoxOptionDirective } from "./combo-box-option.directive";
/**
* Wrapper directive for creating multiple ComboBoxOptions
*/
@Directive({ selector: 'fab-combo-box > options' })
export class ComboBoxOptionsDirective {
@ContentChildren(ComboBoxOptionDirective) readonly directiveItems: QueryList<ComboBoxOptionDirective>;
get items() {
return this.directiveItems.map<IComboBoxOption>(directiveItem => {
return {
key: directiveItem.optionKey,
text: directiveItem.text,
title: directiveItem.title,
itemType: directiveItem.itemType,
index: directiveItem.index,
ariaLabel: directiveItem.ariaLabel,
selected: directiveItem.selected,
disabled: directiveItem.disabled,
data: directiveItem.data,
styles: directiveItem.styles,
useAriaLabelAsText: directiveItem.useAriaLabelAsText
}
});
}
}

Просмотреть файл

@ -127,6 +127,7 @@ export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarPro
if (this.itemsDirective) this._initDirective(this.itemsDirective, 'items');
if (this.farItemsDirective) this._initDirective(this.farItemsDirective, 'farItems');
if (this.overflowItemsDirective) this._initDirective(this.overflowItemsDirective, 'overflowItems');
super.ngAfterContentInit();
}
ngOnDestroy() {

Просмотреть файл

@ -251,6 +251,7 @@ export class FabDetailsListComponent extends ReactWrapperComponent<IDetailsListP
if (this.groupsDirective) {
this._initDirective(this.groupsDirective, 'groups');
}
super.ngAfterContentInit();
}
ngOnDestroy() {