mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 10:50:00 +00:00
node: added MapModel (#3946)
--------- Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>
This commit is contained in:
parent
b903c60a3a
commit
b19cbba7ad
4 changed files with 225 additions and 9 deletions
|
@ -2,7 +2,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
import test from 'ava';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { private_api } from '../index.js'
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import * as path from 'node:path';
|
|||
import { fileURLToPath } from 'url';
|
||||
import Jimp = require("jimp");
|
||||
|
||||
import { private_api, ImageData, ArrayModel } from '../index.js'
|
||||
import { private_api, ImageData, ArrayModel, Model, MapModel } from '../index.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url);
|
||||
const dirname = path.dirname(filename);
|
||||
|
@ -443,6 +443,45 @@ test('ArrayModel', (t) => {
|
|||
t.deepEqual(structArrayModel.values(), new ArrayModel([{ "name": "simon", "age": 22 }, { "name": "florian", "age": 22 }]).values());
|
||||
})
|
||||
|
||||
test("MapModel", (t) => {
|
||||
let compiler = new private_api.ComponentCompiler();
|
||||
let definition = compiler.buildFromSource(`
|
||||
export component App {
|
||||
in-out property <[string]> model;
|
||||
}`, "");
|
||||
t.not(definition, null);
|
||||
|
||||
let instance = definition!.create();
|
||||
t.not(instance, null);
|
||||
|
||||
interface Name {
|
||||
first: string;
|
||||
last: string;
|
||||
}
|
||||
|
||||
const nameModel: ArrayModel<Name> = new ArrayModel([
|
||||
{ first: "Hans", last: "Emil" },
|
||||
{ first: "Max", last: "Mustermann" },
|
||||
{ first: "Roman", last: "Tisch" },
|
||||
]);
|
||||
|
||||
const mapModel = new MapModel(
|
||||
nameModel,
|
||||
(data) => {
|
||||
return data.last + ", " + data.first;
|
||||
}
|
||||
);
|
||||
|
||||
instance!.setProperty("model", mapModel);
|
||||
|
||||
nameModel.setRowData(1, { first: "Simon", last: "Hausmann" });
|
||||
|
||||
const checkModel = instance!.getProperty("model") as Model<string>;
|
||||
t.is(checkModel.rowData(0), "Emil, Hans");
|
||||
t.is(checkModel.rowData(1), "Hausmann, Simon");
|
||||
t.is(checkModel.rowData(2), "Tisch, Roman");
|
||||
})
|
||||
|
||||
test('ArrayModel rowCount', (t) => {
|
||||
let compiler = new private_api.ComponentCompiler;
|
||||
let definition = compiler.buildFromSource(`
|
||||
|
|
|
@ -118,6 +118,8 @@ export interface ImageData {
|
|||
* A model is organized like a table with rows of data. The
|
||||
* fields of the data type T behave like columns.
|
||||
*
|
||||
* @template T the type of the model's items.
|
||||
*
|
||||
* ### Example
|
||||
* As an example let's see the implementation of {@link ArrayModel}
|
||||
*
|
||||
|
@ -178,6 +180,18 @@ export abstract class Model<T> {
|
|||
this.notify = new NullPeer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Model where all elements are mapped by the function `mapFunction`.
|
||||
* @template T the type of the source model's items.
|
||||
* @param mapFunction functions that maps
|
||||
* @returns a new {@link MapModel} that wraps the current model.
|
||||
*/
|
||||
map<U>(
|
||||
mapFunction: (data: T) => U
|
||||
): MapModel<T, U> {
|
||||
return new MapModel(this, mapFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementations of this function must return the current number of rows.
|
||||
*/
|
||||
|
@ -195,7 +209,11 @@ export abstract class Model<T> {
|
|||
* @param row index in range 0..(rowCount() - 1).
|
||||
* @param data new data item to store on the given row index
|
||||
*/
|
||||
abstract setRowData(row: number, data: T): void;
|
||||
setRowData(_row: number, _data: T): void {
|
||||
console.log(
|
||||
"setRowData called on a model which does not re-implement this method. This happens when trying to modify a read-only model"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the view that the data of the current row is changed.
|
||||
|
@ -209,7 +227,7 @@ export abstract class Model<T> {
|
|||
* Notifies the view that multiple rows are added to the model.
|
||||
* @param row index of the first added row.
|
||||
* @param count the number of added items.
|
||||
*/
|
||||
*/
|
||||
protected notifyRowAdded(row: number, count: number): void {
|
||||
this.notify.rowAdded(row, count);
|
||||
}
|
||||
|
@ -218,7 +236,7 @@ export abstract class Model<T> {
|
|||
* Notifies the view that multiple rows are removed to the model.
|
||||
* @param row index of the first removed row.
|
||||
* @param count the number of removed items.
|
||||
*/
|
||||
*/
|
||||
protected notifyRowRemoved(row: number, count: number): void {
|
||||
this.notify.rowRemoved(row, count);
|
||||
}
|
||||
|
@ -235,10 +253,10 @@ export abstract class Model<T> {
|
|||
* @hidden
|
||||
*/
|
||||
class NullPeer {
|
||||
rowDataChanged(row: number): void { }
|
||||
rowAdded(row: number, count: number): void { }
|
||||
rowRemoved(row: number, count: number): void { }
|
||||
reset(): void { }
|
||||
rowDataChanged(row: number): void {}
|
||||
rowAdded(row: number, count: number): void {}
|
||||
rowRemoved(row: number, count: number): void {}
|
||||
reset(): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -332,6 +350,162 @@ export class ArrayModel<T> extends Model<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides rows that are generated by a map function based on the rows of another Model.
|
||||
*
|
||||
* @template T item type of source model that is mapped to U.
|
||||
* @template U the type of the mapped items
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* Here we have a {@link ArrayModel} holding rows of a custom interface `Name` and a {@link MapModel} that maps the name rows
|
||||
* to single string rows.
|
||||
*
|
||||
* ```ts
|
||||
* import { Model, ArrayModel, MapModel } from "./index";
|
||||
*
|
||||
* interface Name {
|
||||
* first: string;
|
||||
* last: string;
|
||||
* }
|
||||
*
|
||||
* const model = new ArrayModel<Name>([
|
||||
* {
|
||||
* first: "Hans",
|
||||
* last: "Emil",
|
||||
* },
|
||||
* {
|
||||
* first: "Max",
|
||||
* last: "Mustermann",
|
||||
* },
|
||||
* {
|
||||
* first: "Roman",
|
||||
* last: "Tisch",
|
||||
* },
|
||||
* ]);
|
||||
*
|
||||
* const mappedModel = new MapModel(
|
||||
* model,
|
||||
* (data) => {
|
||||
* return data.last + ", " + data.first;
|
||||
* }
|
||||
* );
|
||||
*
|
||||
* // prints "Emil, Hans"
|
||||
* console.log(mappedModel.rowData(0));
|
||||
*
|
||||
* // prints "Mustermann, Max"
|
||||
* console.log(mappedModel.rowData(1));
|
||||
*
|
||||
* // prints "Tisch, Roman"
|
||||
* console.log(mappedModel.rowData(2));
|
||||
*
|
||||
* // Alternatively you can use the shortcut {@link MapModel.map}.
|
||||
*
|
||||
* const model = new ArrayModel<Name>([
|
||||
* {
|
||||
* first: "Hans",
|
||||
* last: "Emil",
|
||||
* },
|
||||
* {
|
||||
* first: "Max",
|
||||
* last: "Mustermann",
|
||||
* },
|
||||
* {
|
||||
* first: "Roman",
|
||||
* last: "Tisch",
|
||||
* },
|
||||
* ]);
|
||||
*
|
||||
* const mappedModel = model.map(
|
||||
* (data) => {
|
||||
* return data.last + ", " + data.first;
|
||||
* }
|
||||
* );
|
||||
*
|
||||
*
|
||||
* // prints "Emil, Hans"
|
||||
* console.log(mappedModel.rowData(0));
|
||||
*
|
||||
* // prints "Mustermann, Max"
|
||||
* console.log(mappedModel.rowData(1));
|
||||
*
|
||||
* // prints "Tisch, Roman"
|
||||
* console.log(mappedModel.rowData(2));
|
||||
*
|
||||
* // You can modifying the underlying {@link ArrayModel}:
|
||||
*
|
||||
* const model = new ArrayModel<Name>([
|
||||
* {
|
||||
* first: "Hans",
|
||||
* last: "Emil",
|
||||
* },
|
||||
* {
|
||||
* first: "Max",
|
||||
* last: "Mustermann",
|
||||
* },
|
||||
* {
|
||||
* first: "Roman",
|
||||
* last: "Tisch",
|
||||
* },
|
||||
* ]);
|
||||
*
|
||||
* const mappedModel = model.map(
|
||||
* (data) => {
|
||||
* return data.last + ", " + data.first;
|
||||
* }
|
||||
* );
|
||||
*
|
||||
* model.setRowData(1, { first: "Minnie", last: "Musterfrau" } );
|
||||
*
|
||||
* // prints "Emil, Hans"
|
||||
* console.log(mappedModel.rowData(0));
|
||||
*
|
||||
* // prints "Musterfrau, Minnie"
|
||||
* console.log(mappedModel.rowData(1));
|
||||
*
|
||||
* // prints "Tisch, Roman"
|
||||
* console.log(mappedModel.rowData(2));
|
||||
* ```
|
||||
*/
|
||||
export class MapModel<T, U> extends Model<U> {
|
||||
readonly sourceModel: Model<T>;
|
||||
#mapFunction: (data: T) => U
|
||||
|
||||
/**
|
||||
* Constructs the MapModel with a source model and map functions.
|
||||
* @template T item type of source model that is mapped to U.
|
||||
* @template U the type of the mapped items.
|
||||
* @param sourceModel the wrapped model.
|
||||
* @param mapFunction maps the data from T to U.
|
||||
*/
|
||||
constructor(
|
||||
sourceModel: Model<T>,
|
||||
mapFunction: (data: T) => U
|
||||
) {
|
||||
super();
|
||||
this.sourceModel = sourceModel;
|
||||
this.#mapFunction = mapFunction;
|
||||
this.notify = this.sourceModel.notify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of entries in the model.
|
||||
*/
|
||||
rowCount(): number {
|
||||
return this.sourceModel.rowCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data at the specified row.
|
||||
* @param row index in range 0..(rowCount() - 1).
|
||||
* @returns undefined if row is out of range otherwise the data.
|
||||
*/
|
||||
rowData(row: number): U {
|
||||
return this.#mapFunction(this.sourceModel.rowData(row));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface describes the public API of a Slint component that is common to all instances. Use this to
|
||||
* show() the window on the screen, access the window and subsequent window properties, or start the
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue