210 lines
7.4 KiB
JavaScript
Raw Normal View History

2014-10-10 20:44:55 -07:00
import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facade/dom';
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 {ElementBinder} from './element_binder';
2014-09-28 16:29:11 -07:00
import {SetterFn} from 'change_detection/facade';
import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang';
import {List} from 'facade/collection';
import {Injector} from 'di/di';
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')
constructor(fragment:DocumentFragment, elementInjector:List,
rootElementInjectors:List, textNodes:List, bindElements:List,
protoWatchGroup:ProtoWatchGroup, context) {
2014-10-10 20:44:55 -07:00
this.fragment = fragment;
this.nodes = ListWrapper.clone(fragment.childNodes);
this.elementInjectors = elementInjector;
this.rootElementInjectors = rootElementInjectors;
2014-10-10 20:44:55 -07:00
this.onChangeDispatcher = null;
this.textNodes = textNodes;
this.bindElements = bindElements;
this.watchGroup = protoWatchGroup.instantiate(this);
this.watchGroup.setContext(context);
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) {
2014-10-10 20:44:55 -07:00
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')
@FIELD('final _elementBinders:List<ElementBinder>')
2014-10-10 20:44:55 -07:00
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
@FIELD('final _useRootElement:bool')
2014-09-28 20:02:32 -07:00
constructor(
template:TemplateElement,
elementBinders:List,
2014-10-10 20:44:55 -07:00
protoWatchGroup:ProtoWatchGroup,
useRootElement:boolean) {
2014-09-28 20:02:32 -07:00
this._template = template;
this._elementBinders = elementBinders;
this._protoWatchGroup = protoWatchGroup;
// not implemented
2014-10-10 20:44:55 -07:00
this._useRootElement = useRootElement;
2014-09-28 20:02:32 -07:00
}
instantiate(context, appInjector:Injector):View {
var fragment = DOM.clone(this._template.content);
var elements = DOM.querySelectorAll(fragment, ".ng-binding");
var binders = this._elementBinders;
/**
* TODO: vsavkin: benchmark
* If this performs poorly, the five loops can be collapsed into one.
*/
var elementInjectors = ProtoView._createElementInjectors(elements, binders);
var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors);
var textNodes = ProtoView._textNodes(elements, binders);
var bindElements = ProtoView._bindElements(elements, binders);
ProtoView._instantiateDirectives(elementInjectors, appInjector);
return new View(fragment, elementInjectors, rootElementInjectors, textNodes,
bindElements, this._protoWatchGroup, context);
}
static _createElementInjectors(elements, binders) {
var injectors = ListWrapper.createFixedSize(binders.length);
for (var i = 0; i < binders.length; ++i) {
injectors[i] = ProtoView._createElementInjector(
elements[i], binders[i].protoElementInjector);
}
// Cannot be rolled into loop above, because parentInjector pointers need
// to be set on the children.
for (var i = 0; i < binders.length; ++i) {
binders[i].protoElementInjector.clearElementInjector();
}
return injectors;
}
static _instantiateDirectives(
injectors:List<ElementInjectors>, appInjector:Injector) {
for (var i = 0; i < injectors.length; ++i) {
if (injectors[i] != null) injectors[i].instantiateDirectives(appInjector);
}
}
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, binders) {
var textNodes = [];
for (var i = 0; i < binders.length; ++i) {
ProtoView._collectTextNodes(textNodes, elements[i],
binders[i].textNodeIndices);
}
return textNodes;
}
static _bindElements(elements, binders):List<Element> {
var bindElements = [];
for (var i = 0; i < binders.length; ++i) {
if (binders[i].hasElementPropertyBindings) ListWrapper.push(
bindElements, elements[i]);
}
return bindElements;
}
static _collectTextNodes(allTextNodes, element, indices) {
var childNodes = DOM.childNodes(element);
for (var i = 0; i < indices.length; ++i) {
ListWrapper.push(allTextNodes, childNodes[indices[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, bindElements:List<Element>) {
var element:Element = bindElements[this._elementIndex];
2014-10-10 20:44:55 -07:00
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')
2014-09-28 16:29:11 -07:00
@FIELD('final _setter:SetterFn')
constructor(
elementInjectorIndex:number,
directiveIndex:number,
setterName:string,
setter:SetterFn) {
2014-09-28 16:29:11 -07:00
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 = elementInjector.getAtIndex(this._directiveIndex);
2014-09-28 16:29:11 -07:00
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() {
}
}