mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-4559] Add chips to multivalue metadata properties (#5552)
* [ADF-4559] Add chips to multivalue metadata properties * Fix app config schema * Restore app config * Fix checkListIsSorted method * Fix e2e datatable sorting * Fix e2e tests * Improve chips input
This commit is contained in:
@@ -899,26 +899,6 @@
|
||||
"pattern": "^\\*$",
|
||||
"description": "Wildcard for every aspect"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"description": "",
|
||||
"required": [
|
||||
"includeAll"
|
||||
],
|
||||
"properties": {
|
||||
"includeAll": {
|
||||
"description": "includeAll all property",
|
||||
"type": "boolean"
|
||||
},
|
||||
"postfix": {
|
||||
"description": "exclude",
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/content-metadata-aspect"
|
||||
},
|
||||
@@ -932,6 +912,10 @@
|
||||
"multi-value-pipe-separator": {
|
||||
"description": "Content metadata's separator for multi value properties",
|
||||
"type": "string"
|
||||
},
|
||||
"multi-value-chips": {
|
||||
"description": "Use chips for multi value properties",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -1,95 +1,142 @@
|
||||
<div [attr.data-automation-id]="'card-textitem-label-' + property.key" class="adf-property-label" *ngIf="showProperty() || isEditable()">{{ property.label | translate }}</div>
|
||||
<div [attr.data-automation-id]="'card-textitem-label-' + property.key"
|
||||
class="adf-property-label"
|
||||
*ngIf="showProperty() || isEditable()">{{ property.label | translate }}</div>
|
||||
<div class="adf-property-value">
|
||||
<span *ngIf="!isEditable()">
|
||||
<span *ngIf="!isClickable(); else elseBlock" [attr.data-automation-id]="'card-textitem-value-' + property.key">
|
||||
<span *ngIf="showProperty()"
|
||||
[ngClass]="property.multiline?'adf-textitem-multiline':'adf-textitem-scroll'">
|
||||
{{ property.displayValue }}</span>
|
||||
</span>
|
||||
<ng-template #elseBlock>
|
||||
<div role="button" class="adf-textitem-clickable" [attr.data-automation-id]="'card-textitem-toggle-' + property.key" (click)="clicked()" fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<span class="adf-textitem-clickable-value" [attr.data-automation-id]="'card-textitem-value-' + property.key">
|
||||
<span *ngIf="showProperty(); else elseEmptyValueBlock">{{ property.displayValue }}</span>
|
||||
<span *ngIf="!isClickable(); else nonClickableTemplate"
|
||||
[attr.data-automation-id]="'card-textitem-value-' + property.key">
|
||||
<span *ngIf="!isChipViewEnabled; else chipListTemplate">
|
||||
<span *ngIf="showProperty()"
|
||||
[ngClass]="property.multiline?'adf-textitem-multiline':'adf-textitem-scroll'">
|
||||
{{ property.displayValue }}
|
||||
</span>
|
||||
<button mat-icon-button fxFlex="0 0 auto" *ngIf="showClickableIcon()"
|
||||
class="adf-textitem-action"
|
||||
[attr.aria-label]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.data-automation-id]="'card-textitem-clickable-icon-' + property.key">
|
||||
|
||||
</span>
|
||||
</span>
|
||||
<ng-template #nonClickableTemplate>
|
||||
<div role="button"
|
||||
class="adf-textitem-clickable"
|
||||
[attr.data-automation-id]="'card-textitem-toggle-' + property.key"
|
||||
(click)="clicked()"
|
||||
fxLayout="row"
|
||||
fxLayoutAlign="space-between center">
|
||||
<span class="adf-textitem-clickable-value"
|
||||
[attr.data-automation-id]="'card-textitem-value-' + property.key">
|
||||
<span *ngIf="showProperty(); else emptyValueTemplate">{{ property.displayValue }}</span>
|
||||
</span>
|
||||
<button mat-icon-button
|
||||
fxFlex="0 0 auto"
|
||||
*ngIf="showClickableIcon()"
|
||||
class="adf-textitem-action"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.data-automation-id]="'card-textitem-clickable-icon-' + property.key">
|
||||
<mat-icon class="adf-textitem-icon">{{property?.icon}}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</span>
|
||||
<span *ngIf="isEditable()">
|
||||
<div *ngIf="!inEdit" role="button"
|
||||
tabindex="0"
|
||||
[attr.aria-label]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
(click)="setEditMode(true)"
|
||||
(keydown.enter)="setEditMode(true)"
|
||||
class="adf-textitem-readonly"
|
||||
[attr.data-automation-id]="'card-textitem-toggle-' + property.key"
|
||||
fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<span [attr.data-automation-id]="'card-textitem-value-' + property.key">
|
||||
<span *ngIf="showProperty(); else elseEmptyValueBlock">{{ property.displayValue }}</span>
|
||||
<div *ngIf="!inEdit"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
[attr.aria-label]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
(click)="setEditMode(true)"
|
||||
(keydown.enter)="setEditMode(true)"
|
||||
class="adf-textitem-readonly"
|
||||
[attr.data-automation-id]="'card-textitem-toggle-' + property.key"
|
||||
fxLayout="row"
|
||||
fxLayoutAlign="space-between center">
|
||||
<span *ngIf="!isChipViewEnabled; else chipListTemplate"
|
||||
[attr.data-automation-id]="'card-textitem-value-' + property.key">
|
||||
<span *ngIf="showProperty(); else emptyValueTemplate">{{ property.displayValue }}</span>
|
||||
</span>
|
||||
|
||||
<button mat-icon-button fxFlex="0 0 auto"
|
||||
class="adf-textitem-action"
|
||||
[attr.aria-label]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.data-automation-id]="'card-textitem-edit-icon-' + property.key">
|
||||
<button mat-icon-button
|
||||
fxFlex="0 0 auto"
|
||||
class="adf-textitem-action"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.EDIT' | translate"
|
||||
[attr.data-automation-id]="'card-textitem-edit-icon-' + property.key">
|
||||
|
||||
<mat-icon class="adf-textitem-icon"> create</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="inEdit" class="adf-textitem-editable">
|
||||
<div *ngIf="inEdit"
|
||||
class="adf-textitem-editable">
|
||||
<div class="adf-textitem-editable-controls">
|
||||
<mat-form-field floatPlaceholder="never" class="adf-input-container">
|
||||
<input *ngIf="!property.multiline" #editorInput
|
||||
<mat-form-field floatPlaceholder="never"
|
||||
class="adf-input-container">
|
||||
<input *ngIf="!isChipViewEnabled && !property.multiline"
|
||||
#editorInput
|
||||
(keydown.escape)="reset($event)"
|
||||
(keydown.enter)="update($event)"
|
||||
matInput
|
||||
class="adf-input"
|
||||
[placeholder]="property.default | translate"
|
||||
[(ngModel)]="editedValue"
|
||||
[attr.data-automation-id]="'card-textitem-editinput-' + property.key">
|
||||
<textarea *ngIf="property.multiline" #editorInput
|
||||
matInput
|
||||
matTextareaAutosize
|
||||
matAutosizeMaxRows="1"
|
||||
matAutosizeMaxRows="5"
|
||||
class="adf-textarea"
|
||||
[placeholder]="property.default | translate"
|
||||
[(ngModel)]="editedValue"
|
||||
(input)="onTextAreaInputChange()"
|
||||
[attr.data-automation-id]="'card-textitem-edittextarea-' + property.key"></textarea>
|
||||
matInput
|
||||
class="adf-input"
|
||||
[placeholder]="property.default | translate"
|
||||
[(ngModel)]="editedValue"
|
||||
[attr.data-automation-id]="'card-textitem-editinput-' + property.key">
|
||||
<textarea *ngIf="!isChipViewEnabled && property.multiline"
|
||||
#editorInput
|
||||
matInput
|
||||
matTextareaAutosize
|
||||
matAutosizeMaxRows="1"
|
||||
matAutosizeMaxRows="5"
|
||||
class="adf-textarea"
|
||||
[placeholder]="property.default | translate"
|
||||
[(ngModel)]="editedValue"
|
||||
(input)="onTextAreaInputChange()"
|
||||
[attr.data-automation-id]="'card-textitem-edittextarea-' + property.key"></textarea>
|
||||
<div *ngIf="isChipViewEnabled">
|
||||
<mat-chip-list class="adf-input"
|
||||
#chipList>
|
||||
<mat-chip *ngFor="let propertyValue of editedValue; let idx = index"
|
||||
[removable]="true"
|
||||
(removed)="removeValueFromList(idx)">
|
||||
{{ propertyValue }}
|
||||
<mat-icon matChipRemove>cancel</mat-icon>
|
||||
</mat-chip>
|
||||
<input #editorInput
|
||||
[placeholder]="property.default | translate"
|
||||
[matChipInputFor]="chipList"
|
||||
[matChipInputAddOnBlur]="true"
|
||||
(matChipInputTokenEnd)="addValueToList($event)"
|
||||
[attr.data-automation-id]="'card-textitem-editchipinput-' + property.key">
|
||||
</mat-chip-list>
|
||||
</div>
|
||||
</mat-form-field>
|
||||
<button mat-icon-button class="adf-textitem-action" (click)="update($event)"
|
||||
[attr.aria-label]="'CORE.METADATA.ACTIONS.SAVE' | translate"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
|
||||
[attr.data-automation-id]="'card-textitem-update-' + property.key">
|
||||
|
||||
<button mat-icon-button
|
||||
class="adf-textitem-action"
|
||||
(click)="update($event)"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.SAVE' | translate"
|
||||
[attr.data-automation-id]="'card-textitem-update-' + property.key">
|
||||
<mat-icon class="adf-textitem-icon">done</mat-icon>
|
||||
</button>
|
||||
|
||||
<button mat-icon-button (click)="reset($event)" class="adf-textitem-action"
|
||||
[attr.aria-label]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
|
||||
[attr.data-automation-id]="'card-textitem-reset-' + property.key">
|
||||
<button mat-icon-button
|
||||
(click)="reset($event)"
|
||||
class="adf-textitem-action"
|
||||
[attr.title]="'CORE.METADATA.ACTIONS.CANCEL' | translate"
|
||||
[attr.data-automation-id]="'card-textitem-reset-' + property.key">
|
||||
|
||||
<mat-icon>clear</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<mat-error [attr.data-automation-id]="'card-textitem-error-' + property.key" class="adf-textitem-editable-error" *ngIf="hasErrors()">
|
||||
<mat-error [attr.data-automation-id]="'card-textitem-error-' + property.key"
|
||||
class="adf-textitem-editable-error"
|
||||
*ngIf="hasErrors()">
|
||||
<ul>
|
||||
<li *ngFor="let errorMessage of errorMessages">{{ errorMessage | translate }}</li>
|
||||
</ul>
|
||||
</mat-error>
|
||||
</div>
|
||||
</span>
|
||||
<ng-template #elseEmptyValueBlock>
|
||||
<ng-template #emptyValueTemplate>
|
||||
<span class="adf-textitem-default-value">{{ property.default | translate }}</span>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #chipListTemplate>
|
||||
<mat-chip-list>
|
||||
<mat-chip *ngFor="let propertyValue of editedValue">
|
||||
{{ propertyValue }}
|
||||
</mat-chip>
|
||||
</mat-chip-list>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
@@ -23,6 +23,7 @@ import { CardViewTextItemComponent } from './card-view-textitem.component';
|
||||
import { setupTestBed } from '../../../testing/setup-test-bed';
|
||||
import { CoreTestingModule } from '../../../testing/core.testing.module';
|
||||
import { CardViewItemFloatValidator, CardViewItemIntValidator } from '@alfresco/adf-core';
|
||||
import { MatChipsModule } from '@angular/material';
|
||||
|
||||
describe('CardViewTextItemComponent', () => {
|
||||
|
||||
@@ -31,7 +32,10 @@ describe('CardViewTextItemComponent', () => {
|
||||
const mouseEvent = new MouseEvent('click');
|
||||
|
||||
setupTestBed({
|
||||
imports: [CoreTestingModule]
|
||||
imports: [
|
||||
CoreTestingModule,
|
||||
MatChipsModule
|
||||
]
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -146,6 +150,48 @@ describe('CardViewTextItemComponent', () => {
|
||||
const editIcon = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-edit-icon-${component.property.key}"]`));
|
||||
expect(editIcon).toBeNull('Edit icon should NOT be shown');
|
||||
});
|
||||
|
||||
it('should render chips for multivalue properties when chips are enabled', () => {
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: ['item1', 'item2', 'item3'],
|
||||
key: 'textkey',
|
||||
default: ['FAKE-DEFAULT-KEY'],
|
||||
editable: true,
|
||||
multivalued: true
|
||||
});
|
||||
component.useChipsForMultiValueProperty = true;
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
|
||||
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('item1');
|
||||
expect(valueChips[1].nativeElement.innerText.trim()).toBe('item2');
|
||||
expect(valueChips[2].nativeElement.innerText.trim()).toBe('item3');
|
||||
});
|
||||
|
||||
it('should render string for multivalue properties when chips are disabled', () => {
|
||||
component.property = new CardViewTextItemModel({
|
||||
label: 'Text label',
|
||||
value: ['item1', 'item2', 'item3'],
|
||||
key: 'textkey',
|
||||
default: ['FAKE-DEFAULT-KEY'],
|
||||
editable: true,
|
||||
multivalued: true
|
||||
});
|
||||
|
||||
component.useChipsForMultiValueProperty = false;
|
||||
component.ngOnChanges();
|
||||
fixture.detectChanges();
|
||||
|
||||
const valueChips = fixture.debugElement.query(By.css(`mat-chip-list`));
|
||||
const value = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`));
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.nativeElement.innerText.trim()).toBe('item1,item2,item3');
|
||||
expect(valueChips).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('clickable', () => {
|
||||
|
@@ -20,6 +20,7 @@ import { CardViewTextItemModel } from '../../models/card-view-textitem.model';
|
||||
import { CardViewUpdateService } from '../../services/card-view-update.service';
|
||||
import { AppConfigService } from '../../../app-config/app-config.service';
|
||||
import { BaseCardView } from '../base-card-view';
|
||||
import { MatChipInputEvent } from '@angular/material';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-card-view-textitem',
|
||||
@@ -29,6 +30,7 @@ import { BaseCardView } from '../base-card-view';
|
||||
export class CardViewTextItemComponent extends BaseCardView<CardViewTextItemModel> implements OnChanges {
|
||||
|
||||
static DEFAULT_SEPARATOR = ', ';
|
||||
static DEFAULT_USE_CHIPS = false;
|
||||
|
||||
@Input()
|
||||
editable: boolean = false;
|
||||
@@ -40,18 +42,20 @@ export class CardViewTextItemComponent extends BaseCardView<CardViewTextItemMode
|
||||
private editorInput: any;
|
||||
|
||||
inEdit: boolean = false;
|
||||
editedValue: string;
|
||||
editedValue: string | string[];
|
||||
errorMessages: string[];
|
||||
valueSeparator: string;
|
||||
useChipsForMultiValueProperty: boolean;
|
||||
|
||||
constructor(cardViewUpdateService: CardViewUpdateService,
|
||||
private appConfig: AppConfigService) {
|
||||
super(cardViewUpdateService);
|
||||
this.valueSeparator = this.appConfig.get<string>('content-metadata.multi-value-pipe-separator') || CardViewTextItemComponent.DEFAULT_SEPARATOR;
|
||||
this.useChipsForMultiValueProperty = this.appConfig.get<boolean>('content-metadata.multi-value-chips') || CardViewTextItemComponent.DEFAULT_USE_CHIPS;
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.editedValue = this.property.multiline ? this.property.displayValue : this.property.value;
|
||||
this.resetValue();
|
||||
}
|
||||
|
||||
showProperty(): boolean {
|
||||
@@ -87,19 +91,27 @@ export class CardViewTextItemComponent extends BaseCardView<CardViewTextItemMode
|
||||
}, 0);
|
||||
}
|
||||
|
||||
reset(event: MouseEvent|KeyboardEvent): void {
|
||||
reset(event: Event): void {
|
||||
event.stopPropagation();
|
||||
|
||||
this.editedValue = this.property.multiline ? this.property.displayValue : this.property.value;
|
||||
this.resetValue();
|
||||
this.setEditMode(false);
|
||||
this.resetErrorMessages();
|
||||
}
|
||||
|
||||
resetValue() {
|
||||
if (this.isChipViewEnabled) {
|
||||
this.editedValue = this.property.value ? Array.from(this.property.value) : [];
|
||||
} else {
|
||||
this.editedValue = this.property.multiline ? this.property.displayValue : this.property.value;
|
||||
}
|
||||
}
|
||||
|
||||
private resetErrorMessages() {
|
||||
this.errorMessages = [];
|
||||
}
|
||||
|
||||
update(event: MouseEvent|KeyboardEvent): void {
|
||||
update(event: Event): void {
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.property.isValid(this.editedValue)) {
|
||||
@@ -113,14 +125,35 @@ export class CardViewTextItemComponent extends BaseCardView<CardViewTextItemMode
|
||||
}
|
||||
}
|
||||
|
||||
prepareValueForUpload(property: CardViewTextItemModel, value: string): string | string [] {
|
||||
if (property.multivalued) {
|
||||
prepareValueForUpload(property: CardViewTextItemModel, value: string | string[]): string | string[] {
|
||||
if (property.multivalued && typeof value === 'string') {
|
||||
const listOfValues = value.split(this.valueSeparator.trim()).map((item) => item.trim());
|
||||
return listOfValues;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
removeValueFromList(itemIndex: number) {
|
||||
if (typeof this.editedValue !== 'string') {
|
||||
this.editedValue.splice(itemIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
addValueToList(newListItem: MatChipInputEvent) {
|
||||
const chipInput = newListItem.input;
|
||||
const chipValue = newListItem.value.trim() || '';
|
||||
|
||||
if (typeof this.editedValue !== 'string') {
|
||||
if (chipValue) {
|
||||
this.editedValue.push(chipValue);
|
||||
}
|
||||
|
||||
if (chipInput) {
|
||||
chipInput.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTextAreaInputChange() {
|
||||
this.errorMessages = this.property.getValidationErrors(this.editedValue);
|
||||
}
|
||||
@@ -132,4 +165,8 @@ export class CardViewTextItemComponent extends BaseCardView<CardViewTextItemMode
|
||||
this.cardViewUpdateService.clicked(this.property);
|
||||
}
|
||||
}
|
||||
|
||||
get isChipViewEnabled(): boolean {
|
||||
return this.property.multivalued && this.useChipsForMultiValueProperty;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user