From 1b4e09231948d2f66122e3b3231a5081672dcd92 Mon Sep 17 00:00:00 2001 From: danstarns Date: Wed, 7 Aug 2024 09:21:49 -0700 Subject: [PATCH] init: DataSourceTransformers --- src/common/index.ts | 2 +- src/data_sources/model/DataRecords.ts | 21 ++++++++++ src/data_sources/model/DataSource.ts | 13 ++++++- src/data_sources/types.ts | 13 +++++++ test/specs/data_sources/index.ts | 55 +++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 3 deletions(-) diff --git a/src/common/index.ts b/src/common/index.ts index 57c000ce6..4184d1898 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -5,7 +5,7 @@ interface NOOP {} export type Debounced = Function & { cancel(): void }; -export type SetOptions = Backbone.ModelSetOptions & { avoidStore?: boolean }; +export type SetOptions = Backbone.ModelSetOptions & { avoidStore?: boolean; avoidTransformers?: boolean }; export type AddOptions = Backbone.AddOptions & { temporary?: boolean; action?: string }; diff --git a/src/data_sources/model/DataRecords.ts b/src/data_sources/model/DataRecords.ts index 87a172dae..f1ca18eda 100644 --- a/src/data_sources/model/DataRecords.ts +++ b/src/data_sources/model/DataRecords.ts @@ -1,8 +1,11 @@ +import { AddOptions as _AddOptions } from 'backbone'; import { Collection } from '../../common'; import { DataRecordProps } from '../types'; import DataRecord from './DataRecord'; import DataSource from './DataSource'; +type AddOptions = _AddOptions & { avoidTransformers?: boolean }; + export default class DataRecords extends Collection { dataSource: DataSource; @@ -10,6 +13,24 @@ export default class DataRecords extends Collection { super(models, options); this.dataSource = options.dataSource; } + + add(model: {} | DataRecord, options?: AddOptions): DataRecord; + add(models: ({} | DataRecord)[], options?: AddOptions): DataRecord[]; + add(models: unknown, options?: AddOptions): DataRecord | DataRecord[] { + const onRecordInsert = this.dataSource?.transformers?.onRecordInsert; + + if (options?.avoidTransformers) { + return super.add(models as DataRecord, options); + } + + if (onRecordInsert) { + const m = (Array.isArray(models) ? models : [models]).map(onRecordInsert); + + return super.add(m, options); + } else { + return super.add(models as DataRecord, options); + } + } } DataRecords.prototype.model = DataRecord; diff --git a/src/data_sources/model/DataSource.ts b/src/data_sources/model/DataSource.ts index e3cf1e14c..c88e59dbb 100644 --- a/src/data_sources/model/DataSource.ts +++ b/src/data_sources/model/DataSource.ts @@ -1,6 +1,6 @@ import { AddOptions, CombinedModelConstructorOptions, Model, RemoveOptions } from '../../common'; import EditorModel from '../../editor/model/Editor'; -import { DataRecordProps, DataSourceProps } from '../types'; +import { DataRecordProps, DataSourceProps, DataSourceTransformers } from '../types'; import DataRecord from './DataRecord'; import DataRecords from './DataRecords'; import DataSources from './DataSources'; @@ -8,15 +8,19 @@ import DataSources from './DataSources'; interface DataSourceOptions extends CombinedModelConstructorOptions<{ em: EditorModel }, DataSource> {} export default class DataSource extends Model { + transformers: DataSourceTransformers; + defaults() { return { records: [], + transformers: {}, }; } constructor(props: DataSourceProps, opts: DataSourceOptions) { super(props, opts); - const { records } = props; + const { records, transformers } = props; + this.transformers = transformers || {}; if (!(records instanceof DataRecords)) { this.set({ records: new DataRecords(records!, { dataSource: this }) }); @@ -38,6 +42,11 @@ export default class DataSource extends Model { } addRecord(record: DataRecordProps, opts?: AddOptions) { + const onRecordInsert = this.transformers.onRecordInsert; + if (onRecordInsert) { + record = onRecordInsert(record); + } + return this.records.add(record, opts); } diff --git a/src/data_sources/types.ts b/src/data_sources/types.ts index cbd4ddaa6..544dd86d3 100644 --- a/src/data_sources/types.ts +++ b/src/data_sources/types.ts @@ -12,6 +12,19 @@ export interface DataSourceProps { * DataSource records. */ records?: DataRecords | DataRecord[] | DataRecordProps[]; + + /** + * DataSource validation and transformation factories. + */ + + transformers?: DataSourceTransformers; +} + +export interface DataSourceTransformers { + onRecordInsert?: (record: DataRecordProps) => DataRecordProps; + onRecordUpdate?: (record: DataRecord) => DataRecord; + onRecordDelete?: (record: DataRecord) => DataRecord; + onRecordRead?: (record: DataRecord) => DataRecord; } export interface DataRecordProps extends ObjectAny { diff --git a/test/specs/data_sources/index.ts b/test/specs/data_sources/index.ts index 53f67474b..d6f682957 100644 --- a/test/specs/data_sources/index.ts +++ b/test/specs/data_sources/index.ts @@ -126,6 +126,61 @@ describe('DataSourceManager', () => { }); }); + describe.only('Transformers', () => { + let fixtures: HTMLElement; + let cmpRoot: ComponentWrapper; + + beforeEach(() => { + document.body.innerHTML = '
'; + const { Pages, Components } = em; + Pages.onLoad(); + cmpRoot = Components.getWrapper()!; + const View = Components.getType('wrapper')!.view; + const wrapperEl = new View({ + model: cmpRoot, + config: { ...cmpRoot.config, em }, + }); + wrapperEl.render(); + fixtures = document.body.querySelector('#fixtures')!; + fixtures.appendChild(wrapperEl.el); + }); + + test('onRecordInsert', () => { + const testDataSource: DataSourceProps = { + id: 'test-data-source', + records: [], + transformers: { + onRecordInsert: record => { + record.content = record.content.toUpperCase(); + return record; + }, + }, + }; + dsm.add(testDataSource); + + const cmp = cmpRoot.append({ + tagName: 'h1', + type: 'text', + components: [ + { + type: 'data-variable', + value: 'default', + path: 'test-data-source.id1.content', + }, + ], + })[0]; + + const ds = dsm.get('test-data-source'); + ds.addRecord({ id: 'id1', content: 'i love grapes' }); + + const el = cmp.getEl(); + expect(el?.innerHTML).toContain('I LOVE GRAPES'); + + const result = ds.getRecord('id1')?.get('content'); + expect(result).toBe('I LOVE GRAPES'); + }); + }); + test('add DataSource with records', () => { const eventAdd = jest.fn(); em.on(dsm.events.add, eventAdd);