Skip to content

Commit

Permalink
feat(view): finished first pass content view and observer work
Browse files Browse the repository at this point in the history
  • Loading branch information
EisenbergEffect committed May 8, 2018
1 parent 6dcbadc commit 3927f90
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 199 deletions.
212 changes: 117 additions & 95 deletions scripts/app-bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion scripts/app-bundle.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion scripts/vendor-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -2145,4 +2145,4 @@ var requirejs, require, define;
}(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)));

_aureliaConfigureModuleLoader();
function _aureliaConfigureModuleLoader(){requirejs.config({"baseUrl":"src/","paths":{"root":"src","resources":"resources","elements":"resources/elements","attributes":"resources/attributes","valueConverters":"resources/value-converters","bindingBehaviors":"resources/binding-behaviors","app-bundle":"../scripts/app-bundle"},"packages":[],"stubModules":["text"],"shim":{},"bundles":{"app-bundle":["app-config","app","environment","generated-configuration","main","name-tag-config","name-tag","debug/configuration","debug/reporter","debug/task-queue","runtime/aurelia","runtime/decorators","runtime/di","runtime/dom","runtime/interfaces","runtime/platform","runtime/reporter","runtime/task-queue","debug/binding/binding-context","debug/binding/unparser","jit/binding/expression","runtime/configuration/standard","runtime/binding/array-change-records","runtime/binding/array-observation","runtime/binding/ast","runtime/binding/binding-context","runtime/binding/binding-mode","runtime/binding/binding","runtime/binding/call","runtime/binding/checked-observer","runtime/binding/class-observer","runtime/binding/collection-observation","runtime/binding/connect-queue","runtime/binding/connectable-binding","runtime/binding/dirty-checker","runtime/binding/element-observation","runtime/binding/event-manager","runtime/binding/expression","runtime/binding/listener","runtime/binding/map-change-records","runtime/binding/map-observation","runtime/binding/observation","runtime/binding/observer-locator","runtime/binding/property-observation","runtime/binding/ref","runtime/binding/select-value-observer","runtime/binding/set-observation","runtime/binding/signal","runtime/binding/subscriber-collection","runtime/binding/svg-analyzer","runtime/resources/attr-binding-behavior","runtime/resources/binding-mode-behaviors","runtime/resources/compose","runtime/resources/debounce-binding-behavior","runtime/resources/else","runtime/resources/if-core","runtime/resources/if","runtime/resources/replaceable","runtime/resources/sanitize","runtime/resources/self-binding-behavior","runtime/resources/signals","runtime/resources/throttle-binding-behavior","runtime/resources/update-trigger-binding-behavior","runtime/resources/with","runtime/templating/animator","runtime/templating/component","runtime/templating/instructions","runtime/templating/lifecycle","runtime/templating/render-slot","runtime/templating/shadow-dom","runtime/templating/view-engine","runtime/templating/view","svg/binding/svg-analyzer"]}})}
function _aureliaConfigureModuleLoader(){requirejs.config({"baseUrl":"src/","paths":{"root":"src","resources":"resources","elements":"resources/elements","attributes":"resources/attributes","valueConverters":"resources/value-converters","bindingBehaviors":"resources/binding-behaviors","app-bundle":"../scripts/app-bundle"},"packages":[],"stubModules":["text"],"shim":{},"bundles":{"app-bundle":["app-config","app","environment","generated-configuration","main","name-tag-config","name-tag","debug/configuration","debug/reporter","debug/task-queue","runtime/aurelia","runtime/decorators","runtime/di","runtime/dom","runtime/interfaces","runtime/platform","runtime/reporter","runtime/task-queue","debug/binding/binding-context","debug/binding/unparser","jit/binding/expression","runtime/binding/array-change-records","runtime/binding/array-observation","runtime/binding/ast","runtime/binding/binding-context","runtime/binding/binding-mode","runtime/binding/binding","runtime/binding/call","runtime/binding/checked-observer","runtime/binding/class-observer","runtime/binding/collection-observation","runtime/binding/connect-queue","runtime/binding/connectable-binding","runtime/binding/dirty-checker","runtime/binding/element-observation","runtime/binding/event-manager","runtime/binding/expression","runtime/binding/listener","runtime/binding/map-change-records","runtime/binding/map-observation","runtime/binding/observation","runtime/binding/observer-locator","runtime/binding/property-observation","runtime/binding/ref","runtime/binding/select-value-observer","runtime/binding/set-observation","runtime/binding/signal","runtime/binding/subscriber-collection","runtime/binding/svg-analyzer","runtime/configuration/standard","runtime/resources/attr-binding-behavior","runtime/resources/binding-mode-behaviors","runtime/resources/compose","runtime/resources/debounce-binding-behavior","runtime/resources/else","runtime/resources/if-core","runtime/resources/if","runtime/resources/replaceable","runtime/resources/sanitize","runtime/resources/self-binding-behavior","runtime/resources/signals","runtime/resources/throttle-binding-behavior","runtime/resources/update-trigger-binding-behavior","runtime/resources/with","runtime/templating/animator","runtime/templating/component","runtime/templating/instructions","runtime/templating/lifecycle","runtime/templating/render-slot","runtime/templating/shadow-dom","runtime/templating/view-engine","runtime/templating/view","svg/binding/svg-analyzer"]}})}
4 changes: 4 additions & 0 deletions src/debug/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,9 @@ const codeLookup: Record<string, IMessageInfo> = {
15: {
type: MessageType.error,
message: 'Unexpected call context.'
},
16: {
type: MessageType.error,
message: 'Only one child observer per content view is supported for the life of the content view.'
}
};
15 changes: 7 additions & 8 deletions src/runtime/resources/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { customElement, inject } from "../decorators";
import { IRenderSlot, SwapOrder } from "../templating/render-slot";
import { ViewEngine, ITemplateContainer, VisualWithCentralComponent, IVisual } from "../templating/view-engine";
import { ITargetedInstruction, IHydrateElementInstruction, TargetedInstructionType } from "../templating/instructions";
import { IViewOwner, IViewOwnerType } from "../templating/view";
import { IViewOwner, IViewOwnerType, IContentView } from "../templating/view";
import { IContainer } from "../di";
import { IBindScope } from "../binding/observation";
import { INode, DOM, IView } from "../dom";
import { INode, DOM } from "../dom";

