diff --git a/modules/core/src/compiler/element_injector.js b/modules/core/src/compiler/element_injector.js index 71f33deed4..754d34054d 100644 --- a/modules/core/src/compiler/element_injector.js +++ b/modules/core/src/compiler/element_injector.js @@ -177,8 +177,9 @@ export class ProtoElementInjector extends TreeNode { throw 'Maximum number of directives per element has been reached.'; } + this.textNodes = textNodes; + // dummy fields to make analyzer happy - this.textNodes = []; this.hasProperties = false; } diff --git a/modules/core/src/compiler/view.js b/modules/core/src/compiler/view.js index 150eec3109..d4e19b5025 100644 --- a/modules/core/src/compiler/view.js +++ b/modules/core/src/compiler/view.js @@ -2,10 +2,9 @@ import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facad import {ListWrapper} from 'facade/collection'; import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group'; import {Record} from 'change_detection/record'; -import {Module} from 'di/di'; import {ProtoElementInjector, ElementInjector} from './element_injector'; import {SetterFn} from 'change_detection/facade'; -import {FIELD, IMPLEMENTS, int} from 'facade/lang'; +import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang'; import {List} from 'facade/collection'; /*** @@ -24,12 +23,13 @@ export class View { /// to keep track of the nodes. @FIELD('final nodes:List') @FIELD('final onChangeDispatcher:OnChangeDispatcher') - constructor(fragment:DocumentFragment) { + constructor(fragment:DocumentFragment, elementInjector:List, rootElementInjectors:List, textNodes:List) { this.fragment = fragment; this.nodes = ListWrapper.clone(fragment.childNodes); + this.elementInjectors = elementInjector; + this.rootElementInjectors = rootElementInjectors; this.onChangeDispatcher = null; - this.elementInjectors = null; - this.textNodes = null; + this.textNodes = textNodes; this.bindElements = null; } @@ -52,26 +52,72 @@ export class View { export class ProtoView { @FIELD('final _template:TemplateElement') - @FIELD('final _module:Module') + @FIELD('final _bindings:List') @FIELD('final _protoElementInjectors:List') @FIELD('final _protoWatchGroup:ProtoWatchGroup') @FIELD('final _useRootElement:bool') constructor( template:TemplateElement, - module:Module, - protoElementInjector:List, + bindings:List, + protoElementInjectors:List, protoWatchGroup:ProtoWatchGroup, - useRootElement:boolean) - { + useRootElement:boolean) { this._template = template; - this._module = module; - this._protoElementInjectors = protoElementInjector; + this._bindings = bindings; + this._protoElementInjectors = protoElementInjectors; + + // not implemented this._protoWatchGroup = protoWatchGroup; this._useRootElement = useRootElement; } instantiate():View { - return new View(DOM.clone(this._template.content)); + var fragment = DOM.clone(this._template.content); + var elements = DOM.querySelectorAll(fragment, ".ng-binding"); + var protos = this._protoElementInjectors; + + /** + * TODO: vsavkin: benchmark + * If this performs poorly, the three loops can be collapsed into one. + */ + var elementInjectors = ProtoView._createElementInjectors(elements, protos); + var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors); + var textNodes = ProtoView._textNodes(elements, protos); + + return new View(fragment, elementInjectors, rootElementInjectors, textNodes); + } + + static _createElementInjectors(elements, protos) { + var injectors = ListWrapper.createFixedSize(protos.length); + for (var i = 0; i < protos.length; ++i) { + injectors[i] = ProtoView._createElementInjector(elements[i], protos[i]); + } + ListWrapper.forEach(protos, p => p.clearElementInjector()); + return injectors; + } + + static _createElementInjector(element, proto) { + //TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged + return proto.hasBindings ? proto.instantiate({view:null}) : null; + } + + static _rootElementInjectors(injectors) { + return ListWrapper.filter(injectors, inj => isPresent(inj) && isBlank(inj.parent)); + } + + static _textNodes(elements, protos) { + var textNodes = []; + for (var i = 0; i < protos.length; ++i) { + ProtoView._collectTextNodes(textNodes, elements[i], protos[i]); + } + return textNodes; + } + + static _collectTextNodes(allTextNodes, element, proto) { + var childNodes = DOM.childNodes(element); + ListWrapper.forEach(proto.textNodes, (i) => { + ListWrapper.push(allTextNodes, childNodes[i]); + }); } } diff --git a/modules/core/test/compiler/view_spec.js b/modules/core/test/compiler/view_spec.js index f20cd89787..0a28d73f3f 100644 --- a/modules/core/test/compiler/view_spec.js +++ b/modules/core/test/compiler/view_spec.js @@ -1,49 +1,63 @@ import {describe, xit, it, expect} from 'test_lib/test_lib'; -import {ProtoWatchGroup} from 'change_detection/watch_group'; -import {ProtoView, View} from 'core/compiler/view'; +import {ProtoView} from 'core/compiler/view'; import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector'; import {DOM, Element} from 'facade/dom'; -import {Module} from 'di/di'; + +class Directive { +} export function main() { describe('view', function() { describe('ProtoView', function() { - it('should create an instance of view', function() { - var template = DOM.createTemplate('Hello world!'); - var pv = new ProtoView(template, null, null, null, false); - var view:View = pv.instantiate(); - expect(view instanceof View).toBe(true); - }); - - - xit('should create view instance and locate basic parts', function() { + it('should create view instance and locate basic parts', function() { var template = DOM.createTemplate( - '
' + + '
' + 'Hello {}!' + - '
' + - 'don\'t show me' + + '
' + + 'don\'t show me' + '
' + '
'); - var module:Module = null; - var sectionPI = new ProtoElementInjector(null, null, null); - sectionPI.textNodes = [0]; - var divPI = new ProtoElementInjector(null, null, null); - var spanPI = new ProtoElementInjector(null, null, null); - spanPI.hasProperties = true; - var protoElementInjector:List = [sectionPI, divPI, spanPI]; - var protoWatchGroup:ProtoWatchGroup = null; - var hasSingleRoot:boolean = false; - var pv = new ProtoView(template, module, protoElementInjector, protoWatchGroup, hasSingleRoot); - var view:View = pv.instantiate(); - var section:Element = template.content.firstChild; - var div:Element = DOM.getElementsByTagName(section, 'div'); - var span:Element = DOM.getElementsByTagName(div, 'span'); - expect(DOM.getInnerHTML(view.fragment)).toEqual(DOM.getInnerHTML(section)); // exclude top level
- expect(view.nodes).toEqual([view.fragment.firstChild.childNodes]); // TextNode(Hello...),
- var elementInjector:ElementInjector = view.elementInjectors[1]; - expect(view.elementInjectors).toEqual([null, elementInjector, null]); // only second one has directive - expect(view.bindElements).toEqual([span]); - expect(view.textNodes).toEqual([section.childNodes[0]]); + + var diBindings = []; + + var sectionPI = new ProtoElementInjector(null, [], [0]); + var divPI = new ProtoElementInjector(sectionPI, [Directive], []); + var spanPI = new ProtoElementInjector(divPI, [], []); + var protoElementInjectors = [sectionPI, divPI, spanPI]; + + var protoWatchGroup = null; + var hasSingleRoot = false; + var pv = new ProtoView(template, diBindings, protoElementInjectors, + protoWatchGroup, hasSingleRoot); + + var view = pv.instantiate(); + + var section = DOM.firstChild(template.content); + + expect(DOM.getInnerHTML(DOM.firstChild(view.fragment))).toEqual(DOM.getInnerHTML(section)); // exclude top level
+ + expect(view.elementInjectors.length).toEqual(3); + expect(view.elementInjectors[0]).toBeNull(); + expect(view.elementInjectors[1]).toBeAnInstanceOf(ElementInjector); + expect(view.elementInjectors[2]).toBeNull(); + + expect(view.textNodes.length).toEqual(1); + expect(view.textNodes[0].nodeValue).toEqual('Hello {}!'); + }); + + it('should set root element injectors', function() { + var template = DOM.createTemplate( + '
' + + '
' + + '
'); + + var sectionPI = new ProtoElementInjector(null, [Directive], []); + var divPI = new ProtoElementInjector(sectionPI, [Directive], []); + + var pv = new ProtoView(template, [], [sectionPI, divPI], null, false); + var view = pv.instantiate(); + + expect(view.rootElementInjectors.length).toEqual(1); }); }); }); diff --git a/modules/facade/src/collection.dart b/modules/facade/src/collection.dart index 5c5530b053..f0e6d1af0a 100644 --- a/modules/facade/src/collection.dart +++ b/modules/facade/src/collection.dart @@ -22,6 +22,7 @@ class ListWrapper { static void set(m, k, v) { m[k] = v; } static contains(m, k) => m.containsKey(k); static map(list, fn) => list.map(fn).toList(); + static filter(List list, fn) => list.where(fn).toList(); static find(List list, fn) => list.firstWhere(fn, orElse:() => null); static any(List list, fn) => list.any(fn); static forEach(list, fn) { diff --git a/modules/facade/src/collection.es6 b/modules/facade/src/collection.es6 index b3ef85167d..a3e00df39a 100644 --- a/modules/facade/src/collection.es6 +++ b/modules/facade/src/collection.es6 @@ -47,6 +47,9 @@ export class ListWrapper { } return null; } + static filter(array, pred:Function) { + return array.filter(pred); + } static any(list:List, pred:Function) { for (var i = 0 ; i < list.length; ++i) { if (pred(list[i])) return true; diff --git a/modules/facade/src/dom.dart b/modules/facade/src/dom.dart index fb80840461..d33e4efe8b 100644 --- a/modules/facade/src/dom.dart +++ b/modules/facade/src/dom.dart @@ -9,6 +9,9 @@ class DOM { static query(selector) { return document.querySelector(selector); } + static ElementList querySelectorAll(el, String selector) { + return el.querySelectorAll(selector); + } static on(element, event, callback) { element.addEventListener(event, callback); } @@ -18,6 +21,12 @@ class DOM { static setInnerHTML(el, value) { el.innerHtml = value; } + static Node firstChild(el) { + return el.firstChild; + } + static List childNodes(el) { + return el.childNodes; + } static setText(Text text, String value) { text.text = value; } diff --git a/modules/facade/src/dom.es6 b/modules/facade/src/dom.es6 index b80e3563c7..621af737a8 100644 --- a/modules/facade/src/dom.es6 +++ b/modules/facade/src/dom.es6 @@ -1,23 +1,34 @@ export var DocumentFragment = window.DocumentFragment; export var Node = window.Node; +export var NodeList = window.NodeList; export var Text = window.Text; export var Element = window.HTMLElement; export var TemplateElement = window.HTMLTemplateElement; +import {List} from 'facade/collection'; export class DOM { static query(selector) { return document.querySelector(selector); } + static querySelectorAll(el, selector:string):NodeList { + return el.querySelectorAll(selector); + } static on(el, evt, listener) { el.addEventListener(evt, listener, false); } static getInnerHTML(el) { return el.innerHTML; } + static firstChild(el):Node { + return el.firstChild; + } + static childNodes(el):NodeList { + return el.childNodes; + } static setInnerHTML(el, value) { el.innerHTML = value; } - static setText(text:Text, value:String) { + static setText(text:Text, value:string) { text.nodeValue = value; } static createTemplate(html) {