diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 1e1650e89..045fff3d0 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -104,6 +104,8 @@ module.exports = { ['/api/undo_manager', 'Undo Manager'], ['/api/parser', 'Parser'], ['/api/data_source_manager', 'Data Source Manager'], + ['/api/datasource', `${subDivider}DataSource`], + ['/api/datarecord', `${subDivider}DataRecord`], ], '/': [ '', diff --git a/docs/api.js b/docs/api.js index a9a91b6a6..2d653f3be 100644 --- a/docs/api.js +++ b/docs/api.js @@ -83,6 +83,8 @@ async function generateDocs() { ['pages/model/Page.ts', 'page.md'], ['parser/index.ts', 'parser.md'], ['data_sources/index.ts', 'data_source_manager.md'], + ['data_sources/model/DataSource.ts', 'datasource.md'], + ['data_sources/model/DataRecord.ts', 'datarecord.md'], ].map(async (file) => { const filePath = `${srcRoot}/${file[0]}`; @@ -168,6 +170,9 @@ async function generateDocs() { ['pages/index.ts', 'pages.md'], ['pages/model/Page.ts', 'page.md'], ['parser/index.ts', 'parser.md'], + ['data_sources/index.ts', 'data_source_manager.md'], + ['data_sources/model/DataSource.ts', 'datasource.md'], + ['data_sources/model/DataRecord.ts', 'datarecord.md'], ].map(async (file) => { const filePath = `${srcRoot}/${file[0]}`; diff --git a/docs/api/data_source_manager.md b/docs/api/data_source_manager.md index a0f6a04b1..73400c757 100644 --- a/docs/api/data_source_manager.md +++ b/docs/api/data_source_manager.md @@ -1,12 +1,50 @@ +## DataSources + +This module manages data sources within the editor. +You can initialize the module with the editor by passing an instance of `EditorModel`. + +```js +const editor = new EditorModel(); +const dsm = new DataSourceManager(editor); +``` + +Once the editor is instantiated, you can use the following API to manage data sources: + +```js +const dsm = editor.DataSources; +``` + +* [add][1] - Add a new data source. +* [get][2] - Retrieve a data source by its ID. +* [getAll][3] - Retrieve all data sources. +* [remove][4] - Remove a data source by its ID. +* [clear][5] - Remove all data sources. + +Example of adding a data source: + +```js +const ds = dsm.add({ + id: 'my_data_source_id', + records: [ + { id: 'id1', name: 'value1' }, + { id: 'id2', name: 'value2' } + ] +}); +``` + +### Parameters + +* `em` **EditorModel** Editor model. + ## add Add new data source. ### Parameters -* `props` **[Object][1]** Data source properties. +* `props` **[Object][6]** Data source properties. * `opts` **AddOptions** (optional, default `{}`) ### Examples @@ -29,7 +67,7 @@ Get data source. ### Parameters -* `id` **[String][2]** Data source id. +* `id` **[String][7]** Data source id. ### Examples @@ -45,7 +83,7 @@ Remove data source. ### Parameters -* `id` **([String][2] | [DataSource])** Id of the data source. +* `id` **([String][7] | [DataSource])** Id of the data source. * `opts` **RemoveOptions?** ### Examples @@ -56,6 +94,16 @@ const removed = dsm.remove('DS_ID'); Returns **[DataSource]** Removed data source. -[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object +[1]: #add + +[2]: #get + +[3]: #getall + +[4]: #remove + +[5]: #clear + +[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object -[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String +[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String diff --git a/docs/api/datarecord.md b/docs/api/datarecord.md new file mode 100644 index 000000000..94f88375d --- /dev/null +++ b/docs/api/datarecord.md @@ -0,0 +1,115 @@ + + +## DataRecord + +The `DataRecord` class represents a single record within a data source. +It extends the base `Model` class and provides additional methods and properties specific to data records. +Each `DataRecord` is associated with a `DataSource` and can trigger events when its properties change. + +### DataRecord API + +* [getPath][1] +* [getPaths][2] +* [set][3] + +### Example of Usage + +```js +const record = new DataRecord({ id: 'record1', name: 'value1' }, { collection: dataRecords }); +const path = record.getPath(); // e.g., 'SOURCE_ID.record1' +record.set('name', 'newValue'); +``` + +### Parameters + +* `props` **DataRecordProps** Properties to initialize the data record. +* `opts` **[Object][4]** Options for initializing the data record. + +## getPath + +Get the path of the record. +The path is a string that represents the location of the record within the data source. +Optionally, include a property name to create a more specific path. + +### Parameters + +* `prop` **[String][5]?** Optional property name to include in the path. +* `opts` **[Object][4]?** Options for path generation. + + * `opts.useIndex` **[Boolean][6]?** Whether to use the index of the record in the path. + +### Examples + +```javascript +const pathRecord = record.getPath(); +// e.g., 'SOURCE_ID.record1' +const pathRecord2 = record.getPath('myProp'); +// e.g., 'SOURCE_ID.record1.myProp' +``` + +Returns **[String][5]** The path of the record. + +## getPaths + +Get both ID-based and index-based paths of the record. +Returns an array containing the paths using both ID and index. + +### Parameters + +* `prop` **[String][5]?** Optional property name to include in the paths. + +### Examples + +```javascript +const paths = record.getPaths(); +// e.g., ['SOURCE_ID.record1', 'SOURCE_ID.0'] +``` + +Returns **[Array][7]<[String][5]>** An array of paths. + +## triggerChange + +Trigger a change event for the record. +Optionally, include a property name to trigger a change event for a specific property. + +### Parameters + +* `prop` **[String][5]?** Optional property name to trigger a change event for a specific property. + +## set + +Set a property on the record, optionally using transformers. +If transformers are defined for the record, they will be applied to the value before setting it. + +### Parameters + +* `attributeName` **([String][5] | [Object][4])** The name of the attribute to set, or an object of key-value pairs. +* `value` **any?** The value to set for the attribute. +* `options` **[Object][4]?** Options to apply when setting the attribute. + + * `options.avoidTransformers` **[Boolean][6]?** If true, transformers will not be applied. + +### Examples + +```javascript +record.set('name', 'newValue'); +// Sets 'name' property to 'newValue' +``` + +Returns **[DataRecord][8]** The instance of the DataRecord. + +[1]: #getpath + +[2]: #getpaths + +[3]: #set + +[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object + +[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String + +[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean + +[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array + +[8]: #datarecord diff --git a/docs/api/datasource.md b/docs/api/datasource.md new file mode 100644 index 000000000..305dfd7b3 --- /dev/null +++ b/docs/api/datasource.md @@ -0,0 +1,143 @@ + + +## DataSource + +The `DataSource` class represents a data source within the editor. +It manages a collection of data records and provides methods to interact with them. +The `DataSource` can be extended with transformers to modify records during add, read, and delete operations. + +### DataSource API + +* [addRecord][1] +* [getRecord][2] +* [getRecords][3] +* [removeRecord][4] + +### Example of Usage + +```js +const dataSource = new DataSource({ + records: [ + { id: 'id1', name: 'value1' }, + { id: 'id2', name: 'value2' } + ], + transformers: { + onRecordAdd: ({ record }) => ({ ...record, added: true }), + } +}, { em: editor }); + +dataSource.addRecord({ id: 'id3', name: 'value3' }); +``` + +### Parameters + +* `props` **DataSourceProps** Properties to initialize the data source. +* `opts` **DataSourceOptions** Options to initialize the data source. + +## id + +DataSource id. + +Type: [string][5] + +## records + +DataSource records. + +Type: (DataRecords | [Array][6]\ | [Array][6]\) + +## transformers + +DataSource validation and transformation factories. + +Type: DataSourceTransformers + +## defaults + +Returns the default properties for the data source. +These include an empty array of records and an empty object of transformers. + +Returns **[Object][7]** The default attributes for the data source. + +## constructor + +Initializes a new instance of the `DataSource` class. +It sets up the transformers and initializes the collection of records. +If the `records` property is not an instance of `DataRecords`, it will be converted into one. + +### Parameters + +* `props` **DataSourceProps** Properties to initialize the data source. +* `opts` **DataSourceOptions** Options to initialize the data source. + +## records + +Retrieves the collection of records associated with this data source. + +Returns **DataRecords** The collection of data records. + +## em + +Retrieves the editor model associated with this data source. + +Returns **EditorModel** The editor model. + +## addRecord + +Adds a new record to the data source. +If a transformer is provided for the `onRecordAdd` event, it will be applied to the record before adding it. + +### Parameters + +* `record` **DataRecordProps** The properties of the record to add. +* `opts` **AddOptions?** Options to apply when adding the record. + +Returns **DataRecord** The added data record. + +## getRecord + +Retrieves a record from the data source by its ID. +If a transformer is provided for the `onRecordRead` event, it will be applied to the record before returning it. + +### Parameters + +* `id` **([string][5] | [number][8])** The ID of the record to retrieve. + +Returns **(DataRecord | [undefined][9])** The data record, or `undefined` if no record is found with the given ID. + +## getRecords + +Retrieves all records from the data source. +Each record is processed with the `getRecord` method to apply any read transformers. + +Returns **[Array][6]<(DataRecord | [undefined][9])>** An array of data records. + +## removeRecord + +Removes a record from the data source by its ID. +If a transformer is provided for the `onRecordDelete` event, it will be applied before the record is removed. + +### Parameters + +* `id` **([string][5] | [number][8])** The ID of the record to remove. +* `opts` **RemoveOptions?** Options to apply when removing the record. + +Returns **(DataRecord | [undefined][9])** The removed data record, or `undefined` if no record is found with the given ID. + +[1]: #addrecord + +[2]: #getrecord + +[3]: #getrecords + +[4]: #removerecord + +[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String + +[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array + +[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object + +[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number + +[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined diff --git a/docs/modules/DataSources.md b/docs/modules/DataSources.md index 790785623..23d9313e5 100644 --- a/docs/modules/DataSources.md +++ b/docs/modules/DataSources.md @@ -2,6 +2,277 @@ title: Data Sources --- -# Data Sources +# DataSources -Hey World +## Overview + +**DataSources** are a powerful feature in GrapesJS that allow you to manage and inject data into your components, styles, and traits programmatically. They help you bind dynamic data to your design elements and keep your user interface synchronized with underlying data models. + +### Key Concepts + +1. **DataSource**: A static object with records that can be used throughout GrapesJS. +2. **ComponentDataVariable**: A type of data variable that can be used within components to inject dynamic values. +3. **StyleDataVariable**: A data variable used to bind CSS properties to values in your DataSource. +4. **TraitDataVariable**: A data variable used in component traits to bind data to various UI elements. +5. **Transformers**: Methods for validating and transforming data records in a DataSource. + +## Creating and Adding DataSources + +To start using DataSources, you need to create them and add them to GrapesJS. + +**Example: Creating and Adding a DataSource** + +```ts +const editor = grapesjs.init({ + container: '#gjs', +}); + +const datasource = { + id: 'my-datasource', + records: [ + { id: 'id1', content: 'Hello World' }, + { id: 'id2', color: 'red' }, + ], +}; + +editor.DataSources.add(datasource); +``` + +## Using DataSources with Components + +You can reference DataSources within your components to dynamically inject data. + +**Example: Using DataSources with Components** + +```ts +editor.addComponents([ + { + tagName: 'h1', + type: 'text', + components: [ + { + type: 'data-variable', + value: 'default', + path: 'my-datasource.id1.content', + }, + ], + }, +]); +``` + +In this example, the `h1` component will display "Hello World" by fetching the content from the DataSource with the path `my-datasource.id1.content`. + +## Using DataSources with Styles + +DataSources can also be used to bind data to CSS properties. + +**Example: Using DataSources with Styles** + +```ts +editor.addComponents([ + { + tagName: 'h1', + type: 'text', + components: [ + { + type: 'data-variable', + value: 'default', + path: 'my-datasource.id1.content', + }, + ], + style: { + color: { + type: 'data-variable', + value: 'red', + path: 'my-datasource.id2.color', + }, + }, + }, +]); +``` + +Here, the `h1` component's color will be set to red, as specified in the DataSource at `my-datasource.id2.color`. + +## Using DataSources with Traits + +Traits are used to bind DataSource values to component properties, such as input fields. + +**Example: Using DataSources with Traits** + +```ts +const datasource = { + id: 'my-datasource', + records: [{ id: 'id1', value: 'I Love Grapes' }], +}; +editor.DataSources.add(datasource); + +editor.addComponents([ + { + tagName: 'input', + traits: [ + 'name', + 'type', + { + type: 'text', + label: 'Value', + name: 'value', + value: { + type: 'data-variable', + value: 'default', + path: 'my-datasource.id1.value', + }, + }, + ], + }, +]); +``` + +In this case, the value of the input field is bound to the DataSource value at `my-datasource.id1.value`. + +## DataSource Transformers + +Transformers in DataSources allow you to customize how data is processed during various stages of interaction with the data. The primary transformer functions include: + +### 1. `onRecordAdd` + +This transformer is triggered when a new record is added to the data source. It allows for modification or enrichment of the record before it is stored. + +#### Example Usage + +```javascript +const testDataSource = { + id: 'test-data-source', + records: [], + transformers: { + onRecordAdd: ({ record }) => { + record.content = record.content.toUpperCase(); + return record; + }, + }, +}; +``` + +In this example, every record added will have its `content` field converted to uppercase. + +### 2. `onRecordSet` + +This transformer is invoked when a record's property is updated. It provides an opportunity to validate or transform the new value. + +#### Example Usage + +```javascript +const testDataSource = { + id: 'test-data-source', + records: [], + transformers: { + onRecordSet: ({ id, key, value }) => { + if (key !== 'content') { + return value; + } + if (typeof value !== 'string') { + throw new Error('Value must be a string'); + } + return value.toUpperCase(); + }, + }, +}; +``` + +Here, the transformer ensures that the `content` field is always a string and transforms it to uppercase. + +### 3. `onRecordRead` + +This transformer is used when a record is read from the data source. It allows for post-processing of the data before it is returned. + +#### Example Usage + +```javascript +const testDataSource = { + id: 'test-data-source', + records: [], + transformers: { + onRecordRead: ({ record }) => { + const content = record.get('content'); + return record.set('content', content.toUpperCase(), { avoidTransformers: true }); + }, + }, +}; +``` + +In this example, the `content` field of a record is converted to uppercase when read. + +### 4. `onRecordDelete` + +This transformer is invoked when a record is about to be deleted. It can be used to prevent deletion or to perform additional actions before the record is removed. + +#### Example Usage + +```javascript +const testDataSource = { + id: 'test-data-source', + records: [], + transformers: { + onRecordDelete: ({ record }) => { + if (record.get('content') === 'i love grapes') { + throw new Error('Cannot delete record with content "i love grapes"'); + } + }, + }, +}; +``` + +In this scenario, a record with the `content` of `"i love grapes"` cannot be deleted. + +--- + +These transformers can be customized to meet specific needs, ensuring that data is managed and manipulated in a way that fits your application requirements. + +## Benefits of Using DataSources + +DataSources are integrated with GrapesJS's runtime and BackboneJS models, enabling dynamic updates and synchronization between your data and UI components. This allows you to: + +1. **Inject Configuration**: Manage and inject configuration settings dynamically. +2. **Manage Global Themes**: Apply and update global styling themes. +3. **Mock & Test**: Use DataSources for testing and mocking data during development. +4. **Integrate with Third-Party Services**: Connect and synchronize with external data sources and services. + +**Example: Using DataSources to Manage a Counter** + +```ts +const datasource = { + id: 'my-datasource', + records: [{ id: 'id1', counter: 0 }], +}; + +editor.DataSources.add(datasource); + +editor.addComponents([ + { + tagName: 'span', + type: 'text', + components: [ + { + type: 'data-variable', + value: 'default', + path: 'my-datasource.id1.counter', + }, + ], + }, +]); + +const ds = editor.DataSources.get('my-datasource'); +setInterval(() => { + console.log('Incrementing counter'); + const counterRecord = ds.getRecord('id1'); + counterRecord.set({ counter: counterRecord.get('counter') + 1 }); +}, 1000); +``` + +In this example, a counter is dynamically updated and displayed in the UI, demonstrating the real-time synchronization capabilities of DataSources. + +**Examples of How DataSources Could Be Used:** + +1. Injecting configuration +2. Managing global themes +3. Mocking & testing +4. Third-party integrations diff --git a/src/data_sources/index.ts b/src/data_sources/index.ts index 6075a04db..4aeb227c0 100644 --- a/src/data_sources/index.ts +++ b/src/data_sources/index.ts @@ -1,3 +1,40 @@ +/** + * This module manages data sources within the editor. + * You can initialize the module with the editor by passing an instance of `EditorModel`. + * + * ```js + * const editor = new EditorModel(); + * const dsm = new DataSourceManager(editor); + * ``` + * + * Once the editor is instantiated, you can use the following API to manage data sources: + * + * ```js + * const dsm = editor.DataSources; + * ``` + * + * * [add](#add) - Add a new data source. + * * [get](#get) - Retrieve a data source by its ID. + * * [getAll](#getall) - Retrieve all data sources. + * * [remove](#remove) - Remove a data source by its ID. + * * [clear](#clear) - Remove all data sources. + * + * Example of adding a data source: + * + * ```js + * const ds = dsm.add({ + * id: 'my_data_source_id', + * records: [ + * { id: 'id1', name: 'value1' }, + * { id: 'id2', name: 'value2' } + * ] + * }); + * ``` + * + * @module DataSources + * @param {EditorModel} em - Editor model. + */ + import { ItemManagerModule, ModuleConfig } from '../abstract/Module'; import { AddOptions, RemoveOptions } from '../common'; import EditorModel from '../editor/model/Editor'; diff --git a/src/data_sources/model/DataRecord.ts b/src/data_sources/model/DataRecord.ts index 88b0934dd..cdb90333d 100644 --- a/src/data_sources/model/DataRecord.ts +++ b/src/data_sources/model/DataRecord.ts @@ -1,3 +1,28 @@ +/** + * The `DataRecord` class represents a single record within a data source. + * It extends the base `Model` class and provides additional methods and properties specific to data records. + * Each `DataRecord` is associated with a `DataSource` and can trigger events when its properties change. + * + * ### DataRecord API + * + * * [getPath](#getpath) + * * [getPaths](#getpaths) + * * [set](#set) + * + * ### Example of Usage + * + * ```js + * const record = new DataRecord({ id: 'record1', name: 'value1' }, { collection: dataRecords }); + * const path = record.getPath(); // e.g., 'SOURCE_ID.record1' + * record.set('name', 'newValue'); + * ``` + * + * @module DataRecord + * @param {DataRecordProps} props - Properties to initialize the data record. + * @param {Object} opts - Options for initializing the data record. + * @extends {Model} + */ + import { keys } from 'underscore'; import { Model, SetOptions } from '../../common'; import { DataRecordProps, DataSourcesEvents } from '../types'; @@ -28,20 +53,33 @@ export default class DataRecord ext return this.cl.indexOf(this); } + /** + * Handles changes to the record's attributes. + * This method triggers a change event for each property that has been altered. + * + * @private + * @name handleChange + */ handleChange() { const changed = this.changedAttributes(); keys(changed).forEach((prop) => this.triggerChange(prop)); } /** - * Get path of the record - * @param {String} prop Property name to include - * @returns {String} + * Get the path of the record. + * The path is a string that represents the location of the record within the data source. + * Optionally, include a property name to create a more specific path. + * + * @param {String} [prop] - Optional property name to include in the path. + * @param {Object} [opts] - Options for path generation. + * @param {Boolean} [opts.useIndex] - Whether to use the index of the record in the path. + * @returns {String} - The path of the record. + * @name getPath * @example * const pathRecord = record.getPath(); - * // eg. 'SOURCE_ID.RECORD_ID' + * // e.g., 'SOURCE_ID.record1' * const pathRecord2 = record.getPath('myProp'); - * // eg. 'SOURCE_ID.RECORD_ID.myProp' + * // e.g., 'SOURCE_ID.record1.myProp' */ getPath(prop?: string, opts: { useIndex?: boolean } = {}) { const { dataSource, id, index } = this; @@ -50,10 +88,28 @@ export default class DataRecord ext return `${dsId}.${opts.useIndex ? index : id}${suffix}`; } + /** + * Get both ID-based and index-based paths of the record. + * Returns an array containing the paths using both ID and index. + * + * @param {String} [prop] - Optional property name to include in the paths. + * @returns {Array} - An array of paths. + * @name getPaths + * @example + * const paths = record.getPaths(); + * // e.g., ['SOURCE_ID.record1', 'SOURCE_ID.0'] + */ getPaths(prop?: string) { return [this.getPath(prop), this.getPath(prop, { useIndex: true })]; } + /** + * Trigger a change event for the record. + * Optionally, include a property name to trigger a change event for a specific property. + * + * @param {String} [prop] - Optional property name to trigger a change event for a specific property. + * @name triggerChange + */ triggerChange(prop?: string) { const { dataSource, em } = this; const data = { dataSource, dataRecord: this }; @@ -61,6 +117,20 @@ export default class DataRecord ext paths.forEach((path) => em.trigger(`${DataSourcesEvents.path}:${path}`, { ...data, path })); } + /** + * Set a property on the record, optionally using transformers. + * If transformers are defined for the record, they will be applied to the value before setting it. + * + * @param {String|Object} attributeName - The name of the attribute to set, or an object of key-value pairs. + * @param {any} [value] - The value to set for the attribute. + * @param {Object} [options] - Options to apply when setting the attribute. + * @param {Boolean} [options.avoidTransformers] - If true, transformers will not be applied. + * @returns {DataRecord} - The instance of the DataRecord. + * @name set + * @example + * record.set('name', 'newValue'); + * // Sets 'name' property to 'newValue' + */ set>( attributeName: Partial | A, value?: SetOptions | T[A] | undefined, diff --git a/src/data_sources/model/DataSource.ts b/src/data_sources/model/DataSource.ts index 76153f734..2194bdf08 100644 --- a/src/data_sources/model/DataSource.ts +++ b/src/data_sources/model/DataSource.ts @@ -1,15 +1,81 @@ +/** + * The `DataSource` class represents a data source within the editor. + * It manages a collection of data records and provides methods to interact with them. + * The `DataSource` can be extended with transformers to modify records during add, read, and delete operations. + * + * ### DataSource API + * + * * [addRecord](#addrecord) + * * [getRecord](#getrecord) + * * [getRecords](#getrecords) + * * [removeRecord](#removerecord) + * + * ### Example of Usage + * + * ```js + * const dataSource = new DataSource({ + * records: [ + * { id: 'id1', name: 'value1' }, + * { id: 'id2', name: 'value2' } + * ], + * transformers: { + * onRecordAdd: ({ record }) => ({ ...record, added: true }), + * } + * }, { em: editor }); + * + * dataSource.addRecord({ id: 'id3', name: 'value3' }); + * ``` + * + * @module DataSource + * @param {DataSourceProps} props - Properties to initialize the data source. + * @param {DataSourceOptions} opts - Options to initialize the data source. + * @extends {Model} + */ + import { AddOptions, CombinedModelConstructorOptions, Model, RemoveOptions } from '../../common'; import EditorModel from '../../editor/model/Editor'; -import { DataRecordProps, DataSourceProps, DataSourceTransformers } from '../types'; +import { DataRecordProps } from '../types'; import DataRecord from './DataRecord'; import DataRecords from './DataRecords'; import DataSources from './DataSources'; interface DataSourceOptions extends CombinedModelConstructorOptions<{ em: EditorModel }, DataSource> {} +export interface DataSourceProps { + /** + * DataSource id. + */ + id: string; + + /** + * DataSource records. + */ + records?: DataRecords | DataRecord[] | DataRecordProps[]; + + /** + * DataSource validation and transformation factories. + */ + + transformers?: DataSourceTransformers; +} + +export interface DataSourceTransformers { + onRecordAdd?: (args: { record: DataRecordProps }) => DataRecordProps; + onRecordSet?: (args: { id: string | number; key: string; value: any }) => any; + onRecordDelete?: (args: { record: DataRecord }) => void; + onRecordRead?: (args: { record: DataRecord }) => DataRecord; +} + export default class DataSource extends Model { transformers: DataSourceTransformers; + /** + * Returns the default properties for the data source. + * These include an empty array of records and an empty object of transformers. + * + * @returns {Object} The default attributes for the data source. + * @name defaults + */ defaults() { return { records: [], @@ -17,6 +83,15 @@ export default class DataSource extends Model { }; } + /** + * Initializes a new instance of the `DataSource` class. + * It sets up the transformers and initializes the collection of records. + * If the `records` property is not an instance of `DataRecords`, it will be converted into one. + * + * @param {DataSourceProps} props - Properties to initialize the data source. + * @param {DataSourceOptions} opts - Options to initialize the data source. + * @name constructor + */ constructor(props: DataSourceProps, opts: DataSourceOptions) { super(props, opts); const { records, transformers } = props; @@ -29,18 +104,47 @@ export default class DataSource extends Model { this.listenTo(this.records, 'add', this.onAdd); } + /** + * Retrieves the collection of records associated with this data source. + * + * @returns {DataRecords} The collection of data records. + * @name records + */ get records() { return this.attributes.records as DataRecords; } + /** + * Retrieves the editor model associated with this data source. + * + * @returns {EditorModel} The editor model. + * @name em + */ get em() { return (this.collection as unknown as DataSources).em; } + /** + * Handles the `add` event for records in the data source. + * This method triggers a change event on the newly added record. + * + * @param {DataRecord} dr - The data record that was added. + * @private + * @name onAdd + */ onAdd(dr: DataRecord) { dr.triggerChange(); } + /** + * Adds a new record to the data source. + * If a transformer is provided for the `onRecordAdd` event, it will be applied to the record before adding it. + * + * @param {DataRecordProps} record - The properties of the record to add. + * @param {AddOptions} [opts] - Options to apply when adding the record. + * @returns {DataRecord} The added data record. + * @name addRecord + */ addRecord(record: DataRecordProps, opts?: AddOptions) { const onRecordAdd = this.transformers.onRecordAdd; if (onRecordAdd) { @@ -50,6 +154,14 @@ export default class DataSource extends Model { return this.records.add(record, opts); } + /** + * Retrieves a record from the data source by its ID. + * If a transformer is provided for the `onRecordRead` event, it will be applied to the record before returning it. + * + * @param {string | number} id - The ID of the record to retrieve. + * @returns {DataRecord | undefined} The data record, or `undefined` if no record is found with the given ID. + * @name getRecord + */ getRecord(id: string | number): DataRecord | undefined { const onRecordRead = this.transformers.onRecordRead; const record = this.records.get(id); @@ -60,10 +172,26 @@ export default class DataSource extends Model { return record; } + /** + * Retrieves all records from the data source. + * Each record is processed with the `getRecord` method to apply any read transformers. + * + * @returns {Array} An array of data records. + * @name getRecords + */ getRecords() { return [...this.records.models].map((record) => this.getRecord(record.id)); } + /** + * Removes a record from the data source by its ID. + * If a transformer is provided for the `onRecordDelete` event, it will be applied before the record is removed. + * + * @param {string | number} id - The ID of the record to remove. + * @param {RemoveOptions} [opts] - Options to apply when removing the record. + * @returns {DataRecord | undefined} The removed data record, or `undefined` if no record is found with the given ID. + * @name removeRecord + */ removeRecord(id: string | number, opts?: RemoveOptions): DataRecord | undefined { const onRecordDelete = this.transformers.onRecordDelete; const record = this.getRecord(id); diff --git a/src/data_sources/types.ts b/src/data_sources/types.ts index 756312de0..ca913fb09 100644 --- a/src/data_sources/types.ts +++ b/src/data_sources/types.ts @@ -1,31 +1,4 @@ import { ObjectAny } from '../common'; -import DataRecord from './model/DataRecord'; -import DataRecords from './model/DataRecords'; - -export interface DataSourceProps { - /** - * DataSource id. - */ - id: string; - - /** - * DataSource records. - */ - records?: DataRecords | DataRecord[] | DataRecordProps[]; - - /** - * DataSource validation and transformation factories. - */ - - transformers?: DataSourceTransformers; -} - -export interface DataSourceTransformers { - onRecordAdd?: (args: { record: DataRecordProps }) => DataRecordProps; - onRecordSet?: (args: { id: string | number; key: string; value: any }) => any; - onRecordDelete?: (args: { record: DataRecord }) => void; - onRecordRead?: (args: { record: DataRecord }) => DataRecord; -} export interface DataRecordProps extends ObjectAny { /**