diff --git a/drivers/localStorage.js b/drivers/localStorage.js index 6ab39eb..0db468c 100644 --- a/drivers/localStorage.js +++ b/drivers/localStorage.js @@ -15,6 +15,12 @@ export function getStore(){ addEventListener('storage', e => { // does not trigger on source window! root.item(e.key).value = e.newValue; }); + root.loadItems = function(){ + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + root.item(key); + } + } } return root; } diff --git a/drivers/sql/Items/Db.js b/drivers/sql/Items/Db.js index 7c4c120..0febd90 100644 --- a/drivers/sql/Items/Db.js +++ b/drivers/sql/Items/Db.js @@ -42,5 +42,6 @@ export class Db extends Item { } } + static isPrimitive() { return false; } static ChildClass = Table; } diff --git a/drivers/sql/Items/Row.js b/drivers/sql/Items/Row.js index accba00..0542b70 100644 --- a/drivers/sql/Items/Row.js +++ b/drivers/sql/Items/Row.js @@ -12,8 +12,6 @@ export class Row extends Item { for (const field of fields) this.item(field.name); } - - // select // async selectAll() { // await this.loadItems(); @@ -105,6 +103,7 @@ export class Row extends Item { toString() { return this.key; } valueOf() { return this.key; } + static isPrimitive() { return false; } static ChildClass = Cell; } diff --git a/drivers/sql/Items/Table.js b/drivers/sql/Items/Table.js index b635b58..dd043b8 100644 --- a/drivers/sql/Items/Table.js +++ b/drivers/sql/Items/Table.js @@ -8,7 +8,7 @@ export const Table = class extends Item { this._fields = {}; } async loadItems() { - const rows = await this.parent.query("SELECT * FROM "+this.key);b // todo, just get primaries? + const rows = await this.parent.query("SELECT * FROM "+this.key); // todo, just get primaries? for (const data of rows) { const id = await this.rowId(data); const row = this.item(id); @@ -23,28 +23,28 @@ export const Table = class extends Item { } - async ensure(filter) { - console.error('used?') - const rows = await this.rows(filter); - for (const row of rows) return row; // return first - return this.insert(filter); // else insert, todo: filter? - } - async rows(filter /* limit? */) { - console.error('used?') - const where = await this.objectToWhere(filter); // todo - const all = await this.parent.query("SELECT * FROM "+this.key+" WHERE " + where); - const rows = []; - for (const data of all) { - const id = await this.rowId(data); - const row = this.item(id); - for (const i in data) { - console.log('asyncHandler?'); - row.cell(i).setFromMaster(data[i]); // todo: asyncHandler?? - } - rows.push( row ); - } - return rows; - } + // async ensure(filter) { + // console.error('used?') + // const rows = await this.rows(filter); + // for (const row of rows) return row; // return first + // return this.insert(filter); // else insert, todo: filter? + // } + // async rows(filter /* limit? */) { + // console.error('used?') + // const where = await this.objectToWhere(filter); // todo + // const all = await this.parent.query("SELECT * FROM "+this.key+" WHERE " + where); + // const rows = []; + // for (const data of all) { + // const id = await this.rowId(data); + // const row = this.item(id); + // for (const i in data) { + // console.log('asyncHandler?'); + // row.cell(i).setFromMaster(data[i]); // todo: asyncHandler?? + // } + // rows.push( row ); + // } + // return rows; + // } field(name) { @@ -190,5 +190,6 @@ export const Table = class extends Item { */ + static isPrimitive() { return false; } static ChildClass = Row; }; diff --git a/drivers/sql/Mysql.js b/drivers/sql/Mysql.js index 51a7af4..168fd2d 100644 --- a/drivers/sql/Mysql.js +++ b/drivers/sql/Mysql.js @@ -1,6 +1,7 @@ import { Item } from '../../item.js'; import { Db } from "./Items/Db.js"; -import { Client } from "https://deno.land/x/mysql@v2.10.0/mod.ts"; // Warning: v2.11.0 has a breaking bug! +//import { Client } from "https://deno.land/x/mysql@v2.10.0/mod.ts"; // Warning: v2.11.0 has a breaking bug! +import { Client } from "https://deno.land/x/mysql@v2.12.1/mod.ts"; // Warning: v2.11.0 has a breaking bug! class MysqlDb extends Db { async connect(){ diff --git a/item.js b/item.js index 6e4feed..ad6b076 100644 --- a/item.js +++ b/item.js @@ -53,7 +53,7 @@ export class Item extends EventTarget { $set(value){ const oldValue = this.#value; if (this.constructor.isPrimitive(value)) { - if (!this.#filled || oldValue !== value) { // TODO: we shoul use deepEqual as "primitive" can be an object + if (!this.#filled || !this.constructor.equals(oldValue, value)) { this.#value = value; // structuralClone(value); // TODO: should we clone the value? or should we just use the reference? (if its an object) this.#filled = true; if (!this.#isGetting) { @@ -84,15 +84,11 @@ export class Item extends EventTarget { return this.#value[key]; } remove(){ - if (this.#parent) { - delete this.#parent.#value[this.#key]; - dispatchEvent(this.#parent, 'change', { item: this.#parent, remove: this }); - } else { - throw new Error('cannot remove root item'); - } + if (!this.#parent) throw new Error('cannot remove root item'); + delete this.#parent.#value[this.#key]; + dispatchEvent(this.#parent, 'change', { item: this.#parent, remove: this }); } has(key){ return key in this.#value; } - //asyncHas(key){ return Promise.resolve(key in this.#value) } get proxy(){ return toProxy(this); } @@ -132,6 +128,9 @@ export class Item extends EventTarget { static isPrimitive(value){ return value !== Object(value) || 'toJSON' in value || value instanceof Promise; } + static equals(a, b){ // comparison function between old and new value in case of primitive + if (Object.is(a, b)) return true; // // TODO: we shoul use deepEqual as "primitive" can be an object + } static ChildClass; ChildClass = this.constructor.ChildClass; @@ -159,7 +158,7 @@ let currentEffect = null; * @param {function} fn - A function that executes imeediately and collects the containing items. * @return {function} A function to dispose the effect. */ -export function effect(fn){ +export function effect(fn){ // async? const outer = currentEffect; if (outer) { (outer.nested ??= new Set()).add(fn); @@ -167,7 +166,7 @@ export function effect(fn){ fn.parent = outer; } currentEffect = fn; - fn(); + fn(); // await, so that signals in async functions are collected? currentEffect = outer; return () => fn.disposed = true } diff --git a/tests/denoMysql.js b/tests/denoMysql.js index 2b9d7d8..8805f40 100644 --- a/tests/denoMysql.js +++ b/tests/denoMysql.js @@ -84,7 +84,6 @@ Deno.test("database table", async () => { const row = await table.insert({name: 'Tobias', age: 42}); assertEquals( row.key, '1' ); - const values = table.item(1).item('name').get(); const values2 = table.item(1).item('age').get(); console.log(await Promise.all([values, values2])); // generates only one query diff --git a/tests/mocha.async.html b/tests/mocha.async.html index dc4f4d3..d647eaf 100644 --- a/tests/mocha.async.html +++ b/tests/mocha.async.html @@ -43,7 +43,6 @@ asyncItem.createGetter = async function(...args){ getterRequested++; - console.log('getterRequested') await delay(10); return asyncRootValue; } @@ -160,7 +159,6 @@ asyncItem.value = {b:'new value2', c:'new value2'}; const c = await asyncItem.item('c').value; chai.expect(c).to.equal('new value2'); - console.log(asyncItem) }); /* @@ -232,7 +230,7 @@ describe('normal items with promises', () => { - it('Promise is promitive', async () => { + it('Promise is primitive', async () => { const i = item( Promise.resolve(2) ); const value = await i.value; chai.expect(value).to.equal(2); diff --git a/tests/mocha.html b/tests/mocha.html index b132c34..af2ad87 100644 --- a/tests/mocha.html +++ b/tests/mocha.html @@ -69,6 +69,22 @@ let i = item(date); chai.expect(i.value).to.equal(date); }); + + it('0 not equals -0', () => { + let i = item(0); + let changed = false; + i.addEventListener('change', e => changed = true); + i.value = -0; + chai.expect(changed).to.equal(true); + }); + it('NaN equals NaN', () => { + let i = item(NaN); + let changed = false; + i.addEventListener('change', e => changed = true); + i.value = NaN; + chai.expect(changed).to.equal(false); + }); + }); describe('object value', () => { diff --git a/tests/tree1.html b/tests/tree1.html new file mode 100644 index 0000000..30dc22e --- /dev/null +++ b/tests/tree1.html @@ -0,0 +1,49 @@ + + + + + + + + + + + +
+ + + +
+ + + diff --git a/tools/AsyncDataPoint.js b/tools/AsyncDataPoint.js index f4f9e1f..1684e71 100644 --- a/tools/AsyncDataPoint.js +++ b/tools/AsyncDataPoint.js @@ -1,6 +1,5 @@ /* -Ussage: - +// Ussage: const datapoint = new AsyncDataPoint({ get: () => fetch('https://example.com/todos/1').then(res => res.json()), set: value => fetch('https://example.com/todos/1', {method: 'PUT', body: JSON.stringify(value)}).then(res => res.json()) @@ -8,19 +7,16 @@ const datapoint = new AsyncDataPoint({ datapoint.set({title: 'foo', completed: true}); datapoint.get().then(value => console.log(value)); -// api +// API datapoint.onchange = ({value}) => console.log('value changed', value); datapoint.cacheDuration = 1000; // cache for 1 second datapoint.trustSendingValue = true; // trust sending value: until the sending is done, the value is the new value, despite the uncertainty that the server will fail datapoint.setDebouncePeriod = 5; // debounce period for setter in ms, default 5 -datapoint.setFromMaster({title: 'foo', completed: true}); // set value without saving it to the server +datapoint.setFromMaster({title: 'foo', completed: true}); // set value without saving it to the server (the value comes from the master through an other channel / trusted source) */ export class AsyncDataPoint { - // trustSendingValue = true; - // cacheDuration = 2000; // cache for 2 seconds, false = no cache, true = cache forever - // setDebouncePeriod = 5; // debounce period for setter in ms createGetter = null; // function that returns a promise createSetter = null; // function that returns a promise, if it failed, the promise must be rejected