2014-10-10 20:44:55 -07:00
|
|
|
import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facade/dom';
|
2014-09-28 13:55:01 -07:00
|
|
|
import {ListWrapper} from 'facade/collection';
|
2014-09-28 16:29:11 -07:00
|
|
|
import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group';
|
|
|
|
import {Record} from 'change_detection/record';
|
|
|
|
import {ProtoElementInjector, ElementInjector} from './element_injector';
|
|
|
|
import {SetterFn} from 'change_detection/facade';
|
2014-10-27 11:47:13 -04:00
|
|
|
import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang';
|
2014-09-28 13:55:01 -07:00
|
|
|
import {List} from 'facade/collection';
|
2014-09-28 16:29:11 -07:00
|
|
|
|
2014-10-10 20:44:55 -07:00
|
|
|
/***
|
|
|
|
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
|
|
|
*/
|
2014-09-28 16:29:11 -07:00
|
|
|
@IMPLEMENTS(WatchGroupDispatcher)
|
|
|
|
export class View {
|
2014-10-10 20:44:55 -07:00
|
|
|
@FIELD('final fragment:DocumentFragment')
|
2014-09-28 16:29:11 -07:00
|
|
|
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
|
2014-10-10 20:44:55 -07:00
|
|
|
@FIELD('final rootElementInjectors:List<ElementInjector>')
|
|
|
|
@FIELD('final elementInjectors:List<ElementInjector>')
|
|
|
|
@FIELD('final bindElements:List<Element>')
|
|
|
|
@FIELD('final textNodes:List<Text>')
|
|
|
|
@FIELD('final watchGroup:WatchGroup')
|
2014-09-28 16:29:11 -07:00
|
|
|
/// When the view is part of render tree, the DocumentFragment is empty, which is why we need
|
|
|
|
/// to keep track of the nodes.
|
2014-10-10 20:44:55 -07:00
|
|
|
@FIELD('final nodes:List<Node>')
|
|
|
|
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
|
2014-10-27 11:47:13 -04:00
|
|
|
constructor(fragment:DocumentFragment, elementInjector:List, rootElementInjectors:List, textNodes:List) {
|
2014-10-10 20:44:55 -07:00
|
|
|
this.fragment = fragment;
|
|
|
|
this.nodes = ListWrapper.clone(fragment.childNodes);
|
2014-10-27 11:47:13 -04:00
|
|
|
this.elementInjectors = elementInjector;
|
|
|
|
this.rootElementInjectors = rootElementInjectors;
|
2014-10-10 20:44:55 -07:00
|
|
|
this.onChangeDispatcher = null;
|
2014-10-27 11:47:13 -04:00
|
|
|
this.textNodes = textNodes;
|
2014-10-10 20:44:55 -07:00
|
|
|
this.bindElements = null;
|
2014-09-28 16:29:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
onRecordChange(record:Record, target) {
|
|
|
|
// dispatch to element injector or text nodes based on context
|
2014-10-10 20:44:55 -07:00
|
|
|
if (target instanceof DirectivePropertyMemento) {
|
|
|
|
// we know that it is DirectivePropertyMemento
|
|
|
|
var directiveMemento:DirectivePropertyMemento = target;
|
|
|
|
directiveMemento.invoke(record, this.elementInjectors);
|
|
|
|
} else if (target instanceof ElementPropertyMemento) {
|
|
|
|
var elementMemento:ElementPropertyMemento = target;
|
|
|
|
elementMemento.invoke(record, this.bindElements);
|
2014-09-28 16:29:11 -07:00
|
|
|
} else {
|
2014-10-10 20:44:55 -07:00
|
|
|
// we know it refers to _textNodes.
|
2014-09-28 16:29:11 -07:00
|
|
|
var textNodeIndex:number = target;
|
2014-10-10 20:44:55 -07:00
|
|
|
DOM.setText(this.textNodes[textNodeIndex], record.currentValue);
|
2014-09-28 16:29:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-28 20:02:32 -07:00
|
|
|
export class ProtoView {
|
2014-10-10 20:44:55 -07:00
|
|
|
@FIELD('final _template:TemplateElement')
|
2014-10-27 11:47:13 -04:00
|
|
|
@FIELD('final _bindings:List')
|
2014-10-10 20:44:55 -07:00
|
|
|
@FIELD('final _protoElementInjectors:List<ProtoElementInjector>')
|
|
|
|
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
|
|
|
|
@FIELD('final _useRootElement:bool')
|
2014-09-28 20:02:32 -07:00
|
|
|
constructor(
|
|
|
|
template:TemplateElement,
|
2014-10-27 11:47:13 -04:00
|
|
|
bindings:List,
|
|
|
|
protoElementInjectors:List,
|
2014-10-10 20:44:55 -07:00
|
|
|
protoWatchGroup:ProtoWatchGroup,
|
2014-10-27 11:47:13 -04:00
|
|
|
useRootElement:boolean) {
|
2014-09-28 20:02:32 -07:00
|
|
|
this._template = template;
|
2014-10-27 11:47:13 -04:00
|
|
|
this._bindings = bindings;
|
|
|
|
this._protoElementInjectors = protoElementInjectors;
|
|
|
|
|
|
|
|
// not implemented
|
2014-09-28 20:02:32 -07:00
|
|
|
this._protoWatchGroup = protoWatchGroup;
|
2014-10-10 20:44:55 -07:00
|
|
|
this._useRootElement = useRootElement;
|
2014-09-28 20:02:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
instantiate():View {
|
2014-10-27 11:47:13 -04:00
|
|
|
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]);
|
|
|
|
});
|
2014-09-28 20:02:32 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-10 20:44:55 -07:00
|
|
|
export class ElementPropertyMemento {
|
|
|
|
@FIELD('final _elementIndex:int')
|
|
|
|
@FIELD('final _propertyIndex:string')
|
|
|
|
constructor(elementIndex:int, propertyName:string) {
|
|
|
|
this._elementIndex = elementIndex;
|
|
|
|
this._propertyName = propertyName;
|
|
|
|
}
|
|
|
|
|
|
|
|
invoke(record:Record, elementInjectors:List<Element>) {
|
|
|
|
var element:Element = elementInjectors[this._elementIndex];
|
|
|
|
DOM.setProperty(element, this._propertyName, record.currentValue);
|
|
|
|
}
|
|
|
|
}
|
2014-09-28 16:29:11 -07:00
|
|
|
|
2014-10-10 20:44:55 -07:00
|
|
|
export class DirectivePropertyMemento {
|
2014-09-28 16:29:11 -07:00
|
|
|
@FIELD('final _elementInjectorIndex:int')
|
|
|
|
@FIELD('final _directiveIndex:int')
|
|
|
|
@FIELD('final _setterName:String')
|
|
|
|
@FIELD('final _setter:SetterFn')
|
|
|
|
constructor(
|
|
|
|
elementInjectorIndex:number,
|
|
|
|
directiveIndex:number,
|
|
|
|
setterName:String,
|
|
|
|
setter:SetterFn)
|
|
|
|
{
|
|
|
|
this._elementInjectorIndex = elementInjectorIndex;
|
|
|
|
this._directiveIndex = directiveIndex;
|
|
|
|
this._setterName = setterName;
|
|
|
|
this._setter = setter;
|
|
|
|
}
|
|
|
|
|
|
|
|
invoke(record:Record, elementInjectors:List<ElementInjector>) {
|
|
|
|
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
|
|
|
|
var directive = elementInjectors[this._directiveIndex];
|
|
|
|
this._setter(directive, record.currentValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//TODO(tbosch): I don't like to have done be called from a different place than notify
|
|
|
|
// notify is called by change detection, but done is called by our wrapper on detect changes.
|
|
|
|
export class OnChangeDispatcher {
|
|
|
|
|
|
|
|
@FIELD('_lastView:View')
|
2014-10-10 20:44:55 -07:00
|
|
|
@FIELD('_lastTarget:DirectivePropertyMemento')
|
2014-09-28 16:29:11 -07:00
|
|
|
constructor() {
|
|
|
|
this._lastView = null;
|
|
|
|
this._lastTarget = null;
|
|
|
|
}
|
|
|
|
|
2014-10-10 20:44:55 -07:00
|
|
|
notify(view:View, eTarget:DirectivePropertyMemento) {
|
2014-09-28 16:29:11 -07:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
done() {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|