Skip to content

Commit

Permalink
feat(dom): add prependTo api to nodesequences
Browse files Browse the repository at this point in the history
  • Loading branch information
bigopon committed Oct 19, 2019
1 parent 6007e1a commit b958d57
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 44 deletions.
29 changes: 25 additions & 4 deletions packages/__tests__/runtime-html/dom.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function wrap(inner: string, tag: string): string {
return `<${tag}>${inner}</${tag}>`;
}

function verifyThrows(call: () => void): void {
function verifyThrows(call: () => targetChanged): targetChanged {
let err;
try {
call();
Expand All @@ -19,7 +19,7 @@ function verifyThrows(call: () => void): void {
assert.strictEqual(err instanceof Error, true, `err instanceof Error`);
}

function verifyDoesNotThrow(call: () => void): void {
function verifyDoesNotThrow(call: () => targetChanged): targetChanged {
let err;
try {
call();
Expand Down Expand Up @@ -111,7 +111,7 @@ describe('dom', function () {
// reset doc after each test to clear any spies
const DocumentBackup = Object.create(null);

function restoreBackups(): void {
function restoreBackups(): targetChanged {
Object.assign(ctx.dom, DOMBackup);
Object.assign(ctx.doc, DocumentBackup);
}
Expand Down Expand Up @@ -494,6 +494,27 @@ describe('FragmentNodeSequence', function () {
}
});

describe('prependTo', function() {
for (const width of widthArr) {
for (const depth of depthArr.filter(d => d > 0)) {
it(`should append the view to the parent (depth=${depth},width=${width})`, function() {
const node = ctx.dom.createElement('div');
const fragment = createFragment(ctx, node, 0, depth, width);
sut = new FragmentNodeSequence(ctx.dom, fragment);
const parent = ctx.dom.createElement('div');
sut.prependTo(parent);
assert.strictEqual(parent.childNodes.length, width, `parent.childNodes.length`);
assert.strictEqual(fragment.childNodes.length, 0, `fragment.childNodes.length`);
let i = 0;
while (i < width) {
assert.strictEqual(parent.childNodes.item(i) === sut.childNodes[i], true, `parent.childNodes.item(i) === sut.childNodes[i]`);
i++;
}
});
}
}
});

describe.skip('remove', function () {
for (const width of widthArr) {
for (const depth of depthArr.filter(d => d > 0)) {
Expand Down Expand Up @@ -522,7 +543,7 @@ function createFragment(ctx: HTMLTestContext, node: HTMLElement, level: number,
return root;
}

function appendTree(root: HTMLElement, node: HTMLElement, level: number, depth: number, width: number): void {
function appendTree(root: HTMLElement, node: HTMLElement, level: number, depth: number, width: number): targetChanged {
if (level < depth) {
const children = appendChildren(root, node, width);
for (const child of children) {
Expand Down
36 changes: 36 additions & 0 deletions packages/runtime-html/src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@ export class TextNodeSequence implements INodeSequence {
}
}

public prependTo(parent: Node): void {
if (this.isLinked && !!this.refNode) {
this.addToLinked();
} else {
this.isMounted = true;
parent.insertBefore(this.firstChild, parent.firstChild);
}
}

public remove(): void {
this.isMounted = false;
this.firstChild.remove();
Expand Down Expand Up @@ -448,6 +457,33 @@ export class FragmentNodeSequence implements INodeSequence {
}
}

public prependTo(parent: Node): void {
const targetNode = parent.firstChild;
if (targetNode === null) {
this.appendTo(parent);
return;
}
if (this.isMounted) {
let current = this.firstChild;
const end = this.lastChild;
let next: Node;

while (current != null) {
next = current.nextSibling!;
parent.insertBefore(current, targetNode);

if (current === end) {
break;
}

current = next;
}
} else {
this.isMounted = true;
parent.insertBefore(this.fragment, targetNode);
}
}

public remove(): void {
if (this.isMounted) {
this.isMounted = false;
Expand Down
6 changes: 6 additions & 0 deletions packages/runtime/src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export interface INodeSequence<T extends INode = INode> extends INode {
*/
appendTo(parent: T): void;

/**
* Append this sequence as a child to the start of child list of the parent
*/
prependTo(parent: T): void;

/**
* Remove this sequence from the DOM.
*/
Expand Down Expand Up @@ -182,6 +187,7 @@ const emptySequence: INodeSequence = {
findTargets(): ArrayLike<INode> { return PLATFORM.emptyArray; },
insertBefore(refNode: INode): void { /* do nothing */ },
appendTo(parent: INode): void { /* do nothing */ },
prependTo(parent: INode): void { /* do nothing */ },
remove(): void { /* do nothing */ },
addToLinked(): void { /* do nothing */ },
unlink(): void { /* do nothing */ },
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ export {
export {
ViewModelKind,
IBinding,
IControllerHoldOptions,
ILifecycle,
IViewModel,
IController,
Expand Down
10 changes: 9 additions & 1 deletion packages/runtime/src/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export interface IController<
location?: IRenderLocation<T>;

lockScope(scope: IScope): void;
hold(location: IRenderLocation<T>): void;
hold(location: IRenderLocation<T>, options?: IControllerHoldOptions): void;
release(flags: LifecycleFlags): boolean;
bind(flags: LifecycleFlags, scope?: IScope, partName?: string): ILifecycleTask;
unbind(flags: LifecycleFlags): ILifecycleTask;
Expand All @@ -125,6 +125,14 @@ export interface IController<

export const IController = DI.createInterface<IController>('IController').noDefault();

/**
* Describing characteristics of a render location a controller holds
*/
export interface IControllerHoldOptions {
isContainer?: boolean;
strategy?: 'append' | 'prepend';
}

export interface IRenderContext<T extends INode = INode> extends IContainer {
readonly parentId: number;
render(
Expand Down
29 changes: 25 additions & 4 deletions packages/runtime/src/templating/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ import {
IRenderContext,
IViewCache,
IViewModel,
ViewModelKind
ViewModelKind,
IControllerHoldOptions
} from '../lifecycle';
import {
AggregateContinuationTask,
Expand Down Expand Up @@ -102,6 +103,11 @@ type BindingContext<T extends INode, C extends IViewModel<T>> = IIndexable<C & {
caching(flags: LifecycleFlags): void;
}>;

const defaultControllerHoldOptions: IControllerHoldOptions = {
isContainer: false,
strategy: 'append'
};

export class Controller<
T extends INode = INode,
C extends IViewModel<T> = IViewModel<T>
Expand Down Expand Up @@ -154,6 +160,8 @@ export class Controller<
public nodes?: INodeSequence<T>;
public context?: IContainer | IRenderContext<T>;
public location?: IRenderLocation<T>;
public locationIsContainer?: boolean;
public locationContentStrategy?: 'append' | 'prepend';

// todo: refactor
public constructor(
Expand Down Expand Up @@ -394,9 +402,12 @@ export class Controller<
this.state |= State.hasLockedScope;
}

public hold(location: IRenderLocation<T>): void {
public hold(location: IRenderLocation<T>, holdOptions?: IControllerHoldOptions): void {
holdOptions = holdOptions || defaultControllerHoldOptions;
this.state = (this.state | State.canBeCached) ^ State.canBeCached;
this.location = location;
this.locationIsContainer = !!holdOptions.isContainer;
this.locationContentStrategy = holdOptions.strategy || 'append';
}

public release(flags: LifecycleFlags): boolean {
Expand Down Expand Up @@ -934,9 +945,19 @@ export class Controller<
}

private mountSynthetic(flags: LifecycleFlags): void {
const nodes = this.nodes!; // non null is implied by the hook
const location = this.location!; // non null is implied by the hook
this.state |= State.isMounted;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.nodes!.insertBefore(this.location!); // non-null is implied by the hook

if (this.locationIsContainer) {
if (this.locationContentStrategy === 'append') {
nodes.appendTo(location as T);
} else {
nodes.prependTo(location as T);
}
} else {
nodes.insertBefore(location);
}
}

private unmountCustomElement(flags: LifecycleFlags): void {
Expand Down
Loading

0 comments on commit b958d57

Please sign in to comment.