Skip to content
This repository has been archived by the owner on Dec 16, 2024. It is now read-only.

dotCMS/core#21558 Edit Template Properties Modal makes changes get lost #1865

Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<!--Advance template support was removed on commit https://github.com/dotCMS/core-web/pull/589-->
<dot-edit-layout-designer
(save)="onSave($event)"
(updateTemplate)="nextUpdateTemplate($event)"
[didTemplateChanged]="didTemplateChanged"
[layout]="pageState.layout"
[title]="pageState.page.title"
[theme]="pageState.template.theme"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ActivatedRoute } from '@angular/router';
import { By } from '@angular/platform-browser';
import { Component, DebugElement, Input } from '@angular/core';
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';

import { of, throwError } from 'rxjs';
import { HttpCode, ResponseView } from '@dotcms/dotcms-js';
Expand All @@ -27,6 +27,9 @@ import { DotHttpErrorManagerService } from '@services/dot-http-error-manager/dot
template: ''
})
export class DotEditLayoutDesignerComponentMock {
@Input()
didTemplateChanged: boolean;

@Input()
layout: DotLayout;

Expand Down Expand Up @@ -181,6 +184,28 @@ describe('DotEditLayoutComponent', () => {
expect(component.pageState).toEqual(new DotPageRender(mockDotRenderedPage()));
});

it('should save the layout after 10000', fakeAsync(() => {
const res: DotPageRender = new DotPageRender(mockDotRenderedPage());
spyOn(dotPageLayoutService, 'save').and.returnValue(of(res));

layoutDesignerDe.triggerEventHandler('updateTemplate', fakeLayout);

tick(10000);
expect(dotGlobalMessageService.loading).toHaveBeenCalledWith('Saving');
expect(dotGlobalMessageService.success).toHaveBeenCalledWith('Saved');
expect(dotGlobalMessageService.error).not.toHaveBeenCalled();

expect(dotPageLayoutService.save).toHaveBeenCalledWith('123', {
...fakeLayout,
title: null
});
expect(dotTemplateContainersCacheService.set).toHaveBeenCalledWith({
'/default/': processedContainers[0].container,
'/banner/': processedContainers[1].container
});
expect(component.pageState).toEqual(new DotPageRender(mockDotRenderedPage()));
}));

it('should handle error when save fail', (done) => {
spyOn(dotPageLayoutService, 'save').and.returnValue(
throwError(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { pluck, filter, take } from 'rxjs/operators';
import { Component, HostBinding, OnInit } from '@angular/core';
import { pluck, filter, take, debounceTime, switchMap, takeUntil } from 'rxjs/operators';
import { Component, HostBinding, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { DotPageRenderState } from '../../shared/models/dot-rendered-page-state.model';
import { DotRouterService } from '@services/dot-router/dot-router.service';
Expand All @@ -14,16 +14,21 @@ import { DotLayout } from '@models/dot-edit-layout-designer';
import { DotHttpErrorManagerService } from '@services/dot-http-error-manager/dot-http-error-manager.service';
import { DotEditLayoutService } from '@services/dot-edit-layout/dot-edit-layout.service';
import { HttpErrorResponse } from '@angular/common/http';
import { Subject } from 'rxjs';

@Component({
selector: 'dot-edit-layout',
templateUrl: './dot-edit-layout.component.html',
styleUrls: ['./dot-edit-layout.component.scss']
})
export class DotEditLayoutComponent implements OnInit {
export class DotEditLayoutComponent implements OnInit, OnDestroy {
pageState: DotPageRender | DotPageRenderState;
apiLink: string;

didTemplateChanged = false;
updateTemplate = new Subject<DotLayout>();
destroy$: Subject<boolean> = new Subject<boolean>();

@HostBinding('style.minWidth') width = '100%';

constructor(
Expand All @@ -50,9 +55,37 @@ export class DotEditLayoutComponent implements OnInit {
this.templateContainersCacheService.set(mappedContainers);
});

this.updateTemplate
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add doc explaining why this approach and if you can the link where you found this solution.

.pipe(
takeUntil(this.destroy$),
debounceTime(10000),
filter(() => this.didTemplateChanged),
switchMap((layout: DotLayout) => {
this.dotGlobalMessageService.loading(
this.dotMessageService.get('dot.common.message.saving')
);

return this.dotPageLayoutService.save(this.pageState.page.identifier, {
...layout,
title: null
});
})
)
.subscribe(
(updatedPage: DotPageRender) => this.handleSuccessSaveTemplate(updatedPage),
(err: ResponseView) => this.handleErrorSaveTemplate(err),
// On Complete
() => (this.didTemplateChanged = false)
);

this.apiLink = `api/v1/page/render${this.pageState.page.pageURI}?language_id=${this.pageState.page.languageId}`;
}

ngOnDestroy() {
this.destroy$.next(true);
this.destroy$.complete();
}

/**
* Handle cancel in layout event
*
Expand All @@ -76,31 +109,58 @@ export class DotEditLayoutComponent implements OnInit {
);

this.dotPageLayoutService
.save(this.pageState.page.identifier, {
...value,
// To save a layout and no a template the title should be null
title: null
})
// To save a layout and no a template the title should be null
.save(this.pageState.page.identifier, { ...value, title: null })
.pipe(take(1))
.subscribe(
(updatedPage: DotPageRender) => {
const mappedContainers = this.getRemappedContainers(updatedPage.containers);
this.templateContainersCacheService.set(mappedContainers);

this.dotGlobalMessageService.success(
this.dotMessageService.get('dot.common.message.saved')
);
this.pageState = updatedPage;
},
(err: ResponseView) => {
this.dotGlobalMessageService.error(err.response.statusText);
this.dotHttpErrorManagerService.handle( new HttpErrorResponse(err.response) ).subscribe(() => {
this.dotEditLayoutService.changeDesactivateState(true);
});
}
(updatedPage: DotPageRender) => this.handleSuccessSaveTemplate(updatedPage),
(err: ResponseView) => this.handleErrorSaveTemplate(err),
() => (this.didTemplateChanged = false)
);
}

/**
* Handle next template value;
*
* @param {DotLayout} value
* @memberof DotEditLayoutComponent
*/
nextUpdateTemplate(value: DotLayout) {
this.didTemplateChanged = true;
this.updateTemplate.next(value);
}

/**
*
* Handle Success on Save template
* @param {DotPageRender} updatedPage
* @memberof DotEditLayoutComponent
*/
private handleSuccessSaveTemplate(updatedPage: DotPageRender) {
const mappedContainers = this.getRemappedContainers(updatedPage.containers);
this.templateContainersCacheService.set(mappedContainers);

this.dotGlobalMessageService.success(
this.dotMessageService.get('dot.common.message.saved')
);
this.pageState = updatedPage;
}

/**
*
* Handle Error on Save template
* @param {ResponseView} err
* @memberof DotEditLayoutComponent
*/
private handleErrorSaveTemplate(err: ResponseView) {
this.dotGlobalMessageService.error(err.response.statusText);
this.dotHttpErrorManagerService
.handle(new HttpErrorResponse(err.response))
.subscribe(() => {
this.dotEditLayoutService.changeDesactivateState(true);
});
}

private getRemappedContainers(containers: {
[key: string]: {
container: DotContainer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<dot-portlet-base>
<dot-portlet-toolbar [actions]="actions$ | async">
<dot-portlet-toolbar *ngIf="actions" [actions]="actions">
<dot-global-message right></dot-global-message>
</dot-portlet-toolbar>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ import {
ReactiveFormsModule
} from '@angular/forms';

import { of } from 'rxjs';

import { DotMessageService } from '@services/dot-message/dot-messages.service';
import { MockDotMessageService } from '@tests/dot-message-service.mock';
import { DotTemplateStore, EMPTY_TEMPLATE_ADVANCED } from '../store/dot-template.store';
import { DotTemplateAdvancedComponent } from './dot-template-advanced.component';

@Component({
Expand Down Expand Up @@ -118,21 +115,7 @@ describe('DotTemplateAdvancedComponent', () => {
});

beforeEach(() => {
const storeMock = jasmine.createSpyObj(
'DotTemplateStore',
['createTemplate', 'goToTemplateList'],
{
vm$: of({
original: {
...EMPTY_TEMPLATE_ADVANCED,
body: '<h1>Hello</h1>'
}
}),
didTemplateChanged$: of(false)
}
);

TestBed.overrideProvider(DotTemplateStore, { useValue: storeMock });
fixture = TestBed.createComponent(DotTemplateAdvancedComponent);
component = fixture.componentInstance;
de = fixture.debugElement;
Expand Down Expand Up @@ -186,6 +169,14 @@ describe('DotTemplateAdvancedComponent', () => {
});

describe('events', () => {

it('should emit updateTemplate event when the form changes', () => {
const updateTemplate = spyOn(component.updateTemplate, 'emit');
component.form.get('body').setValue('<body></body>');

expect<any>(updateTemplate).toHaveBeenCalledWith({ body: '<body></body>' });
});

it('should have form and fields', () => {
spyOn(Date, 'now').and.returnValue(1111111);
const container = de.query(By.css('dot-container-selector'));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, OnDestroy, OnInit, Output, Input, SimpleChanges, OnChanges } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

import { Observable, Subject } from 'rxjs';
import { filter, map, take, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

import { DotContainer } from '@shared/models/container/dot-container.model';
import { DotTemplateItem, DotTemplateStore } from '../store/dot-template.store';
import { DotTemplateItem } from '../store/dot-template.store';
import { DotPortletToolbarActions } from '@models/dot-portlet-toolbar.model/dot-portlet-toolbar-actions.model';
import { DotMessageService } from '@services/dot-message/dot-messages.service';

Expand All @@ -14,47 +14,39 @@ import { DotMessageService } from '@services/dot-message/dot-messages.service';
templateUrl: './dot-template-advanced.component.html',
styleUrls: ['./dot-template-advanced.scss']
})
export class DotTemplateAdvancedComponent implements OnInit, OnDestroy {
export class DotTemplateAdvancedComponent implements OnInit, OnDestroy, OnChanges {
@Output() updateTemplate = new EventEmitter<DotTemplateItem>();
@Output() save = new EventEmitter<DotTemplateItem>();
@Output() cancel = new EventEmitter();

@Input() body: string;
@Input() didTemplateChanged: boolean;

// `any` because the type of the editor in the ngx-monaco-editor package is not typed
editor: any;
form: FormGroup;
actions$: Observable<DotPortletToolbarActions>;
actions: DotPortletToolbarActions;
private destroy$: Subject<boolean> = new Subject<boolean>();

constructor(
private store: DotTemplateStore,
private fb: FormBuilder,
private dotMessageService: DotMessageService
) {}

ngOnInit(): void {
this.store.vm$.pipe(take(1)).subscribe(({ original }) => {
if (original.type === 'advanced') {
this.form = this.fb.group({
title: original.title,
body: original.body,
identifier: original.identifier,
friendlyName: original.friendlyName
});
}
});
this.form = this.fb.group({ body: this.body });

this.form
.get('body')
.valueChanges.pipe(
takeUntil(this.destroy$),
filter((body: string) => body !== undefined)
)
.subscribe((body: string) => {
this.store.updateBody(body);
});

this.actions$ = this.store.didTemplateChanged$.pipe(
map((templateChange: boolean) => this.getActions(!templateChange))
);
.valueChanges.pipe(takeUntil(this.destroy$))
.subscribe(() => this.updateTemplate.emit(this.form.value));

this.actions = this.getActions(!this.didTemplateChanged);
}

ngOnChanges(changes: SimpleChanges){
if( changes.didTemplateChanged ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does your vscode is not running the formatter on save?

this.actions = this.getActions(!changes.didTemplateChanged.currentValue);
}
}

ngOnDestroy(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
>
<ng-container *ngIf="item.type === 'advanced'; else elseBlock">
<dot-template-advanced
[didTemplateChanged]="didTemplateChanged"
[body]="item.body"
(updateTemplate)="updateTemplate.emit($event)"
(save)="save.emit($event)"
(cancel)="cancel.emit()"
></dot-template-advanced>
</ng-container>
<ng-template #elseBlock>
<dot-edit-layout-designer
[didTemplateChanged]="didTemplateChanged"
[theme]="item.theme"
[layout]="item.layout"
(updateTemplate)="updateTemplate.emit($event)"
(save)="save.emit($event)"
></dot-edit-layout-designer>
</ng-template>
Expand Down
Loading