ngx-techlify-core 22.3.0 → 22.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{ngx-techlify-core-base-model-oCILHkdE.mjs → ngx-techlify-core-base-model-DfODc3ll.mjs} +2 -2
- package/fesm2022/{ngx-techlify-core-base-model-oCILHkdE.mjs.map → ngx-techlify-core-base-model-DfODc3ll.mjs.map} +1 -1
- package/fesm2022/{ngx-techlify-core-shared.module-DROe5GRx.mjs → ngx-techlify-core-shared.module-C0uAKgWi.mjs} +108 -4
- package/fesm2022/ngx-techlify-core-shared.module-C0uAKgWi.mjs.map +1 -0
- package/fesm2022/{ngx-techlify-core-user-config.service-DW5EgXhL.mjs → ngx-techlify-core-user-config.service-BTZUoFIT.mjs} +2 -2
- package/fesm2022/{ngx-techlify-core-user-config.service-DW5EgXhL.mjs.map → ngx-techlify-core-user-config.service-BTZUoFIT.mjs.map} +1 -1
- package/fesm2022/{ngx-techlify-core-user-sessions.module-BRmDpYG3.mjs → ngx-techlify-core-user-sessions.module-gmNNGpCf.mjs} +4 -4
- package/fesm2022/{ngx-techlify-core-user-sessions.module-BRmDpYG3.mjs.map → ngx-techlify-core-user-sessions.module-gmNNGpCf.mjs.map} +1 -1
- package/fesm2022/ngx-techlify-core.mjs +46 -11
- package/fesm2022/ngx-techlify-core.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ngx-techlify-core.d.ts +33 -0
- package/fesm2022/ngx-techlify-core-shared.module-DROe5GRx.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { J as EnvironmentService, X as AlertService, Y as FormValidatorService } from "./ngx-techlify-core-shared.module-
|
|
1
|
+
import { J as EnvironmentService, X as AlertService, Y as FormValidatorService } from "./ngx-techlify-core-shared.module-C0uAKgWi.mjs";
|
|
2
2
|
import * as i0 from "@angular/core";
|
|
3
3
|
import { Directive, ElementRef, EventEmitter, HostAttributeToken, Injectable, Input, Optional, Output, inject } from "@angular/core";
|
|
4
4
|
import { finalize, fromEvent, merge, of } from "rxjs";
|
|
@@ -674,4 +674,4 @@ function techlifyShowContract(options) {
|
|
|
674
674
|
}
|
|
675
675
|
export { RequestHelperService as a, TechlifyFormComponent as c, TechlifyListComponent as i, TechlifyService as l, TechlifyShowComponent as n, parseLocalDate as o, techlifyListContract as r, techlifyFormContract as s, techlifyShowContract as t };
|
|
676
676
|
|
|
677
|
-
//# sourceMappingURL=ngx-techlify-core-base-model-
|
|
677
|
+
//# sourceMappingURL=ngx-techlify-core-base-model-DfODc3ll.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngx-techlify-core-base-model-oCILHkdE.mjs","names":["i1.FormValidatorService","i2.TechlifyService","debounceTime","map","startWith","distinctUntilChanged","finalize","i1.TechlifyService","i1.TechlifyService"],"sources":["../../../projects/ngx-techlify-core/src/lib/base-model/techlify-service.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-form/techlify-form.component.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-form/contract/techlify-form.contract.ts","../../../projects/ngx-techlify-core/src/lib/local-date-parser.ts","../../../projects/ngx-techlify-core/src/lib/request-helper/request-helper.service.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-list/techlify-list.component.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-list/contract/techlify-list.contract.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-show/techlify-show.component.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-show/contract/techlify-show.contract.ts"],"sourcesContent":["import { HttpService } from '../core';\nimport { Observable } from 'rxjs';\n\n/**\n * Interface used to ensure we have a consistent structure across our listing pages\n *\n * Advantages of this abstract class\n * - Less code, less maintenance, less bugs\n * - more consistent code\n * - comments on more code\n * - save time, save energy\n */\nexport class TechlifyService {\n\n protected baseUrl = 'you-forgot-to-set-a-base-url';\n\n constructor(protected http: HttpService, baseUrl: string) {\n this.baseUrl = baseUrl;\n }\n\n /**\n * Loads a set of the model this service is for\n *\n * @param params Query parameters\n * @return Observable<any>\n */\n index(params?: any, isCache?: boolean): Observable<any> {\n if (isCache) {\n return this.http.cache().get(`api/${this.baseUrl}`, { params });\n }\n\n return this.http.get(`api/${this.baseUrl}`, { params });\n }\n\n /**\n * Add a new record to the database\n *\n * @param model The data of the model to add\n * @param params Query parameters\n * @return Observable<any>\n */\n store(model: any, params?: any): Observable<any> {\n return this.http.post(`api/${this.baseUrl}`, model, { params });\n }\n\n /**\n * Load a single model record, often with all relations and related data\n *\n * @param modelId ID of the model to load\n * @param params Query parameters\n * @return Observable<any>\n */\n show(modelId: any, params?: any): Observable<any> {\n return this.http.get(`api/${this.baseUrl}/${modelId}`, { params });\n }\n\n /**\n * Update fields of a single existing model\n *\n * @param model Model to update\n * @param params Query parameters\n * @return Observable<any>\n */\n update(model: any, params?: any): Observable<any> {\n return this.http.put(`api/${this.baseUrl}/${model.id}`, model, { params });\n }\n\n /**\n * Delete a single model\n *\n * @param model Model to delete\n * @return Observable<any>\n */\n delete(model: any): Observable<any> {\n return this.http.delete(`api/${this.baseUrl}/${model.id}`);\n }\n\n\n}\n","import { UntypedFormGroup } from '@angular/forms';\nimport { FormValidatorService } from '../../core/form-validator.service';\nimport { Directive, EventEmitter, inject, Input, Optional, Output } from '@angular/core';\nimport { TechlifyService } from '../techlify-service';\nimport { finalize } from 'rxjs';\nimport { MatDialogRef } from '@angular/material/dialog';\nimport { AlertService } from '../../core/alert.service';\n\n/**\n * Interface used to ensure we have a consistent structure across our form components\n *\n * It also provides some abstraction of repeated code to reduce our codebase size\n *\n * @todo This should be moved to ngx-techlify-core\n *\n * Advantages of this abstract class\n * - Less code, less maintenance, less bugs\n * - more consistent code\n * - comments on more code\n * - save time, save energy\n */\n@Directive()\nexport abstract class TechlifyFormComponent {\n\n //Form builder instance\n form: UntypedFormGroup;\n\n //Error messages config for the form\n errorMessages: any;\n\n //Boolean to denote the form is in API progress\n isWorking: boolean = false;\n\n //Form dialogref reference\n dialogRef: MatDialogRef<any, any>;\n\n //Array of created multiple entries\n createdEntries: any = [];\n\n //Boolean to denote if form is in update mode\n @Input() isUpdate: boolean = false;\n\n //Boolean to denote if form has ability to create multiple entries\n @Input() isAllowCreateAnother: boolean = true;\n\n //Emits an event once form submit is success\n @Output() success = new EventEmitter<any>();\n\n //Injected alert service\n protected alertService = inject(AlertService, { optional: true });\n\n constructor(\n protected formValidatorService: FormValidatorService,\n @Optional() protected service?: TechlifyService) {\n\n }\n /**\n * Submits the form\n * @param model form model optional, if needed to be overriden\n * @returns \n */\n submit(model?: any) {\n this.form.markAllAsTouched();\n if (!this.service) {\n throw new Error(\n `${this.constructor.name}: Service is required to submit the form`\n );\n }\n if (!this.form.valid) {\n return;\n }\n if (!model) {\n model = this.form.value;\n }\n this.isWorking = true;\n (\n this.isUpdate ?\n this.service.update(model) :\n this.service.store(model)\n )\n .pipe(\n finalize(() => this.isWorking = false),\n )\n .subscribe((res: any) => {\n this.createdEntries.unshift(res?.item);\n if (model?.is_create_another) {\n this.alertService.addAlert('Successfully Created', 'success');\n return;\n }\n if (this.isUpdate) {\n this.finalisedResponse(res?.item);\n } else {\n this.finalisedResponse((this.createdEntries.length == 1) ? this.createdEntries[0] : this.createdEntries);\n }\n\n });\n }\n\n //Send uniformed finalised response for both form button and dialog\n finalisedResponse(response: any) {\n this.success.emit(response);\n if (this.dialogRef) {\n this.dialogRef.close(response);\n }\n }\n\n /**\n * Close the active dialog\n */\n closeDialog() {\n if (this.createdEntries?.length > 0) {\n this.success.emit(this.createdEntries);\n }\n if (this.dialogRef) {\n this.dialogRef.close();\n }\n }\n\n /**\n * Resets the value of a field in the HTML form\n *\n * @param event\n * @param field\n */\n resetFieldValue(event: any, field: string) {\n this.form?.get(field)?.setValue('');\n }\n\n /**\n * Retrieves the value of a field from the HTML form\n *\n * @param field\n * @returns\n */\n getFieldValue(field: string) {\n return this.form?.get(field)?.value;\n }\n\n /**\n * Method to evaluate form fields\n * */\n isFieldValid(field: string) {\n return this.formValidatorService.isFieldValid(field, this.form);\n }\n\n /**\n * Method to find error in form fields\n * */\n getErrorMessage(field: string) {\n return this.formValidatorService.getErrorMessage(field, this.form, this.errorMessages);\n }\n\n}\n","import { of } from 'rxjs';\nimport { TechlifyFormComponent } from '../techlify-form.component';\n\nexport interface TechlifyFormContractOptions {\n getComponent: () => TechlifyFormComponent;\n getService: () => any;\n /**\n * Form control used by the submit and field helper tests. Defaults to `name`.\n */\n fieldName?: string;\n /**\n * Set when the component overrides `submit()` with its own flow; the tests for the\n * base submit flow (store/update, markAllAsTouched, isWorking) are then not registered.\n */\n hasCustomSubmit?: boolean;\n}\n\n/**\n * Reusable test contract for components extending TechlifyFormComponent.\n *\n * Ensures all form components follow the same base behaviour:\n * - Service injection\n * - Default state initialization\n * - Create/update submission flow\n * - Form validation handling\n * - Success event handling\n * - Dialog handling\n * - Field helper methods\n */\nexport function techlifyFormContract(\n options: TechlifyFormContractOptions\n): void {\n\n const fieldName = options.fieldName ?? 'name';\n // Base submit flow tests only apply to components using TechlifyFormComponent.submit()\n const baseSubmitIt = options.hasCustomSubmit ? (() => undefined) as unknown as typeof it : it;\n\n describe('TechlifyFormComponent Contract', () => {\n\n /**\n * Ensures child components correctly pass their service\n * to the base form component.\n */\n it('should pass a service to the base class', () => {\n const component = options.getComponent();\n expect((component as any).service)\n .toBe(options.getService());\n });\n\n /**\n * Ensures default properties are initialized correctly.\n */\n it('should initialize default values', () => {\n const component = options.getComponent();\n expect(component.isWorking).toBeFalse();\n expect(component.isUpdate).toBeFalse();\n expect(component.isAllowCreateAnother).toBeTrue();\n expect(component.createdEntries).toEqual([]);\n });\n\n /**\n * Ensures create forms call store service and finalise response.\n */\n baseSubmitIt('should submit using store when creating', () => {\n const component = options.getComponent();\n component.form.patchValue({\n [fieldName]: 'Test'\n });\n spyOn(component as any, 'finalisedResponse');\n options.getService().store\n .and.returnValue(of({\n item: { id: 1 }\n }));\n component.submit();\n expect(options.getService().store)\n .toHaveBeenCalledWith(component.form.value);\n expect(component.createdEntries)\n .toEqual([{ id: 1 }]);\n expect(component.finalisedResponse)\n .toHaveBeenCalled();\n });\n\n /**\n * Ensures update forms call update service instead of store.\n */\n baseSubmitIt('should submit using update when isUpdate is true', () => {\n const component = options.getComponent();\n component.isUpdate = true;\n component.form.patchValue({\n [fieldName]: 'Updated'\n });\n spyOn(component as any, 'finalisedResponse');\n options.getService().update\n .and.returnValue(of({\n item: { id: 1 }\n }));\n component.submit();\n expect(options.getService().update)\n .toHaveBeenCalledWith(component.form.value);\n expect(component.finalisedResponse)\n .toHaveBeenCalledWith({\n id: 1\n });\n });\n\n /**\n * Ensures invalid forms do not trigger API submission.\n */\n it('should not submit invalid form', () => {\n const component = options.getComponent();\n component.form.setErrors({\n invalid: true\n });\n component.submit();\n expect(options.getService().store)\n .not.toHaveBeenCalled();\n });\n\n /**\n * Ensures form validation is triggered before submission.\n */\n baseSubmitIt('should mark form as touched before submit', () => {\n const component = options.getComponent();\n spyOn(component.form, 'markAllAsTouched');\n component.submit();\n expect(component.form.markAllAsTouched)\n .toHaveBeenCalled();\n });\n\n /**\n * Ensures API progress state is handled correctly.\n */\n baseSubmitIt('should set isWorking while submitting', () => {\n const component = options.getComponent();\n component.form.patchValue({\n [fieldName]: 'Test'\n });\n options.getService().store\n .and.returnValue(of({\n item: { id: 1 }\n }));\n component.submit();\n expect(component.isWorking)\n .toBeFalse();\n });\n\n /**\n * Ensures successful form completion emits success event.\n */\n it('should emit success event on finalisedResponse', () => {\n const component = options.getComponent();\n spyOn(component.success, 'emit');\n component.finalisedResponse({\n id: 1\n });\n expect(component.success.emit)\n .toHaveBeenCalledWith({\n id: 1\n });\n });\n\n /**\n * Ensures dialog closes after successful response.\n */\n it('should close dialog on finalisedResponse when dialog exists', () => {\n const component = options.getComponent();\n component.dialogRef = {\n close: jasmine.createSpy('close')\n } as any;\n component.finalisedResponse({\n id: 1\n });\n expect(component.dialogRef.close)\n .toHaveBeenCalledWith({\n id: 1\n });\n });\n\n /**\n * Ensures form field reset helper works correctly.\n */\n it('should reset field value', () => {\n const component = options.getComponent();\n component.form.patchValue({\n [fieldName]: 'Test'\n });\n component.resetFieldValue(null, fieldName);\n expect(component.getFieldValue(fieldName))\n .toBe('');\n });\n\n /**\n * Ensures form field value helper returns correct value.\n */\n it('should return field value', () => {\n const component = options.getComponent();\n component.form.patchValue({\n [fieldName]: 'Test'\n });\n expect(component.getFieldValue(fieldName))\n .toBe('Test');\n });\n\n /**\n * Ensures closing dialog emits created entries before closing.\n */\n it('should close dialog and emit created entries', () => {\n const component = options.getComponent();\n component.createdEntries = [\n { id: 1 }\n ];\n component.dialogRef = {\n close: jasmine.createSpy('close')\n } as any;\n spyOn(component.success, 'emit');\n component.closeDialog();\n expect(component.success.emit)\n .toHaveBeenCalledWith([\n { id: 1 }\n ]);\n expect(component.dialogRef.close)\n .toHaveBeenCalled();\n });\n\n });\n}","\n//Parse the date string to local date\nexport function parseLocalDate(dateStr: string): Date {\n const [y, m, d] = dateStr.split('-').map(Number);\n return new Date(y, m - 1, d);\n}","/* tslint:disable:radix */\nimport { Injectable } from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { UntypedFormGroup } from '@angular/forms';\nimport { parseLocalDate } from '../local-date-parser';\nimport { DatePipe } from '@angular/common';\n@Injectable({\n providedIn: 'root'\n})\nexport class RequestHelperService {\n\n constructor(\n private router: Router,\n private activatedRoute: ActivatedRoute,\n private datePipe: DatePipe\n ) { }\n\n /**\n * Update the form values from the URL query params.\n * @param formGroup FormGroup\n * @param formConfig FormConfig\n */\n updateFormWithQueryParams(formGroup: UntypedFormGroup, formConfig: any): void {\n this.updateFormWithParams(formGroup, this.activatedRoute.snapshot.queryParams, formConfig);\n }\n\n /**\n * Update the form values from a plain, request-shaped filters object\n * (i.e. the same comma-separated/formatted shape produced by `convertToFormData`,\n * such as route query params or a saved view's persisted filters).\n * @param formGroup FormGroup\n * @param params Request-shaped filters object\n * @param formConfig FormConfig\n */\n updateFormWithParams(formGroup: UntypedFormGroup, params: any, formConfig: any): void {\n for (const key in formGroup.controls) {\n if (Object.prototype.hasOwnProperty.call(formGroup.controls, key) &&\n params && Object.prototype.hasOwnProperty.call(params, key)) {\n formGroup.get(key).patchValue(this.formatValue(params[key], formConfig?.[key]), { emitEvent: false });\n }\n }\n }\n\n /**\n * Convert the object to form data for the request.\n * @param data Data Object\n * @return Object\n */\n convertToFormData(data: any): any {\n const newData = {};\n for (const prop in data) {\n if (Object.prototype.hasOwnProperty.call(data, prop)) {\n if (Array.isArray(data[prop])) {\n // convert array to comma separated string\n newData[prop] = (data[prop] as []).join(',');\n } else if (this.isDateString(data[prop])) {\n newData[prop] = this.datePipe.transform(data[prop], 'yyyy-MM-dd');\n } else {\n newData[prop] = data[prop] ?? ''; //Don't pass null into request, it will break the filters\n }\n }\n }\n return newData;\n }\n\n /**\n * Update URL with the filters.\n * @param queryParams Query Params\n * @return Promise\n */\n updateQueryParams(queryParams: any): Promise<boolean> {\n return this.router.navigate(\n [],\n {\n relativeTo: this.activatedRoute,\n queryParams,\n queryParamsHandling: 'merge'\n }\n );\n }\n\n /**\n * Format value.\n * @description Convert the comma separated values to array if it is multiple selectable.\n * @param value Value to be converted\n * @param formGroupElement Form group element\n */\n formatValue(value: string, formGroupElement: any): string | number[] | number | boolean | Date {\n if (value && formGroupElement?.multiple && formGroupElement?.multiple === true) {\n const array = value?.split(',');\n return array.map((val: any) => this.isNumeric(val) ? Number.parseInt(val) : val);\n } else if (value && formGroupElement?.date && formGroupElement?.date === true) {\n return parseLocalDate(value);\n } else if (value && formGroupElement?.number && formGroupElement?.number === true) {\n return Number.parseInt(value);\n } else if (value && formGroupElement?.boolean) {\n return value === 'true';\n } else {\n return value;\n }\n }\n\n /**\n * @description Checks value to dertmine if the given value is a number\n * @param value Value to be Checked\n */\n isNumeric(str: string): boolean {\n if (typeof str !== 'string') {\n return false;\n }\n\n if (str.trim() === '') {\n return false;\n }\n\n return !Number.isNaN(Number(str));\n }\n\n /**\n * Determine whether the given value is a date string.\n * Matches a YYYY-MM-DD or YYYY/MM/DD pattern (e.g. 2026-06-18 or 2026/06/18)\n * optional time for instances where we also filter by time\n *\n * @param value Value to be checked\n */\n isDateString(value: any): boolean {\n if (typeof value !== 'string') {\n return false;\n }\n\n return /^\\d{4}[-/](0[1-9]|1[0-2])[-/](0[1-9]|[12]\\d|3[01])([T\\s].*)?$/.test(value);\n }\n}\n","import { fromEvent, merge } from 'rxjs';\nimport { debounceTime, distinctUntilChanged, filter, finalize, map, skip, startWith } from 'rxjs/operators';\nimport { Title } from '@angular/platform-browser';\nimport { Directive, ElementRef, HostAttributeToken, inject, Optional } from '@angular/core';\nimport { EnvironmentService } from '../../core/environment.service';\nimport { RequestHelperService } from '../../request-helper/request-helper.service';\nimport { TechlifyService } from '../techlify-service';\nimport { ColumnConfig } from '../../column-selector';\n\n/**\n * Interface used to ensure we have a consistent structure across our listing pages\n *\n * @todo This should be moved to ngx-techlify-core\n *\n * Advantages of this abstract class\n * - Less code, less maintenance, less bugs\n * - more consistent code\n * - comments on more code\n * - save time, save energy\n */\n@Directive()\nexport abstract class TechlifyListComponent {\n\n models: any[] = [];\n selectedModelIds: any[] = [];\n isAllSelected = false;\n page = 1;\n perPage = 50;\n lastPage = 0;\n filterForm: any;\n isViewLoaded = false;\n columnConfigs: ColumnConfig[] = [];\n\n //Boolean to denote the form is in API progress\n _isWorking: boolean = false;\n get isWorking() {\n return this._isWorking;\n }\n set isWorking(value: boolean) {\n this._isWorking = value;\n if (value) {\n //If it is working, set the filterForm to disabled\n this.filterForm?.disable({ emitEvent: false });\n } else {\n //When working completes, set the filterForm to enabled\n this.filterForm?.enable({ emitEvent: false });\n }\n }\n\n //Filters to be exported for view integration\n filters: any;\n\n /**\n * Boolean to turn off, if we don't want to patch filters from route.\n * This can be overriden in child classes if need \n */\n protected _isFilterPatchFromRoute = true;\n get isFilterPatchFromRoute() {\n return this._isFilterPatchFromRoute;\n }\n set isFilterPatchFromRoute(value: boolean) {\n this._isFilterPatchFromRoute = value;\n }\n\n /**\n * Filter form configuration used to decode the route params.\n * Eg: Override in child class\n * ``` \n * filterFormConfiguration: any = {\n status_id: { multiple: true },\n branch_ids: { multiple: true },\n department_ids: { multiple: true },\n employee_ids: { multiple: true },\n course_ids: { multiple: true },\n date_from: { date: true },\n date_to: { date: true },\n due_date_from: { date: true },\n due_date_to: { date: true },\n } \n *```\n */\n filterFormConfiguration: any;\n\n /**\n * Child classes can override this.\n * If left undefined/empty, falls back to environment.appTitle\n */\n protected get pageTitle(): string | undefined {\n return undefined; // default\n }\n\n\n /**\n * Debounce (ms) applied to filter changes before reloading.\n * Uses environment.filterTime when set, otherwise 500.\n */\n protected get filterDebounceTime(): number {\n const filterTime = Number(this.environmentService?.get()?.filterTime);\n return filterTime >= 0 ? filterTime : 500;\n }\n\n /**\n * When filter changes are applied:\n * - 'keyup': once the user stops typing (after filterDebounceTime)\n * - 'focusout': once focus leaves the filter input\n * Uses environment.filterAction when set, otherwise 'keyup'.\n */\n protected get filterTriggerAction(): 'keyup' | 'focusout' {\n const filterAction = String(this.environmentService?.get()?.filterAction ?? '');\n return filterAction === 'focusout' ? 'focusout' : 'keyup';\n }\n\n private titleService = inject(Title, { optional: true });\n private environmentService = inject(EnvironmentService, { optional: true });\n private hostElement = inject(ElementRef, { optional: true });\n /**\n * Set isSetPageTitle=\"false\" on a listing embedded in a page that sets its own title, so the\n * listing leaves the browser tab title alone.\n */\n private isSetPageTitle = inject(new HostAttributeToken('isSetPageTitle'), { optional: true }) !== 'false';\n\n //Request helper service to inherited and overriden if needed\n protected requestHelperService = inject(RequestHelperService, { optional: true });\n\n constructor(@Optional() protected service?: TechlifyService) {\n if (this.isSetPageTitle) {\n const resolvedTitle =\n this.pageTitle && this.pageTitle.trim().length > 0\n ? this.pageTitle\n : this.environmentService?.get().appTitle;\n this.titleService?.setTitle(resolvedTitle);\n }\n }\n\n /**\n * Function that listens for any changes in the filter form group\n * It updates the route automatically\n * Also reload the page on change\n *\n * In 'focusout' mode, typing in a text filter is held until focus leaves it;\n * selects, dates, sorting etc. still apply after the debounce.\n */\n listenFilterChanges(): void {\n let filterChanges$ = this.filterForm.valueChanges.pipe(debounceTime(this.filterDebounceTime));\n if (this.filterTriggerAction === 'focusout') {\n filterChanges$ = merge(\n this.filterForm.valueChanges.pipe(\n filter(() => !this.isTypingInFilter()),\n debounceTime(this.filterDebounceTime)\n ),\n fromEvent(this.hostElement?.nativeElement ?? document, 'focusout')\n ).pipe(\n // focusout fires on every blur (including the filterForm disabling while loading), so only reload on a real change\n map(() => JSON.stringify(this.filterForm.getRawValue())),\n startWith(JSON.stringify(this.filterForm.getRawValue())),\n distinctUntilChanged(),\n skip(1),\n );\n }\n filterChanges$\n .subscribe((_: any) => {\n if (this.isFilterPatchFromRoute) {\n const unformatedFilters = JSON.parse(JSON.stringify(this.filterForm.value));\n this.filters = this.requestHelperService?.convertToFormData(unformatedFilters);\n this.requestHelperService?.updateQueryParams(this.filters);\n }\n this.reload();\n });\n }\n\n /**\n * Whether the user is currently typing in a text filter inside this listing\n */\n private isTypingInFilter(): boolean {\n const element = document.activeElement;\n const isTextField = element instanceof HTMLTextAreaElement\n || (element instanceof HTMLInputElement && !['checkbox', 'radio'].includes(element.type));\n return isTextField && !!this.hostElement?.nativeElement.contains(element);\n }\n\n /**\n * Function decodes the route params and patches the filter\n * Setup the filterFormConfigurtion to override for multiple and date attributes.\n */\n patchFiltersFromRoute() {\n this.requestHelperService?.updateFormWithQueryParams(this.filterForm, this.filterFormConfiguration);\n }\n\n /**\n * Function that loads the main models of the application\n */\n loadData(filters?: any): void {\n if (!this.service) {\n throw new Error(\n `${this.constructor.name}: Service is required to load the data`\n );\n }\n this.isWorking = true;\n if (!filters) {\n /**\n * Convert to form data gives flexibility to convert the form array values to string\n * No need to override the loadData method in child class to convert array values to string.\n * */\n filters = this.requestHelperService?.convertToFormData(JSON.parse(JSON.stringify(this.filterForm.value)));\n filters.page = this.page;\n filters.per_page = this.perPage;\n }\n this.service.index(filters)\n .pipe(finalize(() => this.isWorking = false))\n .subscribe((result: any) => {\n this.lastPage = result?.last_page || this.page;\n if (result?.data?.length > 0) {\n this.models = this.models.concat(result.data);\n }\n });\n };\n\n /**\n * Reload all data from page 1\n */\n reload(): void {\n this.models = [];\n this.selectedModelIds = [];\n this.page = 1;\n if (!this.isWorking) {\n this.loadData();\n }\n }\n\n /**\n * Can be called when the user is scrolling,\n * to trigger loading a new page of data\n */\n onScroll(): void {\n if (!this.isWorking && this.page < this.lastPage) {\n this.page += 1;\n this.loadData();\n }\n }\n\n /**\n * Reset the Filter.\n *\n * @param field Field name.\n */\n resetFilter(field: string): void {\n this.filterForm.get(field).reset('');\n }\n\n /**\n * Updates one or more models in the list by matching on their `id`.\n * Accepts a single model or an array of models.\n * @param models The updated model or array of models.\n */\n modelsUpdatedById(models: any | any[]): void {\n if (!Array.isArray(models)) {\n models = [models];\n }\n models.forEach((updatedModel: any) => {\n const index = this.models.findIndex((m: any) => m.id === updatedModel.id);\n if (index !== -1) {\n this.models[index] = updatedModel;\n }\n });\n this.models = [...this.models];\n }\n\n /**\n * Deletes one or more models in the list by matching on their `id`.\n * Accepts a single model or an array of models.\n * @param models The updated model or array of models.\n */\n modelsDeletedById(models: any | any[]): void {\n if (!Array.isArray(models)) {\n models = [models];\n }\n this.models = this.models.filter((m: any) => !(models.some((item: any) => item.id === m.id)));\n }\n\n /**\n * Clears the list of selected models, this will also\n * close the bulk update form that checks \n * for selected models length\n */\n clearSelectedModels() {\n this.selectedModelIds = [];\n }\n\n /**\n * Adds a new model at the first position in the table.\n * @param model The new model to be added.\n */\n modelCreated(model: any): void {\n if (Array.isArray(model)) {\n //When create another feature is used, we get array of models\n this.models.unshift(...model);\n } else {\n this.models.unshift(model);\n }\n this.models = [...this.models];\n }\n\n /**\n * Used for multi select\n * Trigerred when select all is toggled\n */\n selectAll(): void {\n this.selectedModelIds = [];\n this.models.map((model: any) => {\n model.is_selected = this.isAllSelected;\n if (this.isAllSelected) {\n this.selectedModelIds.push(model.id);\n }\n });\n this.models = [...this.models];\n }\n\n /**\n * Used for multi select\n * Trigerred if any entry section is modified\n */\n isSelectionModified(event: any, model: any): void {\n this.isAllSelected = false;\n if (event.checked) {\n this.selectedModelIds.push(model?.id);\n }\n else {\n this.selectedModelIds = this.selectedModelIds.filter((modelId: any) => modelId != model?.id);\n }\n }\n\n /**\n * Defualt method to listen to sort column updates\n * @param event \n */\n sortColumn(event: any) {\n const direction = event.direction.toString().toUpperCase();\n this.models = [];\n this.filterForm.get('sort_by').setValue(`${event?.active}|${direction}`);\n }\n\n /**\n * It should be overrided in child class to reset a filter form instance.\n * Used by setView method.\n */\n protected resetFilterForm(): void {\n //If filterForm exists, then reset\n if (this.filterForm) {\n this.filterForm.reset();\n }\n }\n\n /**\n * It should be overrided in child class to reset column configs.\n * Used by setView method.\n */\n protected resetColumnConfigs(): void {\n this.columnConfigs = [];\n }\n\n /**\n * Hook method used to set the selected view\n * @param view optional used by my-view-list\n */\n setView(view?: any) {\n //Reset the query params\n this.requestHelperService?.updateQueryParams({});\n this.isViewLoaded = false;\n this.resetColumnConfigs();\n this.resetFilterForm();\n setTimeout(() => {\n if (view) {\n //If there is a view, then set it.\n //Setup view filters, use the new form configuration to map the multi fields\n this.requestHelperService?.updateFormWithParams(this.filterForm, view?.filters, this.filterFormConfiguration);\n //Setup view columns\n this.columnConfigs.forEach(column => {\n column.isSelected = (view?.columns as string[]).some(col => col === column.def);\n });\n }\n this.isViewLoaded = true;\n }, 500);\n }\n}","import { fakeAsync, tick, ComponentFixture } from '@angular/core/testing';\nimport { TechlifyListComponent } from '../techlify-list.component';\n\nexport interface TechlifyListContractOptions {\n getFixture: () => ComponentFixture<any>;\n getComponent: () => TechlifyListComponent;\n getService: () => any;\n filterField?: string;\n /**\n * Set to false when the component reads route data its own way instead of\n * calling `patchFiltersFromRoute()` on init. Defaults to true.\n */\n patchesFiltersFromRoute?: boolean;\n}\n\nexport function techlifyListContract(\n options: TechlifyListContractOptions\n): void {\n // Route filter test only applies to components using patchFiltersFromRoute() on init\n const routeFilterIt = options.patchesFiltersFromRoute === false ? (() => undefined) as unknown as typeof it : it;\n\n describe('TechlifyListComponent Contract', () => {\n it('should pass a service to the base class', () => {\n const component = options.getComponent();\n expect((component as any).service).toBe(options.getService());\n });\n\n it('should have a filter form', () => {\n const component = options.getComponent();\n expect(component.filterForm).toBeTruthy();\n });\n\n it('should call loadData on init', () => {\n const component = options.getComponent();\n spyOn(component as any, 'loadData');\n (component as any).ngOnInit();\n expect(component.loadData).toHaveBeenCalled();\n });\n\n routeFilterIt('should patch filters from route on init', () => {\n const component = options.getComponent();\n spyOn(component as any, 'patchFiltersFromRoute');\n (component as any).ngOnInit();\n expect(component.patchFiltersFromRoute).toHaveBeenCalled();\n });\n\n it('should reload when filter changes', fakeAsync(() => {\n const component = options.getComponent();\n const filterField = options.filterField ?? 'search';\n if (!component.filterForm?.get(filterField)) {\n pending(`Filter '${filterField}' does not exist.`);\n return;\n }\n spyOn(component as any, 'reload');\n component.listenFilterChanges();\n component.filterForm.get(filterField)?.setValue('abc');\n tick(501);\n expect(component.reload).toHaveBeenCalled();\n\n }));\n\n it('should load next page on scroll', () => {\n const component = options.getComponent();\n component.page = 1;\n component.lastPage = 3;\n component.isWorking = false;\n spyOn(component as any, 'loadData');\n component.onScroll();\n expect(component.page).toBe(2);\n expect(component.loadData).toHaveBeenCalled();\n });\n\n it('should not load next page while already loading', () => {\n const component = options.getComponent();\n component.page = 1;\n component.lastPage = 3;\n component.isWorking = true;\n spyOn(component as any, 'loadData');\n component.onScroll();\n expect(component.loadData).not.toHaveBeenCalled();\n });\n\n it('should update sort_by filter', () => {\n const component = options.getComponent();\n const control = component.filterForm.get('sort_by');\n if (!control) {\n pending('No sort_by control.');\n return;\n }\n spyOn(control, 'setValue');\n component.sortColumn({ active: 'name', direction: 'asc' });\n expect(control.setValue).toHaveBeenCalledWith('name|ASC');\n });\n\n });\n\n}","import { Directive, inject, Optional } from '@angular/core';\nimport { Title } from '@angular/platform-browser';\nimport { ActivatedRoute, ParamMap } from '@angular/router';\nimport { finalize, Subscription } from 'rxjs';\nimport { EnvironmentService } from '../../core/environment.service';\nimport { TechlifyService } from '../techlify-service';\n\n/**\n * Interface used to ensure we have a consistent structure across our view (show) pages\n *\n * Every view page follows the same workflow:\n * route param -> id -> service.show(id) -> model\n */\n@Directive()\nexport abstract class TechlifyShowComponent {\n\n //The main model of the page\n model: any;\n\n //Id of the model, taken from the route\n id: any;\n\n //Boolean to denote the page is in API progress\n isWorking: boolean = false;\n\n /**\n * Name of the route param holding the model id.\n * Override in child class if the route uses a different name, eg: 'memoId'\n */\n protected routeParamName = 'id';\n\n /**\n * Child classes can override this.\n * If left undefined/empty, falls back to environment.appTitle\n */\n protected get pageTitle(): string | undefined {\n return undefined; // default\n }\n\n //Active show request, cancelled when a new load starts\n private loadSubscription?: Subscription;\n\n private titleService = inject(Title, { optional: true });\n private environmentService = inject(EnvironmentService, { optional: true });\n\n //Activated route to be inherited and used if needed\n protected route = inject(ActivatedRoute, { optional: true });\n\n constructor(@Optional() protected service?: TechlifyService) {\n const resolvedTitle =\n this.pageTitle && this.pageTitle.trim().length > 0\n ? this.pageTitle\n : this.environmentService?.get().appTitle;\n this.titleService?.setTitle(resolvedTitle);\n }\n\n /**\n * Function that listens for route param changes\n * Sets the id and reloads the model whenever it changes,\n * so navigating between two records on the same page works\n */\n listenRouteChanges(): void {\n this.route?.paramMap.subscribe((params: ParamMap) => {\n this.id = this.getIdFromRoute(params);\n this.loadData();\n });\n }\n\n /**\n * Reads the model id from the route params\n * Uses the current route snapshot when params are not given\n *\n * @param params optional route params\n */\n getIdFromRoute(params?: ParamMap): any {\n const routeParams = params ?? this.route?.snapshot?.paramMap;\n return routeParams?.get(this.routeParamName) ?? null;\n }\n\n /**\n * Query params passed to the show request, eg: relations to load\n * Override in child class if needed\n */\n protected getShowParams(): any {\n return {};\n }\n\n /**\n * Function that loads the main model of the page\n */\n loadData(): void {\n if (!this.service) {\n throw new Error(\n `${this.constructor.name}: Service is required to load the data`\n );\n }\n if (!this.id) {\n return;\n }\n this.loadSubscription?.unsubscribe();\n this.isWorking = true;\n this.loadSubscription = this.service.show(this.id, this.getShowParams())\n .pipe(finalize(() => this.isWorking = false))\n .subscribe((result: any) => {\n this.model = result?.item;\n this.modelLoaded(this.model);\n });\n }\n\n /**\n * Reload the model of the page\n */\n reload(): void {\n this.loadData();\n }\n\n /**\n * Hook called once the model is loaded\n * Override in child class to compute derived data from the model\n *\n * @param model The loaded model\n */\n protected modelLoaded(model: any): void {\n }\n\n /**\n * Replaces the model of the page, eg: after an update form or status change\n *\n * @param model The updated model\n */\n modelUpdated(model: any): void {\n if (model) {\n this.model = model;\n this.modelLoaded(this.model);\n }\n }\n}\n","import { of } from 'rxjs';\nimport { convertToParamMap } from '@angular/router';\nimport { TechlifyShowComponent } from '../techlify-show.component';\n\nexport interface TechlifyShowContractOptions {\n getComponent: () => TechlifyShowComponent;\n getService: () => any;\n routeParamName?: string;\n}\n\n/**\n * Reusable test contract for components extending TechlifyShowComponent.\n *\n * Ensures all view components follow the same base behaviour:\n * - Service injection\n * - Default state initialization\n * - Reading the id from the route\n * - Loading the model through service.show()\n * - Model update handling\n */\nexport function techlifyShowContract(\n options: TechlifyShowContractOptions\n): void {\n\n describe('TechlifyShowComponent Contract', () => {\n\n /**\n * Ensures child components correctly pass their service\n * to the base view component.\n */\n it('should pass a service to the base class', () => {\n const component = options.getComponent();\n expect((component as any).service)\n .toBe(options.getService());\n });\n\n /**\n * Ensures route listening is started on init.\n */\n it('should listen route changes on init', () => {\n const component = options.getComponent();\n spyOn(component, 'listenRouteChanges');\n (component as any).ngOnInit();\n expect(component.listenRouteChanges)\n .toHaveBeenCalled();\n });\n\n /**\n * Ensures the id is read from the configured route param.\n */\n it('should get id from route params', () => {\n const component = options.getComponent();\n const routeParamName = options.routeParamName ?? 'id';\n const params = convertToParamMap({ [routeParamName]: '5' });\n expect(component.getIdFromRoute(params))\n .toBe('5');\n });\n\n /**\n * Ensures the model is loaded using the show service.\n */\n it('should load the model using show', () => {\n const component = options.getComponent();\n options.getService().show\n .and.returnValue(of({\n item: { id: 5 }\n }));\n component.id = 5;\n component.loadData();\n expect(options.getService().show)\n .toHaveBeenCalledWith(5, jasmine.anything());\n expect(component.model)\n .toEqual({ id: 5 });\n expect(component.isWorking)\n .toBeFalse();\n });\n\n /**\n * Ensures no request is made without an id.\n */\n it('should not load without an id', () => {\n const component = options.getComponent();\n options.getService().show.calls.reset();\n component.id = null;\n component.loadData();\n expect(options.getService().show)\n .not.toHaveBeenCalled();\n });\n\n /**\n * Ensures the model is replaced on update.\n */\n it('should replace the model on modelUpdated', () => {\n const component = options.getComponent();\n component.modelUpdated({ id: 7 });\n expect(component.model)\n .toEqual({ id: 7 });\n });\n\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngx-techlify-core-base-model-DfODc3ll.mjs","names":["i1.FormValidatorService","i2.TechlifyService","debounceTime","map","startWith","distinctUntilChanged","finalize","i1.TechlifyService","i1.TechlifyService"],"sources":["../../../projects/ngx-techlify-core/src/lib/base-model/techlify-service.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-form/techlify-form.component.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-form/contract/techlify-form.contract.ts","../../../projects/ngx-techlify-core/src/lib/local-date-parser.ts","../../../projects/ngx-techlify-core/src/lib/request-helper/request-helper.service.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-list/techlify-list.component.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-list/contract/techlify-list.contract.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-show/techlify-show.component.ts","../../../projects/ngx-techlify-core/src/lib/base-model/techlify-show/contract/techlify-show.contract.ts"],"sourcesContent":["import { HttpService } from '../core';\nimport { Observable } from 'rxjs';\n\n/**\n * Interface used to ensure we have a consistent structure across our listing pages\n *\n * Advantages of this abstract class\n * - Less code, less maintenance, less bugs\n * - more consistent code\n * - comments on more code\n * - save time, save energy\n */\nexport class TechlifyService {\n\n protected baseUrl = 'you-forgot-to-set-a-base-url';\n\n constructor(protected http: HttpService, baseUrl: string) {\n this.baseUrl = baseUrl;\n }\n\n /**\n * Loads a set of the model this service is for\n *\n * @param params Query parameters\n * @return Observable<any>\n */\n index(params?: any, isCache?: boolean): Observable<any> {\n if (isCache) {\n return this.http.cache().get(`api/${this.baseUrl}`, { params });\n }\n\n return this.http.get(`api/${this.baseUrl}`, { params });\n }\n\n /**\n * Add a new record to the database\n *\n * @param model The data of the model to add\n * @param params Query parameters\n * @return Observable<any>\n */\n store(model: any, params?: any): Observable<any> {\n return this.http.post(`api/${this.baseUrl}`, model, { params });\n }\n\n /**\n * Load a single model record, often with all relations and related data\n *\n * @param modelId ID of the model to load\n * @param params Query parameters\n * @return Observable<any>\n */\n show(modelId: any, params?: any): Observable<any> {\n return this.http.get(`api/${this.baseUrl}/${modelId}`, { params });\n }\n\n /**\n * Update fields of a single existing model\n *\n * @param model Model to update\n * @param params Query parameters\n * @return Observable<any>\n */\n update(model: any, params?: any): Observable<any> {\n return this.http.put(`api/${this.baseUrl}/${model.id}`, model, { params });\n }\n\n /**\n * Delete a single model\n *\n * @param model Model to delete\n * @return Observable<any>\n */\n delete(model: any): Observable<any> {\n return this.http.delete(`api/${this.baseUrl}/${model.id}`);\n }\n\n\n}\n","import { UntypedFormGroup } from '@angular/forms';\nimport { FormValidatorService } from '../../core/form-validator.service';\nimport { Directive, EventEmitter, inject, Input, Optional, Output } from '@angular/core';\nimport { TechlifyService } from '../techlify-service';\nimport { finalize } from 'rxjs';\nimport { MatDialogRef } from '@angular/material/dialog';\nimport { AlertService } from '../../core/alert.service';\n\n/**\n * Interface used to ensure we have a consistent structure across our form components\n *\n * It also provides some abstraction of repeated code to reduce our codebase size\n *\n * @todo This should be moved to ngx-techlify-core\n *\n * Advantages of this abstract class\n * - Less code, less maintenance, less bugs\n * - more consistent code\n * - comments on more code\n * - save time, save energy\n */\n@Directive()\nexport abstract class TechlifyFormComponent {\n\n //Form builder instance\n form: UntypedFormGroup;\n\n //Error messages config for the form\n errorMessages: any;\n\n //Boolean to denote the form is in API progress\n isWorking: boolean = false;\n\n //Form dialogref reference\n dialogRef: MatDialogRef<any, any>;\n\n //Array of created multiple entries\n createdEntries: any = [];\n\n //Boolean to denote if form is in update mode\n @Input() isUpdate: boolean = false;\n\n //Boolean to denote if form has ability to create multiple entries\n @Input() isAllowCreateAnother: boolean = true;\n\n //Emits an event once form submit is success\n @Output() success = new EventEmitter<any>();\n\n //Injected alert service\n protected alertService = inject(AlertService, { optional: true });\n\n constructor(\n protected formValidatorService: FormValidatorService,\n @Optional() protected service?: TechlifyService) {\n\n }\n /**\n * Submits the form\n * @param model form model optional, if needed to be overriden\n * @returns \n */\n submit(model?: any) {\n this.form.markAllAsTouched();\n if (!this.service) {\n throw new Error(\n `${this.constructor.name}: Service is required to submit the form`\n );\n }\n if (!this.form.valid) {\n return;\n }\n if (!model) {\n model = this.form.value;\n }\n this.isWorking = true;\n (\n this.isUpdate ?\n this.service.update(model) :\n this.service.store(model)\n )\n .pipe(\n finalize(() => this.isWorking = false),\n )\n .subscribe((res: any) => {\n this.createdEntries.unshift(res?.item);\n if (model?.is_create_another) {\n this.alertService.addAlert('Successfully Created', 'success');\n return;\n }\n if (this.isUpdate) {\n this.finalisedResponse(res?.item);\n } else {\n this.finalisedResponse((this.createdEntries.length == 1) ? this.createdEntries[0] : this.createdEntries);\n }\n\n });\n }\n\n //Send uniformed finalised response for both form button and dialog\n finalisedResponse(response: any) {\n this.success.emit(response);\n if (this.dialogRef) {\n this.dialogRef.close(response);\n }\n }\n\n /**\n * Close the active dialog\n */\n closeDialog() {\n if (this.createdEntries?.length > 0) {\n this.success.emit(this.createdEntries);\n }\n if (this.dialogRef) {\n this.dialogRef.close();\n }\n }\n\n /**\n * Resets the value of a field in the HTML form\n *\n * @param event\n * @param field\n */\n resetFieldValue(event: any, field: string) {\n this.form?.get(field)?.setValue('');\n }\n\n /**\n * Retrieves the value of a field from the HTML form\n *\n * @param field\n * @returns\n */\n getFieldValue(field: string) {\n return this.form?.get(field)?.value;\n }\n\n /**\n * Method to evaluate form fields\n * */\n isFieldValid(field: string) {\n return this.formValidatorService.isFieldValid(field, this.form);\n }\n\n /**\n * Method to find error in form fields\n * */\n getErrorMessage(field: string) {\n return this.formValidatorService.getErrorMessage(field, this.form, this.errorMessages);\n }\n\n}\n","import { of } from 'rxjs';\nimport { TechlifyFormComponent } from '../techlify-form.component';\n\nexport interface TechlifyFormContractOptions {\n getComponent: () => TechlifyFormComponent;\n getService: () => any;\n /**\n * Form control used by the submit and field helper tests. Defaults to `name`.\n */\n fieldName?: string;\n /**\n * Set when the component overrides `submit()` with its own flow; the tests for the\n * base submit flow (store/update, markAllAsTouched, isWorking) are then not registered.\n */\n hasCustomSubmit?: boolean;\n}\n\n/**\n * Reusable test contract for components extending TechlifyFormComponent.\n *\n * Ensures all form components follow the same base behaviour:\n * - Service injection\n * - Default state initialization\n * - Create/update submission flow\n * - Form validation handling\n * - Success event handling\n * - Dialog handling\n * - Field helper methods\n */\nexport function techlifyFormContract(\n options: TechlifyFormContractOptions\n): void {\n\n const fieldName = options.fieldName ?? 'name';\n // Base submit flow tests only apply to components using TechlifyFormComponent.submit()\n const baseSubmitIt = options.hasCustomSubmit ? (() => undefined) as unknown as typeof it : it;\n\n describe('TechlifyFormComponent Contract', () => {\n\n /**\n * Ensures child components correctly pass their service\n * to the base form component.\n */\n it('should pass a service to the base class', () => {\n const component = options.getComponent();\n expect((component as any).service)\n .toBe(options.getService());\n });\n\n /**\n * Ensures default properties are initialized correctly.\n */\n it('should initialize default values', () => {\n const component = options.getComponent();\n expect(component.isWorking).toBeFalse();\n expect(component.isUpdate).toBeFalse();\n expect(component.isAllowCreateAnother).toBeTrue();\n expect(component.createdEntries).toEqual([]);\n });\n\n /**\n * Ensures create forms call store service and finalise response.\n */\n baseSubmitIt('should submit using store when creating', () => {\n const component = options.getComponent();\n component.form.patchValue({\n [fieldName]: 'Test'\n });\n spyOn(component as any, 'finalisedResponse');\n options.getService().store\n .and.returnValue(of({\n item: { id: 1 }\n }));\n component.submit();\n expect(options.getService().store)\n .toHaveBeenCalledWith(component.form.value);\n expect(component.createdEntries)\n .toEqual([{ id: 1 }]);\n expect(component.finalisedResponse)\n .toHaveBeenCalled();\n });\n\n /**\n * Ensures update forms call update service instead of store.\n */\n baseSubmitIt('should submit using update when isUpdate is true', () => {\n const component = options.getComponent();\n component.isUpdate = true;\n component.form.patchValue({\n [fieldName]: 'Updated'\n });\n spyOn(component as any, 'finalisedResponse');\n options.getService().update\n .and.returnValue(of({\n item: { id: 1 }\n }));\n component.submit();\n expect(options.getService().update)\n .toHaveBeenCalledWith(component.form.value);\n expect(component.finalisedResponse)\n .toHaveBeenCalledWith({\n id: 1\n });\n });\n\n /**\n * Ensures invalid forms do not trigger API submission.\n */\n it('should not submit invalid form', () => {\n const component = options.getComponent();\n component.form.setErrors({\n invalid: true\n });\n component.submit();\n expect(options.getService().store)\n .not.toHaveBeenCalled();\n });\n\n /**\n * Ensures form validation is triggered before submission.\n */\n baseSubmitIt('should mark form as touched before submit', () => {\n const component = options.getComponent();\n spyOn(component.form, 'markAllAsTouched');\n component.submit();\n expect(component.form.markAllAsTouched)\n .toHaveBeenCalled();\n });\n\n /**\n * Ensures API progress state is handled correctly.\n */\n baseSubmitIt('should set isWorking while submitting', () => {\n const component = options.getComponent();\n component.form.patchValue({\n [fieldName]: 'Test'\n });\n options.getService().store\n .and.returnValue(of({\n item: { id: 1 }\n }));\n component.submit();\n expect(component.isWorking)\n .toBeFalse();\n });\n\n /**\n * Ensures successful form completion emits success event.\n */\n it('should emit success event on finalisedResponse', () => {\n const component = options.getComponent();\n spyOn(component.success, 'emit');\n component.finalisedResponse({\n id: 1\n });\n expect(component.success.emit)\n .toHaveBeenCalledWith({\n id: 1\n });\n });\n\n /**\n * Ensures dialog closes after successful response.\n */\n it('should close dialog on finalisedResponse when dialog exists', () => {\n const component = options.getComponent();\n component.dialogRef = {\n close: jasmine.createSpy('close')\n } as any;\n component.finalisedResponse({\n id: 1\n });\n expect(component.dialogRef.close)\n .toHaveBeenCalledWith({\n id: 1\n });\n });\n\n /**\n * Ensures form field reset helper works correctly.\n */\n it('should reset field value', () => {\n const component = options.getComponent();\n component.form.patchValue({\n [fieldName]: 'Test'\n });\n component.resetFieldValue(null, fieldName);\n expect(component.getFieldValue(fieldName))\n .toBe('');\n });\n\n /**\n * Ensures form field value helper returns correct value.\n */\n it('should return field value', () => {\n const component = options.getComponent();\n component.form.patchValue({\n [fieldName]: 'Test'\n });\n expect(component.getFieldValue(fieldName))\n .toBe('Test');\n });\n\n /**\n * Ensures closing dialog emits created entries before closing.\n */\n it('should close dialog and emit created entries', () => {\n const component = options.getComponent();\n component.createdEntries = [\n { id: 1 }\n ];\n component.dialogRef = {\n close: jasmine.createSpy('close')\n } as any;\n spyOn(component.success, 'emit');\n component.closeDialog();\n expect(component.success.emit)\n .toHaveBeenCalledWith([\n { id: 1 }\n ]);\n expect(component.dialogRef.close)\n .toHaveBeenCalled();\n });\n\n });\n}","\n//Parse the date string to local date\nexport function parseLocalDate(dateStr: string): Date {\n const [y, m, d] = dateStr.split('-').map(Number);\n return new Date(y, m - 1, d);\n}","/* tslint:disable:radix */\nimport { Injectable } from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { UntypedFormGroup } from '@angular/forms';\nimport { parseLocalDate } from '../local-date-parser';\nimport { DatePipe } from '@angular/common';\n@Injectable({\n providedIn: 'root'\n})\nexport class RequestHelperService {\n\n constructor(\n private router: Router,\n private activatedRoute: ActivatedRoute,\n private datePipe: DatePipe\n ) { }\n\n /**\n * Update the form values from the URL query params.\n * @param formGroup FormGroup\n * @param formConfig FormConfig\n */\n updateFormWithQueryParams(formGroup: UntypedFormGroup, formConfig: any): void {\n this.updateFormWithParams(formGroup, this.activatedRoute.snapshot.queryParams, formConfig);\n }\n\n /**\n * Update the form values from a plain, request-shaped filters object\n * (i.e. the same comma-separated/formatted shape produced by `convertToFormData`,\n * such as route query params or a saved view's persisted filters).\n * @param formGroup FormGroup\n * @param params Request-shaped filters object\n * @param formConfig FormConfig\n */\n updateFormWithParams(formGroup: UntypedFormGroup, params: any, formConfig: any): void {\n for (const key in formGroup.controls) {\n if (Object.prototype.hasOwnProperty.call(formGroup.controls, key) &&\n params && Object.prototype.hasOwnProperty.call(params, key)) {\n formGroup.get(key).patchValue(this.formatValue(params[key], formConfig?.[key]), { emitEvent: false });\n }\n }\n }\n\n /**\n * Convert the object to form data for the request.\n * @param data Data Object\n * @return Object\n */\n convertToFormData(data: any): any {\n const newData = {};\n for (const prop in data) {\n if (Object.prototype.hasOwnProperty.call(data, prop)) {\n if (Array.isArray(data[prop])) {\n // convert array to comma separated string\n newData[prop] = (data[prop] as []).join(',');\n } else if (this.isDateString(data[prop])) {\n newData[prop] = this.datePipe.transform(data[prop], 'yyyy-MM-dd');\n } else {\n newData[prop] = data[prop] ?? ''; //Don't pass null into request, it will break the filters\n }\n }\n }\n return newData;\n }\n\n /**\n * Update URL with the filters.\n * @param queryParams Query Params\n * @return Promise\n */\n updateQueryParams(queryParams: any): Promise<boolean> {\n return this.router.navigate(\n [],\n {\n relativeTo: this.activatedRoute,\n queryParams,\n queryParamsHandling: 'merge'\n }\n );\n }\n\n /**\n * Format value.\n * @description Convert the comma separated values to array if it is multiple selectable.\n * @param value Value to be converted\n * @param formGroupElement Form group element\n */\n formatValue(value: string, formGroupElement: any): string | number[] | number | boolean | Date {\n if (value && formGroupElement?.multiple && formGroupElement?.multiple === true) {\n const array = value?.split(',');\n return array.map((val: any) => this.isNumeric(val) ? Number.parseInt(val) : val);\n } else if (value && formGroupElement?.date && formGroupElement?.date === true) {\n return parseLocalDate(value);\n } else if (value && formGroupElement?.number && formGroupElement?.number === true) {\n return Number.parseInt(value);\n } else if (value && formGroupElement?.boolean) {\n return value === 'true';\n } else {\n return value;\n }\n }\n\n /**\n * @description Checks value to dertmine if the given value is a number\n * @param value Value to be Checked\n */\n isNumeric(str: string): boolean {\n if (typeof str !== 'string') {\n return false;\n }\n\n if (str.trim() === '') {\n return false;\n }\n\n return !Number.isNaN(Number(str));\n }\n\n /**\n * Determine whether the given value is a date string.\n * Matches a YYYY-MM-DD or YYYY/MM/DD pattern (e.g. 2026-06-18 or 2026/06/18)\n * optional time for instances where we also filter by time\n *\n * @param value Value to be checked\n */\n isDateString(value: any): boolean {\n if (typeof value !== 'string') {\n return false;\n }\n\n return /^\\d{4}[-/](0[1-9]|1[0-2])[-/](0[1-9]|[12]\\d|3[01])([T\\s].*)?$/.test(value);\n }\n}\n","import { fromEvent, merge } from 'rxjs';\nimport { debounceTime, distinctUntilChanged, filter, finalize, map, skip, startWith } from 'rxjs/operators';\nimport { Title } from '@angular/platform-browser';\nimport { Directive, ElementRef, HostAttributeToken, inject, Optional } from '@angular/core';\nimport { EnvironmentService } from '../../core/environment.service';\nimport { RequestHelperService } from '../../request-helper/request-helper.service';\nimport { TechlifyService } from '../techlify-service';\nimport { ColumnConfig } from '../../column-selector';\n\n/**\n * Interface used to ensure we have a consistent structure across our listing pages\n *\n * @todo This should be moved to ngx-techlify-core\n *\n * Advantages of this abstract class\n * - Less code, less maintenance, less bugs\n * - more consistent code\n * - comments on more code\n * - save time, save energy\n */\n@Directive()\nexport abstract class TechlifyListComponent {\n\n models: any[] = [];\n selectedModelIds: any[] = [];\n isAllSelected = false;\n page = 1;\n perPage = 50;\n lastPage = 0;\n filterForm: any;\n isViewLoaded = false;\n columnConfigs: ColumnConfig[] = [];\n\n //Boolean to denote the form is in API progress\n _isWorking: boolean = false;\n get isWorking() {\n return this._isWorking;\n }\n set isWorking(value: boolean) {\n this._isWorking = value;\n if (value) {\n //If it is working, set the filterForm to disabled\n this.filterForm?.disable({ emitEvent: false });\n } else {\n //When working completes, set the filterForm to enabled\n this.filterForm?.enable({ emitEvent: false });\n }\n }\n\n //Filters to be exported for view integration\n filters: any;\n\n /**\n * Boolean to turn off, if we don't want to patch filters from route.\n * This can be overriden in child classes if need \n */\n protected _isFilterPatchFromRoute = true;\n get isFilterPatchFromRoute() {\n return this._isFilterPatchFromRoute;\n }\n set isFilterPatchFromRoute(value: boolean) {\n this._isFilterPatchFromRoute = value;\n }\n\n /**\n * Filter form configuration used to decode the route params.\n * Eg: Override in child class\n * ``` \n * filterFormConfiguration: any = {\n status_id: { multiple: true },\n branch_ids: { multiple: true },\n department_ids: { multiple: true },\n employee_ids: { multiple: true },\n course_ids: { multiple: true },\n date_from: { date: true },\n date_to: { date: true },\n due_date_from: { date: true },\n due_date_to: { date: true },\n } \n *```\n */\n filterFormConfiguration: any;\n\n /**\n * Child classes can override this.\n * If left undefined/empty, falls back to environment.appTitle\n */\n protected get pageTitle(): string | undefined {\n return undefined; // default\n }\n\n\n /**\n * Debounce (ms) applied to filter changes before reloading.\n * Uses environment.filterTime when set, otherwise 500.\n */\n protected get filterDebounceTime(): number {\n const filterTime = Number(this.environmentService?.get()?.filterTime);\n return filterTime >= 0 ? filterTime : 500;\n }\n\n /**\n * When filter changes are applied:\n * - 'keyup': once the user stops typing (after filterDebounceTime)\n * - 'focusout': once focus leaves the filter input\n * Uses environment.filterAction when set, otherwise 'keyup'.\n */\n protected get filterTriggerAction(): 'keyup' | 'focusout' {\n const filterAction = String(this.environmentService?.get()?.filterAction ?? '');\n return filterAction === 'focusout' ? 'focusout' : 'keyup';\n }\n\n private titleService = inject(Title, { optional: true });\n private environmentService = inject(EnvironmentService, { optional: true });\n private hostElement = inject(ElementRef, { optional: true });\n /**\n * Set isSetPageTitle=\"false\" on a listing embedded in a page that sets its own title, so the\n * listing leaves the browser tab title alone.\n */\n private isSetPageTitle = inject(new HostAttributeToken('isSetPageTitle'), { optional: true }) !== 'false';\n\n //Request helper service to inherited and overriden if needed\n protected requestHelperService = inject(RequestHelperService, { optional: true });\n\n constructor(@Optional() protected service?: TechlifyService) {\n if (this.isSetPageTitle) {\n const resolvedTitle =\n this.pageTitle && this.pageTitle.trim().length > 0\n ? this.pageTitle\n : this.environmentService?.get().appTitle;\n this.titleService?.setTitle(resolvedTitle);\n }\n }\n\n /**\n * Function that listens for any changes in the filter form group\n * It updates the route automatically\n * Also reload the page on change\n *\n * In 'focusout' mode, typing in a text filter is held until focus leaves it;\n * selects, dates, sorting etc. still apply after the debounce.\n */\n listenFilterChanges(): void {\n let filterChanges$ = this.filterForm.valueChanges.pipe(debounceTime(this.filterDebounceTime));\n if (this.filterTriggerAction === 'focusout') {\n filterChanges$ = merge(\n this.filterForm.valueChanges.pipe(\n filter(() => !this.isTypingInFilter()),\n debounceTime(this.filterDebounceTime)\n ),\n fromEvent(this.hostElement?.nativeElement ?? document, 'focusout')\n ).pipe(\n // focusout fires on every blur (including the filterForm disabling while loading), so only reload on a real change\n map(() => JSON.stringify(this.filterForm.getRawValue())),\n startWith(JSON.stringify(this.filterForm.getRawValue())),\n distinctUntilChanged(),\n skip(1),\n );\n }\n filterChanges$\n .subscribe((_: any) => {\n if (this.isFilterPatchFromRoute) {\n const unformatedFilters = JSON.parse(JSON.stringify(this.filterForm.value));\n this.filters = this.requestHelperService?.convertToFormData(unformatedFilters);\n this.requestHelperService?.updateQueryParams(this.filters);\n }\n this.reload();\n });\n }\n\n /**\n * Whether the user is currently typing in a text filter inside this listing\n */\n private isTypingInFilter(): boolean {\n const element = document.activeElement;\n const isTextField = element instanceof HTMLTextAreaElement\n || (element instanceof HTMLInputElement && !['checkbox', 'radio'].includes(element.type));\n return isTextField && !!this.hostElement?.nativeElement.contains(element);\n }\n\n /**\n * Function decodes the route params and patches the filter\n * Setup the filterFormConfigurtion to override for multiple and date attributes.\n */\n patchFiltersFromRoute() {\n this.requestHelperService?.updateFormWithQueryParams(this.filterForm, this.filterFormConfiguration);\n }\n\n /**\n * Function that loads the main models of the application\n */\n loadData(filters?: any): void {\n if (!this.service) {\n throw new Error(\n `${this.constructor.name}: Service is required to load the data`\n );\n }\n this.isWorking = true;\n if (!filters) {\n /**\n * Convert to form data gives flexibility to convert the form array values to string\n * No need to override the loadData method in child class to convert array values to string.\n * */\n filters = this.requestHelperService?.convertToFormData(JSON.parse(JSON.stringify(this.filterForm.value)));\n filters.page = this.page;\n filters.per_page = this.perPage;\n }\n this.service.index(filters)\n .pipe(finalize(() => this.isWorking = false))\n .subscribe((result: any) => {\n this.lastPage = result?.last_page || this.page;\n if (result?.data?.length > 0) {\n this.models = this.models.concat(result.data);\n }\n });\n };\n\n /**\n * Reload all data from page 1\n */\n reload(): void {\n this.models = [];\n this.selectedModelIds = [];\n this.page = 1;\n if (!this.isWorking) {\n this.loadData();\n }\n }\n\n /**\n * Can be called when the user is scrolling,\n * to trigger loading a new page of data\n */\n onScroll(): void {\n if (!this.isWorking && this.page < this.lastPage) {\n this.page += 1;\n this.loadData();\n }\n }\n\n /**\n * Reset the Filter.\n *\n * @param field Field name.\n */\n resetFilter(field: string): void {\n this.filterForm.get(field).reset('');\n }\n\n /**\n * Updates one or more models in the list by matching on their `id`.\n * Accepts a single model or an array of models.\n * @param models The updated model or array of models.\n */\n modelsUpdatedById(models: any | any[]): void {\n if (!Array.isArray(models)) {\n models = [models];\n }\n models.forEach((updatedModel: any) => {\n const index = this.models.findIndex((m: any) => m.id === updatedModel.id);\n if (index !== -1) {\n this.models[index] = updatedModel;\n }\n });\n this.models = [...this.models];\n }\n\n /**\n * Deletes one or more models in the list by matching on their `id`.\n * Accepts a single model or an array of models.\n * @param models The updated model or array of models.\n */\n modelsDeletedById(models: any | any[]): void {\n if (!Array.isArray(models)) {\n models = [models];\n }\n this.models = this.models.filter((m: any) => !(models.some((item: any) => item.id === m.id)));\n }\n\n /**\n * Clears the list of selected models, this will also\n * close the bulk update form that checks \n * for selected models length\n */\n clearSelectedModels() {\n this.selectedModelIds = [];\n }\n\n /**\n * Adds a new model at the first position in the table.\n * @param model The new model to be added.\n */\n modelCreated(model: any): void {\n if (Array.isArray(model)) {\n //When create another feature is used, we get array of models\n this.models.unshift(...model);\n } else {\n this.models.unshift(model);\n }\n this.models = [...this.models];\n }\n\n /**\n * Used for multi select\n * Trigerred when select all is toggled\n */\n selectAll(): void {\n this.selectedModelIds = [];\n this.models.map((model: any) => {\n model.is_selected = this.isAllSelected;\n if (this.isAllSelected) {\n this.selectedModelIds.push(model.id);\n }\n });\n this.models = [...this.models];\n }\n\n /**\n * Used for multi select\n * Trigerred if any entry section is modified\n */\n isSelectionModified(event: any, model: any): void {\n this.isAllSelected = false;\n if (event.checked) {\n this.selectedModelIds.push(model?.id);\n }\n else {\n this.selectedModelIds = this.selectedModelIds.filter((modelId: any) => modelId != model?.id);\n }\n }\n\n /**\n * Defualt method to listen to sort column updates\n * @param event \n */\n sortColumn(event: any) {\n const direction = event.direction.toString().toUpperCase();\n this.models = [];\n this.filterForm.get('sort_by').setValue(`${event?.active}|${direction}`);\n }\n\n /**\n * It should be overrided in child class to reset a filter form instance.\n * Used by setView method.\n */\n protected resetFilterForm(): void {\n //If filterForm exists, then reset\n if (this.filterForm) {\n this.filterForm.reset();\n }\n }\n\n /**\n * It should be overrided in child class to reset column configs.\n * Used by setView method.\n */\n protected resetColumnConfigs(): void {\n this.columnConfigs = [];\n }\n\n /**\n * Hook method used to set the selected view\n * @param view optional used by my-view-list\n */\n setView(view?: any) {\n //Reset the query params\n this.requestHelperService?.updateQueryParams({});\n this.isViewLoaded = false;\n this.resetColumnConfigs();\n this.resetFilterForm();\n setTimeout(() => {\n if (view) {\n //If there is a view, then set it.\n //Setup view filters, use the new form configuration to map the multi fields\n this.requestHelperService?.updateFormWithParams(this.filterForm, view?.filters, this.filterFormConfiguration);\n //Setup view columns\n this.columnConfigs.forEach(column => {\n column.isSelected = (view?.columns as string[]).some(col => col === column.def);\n });\n }\n this.isViewLoaded = true;\n }, 500);\n }\n}","import { fakeAsync, tick, ComponentFixture } from '@angular/core/testing';\nimport { TechlifyListComponent } from '../techlify-list.component';\n\nexport interface TechlifyListContractOptions {\n getFixture: () => ComponentFixture<any>;\n getComponent: () => TechlifyListComponent;\n getService: () => any;\n filterField?: string;\n /**\n * Set to false when the component reads route data its own way instead of\n * calling `patchFiltersFromRoute()` on init. Defaults to true.\n */\n patchesFiltersFromRoute?: boolean;\n}\n\nexport function techlifyListContract(\n options: TechlifyListContractOptions\n): void {\n // Route filter test only applies to components using patchFiltersFromRoute() on init\n const routeFilterIt = options.patchesFiltersFromRoute === false ? (() => undefined) as unknown as typeof it : it;\n\n describe('TechlifyListComponent Contract', () => {\n it('should pass a service to the base class', () => {\n const component = options.getComponent();\n expect((component as any).service).toBe(options.getService());\n });\n\n it('should have a filter form', () => {\n const component = options.getComponent();\n expect(component.filterForm).toBeTruthy();\n });\n\n it('should call loadData on init', () => {\n const component = options.getComponent();\n spyOn(component as any, 'loadData');\n (component as any).ngOnInit();\n expect(component.loadData).toHaveBeenCalled();\n });\n\n routeFilterIt('should patch filters from route on init', () => {\n const component = options.getComponent();\n spyOn(component as any, 'patchFiltersFromRoute');\n (component as any).ngOnInit();\n expect(component.patchFiltersFromRoute).toHaveBeenCalled();\n });\n\n it('should reload when filter changes', fakeAsync(() => {\n const component = options.getComponent();\n const filterField = options.filterField ?? 'search';\n if (!component.filterForm?.get(filterField)) {\n pending(`Filter '${filterField}' does not exist.`);\n return;\n }\n spyOn(component as any, 'reload');\n component.listenFilterChanges();\n component.filterForm.get(filterField)?.setValue('abc');\n tick(501);\n expect(component.reload).toHaveBeenCalled();\n\n }));\n\n it('should load next page on scroll', () => {\n const component = options.getComponent();\n component.page = 1;\n component.lastPage = 3;\n component.isWorking = false;\n spyOn(component as any, 'loadData');\n component.onScroll();\n expect(component.page).toBe(2);\n expect(component.loadData).toHaveBeenCalled();\n });\n\n it('should not load next page while already loading', () => {\n const component = options.getComponent();\n component.page = 1;\n component.lastPage = 3;\n component.isWorking = true;\n spyOn(component as any, 'loadData');\n component.onScroll();\n expect(component.loadData).not.toHaveBeenCalled();\n });\n\n it('should update sort_by filter', () => {\n const component = options.getComponent();\n const control = component.filterForm.get('sort_by');\n if (!control) {\n pending('No sort_by control.');\n return;\n }\n spyOn(control, 'setValue');\n component.sortColumn({ active: 'name', direction: 'asc' });\n expect(control.setValue).toHaveBeenCalledWith('name|ASC');\n });\n\n });\n\n}","import { Directive, inject, Optional } from '@angular/core';\nimport { Title } from '@angular/platform-browser';\nimport { ActivatedRoute, ParamMap } from '@angular/router';\nimport { finalize, Subscription } from 'rxjs';\nimport { EnvironmentService } from '../../core/environment.service';\nimport { TechlifyService } from '../techlify-service';\n\n/**\n * Interface used to ensure we have a consistent structure across our view (show) pages\n *\n * Every view page follows the same workflow:\n * route param -> id -> service.show(id) -> model\n */\n@Directive()\nexport abstract class TechlifyShowComponent {\n\n //The main model of the page\n model: any;\n\n //Id of the model, taken from the route\n id: any;\n\n //Boolean to denote the page is in API progress\n isWorking: boolean = false;\n\n /**\n * Name of the route param holding the model id.\n * Override in child class if the route uses a different name, eg: 'memoId'\n */\n protected routeParamName = 'id';\n\n /**\n * Child classes can override this.\n * If left undefined/empty, falls back to environment.appTitle\n */\n protected get pageTitle(): string | undefined {\n return undefined; // default\n }\n\n //Active show request, cancelled when a new load starts\n private loadSubscription?: Subscription;\n\n private titleService = inject(Title, { optional: true });\n private environmentService = inject(EnvironmentService, { optional: true });\n\n //Activated route to be inherited and used if needed\n protected route = inject(ActivatedRoute, { optional: true });\n\n constructor(@Optional() protected service?: TechlifyService) {\n const resolvedTitle =\n this.pageTitle && this.pageTitle.trim().length > 0\n ? this.pageTitle\n : this.environmentService?.get().appTitle;\n this.titleService?.setTitle(resolvedTitle);\n }\n\n /**\n * Function that listens for route param changes\n * Sets the id and reloads the model whenever it changes,\n * so navigating between two records on the same page works\n */\n listenRouteChanges(): void {\n this.route?.paramMap.subscribe((params: ParamMap) => {\n this.id = this.getIdFromRoute(params);\n this.loadData();\n });\n }\n\n /**\n * Reads the model id from the route params\n * Uses the current route snapshot when params are not given\n *\n * @param params optional route params\n */\n getIdFromRoute(params?: ParamMap): any {\n const routeParams = params ?? this.route?.snapshot?.paramMap;\n return routeParams?.get(this.routeParamName) ?? null;\n }\n\n /**\n * Query params passed to the show request, eg: relations to load\n * Override in child class if needed\n */\n protected getShowParams(): any {\n return {};\n }\n\n /**\n * Function that loads the main model of the page\n */\n loadData(): void {\n if (!this.service) {\n throw new Error(\n `${this.constructor.name}: Service is required to load the data`\n );\n }\n if (!this.id) {\n return;\n }\n this.loadSubscription?.unsubscribe();\n this.isWorking = true;\n this.loadSubscription = this.service.show(this.id, this.getShowParams())\n .pipe(finalize(() => this.isWorking = false))\n .subscribe((result: any) => {\n this.model = result?.item;\n this.modelLoaded(this.model);\n });\n }\n\n /**\n * Reload the model of the page\n */\n reload(): void {\n this.loadData();\n }\n\n /**\n * Hook called once the model is loaded\n * Override in child class to compute derived data from the model\n *\n * @param model The loaded model\n */\n protected modelLoaded(model: any): void {\n }\n\n /**\n * Replaces the model of the page, eg: after an update form or status change\n *\n * @param model The updated model\n */\n modelUpdated(model: any): void {\n if (model) {\n this.model = model;\n this.modelLoaded(this.model);\n }\n }\n}\n","import { of } from 'rxjs';\nimport { convertToParamMap } from '@angular/router';\nimport { TechlifyShowComponent } from '../techlify-show.component';\n\nexport interface TechlifyShowContractOptions {\n getComponent: () => TechlifyShowComponent;\n getService: () => any;\n routeParamName?: string;\n}\n\n/**\n * Reusable test contract for components extending TechlifyShowComponent.\n *\n * Ensures all view components follow the same base behaviour:\n * - Service injection\n * - Default state initialization\n * - Reading the id from the route\n * - Loading the model through service.show()\n * - Model update handling\n */\nexport function techlifyShowContract(\n options: TechlifyShowContractOptions\n): void {\n\n describe('TechlifyShowComponent Contract', () => {\n\n /**\n * Ensures child components correctly pass their service\n * to the base view component.\n */\n it('should pass a service to the base class', () => {\n const component = options.getComponent();\n expect((component as any).service)\n .toBe(options.getService());\n });\n\n /**\n * Ensures route listening is started on init.\n */\n it('should listen route changes on init', () => {\n const component = options.getComponent();\n spyOn(component, 'listenRouteChanges');\n (component as any).ngOnInit();\n expect(component.listenRouteChanges)\n .toHaveBeenCalled();\n });\n\n /**\n * Ensures the id is read from the configured route param.\n */\n it('should get id from route params', () => {\n const component = options.getComponent();\n const routeParamName = options.routeParamName ?? 'id';\n const params = convertToParamMap({ [routeParamName]: '5' });\n expect(component.getIdFromRoute(params))\n .toBe('5');\n });\n\n /**\n * Ensures the model is loaded using the show service.\n */\n it('should load the model using show', () => {\n const component = options.getComponent();\n options.getService().show\n .and.returnValue(of({\n item: { id: 5 }\n }));\n component.id = 5;\n component.loadData();\n expect(options.getService().show)\n .toHaveBeenCalledWith(5, jasmine.anything());\n expect(component.model)\n .toEqual({ id: 5 });\n expect(component.isWorking)\n .toBeFalse();\n });\n\n /**\n * Ensures no request is made without an id.\n */\n it('should not load without an id', () => {\n const component = options.getComponent();\n options.getService().show.calls.reset();\n component.id = null;\n component.loadData();\n expect(options.getService().show)\n .not.toHaveBeenCalled();\n });\n\n /**\n * Ensures the model is replaced on update.\n */\n it('should replace the model on modelUpdated', () => {\n const component = options.getComponent();\n component.modelUpdated({ id: 7 });\n expect(component.model)\n .toEqual({ id: 7 });\n });\n\n });\n}\n"],"mappings":"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"}
|
|
@@ -3399,6 +3399,11 @@ var TechlifyFormButtonComponent = class TechlifyFormButtonComponent {
|
|
|
3399
3399
|
this.model = input(...ngDevMode ? [void 0, { debugName: "model" }] : /* istanbul ignore next */ []);
|
|
3400
3400
|
this.data = input({}, ...ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []);
|
|
3401
3401
|
this.width = input("700px", ...ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []);
|
|
3402
|
+
this.tooltip = input("", ...ngDevMode ? [{ debugName: "tooltip" }] : /* istanbul ignore next */ []);
|
|
3403
|
+
this.multiAddComponent = input(...ngDevMode ? [void 0, { debugName: "multiAddComponent" }] : /* istanbul ignore next */ []);
|
|
3404
|
+
this.multiAddData = input(...ngDevMode ? [void 0, { debugName: "multiAddData" }] : /* istanbul ignore next */ []);
|
|
3405
|
+
this.multiAddWidth = input("1000px", ...ngDevMode ? [{ debugName: "multiAddWidth" }] : /* istanbul ignore next */ []);
|
|
3406
|
+
this.multiAddTooltip = input("Multi Add", ...ngDevMode ? [{ debugName: "multiAddTooltip" }] : /* istanbul ignore next */ []);
|
|
3402
3407
|
this.saved = output();
|
|
3403
3408
|
}
|
|
3404
3409
|
open() {
|
|
@@ -3412,6 +3417,14 @@ var TechlifyFormButtonComponent = class TechlifyFormButtonComponent {
|
|
|
3412
3417
|
if (result) this.saved.emit(result);
|
|
3413
3418
|
});
|
|
3414
3419
|
}
|
|
3420
|
+
openMultiAdd() {
|
|
3421
|
+
this.matDialog.open(this.multiAddComponent(), {
|
|
3422
|
+
width: this.multiAddWidth(),
|
|
3423
|
+
data: this.multiAddData() ?? this.data()
|
|
3424
|
+
}).afterClosed().subscribe((result) => {
|
|
3425
|
+
if (result) this.saved.emit(result);
|
|
3426
|
+
});
|
|
3427
|
+
}
|
|
3415
3428
|
static {
|
|
3416
3429
|
this.ɵfac = i0.ɵɵngDeclareFactory({
|
|
3417
3430
|
minVersion: "12.0.0",
|
|
@@ -3424,7 +3437,7 @@ var TechlifyFormButtonComponent = class TechlifyFormButtonComponent {
|
|
|
3424
3437
|
}
|
|
3425
3438
|
static {
|
|
3426
3439
|
this.ɵcmp = i0.ɵɵngDeclareComponent({
|
|
3427
|
-
minVersion: "17.
|
|
3440
|
+
minVersion: "17.0.0",
|
|
3428
3441
|
version: "22.2.3",
|
|
3429
3442
|
type: TechlifyFormButtonComponent,
|
|
3430
3443
|
isStandalone: true,
|
|
@@ -3457,11 +3470,46 @@ var TechlifyFormButtonComponent = class TechlifyFormButtonComponent {
|
|
|
3457
3470
|
isSignal: true,
|
|
3458
3471
|
isRequired: false,
|
|
3459
3472
|
transformFunction: null
|
|
3473
|
+
},
|
|
3474
|
+
tooltip: {
|
|
3475
|
+
classPropertyName: "tooltip",
|
|
3476
|
+
publicName: "tooltip",
|
|
3477
|
+
isSignal: true,
|
|
3478
|
+
isRequired: false,
|
|
3479
|
+
transformFunction: null
|
|
3480
|
+
},
|
|
3481
|
+
multiAddComponent: {
|
|
3482
|
+
classPropertyName: "multiAddComponent",
|
|
3483
|
+
publicName: "multiAddComponent",
|
|
3484
|
+
isSignal: true,
|
|
3485
|
+
isRequired: false,
|
|
3486
|
+
transformFunction: null
|
|
3487
|
+
},
|
|
3488
|
+
multiAddData: {
|
|
3489
|
+
classPropertyName: "multiAddData",
|
|
3490
|
+
publicName: "multiAddData",
|
|
3491
|
+
isSignal: true,
|
|
3492
|
+
isRequired: false,
|
|
3493
|
+
transformFunction: null
|
|
3494
|
+
},
|
|
3495
|
+
multiAddWidth: {
|
|
3496
|
+
classPropertyName: "multiAddWidth",
|
|
3497
|
+
publicName: "multiAddWidth",
|
|
3498
|
+
isSignal: true,
|
|
3499
|
+
isRequired: false,
|
|
3500
|
+
transformFunction: null
|
|
3501
|
+
},
|
|
3502
|
+
multiAddTooltip: {
|
|
3503
|
+
classPropertyName: "multiAddTooltip",
|
|
3504
|
+
publicName: "multiAddTooltip",
|
|
3505
|
+
isSignal: true,
|
|
3506
|
+
isRequired: false,
|
|
3507
|
+
transformFunction: null
|
|
3460
3508
|
}
|
|
3461
3509
|
},
|
|
3462
3510
|
outputs: { saved: "saved" },
|
|
3463
3511
|
ngImport: i0,
|
|
3464
|
-
template: "<app-techlify-icon role=\"button\" [name]=\"model() ? 'edit' : 'add'\" (click)=\"open()\"></app-techlify-icon>\
|
|
3512
|
+
template: "<app-techlify-icon role=\"button\" [name]=\"model() ? 'edit' : 'add'\" [matTooltip]=\"tooltip()\" (click)=\"open()\"></app-techlify-icon>\n@if (multiAddComponent() && !model()) {\n <app-techlify-icon role=\"button\" name=\"batch-add\" [matTooltip]=\"multiAddTooltip()\" class=\"ms-1\"\n (click)=\"openMultiAdd()\"></app-techlify-icon>\n}\n",
|
|
3465
3513
|
dependencies: [
|
|
3466
3514
|
{
|
|
3467
3515
|
kind: "ngmodule",
|
|
@@ -3471,6 +3519,22 @@ var TechlifyFormButtonComponent = class TechlifyFormButtonComponent {
|
|
|
3471
3519
|
kind: "ngmodule",
|
|
3472
3520
|
type: MaterialModule
|
|
3473
3521
|
},
|
|
3522
|
+
{
|
|
3523
|
+
kind: "directive",
|
|
3524
|
+
type: i3$1.MatTooltip,
|
|
3525
|
+
selector: "[matTooltip]",
|
|
3526
|
+
inputs: [
|
|
3527
|
+
"matTooltipPosition",
|
|
3528
|
+
"matTooltipPositionAtOrigin",
|
|
3529
|
+
"matTooltipDisabled",
|
|
3530
|
+
"matTooltipShowDelay",
|
|
3531
|
+
"matTooltipHideDelay",
|
|
3532
|
+
"matTooltipTouchGestures",
|
|
3533
|
+
"matTooltip",
|
|
3534
|
+
"matTooltipClass"
|
|
3535
|
+
],
|
|
3536
|
+
exportAs: ["matTooltip"]
|
|
3537
|
+
},
|
|
3474
3538
|
{
|
|
3475
3539
|
kind: "ngmodule",
|
|
3476
3540
|
type: TechlifyIconModule
|
|
@@ -3502,7 +3566,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
3502
3566
|
MaterialModule,
|
|
3503
3567
|
TechlifyIconModule
|
|
3504
3568
|
],
|
|
3505
|
-
template: "<app-techlify-icon role=\"button\" [name]=\"model() ? 'edit' : 'add'\" (click)=\"open()\"></app-techlify-icon>\
|
|
3569
|
+
template: "<app-techlify-icon role=\"button\" [name]=\"model() ? 'edit' : 'add'\" [matTooltip]=\"tooltip()\" (click)=\"open()\"></app-techlify-icon>\n@if (multiAddComponent() && !model()) {\n <app-techlify-icon role=\"button\" name=\"batch-add\" [matTooltip]=\"multiAddTooltip()\" class=\"ms-1\"\n (click)=\"openMultiAdd()\"></app-techlify-icon>\n}\n"
|
|
3506
3570
|
}]
|
|
3507
3571
|
}],
|
|
3508
3572
|
ctorParameters: () => [{ type: i1$8.MatDialog }],
|
|
@@ -3539,6 +3603,46 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
3539
3603
|
required: false
|
|
3540
3604
|
}]
|
|
3541
3605
|
}],
|
|
3606
|
+
tooltip: [{
|
|
3607
|
+
type: i0.Input,
|
|
3608
|
+
args: [{
|
|
3609
|
+
isSignal: true,
|
|
3610
|
+
alias: "tooltip",
|
|
3611
|
+
required: false
|
|
3612
|
+
}]
|
|
3613
|
+
}],
|
|
3614
|
+
multiAddComponent: [{
|
|
3615
|
+
type: i0.Input,
|
|
3616
|
+
args: [{
|
|
3617
|
+
isSignal: true,
|
|
3618
|
+
alias: "multiAddComponent",
|
|
3619
|
+
required: false
|
|
3620
|
+
}]
|
|
3621
|
+
}],
|
|
3622
|
+
multiAddData: [{
|
|
3623
|
+
type: i0.Input,
|
|
3624
|
+
args: [{
|
|
3625
|
+
isSignal: true,
|
|
3626
|
+
alias: "multiAddData",
|
|
3627
|
+
required: false
|
|
3628
|
+
}]
|
|
3629
|
+
}],
|
|
3630
|
+
multiAddWidth: [{
|
|
3631
|
+
type: i0.Input,
|
|
3632
|
+
args: [{
|
|
3633
|
+
isSignal: true,
|
|
3634
|
+
alias: "multiAddWidth",
|
|
3635
|
+
required: false
|
|
3636
|
+
}]
|
|
3637
|
+
}],
|
|
3638
|
+
multiAddTooltip: [{
|
|
3639
|
+
type: i0.Input,
|
|
3640
|
+
args: [{
|
|
3641
|
+
isSignal: true,
|
|
3642
|
+
alias: "multiAddTooltip",
|
|
3643
|
+
required: false
|
|
3644
|
+
}]
|
|
3645
|
+
}],
|
|
3542
3646
|
saved: [{
|
|
3543
3647
|
type: i0.Output,
|
|
3544
3648
|
args: ["saved"]
|
|
@@ -4839,4 +4943,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
4839
4943
|
});
|
|
4840
4944
|
export { ActionPopupComponent as $, MaterialModule as A, HttpCacheService as B, AuthenticationGuard as C, CustomIconModule as D, TechlifyIconModule as E, HttpService as F, LogLevel as G, ErrorHandlerService as H, TokenInterceptor as I, EnvironmentService as J, Logger as K, CredentialsService as L, FormConfigurationForFormComponent as M, FileDropperComponent as N, TechlifyIconComponent as O, HTTP_DYNAMIC_INTERCEPTORS as P, SuccessSnackComponent as Q, ApiPrefixInterceptor as R, User as S, SnackModule as T, ConfigService as U, ErrorHandlerInterceptor as V, TechlifyConfig as W, AlertService as X, FormValidatorService as Y, ErrorSnackComponent as Z, FilterService as _, TimelineFilterModule as a, CurrentUserService as b, ExportExcelModule as c, ActionPopupModule as d, ActionPopup as et, TechlifyFormButtonComponent as f, TechlifyDeleteButtonComponent as g, Timeline as h, SearchableSelectorComponent as i, ReadMoreComponent as j, CustomIconComponent as k, ExportExcelButtonComponent as l, calculateTimeline as m, FormConfigurationForFormModule as n, ReadMoreModule as o, TimelineFilterComponent as p, DocumentViewerComponent as q, SearchableSelectorModule as r, FileDropperModule as s, SharedModule as t, DocumentViewerModule as u, RouteReusableStrategy as v, AppUpdateService as w, StorageService as x, AuthenticationService as y, CacheInterceptor as z };
|
|
4841
4945
|
|
|
4842
|
-
//# sourceMappingURL=ngx-techlify-core-shared.module-
|
|
4946
|
+
//# sourceMappingURL=ngx-techlify-core-shared.module-C0uAKgWi.mjs.map
|