const composeSource = {
name: 'au-compose',
Expand All @@ -19,7 +19,7 @@ const composeProps = ['component', 'swapOrder', 'isComposing'];
@inject(IViewOwner, INode, IRenderSlot, ITargetedInstruction)
export class Compose {
//#region Framework-Supplied
private $contentView: IView;
private $contentView: IContentView;
private $bindable: IBindScope[];
private $isBound: boolean;
//#endregion
Expand Down Expand Up @@ -70,15 +70,14 @@ export class Compose {
}
}

/** @internal */ compose(toBeComposed: any) {
/** @internal */
compose(toBeComposed: any) {
const instruction = Object.assign({}, {
resource: toBeComposed,
contentElement: this.createContentElement()
}, this.baseInstruction);

return this.swap(
ViewEngine.visualFromComponent(this.compositionContainer, toBeComposed, instruction)
);
return this.swap(ViewEngine.visualFromComponent(this.compositionContainer, toBeComposed, instruction));
}

private createContentElement() {
Expand Down Expand Up @@ -119,7 +118,7 @@ export class Compose {
newVisual.$bind(this.viewOwner.$scope);
}

return this.slot.swap(newVisual, this.swapOrder || 'after');
return this.slot.swap(newVisual, this.swapOrder || SwapOrder.after);
}

private clear() {
Expand Down
107 changes: 32 additions & 75 deletions src/runtime/templating/render-slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,89 +13,49 @@ function appendVisualToContainer(visual: IVisual, owner: RenderSlotImplementatio
visual.$view.appendTo(owner.anchor);
}

function addVisualToList(visual: IVisual, owner: RenderSlotImplementation) {
function addVisual(visual: IVisual, owner: RenderSlotImplementation) {
visual.$view.insertBefore(owner.anchor);
}

function projectAddVisualToList(visual: IVisual, owner: RenderSlotImplementation) {
let logicalView = owner.logicalView;
let childObserver = logicalView.childObserver;

if (childObserver) {
let contentNodes = logicalView.childNodes;
let contentIndex = contentNodes.indexOf(owner.anchor) - 1;
let projectedNodes = Array.from(visual.$view.childNodes);

projectedNodes.forEach((node: any) => {
if (node.$isContentProjectionSource) {
node.$slot.logicalView = logicalView;
}
});

contentNodes.splice(contentIndex, 0, ...projectedNodes);
childObserver.notifyChildrenChanged();
}

visual.$view.remove = () => ShadowDOMEmulation.undistributeView(visual.$view, owner.slots, owner);
function project_addVisual(visual: IVisual, owner: RenderSlotImplementation) {
ShadowDOMEmulation.distributeView(visual.$view, owner.slots, owner);
owner.logicalView.insertVisualChildBefore(visual, owner.anchor);

visual.$view.remove = () => {
ShadowDOMEmulation.undistributeView(visual.$view, owner.slots, owner);
owner.logicalView.removeVisualChild(visual);
};
}

function insertVisualAtIndex(visual: IVisual, owner: RenderSlotImplementation, index: number) {
function insertVisual(visual: IVisual, owner: RenderSlotImplementation, index: number) {
visual.$view.insertBefore(owner.children[index].$view.firstChild);
}

function projectInsertVisualAtIndex(visual: IVisual, owner: RenderSlotImplementation, index: number) {
let logicalView = owner.logicalView;
let childObserver = logicalView.childObserver;

if (childObserver) {
let contentNodes = logicalView.childNodes;
let contentIndex = contentNodes.indexOf(owner.children[index].$view.firstChild);
let projectedNodes = Array.from(visual.$view.childNodes);

projectedNodes.forEach((node: any) => {
if (node.$isContentProjectionSource) {
node.$slot.logicalView = logicalView;
}
});

contentNodes.splice(contentIndex, 0, ...projectedNodes);
childObserver.notifyChildrenChanged();
}

visual.$view.remove = () => ShadowDOMEmulation.undistributeView(visual.$view, owner.slots, owner);
function project_insertVisual(visual: IVisual, owner: RenderSlotImplementation, index: number) {
ShadowDOMEmulation.distributeView(visual.$view, owner.slots, owner, index);
}
owner.logicalView.insertVisualChildBefore(visual, owner.children[index].$view.firstChild);

function removeView(visual: IVisual, owner: RenderSlotImplementation) {
visual.$view.remove();
visual.$view.remove = () => {
ShadowDOMEmulation.undistributeView(visual.$view, owner.slots, owner);
owner.logicalView.removeVisualChild(visual);
};
}

function projectRemoveView(visual: IVisual, owner: RenderSlotImplementation) {
let logicalView = owner.logicalView;
let childObserver = logicalView.childObserver;

if (childObserver) {
let contentNodes = logicalView.childNodes;
let startIndex = contentNodes.indexOf(visual.$view.firstChild);
let endIndex = contentNodes.indexOf(visual.$view.lastChild);

contentNodes.splice(startIndex, (endIndex - startIndex) + 1);
childObserver.notifyChildrenChanged();
}

ShadowDOMEmulation.undistributeView(visual.$view, owner.slots, owner);
export enum SwapOrder {
before = 'before',
with = 'with',
after = 'after'
}

export type SwapOrder = 'before' | 'with' | 'after';

export const IRenderSlot = DI.createInterface('IRenderSlot');

/**
* Represents a slot or location within the DOM to which views can be added and removed.
* Manages the view lifecycle for its children.
*/
export interface IRenderSlot extends IAttach {
children: ReadonlyArray<IVisual>;

/**
* Adds a view to the slot.
* @param visual The view to add.
Expand Down Expand Up @@ -170,19 +130,17 @@ class RenderSlotImplementation implements IRenderSlot {
private $isAttached = false;
private addVisualCore: (visual: IVisual, owner: RenderSlotImplementation) => void;
private insertVisualCore: (visual: IVisual, owner: RenderSlotImplementation, index: number) => void;
private removeViewCore: (visual: IVisual, owner: RenderSlotImplementation) => void;

/** @internal */ public children: IVisual[] = [];
public children: IVisual[] = [];
/** @internal */ public slots: Record<string, IEmulatedShadowSlot> = null;
/** @internal */ public logicalView: IContentView = null;

constructor(public anchor: INode, anchorIsContainer: boolean) {
(<any>anchor).$slot = this; // Usage: Shadow DOM Emulation
(<any>anchor).$isContentProjectionSource = false; // Usage: Shadow DOM Emulation

this.addVisualCore = anchorIsContainer ? appendVisualToContainer : addVisualToList;
this.insertVisualCore = insertVisualAtIndex;
this.removeViewCore = removeView;
this.addVisualCore = anchorIsContainer ? appendVisualToContainer : addVisual;
this.insertVisualCore = insertVisual;
}

add(visual: IVisual) {
Expand Down Expand Up @@ -222,26 +180,26 @@ class RenderSlotImplementation implements IRenderSlot {
children.splice(targetIndex, 0, visual);

if (this.$isAttached) {
this.removeViewCore(visual, this);
visual.$view.remove();
this.insertVisualCore(visual, this, targetIndex);
}
}

swap(newVisual: IVisual, strategy: SwapOrder = 'after', returnToCache?: boolean, skipAnimation?: boolean) {
swap(newVisual: IVisual, strategy: SwapOrder = SwapOrder.after, returnToCache?: boolean, skipAnimation?: boolean) {
const remove = () => this.removeAll(returnToCache, skipAnimation);
const add = () => this.add(newVisual);

switch(strategy) {
case 'before':
case SwapOrder.before:
const beforeAddResult = add();
return (beforeAddResult instanceof Promise ? beforeAddResult.then(() => <any>remove()) : remove());
case 'with':
case SwapOrder.with:
const withAddResult = add();
const withRemoveResult = remove();
return (withAddResult instanceof Promise || withRemoveResult instanceof Promise)
? Promise.all(<any>[withAddResult, withRemoveResult]).then(x => x[1])
: withRemoveResult;
case 'after':
case SwapOrder.after:
const afterRemoveResult = remove();
return (afterRemoveResult instanceof Promise ? afterRemoveResult.then(() => <any>add()) : add());
}
Expand Down Expand Up @@ -364,15 +322,14 @@ class RenderSlotImplementation implements IRenderSlot {

projectTo(slots: Record<string, IEmulatedShadowSlot>): void {
this.slots = slots;
this.addVisualCore = projectAddVisualToList;
this.insertVisualCore = projectInsertVisualAtIndex;
this.removeViewCore = projectRemoveView;
this.addVisualCore = project_addVisual;
this.insertVisualCore = project_insertVisual;

if (this.$isAttached) {
const children = this.children;

for (let i = 0, ii = children.length; i < ii; ++i) {
projectAddVisualToList(children[i], this);
project_addVisual(children[i], this);
}
}
}
Expand Down
96 changes: 77 additions & 19 deletions src/runtime/templating/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { IScope } from "../binding/binding-context";
import { IBindScope } from "../binding/observation";
import { IAttach } from "./lifecycle";
import { DI, IContainer } from "../di";
import { ITemplate } from "./view-engine";
import { ITemplate, IVisual } from "./view-engine";
import { Constructable } from "../interfaces";
import { ICompiledViewSource } from "./instructions";
import { INode, DOM, IChildObserver, IView } from "../dom";
import { IElementComponent } from "./component";
import { IRenderSlot } from "./render-slot";
import { Reporter } from "../reporter";

export interface IViewOwnerType extends Constructable<IViewOwner> {
template: ITemplate;
Expand Down Expand Up @@ -73,11 +75,13 @@ export const View = {
};

export interface IContentView extends IView {
childObserver?: IContentViewChildObserver;
attachChildObserver(onChildrenChanged: () => void): IContentViewChildObserver;
childObserver?: IChildObserver;
insertVisualChildBefore(visual: IVisual, refNode: INode);
removeVisualChild(visual: IVisual);
attachChildObserver(onChildrenChanged: () => void): IChildObserver;
}

export interface IContentViewChildObserver extends IChildObserver {
interface IContentViewChildObserver extends IChildObserver {
notifyChildrenChanged(): void;
}

Expand All @@ -93,27 +97,81 @@ class ContentView implements IContentView {
this.lastChild = childNodes[childNodes.length - 1];
}

attachChildObserver(onChildrenChanged: () => void) {
let childNodes = this.childNodes;
let observer = {
get childNodes() {
return childNodes;
},
disconnect() {
onChildrenChanged = null;
},
notifyChildrenChanged() {
if (onChildrenChanged !== null) {
onChildrenChanged();
attachChildObserver(onChildrenChanged: () => void): IChildObserver {
let contentViewNodes = this.childNodes;
let observer = this.childObserver;

if (!observer) {
this.childObserver = observer = {
get childNodes() {
return contentViewNodes;
},
disconnect() {
onChildrenChanged = null;
},
notifyChildrenChanged() {
if (onChildrenChanged !== null) {
onChildrenChanged();
}
}
}
};
};

// TODO: materialize content
let workQueue = Array.from(contentViewNodes);

while(workQueue.length) {
let current = workQueue.shift();

if ((<any>current).$isContentProjectionSource) {
let contentIndex = contentViewNodes.indexOf(current);

(<IRenderSlot>(<any>current).$slot).children.forEach(x => {
let childNodes = x.$view.childNodes;
childNodes.forEach(x => workQueue.push(x));
contentViewNodes.splice(contentIndex, 0, ...childNodes);
});

(<any>current).$slot.logicalView = this;
}
}
} else {
Reporter.error(16);
}

return observer;
}

insertVisualChildBefore(visual: IVisual, refNode: INode) {
let childObserver = this.childObserver;

if (childObserver) {
let contentNodes = this.childNodes;
let contentIndex = contentNodes.indexOf(refNode);
let projectedNodes = Array.from(visual.$view.childNodes);

projectedNodes.forEach((node: any) => {
if (node.$isContentProjectionSource) {
node.$slot.logicalView = this;
}
});

contentNodes.splice(contentIndex, 0, ...projectedNodes);
childObserver.notifyChildrenChanged();
}
}

removeVisualChild(visual: IVisual) {
let childObserver = this.childObserver;

if (childObserver) {
let contentNodes = this.childNodes;
let startIndex = contentNodes.indexOf(visual.$view.firstChild);
let endIndex = contentNodes.indexOf(visual.$view.lastChild);

contentNodes.splice(startIndex, (endIndex - startIndex) + 1);
childObserver.notifyChildrenChanged();
}
}

findTargets() { return PLATFORM.emptyArray; }
appendChild(child: INode) {}
insertBefore(refNode: INode): void {}
Expand Down

0 comments on commit 3927f90

Please sign in to comment.