Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions src/app/pages/model/model.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CardComponent, EmptyStateComponent } from 'src/lib/components';
import { getNgxToastComponent } from 'src/app/utils/NgxToastComponent';
import { ICrudFormEvent, IModelComponentSubmitEvent } from 'src/lib/engine/interfaces';
import { Model } from '@decaf-ts/decorator-validation';
import { CrudEvent } from 'src/lib/engine/types';

/**
* @description Angular component page for CRUD operations on dynamic model entities.
Expand Down Expand Up @@ -134,11 +135,11 @@ export class ModelPage extends NgxModelPageDirective implements OnInit {
// await super.ionViewWillEnter();
// }

override async handleEvent(event: ICrudFormEvent): Promise<void> {
override async handleEvent<M extends Model>(event: ICrudFormEvent): Promise<void> {
const { name } = event;
if (name === ComponentEventNames.Submit) {
const { success, message } = (await super.submit(
event,
event as CrudEvent<M>,
true,
)) as IModelComponentSubmitEvent<Model>;
const toast = getNgxToastComponent();
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/crud-form/crud-form.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
@if (initialized) {
<div class="dcf-buttons-grid dcf-grid dcf-grid-small dcf-flex dcf-flex-right">
@if (!action) {
<div>
<div [class.dcf-hidden]="!showCancelButton">
<ion-button fill="clear" (click)="handleReset()">
@if (options.buttons.clear?.icon) {
<ion-icon
Expand Down
5 changes: 4 additions & 1 deletion src/lib/components/crud-form/crud-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* @link {@link CrudFormComponent}
*/

