Skip to content

Commit

Permalink
feat(templating): implement compose element
Browse files Browse the repository at this point in the history
Added some base-level infrastructure to suppoer the compose element. Changed some type aliases to interfaces. Enabled some more framework services for injection into elements.
  • Loading branch information
EisenbergEffect committed Apr 22, 2018
1 parent 4bef6f8 commit 48f91d3
Show file tree
Hide file tree
Showing 11 changed files with 663 additions and 240 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ An experimental re-working of Aurelia, oriented around compile-time reflection a

* [x] `if` Template Controller
* [x] `else` Template Controller
* [ ] `compose` Custom Element
* [x] `compose` Custom Element
* [ ] `repeat` Template Controller

### Application Model
Expand Down
402 changes: 292 additions & 110 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/interfaces","runtime/pal","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/else","runtime/resources/if-core","runtime/resources/if","runtime/templating/animator","runtime/templating/component","runtime/templating/lifecycle","runtime/templating/shadow-dom","runtime/templating/view-engine","runtime/templating/view-slot","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/interfaces","runtime/pal","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/compose","runtime/resources/else","runtime/resources/if-core","runtime/resources/if","runtime/templating/animator","runtime/templating/component","runtime/templating/lifecycle","runtime/templating/shadow-dom","runtime/templating/view-engine","runtime/templating/view-slot","runtime/templating/view","svg/binding/svg-analyzer"]}})}
4 changes: 3 additions & 1 deletion src/runtime/configuration/standard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { ISVGAnalyzer, SVGAnalyzer } from "../binding/svg-analyzer";
import { IEventManager, EventManager } from "../binding/event-manager";
import { IObserverLocator, ObserverLocator } from "../binding/observer-locator";
import { IAnimator, Animator } from "../templating/animator";
import { Compose } from "../resources/compose";

