Last active
August 7, 2017 14:39
-
-
Save bigopon/91f357a81dcf8d9a6a1720646d3b7cc2 to your computer and use it in GitHub Desktop.
View Compiler let
This file contains hidden or 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='./dynamic-input.htm'></require> | |
<style> | |
.input-wrap { display: block; padding: 5px; background: #dedede; border: 1px solid #eee; } | |
.input-wrap + .input-wrap { margin-top: 10px; } | |
.input-wrap > * { padding-top: 5px; } | |
fieldset { margin: 5px; padding: 5px; border: 1px solid #999; } | |
dynamic-input { display: block; } | |
</style> | |
<div class='input-wrap'> | |
<h5>Dynamic Input Element:</h5> | |
<hr/> | |
<dynamic-input | |
view-model.ref='input' | |
type='text' | |
prop1.bind='num1' | |
prop2.bind='num2' | |
prop-1.trigger='onProp1Changed($event)'></dynamic-input> | |
</div> | |
<fieldset test='value.bind: num1; name: num3'> | |
<div class="input-wrap">[num1] in App: <div>${num1 || 'not known'}</div></div> | |
<div class="input-wrap">[prop1] in DynamicInput Element: <div>${input.prop1 || 'not known'}</div></div> | |
<button click.delegate="makeRandom('num1')">Set App's [num1] to a number</button> | |
</fieldset> | |
<fieldset if.bind='num2'> | |
<div class="input-wrap">[num2] in App: <div>${num2 || 'not known'}</div></div> | |
<div class="input-wrap">[prop2] in DynamicInput Element: <div>${input.prop2 || 'not known'}</div></div> | |
<button click.delegate="makeRandom('num2')">Set App's [num2] to a number</button> | |
</fieldset> | |
<br/> | |
<fieldset> | |
[num3] will be updated via event | |
<div class="input-wrap">[num3] in App: <div>${num3 || 'not known'}</div></div> | |
<div class="input-wrap">[prop1] in DynamicInput Element: <div>${input.prop1 || 'not known'}</div></div> | |
</fieldset> | |
</template> |
This file contains hidden or 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 {observable} from './aurelia-framework' | |
import {bindable} from './aurelia-framework' | |
import { | |
HtmlBehaviorResource | |
} from 'aurelia-framework' | |
export class App { | |
makeRandom(prop) { | |
this[prop] = Math.floor(Math.random() * 1e5); | |
} | |
attached() { | |
// console.log(this) | |
} | |
onProp1Changed($event) { | |
console.log($event.detail); // notice console log, logged 2 times because we bind 2 times to same property | |
this.num3 = $event.detail[0]; | |
} | |
} | |
This file contains hidden or 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
/*eslint padded-blocks:0*/ | |
import {useView, customElement, bindable} from 'aurelia-templating'; | |
import {bindingMode} from 'aurelia-binding' | |
import {DOM} from 'aurelia-pal' | |
export function _createDynamicElement(name: string, viewUrl: string, bindableNames: string[]): Function { | |
@customElement(name) | |
@useView(viewUrl) | |
class DynamicElement { | |
constructor(element) { | |
if (element) { | |
this.$el = element; // is this name safe ? | |
} | |
} | |
bind(bindingContext) { | |
this.$parent = bindingContext; | |
} | |
} | |
let parts; | |
let bindableConfig | |
let bindableOptions; | |
let _bindingMode; | |
let baseBinding; | |
let eventMap; | |
let prop; | |
let notifyEvent; | |
for (let i = 0, ii = bindableNames.length; i < ii; ++i) { | |
bindableConfig = bindableNames[i]; | |
parts = bindableConfig.split('&'); | |
_bindingMode = parts[1]; | |
bindableOptions = {}; | |
// Check if has binding mode | |
if (_bindingMode) { | |
bindableOptions.defaultBindingMode = bindingMode[_bindingMode.trim()] || bindingMode.oneWay; | |
} | |
// Check if has notify prop changes by event | |
baseBinding = parts[0].split('^'); | |
prop = baseBinding[0].trim(); | |
if (notifyEvent = baseBinding[1].trim()) { | |
(eventMap || (eventMap = {}))[prop] = notifyEvent; | |
} | |
bindableOptions.name = prop; | |
bindable(bindableOptions)(DynamicElement); | |
} | |
if (eventMap) { | |
// should use define property or just assign ??? | |
let proto = DynamicElement.prototype; | |
proto.$eventMap = eventMap; | |
proto.propertyChanged = propertyChanged; | |
// Inject element too | |
DynamicElement.inject = [DOM.Element]; | |
} | |
return DynamicElement; | |
} | |
function propertyChanged(name, newVal, oldVal) { | |
const $event = DOM.createCustomEvent(this.$eventMap[name], { bubbles: true, detail: [newVal, oldVal] }); | |
this.$el.dispatchEvent($event); | |
} |
This file contains hidden or 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='prop1 ^prop-1 & oneWay, prop2 ^prop2Changed & twoWay'> | |
<div> | |
<label>[prop1] -- oneWay<div>Change event: 'prop-1'</div> | |
<input value.bind='prop1'> | |
</label> | |
</div> | |
<hr/> | |
<div> | |
<label>[prop2] -- twoWay<div>Change event: 'prop-2'</div> | |
<input value.bind='prop2'> | |
</label> | |
</div> | |
</template> |
This file contains hidden or 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="main"> | |
<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 hidden or 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 './xtend-compiler'; | |
import './xtend-html-behavior' | |
console.clear(); | |
export function configure(aurelia) { | |
aurelia.use | |
.standardConfiguration() | |
.globalResources([ | |
'./test' | |
]) | |
.plugin('./xtend-html-resource-plugin'); | |
aurelia.start().then(() => aurelia.setRoot()); | |
} |
This file contains hidden or 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 {bindable} from 'aurelia-framework' | |
export class TestCustomAttribute { | |
@bindable value | |
@bindable name | |
} |
This file contains hidden or 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 { | |
DOM, | |
BehaviorInstruction, | |
TargetInstruction, | |
ViewCompiler | |
} from 'aurelia-framework' | |
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; | |
if (tagName === 'slot') { | |
if (targetLightDOM) { | |
node = makeShadowSlot(this, resources, node, instructions, parentInjectorId); | |
} | |
return node.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); | |
console.log({elementInstruction}) | |
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) { //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; | |
} | |
} | |
} | |
} else if (elementInstruction) { //or if this is on a custom element | |
elementProperty = elementInstruction.type.attributes[info.attrName]; | |
if (elementProperty) { //and this attribute is a custom property | |
info.defaultBindingMode = elementProperty.defaultBindingMode; //set the default binding mode | |
} | |
} | |
if (elementProperty) { | |
instruction = bindingLanguage.createAttributeInstruction(resources, node, info, elementInstruction); | |
} else { | |
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 | |
instruction.originalAttrName = originalAttrName; | |
liftingInstruction = instruction; | |
break; | |
} else { //attached behavior | |
behaviorInstructions.push(instruction); | |
} | |
} else if (elementProperty) { //custom element attribute | |
elementInstruction.attributes[info.attrName].targetProperty = elementProperty.name; | |
} 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 | |
instruction.originalAttrName = originalAttrName; | |
liftingInstruction = instruction; | |
break; | |
} else { //attached behavior | |
behaviorInstructions.push(instruction); | |
} | |
} else if (elementProperty) { //custom element attribute | |
elementInstruction.attributes[attrName] = attrValue; | |
} | |
//else; normal attribute; do nothing | |
} | |
} | |
if (liftingInstruction) { | |
liftingInstruction.viewFactory = viewFactory; | |
node = liftingInstruction.type.compile(this, resources, node, liftingInstruction, parentNode); | |
auTargetID = makeIntoInstructionTarget(node); | |
instructions[auTargetID] = TargetInstruction.lifting(parentInjectorId, liftingInstruction); | |
} else { | |
if (expressions.length || behaviorInstructions.length) { | |
injectorId = behaviorInstructions.length ? getNextInjectorId() : false; | |
for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) { | |
instruction = behaviorInstructions[i]; | |
console.log(instruction) | |
instruction.type.compile(this, resources, node, instruction, parentNode); | |
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); | |
} | |
} | |
auTargetID = makeIntoInstructionTarget(node); | |
instructions[auTargetID] = TargetInstruction.normal( | |
injectorId, | |
parentInjectorId, | |
providers, | |
behaviorInstructions, | |
expressions, | |
elementInstruction | |
); | |
} | |
if (elementInstruction && elementInstruction.skipContentProcessing) { | |
return node.nextSibling; | |
} | |
let currentChild = node.firstChild; | |
while (currentChild) { | |
currentChild = this._compileNode(currentChild, resources, instructions, node, injectorId || parentInjectorId, targetLightDOM); | |
} | |
} | |
return node.nextSibling; | |
} | |
/** | |
* ====================================== | |
* Just overriding things to make it work | |
* | |
* | |
* | |
*/ | |
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; | |
} | |
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 hidden or 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 { | |
BehaviorInstruction, | |
Controller, | |
HtmlBehaviorResource | |
} from 'aurelia-framework' | |
/** | |
* Creates an instance of this behavior. | |
* @param container The DI container to create the instance in. | |
* @param instruction The instruction for this behavior that was constructed during compilation. | |
* @param element The element on which this behavior exists. | |
* @param bindings The bindings that are associated with the view in which this behavior exists. | |
* @return The Controller of this behavior. | |
*/ | |
HtmlBehaviorResource.prototype.create = function(container: Container, instruction?: BehaviorInstruction, element?: Element, bindings?: Binding[]): Controller { | |
let viewHost; | |
let au = null; | |
// console.log({ t: this, instruction, element, bindings }); | |
instruction = instruction || BehaviorInstruction.normal; | |
element = element || null; | |
bindings = bindings || null; | |
if (this.elementName !== null && element) { | |
if (this.usesShadowDOM) { | |
viewHost = element.attachShadow(this.shadowDOMOptions); | |
container.registerInstance(DOM.boundary, viewHost); | |
} else { | |
viewHost = element; | |
if (this.targetShadowDOM) { | |
container.registerInstance(DOM.boundary, viewHost); | |
} | |
} | |
} | |
if (element !== null) { | |
element.au = au = element.au || {}; | |
} | |
let viewModel = instruction.viewModel || container.get(this.target); | |
let controller = new Controller(this, instruction, viewModel, container); | |
let childBindings = this.childBindings; | |
let viewFactory; | |
if (this.liftsContent) { | |
//template controller | |
au.controller = controller; | |
} else if (this.elementName !== null) { | |
//custom element | |
viewFactory = instruction.viewFactory || this.viewFactory; | |
container.viewModel = viewModel; | |
if (viewFactory) { | |
controller.view = viewFactory.create(container, instruction, element); | |
} | |
if (element !== null) { | |
au.controller = controller; | |
if (controller.view) { | |
if (!this.usesShadowDOM && (element.childNodes.length === 1 || element.contentElement)) { //containerless passes content view special contentElement property | |
let contentElement = element.childNodes[0] || element.contentElement; | |
controller.view.contentView = { fragment: contentElement }; //store the content before appending the view | |
contentElement.parentNode && DOM.removeNode(contentElement); //containerless content element has no parent | |
} | |
if (instruction.anchorIsContainer) { | |
if (childBindings !== null) { | |
for (let i = 0, ii = childBindings.length; i < ii; ++i) { | |
controller.view.addBinding(childBindings[i].create(element, viewModel, controller)); | |
} | |
} | |
controller.view.appendNodesTo(viewHost); | |
} else { | |
controller.view.insertNodesBefore(viewHost); | |
} | |
} else if (childBindings !== null) { | |
for (let i = 0, ii = childBindings.length; i < ii; ++i) { | |
bindings.push(childBindings[i].create(element, viewModel, controller)); | |
} | |
} | |
} else if (controller.view) { | |
//dynamic element with view | |
controller.view.controller = controller; | |
if (childBindings !== null) { | |
for (let i = 0, ii = childBindings.length; i < ii; ++i) { | |
controller.view.addBinding(childBindings[i].create(instruction.host, viewModel, controller)); | |
} | |
} | |
} else if (childBindings !== null) { | |
//dynamic element without view | |
for (let i = 0, ii = childBindings.length; i < ii; ++i) { | |
bindings.push(childBindings[i].create(instruction.host, viewModel, controller)); | |
} | |
} | |
} else if (childBindings !== null) { | |
//custom attribute | |
for (let i = 0, ii = childBindings.length; i < ii; ++i) { | |
bindings.push(childBindings[i].create(element, viewModel, controller)); | |
} | |
} | |
if (au !== null) { | |
au[this.htmlName] = controller; | |
} | |
if (instruction.initiatedByBehavior && viewFactory) { | |
controller.view.created(); | |
} | |
return controller; | |
} |
This file contains hidden or 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 {ViewEngine} from 'aurelia-templating'; | |
import {_createDynamicElement} from './dynamic-element'; | |
/*eslint padded-blocks:0*/ | |
import {useView, customElement, bindable} from 'aurelia-templating'; | |
export function getElementName(address) { | |
return /([^\/^\?]+)\.html?/i.exec(address)[1].toLowerCase(); | |
} | |
export function configure(config) { | |
let viewEngine = config.container.get(ViewEngine); | |
let loader = config.aurelia.loader; | |
viewEngine.addResourcePlugin('.htm', { | |
'fetch': function(address) { | |
return loader.loadTemplate(address).then(registryEntry => { | |
let bindable = registryEntry.template.getAttribute('bindable'); | |
let elementName = getElementName(address); | |
if (bindable) { | |
bindable = bindable.split(',').map(x => x.trim()); | |
registryEntry.template.removeAttribute('bindable'); | |
} else { | |
bindable = []; | |
} | |
return { [elementName]: _createDynamicElement(elementName, address, bindable) }; | |
}); | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment