Node.js: Implement Iterable<T> for Model<T>

This makes it much easier to extra data from Models, such as using them
in for loops. This is the equivalent of Model::iter() in Rust.
This commit is contained in:
Simon Hausmann 2024-02-08 10:33:27 +01:00 committed by Simon Hausmann
parent 841cb25565
commit a211a104a1
4 changed files with 87 additions and 3 deletions

View file

@ -427,6 +427,8 @@ test('ArrayModel', (t) => {
let instance = definition!.create();
t.not(instance, null);
t.deepEqual(Array.from(new ArrayModel([3, 2, 1])), [3, 2, 1]);
instance!.setProperty("int-model", new ArrayModel([10, 9, 8]));
let intArrayModel = instance!.getProperty("int-model") as ArrayModel<number>;
@ -608,10 +610,12 @@ test('model from array', (t) => {
instance!.setProperty("int-array", [10, 9, 8]);
let wrapped_int_model = instance!.getProperty("int-array");
t.deepEqual(Array.from(wrapped_int_model), [10, 9, 8]);
t.deepEqual(wrapped_int_model.rowCount(), 3);
t.deepEqual(wrapped_int_model.rowData(0), 10);
t.deepEqual(wrapped_int_model.rowData(1), 9);
t.deepEqual(wrapped_int_model.rowData(2), 8);
t.deepEqual(Array.from(wrapped_int_model), [10, 9, 8]);
instance!.setProperty("string-array", ["Simon", "Olivier", "Auri", "Tobias", "Florian"]);
let wrapped_string_model = instance!.getProperty("string-array");

View file

@ -117,6 +117,31 @@ export interface ImageData {
get height(): number;
}
class ModelIterator<T> implements Iterator<T> {
private row: number;
private model: Model<T>;
constructor(model: Model<T>) {
this.model = model;
this.row = 0;
}
public next(): IteratorResult<T> {
if (this.row < this.model.rowCount()) {
let row = this.row;
this.row++;
return {
done: false,
value: this.model.rowData(row)
}
}
return {
done: true,
value: undefined
}
}
}
/**
* Model<T> is the interface for feeding dynamic data into
* `.slint` views.
@ -176,7 +201,7 @@ export interface ImageData {
*}
* ```
*/
export abstract class Model<T> {
export abstract class Model<T> implements Iterable<T> {
/**
* @hidden
*/
@ -221,6 +246,10 @@ export abstract class Model<T> {
);
}
[Symbol.iterator](): Iterator<T> {
return new ModelIterator(this);
}
/**
* Notifies the view that the data of the current row is changed.
* @param row index of the changed row.

View file

@ -75,7 +75,7 @@ pub fn to_js_unknown(env: &Env, value: &Value) -> Result<JsUnknown> {
maybe_js_model
} else {
let model_wrapper: ReadOnlyRustModel = model.clone().into();
Ok(model_wrapper.into_instance(*env)?.as_object(*env).into_unknown())
model_wrapper.into_js(env)
}
}
_ => env.get_undefined().map(|v| v.into_unknown()),

View file

@ -5,7 +5,7 @@ use std::rc::Rc;
use i_slint_compiler::langtype::Type;
use i_slint_core::model::{Model, ModelNotify, ModelRc};
use napi::bindgen_prelude::*;
use napi::{bindgen_prelude::*, JsSymbol};
use napi::{Env, JsExternal, JsFunction, JsNumber, JsObject, JsUnknown, Result, ValueType};
use crate::{to_js_unknown, to_value, RefCountedReference};
@ -157,4 +157,55 @@ impl ReadOnlyRustModel {
pub fn set_row_data(&self, _env: Env, _row: u32, _data: JsUnknown) {
eprintln!("setRowData called on a model which does not re-implement this method. This happens when trying to modify a read-only model")
}
pub fn into_js(self, env: &Env) -> Result<JsUnknown> {
let model = self.0.clone();
let iterator_env = env.clone();
let mut obj = self.into_instance(*env)?.as_object(*env);
// Implement Iterator protocol by hand until it's stable in napi-rs
let iterator_symbol = env
.get_global()
.and_then(|global| global.get_named_property::<JsObject>("Symbol"))
.and_then(|symbol_obj| symbol_obj.get::<&str, JsSymbol>("iterator"))?
.expect("fatal: Unable to find Symbol.iterator");
obj.set_property(
iterator_symbol,
env.create_function_from_closure("rust model iterator", move |_| {
Ok(ModelIterator { model: model.clone(), row: 0, env: iterator_env }
.into_instance(iterator_env)?
.as_object(iterator_env))
})?,
)?;
Ok(obj.into_unknown())
}
}
#[napi]
pub struct ModelIterator {
model: ModelRc<slint_interpreter::Value>,
row: usize,
env: Env,
}
#[napi]
impl ModelIterator {
#[napi]
pub fn next(&mut self) -> Result<JsUnknown> {
let mut result = self.env.create_object()?;
if self.row >= self.model.row_count() {
result.set_named_property("done", true)?;
} else {
let row = self.row;
self.row += 1;
result.set_named_property(
"value",
self.model.row_data(row).and_then(|value| to_js_unknown(&self.env, &value).ok()),
)?
}
return Ok(result.into_unknown());
}
}