export const StandardConfiguration = {
register(container: IContainer) {
container.register(
If,
Else
Else,
Compose
);

container.register(Registration.instance(IDirtyChecker, DirtyChecker));
Expand Down
80 changes: 2 additions & 78 deletions src/runtime/pal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export const FEATURE = {
export const DOM = {
Element: global.Element,
SVGElement: global.SVGElement,
boundary: 'aurelia-dom-boundary',
addEventListener(eventName: string, callback: EventListenerOrEventListenerObject, capture?: boolean): void {
document.addEventListener(eventName, callback, capture);
},
Expand Down Expand Up @@ -79,56 +78,14 @@ export const DOM = {
createCustomEvent(eventType: string, options: CustomEventInit): CustomEvent {
return new CustomEvent(eventType, options);
},
dispatchEvent(evt): void {
document.dispatchEvent(evt);
},
getComputedStyle(element: Element) {
return global.getComputedStyle(element);
},
getElementById(id: string): Element {
return document.getElementById(id);
},
querySelectorAll(query: string) {
return document.querySelectorAll(query);
},
nextElementSibling(element: Node): Element {
if ('nextElementSibling' in element) {
return element['nextElementSibling'];
}

do {
element = element.nextSibling;
} while (element && element.nodeType !== 1);

return <Element>element;
},
createTemplateFromMarkup(markup: string): Element {
let parser = document.createElement('div');
parser.innerHTML = markup;

let temp = parser.firstElementChild;

if (!temp || temp.nodeName !== 'TEMPLATE') {
throw new Error('Template markup must be wrapped in a <template> element e.g. <template> <!-- markup here --> </template>');
}

return temp;
},
appendNode(newNode: Node, parentNode?: Node): void {
(parentNode || document.body).appendChild(newNode);
},
replaceNode(newNode: Node, node: Node, parentNode?: Node): void {
replaceNode(newNode: Node, node: Node): void {
if (node.parentNode) {
node.parentNode.replaceChild(newNode, node);
} else {
parentNode.replaceChild(newNode, node);
}
},
removeNode(node: Node, parentNode?: Node): void {
removeNode(node: Node): void {
if (node.parentNode) {
node.parentNode.removeChild(node);
} else if (parentNode) {
parentNode.removeChild(node);
}
},
//https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace_in_the_DOM
Expand Down Expand Up @@ -156,39 +113,6 @@ export const DOM = {
DOM.replaceNode(anchor, element);

return anchor;
},
injectStyles(styles: string, destination?: Element, prepend?: boolean, id?: string): Node {
if (id) {
let oldStyle = document.getElementById(id);
if (oldStyle) {
let isStyleTag = oldStyle.tagName.toLowerCase() === 'style';

if (isStyleTag) {
oldStyle.innerHTML = styles;
return;
}

throw new Error('The provided id does not indicate a style tag.');
}
}

let node = document.createElement('style');
node.innerHTML = styles;
node.type = 'text/css';

if (id) {
node.id = id;
}

destination = destination || document.head;

if (prepend && destination.childNodes.length > 0) {
destination.insertBefore(node, destination.childNodes[0]);
} else {
destination.appendChild(node);
}

return node;
}
};

Expand Down
173 changes: 173 additions & 0 deletions src/runtime/resources/compose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { compiledElement, inject } from "../decorators";
import { ViewSlot, SwapOrder } from "../templating/view-slot";
import { ViewEngine, ITargetedInstruction, IElementInstruction, ITemplateContainer, VisualWithCentralComponent, IVisual } from "../templating/view-engine";
import { IViewOwner, IViewOwnerType, IView } from "../templating/view";
import { IContainer } from "../di";
import { IBindScope } from "../binding/observation";
import { DOM } from "../pal";

const composeSource = {
name: 'au-compose',
template: null,
targetInstructions: null
};

const composeProps = ['component', 'swapOrder', 'isComposing'];

@compiledElement(composeSource)
@inject(IViewOwner, DOM.Element, ViewSlot, ITargetedInstruction)
export class Compose {
//#region Framework-Supplied
private $contentView: IView;
private $bindable: IBindScope[];
private $isBound: boolean;
//#endregion

private task: CompositionTask = null;
private visual: VisualWithCentralComponent = null;
private auContent: Element = null;
private baseInstruction: IElementInstruction;
private compositionContainer: ITemplateContainer;

component: any;
swapOrder: SwapOrder;
isComposing: boolean;

constructor(private viewOwner: IViewOwner, private element: HTMLElement, private viewSlot: ViewSlot, instruction: ITargetedInstruction) {
this.viewOwner = viewOwner;
this.viewSlot = viewSlot;

const type = <IViewOwnerType>viewOwner.constructor;
const composeInstruction = <IElementInstruction>instruction;

this.compositionContainer = type.template.container;
this.baseInstruction = {
type: 'element',
instructions: composeInstruction.instructions.filter(x => !composeProps.includes(x)),
resource: null,
replacements: composeInstruction.replacements
};
}

private componentChanged(toBeComposed: any) {
if (this.visual !== null && this.visual.component === toBeComposed) {
return;
}

if (!toBeComposed) {
this.clear();
} else {
const previousTask = this.task;
const newTask = this.task = new CompositionTask(this);

if (previousTask !== null) {
const cancelResult = previousTask.cancel();

if (cancelResult instanceof Promise) {
cancelResult.then(() => newTask.start(toBeComposed));
return;
}
}

newTask.start(toBeComposed);
}
}

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

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

private createContentElement() {
let auContent = this.auContent;

if (auContent == null) {
this.auContent = auContent = DOM.createElement('au-content');

//If the compose element isn't using Shadow DOM
if (this.$contentView !== null) {
let nodes = this.$contentView.childNodes;

for (let i = 0, ii = nodes.length; i < ii; ++i) {
auContent.appendChild(nodes[i]);
}
} else { //if the compose element is using Shadow DOM
let element = this.element;

while(element.firstChild) {
auContent.appendChild(element.firstChild);
}
}
}

return auContent.cloneNode(true);
}

private swap(newVisual: VisualWithCentralComponent) {
let index = this.$bindable.indexOf(this.visual);
if (index !== -1) {
this.$bindable.splice(index, 1);
}

this.visual = newVisual;
this.$bindable.push(newVisual);

if (this.$isBound) {
newVisual.bind(this.viewOwner.$scope);
}

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

private clear() {
this.viewSlot.removeAll();
}
}

class CompositionTask {
private isCancelled = false;
private composeResult = null;

constructor(private compose: Compose) {}

start(toBeComposed) {
if (this.isCancelled) {
return;
}

this.compose.isComposing = true;

if (toBeComposed instanceof Promise) {
toBeComposed.then(x => this.render(x));
} else {
this.render(toBeComposed);
}
}

cancel() {
this.compose.isComposing = false;
this.isCancelled = true;
return this.composeResult;
}

private render(toBeComposed: any) {
if (this.isCancelled) {
return;
}

this.composeResult = this.compose.compose(toBeComposed);

if (this.composeResult instanceof Promise) {
this.composeResult = this.composeResult.then(() => this.compose.isComposing = false);
} else {
this.compose.isComposing = false;
this.composeResult = null;
}
}
}
22 changes: 12 additions & 10 deletions src/runtime/templating/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,22 @@ export interface IBindingBehaviorSource {
name: string;
}

type AttributeType = Constructable & {
export interface IAttributeType extends Constructable {
new(...args: any[]): IAttributeComponent;
source: IAttributeSource;
};

type ElementType = Constructable & {
export interface IElementType extends Constructable {
new(...args: any[]): IElementComponent;
template: ITemplate;
source: ICompiledViewSource;
}

type ValueConverterType = Constructable & {
export interface ValueConverterType extends Constructable {
source: IValueConverterSource;
}

type BindingBehaviorType = Constructable & {
export interface BindingBehaviorType extends Constructable {
source: IBindingBehaviorSource;
}

Expand All @@ -64,7 +64,7 @@ class RuntimeCharacteristics {
hasDetached = false;
hasUnbound = false;

static for(instance, Component: ElementType | AttributeType) {
static for(instance, Component: IElementType | IAttributeType) {
let characteristics = new RuntimeCharacteristics();
let configuredObservables = <Record<string, IObservableDescription>>(<any>Component).observables;
let observables: IObservableDescription[] = [];
Expand Down Expand Up @@ -122,7 +122,7 @@ export const Component = {

return <any>ctor;
},
attribute<T extends Constructable>(nameOrSource: string | IAttributeSource, ctor: T): T & AttributeType {
attribute<T extends Constructable>(nameOrSource: string | IAttributeSource, ctor: T): T & IAttributeType {
let source = ensureSource<IAttributeSource>(nameOrSource);

return class CustomAttribute extends ctor implements IAttributeComponent {
Expand Down Expand Up @@ -229,7 +229,7 @@ export const Component = {
}
};
},
elementFromCompiledSource<T extends Constructable>(source: ICompiledViewSource, ctor: T = null): T & ElementType {
elementFromCompiledSource<T extends Constructable>(source: ICompiledViewSource, ctor: T = null): T & IElementType {
//Support HTML-Only Elements by providing a generated class.
if (ctor === null) {
ctor = <any>class HTMLOnlyElement { };
Expand Down Expand Up @@ -419,14 +419,16 @@ export const Component = {
}
}

//Support Recursive Components by adding self to own view template container.
CompiledComponent.register(template.container);
//If the element has a view, support Recursive Components by adding self to own view template container.
if (template.container !== null) {
CompiledComponent.register(template.container);
}

return CompiledComponent;
}
};

function discoverAndApplyCharacteristics(instance, Component: ElementType | AttributeType) {
function discoverAndApplyCharacteristics(instance, Component: IElementType | IAttributeType) {
let characteristics: RuntimeCharacteristics = (<any>Component).characteristics;

if (characteristics === undefined) {
Expand Down
Loading

0 comments on commit 48f91d3

Please sign in to comment.