-
-
Save AshleyGrant/3221d1010e9969dcd36704799f6f6f9f to your computer and use it in GitHub Desktop.
Aurelia let element
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template bindable='block, title' style='display: block; padding: 4px; background: ${background || "lightgreen"}'> | |
<header>${title}</header> | |
<div></div> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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