Skip to content

Commit

Permalink
[REF] rework beforeRemove code
Browse files Browse the repository at this point in the history
  • Loading branch information
ged-odoo committed Aug 28, 2021
1 parent 0fa8c41 commit 21c3f61
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 43 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ A very fast virtual dom library
- [Manipulating vnodes](#manipulating-vnodes)
- [Creating vnodes](#creating-vnodes)
- [Configuration](#configuration)
- [Extending `blockdom`](#extending-blockdom)
- [Performance Notes](#performance-notes)
- [About this project](#about-this-project)
- [Credits](#credits)
Expand Down Expand Up @@ -338,6 +339,32 @@ Here is a list of every configuration options in `blockdom`:
`blockdom` has different needs (for example, checking if a component is still
alive).

## Extending `blockdom`

`blockdom` is designed to be used as a lower layer in a "real" framework. Some
frameworks (for example, Owl, which is the one I am working on) will need a way
to add some kind of component system. To do that, it seems like the best way is
to add new types of vnodes.

Here is the interface of a blockdom vnode:

```js
export interface VNode<T = any> {
mount(parent: HTMLElement, afterNode: Node | null): void;
moveBefore(other: T | null, afterNode: Node | null): void;
patch(other: T, withBeforeRemove: boolean): void;
beforeRemove(): void;
remove(): void;
firstNode(): Node | undefined;
}
```

So, to add a new vnode type, we simply need to define an object or a class with
these methods, and it will work with the `mount/patch/remove` methods.

Notice the `beforeRemove` method: it is a method used to let the framework know
that a vnode will be removed. It is called before the node is removed.

## Performance Notes

`blockdom` is very fast, I believe. If you read this section, you may be interested
Expand Down
9 changes: 6 additions & 3 deletions src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ function createBlockClass(
}

beforeRemove() {}

remove() {
elementRemove.call(this.el);
}
Expand Down Expand Up @@ -611,7 +612,7 @@ function createBlockClass(
this.el = el as HTMLElement;
this.parentEl = parent;
}
patch(other: Block) {
patch(other: Block, withBeforeRemove: boolean) {
// note: we don't check for equality here. It should be done by the caller
if (this === other) {
return;
Expand Down Expand Up @@ -646,9 +647,11 @@ function createBlockClass(
const child2 = children2![i];
if (child1) {
if (child2) {
child1.patch(child2);
child1.patch(child2, withBeforeRemove);
} else {
child1.beforeRemove();
if (withBeforeRemove) {
child1.beforeRemove();
}
child1.remove();
children1[i] = undefined;
}
Expand Down
12 changes: 7 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export { html } from "./html";
export interface VNode<T = any> {
mount(parent: HTMLElement, afterNode: Node | null): void;
moveBefore(other: T | null, afterNode: Node | null): void;
patch(other: T): void;
patch(other: T, withBeforeRemove: boolean): void;
beforeRemove(): void;
remove(): void;
firstNode(): Node | undefined;
Expand All @@ -27,12 +27,14 @@ export function mount(vnode: VNode, fixture: HTMLElement) {
vnode.mount(fixture, null);
}

export function patch(vnode1: VNode, vnode2: VNode) {
vnode1.patch(vnode2);
export function patch(vnode1: VNode, vnode2: VNode, withBeforeRemove: boolean = false) {
vnode1.patch(vnode2, withBeforeRemove);
}

export function remove(vnode: VNode) {
vnode.beforeRemove();
export function remove(vnode: VNode, withBeforeRemove: boolean = false) {
if (withBeforeRemove) {
vnode.beforeRemove();
}
vnode.remove();
}

Expand Down
35 changes: 15 additions & 20 deletions src/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ class VList {
anchor: Node | undefined;
parentEl?: HTMLElement | undefined;
singleNode?: boolean | undefined;
withBeforeRemove: boolean;

constructor(children: VNode[], withBeforeRemove: boolean) {
constructor(children: VNode[]) {
this.children = children;
this.withBeforeRemove = withBeforeRemove;
}

mount(parent: HTMLElement, afterNode: Node | null) {
Expand All @@ -44,7 +42,7 @@ class VList {
// todo
}

patch(other: VList) {
patch(other: VList, withBeforeRemove: boolean) {
if (this === other) {
return;
}
Expand All @@ -66,7 +64,6 @@ class VList {

const _anchor = this.anchor!;
const isOnlyChild = this.singleNode;
const withBeforeRemove = this.withBeforeRemove;
const parent = this.parentEl!;

// fast path: no new child => only remove
Expand Down Expand Up @@ -110,7 +107,7 @@ class VList {
let startKey1 = startVn1.key;
let startKey2 = startVn2.key;
if (startKey1 === startKey2) {
cPatch.call(startVn1, startVn2);
cPatch.call(startVn1, startVn2, withBeforeRemove);
ch2[startIdx2] = startVn1;
startVn1 = ch1[++startIdx1];
startVn2 = ch2[++startIdx2];
Expand All @@ -120,7 +117,7 @@ class VList {
let endKey1 = endVn1.key;
let endKey2 = endVn2.key;
if (endKey1 === endKey2) {
cPatch.call(endVn1, endVn2);
cPatch.call(endVn1, endVn2, withBeforeRemove);
ch2[endIdx2] = endVn1;
endVn1 = ch1[--endIdx1];
endVn2 = ch2[--endIdx2];
Expand All @@ -129,7 +126,7 @@ class VList {
// -------------------------------------------------------------------
if (startKey1 === endKey2) {
// bnode moved right
cPatch.call(startVn1, endVn2);
cPatch.call(startVn1, endVn2, withBeforeRemove);
ch2[endIdx2] = startVn1;
const nextChild = ch2[endIdx2 + 1];
cMoveBefore.call(startVn1, nextChild, _anchor);
Expand All @@ -140,7 +137,7 @@ class VList {
// -------------------------------------------------------------------
if (endKey1 === startKey2) {
// bnode moved left
cPatch.call(endVn1, startVn2);
cPatch.call(endVn1, startVn2, withBeforeRemove);
ch2[startIdx2] = endVn1;
const nextChild = ch1[startIdx1];
cMoveBefore.call(endVn1, nextChild, _anchor);
Expand All @@ -156,7 +153,7 @@ class VList {
} else {
const elmToMove = ch1[idxInOld];
cMoveBefore.call(elmToMove, startVn1, null);
cPatch.call(elmToMove, startVn2);
cPatch.call(elmToMove, startVn2, withBeforeRemove);
ch2[startIdx2] = elmToMove;
ch1[idxInOld] = null as any;
}
Expand Down Expand Up @@ -185,14 +182,12 @@ class VList {
}

beforeRemove() {
if (this.withBeforeRemove) {
const children = this.children;
const l = children.length;
if (l) {
const beforeRemove = children[0].beforeRemove;
for (let i = 0; i < l; i++) {
beforeRemove.call(children[i]);
}
const children = this.children;
const l = children.length;
if (l) {
const beforeRemove = children[0].beforeRemove;
for (let i = 0; i < l; i++) {
beforeRemove.call(children[i]);
}
}
}
Expand Down Expand Up @@ -223,8 +218,8 @@ class VList {
}
}

export function list(children: VNode[], withBeforeRemove: boolean = false): VNode<VList> {
return new VList(children, withBeforeRemove);
export function list(children: VNode[]): VNode<VList> {
return new VList(children);
}

function createMapping(ch1: any[], startIdx1: number, endIdx2: number): { [key: string]: any } {
Expand Down
12 changes: 5 additions & 7 deletions src/multi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ const nodeRemoveChild = nodeProto.removeChild;
// Multi NODE
// -----------------------------------------------------------------------------

// TODO!!!!!
// todo: either keep a child or a anchor, but not both
// and use same array!!!, and replacechild

class VMulti {
children: (VNode | undefined)[];
anchors?: Node[] | undefined;
Expand Down Expand Up @@ -61,7 +57,7 @@ class VMulti {
}
}

patch(other: VMulti) {
patch(other: VMulti, withBeforeRemove: boolean) {
if (this === other) {
return;
}
Expand All @@ -74,13 +70,15 @@ class VMulti {
const vn2 = children2[i];
if (vn1) {
if (vn2) {
vn1.patch(vn2);
vn1.patch(vn2, withBeforeRemove);
} else {
const afterNode = vn1.firstNode()!;
const anchor = document.createTextNode("");
anchors[i] = anchor;
nodeInsertBefore.call(parentEl, anchor, afterNode);
vn1.beforeRemove();
if (withBeforeRemove) {
vn1.beforeRemove();
}
vn1.remove();
children1[i] = undefined;
}
Expand Down
8 changes: 5 additions & 3 deletions src/toggler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,19 @@ class VToggler {
this.child.moveBefore(other ? other.child : null, afterNode);
}

patch(other: VToggler) {
patch(other: VToggler, withBeforeRemove: boolean) {
if (this === other) {
return;
}
let child1 = this.child;
let child2 = other.child;
if (this.key === other.key) {
child1.patch(child2);
child1.patch(child2, withBeforeRemove);
} else {
child2.mount(this.parentEl!, child1.firstNode()!);
child1.beforeRemove();
if (withBeforeRemove) {
child1.beforeRemove();
}
child1.remove();
this.child = child2;
this.key = other.key;
Expand Down
2 changes: 1 addition & 1 deletion tests/block_attributes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe("properties", () => {

// rerender with a different value, and patch actual dom, to check that
// input value was properly reset by owl
tree.patch(block(["potato"]));
patch(tree, block(["potato"]));
expect(input.value).toBe("potato");
});

Expand Down
8 changes: 4 additions & 4 deletions tests/remove_hook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe("before remove is called", () => {

let n = 1;
child.beforeRemove = () => n++;
patch(tree, block1([], []));
patch(tree, block1([], []), true);
expect(fixture.innerHTML).toBe("<div></div>");
expect(n).toBe(2);
});
Expand All @@ -43,7 +43,7 @@ describe("before remove is called", () => {

let n = 1;
child.beforeRemove = () => n++;
patch(tree, block1([], []));
patch(tree, block1([], []), true);
expect(fixture.innerHTML).toBe("<p></p>");
expect(n).toBe(2);
});
Expand All @@ -60,7 +60,7 @@ describe("before remove is called", () => {

let n = 1;
child1.beforeRemove = () => n++;
patch(tree, block1([], [multi([])]));
patch(tree, block1([], [multi([])]), true);
expect(fixture.innerHTML).toBe("<div></div>");
expect(n).toBe(2);
});
Expand All @@ -77,7 +77,7 @@ describe("before remove is called", () => {

let n = 1;
child1.beforeRemove = () => n++;
patch(tree, block1([], []));
patch(tree, block1([], []), true);
expect(fixture.innerHTML).toBe("<div></div>");
expect(n).toBe(2);
});
Expand Down

0 comments on commit 21c3f61

Please sign in to comment.