[ADF-5390] [ADF-5391] Add multivalue cardview for Date, Datetime, Integers and Decimal properties. (#6980)

* [ADF-5390] Addd multivalue cardview for Date, Datetime, Integers and Decimal properties

* Fix unit test

* Fix linting

* Fix e2e tests

* fix e2e

Co-authored-by: Eugenio Romano <eugenio.romano@alfresco.com>
This commit is contained in:
davidcanonieto 2021-05-09 04:05:26 +01:00 committed by GitHub
parent 71cad4c287
commit bd805cb34b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 280 additions and 54 deletions

View File

@ -125,6 +125,15 @@ export class CardViewComponent implements OnInit, OnDestroy {
format: 'shortDate', format: 'shortDate',
editable: this.isEditable editable: this.isEditable
}), }),
new CardViewDateItemModel({
label: 'CardView Date Item - Multivalue (chips)',
value: [new Date(1983, 11, 24, 10, 0, 30)],
key: 'date',
default: new Date(1983, 11, 24, 10, 0, 30),
format: 'shortDate',
editable: this.isEditable,
multivalued: true
}),
new CardViewDatetimeItemModel({ new CardViewDatetimeItemModel({
label: 'CardView Datetime Item', label: 'CardView Datetime Item',
value: new Date(1983, 11, 24, 10, 0, 0), value: new Date(1983, 11, 24, 10, 0, 0),
@ -133,6 +142,15 @@ export class CardViewComponent implements OnInit, OnDestroy {
format: 'short', format: 'short',
editable: this.isEditable editable: this.isEditable
}), }),
new CardViewDatetimeItemModel({
label: 'CardView Datetime Item - Multivalue (chips)',
value: [new Date(1983, 11, 24, 10, 0, 0)],
key: 'datetime',
default: new Date(1983, 11, 24, 10, 0, 0),
format: 'short',
editable: this.isEditable,
multivalued: true
}),
new CardViewBoolItemModel({ new CardViewBoolItemModel({
label: 'CardView Boolean Item', label: 'CardView Boolean Item',
value: true, value: true,
@ -162,6 +180,15 @@ export class CardViewComponent implements OnInit, OnDestroy {
editable: this.isEditable, editable: this.isEditable,
pipes: [{ pipe: this.decimalNumberPipe }] pipes: [{ pipe: this.decimalNumberPipe }]
}), }),
new CardViewFloatItemModel({
label: 'CardView Float Item - Multivalue (chips)',
value: [9.9],
key: 'float',
default: 0.0,
editable: this.isEditable,
multivalued: true,
pipes: [{ pipe: this.decimalNumberPipe }]
}),
new CardViewKeyValuePairsItemModel({ new CardViewKeyValuePairsItemModel({
label: 'CardView Key-Value Pairs Item', label: 'CardView Key-Value Pairs Item',
value: [{ name: 'hey', value: 'you' }, { name: 'hey', value: 'you' }], value: [{ name: 'hey', value: 'you' }, { name: 'hey', value: 'you' }],

View File

@ -25,9 +25,9 @@ export class MetadataViewPage {
aspectTitle: Locator = by.css(`mat-panel-title`); aspectTitle: Locator = by.css(`mat-panel-title`);
name = element(by.css(`[data-automation-id='card-textitem-value-properties.cm:name']`)); name = element(by.css(`[data-automation-id='card-textitem-value-properties.cm:name']`));
creator = element(by.css(`[data-automation-id='card-textitem-value-createdByUser.displayName']`)); creator = element(by.css(`[data-automation-id='card-textitem-value-createdByUser.displayName']`));
createdDate = element(by.css(`span[data-automation-id='card-dateitem-createdAt'] span`)); createdDate = element(by.css(`span[data-automation-id='card-dateitem-createdAt']`));
modifier = element(by.css(`[data-automation-id='card-textitem-value-modifiedByUser.displayName']`)); modifier = element(by.css(`[data-automation-id='card-textitem-value-modifiedByUser.displayName']`));
modifiedDate = element(by.css(`span[data-automation-id='card-dateitem-modifiedAt'] span`)); modifiedDate = element(by.css(`span[data-automation-id='card-dateitem-modifiedAt']`));
mimetypeName = element(by.css(`[data-automation-id='card-textitem-value-content.mimeTypeName']`)); mimetypeName = element(by.css(`[data-automation-id='card-textitem-value-content.mimeTypeName']`));
size = element(by.css(`[data-automation-id='card-textitem-value-content.sizeInBytes']`)); size = element(by.css(`[data-automation-id='card-textitem-value-content.sizeInBytes']`));
description = element(by.css(`span[data-automation-id='card-textitem-value-properties.cm:description']`)); description = element(by.css(`span[data-automation-id='card-textitem-value-properties.cm:description']`));

View File

@ -35,7 +35,7 @@ export class TaskDetailsPage {
parentTaskIdField = element(by.css('[data-automation-id="card-textitem-value-parentTaskId"] ')); parentTaskIdField = element(by.css('[data-automation-id="card-textitem-value-parentTaskId"] '));
durationField = element(by.css('[data-automation-id="card-textitem-value-duration"] ')); durationField = element(by.css('[data-automation-id="card-textitem-value-duration"] '));
endDateField = element.all(by.css('span[data-automation-id*="endDate"] span')).first(); endDateField = element.all(by.css('span[data-automation-id*="endDate"] span')).first();
createdField = element(by.css('span[data-automation-id="card-dateitem-created"] span')); createdField = element(by.css('span[data-automation-id="card-dateitem-created"]'));
idField = element.all(by.css('[data-automation-id="card-textitem-value-id"]')).first(); idField = element.all(by.css('[data-automation-id="card-textitem-value-id"]')).first();
descriptionField = element(by.css('[data-automation-id="card-textitem-value-description"]')); descriptionField = element(by.css('[data-automation-id="card-textitem-value-description"]'));
dueDateField = element.all(by.css('span[data-automation-id*="dueDate"] span')).first(); dueDateField = element.all(by.css('span[data-automation-id*="dueDate"] span')).first();

View File

@ -119,7 +119,7 @@ export class PropertyGroupTranslatorService {
if (this.isListOfValues(propertyDefinition.constraints)) { if (this.isListOfValues(propertyDefinition.constraints)) {
const options = propertyDefinition.constraints[0].parameters.allowedValues.map((value) => ({ key: value, label: value })); const options = propertyDefinition.constraints[0].parameters.allowedValues.map((value) => ({ key: value, label: value }));
const properties = Object.assign(propertyDefinition, { options$: of(options) }); const properties = Object.assign(propertyDefinition, { options$: of(options) });
cardViewItemProperty = new CardViewSelectItemModel(properties); cardViewItemProperty = new CardViewSelectItemModel(properties);
} else { } else {
@ -132,22 +132,35 @@ export class PropertyGroupTranslatorService {
case D_INT: case D_INT:
case D_LONG: case D_LONG:
cardViewItemProperty = new CardViewIntItemModel(propertyDefinition); cardViewItemProperty = new CardViewIntItemModel(Object.assign(propertyDefinition, {
multivalued: isMultiValued,
pipes: [{ pipe: this.multiValuePipe, params: [this.valueSeparator] }]
}));
break; break;
case D_FLOAT: case D_FLOAT:
case D_DOUBLE: case D_DOUBLE:
cardViewItemProperty = new CardViewFloatItemModel(Object.assign(propertyDefinition, { cardViewItemProperty = new CardViewFloatItemModel(Object.assign(propertyDefinition, {
pipes: [{ pipe: this.decimalNumberPipe }] multivalued: isMultiValued,
pipes: [
{ pipe: this.decimalNumberPipe },
{ pipe: this.multiValuePipe, params: [this.valueSeparator] }
]
})); }));
break; break;
case D_DATE: case D_DATE:
cardViewItemProperty = new CardViewDateItemModel(propertyDefinition); cardViewItemProperty = new CardViewDateItemModel(Object.assign(propertyDefinition, {
multivalued: isMultiValued,
pipes: [{ pipe: this.multiValuePipe, params: [this.valueSeparator] }]
}));
break; break;
case D_DATETIME: case D_DATETIME:
cardViewItemProperty = new CardViewDatetimeItemModel(propertyDefinition); cardViewItemProperty = new CardViewDatetimeItemModel(Object.assign(propertyDefinition, {
multivalued: isMultiValued,
pipes: [{ pipe: this.multiValuePipe, params: [this.valueSeparator] }]
}));
break; break;
case D_BOOLEAN: case D_BOOLEAN:
@ -159,7 +172,7 @@ export class PropertyGroupTranslatorService {
cardViewItemProperty = new CardViewTextItemModel(Object.assign(propertyDefinition, { cardViewItemProperty = new CardViewTextItemModel(Object.assign(propertyDefinition, {
multivalued: isMultiValued, multivalued: isMultiValued,
multiline: isMultiValued, multiline: isMultiValued,
pipes: [{ pipe: this.multiValuePipe, params: [this.valueSeparator]}] pipes: [{ pipe: this.multiValuePipe, params: [this.valueSeparator] }]
})); }));
} }
} }

View File

@ -5,23 +5,23 @@
</div> </div>
<div class="adf-property-value adf-property-value-padding-top"> <div class="adf-property-value adf-property-value-padding-top">
<span *ngIf="!isEditable()" <span *ngIf="!isEditable() && !property.multivalued"
[attr.data-automation-id]="'card-' + property.type + '-value-' + property.key"> [attr.data-automation-id]="'card-' + property.type + '-value-' + property.key">
<span [attr.data-automation-id]="'card-dateitem-' + property.key"> <span *ngIf="showProperty()"
<span *ngIf="showProperty()" [attr.data-automation-id]="'card-dateitem-' + property.key"
(dblclick)="copyToClipboard(property.displayValue)" (dblclick)="copyToClipboard(property.displayValue)"
matTooltipShowDelay="1000" matTooltipShowDelay="1000"
[matTooltip]="'CORE.METADATA.ACTIONS.COPY_TO_CLIPBOARD' | translate">{{ property.displayValue }}</span> [matTooltip]="'CORE.METADATA.ACTIONS.COPY_TO_CLIPBOARD' | translate">{{ property.displayValue}}</span>
</span>
</span> </span>
<div *ngIf="isEditable()" <div *ngIf="isEditable() && !property.multivalued"
class="adf-dateitem-editable"> class="adf-dateitem-editable">
<div class="adf-dateitem-editable-controls"> <div class="adf-dateitem-editable-controls">
<span class="adf-datepicker-toggle" <span class="adf-datepicker-toggle"
[attr.data-automation-id]="'datepicker-label-toggle-' + property.key" [attr.data-automation-id]="'datepicker-label-toggle-' + property.key"
(click)="showDatePicker()"> (click)="showDatePicker()">
<span *ngIf="showProperty(); else elseEmptyValueBlock" <span *ngIf="showProperty(); else elseEmptyValueBlock"
[attr.data-automation-id]="'card-' + property.type + '-value-' + property.key">{{ property.displayValue }}</span> [attr.data-automation-id]="'card-' + property.type + '-value-' + property.key">
{{ property.displayValue }}</span>
</span> </span>
<mat-icon *ngIf="showClearAction()" <mat-icon *ngIf="showClearAction()"
@ -55,4 +55,42 @@
<ng-template #elseEmptyValueBlock> <ng-template #elseEmptyValueBlock>
{{ property.default | translate }} {{ property.default | translate }}
</ng-template> </ng-template>
<div *ngIf="property.multivalued"
class="adf-property-field adf-dateitem-chip-list-container adf-dateitem-editable">
<mat-chip-list #chipList
class="adf-textitem-chip-list">
<mat-chip *ngFor="let propertyValue of property.displayValue let idx = index"
[removable]="isEditable"
(removed)="removeValueFromList(idx)">
{{ propertyValue }}
<mat-icon *ngIf="isEditable()"
matChipRemove>cancel</mat-icon>
</mat-chip>
</mat-chip-list>
<mat-form-field *ngIf="isEditable()"
class="adf-property-field adf-dateitem-editable-controls"
[floatLabel]="'never'"
(click)="showDatePicker()">
<input matInput
class="adf-invisible-date-input"
[attr.tabIndex]="-1"
[matDatetimepicker]="datetimePicker"
(dateChange)="addDateToList($event)">
<mat-datetimepicker-toggle [attr.tabindex]="-1"
matSuffix
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
[attr.data-automation-id]="'datepickertoggle-' + property.key"
[for]="datetimePicker">
</mat-datetimepicker-toggle>
<mat-datetimepicker #datetimePicker
[type]="property.type"
timeInterval="5"
[attr.data-automation-id]="'datepicker-' + property.key"
[startAt]="valueDate">
</mat-datetimepicker>
</mat-form-field>
</div>
</div> </div>

View File

@ -13,6 +13,22 @@
float: right; float: right;
} }
&-dateitem-chip-list-container.adf-property-field {
margin-bottom: -7px !important;
border-bottom: 0;
cursor: pointer;
.adf-dateitem-editable-controls {
margin-top: 15px;
}
.mat-datetimepicker-toggle {
position: absolute;
right: 0;
top: -20px;
}
}
&-dateitem-editable { &-dateitem-editable {
cursor: pointer; cursor: pointer;
border-bottom: 1px solid mat-color($foreground, text, 0.42); border-bottom: 1px solid mat-color($foreground, text, 0.42);
@ -53,6 +69,7 @@
padding-left: 8px; padding-left: 8px;
opacity: 0.3; opacity: 0.3;
} }
&:hover mat-icon.adf-date-reset-icon { &:hover mat-icon.adf-date-reset-icon {
opacity: 1; opacity: 1;
} }

View File

@ -25,6 +25,7 @@ import { CardViewDateItemComponent } from './card-view-dateitem.component';
import { CoreTestingModule } from '../../../testing/core.testing.module'; import { CoreTestingModule } from '../../../testing/core.testing.module';
import { ClipboardService } from '../../../clipboard/clipboard.service'; import { ClipboardService } from '../../../clipboard/clipboard.service';
import { AppConfigService } from '@alfresco/adf-core'; import { AppConfigService } from '@alfresco/adf-core';
import { CardViewDatetimeItemModel } from './../../models/card-view-datetimeitem.model';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
describe('CardViewDateItemComponent', () => { describe('CardViewDateItemComponent', () => {
@ -192,7 +193,7 @@ describe('CardViewDateItemComponent', () => {
component.editable = true; component.editable = true;
component.property.editable = true; component.property.editable = true;
const cardViewUpdateService = TestBed.inject(CardViewUpdateService); const cardViewUpdateService = TestBed.inject(CardViewUpdateService);
const expectedDate = moment('Jul 10 2017', 'MMM DD YY'); const expectedDate = moment('Jul 10 2017', 'MMM DD YYYY');
fixture.detectChanges(); fixture.detectChanges();
const property = { ...component.property }; const property = { ...component.property };
@ -231,7 +232,7 @@ describe('CardViewDateItemComponent', () => {
component.editable = false; component.editable = false;
fixture.detectChanges(); fixture.detectChanges();
const doubleClickEl = fixture.debugElement.query(By.css(`[data-automation-id="card-dateitem-${component.property.key}"] span`)); const doubleClickEl = fixture.debugElement.query(By.css(`[data-automation-id="card-dateitem-${component.property.key}"]`));
doubleClickEl.triggerEventHandler('dblclick', new MouseEvent('dblclick')); doubleClickEl.triggerEventHandler('dblclick', new MouseEvent('dblclick'));
fixture.detectChanges(); fixture.detectChanges();
@ -346,4 +347,42 @@ describe('CardViewDateItemComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(component.property.value).toEqual(expectedDate.toDate()); expect(component.property.value).toEqual(expectedDate.toDate());
}); });
it('should render chips for multivalue dates when chips are enabled', async () => {
component.property = new CardViewDateItemModel({
label: 'Text label',
value: ['Jul 10 2017 00:01:00', 'Jul 11 2017 00:01:00', 'Jul 12 2017 00:01:00'],
key: 'textkey',
editable: true,
multivalued: true
});
fixture.detectChanges();
await fixture.whenStable();
const valueChips = fixture.debugElement.queryAll(By.css(`mat-chip`));
expect(valueChips).not.toBeNull();
expect(valueChips.length).toBe(3);
expect(valueChips[0].nativeElement.innerText.trim()).toBe('Jul 10, 2017');
expect(valueChips[1].nativeElement.innerText.trim()).toBe('Jul 11, 2017');
expect(valueChips[2].nativeElement.innerText.trim()).toBe('Jul 12, 2017');
});
it('should render chips for multivalue datetimes when chips are enabled', async () => {
component.property = new CardViewDatetimeItemModel({
label: 'Text label',
value: ['Jul 10 2017 00:01:00', 'Jul 11 2017 00:01:00', 'Jul 12 2017 00:01:00'],
key: 'textkey',
editable: true,
multivalued: true
});
fixture.detectChanges();
await fixture.whenStable();
const valueChips = fixture.debugElement.queryAll(By.css(`mat-chip`));
expect(valueChips).not.toBeNull();
expect(valueChips.length).toBe(3);
expect(valueChips[0].nativeElement.innerText.trim()).toBe('Jul 10, 2017, 0:01');
expect(valueChips[1].nativeElement.innerText.trim()).toBe('Jul 11, 2017, 0:01');
expect(valueChips[2].nativeElement.innerText.trim()).toBe('Jul 12, 2017, 0:01');
});
}); });

View File

@ -89,6 +89,8 @@ export class CardViewDateItemComponent extends BaseCardView<CardViewDateItemMode
if (this.property.value) { if (this.property.value) {
this.valueDate = moment(this.property.value, this.dateFormat); this.valueDate = moment(this.property.value, this.dateFormat);
} else if (this.property.multivalued && !this.property.value) {
this.property.value = [];
} }
} }
@ -118,8 +120,8 @@ export class CardViewDateItemComponent extends BaseCardView<CardViewDateItemMode
const momentDate = moment(newDateValue.value, this.dateFormat, true); const momentDate = moment(newDateValue.value, this.dateFormat, true);
if (momentDate.isValid()) { if (momentDate.isValid()) {
this.valueDate = momentDate; this.valueDate = momentDate;
this.cardViewUpdateService.update(<CardViewDateItemModel> { ...this.property }, momentDate.toDate());
this.property.value = momentDate.toDate(); this.property.value = momentDate.toDate();
this.update();
} }
} }
} }
@ -135,4 +137,24 @@ export class CardViewDateItemComponent extends BaseCardView<CardViewDateItemMode
const clipboardMessage = this.translateService.instant('CORE.METADATA.ACCESSIBILITY.COPY_TO_CLIPBOARD_MESSAGE'); const clipboardMessage = this.translateService.instant('CORE.METADATA.ACCESSIBILITY.COPY_TO_CLIPBOARD_MESSAGE');
this.clipboardService.copyContentToClipboard(valueToCopy, clipboardMessage); this.clipboardService.copyContentToClipboard(valueToCopy, clipboardMessage);
} }
addDateToList(newDateValue) {
if (newDateValue) {
const momentDate = moment(newDateValue.value, this.dateFormat, true);
if (momentDate.isValid()) {
this.property.value.push(momentDate.toDate());
this.update();
}
}
}
removeValueFromList(itemIndex: number) {
this.property.value.splice(itemIndex, 1);
this.update();
}
update() {
this.cardViewUpdateService.update(<CardViewDateItemModel> { ...this.property }, this.property.value);
}
} }

