Skip to content

Instantly share code, notes, and snippets.

@AshleyGrant
Forked from bigopon/app.html
Last active December 21, 2017 15:26
Show Gist options
  • Save AshleyGrant/3221d1010e9969dcd36704799f6f6f9f to your computer and use it in GitHub Desktop.
Save AshleyGrant/3221d1010e9969dcd36704799f6f6f9f to your computer and use it in GitHub Desktop.
Aurelia let element
<template>
<require from='./card.html'></require>
<require from='./card-with-slot.html'></require>
<style>
b { font-weight: bold; }
label { display: block; }
</style>
<div ref="myDiv"
msg.bind='message'
fun-message.bind='myDiv.msg | fun'
no-fun-message.bind='myDiv.msg | noFun'
sentence='Full name is: "${fullName}" and first name is "${firstName}" and last name is "${lastName}"'
literal-string='I am a literal stirng in let element. Triggered warning.'></div>
<input value.bind='message' />
<hr/>
Raw message: ${myDiv.msg}
<hr/>
Fun message:<br/>${myDiv.funMessage}
<hr/>
No fun message:<br/>${myDiv.noFunMessage}
<hr/>
<!--
Usage with repeat or any templateController attribute
-->
<div ref="rootEl" repeat.for='day of days' day-name.bind='day | toShort'>
<span>Day name: ${rootEl.dayName}. Index: ${$index}</span>
</div>
<hr/>
<hr/>
<b>Interpolation binding demo</b><br/><br/>
<label>First name: <input value.bind='firstName' /></label>
<label>Middle name: <input value.bind='middleName' /></label>
<label>Last name: <input value.bind='lastName' /></label>
<br/>
<b>App's sentence</b>: ${myDiv.sentence}<br/><br/>
<b>App's literal string:</b> ${myDiv.literalString}
<hr/>
<hr/>
<b>Common confusion binding demo</b><br/><br/>
<card>
<let card.bind='myDiv.sentence'></let>
</card>
<br/>
<button click.delegate='showCard = !showCard'>Show the card</button>
<card if.bind='showCard'>
<div ref="divInCard" card-in-if.bind='myDiv.sentence'></div>
</card>
<br/>
<card-with-slot>
<div ref="divInCardSlot" card-slot='This is a demo for let confusion'></div>
</card-with-slot>
<br/>
<b>App's [card]: </b>${card}<br/><br/>
<b>App's [cardInIf]: </b>${divInCard.cardInIf}<br/><br/>
<b>App's [cardSlot]:</b>${divInCardSlot.cardSlot}<br/>
<hr/>
<h1>Section : </h1>
Defining children properties from parent via replaceable part<br />
Show Footer: <input type="checkbox" checked.bind="showFooter"
<card-with-slot>
<template replace-part='card-dynamic-variables'>
<div ref="footerVisible" footer-visible.bind='showFooter'></let>
</template>
</card-with-slot>
</template>
import './templating-binding-language'
import './view-compiler'
import './view-factory'
import { computedFrom } from 'aurelia-binding';
export class App {
message = 'Hello world!'
days = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday'
];
attached() {
window.app = this;
}
firstName = 'Crispy'
middleName = 'Creamy'
lastName = 'Coconut'
@computedFrom('firstName', 'lastName', 'middleName')
get fullName() {
return `${this.firstName} ${this.middleName} ${this.lastName}`;
}
}
export class FunValueConverter {
toView(val) {
return val + ' [fun] ' + funNames[Math.floor(Math.random() * funNames.length)];
}
}
export class NoFunValueConverter {
toView(val) {
return val + ' [no fun] ' + noFunNames[Math.floor(Math.random() * noFunNames.length)];
}
}
export class ToShortValueConverter {
toView(day) {
return day ? day.substr(0, 3) : '';
}
}
const funNames = [
'Twitter',
'Games',
'Ping Pong',
'Keyboard smashing',
'Wack a mole',
'Walk a mile'
];
const noFunNames = [
'Get mad',
'Get angry',
'Get sad',
'Get fuzzy',
'Get rad',
'Get blurry'
]
<template bindable='block, title' style='display: block; padding: 4px; background: ${background || "lightpink"}'>
<header>${title}</header>
<div><slot></slot></div>
<footer show.bind='footerVisible.footerVisible'>Card footer. Shown when `footerVisible = true`</footer>
<template replaceable part='card-dynamic-variables'></template>
</template>
<template bindable='block, title' style='display: block; padding: 4px; background: ${background || "lightgreen"}'>
<header>${title}</header>
<div></div>
</template>
<!doctype html>
<html>
<head>
<title>Aurelia</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<style>
body {
padding: 20px;
}
.form-component {
display: block;
margin-bottom: 20px;
}
</style>
</head>
<body aurelia-app>
<h1>Loading...</h1>
<script src="https://jdanyow.github.io/rjs-bundle/node_modules/requirejs/require.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/config.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/bundles/aurelia.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/bundles/babel.js"></script>
<script>
require(['aurelia-bootstrapper']);
</script>
</body>
</html>
import {
bindingMode,
createOverrideContext,
} from 'aurelia-framework';
import {
InterpolationBinding,
ChildInterpolationBinding
} from 'aurelia-templating-binding';
export class LetInterpolationBindingExpression {
/**
* @param {ObserverLocator} observerLocator
* @param {string} targetProperty
* @param {string[]} parts
* @param {bindingMode} mode
* @param {Lookups} lookupFunctions
* @param {string} attribute
*/
constructor(observerLocator, targetProperty, parts, lookupFunctions, attribute) {
this.observerLocator = observerLocator;
this.targetProperty = targetProperty;
this.parts = parts;
this.lookupFunctions = lookupFunctions;
this.attribute = this.attrToRemove = attribute;
this.discrete = false;
}
createBinding() {
return new LetInterpolationBinding(
this.observerLocator,
this.parts,
this.targetProperty,
this.lookupFunctions
);
}
}
export class LetInterpolationBinding {
/**
*
* @param {ObserverLocator} observerLocator
* @param {string[]} parts
* @param {strign} targetProperty
* @param {Lookups} lookupFunctions
*/
constructor(observerLocator, parts, targetProperty, lookupFunctions) {
this.observerLocator = observerLocator;
this.parts = parts;
this.targetProperty = targetProperty;
this.lookupFunctions = lookupFunctions;
this.target = null;
}
/**
* @param {Scope} source
*/
bind(source) {
if (this.isBound) {
if (this.source === source) {
return;
}
this.unbind();
}
this.isBound = true;
this.source = source;
this.target = source.bindingContext;
this.interpolationBinding = this.createInterpolationBinding();
this.interpolationBinding.bind(source);
}
unbind() {
if (!this.isBound) {
return;
}
this.isBound = false;
this.source = null;
this.target = null;
this.interpolationBinding.unbind();
this.interpolationBinding = null;
}
createInterpolationBinding() {
if (this.parts.length === 3) {
return new ChildInterpolationBinding(
this.target,
this.observerLocator,
this.parts[1],
bindingMode.oneWay,
this.lookupFunctions,
this.targetProperty,
this.parts[0],
this.parts[2]
);
}
return new InterpolationBinding(this.observerLocator,
this.parts,
this.target,
this.targetProperty,
bindingMode.oneWay,
this.lookupFunctions
);
}
}
import {connectable, sourceContext, camelCase, createOverrideContext,
enqueueBindingConnect
} from 'aurelia-binding';
export class LetExpression {
/**
* @param {ObserverLocator} observerLocator
* @param {string} targetProperty
* @param {Expression} sourceExpression
* @param {{}} lookupFunctions
*/
constructor(observerLocator, targetProperty, sourceExpression, lookupFunctions) {
this.observerLocator = observerLocator;
this.sourceExpression = sourceExpression;
this.targetProperty = targetProperty;
this.lookupFunctions = lookupFunctions;
this.discrete = false;
}
createBinding() {
return new Let(
this.observerLocator,
this.sourceExpression,
this.targetProperty,
this.lookupFunctions
);
}
}
@connectable()
export class Let {
/**
*
* @param {ObserverLocator} observerLocator
* @param {Expression} sourceExpression
* @param {Function | Element} target
* @param {string} targetProperty
* @param {*} lookupFunctions
*/
constructor(observerLocator, sourceExpression, targetProperty, lookupFunctions) {
this.observerLocator = observerLocator;
this.sourceExpression = sourceExpression;
this.targetProperty = targetProperty;
this.lookupFunctions = lookupFunctions;
this.source = this.originalSource = this.target = null;
}
updateSource() {
const value = this.sourceExpression.evaluate(this.source, this.lookupFunctions);
this.target[this.targetProperty] = value;
}
call(context, newValue, oldValue) {
if (!this.isBound) {
return;
}
if (context === sourceContext) {
this.updateSource();
return;
}
throw new Error(`Unexpected call context ${context}`);
}
/**
* @param {Scope} source Binding context
*/
bind(source) {
if (this.isBound) {
if (this.source === source) {
return;
}
this.unbind();
}
this.isBound = true;
this.source = source;
this.target = source.bindingContext;
if (this.sourceExpression.bind) {
this.sourceExpression.bind(this, source, this.lookupFunctions);
}
enqueueBindingConnect(this);
}
unbind() {
if (!this.isBound) {
return;
}
this.isBound = false;
if (this.sourceExpression.unbind) {
this.sourceExpression.unbind(this, this.source);
}
this.source = null;
this.originalSource = null;
this.target = null;
this.unobserve(true);
}
connect() {
if (!this.isBound) {
return;
}
this.updateSource();
this.sourceExpression.connect(this, this.source);
}
}
import {
camelCase,
LiteralString
} from 'aurelia-framework'
import {
SyntaxInterpreter,
TemplatingBindingLanguage
} from 'aurelia-templating-binding'
import * as LogManager from 'aurelia-logging';
import {LetExpression} from './let'
import {LetInterpolationBindingExpression} from './let-interpolation';
TemplatingBindingLanguage.prototype.createLetExpressions = function(resources, letElement, existingLetExpressions) {
existingLetExpressions = existingLetExpressions || [];
let attributes = letElement.attributes;
/**@type {Attr} */
let attr;
/**@type {string[]} */
let parts;
let attrName;
let attrValue;
let command;
for (let i = 0, ii = attributes.length; ii > i; ++i) {
attr = attributes[i];
attrName = attr.name;
attrValue = attr.nodeValue;
parts = attrName.split('.');
if (parts.length === 2) {
command = parts[1];
if (command !== 'bind') {
LogManager.getLogger('templating-binding-language')
.warn(`Detected invalid let command. Expected "${part[0]}.bind", given "${attrName}"`);
continue;
}
existingLetExpressions.push(new LetExpression(
this.observerLocator,
camelCase(parts[0]),
this.parser.parse(attrValue),
resources.lookupFunctions
));
} else {
if (attrValue.indexOf('${') === -1) {
LogManager.getLogger('templating-binding-language')
.warn(`Detected string literal in let bindings. Did you mean "${ attrName }.bind=${ attrValue }" or "${ attrName }=\${${ attrValue }}" ?`);
}
attrName = camelCase(attrName);
parts = this.parseInterpolation(resources, attrValue);
if (parts) {
existingLetExpressions.push(new LetInterpolationBindingExpression(
this.observerLocator,
attrName,
parts,
resources.lookupFunctions
));
} else {
existingLetExpressions.push(new LetExpression(
this.observerLocator,
attrName,
new LiteralString(attrValue),
resources.lookupFunctions
));
}
}
}
return existingLetExpressions;
}
import {
ViewCompiler,
ViewResources,
ViewFactory,
BoundViewFactory,
BindingLanguage,
ViewCompileInstruction,
BehaviorInstruction,
TargetInstruction,
inject,
DOM,
FEATURE,
ShadowDOM
} from 'aurelia-framework';
ViewCompiler.prototype.compile = function(
source: Element|DocumentFragment|string,
resources?: ViewResources,
compileInstruction?: ViewCompileInstruction
): ViewFactory {
resources = resources || this.resources;
compileInstruction = compileInstruction || ViewCompileInstruction.normal;
source = typeof source === 'string' ? DOM.createTemplateFromMarkup(source) : source;
let content;
let part;
let cacheSize;
if (source.content) {
part = source.getAttribute('part');
cacheSize = source.getAttribute('view-cache');
content = DOM.adoptNode(source.content);
} else {
content = source;
}
compileInstruction.targetShadowDOM = compileInstruction.targetShadowDOM && FEATURE.shadowDOM;
resources._invokeHook('beforeCompile', content, resources, compileInstruction);
let instructions = {
letExpressions: [] // This is new. All let expression will be extracted to here
};
this._compileNode(content, resources, instructions, source, 'root', !compileInstruction.targetShadowDOM);
let firstChild = content.firstChild;
if (firstChild && firstChild.nodeType === 1) {
let targetId = firstChild.getAttribute('au-target-id');
if (targetId) {
let ins = instructions[targetId];
if (ins.shadowSlot || ins.lifting || (ins.elementInstruction && !ins.elementInstruction.anchorIsContainer)) {
content.insertBefore(DOM.createComment('view'), firstChild);
}
}
}
let factory = new ViewFactory(content, instructions, resources);
factory.surrogateInstruction = compileInstruction.compileSurrogate ? this._compileSurrogate(source, resources) : null;
factory.part = part;
if (cacheSize) {
factory.setCacheSize(cacheSize);
}
resources._invokeHook('afterCompile', factory);
return factory;
};
ViewCompiler.prototype._compileElement = function(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM) {
let tagName = node.tagName.toLowerCase();
let attributes = node.attributes;
let expressions = [];
let expression;
let behaviorInstructions = [];
let providers = [];
let bindingLanguage = resources.getBindingLanguage(this.bindingLanguage);
let liftingInstruction;
let viewFactory;
let type;
let elementInstruction;
let elementProperty;
let i;
let ii;
let attr;
let attrName;
let attrValue;
let originalAttrName;
let instruction;
let info;
let property;
let knownAttribute;
let auTargetID;
let injectorId;
let nextSibling;
if (tagName === 'slot') {
if (targetLightDOM) {
node = makeShadowSlot(this, resources, node, instructions, parentInjectorId);
}
return node.nextSibling;
} else if (tagName === 'let') {
bindingLanguage.createLetExpressions(resources, node, instructions.letExpressions);
nextSibling = node.nextSibling;
DOM.removeNode(node);
return nextSibling;
} else if (tagName === 'template') {
if (!('content' in node)) {
throw new Error('You cannot place a template element within ' + node.namespaceURI + ' namespace');
}
viewFactory = this.compile(node, resources);
viewFactory.part = node.getAttribute('part');
} else {
type = resources.getElement(node.getAttribute('as-element') || tagName);
if (type) {
elementInstruction = BehaviorInstruction.element(node, type);
type.processAttributes(this, resources, node, attributes, elementInstruction);
behaviorInstructions.push(elementInstruction);
}
}
for (i = 0, ii = attributes.length; i < ii; ++i) {
attr = attributes[i];
originalAttrName = attrName = attr.name;
attrValue = attr.value;
info = bindingLanguage.inspectAttribute(resources, tagName, attrName, attrValue);
if (targetLightDOM && info.attrName === 'slot') {
info.attrName = attrName = 'au-slot';
}
type = resources.getAttribute(info.attrName);
elementProperty = null;
if (type) {
knownAttribute = resources.mapAttribute(info.attrName);
if (knownAttribute) {
property = type.attributes[knownAttribute];
if (property) {
info.defaultBindingMode = property.defaultBindingMode;
if (!info.command && !info.expression) {
info.command = property.hasOptions ? 'options' : null;
}
if (info.command && info.command !== 'options' && type.primaryProperty) {
const primaryProperty = type.primaryProperty;
attrName = info.attrName = primaryProperty.name;
info.defaultBindingMode = primaryProperty.defaultBindingMode;
}
}
}
} else if (elementInstruction) {
elementProperty = elementInstruction.type.attributes[info.attrName];
if (elementProperty) {
info.defaultBindingMode = elementProperty.defaultBindingMode;
}
}
if (elementProperty) {
instruction = bindingLanguage.createAttributeInstruction(resources, node, info, elementInstruction);
} else {
instruction = bindingLanguage.createAttributeInstruction(resources, node, info, undefined, type);
}
if (instruction) {
if (instruction.alteredAttr) {
type = resources.getAttribute(instruction.attrName);
}
if (instruction.discrete) {
expressions.push(instruction);
} else {
if (type) {
instruction.type = type;
this._configureProperties(instruction, resources);
if (type.liftsContent) {
instruction.originalAttrName = originalAttrName;
liftingInstruction = instruction;
break;
} else {
behaviorInstructions.push(instruction);
}
} else if (elementProperty) {
elementInstruction.attributes[info.attrName].targetProperty = elementProperty.name;
} else {
expressions.push(instruction.attributes[instruction.attrName]);
}
}
} else {
if (type) {
instruction = BehaviorInstruction.attribute(attrName, type);
instruction.attributes[resources.mapAttribute(attrName)] = attrValue;
if (type.liftsContent) {
instruction.originalAttrName = originalAttrName;
liftingInstruction = instruction;
break;
} else {
behaviorInstructions.push(instruction);
}
} else if (elementProperty) {
elementInstruction.attributes[attrName] = attrValue;
}
}
}
if (liftingInstruction) {
liftingInstruction.viewFactory = viewFactory;
node = liftingInstruction.type.compile(this, resources, node, liftingInstruction, parentNode);
auTargetID = makeIntoInstructionTarget(node);
instructions[auTargetID] = TargetInstruction.lifting(parentInjectorId, liftingInstruction);
} else {
let skipContentProcessing = false;
if (expressions.length || behaviorInstructions.length) {
injectorId = behaviorInstructions.length ? getNextInjectorId() : false;
for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) {
instruction = behaviorInstructions[i];
instruction.type.compile(this, resources, node, instruction, parentNode);
providers.push(instruction.type.target);
skipContentProcessing = skipContentProcessing || instruction.skipContentProcessing;
}
for (i = 0, ii = expressions.length; i < ii; ++i) {
expression = expressions[i];
if (expression.attrToRemove !== undefined) {
node.removeAttribute(expression.attrToRemove);
}
}
auTargetID = makeIntoInstructionTarget(node);
instructions[auTargetID] = TargetInstruction.normal(injectorId, parentInjectorId, providers, behaviorInstructions, expressions, elementInstruction);
}
if (skipContentProcessing) {
return node.nextSibling;
}
let currentChild = node.firstChild;
while (currentChild) {
currentChild = this._compileNode(currentChild, resources, instructions, node, injectorId || parentInjectorId, targetLightDOM);
}
}
return node.nextSibling;
}
/**********************************************
* | *
* BELOW is unteresting part. No need to scan *
* | *
* | *
* <-------> *
* *
* *
**********************************************/
ViewCompiler.prototype._compileNode = function(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM) {
switch (node.nodeType) {
case 1: //element node
return this._compileElement(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM);
case 3: //text node
//use wholeText to retrieve the textContent of all adjacent text nodes.
let expression = resources.getBindingLanguage(this.bindingLanguage).inspectTextContent(resources, node.wholeText);
if (expression) {
let marker = DOM.createElement('au-marker');
let auTargetID = makeIntoInstructionTarget(marker);
(node.parentNode || parentNode).insertBefore(marker, node);
node.textContent = ' ';
instructions[auTargetID] = TargetInstruction.contentExpression(expression);
//remove adjacent text nodes.
while (node.nextSibling && node.nextSibling.nodeType === 3) {
(node.parentNode || parentNode).removeChild(node.nextSibling);
}
} else {
//skip parsing adjacent text nodes.
while (node.nextSibling && node.nextSibling.nodeType === 3) {
node = node.nextSibling;
}
}
return node.nextSibling;
case 11: //document fragment node
let currentChild = node.firstChild;
while (currentChild) {
currentChild = this._compileNode(currentChild, resources, instructions, node, parentInjectorId, targetLightDOM);
}
break;
default:
break;
}
return node.nextSibling;
};
ViewCompiler.prototype._compileSurrogate = function(node, resources) {
let tagName = node.tagName.toLowerCase();
let attributes = node.attributes;
let bindingLanguage = resources.getBindingLanguage(this.bindingLanguage);
let knownAttribute;
let property;
let instruction;
let i;
let ii;
let attr;
let attrName;
let attrValue;
let info;
let type;
let expressions = [];
let expression;
let behaviorInstructions = [];
let values = {};
let hasValues = false;
let providers = [];
for (i = 0, ii = attributes.length; i < ii; ++i) {
attr = attributes[i];
attrName = attr.name;
attrValue = attr.value;
info = bindingLanguage.inspectAttribute(resources, tagName, attrName, attrValue);
type = resources.getAttribute(info.attrName);
if (type) { //do we have an attached behavior?
knownAttribute = resources.mapAttribute(info.attrName); //map the local name to real name
if (knownAttribute) {
property = type.attributes[knownAttribute];
if (property) { //if there's a defined property
info.defaultBindingMode = property.defaultBindingMode; //set the default binding mode
if (!info.command && !info.expression) { // if there is no command or detected expression
info.command = property.hasOptions ? 'options' : null; //and it is an optons property, set the options command
}
// if the attribute itself is bound to a default attribute value then we have to
// associate the attribute value with the name of the default bindable property
// (otherwise it will remain associated with "value")
if (info.command && (info.command !== 'options') && type.primaryProperty) {
const primaryProperty = type.primaryProperty;
attrName = info.attrName = primaryProperty.name;
// note that the defaultBindingMode always overrides the attribute bindingMode which is only used for "single-value" custom attributes
// when using the syntax `<div square.bind="color"></div>`
info.defaultBindingMode = primaryProperty.defaultBindingMode;
}
}
}
}
instruction = bindingLanguage.createAttributeInstruction(resources, node, info, undefined, type);
if (instruction) { //HAS BINDINGS
if (instruction.alteredAttr) {
type = resources.getAttribute(instruction.attrName);
}
if (instruction.discrete) { //ref binding or listener binding
expressions.push(instruction);
} else { //attribute bindings
if (type) { //templator or attached behavior found
instruction.type = type;
this._configureProperties(instruction, resources);
if (type.liftsContent) { //template controller
throw new Error('You cannot place a template controller on a surrogate element.');
} else { //attached behavior
behaviorInstructions.push(instruction);
}
} else { //standard attribute binding
expressions.push(instruction.attributes[instruction.attrName]);
}
}
} else { //NO BINDINGS
if (type) { //templator or attached behavior found
instruction = BehaviorInstruction.attribute(attrName, type);
instruction.attributes[resources.mapAttribute(attrName)] = attrValue;
if (type.liftsContent) { //template controller
throw new Error('You cannot place a template controller on a surrogate element.');
} else { //attached behavior
behaviorInstructions.push(instruction);
}
} else if (attrName !== 'id' && attrName !== 'part' && attrName !== 'replace-part') {
hasValues = true;
values[attrName] = attrValue;
}
}
}
if (expressions.length || behaviorInstructions.length || hasValues) {
for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) {
instruction = behaviorInstructions[i];
instruction.type.compile(this, resources, node, instruction);
providers.push(instruction.type.target);
}
for (i = 0, ii = expressions.length; i < ii; ++i) {
expression = expressions[i];
if (expression.attrToRemove !== undefined) {
node.removeAttribute(expression.attrToRemove);
}
}
return TargetInstruction.surrogate(providers, behaviorInstructions, expressions, values);
}
return null;
}
ViewCompiler.prototype._configureProperties = function(instruction, resources) {
let type = instruction.type;
let attrName = instruction.attrName;
let attributes = instruction.attributes;
let property;
let key;
let value;
let knownAttribute = resources.mapAttribute(attrName);
if (knownAttribute && attrName in attributes && knownAttribute !== attrName) {
attributes[knownAttribute] = attributes[attrName];
delete attributes[attrName];
}
for (key in attributes) {
value = attributes[key];
if (value !== null && typeof value === 'object') {
property = type.attributes[key];
if (property !== undefined) {
value.targetProperty = property.name;
} else {
value.targetProperty = key;
}
}
}
}
/**
* Override built-in to demo
*
*/
let nextInjectorId = 0;
function getNextInjectorId() {
return ++nextInjectorId;
}
let lastAUTargetID = 0;
function getNextAUTargetID() {
return (++lastAUTargetID).toString();
}
function makeIntoInstructionTarget(element) {
let value = element.getAttribute('class');
let auTargetID = getNextAUTargetID();
element.setAttribute('class', (value ? value + ' au-target' : 'au-target'));
element.setAttribute('au-target-id', auTargetID);
return auTargetID;
}
function makeShadowSlot(compiler, resources, node, instructions, parentInjectorId) {
let auShadowSlot = DOM.createElement('au-shadow-slot');
DOM.replaceNode(auShadowSlot, node);
let auTargetID = makeIntoInstructionTarget(auShadowSlot);
let instruction = TargetInstruction.shadowSlot(parentInjectorId);
instruction.slotName = node.getAttribute('name') || ShadowDOM.defaultSlotKey;
instruction.slotDestination = node.getAttribute('slot');
if (node.innerHTML.trim()) {
let fragment = DOM.createDocumentFragment();
let child;
while (child = node.firstChild) {
fragment.appendChild(child);
}
instruction.slotFallbackFactory = compiler.compile(fragment, resources);
}
instructions[auTargetID] = instruction;
return auShadowSlot;
}
import {
Container,
resolver,
ViewFactory,
BoundViewFactory,
View,
ViewSlot,
ShadowSlot,
PassThroughSlot,
ViewResources,
BehaviorInstruction,
TargetInstruction,
DOM,
ElementEvents,
CompositionTransaction
} from 'aurelia-framework';
ViewFactory.prototype.create = function(container, createInstruction, element) {
createInstruction = createInstruction || BehaviorInstruction.normal;
let cachedView = this.getCachedView();
if (cachedView !== null) {
return cachedView;
}
let fragment = createInstruction.enhance ? this.template : this.template.cloneNode(true);
let instructables = fragment.querySelectorAll('.au-target');
let instructions = this.instructions;
let resources = this.resources;
let controllers = [];
/**************************
* Interesting part
*
* Create let bindings before anything else
*
**************************/
let bindings = this.instructions.letExpressions.map(e => e.createBinding());
let children = [];
let shadowSlots = Object.create(null);
let containers = { root: container };
let partReplacements = createInstruction.partReplacements;
let i;
let ii;
let view;
let instructable;
let instruction;
this.resources._invokeHook('beforeCreate', this, container, fragment, createInstruction);
if (element && this.surrogateInstruction !== null) {
applySurrogateInstruction(container, element, this.surrogateInstruction, controllers, bindings, children);
}
if (createInstruction.enhance && fragment.hasAttribute('au-target-id')) {
instructable = fragment;
instruction = instructions[instructable.getAttribute('au-target-id')];
applyInstructions(containers, instructable, instruction, controllers, bindings, children, shadowSlots, partReplacements, resources);
}
for (i = 0, ii = instructables.length; i < ii; ++i) {
instructable = instructables[i];
instruction = instructions[instructable.getAttribute('au-target-id')];
applyInstructions(containers, instructable, instruction, controllers, bindings, children, shadowSlots, partReplacements, resources);
}
view = new View(container, this, fragment, controllers, bindings, children, shadowSlots);
if (!createInstruction.initiatedByBehavior) {
view.created();
}
this.resources._invokeHook('afterCreate', view);
return view;
}
@resolver
class ProviderResolver {
get(container, key) {
let id = key.__providerId__;
return id in container ? container[id] : (container[id] = container.invoke(key));
}
}
let providerResolverInstance = new ProviderResolver();
function elementContainerGet(key) {
if (key === DOM.Element) {
return this.element;
}
if (key === BoundViewFactory) {
if (this.boundViewFactory) {
return this.boundViewFactory;
}
let factory = this.instruction.viewFactory;
let partReplacements = this.partReplacements;
if (partReplacements) {
factory = partReplacements[factory.part] || factory;
}
this.boundViewFactory = new BoundViewFactory(this, factory, partReplacements);
return this.boundViewFactory;
}
if (key === ViewSlot) {
if (this.viewSlot === undefined) {
this.viewSlot = new ViewSlot(this.element, this.instruction.anchorIsContainer);
this.element.isContentProjectionSource = this.instruction.lifting;
this.children.push(this.viewSlot);
}
return this.viewSlot;
}
if (key === ElementEvents) {
return this.elementEvents || (this.elementEvents = new ElementEvents(this.element));
}
if (key === CompositionTransaction) {
return this.compositionTransaction || (this.compositionTransaction = this.parent.get(key));
}
if (key === ViewResources) {
return this.viewResources;
}
if (key === TargetInstruction) {
return this.instruction;
}
return this.superGet(key);
}
function createElementContainer(parent, element, instruction, children, partReplacements, resources) {
let container = parent.createChild();
let providers;
let i;
container.element = element;
container.instruction = instruction;
container.children = children;
container.viewResources = resources;
container.partReplacements = partReplacements;
providers = instruction.providers;
i = providers.length;
while (i--) {
container._resolvers.set(providers[i], providerResolverInstance);
}
container.superGet = container.get;
container.get = elementContainerGet;
return container;
}
function hasAttribute(name) {
return this._element.hasAttribute(name);
}
function getAttribute(name) {
return this._element.getAttribute(name);
}
function setAttribute(name, value) {
this._element.setAttribute(name, value);
}
function makeElementIntoAnchor(element, elementInstruction) {
let anchor = DOM.createComment('anchor');
if (elementInstruction) {
let firstChild = element.firstChild;
if (firstChild && firstChild.tagName === 'AU-CONTENT') {
anchor.contentElement = firstChild;
}
anchor._element = element;
anchor.hasAttribute = hasAttribute;
anchor.getAttribute = getAttribute;
anchor.setAttribute = setAttribute;
}
DOM.replaceNode(anchor, element);
return anchor;
}
function applyInstructions(containers, element, instruction, controllers, bindings, children, shadowSlots, partReplacements, resources) {
let behaviorInstructions = instruction.behaviorInstructions;
let expressions = instruction.expressions;
let elementContainer;
let i;
let ii;
let current;
let instance;
if (instruction.contentExpression) {
bindings.push(instruction.contentExpression.createBinding(element.nextSibling));
element.nextSibling.auInterpolationTarget = true;
element.parentNode.removeChild(element);
return;
}
if (instruction.shadowSlot) {
let commentAnchor = DOM.createComment('slot');
let slot;
if (instruction.slotDestination) {
slot = new PassThroughSlot(commentAnchor, instruction.slotName, instruction.slotDestination, instruction.slotFallbackFactory);
} else {
slot = new ShadowSlot(commentAnchor, instruction.slotName, instruction.slotFallbackFactory);
}
DOM.replaceNode(commentAnchor, element);
shadowSlots[instruction.slotName] = slot;
controllers.push(slot);
return;
}
if (behaviorInstructions.length) {
if (!instruction.anchorIsContainer) {
element = makeElementIntoAnchor(element, instruction.elementInstruction);
}
containers[instruction.injectorId] = elementContainer =
createElementContainer(
containers[instruction.parentInjectorId],
element,
instruction,
children,
partReplacements,
resources
);
for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) {
current = behaviorInstructions[i];
instance = current.type.create(elementContainer, current, element, bindings);
controllers.push(instance);
}
}
for (i = 0, ii = expressions.length; i < ii; ++i) {
bindings.push(expressions[i].createBinding(element));
}
}
function styleStringToObject(style, target) {
let attributes = style.split(';');
let firstIndexOfColon;
let i;
let current;
let key;
let value;
target = target || {};
for (i = 0; i < attributes.length; i++) {
current = attributes[i];
firstIndexOfColon = current.indexOf(':');
key = current.substring(0, firstIndexOfColon).trim();
value = current.substring(firstIndexOfColon + 1).trim();
target[key] = value;
}
return target;
}
function styleObjectToString(obj) {
let result = '';
for (let key in obj) {
result += key + ':' + obj[key] + ';';
}
return result;
}
function applySurrogateInstruction(container, element, instruction, controllers, bindings, children) {
let behaviorInstructions = instruction.behaviorInstructions;
let expressions = instruction.expressions;
let providers = instruction.providers;
let values = instruction.values;
let i;
let ii;
let current;
let instance;
let currentAttributeValue;
i = providers.length;
while (i--) {
container._resolvers.set(providers[i], providerResolverInstance);
}
//apply surrogate attributes
for (let key in values) {
currentAttributeValue = element.getAttribute(key);
if (currentAttributeValue) {
if (key === 'class') {
//merge the surrogate classes
element.setAttribute('class', currentAttributeValue + ' ' + values[key]);
} else if (key === 'style') {
//merge the surrogate styles
let styleObject = styleStringToObject(values[key]);
styleStringToObject(currentAttributeValue, styleObject);
element.setAttribute('style', styleObjectToString(styleObject));
}
//otherwise, do not overwrite the consumer's attribute
} else {
//copy the surrogate attribute
element.setAttribute(key, values[key]);
}
}
//apply surrogate behaviors
if (behaviorInstructions.length) {
for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) {
current = behaviorInstructions[i];
instance = current.type.create(container, current, element, bindings);
if (instance.contentView) {
children.push(instance.contentView);
}
controllers.push(instance);
}
}
//apply surrogate bindings
for (i = 0, ii = expressions.length; i < ii; ++i) {
bindings.push(expressions[i].createBinding(element));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment