Add array rendering support

TestBranch
KernelDeimos 3 years ago
parent e36620854c
commit a67e04bdc4

@ -1,5 +1,5 @@
import { TAttributes } from "../html/htmldefs"; import { TAttributes } from "../html/htmldefs";
import { ScalarVAO } from "../vao/ScalarVAO.js"; import { VAOUtil } from "../vao/VAOUtil.js";
import { AbstractVAO, Detachable, VAO, VAOConstructor } from "../vao/VAO.js"; import { AbstractVAO, Detachable, VAO, VAOConstructor } from "../vao/VAO.js";
import { Renderable, INotifyMounted } from "./componentdefs"; import { Renderable, INotifyMounted } from "./componentdefs";
@ -132,7 +132,6 @@ export abstract class Component {
element.remove(); element.remove();
} }
// NEXT: update VAO rerender listeners in createElements
const newElements = this.createElements(); const newElements = this.createElements();
referenceElement.replaceWith(...newElements); referenceElement.replaceWith(...newElements);
this.notifyMounted({ this.notifyMounted({
@ -160,7 +159,7 @@ export abstract class Component {
createDynamicVAO_ (vaoName: string, value?: any) { createDynamicVAO_ (vaoName: string, value?: any) {
// TODO: [E], [I] // TODO: [E], [I]
this.vaos_[vaoName] = new ScalarVAO(value ?? null); this.vaos_[vaoName] = VAOUtil.getAppropriateVAO(value);
} }
// TODO: [H] // TODO: [H]

@ -2,6 +2,9 @@ import { TAttributes } from './html/htmldefs.js';
import { Renderable, RenderableConstructor } from './component/componentdefs.js'; import { Renderable, RenderableConstructor } from './component/componentdefs.js';
import { Tag } from './component/Tag.js'; import { Tag } from './component/Tag.js';
import { Component as ExportableComponent } from './component/Component.js'; import { Component as ExportableComponent } from './component/Component.js';
import { ScalarVAO as ExportableScalarVAO } from './vao/ScalarVAO.js';
import { ArrayVAO as ExportableArrayVAO } from './vao/ArrayVAO.js';
import { AbstractVAO as ExportableAbstractVAO } from './vao/VAO.js';
export function createElement( export function createElement(
tag: string | RenderableConstructor, tag: string | RenderableConstructor,
@ -28,3 +31,6 @@ export class Example {
} }
export const Component = ExportableComponent; export const Component = ExportableComponent;
export const AbstractVAO = ExportableAbstractVAO;
export const ScalarVAO = ExportableScalarVAO;
export const ArrayVAO = ExportableArrayVAO;

@ -0,0 +1,74 @@
import { AbstractVAO, TMappingFn, VAO } from "./VAO.js";
import { RenderableVAO } from "./RenderableVAO.js";
import { VAOUtil } from "./VAOUtil.js";
// === TODO ===
// [A] yielded VAO should follow array VAO
// [B] consider if this way of passing detachable can be improved
const DIFF_INSERT = {};
const DIFF_REMOVE = {};
const DIFF_REPLACE = {};
export class ArrayVAO extends AbstractVAO {
private value_: any[]
protected getTopics_(): string[] {
return [
'change',
'item.insert',
'item.remove',
'item.replace'
]
}
constructor (initialValue: any[]) {
super();
if ( ! Array.isArray(initialValue) ) {
throw new Error('ArrayVAO expects an array');
}
this.value_ = initialValue;
}
// TODO: move to AbstractVAO if this isn't modified by version 1.2.0
set (value: any[]) {
let o = this.value_;
this.value_ = value;
if ( o !== this.value_ ) {
this.pub('change');
}
}
get (): any {
return this.value_;
}
each (mappingFn: TMappingFn): VAO[] {
const results = [];
// TODO: [A]
for ( const item of this.value_ ) {
const vao = VAOUtil.getAppropriateVAO(item);
const [renderable, detachable] =
vao.map(mappingFn, RenderableVAO);
// TODO: [B]
;(renderable as RenderableVAO).detachable?.detach?.();
results.push(renderable)
}
return results;
}
// diffArrays_(oldArray, newArray) {
// let ptrOld = 0;
// let ptrNew = 0;
// const events = [];
// while ( ptrOld < oldArray.length ) {
// }
// while ( otrNew < newArray.length ) {
// events.push();
// }
// }
}

@ -0,0 +1,85 @@
import { ScalarVAO } from "./ScalarVAO.js";
import { Detachable } from "./VAO.js";
import { Renderable, INotifyMounted } from "../component/componentdefs.js";
export class RenderableVAO extends ScalarVAO {
protected renderChildren_: Renderable[] | null = null;
protected elements_: Element[];
protected parent_: Element | null = null;
protected rerenderSubscription_: Detachable | null;
public detachable: Detachable | null = null;
constructor (value: any) {
super(value);
this.elements_ = [];
this.rerenderSubscription_ = null;
}
createElements (): Element[] {
let renderables = this.get();
let renderablesTS: Renderable[] = Array.isArray(renderables)
? renderables : [renderables];
const elements = [];
for ( const renderable of renderablesTS ) {
const theseElements = renderable.createElements();
elements.push(...theseElements);
}
this.elements_ = elements;
return elements;
}
rerenderSelf () {
if ( ! this.parent_ ) {
throw new Error('cannot re-render; no existing element');
}
if ( this.elements_.length === 0 ) {
throw new Error('cannot re-render a component with zero elements');
}
this.onWillDetachFromDOM_();
// The reference element lets us know where to put new elements
const referenceElement = this.elements_[0];
// Remove following elements - we're going to replace the first one
for ( const element of this.elements_.slice(1) ) {
element.remove();
}
const newElements = this.createElements();
referenceElement.replaceWith(...newElements);
this.notifyMounted({
parent: this.parent_,
})
}
notifyWillDetachFromDOM () {
if ( this.detachable ) this.detachable.detach();
this.onWillDetachFromDOM_();
}
onWillDetachFromDOM_ () {
if ( this.rerenderSubscription_ ) {
this.rerenderSubscription_.detach();
}
// Detach listeners from child renderables generated last time
if ( ! this.renderChildren_ ) {
throw new Error('tried to detach before rendering');
}
for ( const renderable of this.renderChildren_ ) {
renderable.notifyWillDetachFromDOM();
}
}
notifyMounted(event: INotifyMounted) {
this.parent_ = event.parent;
this.rerenderSubscription_ = this.sub('change', () => {
this.rerenderSelf();
})
}
}

@ -18,11 +18,14 @@ export type TValueEvent = {
extra: any, extra: any,
} }
export type TMappingFn = (v: any) => any
export interface VAO { export interface VAO {
sub (topic: string, listener: () => void): Detachable sub (topic: string, listener: () => void): Detachable
pub (topic: string, extra: any): void pub (topic: string, extra: any): void
get (): any get (): any
set (value: any): void set (value: any): void
map (mappingFn: TMappingFn, outputType?: VAOConstructor):
[VAO, Detachable]
} }
export interface VAOConstructor { export interface VAOConstructor {
@ -86,4 +89,24 @@ export abstract class AbstractVAO implements VAO {
node.listener(event); node.listener(event);
} }
} }
map (mappingFn: TMappingFn, outputType?: VAOConstructor):
[VAO, Detachable] {
if ( ! outputType ) {
throw new Error(
"I want it to use ScalarVAO here but then there would " +
"be a circular dependency"
)
}
const outputVAO = new outputType(mappingFn(this.get()));
const l = () => {
outputVAO.set(mappingFn(this.get()))
}
const sub = this.sub('change', l);
return [outputVAO, sub];
}
} }

@ -0,0 +1,17 @@
import { ScalarVAO } from "./ScalarVAO.js";
import { ArrayVAO } from "./ArrayVAO.js";
import { AbstractVAO, VAO } from "./VAO.js";
export class VAOUtil {
static getAppropriateVAO (value: any): VAO {
if ( value instanceof AbstractVAO ) {
return value;
}
if ( Array.isArray(value) ) {
return new ArrayVAO(value);
}
return new ScalarVAO(value);
}
}
Loading…
Cancel
Save