fix(compiler): always wrap views into an own <template> element
				
					
				
			This is needed to allow view instantiation also in browsers that don’t support the `<template>` element and because of this would return elements from the content of `<template>` elements when using `element.querySelectorAll`. Also stores the `elementBinder.nestedProtoView` correctly when a template directive was used on a `<template>` element.
This commit is contained in:
		
							parent
							
								
									95d86d151a
								
							
						
					
					
						commit
						63053438ea
					
				| @ -1,5 +1,6 @@ | ||||
| import {isBlank} from 'facade/lang'; | ||||
| import {List, ListWrapper} from 'facade/collection'; | ||||
| import {DOM} from 'facade/dom'; | ||||
| import {DOM, Element} from 'facade/dom'; | ||||
| import {CompileElement} from './compile_element'; | ||||
| import {CompileStep} from './compile_step'; | ||||
| 
 | ||||
| @ -13,12 +14,14 @@ export class CompileControl { | ||||
|   _parent:CompileElement; | ||||
|   _current:CompileElement; | ||||
|   _results; | ||||
|   _additionalChildren; | ||||
|   constructor(steps) { | ||||
|     this._steps = steps; | ||||
|     this._currentStepIndex = 0; | ||||
|     this._parent = null; | ||||
|     this._current = null; | ||||
|     this._results = null; | ||||
|     this._additionalChildren = null; | ||||
|   } | ||||
| 
 | ||||
|   // only public so that it can be used by compile_pipeline
 | ||||
| @ -39,15 +42,21 @@ export class CompileControl { | ||||
| 
 | ||||
|     this._currentStepIndex = previousStepIndex; | ||||
|     this._parent = previousParent; | ||||
| 
 | ||||
|     var localAdditionalChildren = this._additionalChildren; | ||||
|     this._additionalChildren = null; | ||||
|     return localAdditionalChildren; | ||||
|   } | ||||
| 
 | ||||
|   addParent(newElement:CompileElement) { | ||||
|     var currEl = this._current.element; | ||||
|     var newEl = newElement.element; | ||||
|     DOM.parentElement(currEl).insertBefore(newEl, currEl); | ||||
|     DOM.appendChild(newEl, currEl); | ||||
| 
 | ||||
|     this.internalProcess(this._results, this._currentStepIndex+1, this._parent, newElement); | ||||
|     this._parent = newElement; | ||||
|   } | ||||
| 
 | ||||
|   addChild(element:CompileElement) { | ||||
|     if (isBlank(this._additionalChildren)) { | ||||
|       this._additionalChildren = ListWrapper.create(); | ||||
|     } | ||||
|     ListWrapper.push(this._additionalChildren, element); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| import {isPresent} from 'facade/lang'; | ||||
| import {List, ListWrapper} from 'facade/collection'; | ||||
| import {Element, Node, DOM} from 'facade/dom'; | ||||
| import {CompileElement} from './compile_element'; | ||||
| @ -17,18 +18,24 @@ export class CompilePipeline { | ||||
| 
 | ||||
|   process(rootElement:Element):List { | ||||
|     var results = ListWrapper.create(); | ||||
|     this._process(results, null, rootElement); | ||||
|     this._process(results, null, new CompileElement(rootElement)); | ||||
|     return results; | ||||
|   } | ||||
| 
 | ||||
|   _process(results, parent:CompileElement, element:Element) { | ||||
|     var current = new CompileElement(element); | ||||
|     this._control.internalProcess(results, 0, parent, current); | ||||
|     var childNodes = DOM.templateAwareRoot(element).childNodes; | ||||
|   _process(results, parent:CompileElement, current:CompileElement) { | ||||
|     var additionalChildren = this._control.internalProcess(results, 0, parent, current); | ||||
| 
 | ||||
|     var childNodes = DOM.templateAwareRoot(current.element).childNodes; | ||||
|     for (var i=0; i<childNodes.length; i++) { | ||||
|       var node = childNodes[i]; | ||||
|       if (node.nodeType === Node.ELEMENT_NODE) { | ||||
|         this._process(results, current, node); | ||||
|         this._process(results, current, new CompileElement(node)); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (isPresent(additionalChildren)) { | ||||
|       for (var i=0; i<additionalChildren.length; i++) { | ||||
|         this._process(results, current, additionalChildren[i]); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import {isBlank, isPresent} from 'facade/lang'; | ||||
| import {DOM, TemplateElement} from 'facade/dom'; | ||||
| import {MapWrapper, StringMapWrapper} from 'facade/collection'; | ||||
| import {MapWrapper, ListWrapper} from 'facade/collection'; | ||||
| 
 | ||||
| import {Parser} from 'change_detection/parser/parser'; | ||||
| 
 | ||||
| @ -9,9 +9,20 @@ import {CompileElement} from './compile_element'; | ||||
| import {CompileControl} from './compile_control'; | ||||
| 
 | ||||
| /** | ||||
|  * Splits views at template directives: | ||||
|  * Replaces the element with an empty <template> element that contains the | ||||
|  * template directive and all property bindings needed for the template directive. | ||||
|  * Splits views at `<template>` elements or elements with `template` attribute: | ||||
|  * For `<template>` elements: | ||||
|  * - moves the content into a new and disconnected `<template>` element | ||||
|  *   that is marked as view root. | ||||
|  * | ||||
|  * For elements with a `template` attribute: | ||||
|  * - replaces the element with an empty `<template>` element, | ||||
|  *   parses the content of the `template` attribute and adds the information to that | ||||
|  *   `<template>` element. Marks the elements as view root. | ||||
|  * | ||||
|  * Note: In both cases the root of the nested view is disconnected from its parent element. | ||||
|  * This is needed for browsers that don't support the `<template>` element | ||||
|  * as we want to do locate elements with bindings using `getElementsByClassName` later on, | ||||
|  * which should not descend into the nested view. | ||||
|  * | ||||
|  * Fills: | ||||
|  * - CompileElement#isViewRoot | ||||
| @ -26,19 +37,41 @@ export class ViewSplitter extends CompileStep { | ||||
| 
 | ||||
|   process(parent:CompileElement, current:CompileElement, control:CompileControl) { | ||||
|     var element = current.element; | ||||
|     if (isBlank(parent) || (current.element instanceof TemplateElement)) { | ||||
|     if (isBlank(parent)) { | ||||
|       current.isViewRoot = true; | ||||
|     } else { | ||||
|       if (current.element instanceof TemplateElement) { | ||||
|         if (!current.isViewRoot) { | ||||
|           var viewRoot = new CompileElement(DOM.createTemplate('')); | ||||
|           this._moveChildNodes(current.element.content, viewRoot.element.content); | ||||
|           viewRoot.isViewRoot = true; | ||||
|           control.addChild(viewRoot); | ||||
|         } | ||||
|       } else { | ||||
|         var templateBindings = MapWrapper.get(current.attrs(), 'template'); | ||||
|         if (isPresent(templateBindings)) { | ||||
|           var newParent = new CompileElement(DOM.createTemplate('')); | ||||
|           current.isViewRoot = true; | ||||
|         var templateElement = DOM.createTemplate(''); | ||||
|         var newParentElement = new CompileElement(templateElement); | ||||
|         this._parseTemplateBindings(templateBindings, newParentElement); | ||||
|         control.addParent(newParentElement); | ||||
|           this._parseTemplateBindings(templateBindings, newParent); | ||||
|           this._addParentElement(current.element, newParent.element); | ||||
| 
 | ||||
|           control.addParent(newParent); | ||||
|           current.element.remove(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   _moveChildNodes(source, target) { | ||||
|     while (isPresent(source.firstChild)) { | ||||
|       DOM.appendChild(target, source.firstChild); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   _addParentElement(currentElement, newParentElement) { | ||||
|     DOM.parentElement(currentElement).insertBefore(newParentElement, currentElement); | ||||
|     DOM.appendChild(newParentElement, currentElement); | ||||
|   } | ||||
| 
 | ||||
|   _parseTemplateBindings(templateBindings:string, compileElement:CompileElement) { | ||||
|     var bindings = this._parser.parseTemplateBindings(templateBindings); | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib'; | ||||
| import {ListWrapper, List} from 'facade/collection'; | ||||
| import {DOM} from 'facade/dom'; | ||||
| import {isPresent, NumberWrapper} from 'facade/lang'; | ||||
| import {isPresent, NumberWrapper, StringWrapper} from 'facade/lang'; | ||||
| 
 | ||||
| import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline'; | ||||
| import {CompileElement} from 'core/compiler/pipeline/compile_element'; | ||||
| @ -21,16 +21,6 @@ export function main() { | ||||
|     }); | ||||
| 
 | ||||
|     describe('control.addParent', () => { | ||||
|       it('should wrap the underlying DOM element', () => { | ||||
|         var element = createElement('<div id="1"><span wrap0="1" id="2"><b id="3"></b></span></div>'); | ||||
|         var pipeline = new CompilePipeline([ | ||||
|           createWrapperStep('wrap0', []) | ||||
|         ]); | ||||
|         pipeline.process(element); | ||||
| 
 | ||||
|         expect(DOM.getOuterHTML(element)).toEqual('<div id="1"><a id="wrap0#0"><span wrap0="1" id="2"><b id="3"></b></span></a></div>'); | ||||
|       }); | ||||
| 
 | ||||
|       it('should report the new parent to the following processor and the result', () => { | ||||
|         var element = createElement('<div id="1"><span wrap0="1" id="2"><b id="3"></b></span></div>'); | ||||
|         var step0Log = []; | ||||
| @ -95,6 +85,26 @@ export function main() { | ||||
| 
 | ||||
|     }); | ||||
| 
 | ||||
|     describe('control.addChild', () => { | ||||
|       it('should report the new child to all processors and the result', () => { | ||||
|         var element = createElement('<div id="1"><div id="2"></div></div>'); | ||||
|         var resultLog = []; | ||||
|         var newChild = new CompileElement(createElement('<div id="3"></div>')); | ||||
|         var pipeline = new CompilePipeline([ | ||||
|           new MockStep((parent, current, control) => { | ||||
|             if (StringWrapper.equals(current.element.id, '1')) { | ||||
|               control.addChild(newChild); | ||||
|             } | ||||
|           }), | ||||
|           createLoggerStep(resultLog) | ||||
|         ]); | ||||
|         var result = pipeline.process(element); | ||||
|         expect(result[2]).toBe(newChild); | ||||
|         expect(resultLog).toEqual(['1', '1<2', '1<3']); | ||||
|         expect(resultIdLog(result)).toEqual(['1', '2', '3']); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -22,21 +22,35 @@ export function main() { | ||||
|       expect(results[0].isViewRoot).toBe(true); | ||||
|     }); | ||||
| 
 | ||||
|     it('should mark <template> elements as viewRoot', () => { | ||||
|       var rootElement = createElement('<div><template></template></div>'); | ||||
|     describe('<template> elements', () => { | ||||
| 
 | ||||
|       it('should move the content into a new <template> element and mark that as viewRoot', () => { | ||||
|         var rootElement = createElement('<div><template if="true">a</template></div>'); | ||||
|         var results = createPipeline().process(rootElement); | ||||
|       expect(results[1].isViewRoot).toBe(true); | ||||
|         expect(DOM.getOuterHTML(results[1].element)).toEqual('<template if="true"></template>'); | ||||
|         expect(results[1].isViewRoot).toBe(false); | ||||
|         expect(DOM.getOuterHTML(results[2].element)).toEqual('<template>a</template>'); | ||||
|         expect(results[2].isViewRoot).toBe(true); | ||||
|       }); | ||||
| 
 | ||||
|       it('should not wrap a root <template> element', () => { | ||||
|         var rootElement = createElement('<div></div>'); | ||||
|         var results = createPipeline().process(rootElement); | ||||
|         expect(results.length).toBe(1); | ||||
|         expect(DOM.getOuterHTML(rootElement)).toEqual('<div></div>'); | ||||
|       }); | ||||
| 
 | ||||
|     }); | ||||
| 
 | ||||
|     describe('elements with template attribute', () => { | ||||
| 
 | ||||
|       it('should insert an empty <template> element', () => { | ||||
|         var rootElement = createElement('<div><div template></div></div>'); | ||||
|       it('should replace the element with an empty <template> element', () => { | ||||
|         var rootElement = createElement('<div><span template=""></span></div>'); | ||||
|         var originalChild = rootElement.childNodes[0]; | ||||
|         var results = createPipeline().process(rootElement); | ||||
|         expect(results[0].element).toBe(rootElement); | ||||
|         expect(results[1].element instanceof TemplateElement).toBe(true); | ||||
|         expect(DOM.getInnerHTML(results[1].element)).toEqual(''); | ||||
|         expect(DOM.getOuterHTML(results[0].element)).toEqual('<div><template></template></div>'); | ||||
|         expect(DOM.getOuterHTML(results[2].element)).toEqual('<span template=""></span>') | ||||
|         expect(results[2].element).toBe(originalChild); | ||||
|       }); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user