feat(View): implement ProtoView.instantiate
This commit is contained in:
parent
79d270c3dd
commit
31831eee5e
@ -177,8 +177,9 @@ export class ProtoElementInjector extends TreeNode {
|
|||||||
throw 'Maximum number of directives per element has been reached.';
|
throw 'Maximum number of directives per element has been reached.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.textNodes = textNodes;
|
||||||
|
|
||||||
// dummy fields to make analyzer happy
|
// dummy fields to make analyzer happy
|
||||||
this.textNodes = [];
|
|
||||||
this.hasProperties = false;
|
this.hasProperties = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,9 @@ import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facad
|
|||||||
import {ListWrapper} from 'facade/collection';
|
import {ListWrapper} from 'facade/collection';
|
||||||
import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group';
|
import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group';
|
||||||
import {Record} from 'change_detection/record';
|
import {Record} from 'change_detection/record';
|
||||||
import {Module} from 'di/di';
|
|
||||||
import {ProtoElementInjector, ElementInjector} from './element_injector';
|
import {ProtoElementInjector, ElementInjector} from './element_injector';
|
||||||
import {SetterFn} from 'change_detection/facade';
|
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';
|
import {List} from 'facade/collection';
|
||||||
|
|
||||||
/***
|
/***
|
||||||
@ -24,12 +23,13 @@ export class View {
|
|||||||
/// to keep track of the nodes.
|
/// to keep track of the nodes.
|
||||||
@FIELD('final nodes:List<Node>')
|
@FIELD('final nodes:List<Node>')
|
||||||
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
|
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
|
||||||
constructor(fragment:DocumentFragment) {
|
constructor(fragment:DocumentFragment, elementInjector:List, rootElementInjectors:List, textNodes:List) {
|
||||||
this.fragment = fragment;
|
this.fragment = fragment;
|
||||||
this.nodes = ListWrapper.clone(fragment.childNodes);
|
this.nodes = ListWrapper.clone(fragment.childNodes);
|
||||||
|
this.elementInjectors = elementInjector;
|
||||||
|
this.rootElementInjectors = rootElementInjectors;
|
||||||
this.onChangeDispatcher = null;
|
this.onChangeDispatcher = null;
|
||||||
this.elementInjectors = null;
|
this.textNodes = textNodes;
|
||||||
this.textNodes = null;
|
|
||||||
this.bindElements = null;
|
this.bindElements = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,26 +52,72 @@ export class View {
|
|||||||
|
|
||||||
export class ProtoView {
|
export class ProtoView {
|
||||||
@FIELD('final _template:TemplateElement')
|
@FIELD('final _template:TemplateElement')
|
||||||
@FIELD('final _module:Module')
|
@FIELD('final _bindings:List')
|
||||||
@FIELD('final _protoElementInjectors:List<ProtoElementInjector>')
|
@FIELD('final _protoElementInjectors:List<ProtoElementInjector>')
|
||||||
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
|
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
|
||||||
@FIELD('final _useRootElement:bool')
|
@FIELD('final _useRootElement:bool')
|
||||||
constructor(
|
constructor(
|
||||||
template:TemplateElement,
|
template:TemplateElement,
|
||||||
module:Module,
|
bindings:List,
|
||||||
protoElementInjector:List<ProtoElementInjector>,
|
protoElementInjectors:List,
|
||||||
protoWatchGroup:ProtoWatchGroup,
|
protoWatchGroup:ProtoWatchGroup,
|
||||||
useRootElement:boolean)
|
useRootElement:boolean) {
|
||||||
{
|
|
||||||
this._template = template;
|
this._template = template;
|
||||||
this._module = module;
|
this._bindings = bindings;
|
||||||
this._protoElementInjectors = protoElementInjector;
|
this._protoElementInjectors = protoElementInjectors;
|
||||||
|
|
||||||
|
// not implemented
|
||||||
this._protoWatchGroup = protoWatchGroup;
|
this._protoWatchGroup = protoWatchGroup;
|
||||||
this._useRootElement = useRootElement;
|
this._useRootElement = useRootElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
instantiate():View {
|
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]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,49 +1,63 @@
|
|||||||
import {describe, xit, it, expect} from 'test_lib/test_lib';
|
import {describe, xit, it, expect} from 'test_lib/test_lib';
|
||||||
import {ProtoWatchGroup} from 'change_detection/watch_group';
|
import {ProtoView} from 'core/compiler/view';
|
||||||
import {ProtoView, View} from 'core/compiler/view';
|
|
||||||
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
|
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
|
||||||
import {DOM, Element} from 'facade/dom';
|
import {DOM, Element} from 'facade/dom';
|
||||||
import {Module} from 'di/di';
|
|
||||||
|
class Directive {
|
||||||
|
}
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('view', function() {
|
describe('view', function() {
|
||||||
describe('ProtoView', function() {
|
describe('ProtoView', function() {
|
||||||
it('should create an instance of view', function() {
|
it('should create view instance and locate basic parts', function() {
|
||||||
var template = DOM.createTemplate('Hello <b>world</b>!');
|
|
||||||
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() {
|
|
||||||
var template = DOM.createTemplate(
|
var template = DOM.createTemplate(
|
||||||
'<section class="ng-binding" no-injector>' +
|
'<section class="ng-binding">' +
|
||||||
'Hello {}!' +
|
'Hello {}!' +
|
||||||
'<div directive class="ng-binding" injector>' +
|
'<div directive class="ng-binding">' +
|
||||||
'<span class="ng-binding" [hidden]="exp" no-injector>don\'t show me</span>' +
|
'<span class="ng-binding">don\'t show me</span>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</section>');
|
'</section>');
|
||||||
var module:Module = null;
|
|
||||||
var sectionPI = new ProtoElementInjector(null, null, null);
|
var diBindings = [];
|
||||||
sectionPI.textNodes = [0];
|
|
||||||
var divPI = new ProtoElementInjector(null, null, null);
|
var sectionPI = new ProtoElementInjector(null, [], [0]);
|
||||||
var spanPI = new ProtoElementInjector(null, null, null);
|
var divPI = new ProtoElementInjector(sectionPI, [Directive], []);
|
||||||
spanPI.hasProperties = true;
|
var spanPI = new ProtoElementInjector(divPI, [], []);
|
||||||
var protoElementInjector:List<ProtoElementInjector> = [sectionPI, divPI, spanPI];
|
var protoElementInjectors = [sectionPI, divPI, spanPI];
|
||||||
var protoWatchGroup:ProtoWatchGroup = null;
|
|
||||||
var hasSingleRoot:boolean = false;
|
var protoWatchGroup = null;
|
||||||
var pv = new ProtoView(template, module, protoElementInjector, protoWatchGroup, hasSingleRoot);
|
var hasSingleRoot = false;
|
||||||
var view:View = pv.instantiate();
|
var pv = new ProtoView(template, diBindings, protoElementInjectors,
|
||||||
var section:Element = template.content.firstChild;
|
protoWatchGroup, hasSingleRoot);
|
||||||
var div:Element = DOM.getElementsByTagName(section, 'div');
|
|
||||||
var span:Element = DOM.getElementsByTagName(div, 'span');
|
var view = pv.instantiate();
|
||||||
expect(DOM.getInnerHTML(view.fragment)).toEqual(DOM.getInnerHTML(section)); // exclude top level <section>
|
|
||||||
expect(view.nodes).toEqual([view.fragment.firstChild.childNodes]); // TextNode(Hello...), <div>
|
var section = DOM.firstChild(template.content);
|
||||||
var elementInjector:ElementInjector = view.elementInjectors[1];
|
|
||||||
expect(view.elementInjectors).toEqual([null, elementInjector, null]); // only second one has directive
|
expect(DOM.getInnerHTML(DOM.firstChild(view.fragment))).toEqual(DOM.getInnerHTML(section)); // exclude top level <section>
|
||||||
expect(view.bindElements).toEqual([span]);
|
|
||||||
expect(view.textNodes).toEqual([section.childNodes[0]]);
|
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(
|
||||||
|
'<section directive class="ng-binding">' +
|
||||||
|
'<div directive class="ng-binding"></div>' +
|
||||||
|
'</section>');
|
||||||
|
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,7 @@ class ListWrapper {
|
|||||||
static void set(m, k, v) { m[k] = v; }
|
static void set(m, k, v) { m[k] = v; }
|
||||||
static contains(m, k) => m.containsKey(k);
|
static contains(m, k) => m.containsKey(k);
|
||||||
static map(list, fn) => list.map(fn).toList();
|
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 find(List list, fn) => list.firstWhere(fn, orElse:() => null);
|
||||||
static any(List list, fn) => list.any(fn);
|
static any(List list, fn) => list.any(fn);
|
||||||
static forEach(list, fn) {
|
static forEach(list, fn) {
|
||||||
|
@ -47,6 +47,9 @@ export class ListWrapper {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
static filter(array, pred:Function) {
|
||||||
|
return array.filter(pred);
|
||||||
|
}
|
||||||
static any(list:List, pred:Function) {
|
static any(list:List, pred:Function) {
|
||||||
for (var i = 0 ; i < list.length; ++i) {
|
for (var i = 0 ; i < list.length; ++i) {
|
||||||
if (pred(list[i])) return true;
|
if (pred(list[i])) return true;
|
||||||
|
@ -9,6 +9,9 @@ class DOM {
|
|||||||
static query(selector) {
|
static query(selector) {
|
||||||
return document.querySelector(selector);
|
return document.querySelector(selector);
|
||||||
}
|
}
|
||||||
|
static ElementList querySelectorAll(el, String selector) {
|
||||||
|
return el.querySelectorAll(selector);
|
||||||
|
}
|
||||||
static on(element, event, callback) {
|
static on(element, event, callback) {
|
||||||
element.addEventListener(event, callback);
|
element.addEventListener(event, callback);
|
||||||
}
|
}
|
||||||
@ -18,6 +21,12 @@ class DOM {
|
|||||||
static setInnerHTML(el, value) {
|
static setInnerHTML(el, value) {
|
||||||
el.innerHtml = value;
|
el.innerHtml = value;
|
||||||
}
|
}
|
||||||
|
static Node firstChild(el) {
|
||||||
|
return el.firstChild;
|
||||||
|
}
|
||||||
|
static List<Node> childNodes(el) {
|
||||||
|
return el.childNodes;
|
||||||
|
}
|
||||||
static setText(Text text, String value) {
|
static setText(Text text, String value) {
|
||||||
text.text = value;
|
text.text = value;
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,34 @@
|
|||||||
export var DocumentFragment = window.DocumentFragment;
|
export var DocumentFragment = window.DocumentFragment;
|
||||||
export var Node = window.Node;
|
export var Node = window.Node;
|
||||||
|
export var NodeList = window.NodeList;
|
||||||
export var Text = window.Text;
|
export var Text = window.Text;
|
||||||
export var Element = window.HTMLElement;
|
export var Element = window.HTMLElement;
|
||||||
export var TemplateElement = window.HTMLTemplateElement;
|
export var TemplateElement = window.HTMLTemplateElement;
|
||||||
|
import {List} from 'facade/collection';
|
||||||
|
|
||||||
export class DOM {
|
export class DOM {
|
||||||
static query(selector) {
|
static query(selector) {
|
||||||
return document.querySelector(selector);
|
return document.querySelector(selector);
|
||||||
}
|
}
|
||||||
|
static querySelectorAll(el, selector:string):NodeList {
|
||||||
|
return el.querySelectorAll(selector);
|
||||||
|
}
|
||||||
static on(el, evt, listener) {
|
static on(el, evt, listener) {
|
||||||
el.addEventListener(evt, listener, false);
|
el.addEventListener(evt, listener, false);
|
||||||
}
|
}
|
||||||
static getInnerHTML(el) {
|
static getInnerHTML(el) {
|
||||||
return el.innerHTML;
|
return el.innerHTML;
|
||||||
}
|
}
|
||||||
|
static firstChild(el):Node {
|
||||||
|
return el.firstChild;
|
||||||
|
}
|
||||||
|
static childNodes(el):NodeList {
|
||||||
|
return el.childNodes;
|
||||||
|
}
|
||||||
static setInnerHTML(el, value) {
|
static setInnerHTML(el, value) {
|
||||||
el.innerHTML = value;
|
el.innerHTML = value;
|
||||||
}
|
}
|
||||||
static setText(text:Text, value:String) {
|
static setText(text:Text, value:string) {
|
||||||
text.nodeValue = value;
|
text.nodeValue = value;
|
||||||
}
|
}
|
||||||
static createTemplate(html) {
|
static createTemplate(html) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user