Browse Source

Dialog to notify user about unsaved changes when editing content. Closes #34

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
60f8397750
  1. 3
      src/Squidex/app/features/content/module.ts
  2. 22
      src/Squidex/app/features/content/pages/content/content-page.component.html
  3. 40
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  4. 27
      src/Squidex/app/framework/angular/can-deactivate.guard.spec.ts
  5. 21
      src/Squidex/app/framework/angular/can-deactivate.guard.ts
  6. 6
      src/Squidex/app/framework/angular/markdown-editor.component.ts
  7. 6
      src/Squidex/app/framework/angular/rich-editor.component.ts
  8. 18
      src/Squidex/app/framework/angular/stars.component.ts
  9. 3
      src/Squidex/app/framework/declarations.ts
  10. 2
      src/Squidex/app/framework/module.ts

3
src/Squidex/app/features/content/module.ts

@ -9,6 +9,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {
CanDeactivateGuard,
HistoryComponent,
ResolveAppLanguagesGuard,
ResolveContentGuard,
@ -43,6 +44,7 @@ const routes: Routes = [
{
path: 'new',
component: ContentPageComponent,
canDeactivate: [CanDeactivateGuard],
children: [
{
path: 'assets',
@ -52,6 +54,7 @@ const routes: Routes = [
}, {
path: ':contentId',
component: ContentPageComponent,
canDeactivate: [CanDeactivateGuard],
resolve: {
content: ResolveContentGuard
},

22
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -55,4 +55,24 @@
</sqx-panel>
</form>
<router-outlet></router-outlet>
<router-outlet></router-outlet>
<div class="modal" *sqxModalView="cancelDialog" [@fade]>
<div class="modal-backdrop"></div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Unsaved changes</h4>
</div>
<div class="modal-body">
You have unsaved changes, do you want to close the current content view?`
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" (click)="confirmLeave()">Yes</button>
<button type="button" class="btn btn-success" (click)="cancelLeave()">No</button>
</div>
</div>
</div>
</div>

40
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -9,7 +9,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { AbstractControl, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { Observable, Subject, Subscription } from 'rxjs';
import {
ContentCreated,
@ -21,8 +21,11 @@ import {
AppComponentBase,
AppLanguageDto,
AppsStoreService,
CanComponentDeactivate,
ContentDto,
ContentsService,
fadeAnimation,
ModalView,
MessageBus,
NotificationService,
NumberFieldPropertiesDto,
@ -35,14 +38,19 @@ import {
@Component({
selector: 'sqx-content-page',
styleUrls: ['./content-page.component.scss'],
templateUrl: './content-page.component.html'
templateUrl: './content-page.component.html',
animations: [
fadeAnimation
]
})
export class ContentPageComponent extends AppComponentBase implements OnDestroy, OnInit {
export class ContentPageComponent extends AppComponentBase implements CanComponentDeactivate, OnDestroy, OnInit {
private contentDeletedSubscription: Subscription;
private version: Version = new Version('');
private cancelPromise: Subject<boolean> | null = null;
public schema: SchemaDetailsDto;
public cancelDialog = new ModalView();
public contentFormSubmitted = false;
public contentForm: FormGroup;
public contentData: any = null;
@ -91,6 +99,28 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
});
}
public canDeactivate(): Observable<boolean> | Promise<boolean> | boolean {
if (!this.contentForm.dirty) {
return true;
} else {
this.cancelDialog.show();
return this.cancelPromise = new Subject<boolean>();
}
}
public confirmLeave() {
this.cancelDialog.hide();
this.cancelPromise.next(true);
this.cancelPromise = null;
}
public cancelLeave() {
this.cancelDialog.hide();
this.cancelPromise.next(false);
this.cancelPromise = null;
}
public saveAndPublish() {
this.saveContent(true);
}
@ -149,6 +179,8 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
}
private enable() {
this.contentForm.markAsPristine();
for (const field of this.schema.fields.filter(f => !f.isDisabled)) {
const fieldForm = this.contentForm.controls[field.name];
@ -207,6 +239,8 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
}
private populateForm(content: ContentDto) {
this.contentForm.markAsPristine();
if (!content) {
this.contentData = undefined;
this.contentId = undefined;

27
src/Squidex/app/framework/angular/can-deactivate.guard.spec.ts

@ -0,0 +1,27 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { CanDeactivateGuard } from './can-deactivate.guard';
describe('CanDeactivateGuard', () => {
it('should call component', () => {
let called = false;
const component = {
canDeactivate: () => {
called = true;
return true;
}
};
const result = new CanDeactivateGuard().canDeactivate(component);
expect(result).toBeTruthy();
expect(called).toBeTruthy();
});
});

21
src/Squidex/app/framework/angular/can-deactivate.guard.ts

@ -0,0 +1,21 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs/Observable';
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
@Injectable()
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
public canDeactivate(component: CanComponentDeactivate) {
return component.canDeactivate ? component.canDeactivate() : true;
}
}

6
src/Squidex/app/framework/angular/markdown-editor.component.ts

@ -81,7 +81,11 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI
this.simplemde.codemirror.on('change', () => {
const value = this.simplemde.value();
this.changeCallback(value);
if (this.value !== value) {
this.value = value;
this.changeCallback(value);
}
});
this.simplemde.codemirror.on('blur', () => {

6
src/Squidex/app/framework/angular/rich-editor.component.ts

@ -75,7 +75,11 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
self.tinyEditor.on('change', () => {
const value = editor.getContent();
self.changeCallback(value);
if (this.value !== value) {
this.value = value;
self.changeCallback(value);
}
});
self.tinyEditor.on('blur', () => {

18
src/Squidex/app/framework/angular/stars.component.ts

@ -92,11 +92,13 @@ export class StarsComponent implements ControlValueAccessor {
return;
}
this.value = null;
this.stars = 0;
if (this.value !== null) {
this.value = null;
this.stars = 0;
this.changeCallback(this.value);
this.touchedCallback();
this.changeCallback(this.value);
this.touchedCallback();
}
return false;
}
@ -106,10 +108,12 @@ export class StarsComponent implements ControlValueAccessor {
return;
}
this.value = this.stars = value;
if (this.value !== value) {
this.value = this.stars = value;
this.changeCallback(this.value);
this.touchedCallback();
this.changeCallback(this.value);
this.touchedCallback();
}
return false;
}

3
src/Squidex/app/framework/declarations.ts

@ -7,7 +7,7 @@
export * from './angular/animations';
export * from './angular/autocomplete.component';
export * from './angular/validators';
export * from './angular/can-deactivate.guard';
export * from './angular/cloak.directive';
export * from './angular/control-errors.component';
export * from './angular/copy.directive';
@ -38,6 +38,7 @@ export * from './angular/tag-editor.component';
export * from './angular/title.component';
export * from './angular/toggle.component';
export * from './angular/user-report.component';
export * from './angular/validators';
export * from './configurations';
export * from './services/clipboard.service';

2
src/Squidex/app/framework/module.ts

@ -13,6 +13,7 @@ import { RouterModule } from '@angular/router';
import {
AutocompleteComponent,
CanDeactivateGuard,
ClipboardService,
CloakDirective,
ControlErrorsComponent,
@ -153,6 +154,7 @@ export class SqxFrameworkModule {
return {
ngModule: SqxFrameworkModule,
providers: [
CanDeactivateGuard,
ClipboardService,
LocalStoreService,
MessageBus,

Loading…
Cancel
Save