diff --git a/bower.json b/bower.json index c971c7d1..874f0760 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "aurelia-templating", - "version": "0.13.2", + "version": "0.13.3", "description": "An extensible HTML templating engine supporting databinding, custom elements, attached behaviors and more.", "keywords": [ "aurelia", diff --git a/dist/amd/aurelia-templating.d.ts b/dist/amd/aurelia-templating.d.ts index 0158ce89..d13d77bd 100644 --- a/dist/amd/aurelia-templating.d.ts +++ b/dist/amd/aurelia-templating.d.ts @@ -140,7 +140,7 @@ declare module 'aurelia-templating' { // NOTE: Adding a fragment to the document causes the nodes to be removed from the fragment. // NOTE: Adding to the fragment, causes the nodes to be removed from the document. export class View { - constructor(fragment: any, behaviors: any, bindings: any, children: any, systemControlled: any, contentSelectors: any); + constructor(container: any, fragment: any, behaviors: any, bindings: any, children: any, systemControlled: any, contentSelectors: any); created(executionContext: any): any; bind(executionContext: any, systemUpdate: any): any; addBinding(binding: any): any; @@ -186,13 +186,14 @@ declare module 'aurelia-templating' { } export class ViewFactory { constructor(template: any, instructions: any, resources: any); - create(container: any, executionContext: any, options?: any): any; + create(container: any, executionContext: any, options?: any, element?: any): any; } export class ViewCompiler { static inject(): any; constructor(bindingLanguage: any); compile(templateOrFragment: any, resources: any, options?: any): any; compileNode(node: any, resources: any, instructions: any, parentNode: any, parentInjectorId: any, targetLightDOM: any): any; + compileSurrogate(node: any, resources: any): any; compileElement(node: any, resources: any, instructions: any, parentNode: any, parentInjectorId: any, targetLightDOM: any): any; } export class ViewEngine { diff --git a/dist/amd/index.js b/dist/amd/index.js index fe12a9e9..8edb587c 100644 --- a/dist/amd/index.js +++ b/dist/amd/index.js @@ -393,9 +393,10 @@ define(['exports', 'core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depen exports.ViewResources = ViewResources; var View = (function () { - function View(fragment, behaviors, bindings, children, systemControlled, contentSelectors) { + function View(container, fragment, behaviors, bindings, children, systemControlled, contentSelectors) { _classCallCheck(this, View); + this.container = container; this.fragment = fragment; this.behaviors = behaviors; this.bindings = bindings; @@ -1145,6 +1146,43 @@ define(['exports', 'core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depen } } + function applySurrogateInstruction(container, element, instruction, behaviors, bindings, children) { + var behaviorInstructions = instruction.behaviorInstructions, + expressions = instruction.expressions, + providers = instruction.providers, + values = instruction.values, + i = undefined, + ii = undefined, + current = undefined, + instance = undefined; + + i = providers.length; + while (i--) { + container.registerSingleton(providers[i]); + } + + for (var key in values) { + element.setAttribute(key, values[key]); + } + + if (behaviorInstructions.length) { + for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) { + current = behaviorInstructions[i]; + instance = current.type.create(container, current, element, bindings, current.partReplacements); + + if (instance.contentView) { + children.push(instance.contentView); + } + + behaviors.push(instance); + } + } + + for (i = 0, ii = expressions.length; i < ii; ++i) { + bindings.push(expressions[i].createBinding(element)); + } + } + var BoundViewFactory = (function () { function BoundViewFactory(parentContainer, viewFactory, executionContext) { _classCallCheck(this, BoundViewFactory); @@ -1185,6 +1223,7 @@ define(['exports', 'core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depen ViewFactory.prototype.create = function create(container, executionContext) { var options = arguments[2] === undefined ? defaultFactoryOptions : arguments[2]; + var element = arguments[3] === undefined ? null : arguments[3]; var fragment = this.template.cloneNode(true), instructables = fragment.querySelectorAll('.au-target'), @@ -1200,11 +1239,15 @@ define(['exports', 'core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depen ii, view; + if (element !== null && this.surrogateInstruction !== null) { + applySurrogateInstruction(container, element, this.surrogateInstruction, behaviors, bindings, children); + } + for (i = 0, ii = instructables.length; i < ii; ++i) { applyInstructions(containers, executionContext, instructables[i], instructions[i], behaviors, bindings, children, contentSelectors, partReplacements, resources); } - view = new View(fragment, behaviors, bindings, children, options.systemControlled, contentSelectors); + view = new View(container, fragment, behaviors, bindings, children, options.systemControlled, contentSelectors); view.created(executionContext); if (!options.suppressBind) { @@ -1316,6 +1359,7 @@ define(['exports', 'core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depen content.appendChild(document.createComment('')); var factory = new ViewFactory(content, instructions, resources); + factory.surrogateInstruction = templateOrFragment.content ? this.compileSurrogate(templateOrFragment, resources) : null; if (part) { factory.part = part; @@ -1357,6 +1401,118 @@ define(['exports', 'core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depen return node.nextSibling; }; + ViewCompiler.prototype.compileSurrogate = function compileSurrogate(node, resources) { + var attributes = node.attributes, + bindingLanguage = this.bindingLanguage, + knownAttribute = undefined, + property = undefined, + instruction = undefined, + i = undefined, + ii = undefined, + attr = undefined, + attrName = undefined, + attrValue = undefined, + info = undefined, + type = undefined, + expressions = [], + expression = undefined, + behaviorInstructions = [], + values = {}, + hasValues = false, + providers = []; + + for (i = 0, ii = attributes.length; i < ii; ++i) { + attr = attributes[i]; + attrName = attr.name; + attrValue = attr.value; + + info = bindingLanguage.inspectAttribute(resources, attrName, attrValue); + type = resources.getAttribute(info.attrName); + + 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; + } + } + } + } + + instruction = bindingLanguage.createAttributeInstruction(resources, node, info); + + if (instruction) { + if (instruction.alteredAttr) { + type = resources.getAttribute(instruction.attrName); + } + + if (instruction.discrete) { + expressions.push(instruction); + } else { + if (type) { + instruction.type = type; + configureProperties(instruction, resources); + + if (type.liftsContent) { + throw new Error('You cannot place a template controller on a surrogate element.'); + } else { + behaviorInstructions.push(instruction); + } + } else { + expressions.push(instruction.attributes[instruction.attrName]); + } + } + } else { + if (type) { + instruction = { attrName: attrName, type: type, attributes: {} }; + instruction.attributes[resources.mapAttribute(attrName)] = attrValue; + + if (type.liftsContent) { + throw new Error('You cannot place a template controller on a surrogate element.'); + } else { + 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 { + anchorIsContainer: false, + isCustomElement: false, + injectorId: null, + parentInjectorId: null, + expressions: expressions, + behaviorInstructions: behaviorInstructions, + providers: providers, + values: values + }; + } + + return null; + }; + ViewCompiler.prototype.compileElement = function compileElement(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM) { var tagName = node.tagName.toLowerCase(), attributes = node.attributes, @@ -2313,7 +2469,7 @@ define(['exports', 'core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depen viewFactory = instruction.viewFactory || this.viewFactory; if (viewFactory) { - behaviorInstance.view = viewFactory.create(container, executionContext, instruction); + behaviorInstance.view = viewFactory.create(container, executionContext, instruction, element); } if (element) { diff --git a/dist/commonjs/aurelia-templating.d.ts b/dist/commonjs/aurelia-templating.d.ts index 0158ce89..d13d77bd 100644 --- a/dist/commonjs/aurelia-templating.d.ts +++ b/dist/commonjs/aurelia-templating.d.ts @@ -140,7 +140,7 @@ declare module 'aurelia-templating' { // NOTE: Adding a fragment to the document causes the nodes to be removed from the fragment. // NOTE: Adding to the fragment, causes the nodes to be removed from the document. export class View { - constructor(fragment: any, behaviors: any, bindings: any, children: any, systemControlled: any, contentSelectors: any); + constructor(container: any, fragment: any, behaviors: any, bindings: any, children: any, systemControlled: any, contentSelectors: any); created(executionContext: any): any; bind(executionContext: any, systemUpdate: any): any; addBinding(binding: any): any; @@ -186,13 +186,14 @@ declare module 'aurelia-templating' { } export class ViewFactory { constructor(template: any, instructions: any, resources: any); - create(container: any, executionContext: any, options?: any): any; + create(container: any, executionContext: any, options?: any, element?: any): any; } export class ViewCompiler { static inject(): any; constructor(bindingLanguage: any); compile(templateOrFragment: any, resources: any, options?: any): any; compileNode(node: any, resources: any, instructions: any, parentNode: any, parentInjectorId: any, targetLightDOM: any): any; + compileSurrogate(node: any, resources: any): any; compileElement(node: any, resources: any, instructions: any, parentNode: any, parentInjectorId: any, targetLightDOM: any): any; } export class ViewEngine { diff --git a/dist/commonjs/index.js b/dist/commonjs/index.js index aa27fba2..70259153 100644 --- a/dist/commonjs/index.js +++ b/dist/commonjs/index.js @@ -412,9 +412,10 @@ var ViewResources = (function (_ResourceRegistry) { exports.ViewResources = ViewResources; var View = (function () { - function View(fragment, behaviors, bindings, children, systemControlled, contentSelectors) { + function View(container, fragment, behaviors, bindings, children, systemControlled, contentSelectors) { _classCallCheck(this, View); + this.container = container; this.fragment = fragment; this.behaviors = behaviors; this.bindings = bindings; @@ -1164,6 +1165,43 @@ function applyInstructions(containers, executionContext, element, instruction, b } } +function applySurrogateInstruction(container, element, instruction, behaviors, bindings, children) { + var behaviorInstructions = instruction.behaviorInstructions, + expressions = instruction.expressions, + providers = instruction.providers, + values = instruction.values, + i = undefined, + ii = undefined, + current = undefined, + instance = undefined; + + i = providers.length; + while (i--) { + container.registerSingleton(providers[i]); + } + + for (var key in values) { + element.setAttribute(key, values[key]); + } + + if (behaviorInstructions.length) { + for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) { + current = behaviorInstructions[i]; + instance = current.type.create(container, current, element, bindings, current.partReplacements); + + if (instance.contentView) { + children.push(instance.contentView); + } + + behaviors.push(instance); + } + } + + for (i = 0, ii = expressions.length; i < ii; ++i) { + bindings.push(expressions[i].createBinding(element)); + } +} + var BoundViewFactory = (function () { function BoundViewFactory(parentContainer, viewFactory, executionContext) { _classCallCheck(this, BoundViewFactory); @@ -1204,6 +1242,7 @@ var ViewFactory = (function () { ViewFactory.prototype.create = function create(container, executionContext) { var options = arguments[2] === undefined ? defaultFactoryOptions : arguments[2]; + var element = arguments[3] === undefined ? null : arguments[3]; var fragment = this.template.cloneNode(true), instructables = fragment.querySelectorAll('.au-target'), @@ -1219,11 +1258,15 @@ var ViewFactory = (function () { ii, view; + if (element !== null && this.surrogateInstruction !== null) { + applySurrogateInstruction(container, element, this.surrogateInstruction, behaviors, bindings, children); + } + for (i = 0, ii = instructables.length; i < ii; ++i) { applyInstructions(containers, executionContext, instructables[i], instructions[i], behaviors, bindings, children, contentSelectors, partReplacements, resources); } - view = new View(fragment, behaviors, bindings, children, options.systemControlled, contentSelectors); + view = new View(container, fragment, behaviors, bindings, children, options.systemControlled, contentSelectors); view.created(executionContext); if (!options.suppressBind) { @@ -1335,6 +1378,7 @@ var ViewCompiler = (function () { content.appendChild(document.createComment('')); var factory = new ViewFactory(content, instructions, resources); + factory.surrogateInstruction = templateOrFragment.content ? this.compileSurrogate(templateOrFragment, resources) : null; if (part) { factory.part = part; @@ -1376,6 +1420,118 @@ var ViewCompiler = (function () { return node.nextSibling; }; + ViewCompiler.prototype.compileSurrogate = function compileSurrogate(node, resources) { + var attributes = node.attributes, + bindingLanguage = this.bindingLanguage, + knownAttribute = undefined, + property = undefined, + instruction = undefined, + i = undefined, + ii = undefined, + attr = undefined, + attrName = undefined, + attrValue = undefined, + info = undefined, + type = undefined, + expressions = [], + expression = undefined, + behaviorInstructions = [], + values = {}, + hasValues = false, + providers = []; + + for (i = 0, ii = attributes.length; i < ii; ++i) { + attr = attributes[i]; + attrName = attr.name; + attrValue = attr.value; + + info = bindingLanguage.inspectAttribute(resources, attrName, attrValue); + type = resources.getAttribute(info.attrName); + + 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; + } + } + } + } + + instruction = bindingLanguage.createAttributeInstruction(resources, node, info); + + if (instruction) { + if (instruction.alteredAttr) { + type = resources.getAttribute(instruction.attrName); + } + + if (instruction.discrete) { + expressions.push(instruction); + } else { + if (type) { + instruction.type = type; + configureProperties(instruction, resources); + + if (type.liftsContent) { + throw new Error('You cannot place a template controller on a surrogate element.'); + } else { + behaviorInstructions.push(instruction); + } + } else { + expressions.push(instruction.attributes[instruction.attrName]); + } + } + } else { + if (type) { + instruction = { attrName: attrName, type: type, attributes: {} }; + instruction.attributes[resources.mapAttribute(attrName)] = attrValue; + + if (type.liftsContent) { + throw new Error('You cannot place a template controller on a surrogate element.'); + } else { + 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 { + anchorIsContainer: false, + isCustomElement: false, + injectorId: null, + parentInjectorId: null, + expressions: expressions, + behaviorInstructions: behaviorInstructions, + providers: providers, + values: values + }; + } + + return null; + }; + ViewCompiler.prototype.compileElement = function compileElement(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM) { var tagName = node.tagName.toLowerCase(), attributes = node.attributes, @@ -2332,7 +2488,7 @@ var HtmlBehaviorResource = (function () { viewFactory = instruction.viewFactory || this.viewFactory; if (viewFactory) { - behaviorInstance.view = viewFactory.create(container, executionContext, instruction); + behaviorInstance.view = viewFactory.create(container, executionContext, instruction, element); } if (element) { diff --git a/dist/es6/aurelia-templating.d.ts b/dist/es6/aurelia-templating.d.ts index 0158ce89..d13d77bd 100644 --- a/dist/es6/aurelia-templating.d.ts +++ b/dist/es6/aurelia-templating.d.ts @@ -140,7 +140,7 @@ declare module 'aurelia-templating' { // NOTE: Adding a fragment to the document causes the nodes to be removed from the fragment. // NOTE: Adding to the fragment, causes the nodes to be removed from the document. export class View { - constructor(fragment: any, behaviors: any, bindings: any, children: any, systemControlled: any, contentSelectors: any); + constructor(container: any, fragment: any, behaviors: any, bindings: any, children: any, systemControlled: any, contentSelectors: any); created(executionContext: any): any; bind(executionContext: any, systemUpdate: any): any; addBinding(binding: any): any; @@ -186,13 +186,14 @@ declare module 'aurelia-templating' { } export class ViewFactory { constructor(template: any, instructions: any, resources: any); - create(container: any, executionContext: any, options?: any): any; + create(container: any, executionContext: any, options?: any, element?: any): any; } export class ViewCompiler { static inject(): any; constructor(bindingLanguage: any); compile(templateOrFragment: any, resources: any, options?: any): any; compileNode(node: any, resources: any, instructions: any, parentNode: any, parentInjectorId: any, targetLightDOM: any): any; + compileSurrogate(node: any, resources: any): any; compileElement(node: any, resources: any, instructions: any, parentNode: any, parentInjectorId: any, targetLightDOM: any): any; } export class ViewEngine { diff --git a/dist/es6/index.js b/dist/es6/index.js index 599f45d1..bd3cc1ee 100644 --- a/dist/es6/index.js +++ b/dist/es6/index.js @@ -349,7 +349,8 @@ export class ViewResources extends ResourceRegistry { //NOTE: Adding to the fragment, causes the nodes to be removed from the document. export class View { - constructor(fragment, behaviors, bindings, children, systemControlled, contentSelectors){ + constructor(container, fragment, behaviors, bindings, children, systemControlled, contentSelectors){ + this.container = container; this.fragment = fragment; this.behaviors = behaviors; this.bindings = bindings; @@ -1082,6 +1083,40 @@ function applyInstructions(containers, executionContext, element, instruction, } } +function applySurrogateInstruction(container, element, instruction, behaviors, bindings, children){ + let behaviorInstructions = instruction.behaviorInstructions, + expressions = instruction.expressions, + providers = instruction.providers, + values = instruction.values, + i, ii, current, instance; + + i = providers.length; + while(i--) { + container.registerSingleton(providers[i]); + } + + for(let key in values){ + element.setAttribute(key, values[key]); + } + + if(behaviorInstructions.length){ + for(i = 0, ii = behaviorInstructions.length; i < ii; ++i){ + current = behaviorInstructions[i]; + instance = current.type.create(container, current, element, bindings, current.partReplacements); + + if(instance.contentView){ + children.push(instance.contentView); + } + + behaviors.push(instance); + } + } + + for(i = 0, ii = expressions.length; i < ii; ++i){ + bindings.push(expressions[i].createBinding(element)); + } +} + export class BoundViewFactory { constructor(parentContainer, viewFactory, executionContext){ this.parentContainer = parentContainer; @@ -1112,7 +1147,7 @@ export class ViewFactory{ this.resources = resources; } - create(container, executionContext, options=defaultFactoryOptions){ + create(container, executionContext, options=defaultFactoryOptions, element=null){ var fragment = this.template.cloneNode(true), instructables = fragment.querySelectorAll('.au-target'), instructions = this.instructions, @@ -1125,12 +1160,16 @@ export class ViewFactory{ partReplacements = options.partReplacements || this.partReplacements, i, ii, view; + if(element !== null && this.surrogateInstruction !== null){ + applySurrogateInstruction(container, element, this.surrogateInstruction, behaviors, bindings, children); + } + for(i = 0, ii = instructables.length; i < ii; ++i){ applyInstructions(containers, executionContext, instructables[i], instructions[i], behaviors, bindings, children, contentSelectors, partReplacements, resources); } - view = new View(fragment, behaviors, bindings, children, options.systemControlled, contentSelectors); + view = new View(container, fragment, behaviors, bindings, children, options.systemControlled, contentSelectors); view.created(executionContext); if(!options.suppressBind){ @@ -1216,7 +1255,6 @@ export class ViewCompiler { if(templateOrFragment.content){ part = templateOrFragment.getAttribute('part'); content = document.adoptNode(templateOrFragment.content, true); - //TODO: read in element instructions }else{ content = templateOrFragment; } @@ -1227,6 +1265,7 @@ export class ViewCompiler { content.appendChild(document.createComment('')); var factory = new ViewFactory(content, instructions, resources); + factory.surrogateInstruction = templateOrFragment.content ? this.compileSurrogate(templateOrFragment, resources) : null; if(part){ factory.part = part; @@ -1270,6 +1309,108 @@ export class ViewCompiler { return node.nextSibling; } + compileSurrogate(node, resources){ + let attributes = node.attributes, + bindingLanguage = this.bindingLanguage, + knownAttribute, property, instruction, + i, ii, attr, attrName, attrValue, info, type, + expressions = [], expression, + behaviorInstructions = [], + values = {}, hasValues = false, + providers = []; + + for(i = 0, ii = attributes.length; i < ii; ++i){ + attr = attributes[i]; + attrName = attr.name; + attrValue = attr.value; + + info = bindingLanguage.inspectAttribute(resources, 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 + } + } + } + } + + instruction = bindingLanguage.createAttributeInstruction(resources, node, info); + + 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; + 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 = { attrName:attrName, type:type, attributes:{} }; + 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 { + anchorIsContainer: false, + isCustomElement: false, + injectorId: null, + parentInjectorId: null, + expressions: expressions, + behaviorInstructions: behaviorInstructions, + providers: providers, + values:values + }; + } + + return null; + } + compileElement(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM){ var tagName = node.tagName.toLowerCase(), attributes = node.attributes, @@ -2152,9 +2293,7 @@ export class HtmlBehaviorResource { viewFactory = instruction.viewFactory || this.viewFactory; if(viewFactory){ - //TODO: apply element instructions? var results = viewFactory.applyElementInstructions(container, executionContext, element); - behaviorInstance.view = viewFactory.create(container, executionContext, instruction); - //TODO: register results with view + behaviorInstance.view = viewFactory.create(container, executionContext, instruction, element); } if(element){ diff --git a/dist/index.d.ts b/dist/index.d.ts index 0158ce89..d13d77bd 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -140,7 +140,7 @@ declare module 'aurelia-templating' { // NOTE: Adding a fragment to the document causes the nodes to be removed from the fragment. // NOTE: Adding to the fragment, causes the nodes to be removed from the document. export class View { - constructor(fragment: any, behaviors: any, bindings: any, children: any, systemControlled: any, contentSelectors: any); + constructor(container: any, fragment: any, behaviors: any, bindings: any, children: any, systemControlled: any, contentSelectors: any); created(executionContext: any): any; bind(executionContext: any, systemUpdate: any): any; addBinding(binding: any): any; @@ -186,13 +186,14 @@ declare module 'aurelia-templating' { } export class ViewFactory { constructor(template: any, instructions: any, resources: any); - create(container: any, executionContext: any, options?: any): any; + create(container: any, executionContext: any, options?: any, element?: any): any; } export class ViewCompiler { static inject(): any; constructor(bindingLanguage: any); compile(templateOrFragment: any, resources: any, options?: any): any; compileNode(node: any, resources: any, instructions: any, parentNode: any, parentInjectorId: any, targetLightDOM: any): any; + compileSurrogate(node: any, resources: any): any; compileElement(node: any, resources: any, instructions: any, parentNode: any, parentInjectorId: any, targetLightDOM: any): any; } export class ViewEngine { diff --git a/dist/index.js b/dist/index.js index 599f45d1..bd3cc1ee 100644 --- a/dist/index.js +++ b/dist/index.js @@ -349,7 +349,8 @@ export class ViewResources extends ResourceRegistry { //NOTE: Adding to the fragment, causes the nodes to be removed from the document. export class View { - constructor(fragment, behaviors, bindings, children, systemControlled, contentSelectors){ + constructor(container, fragment, behaviors, bindings, children, systemControlled, contentSelectors){ + this.container = container; this.fragment = fragment; this.behaviors = behaviors; this.bindings = bindings; @@ -1082,6 +1083,40 @@ function applyInstructions(containers, executionContext, element, instruction, } } +function applySurrogateInstruction(container, element, instruction, behaviors, bindings, children){ + let behaviorInstructions = instruction.behaviorInstructions, + expressions = instruction.expressions, + providers = instruction.providers, + values = instruction.values, + i, ii, current, instance; + + i = providers.length; + while(i--) { + container.registerSingleton(providers[i]); + } + + for(let key in values){ + element.setAttribute(key, values[key]); + } + + if(behaviorInstructions.length){ + for(i = 0, ii = behaviorInstructions.length; i < ii; ++i){ + current = behaviorInstructions[i]; + instance = current.type.create(container, current, element, bindings, current.partReplacements); + + if(instance.contentView){ + children.push(instance.contentView); + } + + behaviors.push(instance); + } + } + + for(i = 0, ii = expressions.length; i < ii; ++i){ + bindings.push(expressions[i].createBinding(element)); + } +} + export class BoundViewFactory { constructor(parentContainer, viewFactory, executionContext){ this.parentContainer = parentContainer; @@ -1112,7 +1147,7 @@ export class ViewFactory{ this.resources = resources; } - create(container, executionContext, options=defaultFactoryOptions){ + create(container, executionContext, options=defaultFactoryOptions, element=null){ var fragment = this.template.cloneNode(true), instructables = fragment.querySelectorAll('.au-target'), instructions = this.instructions, @@ -1125,12 +1160,16 @@ export class ViewFactory{ partReplacements = options.partReplacements || this.partReplacements, i, ii, view; + if(element !== null && this.surrogateInstruction !== null){ + applySurrogateInstruction(container, element, this.surrogateInstruction, behaviors, bindings, children); + } + for(i = 0, ii = instructables.length; i < ii; ++i){ applyInstructions(containers, executionContext, instructables[i], instructions[i], behaviors, bindings, children, contentSelectors, partReplacements, resources); } - view = new View(fragment, behaviors, bindings, children, options.systemControlled, contentSelectors); + view = new View(container, fragment, behaviors, bindings, children, options.systemControlled, contentSelectors); view.created(executionContext); if(!options.suppressBind){ @@ -1216,7 +1255,6 @@ export class ViewCompiler { if(templateOrFragment.content){ part = templateOrFragment.getAttribute('part'); content = document.adoptNode(templateOrFragment.content, true); - //TODO: read in element instructions }else{ content = templateOrFragment; } @@ -1227,6 +1265,7 @@ export class ViewCompiler { content.appendChild(document.createComment('')); var factory = new ViewFactory(content, instructions, resources); + factory.surrogateInstruction = templateOrFragment.content ? this.compileSurrogate(templateOrFragment, resources) : null; if(part){ factory.part = part; @@ -1270,6 +1309,108 @@ export class ViewCompiler { return node.nextSibling; } + compileSurrogate(node, resources){ + let attributes = node.attributes, + bindingLanguage = this.bindingLanguage, + knownAttribute, property, instruction, + i, ii, attr, attrName, attrValue, info, type, + expressions = [], expression, + behaviorInstructions = [], + values = {}, hasValues = false, + providers = []; + + for(i = 0, ii = attributes.length; i < ii; ++i){ + attr = attributes[i]; + attrName = attr.name; + attrValue = attr.value; + + info = bindingLanguage.inspectAttribute(resources, 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 + } + } + } + } + + instruction = bindingLanguage.createAttributeInstruction(resources, node, info); + + 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; + 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 = { attrName:attrName, type:type, attributes:{} }; + 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 { + anchorIsContainer: false, + isCustomElement: false, + injectorId: null, + parentInjectorId: null, + expressions: expressions, + behaviorInstructions: behaviorInstructions, + providers: providers, + values:values + }; + } + + return null; + } + compileElement(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM){ var tagName = node.tagName.toLowerCase(), attributes = node.attributes, @@ -2152,9 +2293,7 @@ export class HtmlBehaviorResource { viewFactory = instruction.viewFactory || this.viewFactory; if(viewFactory){ - //TODO: apply element instructions? var results = viewFactory.applyElementInstructions(container, executionContext, element); - behaviorInstance.view = viewFactory.create(container, executionContext, instruction); - //TODO: register results with view + behaviorInstance.view = viewFactory.create(container, executionContext, instruction, element); } if(element){ diff --git a/dist/system/aurelia-templating.d.ts b/dist/system/aurelia-templating.d.ts index 0158ce89..d13d77bd 100644 --- a/dist/system/aurelia-templating.d.ts +++ b/dist/system/aurelia-templating.d.ts @@ -140,7 +140,7 @@ declare module 'aurelia-templating' { // NOTE: Adding a fragment to the document causes the nodes to be removed from the fragment. // NOTE: Adding to the fragment, causes the nodes to be removed from the document. export class View { - constructor(fragment: any, behaviors: any, bindings: any, children: any, systemControlled: any, contentSelectors: any); + constructor(container: any, fragment: any, behaviors: any, bindings: any, children: any, systemControlled: any, contentSelectors: any); created(executionContext: any): any; bind(executionContext: any, systemUpdate: any): any; addBinding(binding: any): any; @@ -186,13 +186,14 @@ declare module 'aurelia-templating' { } export class ViewFactory { constructor(template: any, instructions: any, resources: any); - create(container: any, executionContext: any, options?: any): any; + create(container: any, executionContext: any, options?: any, element?: any): any; } export class ViewCompiler { static inject(): any; constructor(bindingLanguage: any); compile(templateOrFragment: any, resources: any, options?: any): any; compileNode(node: any, resources: any, instructions: any, parentNode: any, parentInjectorId: any, targetLightDOM: any): any; + compileSurrogate(node: any, resources: any): any; compileElement(node: any, resources: any, instructions: any, parentNode: any, parentInjectorId: any, targetLightDOM: any): any; } export class ViewEngine { diff --git a/dist/system/index.js b/dist/system/index.js index c599a178..e4c4b7b8 100644 --- a/dist/system/index.js +++ b/dist/system/index.js @@ -216,6 +216,43 @@ System.register(['core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depende } } + function applySurrogateInstruction(container, element, instruction, behaviors, bindings, children) { + var behaviorInstructions = instruction.behaviorInstructions, + expressions = instruction.expressions, + providers = instruction.providers, + values = instruction.values, + i = undefined, + ii = undefined, + current = undefined, + instance = undefined; + + i = providers.length; + while (i--) { + container.registerSingleton(providers[i]); + } + + for (var key in values) { + element.setAttribute(key, values[key]); + } + + if (behaviorInstructions.length) { + for (i = 0, ii = behaviorInstructions.length; i < ii; ++i) { + current = behaviorInstructions[i]; + instance = current.type.create(container, current, element, bindings, current.partReplacements); + + if (instance.contentView) { + children.push(instance.contentView); + } + + behaviors.push(instance); + } + } + + for (i = 0, ii = expressions.length; i < ii; ++i) { + bindings.push(expressions[i].createBinding(element)); + } + } + function getNextInjectorId() { return ++nextInjectorId; } @@ -775,9 +812,10 @@ System.register(['core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depende _export('ViewResources', ViewResources); View = (function () { - function View(fragment, behaviors, bindings, children, systemControlled, contentSelectors) { + function View(container, fragment, behaviors, bindings, children, systemControlled, contentSelectors) { _classCallCheck(this, View); + this.container = container; this.fragment = fragment; this.behaviors = behaviors; this.bindings = bindings; @@ -1428,6 +1466,7 @@ System.register(['core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depende ViewFactory.prototype.create = function create(container, executionContext) { var options = arguments[2] === undefined ? defaultFactoryOptions : arguments[2]; + var element = arguments[3] === undefined ? null : arguments[3]; var fragment = this.template.cloneNode(true), instructables = fragment.querySelectorAll('.au-target'), @@ -1443,11 +1482,15 @@ System.register(['core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depende ii, view; + if (element !== null && this.surrogateInstruction !== null) { + applySurrogateInstruction(container, element, this.surrogateInstruction, behaviors, bindings, children); + } + for (i = 0, ii = instructables.length; i < ii; ++i) { applyInstructions(containers, executionContext, instructables[i], instructions[i], behaviors, bindings, children, contentSelectors, partReplacements, resources); } - view = new View(fragment, behaviors, bindings, children, options.systemControlled, contentSelectors); + view = new View(container, fragment, behaviors, bindings, children, options.systemControlled, contentSelectors); view.created(executionContext); if (!options.suppressBind) { @@ -1521,6 +1564,7 @@ System.register(['core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depende content.appendChild(document.createComment('')); var factory = new ViewFactory(content, instructions, resources); + factory.surrogateInstruction = templateOrFragment.content ? this.compileSurrogate(templateOrFragment, resources) : null; if (part) { factory.part = part; @@ -1562,6 +1606,118 @@ System.register(['core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depende return node.nextSibling; }; + ViewCompiler.prototype.compileSurrogate = function compileSurrogate(node, resources) { + var attributes = node.attributes, + bindingLanguage = this.bindingLanguage, + knownAttribute = undefined, + property = undefined, + instruction = undefined, + i = undefined, + ii = undefined, + attr = undefined, + attrName = undefined, + attrValue = undefined, + info = undefined, + type = undefined, + expressions = [], + expression = undefined, + behaviorInstructions = [], + values = {}, + hasValues = false, + providers = []; + + for (i = 0, ii = attributes.length; i < ii; ++i) { + attr = attributes[i]; + attrName = attr.name; + attrValue = attr.value; + + info = bindingLanguage.inspectAttribute(resources, attrName, attrValue); + type = resources.getAttribute(info.attrName); + + 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; + } + } + } + } + + instruction = bindingLanguage.createAttributeInstruction(resources, node, info); + + if (instruction) { + if (instruction.alteredAttr) { + type = resources.getAttribute(instruction.attrName); + } + + if (instruction.discrete) { + expressions.push(instruction); + } else { + if (type) { + instruction.type = type; + configureProperties(instruction, resources); + + if (type.liftsContent) { + throw new Error('You cannot place a template controller on a surrogate element.'); + } else { + behaviorInstructions.push(instruction); + } + } else { + expressions.push(instruction.attributes[instruction.attrName]); + } + } + } else { + if (type) { + instruction = { attrName: attrName, type: type, attributes: {} }; + instruction.attributes[resources.mapAttribute(attrName)] = attrValue; + + if (type.liftsContent) { + throw new Error('You cannot place a template controller on a surrogate element.'); + } else { + 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 { + anchorIsContainer: false, + isCustomElement: false, + injectorId: null, + parentInjectorId: null, + expressions: expressions, + behaviorInstructions: behaviorInstructions, + providers: providers, + values: values + }; + } + + return null; + }; + ViewCompiler.prototype.compileElement = function compileElement(node, resources, instructions, parentNode, parentInjectorId, targetLightDOM) { var tagName = node.tagName.toLowerCase(), attributes = node.attributes, @@ -2499,7 +2655,7 @@ System.register(['core-js', 'aurelia-metadata', 'aurelia-path', 'aurelia-depende viewFactory = instruction.viewFactory || this.viewFactory; if (viewFactory) { - behaviorInstance.view = viewFactory.create(container, executionContext, instruction); + behaviorInstance.view = viewFactory.create(container, executionContext, instruction, element); } if (element) { diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index 56eda15b..c651012c 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,3 +1,11 @@ +### 0.13.3 (2015-07-13) + + +#### Features + +* **view-compiler:** enable surrogate behaviors, bindings and attributes ([a1fcdffb](http://github.com/aurelia/templating/commit/a1fcdffbcb1b0dcfbd23387c467302d540a9144f), closes [#61](http://github.com/aurelia/templating/issues/61)) + + ### 0.13.2 (2015-07-07) diff --git a/package.json b/package.json index ecd9930b..0ca2679f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aurelia-templating", - "version": "0.13.2", + "version": "0.13.3", "description": "An extensible HTML templating engine supporting databinding, custom elements, attached behaviors and more.", "keywords": [ "aurelia",