import { Component, OnInit } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { IonButton, IonIcon } from '@ionic/angular/standalone';
import { TranslatePipe } from '@ngx-translate/core';
Expand All @@ -31,6 +31,9 @@ import { getModelAndRepository } from '../../engine/helpers';
host: { '[attr.id]': 'uid' },
})
export class CrudFormComponent extends NgxFormDirective implements OnInit {
@Input()
showCancelButton = true;

constructor() {
super('CrudFormComponent');
}
Expand Down
75 changes: 39 additions & 36 deletions src/lib/engine/NgxModelPageDirective.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,8 @@
import { Model, Primitives } from '@decaf-ts/decorator-validation';
import { ComponentEventNames } from '@decaf-ts/ui-decorators';
import { NgxPageDirective } from './NgxPageDirective';
import {
IBaseCustomEvent,
ICrudFormEvent,
ILayoutModelContext,
IModelComponentSubmitEvent,
} from './interfaces';
import { DecafRepository, KeyValue } from './types';
import { ICrudFormEvent, ILayoutModelContext, IModelComponentSubmitEvent } from './interfaces';
import { CrudEvent, DecafRepository, KeyValue } from './types';
import { Constructor, Metadata } from '@decaf-ts/decoration';
import { getModelAndRepository } from './helpers';

Expand Down Expand Up @@ -173,9 +168,9 @@
*
* @param {IBaseCustomEvent} event - The event object containing event data and metadata
*/
override async handleEvent(
event: IBaseCustomEvent & ICrudFormEvent & CustomEvent,
repository?: DecafRepository<Model>,
override async handleEvent<M extends Model>(
event: CrudEvent<M>,
repository?: DecafRepository<M>,
): Promise<void> {
const { name, role, handler, data, modelId, handlers } = event;
if (!this.modelId && modelId) {
Expand All @@ -190,7 +185,7 @@
await this.submit(event, true, repository);
break;
default:
this.listenEvent.emit(event as IBaseCustomEvent | ICrudFormEvent);
this.listenEvent.emit(event);
}
}

Expand Down Expand Up @@ -249,7 +244,7 @@
// }

async getTransactionRepository<M extends Model>(
event: ICrudFormEvent,
event: CrudEvent<M>,
repo: DecafRepository<M>,
): Promise<DecafRepository<M>> {
if (!repo) {
Expand All @@ -263,7 +258,7 @@
)) as ILayoutModelContext;
if (context) {
// parse data from main model to event
event.data = context.data;
event.data = context.data as M;
return context.repository as DecafRepository<M>;
}
}
Expand Down Expand Up @@ -315,7 +310,7 @@
const getRepository = async (
modelName: string,
acc: KeyValue = {},
parent: string = '',

Check warning on line 313 in src/lib/engine/NgxModelPageDirective.ts

View workflow job for this annotation

GitHub Actions / coverage (22)

'parent' is assigned a value but never used
): Promise<DecafRepository<Model> | void> => {
if (this._repository) {
return this._repository as DecafRepository<Model>;
Expand Down Expand Up @@ -378,22 +373,24 @@
}

async process<M extends Model>(
event: ICrudFormEvent,
event: CrudEvent<M>,
model?: M,
submit: boolean = false,

Check warning on line 378 in src/lib/engine/NgxModelPageDirective.ts

View workflow job for this annotation

GitHub Actions / coverage (22)

'submit' is assigned a value but never used
): Promise<ILayoutModelContext | IModelComponentSubmitEvent<M>> {
const result = { models: {} } as ILayoutModelContext;
const iterate = async (evt: ICrudFormEvent, model: string | M, parent?: string) => {
const eventData = event.data as KeyValue;
const iterate = async (
evt: ICrudFormEvent & { data: KeyValue },
model: string | M,
parent?: string,
) => {
const constructor = this.getModelConstrutor(model);
if (constructor) {
const properties = Metadata.properties(constructor) as string[];
const promises = properties.map(async (prop) => {
const type = Metadata.type(constructor as Constructor<Model>, prop).name;
let data =
(evt.data as KeyValue)[prop] ||
(parent
? (event.data as KeyValue)[parent as string][prop]
: (event.data as KeyValue)[prop]);
evt.data?.[prop] || (parent ? eventData?.[parent as string][prop] : eventData?.[prop]);
if (data) {
if (parent || Array.isArray(data)) data = [...Object.values(data)];
const context = getModelAndRepository(type) || getModelAndRepository(prop);
Expand All @@ -402,7 +399,9 @@
await iterate(evt, type, prop);
} else {
const { repository, model, pk, pkType } = context;
if (!this.pk) this.pk = pk;
if (!this.pk) {
this.pk = pk;
}
if (!result.context) {
result.context = {
repository,
Expand Down Expand Up @@ -451,28 +450,30 @@
* page, and displays success notifications. Comprehensive error handling ensures robust
* operation with detailed logging.
*
* @param {IBaseCustomEvent} event - The submit event containing form data
* @param {CrudEvent<M>} event - The submit event containing form data
* @return {Promise<IModelComponentSubmitEvent|void>} Promise that resolves on success or throws on error
*/
override async submit<M extends Model>(
event: ICrudFormEvent,
event: CrudEvent<M>,
redirect: boolean = false,
repo?: DecafRepository<M>,
pk?: string,
pk?: keyof M,
): Promise<IModelComponentSubmitEvent<M>> {
let success = false;
let message = '';
let result = null;
try {
const repository = await this.getTransactionRepository(event, repo as DecafRepository<M>);
const repository = await this.getTransactionRepository<M>(event, repo!);
const { data, role } = event;
const operation = (role || this.operation) as CrudOperations;
if (data) {
if (!pk) pk = Model.pk(repository.class) as string;
if (!pk) {
pk = Model.pk(repository.class) as keyof M;
}
if (!this.modelId) {
this.modelId = (data as M)[pk as keyof M] as PrimaryKeyType;
this.modelId = data?.[pk] as PrimaryKeyType;
}
const model = await this.transactionBegin(data as M, repository, operation);
const model = await this.transactionBegin(data, repository, operation);
if (!model) {
return {
success: false,
Expand All @@ -491,7 +492,7 @@
case OperationKeys.UPDATE: {
const models = (!Array.isArray(model) ? [model] : model) as M[];
for (const m of models) {
const uid = m[pk as keyof M];
const uid = m[pk as keyof M] as PrimaryKeyType;
const check = uid ? await repository.read(uid as PrimaryKeyType) : false;
result = await (!check ? repository.create(m as M) : repository.update(m as M));
}
Expand All @@ -503,17 +504,19 @@
: repository.deleteAll(model as []));
break;
}

const pkValue = (model as KeyValue)[pk] || (model as KeyValue)[this.pk] || model || '';
message = await this.translate(`operations.${operation}.${result ? 'success' : 'error'}`, {
'0': repository.class.name || pk,
'1': pkValue,
});
success = result ? true : false;
if (success) {
if ((result as KeyValue)?.[this.pk]) this.modelId = (result as KeyValue)[this.pk];
if (redirect) this.location.back();
if ((result as KeyValue)?.[this.pk]) {
this.modelId = (result as KeyValue)[this.pk];
}
if (redirect) {
this.location.back();
}
}
message = await this.translate(`operations.${operation}.${result ? 'success' : 'error'}`, {
'0': repository.class.name || pk,
'1': this.modelId || '',
});
}
} catch (error: unknown) {
this.log
Expand Down
8 changes: 4 additions & 4 deletions src/lib/engine/NgxRenderableComponentDirective.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
import {
IRenderedModel,
AngularDynamicOutput,
IBaseCustomEvent,

Check warning on line 17 in src/lib/engine/NgxRenderableComponentDirective.ts

View workflow job for this annotation

GitHub Actions / coverage (22)

'IBaseCustomEvent' is defined but never used
ICrudFormEvent,

Check warning on line 18 in src/lib/engine/NgxRenderableComponentDirective.ts

View workflow job for this annotation

GitHub Actions / coverage (22)

'ICrudFormEvent' is defined but never used
} from './interfaces';
import { FormParent, KeyValue } from './types';
import { CrudEvent, FormParent, KeyValue } from './types';
import { NgxRenderingEngine } from './NgxRenderingEngine';
import { shareReplay, takeUntil } from 'rxjs';
import { NgxModelPageDirective } from './NgxModelPageDirective';
import { ModelKeys } from '@decaf-ts/decorator-validation';
import { Model, ModelKeys } from '@decaf-ts/decorator-validation';

@Directive()
export class NgxRenderableComponentDirective
Expand Down Expand Up @@ -156,7 +156,7 @@
* @return {void}
* @memberOf NgxComponentDirective
*/
protected async subscribeEvents(component?: Type<unknown>): Promise<void> {
protected async subscribeEvents<M extends Model>(component?: Type<unknown>): Promise<void> {
if (!component) component = this?.output?.component;
if (!this.instance && component) this.instance = component;
if (this.instance && component) {
Expand All @@ -171,7 +171,7 @@
component: component.name || '',
name: key,
...event,
} as IBaseCustomEvent & ICrudFormEvent & CustomEvent);
} as CrudEvent<M>);
});
}
}
Expand Down
12 changes: 7 additions & 5 deletions src/lib/engine/NgxRepositoryDirective.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { IFilterQuery } from './interfaces';
import { Subject } from 'rxjs';
import { getModelAndRepository } from './helpers';

type TransactionResponse<M extends Model> = M | M[] | PrimaryKeyType | PrimaryKeyType[] | undefined;

@Directive()
export class NgxRepositoryDirective<M extends Model> extends DecafComponent<M> {
private _context?: DecafRepository<M>;
Expand Down Expand Up @@ -350,7 +352,7 @@ export class NgxRepositoryDirective<M extends Model> extends DecafComponent<M> {
model: M,
repository: DecafRepository<M>,
operation: CrudOperations,
): Promise<M | M[] | PrimaryKeyType | undefined> {
): Promise<TransactionResponse<M>> {
try {
const hook = `after${operation.charAt(0).toUpperCase() + operation.slice(1)}`;
const handler = this.handlers?.[hook] || undefined;
Expand Down Expand Up @@ -382,16 +384,16 @@ export class NgxRepositoryDirective<M extends Model> extends DecafComponent<M> {
protected buildTransactionModel<M extends Model>(
data: KeyValue | KeyValue[],
repository: DecafRepository<M>,
operation?: CrudOperations,
): M | M[] | PrimaryKeyType | PrimaryKeyType[] {
operation?: OperationKeys,
): TransactionResponse<M> {
if (!operation) {
operation = this.operation as CrudOperations;
operation = this.operation as OperationKeys;
}
operation = (
[OperationKeys.READ, OperationKeys.DELETE].includes(operation)
? OperationKeys.DELETE
: operation.toLowerCase()
) as CrudOperations;
) as OperationKeys;

if (Array.isArray(data))
return data.map((item) => this.buildTransactionModel(item, repository, operation)) as M[];
Expand Down
14 changes: 12 additions & 2 deletions src/lib/engine/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@
import { IonCheckbox, IonInput, IonSelect, IonTextarea } from '@ionic/angular';
import { TextFieldTypes } from '@ionic/core';
import { FormArray, FormGroup } from '@angular/forms';
import { FormServiceControl, I18nResourceConfig, InputOption } from './interfaces';
import {
FormServiceControl,
I18nResourceConfig,
IBaseCustomEvent,
ICrudFormEvent,
InputOption,
} from './interfaces';
import { Adapter, Repository } from '@decaf-ts/core';
import { Context, RepositoryFlags } from '@decaf-ts/db-decorators';
import { Context, CrudOperations, RepositoryFlags } from '@decaf-ts/db-decorators';
import { ComparisonValidationKeys, Model } from '@decaf-ts/decorator-validation';
import { ActionRoles, ListItemPositions, WindowColorSchemes } from './constants';
import {
Expand Down Expand Up @@ -289,3 +295,7 @@ export type PropsMapperFn<T extends NgxComponentDirective> = Record<
>;

export type DecafComponentConstructor = DecafComponent<Model>;

export type CrudEvent<M extends Model> = IBaseCustomEvent &
ICrudFormEvent &
CustomEvent & { data: M; role?: CrudOperations };
Loading