[ADF-4342] Create localized pipe and centralize date format (#4813)

* [ADF-4342] Date Format defined in app config

* [ADF-4342] Create localized pipe and centralize date format

* Add unit test for new date pipe

* Add info internationalization docs

* Fix lining

* Fix linting

* Fix date pipe unit test

* [ADF-4342] Add supported language files

* Fix e2e tests
This commit is contained in:
davidcanonieto 2019-06-11 09:35:35 +01:00 committed by Denys Vuika
parent 990fa4625b
commit 7497822a46
38 changed files with 473 additions and 67 deletions

View File

@ -78,6 +78,7 @@
"UPLOADER": "Uploader", "UPLOADER": "Uploader",
"WEBSCRIPT": "Webscript", "WEBSCRIPT": "Webscript",
"TAG": "Tag", "TAG": "Tag",
"DATE": "Date",
"TRASHCAN": "Trashcan", "TRASHCAN": "Trashcan",
"SOCIAL": "Social", "SOCIAL": "Social",
"SETTINGS": "Settings", "SETTINGS": "Settings",

View File

@ -79,7 +79,7 @@
"direction": "rtl" "direction": "rtl"
}, },
{ {
"key": "cz", "key": "cs",
"label": "Czech" "label": "Czech"
}, },
{ {
@ -438,6 +438,11 @@
20 20
] ]
}, },
"dateValues":{
"defaultDateFormat": "medium",
"defaultDateTimeFormat": "DD/MM/YYYY HH:mm",
"defaultLocale": "en-US"
},
"files": { "files": {
"excluded": [ "excluded": [
".DS_Store", ".DS_Store",

View File

@ -126,6 +126,15 @@ export const appRoutes: Routes = [
} }
] ]
}, },
{
path: 'date',
children: [
{
path: '',
loadChildren: 'app/components/date/date.module#AppDateModule'
}
]
},
{ {
path: 'card-view', path: 'card-view',
children: [ children: [

View File

@ -72,6 +72,7 @@ export class AppLayoutComponent implements OnInit {
{ href: '/webscript', icon: 'extension', title: 'APP_LAYOUT.WEBSCRIPT' }, { href: '/webscript', icon: 'extension', title: 'APP_LAYOUT.WEBSCRIPT' },
{ href: '/tag', icon: 'local_offer', title: 'APP_LAYOUT.TAG' }, { href: '/tag', icon: 'local_offer', title: 'APP_LAYOUT.TAG' },
{ href: '/social', icon: 'thumb_up', title: 'APP_LAYOUT.SOCIAL' }, { href: '/social', icon: 'thumb_up', title: 'APP_LAYOUT.SOCIAL' },
{ href: '/date', icon: 'calendar_today', title: 'APP_LAYOUT.DATE' },
{ href: '/settings-layout', icon: 'settings', title: 'APP_LAYOUT.SETTINGS' }, { href: '/settings-layout', icon: 'settings', title: 'APP_LAYOUT.SETTINGS' },
{ href: '/config-editor', icon: 'code', title: 'APP_LAYOUT.CONFIG-EDITOR' }, { href: '/config-editor', icon: 'code', title: 'APP_LAYOUT.CONFIG-EDITOR' },
{ href: '/extendedSearch', icon: 'search', title: 'APP_LAYOUT.SEARCH' }, { href: '/extendedSearch', icon: 'search', title: 'APP_LAYOUT.SEARCH' },

View File

@ -0,0 +1,32 @@
<div class="adf-date-pipes-container">
<h2>Date Pipes</h2>
<mat-form-field class="adf-date-field">
<input matInput
placeholder="Format"
[(ngModel)]="format">
</mat-form-field>
<mat-form-field class="adf-date-field">
<mat-select placeholder="Locale dropdown" [(ngModel)]="locale">
<mat-option *ngFor="let language of languages" [value]="language.key">
{{language.label}}
</mat-option>
</mat-select>
</mat-form-field>
<h3>AdfLocalizedDate Pipe - Default</h3>
<div>{{ today | adfLocalizedDate }} </div>
<br>
<h3>AdfLocalizedDate Pipe - Custom format</h3>
<div>{{ today | adfLocalizedDate : format }} </div>
<br>
<h3>AdfLocalizedDate Pipe - Custom format and locale</h3>
<div>{{ today | adfLocalizedDate : format : locale }} </div>
<br>
<h3>AdfTimeAgo Pipe</h3>
<div>{{ today | adfTimeAgo }} </div>
<br>
<h3>AdfTimeAgo Pipe - Custom locale</h3>
<div>{{ today | adfTimeAgo : locale}} </div>
</div>

View File

@ -0,0 +1,7 @@
.adf-date-pipes-container {
padding: 20px;
}
.adf-date-field {
margin: 20px;
}

View File

@ -0,0 +1,36 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component } from '@angular/core';
import { AppConfigService } from '@alfresco/adf-core';
@Component({
selector: 'app-date-page',
templateUrl: './date.component.html',
styleUrls: ['date.component.scss']
})
export class DateComponent {
today = new Date();
locale: string;
format: string;
languages: any[];
constructor(private appConfig: AppConfigService) {
this.languages = this.appConfig.get('languages', []);
}
}

View File

@ -0,0 +1,39 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DateComponent } from './date.component';
import { CommonModule } from '@angular/common';
import { CoreModule } from '@alfresco/adf-core';
const routes: Routes = [
{
path: '',
component: DateComponent
}
];
@NgModule({
imports: [
CommonModule,
CoreModule.forChild(),
RouterModule.forChild(routes)
],
declarations: [DateComponent]
})
export class AppDateModule {}

View File

@ -256,6 +256,7 @@
<data-column <data-column
*ngIf="showNameColumn && hyperlinkNavigation" *ngIf="showNameColumn && hyperlinkNavigation"
key="name" key="name"
class="adf-ellipsis-cell"
title="{{'DOCUMENT_LIST.COLUMNS.DISPLAY_NAME' | translate}}" title="{{'DOCUMENT_LIST.COLUMNS.DISPLAY_NAME' | translate}}"
[formatTooltip]="getNodeNameTooltip"> [formatTooltip]="getNodeNameTooltip">
<ng-template let-context> <ng-template let-context>
@ -282,7 +283,7 @@
key="content.sizeInBytes" key="content.sizeInBytes"
title="{{'DOCUMENT_LIST.COLUMNS.SIZE' | translate}}" title="{{'DOCUMENT_LIST.COLUMNS.SIZE' | translate}}"
type="fileSize" type="fileSize"
class="adf-desktop-only"> class="adf-desktop-only adf-ellipsis-cell">
</data-column> </data-column>
<data-column <data-column
*ngIf="searchTerm" *ngIf="searchTerm"
@ -313,7 +314,7 @@
<data-column <data-column
title="{{'DOCUMENT_LIST.COLUMNS.IS_LOCKED' | translate}}" title="{{'DOCUMENT_LIST.COLUMNS.IS_LOCKED' | translate}}"
key="id" key="id"
class="adf-desktop-only"> class="adf-desktop-only adf-ellipsis-cell">
<ng-template let-entry="$implicit"> <ng-template let-entry="$implicit">
<button mat-icon-button [adf-node-lock]="entry.row.node.entry" class="adf-lock-button"> <button mat-icon-button [adf-node-lock]="entry.row.node.entry" class="adf-lock-button">
<mat-icon *ngIf="entry.row.getValue('isLocked')">lock</mat-icon> <mat-icon *ngIf="entry.row.getValue('isLocked')">lock</mat-icon>
@ -333,6 +334,12 @@
[format]="enableMediumTimeFormat ? 'medium' : 'timeAgo'" [format]="enableMediumTimeFormat ? 'medium' : 'timeAgo'"
class="adf-desktop-only adf-ellipsis-cell"> class="adf-desktop-only adf-ellipsis-cell">
</data-column> </data-column>
<data-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED' | translate}}"
key="createdAt"
type="date"
class="adf-desktop-only adf-ellipsis-cell">
</data-column>
</data-columns> </data-columns>

View File

@ -171,12 +171,12 @@
<data-column key="description" title="Description"></data-column> <data-column key="description" title="Description"></data-column>
<data-column key="created" title="Created"> <data-column key="created" title="Created">
<ng-template let-entry="$implicit"> <ng-template let-entry="$implicit">
<div>{{entry.row.obj.created | date:'MMM d, yyyy' }} </div> <div>{{entry.row.obj.created | adfLocalizedDate: 'MMM d, yyyy' }} </div>
</ng-template> </ng-template>
</data-column> </data-column>
<data-column key="dueDate" title="Due Date" type="date"> <data-column key="dueDate" title="Due Date" type="date">
<ng-template let-entry="$implicit"> <ng-template let-entry="$implicit">
<div>{{entry.row.obj.dueDate | date:'MMM d, yyyy' }} </div> <div>{{entry.row.obj.dueDate | adfLocalizedDate: 'MMM d, yyyy' }} </div>
</ng-template> </ng-template>
</data-column> </data-column>
<data-column key="processInstanceId" title="Process Instance Id"></data-column> <data-column key="processInstanceId" title="Process Instance Id"></data-column>

View File

@ -76,7 +76,7 @@
key="archivedAt" key="archivedAt"
title="DOCUMENT_LIST.COLUMNS.DELETED_ON"> title="DOCUMENT_LIST.COLUMNS.DELETED_ON">
<ng-template let-value="value"> <ng-template let-value="value">
<span title="{{ value | date:'medium' }}" <span title="{{ value | adfLocalizedDate: 'medium' }}"
class="adf-datatable-cell-value">{{ value | adfTimeAgo: currentLocale }}</span> class="adf-datatable-cell-value">{{ value | adfTimeAgo: currentLocale }}</span>
</ng-template> </ng-template>
</data-column> </data-column>

View File

@ -0,0 +1,28 @@
# [Localized Date pipe](../../../lib/core/pipes/localized-date.pipe.ts "Defined in localized-date.pipe.ts")
Converts a date to an given format and locale.
## Basic Usage
<!-- {% raw %} -->
```HTML
<div>
Created date: {{ date | adfLocalizedDate }}
</div>
```
<!-- {% endraw %} -->
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| format | string | 'medium' | A format to apply to the date value. [Date Pipe Formats.](https://angular.io/api/common/DatePipe#custom-format-options) |
| locale | string | 'en-US' | A locale id for the locale format rules to use. |
## Details
The pipe takes a date and formats it and localizes it so the date is displayed in the proper format for the region. It uses the [Angular Date Pipe](https://angular.io/api/common/DatePipe#custom-format-options) so all the pre-defined and custom formats can be used.
When localizing a date, you will need to add the specific locale file for your region in order to use it. Read more about internationalization [here](https://angular.io/guide/i18n#i18n-pipes).

View File

@ -21,8 +21,14 @@ Converts a recent past date into a number of days ago.
<!-- {% endraw %} --> <!-- {% endraw %} -->
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| locale | string | 'en-US' | A locale id for the locale format rules to use. |
## Details ## Details
The pipe finds the difference between the input date and the current date. If it The pipe finds the difference between the input date and the current date. If it
is less than seven days then then the date will be formatted as "X days ago". is less than seven days then then the date will be formatted as "X days ago".
Otherwise, the usual full date format is used. Otherwise, the usual full date format is used.
By default, it localizes the date to the language that is currently in use by the app. Furthermore, a different locale id can be passed to the pipe to overwrite the locale id to set a custom one.

View File

@ -18,6 +18,7 @@ fairly straightforward to maintain.
- [I18n concepts](#i18n-concepts) - [I18n concepts](#i18n-concepts)
- [ADF support for i18n](#adf-support-for-i18n) - [ADF support for i18n](#adf-support-for-i18n)
- [Using the translate pipe](#using-the-translate-pipe) - [Using the translate pipe](#using-the-translate-pipe)
- [Using the localized date pipe](#using-the-localized-date-pipe)
- [Adding and replacing messages](#adding-and-replacing-messages) - [Adding and replacing messages](#adding-and-replacing-messages)
- [Interpolations](#interpolations) - [Interpolations](#interpolations)
- [How the display language is selected](#how-the-display-language-is-selected) - [How the display language is selected](#how-the-display-language-is-selected)
@ -153,6 +154,30 @@ component's `.ts` file:
<!-- {% endraw %} --> <!-- {% endraw %} -->
## Using the localized date pipe
Date values are also localized in your ADF app. By default they are localized to en-US, although you can easily change this by adding the localization files provided by Angular.
If you want to use a different locale simply add the locale file for your region in your `app.module.ts`.
<!-- {% raw %} -->
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
registerLocaleData(localeFr);
<!-- {% endraw %} -->
Usage of the [localized date pipe](../core/pipes/localized-date.pipe.md).
<!-- {% raw %} -->
{{ date | adfLocalizedDate: format : locale }}
<!-- {% endraw %} -->
Find more info about this in the [Angular sDocs](https://angular.io/guide/i18n#setting-up-the-locale-of-your-app).
## Adding and replacing messages ## Adding and replacing messages
The built-in translations certainly won't cover everything you will need for The built-in translations certainly won't cover everything you will need for

View File

@ -44,7 +44,7 @@ describe('Process Header cloud component', () => {
const simpleApp = resources.ACTIVITI7_APPS.SIMPLE_APP.name; const simpleApp = resources.ACTIVITI7_APPS.SIMPLE_APP.name;
const subProcessApp = resources.ACTIVITI7_APPS.SUB_PROCESS_APP.name; const subProcessApp = resources.ACTIVITI7_APPS.SUB_PROCESS_APP.name;
const formatDate = 'DD-MM-YYYY'; const formatDate = 'MMM D YYYY';
const processHeaderCloudPage = new ProcessHeaderCloudPage(); const processHeaderCloudPage = new ProcessHeaderCloudPage();

View File

@ -39,7 +39,7 @@ describe('Task Header cloud component', () => {
const simpleApp = resources.ACTIVITI7_APPS.SIMPLE_APP.name; const simpleApp = resources.ACTIVITI7_APPS.SIMPLE_APP.name;
const priority = 30; const priority = 30;
const description = 'descriptionTask'; const description = 'descriptionTask';
const formatDate = 'DD-MM-YYYY'; const formatDate = 'MMM D YYYY';
const taskHeaderCloudPage = new TaskHeaderCloudPage(); const taskHeaderCloudPage = new TaskHeaderCloudPage();

View File

@ -518,6 +518,29 @@
} }
} }
}, },
"dateValues": {
"description": "Configuration of date formats in the app",
"type": "object",
"required": [
"defaultDateFormat",
"defaultDateTimeFormat",
"defaultLocale"
],
"properties": {
"defaultDateFormat": {
"description": "Default date format",
"type": "string"
},
"defaultDateTimeFormat": {
"description": "Default date time format",
"type": "string"
},
"defaultLocale": {
"description": "Default date locale",
"type": "string"
}
}
},
"files": { "files": {
"description": "Configuration of rules applied to file upload", "description": "Configuration of rules applied to file upload",
"type": "object", "type": "object",

View File

@ -26,6 +26,7 @@ import { CardViewUpdateService } from '../../services/card-view-update.service';
import { UserPreferencesService, UserPreferenceValues } from '../../../services/user-preferences.service'; import { UserPreferencesService, UserPreferenceValues } from '../../../services/user-preferences.service';
import { MomentDateAdapter } from '../../../utils/momentDateAdapter'; import { MomentDateAdapter } from '../../../utils/momentDateAdapter';
import { MOMENT_DATE_FORMATS } from '../../../utils/moment-date-formats.model'; import { MOMENT_DATE_FORMATS } from '../../../utils/moment-date-formats.model';
import { AppConfigService } from '../../../app-config/app-config.service';
@Component({ @Component({
providers: [ providers: [
@ -40,8 +41,6 @@ import { MOMENT_DATE_FORMATS } from '../../../utils/moment-date-formats.model';
}) })
export class CardViewDateItemComponent implements OnInit { export class CardViewDateItemComponent implements OnInit {
public SHOW_FORMAT: string = 'MMM DD YY';
@Input() @Input()
property: CardViewDateItemModel; property: CardViewDateItemModel;
@ -55,10 +54,13 @@ export class CardViewDateItemComponent implements OnInit {
public datepicker: MatDatetimepicker<any>; public datepicker: MatDatetimepicker<any>;
valueDate: Moment; valueDate: Moment;
dateFormat: string;
constructor(private cardViewUpdateService: CardViewUpdateService, constructor(private cardViewUpdateService: CardViewUpdateService,
private dateAdapter: DateAdapter<Moment>, private dateAdapter: DateAdapter<Moment>,
private userPreferencesService: UserPreferencesService) { private userPreferencesService: UserPreferencesService,
private appConfig: AppConfigService) {
this.dateFormat = this.appConfig.get('dateValues.defaultDateFormat');
} }
ngOnInit() { ngOnInit() {
@ -66,10 +68,10 @@ export class CardViewDateItemComponent implements OnInit {
this.dateAdapter.setLocale(locale); this.dateAdapter.setLocale(locale);
}); });
(<MomentDateAdapter> this.dateAdapter).overrideDisplayFormat = this.SHOW_FORMAT; (<MomentDateAdapter> this.dateAdapter).overrideDisplayFormat = this.dateFormat;
if (this.property.value) { if (this.property.value) {
this.valueDate = moment(this.property.value, this.SHOW_FORMAT); this.valueDate = moment(this.property.value, this.dateFormat);
} }
} }
@ -87,7 +89,7 @@ export class CardViewDateItemComponent implements OnInit {
onDateChanged(newDateValue) { onDateChanged(newDateValue) {
if (newDateValue) { if (newDateValue) {
const momentDate = moment(newDateValue.value, this.SHOW_FORMAT, true); const momentDate = moment(newDateValue.value, this.dateFormat, true);
if (momentDate.isValid()) { if (momentDate.isValid()) {
this.valueDate = momentDate; this.valueDate = momentDate;
this.cardViewUpdateService.update(this.property, momentDate.toDate()); this.cardViewUpdateService.update(this.property, momentDate.toDate());

View File

@ -57,6 +57,41 @@ import { ExtensionsModule } from '@alfresco/adf-extensions';
import { directionalityConfigFactory } from './services/directionality-config-factory'; import { directionalityConfigFactory } from './services/directionality-config-factory';
import { DirectionalityConfigService } from './services/directionality-config.service'; import { DirectionalityConfigService } from './services/directionality-config.service';
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeDe from '@angular/common/locales/de';
import localeIt from '@angular/common/locales/it';
import localeEs from '@angular/common/locales/es';
import localeJa from '@angular/common/locales/ja';
import localeNl from '@angular/common/locales/nl';
import localePt from '@angular/common/locales/pt';
import localeNb from '@angular/common/locales/nb';
import localeRu from '@angular/common/locales/ru';
import localeCh from '@angular/common/locales/zh';
import localeAr from '@angular/common/locales/ar';
import localeCs from '@angular/common/locales/cs';
import localePl from '@angular/common/locales/pl';
import localeFi from '@angular/common/locales/fi';
import localeDa from '@angular/common/locales/da';
import localeSv from '@angular/common/locales/sv';
registerLocaleData(localeFr);
registerLocaleData(localeDe);
registerLocaleData(localeIt);
registerLocaleData(localeEs);
registerLocaleData(localeJa);
registerLocaleData(localeNl);
registerLocaleData(localePt);
registerLocaleData(localeNb);
registerLocaleData(localeRu);
registerLocaleData(localeCh);
registerLocaleData(localeAr);
registerLocaleData(localeCs);
registerLocaleData(localePl);
registerLocaleData(localeFi);
registerLocaleData(localeDa);
registerLocaleData(localeSv);
@NgModule({ @NgModule({
imports: [ imports: [
TranslateModule, TranslateModule,

View File

@ -28,14 +28,14 @@ describe('DataTableCellComponent', () => {
}); });
it('should use medium format by default', () => { it('should use medium format by default', () => {
const component = new DateCellComponent(null, null); const component = new DateCellComponent(null, null, new AppConfigService(null));
expect(component.format).toBe('medium'); expect(component.format).toBe('medium');
}); });
it('should use column format', () => { it('should use column format', () => {
const component = new DateCellComponent(null, <any> { const component = new DateCellComponent(null, <any> {
nodeUpdated: new Subject<any>() nodeUpdated: new Subject<any>()
}); }, new AppConfigService(null));
component.column = { component.column = {
key: 'created', key: 'created',
type: 'date', type: 'date',
@ -49,7 +49,8 @@ describe('DataTableCellComponent', () => {
it('should update cell data on alfrescoApiService.nodeUpdated event', () => { it('should update cell data on alfrescoApiService.nodeUpdated event', () => {
const component = new DateCellComponent( const component = new DateCellComponent(
null, null,
alfrescoApiService alfrescoApiService,
new AppConfigService(null)
); );
component.column = { component.column = {
@ -83,7 +84,8 @@ describe('DataTableCellComponent', () => {
it('not should update cell data if ids don`t match', () => { it('not should update cell data if ids don`t match', () => {
const component = new DateCellComponent( const component = new DateCellComponent(
null, null,
alfrescoApiService alfrescoApiService,
new AppConfigService(null)
); );
component.column = { component.column = {

View File

@ -121,7 +121,7 @@
<mat-icon>{{ data.getValue(row, col) }}</mat-icon> <mat-icon>{{ data.getValue(row, col) }}</mat-icon>
</div> </div>
<div *ngSwitchCase="'date'" class="adf-cell-value" <div *ngSwitchCase="'date'" class="adf-cell-value"
[attr.data-automation-id]="'date_' + (data.getValue(row, col) | date: 'medium') "> [attr.data-automation-id]="'date_' + (data.getValue(row, col) | adfLocalizedDate: 'medium') ">
<adf-date-cell class="adf-datatable-center-date-column-ie" <adf-date-cell class="adf-datatable-center-date-column-ie"
[data]="data" [data]="data"
[column]="col" [column]="col"

View File

@ -22,6 +22,7 @@ import {
UserPreferenceValues UserPreferenceValues
} from '../../../services/user-preferences.service'; } from '../../../services/user-preferences.service';
import { AlfrescoApiService } from '../../../services/alfresco-api.service'; import { AlfrescoApiService } from '../../../services/alfresco-api.service';
import { AppConfigService } from '../../../app-config/app-config.service';
@Component({ @Component({
selector: 'adf-date-cell', selector: 'adf-date-cell',
@ -30,20 +31,19 @@ import { AlfrescoApiService } from '../../../services/alfresco-api.service';
<ng-container> <ng-container>
<span <span
[attr.aria-label]="value$ | async | adfTimeAgo: currentLocale" [attr.aria-label]="value$ | async | adfTimeAgo: currentLocale"
title="{{ tooltip | date: 'medium' }}" title="{{ tooltip | adfLocalizedDate: 'medium' }}"
class="adf-datatable-cell-value" class="adf-datatable-cell-value"
*ngIf="format === 'timeAgo'; else standard_date" *ngIf="format === 'timeAgo'; else standard_date">
>
{{ value$ | async | adfTimeAgo: currentLocale }} {{ value$ | async | adfTimeAgo: currentLocale }}
</span> </span>
</ng-container> </ng-container>
<ng-template #standard_date> <ng-template #standard_date>
<span <span
title="{{ tooltip | date: format }}"
class="adf-datatable-cell-value" class="adf-datatable-cell-value"
[attr.aria-label]="value$ | async | date: format" title="{{ tooltip | adfLocalizedDate: format }}"
> class="adf-datatable-cell-value"
{{ value$ | async | date: format }} [attr.aria-label]="value$ | async | adfLocalizedDate: format">
{{ value$ | async | adfLocalizedDate: format }}
</span> </span>
</ng-template> </ng-template>
`, `,
@ -51,21 +51,27 @@ import { AlfrescoApiService } from '../../../services/alfresco-api.service';
host: { class: 'adf-date-cell adf-datatable-content-cell' } host: { class: 'adf-date-cell adf-datatable-content-cell' }
}) })
export class DateCellComponent extends DataTableCellComponent { export class DateCellComponent extends DataTableCellComponent {
static DATE_FORMAT = 'medium';
currentLocale: string; currentLocale: string;
dateFormat: string;
get format(): string { get format(): string {
if (this.column) { if (this.column) {
return this.column.format || 'medium'; return this.column.format || this.dateFormat;
} }
return 'medium'; return this.dateFormat;
} }
constructor( constructor(
userPreferenceService: UserPreferencesService, userPreferenceService: UserPreferencesService,
alfrescoApiService: AlfrescoApiService alfrescoApiService: AlfrescoApiService,
appConfig: AppConfigService
) { ) {
super(alfrescoApiService); super(alfrescoApiService);
this.dateFormat = appConfig.get('dateValues.defaultDateFormat', DateCellComponent.DATE_FORMAT);
if (userPreferenceService) { if (userPreferenceService) {
userPreferenceService userPreferenceService
.select(UserPreferenceValues.Locale) .select(UserPreferenceValues.Locale)

View File

@ -39,9 +39,10 @@ import { baseHost, WidgetComponent } from './../widget.component';
}) })
export class DateWidgetComponent extends WidgetComponent implements OnInit { export class DateWidgetComponent extends WidgetComponent implements OnInit {
DATE_FORMAT = 'DD/MM/YYYY';
minDate: Moment; minDate: Moment;
maxDate: Moment; maxDate: Moment;
displayDate: Moment; displayDate: Moment;
constructor(public formService: FormService, constructor(public formService: FormService,
@ -60,11 +61,11 @@ export class DateWidgetComponent extends WidgetComponent implements OnInit {
if (this.field) { if (this.field) {
if (this.field.minValue) { if (this.field.minValue) {
this.minDate = moment(this.field.minValue, 'DD/MM/YYYY'); this.minDate = moment(this.field.minValue, this.DATE_FORMAT);
} }
if (this.field.maxValue) { if (this.field.maxValue) {
this.maxDate = moment(this.field.maxValue, 'DD/MM/YYYY'); this.maxDate = moment(this.field.maxValue, this.DATE_FORMAT);
} }
} }
this.displayDate = moment(this.field.value, this.field.dateDisplayFormat); this.displayDate = moment(this.field.value, this.field.dateDisplayFormat);

View File

@ -1,6 +1,6 @@
<div> <div>
<mat-form-field class="adf-date-editor"> <mat-form-field class="adf-date-editor">
<label [attr.for]="column.id">{{column.name}} (d-M-yyyy)</label> <label [attr.for]="column.id">{{column.name}} (DATE_FORMAT)</label>
<input matInput <input matInput
id="dateInput" id="dateInput"
type="text" type="text"

View File

@ -18,6 +18,7 @@
/* tslint:disable:component-selector */ /* tslint:disable:component-selector */
import { UserPreferencesService, UserPreferenceValues } from '../../../../../../services/user-preferences.service'; import { UserPreferencesService, UserPreferenceValues } from '../../../../../../services/user-preferences.service';
import { MomentDateAdapter } from '../../../../../../utils/momentDateAdapter'; import { MomentDateAdapter } from '../../../../../../utils/momentDateAdapter';
import { MOMENT_DATE_FORMATS } from '../../../../../../utils/moment-date-formats.model'; import { MOMENT_DATE_FORMATS } from '../../../../../../utils/moment-date-formats.model';
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';

View File

@ -1,6 +1,6 @@
<div> <div>
<mat-form-field class="adf-date-editor"> <mat-form-field class="adf-date-editor">
<label [attr.for]="column.id">{{column.name}} {{DATE_FORMAT}}</label> <label [attr.for]="column.id">{{column.name}} {{DATE_TIME_FORMAT}}</label>
<input matInput <input matInput
[matDatetimepicker]="datetimePicker" [matDatetimepicker]="datetimePicker"
[(ngModel)]="value" [(ngModel)]="value"
@ -9,12 +9,16 @@
[disabled]="!column.editable" [disabled]="!column.editable"
(focusout)="onDateChanged($event.srcElement.value)" (focusout)="onDateChanged($event.srcElement.value)"
(dateChange)="onDateChanged($event)"> (dateChange)="onDateChanged($event)">
<mat-datetimepicker-toggle matSuffix [for]="datetimePicker" <mat-datetimepicker-toggle
*ngIf="column.editable" matSuffix
class="adf-date-editor-button"></mat-datetimepicker-toggle> [for]="datetimePicker"
class="adf-date-editor-button">
</mat-datetimepicker-toggle>
</mat-form-field> </mat-form-field>
<mat-datetimepicker #datetimePicker <mat-datetimepicker
#datetimePicker
type="datetime" type="datetime"
openOnFocus="true" openOnFocus="true"
timeInterval="5"></mat-datetimepicker> timeInterval="5">
</mat-datetimepicker>
</div> </div>

View File

@ -43,7 +43,7 @@ import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetim
}) })
export class DateTimeEditorComponent implements OnInit { export class DateTimeEditorComponent implements OnInit {
DATE_FORMAT: string = 'D-M-YYYY hh:mm A'; DATE_TIME_FORMAT: string = 'DD/MM/YYYY HH:mm';
value: any; value: any;
@ -69,19 +69,19 @@ export class DateTimeEditorComponent implements OnInit {
}); });
const momentDateAdapter = <MomentDateAdapter> this.dateAdapter; const momentDateAdapter = <MomentDateAdapter> this.dateAdapter;
momentDateAdapter.overrideDisplayFormat = this.DATE_FORMAT; momentDateAdapter.overrideDisplayFormat = this.DATE_TIME_FORMAT;
this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_FORMAT); this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_TIME_FORMAT);
} }
onDateChanged(newDateValue) { onDateChanged(newDateValue) {
if (newDateValue && newDateValue.value) { if (newDateValue && newDateValue.value) {
const newValue = moment(newDateValue.value, this.DATE_FORMAT); const newValue = moment(newDateValue.value, this.DATE_TIME_FORMAT);
this.row.value[this.column.id] = newDateValue.value.format(this.DATE_FORMAT); this.row.value[this.column.id] = newDateValue.value.format(this.DATE_TIME_FORMAT);
this.value = newValue; this.value = newValue;
this.table.flushValue(); this.table.flushValue();
} else if (newDateValue) { } else if (newDateValue) {
const newValue = moment(newDateValue, this.DATE_FORMAT); const newValue = moment(newDateValue, this.DATE_TIME_FORMAT);
this.value = newValue; this.value = newValue;
this.row.value[this.column.id] = newDateValue; this.row.value[this.column.id] = newDateValue;
this.table.flushValue(); this.table.flushValue();

View File

@ -0,0 +1,56 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LocalizedDatePipe } from './localized-date.pipe';
import { async, TestBed } from '@angular/core/testing';
import { AppConfigService } from '../app-config/app-config.service';
import { UserPreferencesService } from '../services/user-preferences.service';
import { of } from 'rxjs';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module';
describe('LocalizedDatePipe', () => {
let pipe: LocalizedDatePipe;
let userPreferences: UserPreferencesService;
setupTestBed({
imports: [CoreTestingModule]
});
beforeEach(async(() => {
userPreferences = TestBed.get(UserPreferencesService);
spyOn(userPreferences, 'select').and.returnValue(of(''));
pipe = new LocalizedDatePipe(userPreferences, new AppConfigService(null));
}));
it('should return time with locale en-US', () => {
const date = new Date('1990-11-03');
expect(pipe.transform(date)).toBe('Nov 3, 1990, 12:00:00 AM');
});
it('should return correct date when formating and locating it', () => {
const date = new Date();
expect(new Date(pipe.transform(date)).toDateString()).toBe(date.toDateString());
});
it('should return formated time when a formar is given', () => {
const date = new Date('1990-11-03');
expect(pipe.transform(date, 'MMM dd')).toBe('Nov 03');
});
});

View File

@ -0,0 +1,50 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DatePipe } from '@angular/common';
import { Pipe, PipeTransform } from '@angular/core';
import { AppConfigService } from '../app-config/app-config.service';
import { UserPreferencesService, UserPreferenceValues } from '../services/user-preferences.service';
@Pipe({
name: 'adfLocalizedDate',
pure: false
})
export class LocalizedDatePipe implements PipeTransform {
static DEFAULT_LOCALE = 'en-US';
static DEFAULT_DATE_TIME_FORMAT = 'medium';
defaultLocale: string;
defaultFormat: string;
constructor(public userPreferenceService: UserPreferencesService,
public appConfig: AppConfigService) {
this.userPreferenceService.select(UserPreferenceValues.Locale).subscribe((locale) => {
this.defaultLocale = locale || LocalizedDatePipe.DEFAULT_LOCALE;
});
this.defaultFormat = this.appConfig.get<string>('dateValues.defaultFormat', LocalizedDatePipe.DEFAULT_DATE_TIME_FORMAT);
}
transform(value: any, format?: string, locale?: string): any {
const actualFormat = format || this.defaultFormat;
const actualLocale = locale || this.defaultLocale;
const datePipe: DatePipe = new DatePipe(actualLocale);
return datePipe.transform(value, actualFormat);
}
}

View File

@ -28,6 +28,7 @@ import { FullNamePipe } from './full-name.pipe';
import { FormatSpacePipe } from './format-space.pipe'; import { FormatSpacePipe } from './format-space.pipe';
import { FileTypePipe } from './file-type.pipe'; import { FileTypePipe } from './file-type.pipe';
import { MultiValuePipe } from './multi-value.pipe'; import { MultiValuePipe } from './multi-value.pipe';
import { LocalizedDatePipe } from './localized-date.pipe';
@NgModule({ @NgModule({
imports: [ imports: [
@ -43,7 +44,8 @@ import { MultiValuePipe } from './multi-value.pipe';
NodeNameTooltipPipe, NodeNameTooltipPipe,
FormatSpacePipe, FormatSpacePipe,
FileTypePipe, FileTypePipe,
MultiValuePipe MultiValuePipe,
LocalizedDatePipe
], ],
providers: [ providers: [
FileSizePipe, FileSizePipe,
@ -54,7 +56,8 @@ import { MultiValuePipe } from './multi-value.pipe';
NodeNameTooltipPipe, NodeNameTooltipPipe,
FormatSpacePipe, FormatSpacePipe,
FileTypePipe, FileTypePipe,
MultiValuePipe MultiValuePipe,
LocalizedDatePipe
], ],
exports: [ exports: [
FileSizePipe, FileSizePipe,
@ -66,7 +69,8 @@ import { MultiValuePipe } from './multi-value.pipe';
NodeNameTooltipPipe, NodeNameTooltipPipe,
FormatSpacePipe, FormatSpacePipe,
FileTypePipe, FileTypePipe,
MultiValuePipe MultiValuePipe,
LocalizedDatePipe
] ]
}) })
export class PipeModule { export class PipeModule {

View File

@ -23,5 +23,6 @@ export * from './time-ago.pipe';
export * from './user-initial.pipe'; export * from './user-initial.pipe';
export * from './full-name.pipe'; export * from './full-name.pipe';
export * from './multi-value.pipe'; export * from './multi-value.pipe';
export * from './localized-date.pipe';
export * from './pipe.module'; export * from './pipe.module';

View File

@ -16,14 +16,26 @@
*/ */
import { TimeAgoPipe } from './time-ago.pipe'; import { TimeAgoPipe } from './time-ago.pipe';
import { async } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
import { AppConfigService } from '../app-config/app-config.service';
import { UserPreferencesService } from '../services/user-preferences.service';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module';
import { of } from 'rxjs';
describe('TimeAgoPipe', () => { describe('TimeAgoPipe', () => {
let pipe: TimeAgoPipe; let pipe: TimeAgoPipe;
let userPreferences: UserPreferencesService;
setupTestBed({
imports: [CoreTestingModule]
});
beforeEach(async(() => { beforeEach(async(() => {
pipe = new TimeAgoPipe(); userPreferences = TestBed.get(UserPreferencesService);
spyOn(userPreferences, 'select').and.returnValue(of(''));
pipe = new TimeAgoPipe(userPreferences, new AppConfigService(null));
})); }));
it('should return time difference for a given date', () => { it('should return time difference for a given date', () => {

View File

@ -17,20 +17,34 @@
import moment from 'moment-es6'; import moment from 'moment-es6';
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
import { AppConfigService } from '../app-config/app-config.service';
import { UserPreferenceValues, UserPreferencesService } from '../services/user-preferences.service';
@Pipe({ @Pipe({
name: 'adfTimeAgo' name: 'adfTimeAgo'
}) })
export class TimeAgoPipe implements PipeTransform { export class TimeAgoPipe implements PipeTransform {
defaultLocale = 'en-US'; static DEFAULT_LOCALE = 'en-US';
static DEFAULT_DATE_TIME_FORMAT = 'DD/MM/YYYY HH:mm';
defaultLocale: string;
defaultDateTimeFormat: string;
constructor(public userPreferenceService: UserPreferencesService,
public appConfig: AppConfigService) {
this.userPreferenceService.select(UserPreferenceValues.Locale).subscribe((locale) => {
this.defaultLocale = locale || TimeAgoPipe.DEFAULT_LOCALE;
});
this.defaultDateTimeFormat = this.appConfig.get<string>('dateValues.defaultDateTimeFormat', TimeAgoPipe.DEFAULT_DATE_TIME_FORMAT);
}
transform(value: Date, locale?: string) { transform(value: Date, locale?: string) {
if (value !== null && value !== undefined ) { if (value !== null && value !== undefined ) {
const actualLocale = locale ? locale : this.defaultLocale; const actualLocale = locale || this.defaultLocale;
const then = moment(value); const then = moment(value);
const diff = moment().locale(actualLocale).diff(then, 'days'); const diff = moment().locale(actualLocale).diff(then, 'days');
return diff > 7 ? then.locale(actualLocale).format('DD/MM/YYYY HH:mm') : then.locale(actualLocale).fromNow(); return diff > 7 ? then.locale(actualLocale).format(this.defaultDateTimeFormat) : then.locale(actualLocale).fromNow();
} }
return ''; return '';
} }

View File

@ -128,7 +128,7 @@ describe('ProcessHeaderCloudComponent', () => {
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-startDate"] .adf-property-value')); const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-startDate"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toBe('09-03-2019'); expect(valueEl.nativeElement.innerText.trim()).toBe('Mar 09 2019');
}); });
})); }));
@ -138,7 +138,7 @@ describe('ProcessHeaderCloudComponent', () => {
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-lastModified"] .adf-property-value')); const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-lastModified"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toBe('09-03-2019'); expect(valueEl.nativeElement.innerText.trim()).toBe('Mar 09 2019');
}); });
})); }));

View File

@ -102,14 +102,12 @@ export class ProcessHeaderCloudComponent implements OnChanges {
{ {
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.START_DATE', label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.START_DATE',
value: this.processInstanceDetails.startDate, value: this.processInstanceDetails.startDate,
format: 'DD-MM-YYYY',
key: 'startDate' key: 'startDate'
}), }),
new CardViewDateItemModel( new CardViewDateItemModel(
{ {
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.LAST_MODIFIED', label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.LAST_MODIFIED',
value: this.processInstanceDetails.lastModified, value: this.processInstanceDetails.lastModified,
format: 'DD-MM-YYYY',
key: 'lastModified' key: 'lastModified'
}), }),
new CardViewTextItemModel( new CardViewTextItemModel(

View File

@ -118,7 +118,7 @@ describe('TaskHeaderCloudComponent', () => {
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-property-value')); const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toBe('18-12-2018'); expect(valueEl.nativeElement.innerText.trim()).toBe('Dec 18 2018');
}); });
})); }));

View File

@ -59,6 +59,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
properties: CardViewItem[]; properties: CardViewItem[];
inEdit: boolean = false; inEdit: boolean = false;
parentTaskName: string; parentTaskName: string;
dateFormat: string;
private subscriptions: Subscription[] = []; private subscriptions: Subscription[] = [];
@ -68,7 +69,9 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
private appConfig: AppConfigService, private appConfig: AppConfigService,
private router: Router, private router: Router,
private cardViewUpdateService: CardViewUpdateService private cardViewUpdateService: CardViewUpdateService
) { } ) {
this.dateFormat = this.appConfig.get('dateValues.defaultDateFormat');
}
ngOnInit() { ngOnInit() {
if ((this.appName || this.appName === '') && this.taskId) { if ((this.appName || this.appName === '') && this.taskId) {
@ -122,7 +125,6 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.DUE_DATE', label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.DUE_DATE',
value: this.taskDetails.dueDate, value: this.taskDetails.dueDate,
key: 'dueDate', key: 'dueDate',
format: 'DD-MM-YYYY',
default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.DUE_DATE_DEFAULT'), default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.DUE_DATE_DEFAULT'),
editable: true editable: true
} }
@ -139,7 +141,6 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
{ {
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.CREATED', label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.CREATED',
value: this.taskDetails.createdDate, value: this.taskDetails.createdDate,
format: 'DD-MM-YYYY',
key: 'created' key: 'created'
} }
), ),
@ -162,7 +163,6 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
{ {
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.END_DATE', label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.END_DATE',
value: this.taskDetails.completedDate, value: this.taskDetails.completedDate,
format: 'DD-MM-YYYY',
key: 'endDate' key: 'endDate'
} }
), ),

View File

@ -25,6 +25,7 @@
"coverage": "./lib/config/create-coverage-index.sh && lite-server -c ./lib/config/proxy-coverage.json", "coverage": "./lib/config/create-coverage-index.sh && lite-server -c ./lib/config/proxy-coverage.json",
"03": "echo -------------------------------------------- Lint -----------------------------------------------", "03": "echo -------------------------------------------- Lint -----------------------------------------------",
"03s": "", "03s": "",
"lint-demo": "./node_modules/.bin/tslint -p ./demo-shell/tsconfig.json -c ./demo-shell/tslint.json",
"lint-lib": "./node_modules/.bin/tslint -p ./lib/tsconfig.json -c ./lib/tslint.json", "lint-lib": "./node_modules/.bin/tslint -p ./lib/tsconfig.json -c ./lib/tslint.json",
"lint-e2e": "tsc -p ./e2e/tsconfig.e2e.json && ./node_modules/.bin/tslint -p ./e2e/tsconfig.e2e.json -c ./tslint.json", "lint-e2e": "tsc -p ./e2e/tsconfig.e2e.json && ./node_modules/.bin/tslint -p ./e2e/tsconfig.e2e.json -c ./tslint.json",
"validate-config": "ajv validate -s ./lib/core/app-config/schema.json -d ./demo-shell/src/app.config.json --errors=text --verbose", "validate-config": "ajv validate -s ./lib/core/app-config/schema.json -d ./demo-shell/src/app.config.json --errors=text --verbose",
@ -237,7 +238,7 @@
"typings": "./index.d.ts", "typings": "./index.d.ts",
"lint-staged": { "lint-staged": {
"linters": { "linters": {
"**/demo-shell/src/**/*.ts": "npm run lint-lib -- --fix", "**/demo-shell/src/**/*.ts": "npm run lint-demo -- --fix",
"**/lib/**/*.ts": "npm run lint-lib -- --fix", "**/lib/**/*.ts": "npm run lint-lib -- --fix",
"**/e2e/**/*.ts": "npm run lint-e2e -- --fix", "**/e2e/**/*.ts": "npm run lint-e2e -- --fix",
"*.scss": "npm run stylelint -- --fix" "*.scss": "npm run stylelint -- --fix"