diff --git a/docs/core/services/card-view-update.service.md b/docs/core/services/card-view-update.service.md index 959b79173a..3529cebaf1 100644 --- a/docs/core/services/card-view-update.service.md +++ b/docs/core/services/card-view-update.service.md @@ -126,6 +126,16 @@ respondToCardClick(cn: ClickNotification) { Note that this function will only be called if the `clickable` property of the model object is set to true for this item. +## Update cardview update item + +[`updateElement`](../../../lib/core/card-view/services/card-view-update.service.ts) function helps to update the card view item. It takes the [`CardViewBaseItemModel`](../../../lib/core/card-view/models/card-view-baseitem.model.ts) type object as parameter. + +Example + +```javascript + this.cardViewUpdateService.updateElement(cardViewBaseItemModel) +``` + ## See also - [Card view component](../components/card-view.component.md) diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts index 1f8825d9c3..52430368e9 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.spec.ts @@ -147,12 +147,10 @@ describe('ContentMetadataComponent', () => { expect(logService.error).toHaveBeenCalledWith(new Error('My bad')); }); - it('should raise error message and reload the properties', (done) => { - spyOn(contentMetadataService, 'getBasicProperties'); + it('should raise error message', (done) => { const property = { key: 'property-key', value: 'original-value' }; const sub = contentMetadataService.error.subscribe((err) => { - expect(contentMetadataService.getBasicProperties).toHaveBeenCalledWith(node); expect(err.statusCode).toBe(0); expect(err.message).toBe('METADATA.ERRORS.GENERIC'); sub.unsubscribe(); diff --git a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts index 9bd1030795..dfdafda85b 100644 --- a/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts +++ b/lib/content-services/src/lib/content-metadata/components/content-metadata/content-metadata.component.ts @@ -94,7 +94,7 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy { switchMap((changes) => this.saveNode(changes).pipe( catchError((err) => { - this.loadProperties(this.node); + this.cardViewUpdateService.updateElement(changes.target); this.handleUpdateError(err); return of(null); }) diff --git a/lib/core/card-view/components/base-card-view.ts b/lib/core/card-view/components/base-card-view.ts new file mode 100644 index 0000000000..31c0c67840 --- /dev/null +++ b/lib/core/card-view/components/base-card-view.ts @@ -0,0 +1,47 @@ +/*! + * @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 { Input, OnDestroy } from '@angular/core'; +import { CardViewUpdateService } from '../services/card-view.services'; +import { CardViewItem } from '../interfaces/card-view.interfaces'; +import { CardViewBaseItemModel } from '../models/card-view-baseitem.model'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +export abstract class BaseCardView implements OnDestroy { + + @Input() + property: T; + + protected destroy$ = new Subject(); + + constructor(protected cardViewUpdateService: CardViewUpdateService) { + this.cardViewUpdateService.updateItem$ + .pipe(takeUntil(this.destroy$)) + .subscribe((itemModel: CardViewBaseItemModel) => { + if (this.property.key === itemModel.key) { + this.property.value = itemModel.value; + } + }); + } + + ngOnDestroy(): void { + this.destroy$.next(true); + this.destroy$.complete(); + } + +} diff --git a/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.ts b/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.ts index 388f52adad..04f82608e8 100644 --- a/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.ts +++ b/lib/core/card-view/components/card-view-arrayitem/card-view-arrayitem.component.ts @@ -15,22 +15,21 @@ * limitations under the License. */ -import { Component, Input } from '@angular/core'; +import { Component } from '@angular/core'; import { CardViewArrayItemModel } from '../../models/card-view-arrayitem.model'; import { CardViewUpdateService } from '../../services/card-view-update.service'; +import { BaseCardView } from '../base-card-view'; @Component({ selector: 'adf-card-view-arrayitem', templateUrl: './card-view-arrayitem.component.html', styleUrls: ['./card-view-arrayitem.component.scss'] }) -export class CardViewArrayItemComponent { +export class CardViewArrayItemComponent extends BaseCardView { - /** The CardViewArrayItemModel of data used to populate the cardView array items. */ - @Input() - property: CardViewArrayItemModel; - - constructor(private cardViewUpdateService: CardViewUpdateService) {} + constructor(cardViewUpdateService: CardViewUpdateService) { + super(cardViewUpdateService); + } clicked(): void { if (this.isClickable()) { diff --git a/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.spec.ts b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.spec.ts index 972a693423..3d87e62072 100644 --- a/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.spec.ts +++ b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.spec.ts @@ -178,10 +178,11 @@ describe('CardViewBoolItemComponent', () => { it('should trigger the update event when changing the checkbox', () => { const cardViewUpdateService = TestBed.get(CardViewUpdateService); spyOn(cardViewUpdateService, 'update'); + const property = { ... component.property }; component.changed( { checked: true }); - expect(cardViewUpdateService.update).toHaveBeenCalledWith(component.property, true); + expect(cardViewUpdateService.update).toHaveBeenCalledWith(property, true); }); it('should update the property value after a changed', async(() => { @@ -198,10 +199,11 @@ describe('CardViewBoolItemComponent', () => { const cardViewUpdateService = TestBed.get(CardViewUpdateService); component.property.value = false; fixture.detectChanges(); + const property = { ...component.property }; const disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe( (updateNotification) => { - expect(updateNotification.target).toBe(component.property); + expect(updateNotification.target).toEqual(property); expect(updateNotification.changed).toEqual({ boolkey: true }); disposableUpdate.unsubscribe(); done(); diff --git a/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.ts b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.ts index 82358b8b28..df0fde85f7 100644 --- a/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.ts +++ b/lib/core/card-view/components/card-view-boolitem/card-view-boolitem.component.ts @@ -19,6 +19,7 @@ import { Component, Input } from '@angular/core'; import { MatCheckboxChange } from '@angular/material'; import { CardViewBoolItemModel } from '../../models/card-view-boolitem.model'; import { CardViewUpdateService } from '../../services/card-view-update.service'; +import { BaseCardView } from '../base-card-view'; @Component({ selector: 'adf-card-view-boolitem', @@ -26,22 +27,21 @@ import { CardViewUpdateService } from '../../services/card-view-update.service'; styleUrls: ['./card-view-boolitem.component.scss'] }) -export class CardViewBoolItemComponent { - - @Input() - property: CardViewBoolItemModel; +export class CardViewBoolItemComponent extends BaseCardView { @Input() editable: boolean; - constructor(private cardViewUpdateService: CardViewUpdateService) {} + constructor(cardViewUpdateService: CardViewUpdateService) { + super(cardViewUpdateService); + } isEditable() { return this.editable && this.property.editable; } changed(change: MatCheckboxChange) { - this.cardViewUpdateService.update(this.property, change.checked ); + this.cardViewUpdateService.update( { ...this.property }, change.checked ); this.property.value = change.checked; } } diff --git a/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.spec.ts b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.spec.ts index 8d0fc64d47..62dbff5d4c 100644 --- a/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.spec.ts +++ b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.spec.ts @@ -189,10 +189,11 @@ describe('CardViewDateItemComponent', () => { const cardViewUpdateService = TestBed.get(CardViewUpdateService); const expectedDate = moment('Jul 10 2017', 'MMM DD YY'); fixture.detectChanges(); + const property = { ...component.property }; const disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe( (updateNotification) => { - expect(updateNotification.target).toBe(component.property); + expect(updateNotification.target).toEqual(property); expect(updateNotification.changed).toEqual({ dateKey: expectedDate.toDate() }); disposableUpdate.unsubscribe(); done(); @@ -287,10 +288,11 @@ describe('CardViewDateItemComponent', () => { component.property.value = 'Jul 10 2017'; fixture.detectChanges(); const cardViewUpdateService = TestBed.get(CardViewUpdateService); + const property = { ...component.property }; const disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe( (updateNotification) => { - expect(updateNotification.target).toBe(component.property); + expect(updateNotification.target).toEqual(property); expect(updateNotification.changed).toEqual({ dateKey: null }); disposableUpdate.unsubscribe(); } diff --git a/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts index d1891bb4c8..c0a5a92654 100644 --- a/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts +++ b/lib/core/card-view/components/card-view-dateitem/card-view-dateitem.component.ts @@ -15,10 +15,10 @@ * limitations under the License. */ -import { Component, Input, OnInit, ViewChild, OnDestroy } from '@angular/core'; +import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material'; -import { MatDatetimepicker, DatetimeAdapter, MAT_DATETIME_FORMATS } from '@mat-datetimepicker/core'; -import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetimepicker/moment'; +import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimepicker } from '@mat-datetimepicker/core'; +import { MAT_MOMENT_DATETIME_FORMATS, MomentDatetimeAdapter } from '@mat-datetimepicker/moment'; import moment from 'moment-es6'; import { Moment } from 'moment'; import { CardViewDateItemModel } from '../../models/card-view-dateitem.model'; @@ -29,6 +29,7 @@ import { MOMENT_DATE_FORMATS } from '../../../utils/moment-date-formats.model'; import { AppConfigService } from '../../../app-config/app-config.service'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { BaseCardView } from '../base-card-view'; @Component({ providers: [ @@ -41,7 +42,7 @@ import { takeUntil } from 'rxjs/operators'; templateUrl: './card-view-dateitem.component.html', styleUrls: ['./card-view-dateitem.component.scss'] }) -export class CardViewDateItemComponent implements OnInit, OnDestroy { +export class CardViewDateItemComponent extends BaseCardView implements OnInit, OnDestroy { @Input() property: CardViewDateItemModel; @@ -63,10 +64,11 @@ export class CardViewDateItemComponent implements OnInit, OnDestroy { private onDestroy$ = new Subject(); - constructor(private cardViewUpdateService: CardViewUpdateService, + constructor(cardViewUpdateService: CardViewUpdateService, private dateAdapter: DateAdapter, private userPreferencesService: UserPreferencesService, private appConfig: AppConfigService) { + super(cardViewUpdateService); this.dateFormat = this.appConfig.get('dateValues.defaultDateFormat'); } @@ -112,7 +114,7 @@ export class CardViewDateItemComponent implements OnInit, OnDestroy { const momentDate = moment(newDateValue.value, this.dateFormat, true); if (momentDate.isValid()) { this.valueDate = momentDate; - this.cardViewUpdateService.update(this.property, momentDate.toDate()); + this.cardViewUpdateService.update( { ...this.property }, momentDate.toDate()); this.property.value = momentDate.toDate(); } } @@ -120,7 +122,7 @@ export class CardViewDateItemComponent implements OnInit, OnDestroy { onDateClear() { this.valueDate = null; - this.cardViewUpdateService.update(this.property, null); + this.cardViewUpdateService.update( { ...this.property }, null); this.property.value = null; this.property.default = null; } diff --git a/lib/core/card-view/components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component.ts b/lib/core/card-view/components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component.ts index 9b8697a407..11f4c6385a 100644 --- a/lib/core/card-view/components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component.ts +++ b/lib/core/card-view/components/card-view-keyvaluepairsitem/card-view-keyvaluepairsitem.component.ts @@ -20,6 +20,7 @@ import { CardViewUpdateService } from '../../services/card-view-update.service'; import { CardViewKeyValuePairsItemModel } from '../../models/card-view.models'; import { CardViewKeyValuePairsItemType } from '../../interfaces/card-view.interfaces'; import { MatTableDataSource } from '@angular/material'; +import { BaseCardView } from '../base-card-view'; @Component({ selector: 'adf-card-view-boolitem', @@ -27,10 +28,7 @@ import { MatTableDataSource } from '@angular/material'; styleUrls: ['./card-view-keyvaluepairsitem.component.scss'] }) -export class CardViewKeyValuePairsItemComponent implements OnChanges { - - @Input() - property: CardViewKeyValuePairsItemModel; +export class CardViewKeyValuePairsItemComponent extends BaseCardView implements OnChanges { @Input() editable: boolean = false; @@ -38,7 +36,9 @@ export class CardViewKeyValuePairsItemComponent implements OnChanges { values: CardViewKeyValuePairsItemType[]; matTableValues: MatTableDataSource; - constructor(private cardViewUpdateService: CardViewUpdateService) {} + constructor(cardViewUpdateService: CardViewUpdateService) { + super(cardViewUpdateService); + } ngOnChanges() { this.values = this.property.value || []; @@ -68,7 +68,7 @@ export class CardViewKeyValuePairsItemComponent implements OnChanges { const validValues = this.values.filter((i) => i.name.length && i.value.length); if (remove || validValues.length) { - this.cardViewUpdateService.update(this.property, validValues); + this.cardViewUpdateService.update( { ...this.property }, validValues); this.property.value = validValues; } } diff --git a/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.ts b/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.ts index 6a547f1c91..3b9cee88e5 100644 --- a/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.ts +++ b/lib/core/card-view/components/card-view-mapitem/card-view-mapitem.component.ts @@ -18,6 +18,7 @@ import { Component, Input } from '@angular/core'; import { CardViewMapItemModel } from '../../models/card-view-mapitem.model'; import { CardViewUpdateService } from '../../services/card-view-update.service'; +import { BaseCardView } from '../base-card-view'; @Component({ selector: 'adf-card-view-mapitem', @@ -25,14 +26,16 @@ import { CardViewUpdateService } from '../../services/card-view-update.service'; styleUrls: ['./card-view-mapitem.component.scss'] }) -export class CardViewMapItemComponent { +export class CardViewMapItemComponent extends BaseCardView { @Input() property: CardViewMapItemModel; @Input() displayEmpty: boolean = true; - constructor(private cardViewUpdateService: CardViewUpdateService) {} + constructor(cardViewUpdateService: CardViewUpdateService) { + super(cardViewUpdateService); + } showProperty() { return this.displayEmpty || !this.property.isEmpty(); diff --git a/lib/core/card-view/components/card-view-selectitem/card-view-selectitem.component.ts b/lib/core/card-view/components/card-view-selectitem/card-view-selectitem.component.ts index f4c374285b..e4c789a156 100644 --- a/lib/core/card-view/components/card-view-selectitem/card-view-selectitem.component.ts +++ b/lib/core/card-view/components/card-view-selectitem/card-view-selectitem.component.ts @@ -21,14 +21,14 @@ import { CardViewUpdateService } from '../../services/card-view-update.service'; import { Observable } from 'rxjs'; import { CardViewSelectItemOption } from '../../interfaces/card-view.interfaces'; import { MatSelectChange } from '@angular/material'; +import { BaseCardView } from '../base-card-view'; @Component({ selector: 'adf-card-view-selectitem', templateUrl: './card-view-selectitem.component.html', styleUrls: ['./card-view-selectitem.component.scss'] }) -export class CardViewSelectItemComponent implements OnChanges { - @Input() property: CardViewSelectItemModel; +export class CardViewSelectItemComponent extends BaseCardView> implements OnChanges { @Input() editable: boolean = false; @@ -39,7 +39,9 @@ export class CardViewSelectItemComponent implements OnChanges { value: string; - constructor(private cardViewUpdateService: CardViewUpdateService) {} + constructor(cardViewUpdateService: CardViewUpdateService) { + super(cardViewUpdateService); + } ngOnChanges(): void { this.value = this.property.value; @@ -55,7 +57,7 @@ export class CardViewSelectItemComponent implements OnChanges { onChange(event: MatSelectChange): void { const selectedOption = event.value !== undefined ? event.value : null; - this.cardViewUpdateService.update(this.property, selectedOption); + this.cardViewUpdateService.update(> { ...this.property }, selectedOption); this.property.value = selectedOption; } diff --git a/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.spec.ts b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.spec.ts index f2f3b63499..a7ce93e1f2 100644 --- a/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.spec.ts +++ b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.spec.ts @@ -265,7 +265,7 @@ describe('CardViewTextItemComponent', () => { fixture.detectChanges(); const disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe((updateNotification) => { - expect(updateNotification.target).toBe(component.property); + expect(updateNotification.target).toEqual({ ...component.property }); expect(updateNotification.changed).toEqual({ textkey: expectedText }); disposableUpdate.unsubscribe(); }); @@ -309,10 +309,11 @@ describe('CardViewTextItemComponent', () => { const cardViewUpdateService = TestBed.get(CardViewUpdateService); spyOn(cardViewUpdateService, 'update'); component.editedValue = 'updated-value'; + const property = { ...component.property }; component.update(event); - expect(cardViewUpdateService.update).toHaveBeenCalledWith(component.property, 'updated-value'); + expect(cardViewUpdateService.update).toHaveBeenCalledWith(property, 'updated-value'); }); it('should NOT trigger the update event if the editedValue is invalid', () => { @@ -384,7 +385,7 @@ describe('CardViewTextItemComponent', () => { const disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe( (updateNotification) => { - expect(updateNotification.target).toBe(component.property); + expect(updateNotification.target).toEqual({ ...component.property }); expect(updateNotification.changed).toEqual({ textkey: expectedText }); disposableUpdate.unsubscribe(); done(); @@ -413,7 +414,7 @@ describe('CardViewTextItemComponent', () => { const disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe( (updateNotification) => { - expect(updateNotification.target).toBe(component.property); + expect(updateNotification.target).toEqual({ ...component.property }); expect(updateNotification.changed).toEqual({ textkey: expectedText }); disposableUpdate.unsubscribe(); } @@ -437,6 +438,24 @@ describe('CardViewTextItemComponent', () => { expect(component.property.value).toBe(expectedText); })); + it('should update the value using the updateItem$ subject', async(() => { + component.inEdit = false; + component.property.isValid = () => true; + const cardViewUpdateService = TestBed.get(CardViewUpdateService); + const expectedText = 'changed text'; + fixture.detectChanges(); + + const textItemReadOnly = fixture.debugElement.query(By.css(`[data-automation-id="card-textitem-value-${component.property.key}"]`)); + expect(textItemReadOnly.nativeElement.textContent).toEqual('Lorem ipsum'); + expect(component.property.value).toBe('Lorem ipsum'); + + cardViewUpdateService.updateElement({ key: component.property.key, value: expectedText }); + fixture.detectChanges(); + + expect(textItemReadOnly.nativeElement.textContent).toEqual(expectedText); + expect(component.property.value).toBe(expectedText); + })); + it('should reset the value using the escape key', async(() => { component.inEdit = false; component.property.isValid = () => true; @@ -488,7 +507,7 @@ describe('CardViewTextItemComponent', () => { fixture.detectChanges(); const disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe((updateNotification) => { - expect(updateNotification.target).toBe(component.property); + expect(updateNotification.target).toEqual({ ...component.property }); expect(updateNotification.changed).toEqual({ textkey: expectedText }); disposableUpdate.unsubscribe(); }); @@ -625,7 +644,7 @@ describe('CardViewTextItemComponent', () => { expect(component.property.value).not.toEqual(expectedNumber); const disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe((updateNotification) => { - expect(updateNotification.target).toBe(component.property); + expect(updateNotification.target).toEqual({ ...component.property }); expect(updateNotification.changed).toEqual({ textkey: expectedNumber.toString() }); disposableUpdate.unsubscribe(); }); @@ -699,7 +718,7 @@ describe('CardViewTextItemComponent', () => { fixture.detectChanges(); const disposableUpdate = cardViewUpdateService.itemUpdated$.subscribe((updateNotification) => { - expect(updateNotification.target).toBe(component.property); + expect(updateNotification.target).toEqual({ ...component.property }); expect(updateNotification.changed).toEqual({ textkey: expectedFloat.toString() }); disposableUpdate.unsubscribe(); }); diff --git a/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts index 34e3ddc3dd..14ffc3b17c 100644 --- a/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts +++ b/lib/core/card-view/components/card-view-textitem/card-view-textitem.component.ts @@ -19,19 +19,17 @@ import { Component, Input, OnChanges, ViewChild } from '@angular/core'; 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'; @Component({ selector: 'adf-card-view-textitem', templateUrl: './card-view-textitem.component.html', styleUrls: ['./card-view-textitem.component.scss'] }) -export class CardViewTextItemComponent implements OnChanges { +export class CardViewTextItemComponent extends BaseCardView implements OnChanges { static DEFAULT_SEPARATOR = ', '; - @Input() - property: CardViewTextItemModel; - @Input() editable: boolean = false; @@ -46,8 +44,9 @@ export class CardViewTextItemComponent implements OnChanges { errorMessages: string[]; valueSeparator: string; - constructor(private cardViewUpdateService: CardViewUpdateService, + constructor(cardViewUpdateService: CardViewUpdateService, private appConfig: AppConfigService) { + super(cardViewUpdateService); this.valueSeparator = this.appConfig.get('content-metadata.multi-value-pipe-separator') || CardViewTextItemComponent.DEFAULT_SEPARATOR; } @@ -105,7 +104,7 @@ export class CardViewTextItemComponent implements OnChanges { if (this.property.isValid(this.editedValue)) { const updatedValue = this.prepareValueForUpload(this.property, this.editedValue); - this.cardViewUpdateService.update(this.property, updatedValue); + this.cardViewUpdateService.update( { ...this.property }, updatedValue); this.property.value = updatedValue; this.setEditMode(false); this.resetErrorMessages(); diff --git a/lib/core/card-view/services/card-view-update.service.ts b/lib/core/card-view/services/card-view-update.service.ts index 04d742ab47..94ae7b7477 100644 --- a/lib/core/card-view/services/card-view-update.service.ts +++ b/lib/core/card-view/services/card-view-update.service.ts @@ -43,6 +43,7 @@ export class CardViewUpdateService { itemUpdated$ = new Subject(); itemClicked$ = new Subject(); + updateItem$ = new Subject(); update(property: CardViewBaseItemModel, newValue: any) { this.itemUpdated$.next({ @@ -56,4 +57,13 @@ export class CardViewUpdateService { target: property }); } + + /** + * Updates the cardview items property + * @param CardViewBaseItemModel + */ + updateElement(notification: CardViewBaseItemModel) { + this.updateItem$.next(notification); + } + } diff --git a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts index d381ce5f1d..f69883a6f6 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.spec.ts @@ -39,7 +39,6 @@ describe('TaskHeaderCloudComponent', () => { let appConfigService: AppConfigService; let taskCloudService: TaskCloudService; let getTaskByIdSpy: jasmine.Spy; - let updateTaskSpy: jasmine.Spy; let getCandidateGroupsSpy: jasmine.Spy; let getCandidateUsersSpy: jasmine.Spy; let isTaskEditableSpy: jasmine.Spy; @@ -64,7 +63,6 @@ describe('TaskHeaderCloudComponent', () => { component.appName = 'mock-app-name'; component.taskId = 'mock-task-id'; getTaskByIdSpy = spyOn(taskCloudService, 'getTaskById').and.returnValue(of(assignedTaskDetailsCloudMock)); - updateTaskSpy = spyOn(taskCloudService, 'updateTask').and.returnValue(of(assignedTaskDetailsCloudMock)); isTaskEditableSpy = spyOn(taskCloudService, 'isTaskEditable').and.returnValue(true); getCandidateUsersSpy = spyOn(taskCloudService, 'getCandidateUsers').and.returnValue(of(mockCandidateUsers)); getCandidateGroupsSpy = spyOn(taskCloudService, 'getCandidateGroups').and.returnValue(of(mockCandidateGroups)); @@ -162,6 +160,7 @@ describe('TaskHeaderCloudComponent', () => { })); it('should be able to call update service on updating task description', async(() => { + spyOn(taskCloudService, 'updateTask').and.returnValue(of(assignedTaskDetailsCloudMock)); fixture.detectChanges(); fixture.whenStable().then(() => { const descriptionEditIcon = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-edit-icon-description"]')); @@ -176,10 +175,36 @@ describe('TaskHeaderCloudComponent', () => { const submitEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-update-description"]')); submitEl.nativeElement.click(); fixture.detectChanges(); - expect(updateTaskSpy).toHaveBeenCalled(); + expect(taskCloudService.updateTask).toHaveBeenCalled(); }); })); - }); + + it('should roll back task description on error', async () => { + spyOn(taskCloudService, 'updateTask').and.returnValue(throwError('fake')); + fixture.detectChanges(); + + await fixture.whenStable(); + let description = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-description"]')); + expect(description.nativeElement.innerText.trim()).toEqual('This is the description'); + + const descriptionEditIcon = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-edit-icon-description"]')); + descriptionEditIcon.nativeElement.click(); + fixture.detectChanges(); + + const inputEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-edittextarea-description"]')); + inputEl.nativeElement.value = 'updated description'; + inputEl.nativeElement.dispatchEvent(new Event('input')); + + const submitEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-update-description"]')); + submitEl.nativeElement.click(); + expect(taskCloudService.updateTask).toHaveBeenCalled(); + + await fixture.whenStable(); + fixture.detectChanges(); + description = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-description"]')); + expect(description.nativeElement.innerText.trim()).toEqual('This is the description'); + }); + }); describe('Task with parentTaskId', () => { diff --git a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts index c90cf50571..923010a4f3 100644 --- a/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-header/components/task-header-cloud.component.ts @@ -16,7 +16,7 @@ */ import { Component, Input, EventEmitter, Output, OnDestroy, OnChanges, OnInit } from '@angular/core'; -import { takeUntil, concatMap } from 'rxjs/operators'; +import { takeUntil, concatMap, catchError } from 'rxjs/operators'; import { Subject, of, forkJoin } from 'rxjs'; import { CardViewDateItemModel, @@ -274,12 +274,16 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy, OnChanges { */ private updateTaskDetails(updateNotification: UpdateNotification) { this.taskCloudService.updateTask(this.appName, this.taskId, updateNotification.changed) - .subscribe( - (taskDetails) => { - this.taskDetails = taskDetails; + .pipe(catchError(() => { + this.cardViewUpdateService.updateElement(updateNotification.target); + return of(null); + })) + .subscribe((taskDetails) => { + if (taskDetails) { + this.taskDetails = taskDetails; + } this.refreshData(); - } - ); + }); } private loadParentName(taskId: string) { diff --git a/lib/process-services/src/lib/task-list/components/task-details.component.ts b/lib/process-services/src/lib/task-list/components/task-details.component.ts index 431c5c6649..e2a9bbba76 100644 --- a/lib/process-services/src/lib/task-list/components/task-details.component.ts +++ b/lib/process-services/src/lib/task-list/components/task-details.component.ts @@ -38,12 +38,12 @@ import { OnDestroy } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { Observable, Observer, Subject } from 'rxjs'; +import { Observable, Observer, of, Subject } from 'rxjs'; import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { TaskDetailsModel } from '../models/task-details.model'; import { TaskListService } from './../services/tasklist.service'; import { UserRepresentation } from '@alfresco/js-api'; -import { share, takeUntil } from 'rxjs/operators'; +import { catchError, share, takeUntil } from 'rxjs/operators'; @Component({ selector: 'adf-task-details', @@ -275,6 +275,10 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { private updateTaskDetails(updateNotification: UpdateNotification) { this.taskListService .updateTask(this.taskId, updateNotification.changed) + .pipe(catchError(() => { + this.cardViewUpdateService.updateElement(updateNotification.target); + return of(null); + })) .subscribe(() => this.loadDetails(this.taskId)); }