View File

@ -72,7 +72,7 @@
[floatLabel]="'never'"> [floatLabel]="'never'">
<input matInput <input matInput
class="adf-property-value" class="adf-property-value"
[placeholder]="property.default | translate" [placeholder]="editedValue ? '' : property.default | translate"
[matChipInputFor]="chipList" [matChipInputFor]="chipList"
[matChipInputAddOnBlur]="true" [matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="addValueToList($event)" (matChipInputTokenEnd)="addValueToList($event)"

View File

@ -180,7 +180,48 @@ describe('CardViewTextItemComponent', () => {
expect(valueChips[0].nativeElement.innerText.trim()).toBe('item1'); expect(valueChips[0].nativeElement.innerText.trim()).toBe('item1');
expect(valueChips[1].nativeElement.innerText.trim()).toBe('item2'); expect(valueChips[1].nativeElement.innerText.trim()).toBe('item2');
expect(valueChips[2].nativeElement.innerText.trim()).toBe('item3'); expect(valueChips[2].nativeElement.innerText.trim()).toBe('item3');
});
it('should render chips for multivalue integers when chips are enabled', async () => {
component.property = new CardViewIntItemModel({
label: 'Text label',
value: [1, 2, 3],
key: 'textkey',
editable: true,
multivalued: true
});
component.useChipsForMultiValueProperty = true;
component.ngOnChanges({ property: new SimpleChange(null, null, true) });
fixture.detectChanges();
await fixture.whenStable();
const valueChips = fixture.debugElement.queryAll(By.css(`mat-chip`));
expect(valueChips).not.toBeNull();
expect(valueChips.length).toBe(3);
expect(valueChips[0].nativeElement.innerText.trim()).toBe('1');
expect(valueChips[1].nativeElement.innerText.trim()).toBe('2');
expect(valueChips[2].nativeElement.innerText.trim()).toBe('3');
});
it('should render chips for multivalue decimal numbers when chips are enabled', async () => {
component.property = new CardViewFloatItemModel({
label: 'Text label',
value: [1.1, 2.2, 3.3],
key: 'textkey',
editable: true,
multivalued: true
});
component.useChipsForMultiValueProperty = true;
component.ngOnChanges({ property: new SimpleChange(null, null, true) });
fixture.detectChanges();
await fixture.whenStable();
const valueChips = fixture.debugElement.queryAll(By.css(`mat-chip`));
expect(valueChips).not.toBeNull();
expect(valueChips.length).toBe(3);
expect(valueChips[0].nativeElement.innerText.trim()).toBe('1.1');
expect(valueChips[1].nativeElement.innerText.trim()).toBe('2.2');
expect(valueChips[2].nativeElement.innerText.trim()).toBe('3.3');
}); });
it('should render string for multivalue properties when chips are disabled', async () => { it('should render string for multivalue properties when chips are disabled', async () => {

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { CardViewTextItemModel } from '../../models/card-view-textitem.model'; import { CardViewTextItemModel } from '../../models/card-view-textitem.model';
import { CardViewUpdateService } from '../../services/card-view-update.service'; import { CardViewUpdateService } from '../../services/card-view-update.service';
import { BaseCardView } from '../base-card-view'; import { BaseCardView } from '../base-card-view';
@ -68,7 +68,8 @@ export class CardViewTextItemComponent extends BaseCardView<CardViewTextItemMode
constructor(cardViewUpdateService: CardViewUpdateService, constructor(cardViewUpdateService: CardViewUpdateService,
private clipboardService: ClipboardService, private clipboardService: ClipboardService,
private translateService: TranslationService) { private translateService: TranslationService,
private cd: ChangeDetectorRef) {
super(cardViewUpdateService); super(cardViewUpdateService);
} }
@ -76,7 +77,7 @@ export class CardViewTextItemComponent extends BaseCardView<CardViewTextItemMode
if (changes.property && changes.property.firstChange) { if (changes.property && changes.property.firstChange) {
this.textInput.valueChanges this.textInput.valueChanges
.pipe( .pipe(
filter(textInputValue => textInputValue !== this.editedValue), filter(textInputValue => textInputValue !== this.editedValue && textInputValue !== null),
debounceTime(50), debounceTime(50),
takeUntil(this.onDestroy$) takeUntil(this.onDestroy$)
) )
@ -125,9 +126,8 @@ export class CardViewTextItemComponent extends BaseCardView<CardViewTextItemMode
update(): void { update(): void {
if (this.property.isValid(this.editedValue)) { if (this.property.isValid(this.editedValue)) {
const updatedValue = this.prepareValueForUpload(this.property, this.editedValue); this.property.value = this.prepareValueForUpload(this.property, this.editedValue);
this.cardViewUpdateService.update(<CardViewTextItemModel> { ...this.property }, updatedValue); this.cardViewUpdateService.update(<CardViewTextItemModel> { ...this.property }, this.property.value);
this.property.value = updatedValue;
this.resetErrorMessages(); this.resetErrorMessages();
} else { } else {
this.errors = this.property.getValidationErrors(this.editedValue); this.errors = this.property.getValidationErrors(this.editedValue);
@ -143,9 +143,10 @@ export class CardViewTextItemComponent extends BaseCardView<CardViewTextItemMode
} }
removeValueFromList(itemIndex: number) { removeValueFromList(itemIndex: number) {
if (typeof this.editedValue !== 'string') { if (Array.isArray(this.editedValue)) {
this.editedValue.splice(itemIndex, 1); this.editedValue.splice(itemIndex, 1);
this.update(); this.update();
this.cd.detectChanges();
} }
} }

View File

@ -29,4 +29,5 @@ export interface CardViewItemProperties {
validators?: CardViewItemValidator[]; validators?: CardViewItemValidator[];
data?: any; data?: any;
constraints?: Constraint[]; constraints?: Constraint[];
multivalued?: boolean;
} }

View File

@ -29,6 +29,7 @@ export abstract class CardViewBaseItemModel {
validators?: CardViewItemValidator[]; validators?: CardViewItemValidator[];
data?: any; data?: any;
type?: string; type?: string;
multivalued?: boolean;
constructor(cardViewItemProperties: CardViewItemProperties) { constructor(cardViewItemProperties: CardViewItemProperties) {
this.label = cardViewItemProperties.label || ''; this.label = cardViewItemProperties.label || '';
@ -40,6 +41,7 @@ export abstract class CardViewBaseItemModel {
this.icon = cardViewItemProperties.icon || ''; this.icon = cardViewItemProperties.icon || '';
this.validators = cardViewItemProperties.validators || []; this.validators = cardViewItemProperties.validators || [];
this.data = cardViewItemProperties.data || null; this.data = cardViewItemProperties.data || null;
this.multivalued = !!cardViewItemProperties.multivalued;
if (cardViewItemProperties?.constraints?.length ?? 0) { if (cardViewItemProperties?.constraints?.length ?? 0) {
for (const constraint of cardViewItemProperties.constraints) { for (const constraint of cardViewItemProperties.constraints) {
@ -51,7 +53,7 @@ export abstract class CardViewBaseItemModel {
} }
isEmpty(): boolean { isEmpty(): boolean {
return this.value === undefined || this.value === null || this.value === ''; return this.value === undefined || this.value === null || this.value.length === 0;
} }
isValid(newValue: any): boolean { isValid(newValue: any): boolean {

View File

@ -42,11 +42,19 @@ export class CardViewDateItemModel extends CardViewBaseItemModel implements Card
} }
get displayValue() { get displayValue() {
if (!this.value) { if (this.multivalued) {
return this.default; if (this.value) {
return this.value.map((date) => this.transformDate(date));
} else {
return this.default ? [this.default] : [];
}
} else { } else {
this.localizedDatePipe = new LocalizedDatePipe(); return this.value ? this.transformDate(this.value) : this.default;
return this.localizedDatePipe.transform(this.value, this.format, this.locale);
} }
} }
transformDate(value: any) {
this.localizedDatePipe = new LocalizedDatePipe();
return this.localizedDatePipe.transform(value, this.format, this.locale);
}
} }

View File

@ -29,8 +29,12 @@ export class CardViewFloatItemModel extends CardViewTextItemModel implements Car
super(cardViewTextItemProperties); super(cardViewTextItemProperties);
this.validators.push(new CardViewItemFloatValidator()); this.validators.push(new CardViewItemFloatValidator());
if (cardViewTextItemProperties.value) { if (cardViewTextItemProperties.value && !cardViewTextItemProperties.multivalued) {
this.value = parseFloat(cardViewTextItemProperties.value); this.value = parseFloat(cardViewTextItemProperties.value);
} }
} }
get displayValue(): string {
return this.applyPipes(this.value);
}
} }

View File

@ -29,8 +29,12 @@ export class CardViewIntItemModel extends CardViewTextItemModel implements CardV
super(cardViewTextItemProperties); super(cardViewTextItemProperties);
this.validators.push(new CardViewItemIntValidator()); this.validators.push(new CardViewItemIntValidator());
if (cardViewTextItemProperties.value) { if (cardViewTextItemProperties.value && !cardViewTextItemProperties.multivalued) {
this.value = parseInt(cardViewTextItemProperties.value, 10); this.value = parseInt(cardViewTextItemProperties.value, 10);
} }
} }
get displayValue(): string {
return this.applyPipes(this.value);
}
} }

View File

@ -24,14 +24,12 @@ export class CardViewTextItemModel extends CardViewBaseItemModel implements Card
type: string = 'text'; type: string = 'text';
inputType: string = 'text'; inputType: string = 'text';
multiline?: boolean; multiline?: boolean;
multivalued?: boolean;
pipes?: CardViewTextItemPipeProperty[]; pipes?: CardViewTextItemPipeProperty[];
clickCallBack?: any; clickCallBack?: any;
constructor(cardViewTextItemProperties: CardViewTextItemProperties) { constructor(cardViewTextItemProperties: CardViewTextItemProperties) {
super(cardViewTextItemProperties); super(cardViewTextItemProperties);
this.multiline = !!cardViewTextItemProperties.multiline; this.multiline = !!cardViewTextItemProperties.multiline;
this.multivalued = !!cardViewTextItemProperties.multivalued;
this.pipes = cardViewTextItemProperties.pipes || []; this.pipes = cardViewTextItemProperties.pipes || [];
this.clickCallBack = cardViewTextItemProperties.clickCallBack ? cardViewTextItemProperties.clickCallBack : null; this.clickCallBack = cardViewTextItemProperties.clickCallBack ? cardViewTextItemProperties.clickCallBack : null;
@ -44,7 +42,7 @@ export class CardViewTextItemModel extends CardViewBaseItemModel implements Card
return this.applyPipes(this.value); return this.applyPipes(this.value);
} }
private applyPipes(displayValue) { applyPipes(displayValue) {
if (this.pipes.length) { if (this.pipes.length) {
displayValue = this.pipes.reduce((accumulator, { pipe, params = [] }) => { displayValue = this.pipes.reduce((accumulator, { pipe, params = [] }) => {
return pipe.transform(accumulator, ...params); return pipe.transform(accumulator, ...params);

View File

@ -22,8 +22,13 @@ export class CardViewItemFloatValidator implements CardViewItemValidator {
message = 'CORE.CARDVIEW.VALIDATORS.FLOAT_VALIDATION_ERROR'; message = 'CORE.CARDVIEW.VALIDATORS.FLOAT_VALIDATION_ERROR';
isValid(value: any): boolean { isValid(value: any): boolean {
return value === '' if (Array.isArray(value)) {
|| !isNaN(parseFloat(value)) return value.every(this.isDecimalNumber);
&& isFinite(value); }
return value === '' || this.isDecimalNumber(value);
}
isDecimalNumber(value: any): boolean {
return !isNaN(parseFloat(value)) && isFinite(value);
} }
} }

View File

@ -22,13 +22,15 @@ export class CardViewItemIntValidator implements CardViewItemValidator {
message = 'CORE.CARDVIEW.VALIDATORS.INT_VALIDATION_ERROR'; message = 'CORE.CARDVIEW.VALIDATORS.INT_VALIDATION_ERROR';
isValid(value: any): boolean { isValid(value: any): boolean {
return value === '' if (Array.isArray(value)) {
|| !isNaN(value) return value.every(this.isIntegerNumber);
&& this.isIntegerNumber(value); }
return value === '' || !isNaN(value) && this.isIntegerNumber(value);
} }
isIntegerNumber(value: any): boolean { isIntegerNumber(value: any): boolean {
const parsedNumber = parseFloat(value); const parsedNumber = Number(value);
return (parsedNumber | 0) === parsedNumber; return (parsedNumber | 0) === parsedNumber;
} }
} }

View File

@ -71,8 +71,8 @@ export class DecimalNumberPipe implements PipeTransform, OnDestroy {
const actualDigitsInfo = `${actualMinIntegerDigits}.${actualMinFractionDigits}-${actualMaxFractionDigits}`; const actualDigitsInfo = `${actualMinIntegerDigits}.${actualMinFractionDigits}-${actualMaxFractionDigits}`;
const actualLocale = locale || this.defaultLocale; const actualLocale = locale || this.defaultLocale;
const datePipe: DecimalPipe = new DecimalPipe(actualLocale); const decimalPipe: DecimalPipe = new DecimalPipe(actualLocale);
return datePipe.transform(value, actualDigitsInfo); return decimalPipe.transform(value, actualDigitsInfo);
} }
ngOnDestroy(): void { ngOnDestroy(): void {

View File

@ -36,11 +36,16 @@ describe('FullNamePipe', () => {
pipe = TestBed.inject(MultiValuePipe); pipe = TestBed.inject(MultiValuePipe);
}); });
it('should add the separator when a list is provided', () => { it('should add the separator when a string list is provided', () => {
const values = ['cat', 'house', 'dog']; const values = ['cat', 'house', 'dog'];
expect(pipe.transform(values)).toBe('cat, house, dog'); expect(pipe.transform(values)).toBe('cat, house, dog');
}); });
it('should add the separator when a number list is provided', () => {
const values = [1, 2, 3];
expect(pipe.transform(values)).toBe('1, 2, 3');
});
it('should add custom separator when set', () => { it('should add custom separator when set', () => {
const values = ['cat', 'house', 'dog']; const values = ['cat', 'house', 'dog'];
const customSeparator = ' - '; const customSeparator = ' - ';

View File

@ -22,13 +22,12 @@ export class MultiValuePipe implements PipeTransform {
static DEFAULT_SEPARATOR = ', '; static DEFAULT_SEPARATOR = ', ';
transform(values: string | string [], valueSeparator: string = MultiValuePipe.DEFAULT_SEPARATOR): string { transform(values: any | any[], valueSeparator: string = MultiValuePipe.DEFAULT_SEPARATOR): string {
if (values && values instanceof Array) { if (values && values instanceof Array) {
const valueList = values.map((value) => value.trim()); return values.join(valueSeparator);
return valueList.join(valueSeparator);
} }
return <string> values; return values;
} }
} }

View File

@ -28,7 +28,7 @@ export class TaskHeaderCloudPage {
priorityCardSelectItem = new CardSelectItemPage('priority'); priorityCardSelectItem = new CardSelectItemPage('priority');
dueDateField = element.all(by.css('span[data-automation-id*="dueDate"] span')).first(); dueDateField = element.all(by.css('span[data-automation-id*="dueDate"] span')).first();
categoryCardTextItem = new CardTextItemPage('category'); categoryCardTextItem = new CardTextItemPage('category');
createdField = element(by.css('span[data-automation-id="card-dateitem-created"] span')); createdField = element(by.css('span[data-automation-id="card-dateitem-created"]'));
parentNameCardTextItem = new CardTextItemPage('parentName'); parentNameCardTextItem = new CardTextItemPage('parentName');
parentTaskIdCardTextItem = new CardTextItemPage('parentTaskId'); parentTaskIdCardTextItem = new CardTextItemPage('parentTaskId');
endDateField = element.all(by.css('span[data-automation-id*="endDate"] span')).first(); endDateField = element.all(by.css('span[data-automation-id*="endDate"] span')).first();