From 222e0be33b145aceb87cdffa66bf460d134b1642 Mon Sep 17 00:00:00 2001 From: David Lebee Date: Wed, 18 Aug 2021 16:38:26 -0400 Subject: [PATCH] ds-command --- package-lock.json | 6 +- package.json | 2 +- .../ds-command-content.directive.ts | 10 + .../directives/ds-command-error.directive.ts | 10 + .../directives/ds-command-footer.directive.ts | 10 + .../ds-command-no-command.directive.ts | 10 + .../directives/ds-command-submit.directive.ts | 11 ++ .../ds-command-validation.directive.ts | 10 + .../lib/ds-command/ds-command.component.html | 30 +++ .../lib/ds-command/ds-command.component.scss | 0 .../lib/ds-command/ds-command.component.ts | 182 ++++++++++++++++++ .../src/lib/ds-command/ds-command.module.ts | 20 ++ .../poweredsoft/ngx-cdk-ui/src/public-api.ts | 12 +- src/app/app-routing.module.ts | 3 + src/app/app.component.html | 3 + .../ds-command-demo-page.component.html | 60 ++++++ .../ds-command-demo-page.component.scss | 0 .../ds-command-demo-page.component.ts | 81 ++++++++ .../ds-command-demo-routing.module.ts | 17 ++ .../ds-command-demo/ds-command-demo.module.ts | 21 ++ 20 files changed, 493 insertions(+), 5 deletions(-) create mode 100644 projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-content.directive.ts create mode 100644 projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-error.directive.ts create mode 100644 projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-footer.directive.ts create mode 100644 projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-no-command.directive.ts create mode 100644 projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-submit.directive.ts create mode 100644 projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-validation.directive.ts create mode 100644 projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.html create mode 100644 projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.scss create mode 100644 projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.ts create mode 100644 projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.module.ts create mode 100644 src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.html create mode 100644 src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.scss create mode 100644 src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.ts create mode 100644 src/app/ds-command-demo/ds-command-demo-routing.module.ts create mode 100644 src/app/ds-command-demo/ds-command-demo.module.ts diff --git a/package-lock.json b/package-lock.json index a78a483..e7d95cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1684,9 +1684,9 @@ } }, "@poweredsoft/ngx-data": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@poweredsoft/ngx-data/-/ngx-data-0.0.21.tgz", - "integrity": "sha512-+g8plxcrivlnd5VBsHtGiqfXabRlDeYeyQI5lOCZRlfLt+IwIlCUB5kS0Hm3LvWZddyQvhg/stAzaeao0uyt3A==", + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@poweredsoft/ngx-data/-/ngx-data-0.0.22.tgz", + "integrity": "sha512-U4kMKlshTpu2/BpjQ/ULESFbaxJOYUjB6Qoax5rXY/vXErYBdVeQWhv/v4MxMLn8gPltjOzVKpEnQPVAlWZT5g==", "requires": { "tslib": "^1.9.0" } diff --git a/package.json b/package.json index cb21677..4bb7512 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@angular/router": "~9.1.4", "@ng-select/ng-select": "^4.0.1", "@poweredsoft/data": "0.0.30", - "@poweredsoft/ngx-data": "0.0.21", + "@poweredsoft/ngx-data": "0.0.22", "@poweredsoft/ngx-data-apollo": "0.0.10", "apollo-angular": "^1.8.0", "apollo-angular-link-http": "^1.9.0", diff --git a/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-content.directive.ts b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-content.directive.ts new file mode 100644 index 0000000..7f8c13b --- /dev/null +++ b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-content.directive.ts @@ -0,0 +1,10 @@ +import { Directive, TemplateRef } from '@angular/core'; + +@Directive({ + selector: '[psDsCommandContent]' +}) +export class DsCommandContentDirective { + + constructor(public template: TemplateRef) { } + +} diff --git a/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-error.directive.ts b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-error.directive.ts new file mode 100644 index 0000000..3b7f6ba --- /dev/null +++ b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-error.directive.ts @@ -0,0 +1,10 @@ +import { Directive, TemplateRef } from '@angular/core'; + +@Directive({ + selector: '[psDsCommandError]' +}) +export class DsCommandErrorDirective { + + constructor(public template: TemplateRef) { } + +} diff --git a/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-footer.directive.ts b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-footer.directive.ts new file mode 100644 index 0000000..f5dcd07 --- /dev/null +++ b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-footer.directive.ts @@ -0,0 +1,10 @@ +import { Directive, TemplateRef } from '@angular/core'; + +@Directive({ + selector: '[psDsCommandFooter]' +}) +export class DsCommandFooterDirective { + + constructor(public template: TemplateRef) { } + +} diff --git a/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-no-command.directive.ts b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-no-command.directive.ts new file mode 100644 index 0000000..696ec08 --- /dev/null +++ b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-no-command.directive.ts @@ -0,0 +1,10 @@ +import { Directive, TemplateRef } from '@angular/core'; + +@Directive({ + selector: '[psDsCommandNoCommand]' +}) +export class DsCommandNoCommandDirective { + + constructor(public template: TemplateRef) { } + +} diff --git a/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-submit.directive.ts b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-submit.directive.ts new file mode 100644 index 0000000..eac4b84 --- /dev/null +++ b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-submit.directive.ts @@ -0,0 +1,11 @@ +import { Directive, ElementRef, HostListener } from '@angular/core'; + +@Directive({ + selector: '[psDsCommandSubmit]' +}) +export class DsCommandSubmitDirective { + + + constructor(public element: ElementRef) { } + +} diff --git a/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-validation.directive.ts b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-validation.directive.ts new file mode 100644 index 0000000..5633e4b --- /dev/null +++ b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/directives/ds-command-validation.directive.ts @@ -0,0 +1,10 @@ +import { Directive, TemplateRef } from '@angular/core'; + +@Directive({ + selector: '[psDsCommandValidation]' +}) +export class DsCommandValidationDirective { + + constructor(public template: TemplateRef) { } + +} diff --git a/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.html b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.html new file mode 100644 index 0000000..0653c8f --- /dev/null +++ b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.scss b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.ts b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.ts new file mode 100644 index 0000000..dac1eca --- /dev/null +++ b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.component.ts @@ -0,0 +1,182 @@ +import { Component, ContentChild, ContentChildren, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output, QueryList } from '@angular/core'; +import { IDataSource, IDataSourceValidationError } from '@poweredsoft/data'; +import { Subscription } from 'rxjs'; +import { finalize } from 'rxjs/operators'; +import { DsCommandContentDirective } from './directives/ds-command-content.directive'; +import { DsCommandErrorDirective } from './directives/ds-command-error.directive'; +import { DsCommandFooterDirective } from './directives/ds-command-footer.directive'; +import { DsCommandNoCommandDirective } from './directives/ds-command-no-command.directive'; +import { DsCommandSubmitDirective } from './directives/ds-command-submit.directive'; +import { DsCommandValidationDirective } from './directives/ds-command-validation.directive'; + +export interface DsCommandPropertyError +{ + name: string, + errors: string[]; +} + +@Component({ + selector: 'ps-ds-command', + templateUrl: './ds-command.component.html', + styleUrls: ['./ds-command.component.scss'] +}) +export class DsCommandComponent implements OnInit, OnDestroy { + @Input() dataSource: IDataSource; + @Input() name: string; + @Input() model: any; + @Input() refreshOnSuccess: boolean; + @Input() resolveCommand: boolean; + + @Output() success = new EventEmitter(); + @Output() loading = new EventEmitter(); + @Output() commandChange = new EventEmitter(); + + @ContentChild(DsCommandContentDirective) commandContent: DsCommandContentDirective; + @ContentChild(DsCommandFooterDirective) footerContent: DsCommandFooterDirective; + @ContentChild(DsCommandNoCommandDirective) noCommandContent: DsCommandNoCommandDirective; + @ContentChild(DsCommandErrorDirective) errorContent: DsCommandErrorDirective; + @ContentChild(DsCommandValidationDirective) validationDirective: DsCommandValidationDirective; + @ContentChildren(DsCommandSubmitDirective) submitDirectives: QueryList; + + lastErrorMessage: string; + lastValidationResult: DsCommandPropertyError[]; + + protected _command: any = null; + + private _loading = false; + private _validationErrorSubscription: Subscription; + private _notifyMessageSubscription: Subscription; + + + @Input() set command(value: any) { + if (this._command != value) { + this._command = value; + this.commandChange.emit(value); + } + } + + get noCommand() { + return this._command ? true : false; + } + + get command() { + return this._command; + } + + get hasContent() { + return this.commandContent != null && this.commandContent.template != null; + } + + get footerTemplate() { + return this.footerContent.template; + } + + get hasFooterTemplate() { + return this.footerContent?.template != null; + } + + get hasValidationTemplate() { + return this.validationDirective != null && this.validationDirective.template != null; + } + + get validationTemplate() { + return this.validationDirective.template; + } + + get contentTemplate() { + return this.commandContent.template; + } + + constructor() { + + } + + ngOnDestroy() { + this._validationErrorSubscription?.unsubscribe(); + this._notifyMessageSubscription?.unsubscribe(); + } + + ngOnInit(): void { + + this._validationErrorSubscription = this.dataSource + .validationError$.subscribe(validationResult => { + this.lastValidationResult = Object.keys(validationResult.errors).reduce((prev, attr) => { + prev.push({ + name: attr, + errors: validationResult.errors[attr] + }) + return prev; + }, []); + }); + + this._notifyMessageSubscription = this.dataSource.notifyMessage$.subscribe(message => { + if (message.type != 'info') + this.lastErrorMessage = message.message; + }); + + const shouldResolve = this.resolveCommand === undefined ? true : this.resolveCommand; + if (shouldResolve) + this.resolveModel(); + } + + resolveModel() { + this.dataSource.resolveCommandModelByName({ + model: this.model, + command: this.name + }).subscribe( + commandModel => { + this.command = commandModel; + }, + _ => { } + ); + } + + private changeLoadingState(loading: boolean) { + if (loading != this._loading) { + this._loading = loading; + this.loading.emit(loading); + } + } + + @HostListener("click", ["$event"]) + childClicked($event: MouseEvent) { + const element = $event.target; + const found = this.submitDirectives + .toArray() + .find(t => t.element.nativeElement == element); + + if (found != null) + this.execute() + } + + execute() { + + // secure from double send. + if (this._loading) + return; + + this.changeLoadingState(true); + this.lastValidationResult = null; + this.lastErrorMessage = null; + this.dataSource.executeCommandByName(this.name, this._command) + .pipe( + finalize(() => this.changeLoadingState(false)) + ) + .subscribe( + commandResult => { + this.success.emit(commandResult); + if (this.refreshOnSuccess) + this.dataSource.refresh(); + }, + _ => { + // we just catch the error, this way its not uncatched its handled via the other + // subscriptions. + } + ); + } + + get isLoading() { + return this._loading; + } + +} diff --git a/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.module.ts b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.module.ts new file mode 100644 index 0000000..986c484 --- /dev/null +++ b/projects/poweredsoft/ngx-cdk-ui/src/lib/ds-command/ds-command.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { DsCommandComponent } from './ds-command.component'; +import { DsCommandContentDirective } from './directives/ds-command-content.directive'; +import { DsCommandSubmitDirective } from './directives/ds-command-submit.directive'; +import { DsCommandValidationDirective } from './directives/ds-command-validation.directive'; +import { DsCommandFooterDirective } from './directives/ds-command-footer.directive'; +import { DsCommandErrorDirective } from './directives/ds-command-error.directive'; +import { DsCommandNoCommandDirective } from './directives/ds-command-no-command.directive'; + + + +@NgModule({ + declarations: [DsCommandComponent, DsCommandContentDirective, DsCommandSubmitDirective, DsCommandValidationDirective, DsCommandFooterDirective, DsCommandErrorDirective, DsCommandNoCommandDirective], + imports: [ + CommonModule + ], + exports: [DsCommandComponent, DsCommandContentDirective, DsCommandSubmitDirective, DsCommandValidationDirective, DsCommandFooterDirective, DsCommandErrorDirective, DsCommandNoCommandDirective] +}) +export class DsCommandModule { } diff --git a/projects/poweredsoft/ngx-cdk-ui/src/public-api.ts b/projects/poweredsoft/ngx-cdk-ui/src/public-api.ts index 8d86ba6..d13ca84 100644 --- a/projects/poweredsoft/ngx-cdk-ui/src/public-api.ts +++ b/projects/poweredsoft/ngx-cdk-ui/src/public-api.ts @@ -35,4 +35,14 @@ export * from './lib/view/view.module'; export * from './lib/view/view.component'; export * from './lib/view/directives/view-content.directive'; export * from './lib/view/directives/view-loading.directive'; -export * from './lib/view/directives/view-no-records.directive'; \ No newline at end of file +export * from './lib/view/directives/view-no-records.directive'; + +// ds-command +export * from './lib/ds-command/ds-command.module'; +export * from './lib/ds-command/ds-command.component'; +export * from './lib/ds-command/directives/ds-command-content.directive'; +export * from './lib/ds-command/directives/ds-command-submit.directive'; +export * from './lib/ds-command/directives/ds-command-validation.directive'; +export * from './lib/ds-command/directives/ds-command-footer.directive'; +export * from './lib/ds-command/directives/ds-command-no-command.directive'; +export * from './lib/ds-command/directives/ds-command-error.directive'; \ No newline at end of file diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index cf15df7..1411b30 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -19,6 +19,9 @@ const routes: Routes = [ { path: 'list-view', loadChildren: () => import('./list-view-demo/list-view-demo.module').then(m => m.ListViewDemoModule) + },{ + path: 'ds-command', + loadChildren: () => import('./ds-command-demo/ds-command-demo.module').then(m => m.DsCommandDemoModule) }, { path: 'command-modal', diff --git a/src/app/app.component.html b/src/app/app.component.html index 02450bf..374e45a 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -15,6 +15,9 @@ + diff --git a/src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.html b/src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.html new file mode 100644 index 0000000..e7578ee --- /dev/null +++ b/src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.html @@ -0,0 +1,60 @@ + + +
+
+ {{ model.firstName }} {{ model.lastName }} +
+
+ {{ model.number }} + + ,{{model.extension}} + + +
+ +
+ +
+
+ + +
+ + + + +
+ +
+ + + + +
+
+ +
+ + + {{ err }}
+
+
+
+ + + +   + + + + +
+
+
+
+
\ No newline at end of file diff --git a/src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.scss b/src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.ts b/src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.ts new file mode 100644 index 0000000..fd59bd8 --- /dev/null +++ b/src/app/ds-command-demo/ds-command-demo-page/ds-command-demo-page.component.ts @@ -0,0 +1,81 @@ +import { Component, OnInit } from '@angular/core'; +import { IDataSource, IDataSourceValidationError } from '@poweredsoft/data'; +import { HttpDataSourceService } from '@poweredsoft/ngx-data'; +import { of, throwError } from 'rxjs'; + +interface ContactQuery { + contactId: number +} + +interface Contact { + id: number; + firstName: string, + lastName: string, + number: string, + extension: string; +} + +interface ChangeContactPhone { + contactId: number, + number: string, + extension: string +} + +@Component({ + selector: 'ps-ds-command-demo-page', + templateUrl: './ds-command-demo-page.component.html', + styleUrls: ['./ds-command-demo-page.component.scss'] +}) +export class DsCommandDemoPageComponent implements OnInit { + + private contact: Contact = { + id: 1, + firstName: "David", + lastName: "Lebee", + extension: null, + number: "514 448 8444" + }; + + formActivated: boolean = false; + dataSource: IDataSource; + + constructor(private hdss: HttpDataSourceService) { + this.dataSource = this.hdss.singleBuilder() + .keyResolver(m => m.id) + .queryHandler(_ => of(this.contact)) + .addCommandByCallback("changePhone", (command) => { + + if (!command.number || command.number.length == 0) { + return throwError({ + status: 400, + error: { + errors: { + number: ['number is required'] + } + } + }) + } + + return of(command); + }, e => { + return of({ + contactId: e.model.id, + number: e.model.number, + extension: e.model.extension + }); + }) + .createDataSource(); + + } + + ngOnInit(): void { + this.dataSource.refresh(); + } + + onSuccess(command: ChangeContactPhone) { + this.contact.number = command.number; + this.contact.extension = command.extension; + this.formActivated = false; + } + +} diff --git a/src/app/ds-command-demo/ds-command-demo-routing.module.ts b/src/app/ds-command-demo/ds-command-demo-routing.module.ts new file mode 100644 index 0000000..94115ab --- /dev/null +++ b/src/app/ds-command-demo/ds-command-demo-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { DsCommandDemoPageComponent } from './ds-command-demo-page/ds-command-demo-page.component'; + + +const routes: Routes = [ + { + path: '', + component: DsCommandDemoPageComponent + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class DsCommandDemoRoutingModule { } diff --git a/src/app/ds-command-demo/ds-command-demo.module.ts b/src/app/ds-command-demo/ds-command-demo.module.ts new file mode 100644 index 0000000..7a2df2f --- /dev/null +++ b/src/app/ds-command-demo/ds-command-demo.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { DsCommandDemoRoutingModule } from './ds-command-demo-routing.module'; +import { DsCommandDemoPageComponent } from './ds-command-demo-page/ds-command-demo-page.component'; +import { DsCommandModule, DsValidationErrorModule, ViewModule } from '@poweredsoft/ngx-cdk-ui'; +import { FormsModule } from '@angular/forms'; + + +@NgModule({ + declarations: [DsCommandDemoPageComponent], + imports: [ + CommonModule, + DsCommandDemoRoutingModule, + DsCommandModule, + ViewModule, + FormsModule, + DsValidationErrorModule + ] +}) +export class DsCommandDemoModule { }