Skip to content

Commit

Permalink
feat(angular-material): Date Picker Format and View
Browse files Browse the repository at this point in the history
Use a custom dateFormatter to enable the date picker format option.
Enable the JSON Forms view options.
  • Loading branch information
LukasBoll committed Mar 11, 2024
1 parent 42bd296 commit a7bfafa
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 76 deletions.
9 changes: 2 additions & 7 deletions packages/angular-material/example/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
UISchemaTester,
} from '@jsonforms/core';
import { angularMaterialRenderers } from '../../lib';
import { DateAdapter } from '@angular/material/core';

const uiSchema = {
type: 'HorizontalLayout',
Expand Down Expand Up @@ -94,18 +93,15 @@ export class AppComponent {
readonly examples = getExamples();
selectedExample: ExampleDescription | undefined;
i18n: JsonFormsI18nState;
private dateAdapter;
readonly = false;
data: any;
uischemas: { tester: UISchemaTester; uischema: UISchemaElement }[] = [
{ tester: itemTester, uischema: uiSchema },
];

constructor(dateAdapter: DateAdapter<Date>) {
constructor() {
this.selectedExample = this.examples[19];
this.dateAdapter = dateAdapter;
this.i18n = this.selectedExample.i18n ?? defaultI18n;
dateAdapter.setLocale(this.i18n.locale);
}

onChange(ev: any) {
Expand All @@ -116,8 +112,7 @@ export class AppComponent {
}

changeLocale(locale: string) {
this.i18n.locale = locale;
this.dateAdapter.setLocale(locale);
this.i18n = { ...this.i18n, locale };
}

toggleReadonly() {
Expand Down
2 changes: 2 additions & 0 deletions packages/angular-material/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@angular/router": "^16.0.0 || ^17.0.0",
"@jsonforms/angular": "3.2.1",
"@jsonforms/core": "3.2.1",
"dayjs": "^1.11.7",
"rxjs": "^6.6.0 || ^7.4.0"
},
"dependencies": {
Expand Down Expand Up @@ -108,6 +109,7 @@
"@typescript-eslint/parser": "^5.54.1",
"babel-loader": "^8.0.6",
"copy-webpack-plugin": "^11.0.0",
"dayjs": "^1.11.10",
"eslint": "^8.56.0",
"eslint-config-prettier": "^8.7.0",
"eslint-plugin-import": "^2.27.5",
Expand Down
114 changes: 104 additions & 10 deletions packages/angular-material/src/library/controls/date.renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,33 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { isDateControl, RankedTester, rankWith } from '@jsonforms/core';
import {
Component,
ChangeDetectionStrategy,
Inject,
ViewEncapsulation,
} from '@angular/core';
import {
defaultDateFormat,
isDateControl,
JsonFormsState,
RankedTester,
rankWith,
StatePropsOfControl,
} from '@jsonforms/core';
import { JsonFormsAngularService, JsonFormsControl } from '@jsonforms/angular';
import { DateAdapter } from '@angular/material/core';
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
import { MyFormat } from '../util/date-format';
import { DayJsDateAdapter } from '../util/dayjs-date-adapter';
import { MatDatepicker } from '@angular/material/datepicker';

@Component({
selector: 'DateControlRenderer',
template: `
<mat-form-field [ngStyle]="{ display: hidden ? 'none' : '' }">
<mat-form-field
class="date-control-renderer"
[ngStyle]="{ display: hidden ? 'none' : '' }"
>
<mat-label>{{ label }}</mat-label>
<input
matInput
Expand All @@ -45,37 +63,113 @@ import { DateAdapter } from '@angular/material/core';
matSuffix
[for]="datepicker"
></mat-datepicker-toggle>
<mat-datepicker #datepicker></mat-datepicker>
<mat-hint *ngIf="shouldShowUnfocusedDescription()">{{
<mat-datepicker
#datepicker
(monthSelected)="monthSelected($event, datepicker)"
(yearSelected)="yearSelected($event, datepicker)"
[startView]="startView"
[panelClass]="panelClass"
></mat-datepicker>
<mat-hint *ngIf="shouldShowUnfocusedDescription() || focused">{{
description
}}</mat-hint>
<mat-error>{{ error }}</mat-error>
</mat-form-field>
`,
styles: [
`
:host {
DateControlRenderer {
display: flex;
flex-direction: row;
}
mat-form-field {
.date-control-renderer {
flex: 1 1 auto;
}
.no-panel-navigation .mat-calendar-period-button {
pointer-events: none;
}
.no-panel-navigation .mat-calendar-arrow {
display: none;
}
`,
],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: DateAdapter,
useClass: DayJsDateAdapter,
},
{
provide: MAT_DATE_FORMATS,
useClass: MyFormat,
},
],
})
export class DateControlRenderer extends JsonFormsControl {
focused = false;
views: string[] = [];
startView = '';
panelClass = '';

constructor(
jsonformsService: JsonFormsAngularService,
private dateAdapter: DateAdapter<Date>
@Inject(MAT_DATE_FORMATS) private dateFormat: MyFormat,
@Inject(DateAdapter) private dateAdapter: DayJsDateAdapter
) {
super(jsonformsService);
}

getEventValue = (event: any) => {
return this.dateAdapter.format(event.value, 'YYYY-MM-DD');
const value = event.value ? event.value : event;
return this.dateAdapter.toSaveFormat(value);
};

protected mapToProps(state: JsonFormsState): StatePropsOfControl {
const props = super.mapToProps(state);
const saveFormat = this.uischema?.options?.dateSaveFormat
? this.uischema.options.dateSaveFormat
: defaultDateFormat;
this.views = this.uischema?.options?.views
? this.uischema.options.views
: ['year', 'month', 'day'];
this.setViewProperties();

const dateFormat = this.uischema?.options?.dateFormat;

if (dateFormat) {
this.dateFormat.setDisplayFormat(dateFormat);
}

this.dateAdapter.setSaveFormat(saveFormat);
if (this.jsonFormsService.getLocale()) {
this.dateAdapter.setLocale(this.jsonFormsService.getLocale());
}
const date = this.dateAdapter.parseSaveFormat(props.data);
return { ...props, data: date };
}

yearSelected($event: any, datepicker: MatDatepicker<DayJsDateAdapter>) {
if (!this.views.includes('day') && !this.views.includes('month')) {
this.onChange($event);
datepicker.close();
}
}
monthSelected($event: any, datepicker: MatDatepicker<DayJsDateAdapter>) {
if (!this.views.includes('day')) {
this.onChange($event);
datepicker.close();
}
}

setViewProperties() {
if (!this.views.includes('day')) {
this.startView = 'multi-year';
this.panelClass = 'no-panel-navigation';
} else {
this.startView = 'month';
}
}
}

export const DateControlRendererTester: RankedTester = rankWith(
Expand Down
30 changes: 3 additions & 27 deletions packages/angular-material/src/library/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { CommonModule, DatePipe } from '@angular/common';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
Expand All @@ -35,18 +35,14 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatNativeDateModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSliderModule } from '@angular/material/slider';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import {
DateAdapter,
MatNativeDateModule,
MAT_DATE_FORMATS,
} from '@angular/material/core';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { JsonFormsModule } from '@jsonforms/angular';
import { AutocompleteControlRenderer } from './controls/autocomplete.renderer';
Expand All @@ -68,7 +64,6 @@ import { HorizontalLayoutRenderer } from './layouts/horizontal-layout.renderer';
import { VerticalLayoutRenderer } from './layouts/vertical-layout.renderer';
import { ArrayLayoutRenderer } from './layouts/array-layout.renderer';
import { LayoutChildrenRenderPropsPipe } from './layouts';
import { DayJsDateAdapter } from './util/dayjs-date-adapter';

@NgModule({
imports: [
Expand Down Expand Up @@ -137,25 +132,6 @@ import { DayJsDateAdapter } from './util/dayjs-date-adapter';
MatAutocompleteModule,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
provide: DateAdapter,
useClass: DayJsDateAdapter,
},
{
provide: MAT_DATE_FORMATS,
useValue: {
parse: {
dateInput: 'YYYY-MM-DD',
},
display: {
dateInput: 'YYYY-MM-DD',
monthYearLabel: 'YYYY-MM',
dateA11yLabel: 'YYYY-MM-DD',
monthYearA11yLabel: 'YYYY-MM',
},
},
},
],
providers: [],
})
export class JsonFormsAngularMaterialModule {}
24 changes: 24 additions & 0 deletions packages/angular-material/src/library/util/date-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';

@Injectable()
export class MyFormat {
displayFormat = 'M/D/YYYY';

setDisplayFormat(displayFormat: string) {
this.displayFormat = displayFormat;
}

get display() {
return {
monthYearLabel: 'YYYY-MM',
dateA11yLabel: 'YYYY-MM-DD',
monthYearA11yLabel: 'YYYY-MM',
dateInput: this.displayFormat,
};
}
get parse() {
return {
dateInput: this.displayFormat,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,40 +23,66 @@
THE SOFTWARE.
*/

import { Injectable } from '@angular/core';
import { NativeDateAdapter } from '@angular/material/core';
import { defaultDateFormat } from '@jsonforms/core';
import dayjs from 'dayjs';
import customParsing from 'dayjs/plugin/customParseFormat';

// allows to parse date strings with custom format
dayjs.extend(customParsing);

/**
* date adapter for dayjs to parse and format dates
* date adapter for dayjs to parse and format dates
*/
@Injectable()
export class DayJsDateAdapter extends NativeDateAdapter {
saveFormat: string = defaultDateFormat;

setSaveFormat(format: string) {
this.saveFormat = format;
}

/**
* parses a given user input string in the YYYY-MM-DD format into a date object
* @param value date string to be parsed (YYYY-MM-DD)
* @returns date object or null if parsing failed
*/
parse(value: any): Date | null {
* parses a given data prop string in the save-format into a date object
* @param value date string to be parsed
* @returns date object or null if parsing failed
*/
parseSaveFormat(value: string): Date | null {
return this.parse(value, this.saveFormat);
}

parse(value: string, format: string): Date | null {
if (!value) {
return null;
}
const date = dayjs(value, 'YYYY-MM-DD', true);
}
const date = dayjs(value, format);

if (date.isValid()) {
return date.toDate();
} else {
return null;
}
}

toSaveFormat(value: Date) {
if (!value) {
return undefined;
}
const date = dayjs(value);
if (date.isValid()) {
return date.format(this.saveFormat);
} else {
return undefined;
}
}

/**
* transforms the date to a string representation for display
* @param date date to be formatted
* @param displayFormat format to be used for formatting the date e.g. YYYY-MM-DD
* @returns string representation of the date
*/
* transforms the date to a string representation for display
* @param date date to be formatted
* @param displayFormat format to be used for formatting the date e.g. YYYY-MM-DD
* @returns string representation of the date
*/
format(date: Date, displayFormat: string): string {
return dayjs(date).format(displayFormat);
}
Expand All @@ -72,5 +98,4 @@ export class DayJsDateAdapter extends NativeDateAdapter {
return null;
}
}

}
Loading

0 comments on commit a7bfafa

Please sign in to comment.