feat(EventManager): implement the EventManager

This commit is contained in:
Victor Berchet 2015-02-09 15:11:31 +01:00
parent 91fd5a69bf
commit 8844671c8d
26 changed files with 495 additions and 69 deletions

View File

@ -15,6 +15,8 @@ import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {XHR} from 'angular2/src/core/compiler/xhr/xhr'; import {XHR} from 'angular2/src/core/compiler/xhr/xhr';
import {XHRImpl} from 'angular2/src/core/compiler/xhr/xhr_impl'; import {XHRImpl} from 'angular2/src/core/compiler/xhr/xhr_impl';
import {EventManager} from 'angular2/src/core/events/event_manager';
import {HammerGesturesPlugin} from 'angular2/src/core/events/hammer_gestures';
var _rootInjector: Injector; var _rootInjector: Injector;
@ -58,7 +60,7 @@ function _injectorBindings(appComponentType) {
}, [appComponentAnnotatedTypeToken, appDocumentToken]), }, [appComponentAnnotatedTypeToken, appDocumentToken]),
bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement, bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement,
appComponentAnnotatedType, strategy) => { appComponentAnnotatedType, strategy, eventManager) => {
return compiler.compile(appComponentAnnotatedType.type, null).then( return compiler.compile(appComponentAnnotatedType.type, null).then(
(protoView) => { (protoView) => {
var appProtoView = ProtoView.createRootProtoView(protoView, appElement, var appProtoView = ProtoView.createRootProtoView(protoView, appElement,
@ -67,18 +69,22 @@ function _injectorBindings(appComponentType) {
// The light Dom of the app element is not considered part of // The light Dom of the app element is not considered part of
// the angular application. Thus the context and lightDomInjector are // the angular application. Thus the context and lightDomInjector are
// empty. // empty.
var view = appProtoView.instantiate(null); var view = appProtoView.instantiate(null, eventManager);
view.hydrate(injector, null, new Object()); view.hydrate(injector, null, new Object());
return view; return view;
}); });
}, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken, }, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken,
ShadowDomStrategy]), ShadowDomStrategy, EventManager]),
bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector, bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector,
[appViewToken]), [appViewToken]),
bind(appComponentType).toFactory((rootView) => rootView.elementInjectors[0].getComponent(), bind(appComponentType).toFactory((rootView) => rootView.elementInjectors[0].getComponent(),
[appViewToken]), [appViewToken]),
bind(LifeCycle).toFactory(() => new LifeCycle(null, assertionsEnabled()),[]) bind(LifeCycle).toFactory(() => new LifeCycle(null, assertionsEnabled()),[]),
bind(EventManager).toFactory((zone) => {
var plugins = [new HammerGesturesPlugin()];
return new EventManager(plugins, zone);
}, [VmTurnZone]),
]; ];
} }
@ -106,7 +112,7 @@ export function bootstrap(appComponentType: Type, bindings=null, givenBootstrapE
// TODO(rado): prepopulate template cache, so applications with only // TODO(rado): prepopulate template cache, so applications with only
// index.html and main.js are possible. // index.html and main.js are possible.
var appInjector = _createAppInjector(appComponentType, bindings); var appInjector = _createAppInjector(appComponentType, bindings, zone);
PromiseWrapper.then(appInjector.asyncGet(appViewToken), PromiseWrapper.then(appInjector.asyncGet(appViewToken),
(rootView) => { (rootView) => {
@ -126,10 +132,11 @@ export function bootstrap(appComponentType: Type, bindings=null, givenBootstrapE
return bootstrapProcess.promise; return bootstrapProcess.promise;
} }
function _createAppInjector(appComponentType: Type, bindings: List): Injector { function _createAppInjector(appComponentType: Type, bindings: List, zone: VmTurnZone): Injector {
if (isBlank(_rootInjector)) _rootInjector = new Injector(_rootBindings); if (isBlank(_rootInjector)) _rootInjector = new Injector(_rootBindings);
var mergedBindings = isPresent(bindings) ? var mergedBindings = isPresent(bindings) ?
ListWrapper.concat(_injectorBindings(appComponentType), bindings) : ListWrapper.concat(_injectorBindings(appComponentType), bindings) :
_injectorBindings(appComponentType); _injectorBindings(appComponentType);
ListWrapper.push(mergedBindings, bind(VmTurnZone).toValue(zone));
return _rootInjector.createChild(mergedBindings); return _rootInjector.createChild(mergedBindings);
} }

View File

@ -497,8 +497,7 @@ export class ElementInjector extends TreeNode {
if (isPresent(this._eventCallbacks)) { if (isPresent(this._eventCallbacks)) {
var callback = MapWrapper.get(this._eventCallbacks, dep.eventEmitterName); var callback = MapWrapper.get(this._eventCallbacks, dep.eventEmitterName);
if (isPresent(callback)) { if (isPresent(callback)) {
var locals = MapWrapper.create(); return ProtoView.buildInnerCallback(callback, view);
return ProtoView.buildInnerCallback(callback, view, locals);
} }
} }
return (_) => {}; return (_) => {};

View File

@ -16,6 +16,7 @@ import {Content} from './shadow_dom_emulation/content_tag';
import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom'; import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom';
import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ShadowDomStrategy} from './shadow_dom_strategy';
import {ViewPool} from './view_pool'; import {ViewPool} from './view_pool';
import {EventManager} from 'angular2/src/core/events/event_manager';
const NG_BINDING_CLASS = 'ng-binding'; const NG_BINDING_CLASS = 'ng-binding';
const NG_BINDING_CLASS_SELECTOR = '.ng-binding'; const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
@ -294,19 +295,19 @@ export class ProtoView {
} }
// TODO(rado): hostElementInjector should be moved to hydrate phase. // TODO(rado): hostElementInjector should be moved to hydrate phase.
instantiate(hostElementInjector: ElementInjector):View { instantiate(hostElementInjector: ElementInjector, eventManager: EventManager):View {
if (this._viewPool.length() == 0) this._preFillPool(hostElementInjector); if (this._viewPool.length() == 0) this._preFillPool(hostElementInjector, eventManager);
var view = this._viewPool.pop(); var view = this._viewPool.pop();
return isPresent(view) ? view : this._instantiate(hostElementInjector); return isPresent(view) ? view : this._instantiate(hostElementInjector, eventManager);
} }
_preFillPool(hostElementInjector: ElementInjector) { _preFillPool(hostElementInjector: ElementInjector, eventManager: EventManager) {
for (var i = 0; i < VIEW_POOL_PREFILL; i++) { for (var i = 0; i < VIEW_POOL_PREFILL; i++) {
this._viewPool.push(this._instantiate(hostElementInjector)); this._viewPool.push(this._instantiate(hostElementInjector, eventManager));
} }
} }
_instantiate(hostElementInjector: ElementInjector): View { _instantiate(hostElementInjector: ElementInjector, eventManager: EventManager): View {
var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element); var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element);
var elementsWithBindingsDynamic; var elementsWithBindingsDynamic;
if (this.isTemplateElement) { if (this.isTemplateElement) {
@ -387,7 +388,7 @@ export class ProtoView {
var bindingPropagationConfig = null; var bindingPropagationConfig = null;
if (isPresent(binder.componentDirective)) { if (isPresent(binder.componentDirective)) {
var strategy = this.shadowDomStrategy; var strategy = this.shadowDomStrategy;
var childView = binder.nestedProtoView.instantiate(elementInjector); var childView = binder.nestedProtoView.instantiate(elementInjector, eventManager);
view.changeDetector.addChild(childView.changeDetector); view.changeDetector.addChild(childView.changeDetector);
lightDom = strategy.constructLightDom(view, childView, element); lightDom = strategy.constructLightDom(view, childView, element);
@ -402,7 +403,8 @@ export class ProtoView {
var viewPort = null; var viewPort = null;
if (isPresent(binder.templateDirective)) { if (isPresent(binder.templateDirective)) {
var destLightDom = this._directParentElementLightDom(protoElementInjector, preBuiltObjects); var destLightDom = this._directParentElementLightDom(protoElementInjector, preBuiltObjects);
viewPort = new ViewPort(view, element, binder.nestedProtoView, elementInjector, destLightDom); viewPort = new ViewPort(view, element, binder.nestedProtoView, elementInjector,
eventManager, destLightDom);
ListWrapper.push(viewPorts, viewPort); ListWrapper.push(viewPorts, viewPort);
} }
@ -416,7 +418,8 @@ export class ProtoView {
if (isPresent(binder.events)) { if (isPresent(binder.events)) {
MapWrapper.forEach(binder.events, (expr, eventName) => { MapWrapper.forEach(binder.events, (expr, eventName) => {
if (isBlank(elementInjector) || !elementInjector.hasEventEmitter(eventName)) { if (isBlank(elementInjector) || !elementInjector.hasEventEmitter(eventName)) {
ProtoView._addNativeEventListener(element, eventName, expr, view); var handler = ProtoView.buildInnerCallback(expr, view);
eventManager.addEventListener(element, eventName, handler);
} }
}); });
} }
@ -432,24 +435,15 @@ export class ProtoView {
this._viewPool.push(view); this._viewPool.push(view);
} }
static _addNativeEventListener(element: Element, eventName: string, expr: AST, view: View) { static buildInnerCallback(expr:AST, view:View) {
var locals = MapWrapper.create(); var locals = MapWrapper.create();
var innerCallback = ProtoView.buildInnerCallback(expr, view, locals);
DOM.on(element, eventName, (event) => {
if (event.target === element) {
innerCallback(event);
}
});
}
static buildInnerCallback(expr:AST, view:View, locals: Map) {
return (event) => { return (event) => {
// Most of the time the event will be fired only when the view is // Most of the time the event will be fired only when the view is
// in the live document. However, in a rare circumstance the // in the live document. However, in a rare circumstance the
// view might get dehydrated, in between the event queuing up and // view might get dehydrated, in between the event queuing up and
// firing. // firing.
if (view.hydrated()) { if (view.hydrated()) {
MapWrapper.set(locals, `$event`, event); MapWrapper.set(locals, '$event', event);
var context = new ContextWithVariableBindings(view.context, locals); var context = new ContextWithVariableBindings(view.context, locals);
expr.eval(context); expr.eval(context);
} }

View File

@ -5,6 +5,7 @@ import {BaseException} from 'angular2/src/facade/lang';
import {Injector} from 'angular2/di'; import {Injector} from 'angular2/di';
import {ElementInjector} from 'angular2/src/core/compiler/element_injector'; import {ElementInjector} from 'angular2/src/core/compiler/element_injector';
import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {EventManager} from 'angular2/src/core/events/event_manager';
export class ViewPort { export class ViewPort {
parentView: View; parentView: View;
@ -12,12 +13,13 @@ export class ViewPort {
defaultProtoView: ProtoView; defaultProtoView: ProtoView;
_views: List<View>; _views: List<View>;
_lightDom: any; _lightDom: any;
_eventManager: EventManager;
elementInjector: ElementInjector; elementInjector: ElementInjector;
appInjector: Injector; appInjector: Injector;
hostElementInjector: ElementInjector; hostElementInjector: ElementInjector;
constructor(parentView: View, templateElement: Element, defaultProtoView: ProtoView, constructor(parentView: View, templateElement: Element, defaultProtoView: ProtoView,
elementInjector: ElementInjector, lightDom = null) { elementInjector: ElementInjector, eventManager: EventManager, lightDom = null) {
this.parentView = parentView; this.parentView = parentView;
this.templateElement = templateElement; this.templateElement = templateElement;
this.defaultProtoView = defaultProtoView; this.defaultProtoView = defaultProtoView;
@ -28,6 +30,7 @@ export class ViewPort {
this._views = []; this._views = [];
this.appInjector = null; this.appInjector = null;
this.hostElementInjector = null; this.hostElementInjector = null;
this._eventManager = eventManager;
} }
hydrate(appInjector: Injector, hostElementInjector: ElementInjector) { hydrate(appInjector: Injector, hostElementInjector: ElementInjector) {
@ -70,7 +73,7 @@ export class ViewPort {
if (!this.hydrated()) throw new BaseException( if (!this.hydrated()) throw new BaseException(
'Cannot create views on a dehydrated view port'); 'Cannot create views on a dehydrated view port');
// TODO(rado): replace with viewFactory. // TODO(rado): replace with viewFactory.
var newView = this.defaultProtoView.instantiate(this.hostElementInjector); var newView = this.defaultProtoView.instantiate(this.hostElementInjector, this._eventManager);
newView.hydrate(this.appInjector, this.hostElementInjector, this.parentView.context); newView.hydrate(this.appInjector, this.hostElementInjector, this.parentView.context);
return this.insert(newView, atIndex); return this.insert(newView, atIndex);
} }

View File

@ -0,0 +1,66 @@
import {isBlank, BaseException, isPresent} from 'angular2/src/facade/lang';
import {DOM, Element} from 'angular2/src/facade/dom';
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
export class EventManager {
_plugins: List<EventManagerPlugin>;
_zone: VmTurnZone;
constructor(plugins: List<EventManagerPlugin>, zone: VmTurnZone) {
this._zone = zone;
this._plugins = plugins;
for (var i = 0; i < plugins.length; i++) {
plugins[i].manager = this;
}
}
addEventListener(element: Element, eventName: string, handler: Function) {
var plugin = this._findPluginFor(eventName);
if (isPresent(plugin)) {
plugin.addEventListener(element, eventName, handler);
} else {
this._addNativeEventListener(element, eventName, handler);
}
}
getZone(): VmTurnZone {
return this._zone;
}
_findPluginFor(eventName: string): EventManagerPlugin {
var plugins = this._plugins;
for (var i = 0; i < plugins.length; i++) {
var plugin = plugins[i];
if (plugin.supports(eventName)) {
return plugin;
}
}
return null;
}
_addNativeEventListener(element: Element, eventName: string, handler: Function) {
this._zone.runOutsideAngular(() => {
DOM.on(element, eventName, (event) => {
if (event.target === element) {
this._zone.run(function() {
handler(event);
});
}
});
});
}
}
export class EventManagerPlugin {
manager: EventManager;
supports(eventName: string): boolean {
return false;
}
addEventListener(element: Element, eventName: string, handler: Function) {
throw "not implemented";
}
}

View File

@ -0,0 +1,52 @@
import {EventManagerPlugin} from './event_manager';
import {StringMapWrapper} from 'angular2/src/facade/collection';
var _eventNames = {
// pan
'pan': true,
'panstart': true,
'panmove': true,
'panend': true,
'pancancel': true,
'panleft': true,
'panright': true,
'panup': true,
'pandown': true,
// pinch
'pinch': true,
'pinchstart': true,
'pinchmove': true,
'pinchend': true,
'pinchcancel': true,
'pinchin': true,
'pinchout': true,
// press
'press': true,
'pressup': true,
// rotate
'rotate': true,
'rotatestart': true,
'rotatemove': true,
'rotateend': true,
'rotatecancel': true,
// swipe
'swipe': true,
'swipeleft': true,
'swiperight': true,
'swipeup': true,
'swipedown': true,
// tap
'tap': true,
};
export class HammerGesturesPluginCommon extends EventManagerPlugin {
constructor() {
super();
}
supports(eventName: string): boolean {
eventName = eventName.toLowerCase();
return StringMapWrapper.contains(_eventNames, eventName);
}
}

View File

@ -0,0 +1,86 @@
library angular.events;
import 'dart:html';
import './hammer_common.dart';
import '../../facade/dom.dart' show Element;
import '../../facade/lang.dart' show BaseException;
import 'dart:js' as js;
class HammerGesturesPlugin extends HammerGesturesPluginCommon {
bool supports(String eventName) {
if (!super.supports(eventName)) return false;
if (!js.context.hasProperty('Hammer')) {
throw new BaseException('Hammer.js is not loaded, can not bind ${eventName} event');
}
return true;
}
addEventListener(Element element, String eventName, Function handler) {
var zone = this.manager.getZone();
eventName = eventName.toLowerCase();
zone.runOutsideAngular(() {
// Creating the manager bind events, must be done outside of angular
var mc = new js.JsObject(js.context['Hammer'], [element]);
var jsObj = mc.callMethod('get', ['pinch']);
jsObj.callMethod('set', [new js.JsObject.jsify({'enable': true})]);
jsObj = mc.callMethod('get', ['rotate']);
jsObj.callMethod('set', [new js.JsObject.jsify({'enable': true})]);
mc.callMethod('on', [
eventName,
(eventObj) {
zone.run(() {
var dartEvent = new HammerEvent._fromJsEvent(eventObj);
handler(dartEvent);
});
}
]);
});
}
}
class HammerEvent {
num angle;
num centerX;
num centerY;
int deltaTime;
int deltaX;
int deltaY;
int direction;
int distance;
num rotation;
num scale;
Node target;
int timeStamp;
String type;
num velocity;
num velocityX;
num velocityY;
js.JsObject jsEvent;
HammerEvent._fromJsEvent(js.JsObject event) {
angle = event['angle'];
var center = event['center'];
centerX = center['x'];
centerY = center['y'];
deltaTime = event['deltaTime'];
deltaX = event['deltaX'];
deltaY = event['deltaY'];
direction = event['direction'];
distance = event['distance'];
rotation = event['rotation'];
scale = event['scale'];
target = event['target'];
timeStamp = event['timeStamp'];
type = event['type'];
velocity = event['velocity'];
velocityX = event['velocityX'];
velocityY = event['velocityY'];
jsEvent = event;
}
}

View File

@ -0,0 +1,37 @@
import {HammerGesturesPluginCommon} from './hammer_common';
import {Element} from 'angular2/src/facade/dom';
import {isPresent, BaseException} from 'angular2/src/facade/lang';
export class HammerGesturesPlugin extends HammerGesturesPluginCommon {
constructor() {
super();
}
supports(eventName:string):boolean {
if (!super.supports(eventName)) return false;
if (!isPresent(window.Hammer)) {
throw new BaseException(`Hammer.js is not loaded, can not bind ${eventName} event`);
}
return true;
}
addEventListener(element:Element, eventName:string, handler:Function) {
var zone = this.manager.getZone();
eventName = eventName.toLowerCase();
zone.runOutsideAngular(function () {
// Creating the manager bind events, must be done outside of angular
var mc = new Hammer(element);
mc.get('pinch').set({enable: true});
mc.get('rotate').set({enable: true});
mc.on(eventName, function (eventObj) {
zone.run(function () {
handler(eventObj);
});
});
});
}
}

View File

@ -60,6 +60,7 @@ class MapWrapper {
// TODO: how to export StringMap=Map as a type? // TODO: how to export StringMap=Map as a type?
class StringMapWrapper { class StringMapWrapper {
static HashMap create() => new HashMap(); static HashMap create() => new HashMap();
static bool contains(Map map, key) => map.containsKey(key);
static get(Map map, key) => map[key]; static get(Map map, key) => map[key];
static void set(Map map, key, value) { static void set(Map map, key, value) {
map[key] = value; map[key] = value;

View File

@ -40,6 +40,9 @@ export class StringMapWrapper {
// http://jsperf.com/ng2-object-create-null // http://jsperf.com/ng2-object-create-null
return { }; return { };
} }
static contains(map, key) {
return map.hasOwnProperty(key);
}
static get(map, key) { static get(map, key) {
return map.hasOwnProperty(key) ? map[key] : undefined; return map.hasOwnProperty(key) ? map[key] : undefined;
} }

View File

@ -131,7 +131,7 @@ export function main() {
}); });
}); });
it("should make the provided binings available to the application component", (done) => { it("should make the provided bindings available to the application component", (done) => {
var injectorPromise = bootstrap(HelloRootCmp3, [ var injectorPromise = bootstrap(HelloRootCmp3, [
testBindings, testBindings,
bind("appBinding").toValue("BoundValue") bind("appBinding").toValue("BoundValue")

View File

@ -410,7 +410,7 @@ export function main() {
}); });
it('should return viewPort', function () { it('should return viewPort', function () {
var viewPort = new ViewPort(null, null, null, null); var viewPort = new ViewPort(null, null, null, null, null);
var inj = injector([], null, null, new PreBuiltObjects(null, null, viewPort, null, null)); var inj = injector([], null, null, new PreBuiltObjects(null, null, viewPort, null, null));
expect(inj.get(ViewPort)).toEqual(viewPort); expect(inj.get(ViewPort)).toEqual(viewPort);

View File

@ -37,7 +37,7 @@ export function main() {
var view, ctx, cd; var view, ctx, cd;
function createView(pv) { function createView(pv) {
ctx = new MyComp(); ctx = new MyComp();
view = pv.instantiate(null); view = pv.instantiate(null, null);
view.hydrate(new Injector([]), null, ctx); view.hydrate(new Injector([]), null, ctx);
cd = view.changeDetector; cd = view.changeDetector;
} }

View File

@ -83,7 +83,7 @@ export function main() {
function instantiateView(protoView) { function instantiateView(protoView) {
evalContext = new Context(); evalContext = new Context();
view = protoView.instantiate(null); view = protoView.instantiate(null, null);
view.hydrate(new Injector([]), null, evalContext); view.hydrate(new Injector([]), null, evalContext);
changeDetector = view.changeDetector; changeDetector = view.changeDetector;
} }

View File

@ -345,7 +345,7 @@ class MyComp {
} }
function createView(pv) { function createView(pv) {
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(new Injector([]), null, {}); view.hydrate(new Injector([]), null, {});
return view; return view;
} }

View File

@ -15,7 +15,8 @@ import {Injector} from 'angular2/di';
import {View} from 'angular2/src/core/compiler/view'; import {View} from 'angular2/src/core/compiler/view';
import {ViewPort} from 'angular2/src/core/compiler/viewport'; import {ViewPort} from 'angular2/src/core/compiler/viewport';
import {reflector} from 'angular2/src/reflection/reflection'; import {reflector} from 'angular2/src/reflection/reflection';
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
import {EventManager} from 'angular2/src/core/events/event_manager';
@proxy @proxy
@IMPLEMENTS(ViewPort) @IMPLEMENTS(ViewPort)
@ -43,9 +44,9 @@ export function main() {
describe('view', function() { describe('view', function() {
var parser, someComponentDirective, someTemplateDirective; var parser, someComponentDirective, someTemplateDirective;
function createView(protoView) { function createView(protoView, eventManager: EventManager = null) {
var ctx = new MyEvaluationContext(); var ctx = new MyEvaluationContext();
var view = protoView.instantiate(null); var view = protoView.instantiate(null, eventManager);
view.hydrate(null, null, ctx); view.hydrate(null, null, ctx);
return view; return view;
} }
@ -60,7 +61,7 @@ export function main() {
var view; var view;
beforeEach(() => { beforeEach(() => {
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(), null); var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(), null);
view = pv.instantiate(null); view = pv.instantiate(null, null);
}); });
it('should be dehydrated by default', () => { it('should be dehydrated by default', () => {
@ -81,7 +82,7 @@ export function main() {
var fakeView = new FakeView(); var fakeView = new FakeView();
pv.returnToPool(fakeView); pv.returnToPool(fakeView);
expect(pv.instantiate(null)).toBe(fakeView); expect(pv.instantiate(null, null)).toBe(fakeView);
}); });
}); });
@ -115,7 +116,7 @@ export function main() {
}); });
}); });
describe('instatiated and hydrated', function() { describe('instantiated and hydrated', function() {
function createCollectDomNodesTestCases(useTemplateElement:boolean) { function createCollectDomNodesTestCases(useTemplateElement:boolean) {
@ -126,7 +127,7 @@ export function main() {
it('should collect the root node in the ProtoView element', () => { it('should collect the root node in the ProtoView element', () => {
var pv = new ProtoView(templateAwareCreateElement('<div id="1"></div>'), var pv = new ProtoView(templateAwareCreateElement('<div id="1"></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(), null);
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.nodes.length).toBe(1); expect(view.nodes.length).toBe(1);
expect(view.nodes[0].getAttribute('id')).toEqual('1'); expect(view.nodes[0].getAttribute('id')).toEqual('1');
@ -140,7 +141,7 @@ export function main() {
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop')); pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop'));
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.bindElements.length).toEqual(1); expect(view.bindElements.length).toEqual(1);
expect(view.bindElements[0]).toBe(view.nodes[0]); expect(view.bindElements[0]).toBe(view.nodes[0]);
@ -152,7 +153,7 @@ export function main() {
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a')); pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a'));
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.bindElements.length).toEqual(1); expect(view.bindElements.length).toEqual(1);
expect(view.bindElements[0]).toBe(view.nodes[0].childNodes[1]); expect(view.bindElements[0]).toBe(view.nodes[0].childNodes[1]);
@ -169,7 +170,7 @@ export function main() {
pv.bindTextNode(0, parser.parseBinding('a', null)); pv.bindTextNode(0, parser.parseBinding('a', null));
pv.bindTextNode(2, parser.parseBinding('b', null)); pv.bindTextNode(2, parser.parseBinding('b', null));
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.textNodes.length).toEqual(2); expect(view.textNodes.length).toEqual(2);
expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[0]); expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[0]);
@ -182,7 +183,7 @@ export function main() {
pv.bindElement(null); pv.bindElement(null);
pv.bindTextNode(0, parser.parseBinding('b', null)); pv.bindTextNode(0, parser.parseBinding('b', null));
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.textNodes.length).toEqual(1); expect(view.textNodes.length).toEqual(1);
expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[1].childNodes[0]); expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[1].childNodes[0]);
@ -197,7 +198,7 @@ export function main() {
var pv = new ProtoView(template, new DynamicProtoChangeDetector(), var pv = new ProtoView(template, new DynamicProtoChangeDetector(),
new NativeShadowDomStrategy()); new NativeShadowDomStrategy());
pv.instantiateInPlace = true; pv.instantiateInPlace = true;
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.nodes[0]).toBe(template); expect(view.nodes[0]).toBe(template);
}); });
@ -206,7 +207,7 @@ export function main() {
var template = el('<div></div>') var template = el('<div></div>')
var view = new ProtoView(template, new DynamicProtoChangeDetector(), var view = new ProtoView(template, new DynamicProtoChangeDetector(),
new NativeShadowDomStrategy()) new NativeShadowDomStrategy())
.instantiate(null); .instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.nodes[0]).not.toBe(template); expect(view.nodes[0]).not.toBe(template);
}); });
@ -226,7 +227,7 @@ export function main() {
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(), null);
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.elementInjectors.length).toBe(1); expect(view.elementInjectors.length).toBe(1);
expect(view.elementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true); expect(view.elementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true);
@ -239,7 +240,7 @@ export function main() {
pv.bindElement(protoParent); pv.bindElement(protoParent);
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective])); pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.elementInjectors.length).toBe(2); expect(view.elementInjectors.length).toBe(2);
expect(view.elementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true); expect(view.elementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true);
@ -257,7 +258,7 @@ export function main() {
var hostProtoInjector = new ProtoElementInjector(null, 0, []); var hostProtoInjector = new ProtoElementInjector(null, 0, []);
var hostInjector = hostProtoInjector.instantiate(null, null, null); var hostInjector = hostProtoInjector.instantiate(null, null, null);
var view; var view;
expect(() => view = pv.instantiate(hostInjector)).not.toThrow(); expect(() => view = pv.instantiate(hostInjector, null)).not.toThrow();
expect(testProtoElementInjector.parentElementInjector).toBe(view.elementInjectors[0]); expect(testProtoElementInjector.parentElementInjector).toBe(view.elementInjectors[0]);
expect(testProtoElementInjector.hostElementInjector).toBeNull(); expect(testProtoElementInjector.hostElementInjector).toBeNull();
}); });
@ -271,7 +272,7 @@ export function main() {
var hostProtoInjector = new ProtoElementInjector(null, 0, []); var hostProtoInjector = new ProtoElementInjector(null, 0, []);
var hostInjector = hostProtoInjector.instantiate(null, null, null); var hostInjector = hostProtoInjector.instantiate(null, null, null);
expect(() => pv.instantiate(hostInjector)).not.toThrow(); expect(() => pv.instantiate(hostInjector, null)).not.toThrow();
expect(testProtoElementInjector.parentElementInjector).toBeNull(); expect(testProtoElementInjector.parentElementInjector).toBeNull();
expect(testProtoElementInjector.hostElementInjector).toBe(hostInjector); expect(testProtoElementInjector.hostElementInjector).toBe(hostInjector);
}); });
@ -286,7 +287,7 @@ export function main() {
pv.bindElement(protoParent); pv.bindElement(protoParent);
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective])); pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.rootElementInjectors.length).toBe(1); expect(view.rootElementInjectors.length).toBe(1);
expect(view.rootElementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true); expect(view.rootElementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true);
@ -298,7 +299,7 @@ export function main() {
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective])); pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective]));
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.rootElementInjectors.length).toBe(2) expect(view.rootElementInjectors.length).toBe(2)
expect(view.rootElementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true); expect(view.rootElementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true);
@ -321,7 +322,7 @@ export function main() {
function createNestedView(protoView) { function createNestedView(protoView) {
ctx = new MyEvaluationContext(); ctx = new MyEvaluationContext();
var view = protoView.instantiate(null); var view = protoView.instantiate(null, null);
view.hydrate(new Injector([]), null, ctx); view.hydrate(new Injector([]), null, ctx);
return view; return view;
} }
@ -439,7 +440,7 @@ export function main() {
var view, ctx, called, receivedEvent, dispatchedEvent; var view, ctx, called, receivedEvent, dispatchedEvent;
function createViewAndContext(protoView) { function createViewAndContext(protoView) {
view = createView(protoView); view = createView(protoView, new EventManager([], new FakeVmTurnZone()));
ctx = view.context; ctx = view.context;
called = 0; called = 0;
receivedEvent = null; receivedEvent = null;
@ -458,7 +459,7 @@ export function main() {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'), var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(), null);
pv.bindElement(new TestProtoElementInjector(null, 0, [])); pv.bindElement(new TestProtoElementInjector(null, 0, []));
pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null)); pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
return pv; return pv;
} }
@ -493,7 +494,7 @@ export function main() {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'), var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(), null);
pv.bindElement(new TestProtoElementInjector(null, 0, [EventEmitterDirective])); pv.bindElement(new TestProtoElementInjector(null, 0, [EventEmitterDirective]));
pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null)); pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
createViewAndContext(pv); createViewAndContext(pv);
var dir = view.elementInjectors[0].get(EventEmitterDirective); var dir = view.elementInjectors[0].get(EventEmitterDirective);
@ -611,7 +612,7 @@ export function main() {
it('should create the root component when instantiated', () => { it('should create the root component when instantiated', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, element, var rootProtoView = ProtoView.createRootProtoView(pv, element,
someComponentDirective, new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); someComponentDirective, new DynamicProtoChangeDetector(), new NativeShadowDomStrategy());
var view = rootProtoView.instantiate(null); var view = rootProtoView.instantiate(null, null);
view.hydrate(new Injector([]), null, null); view.hydrate(new Injector([]), null, null);
expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null); expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null);
}); });
@ -619,7 +620,7 @@ export function main() {
it('should inject the protoView into the shadowDom', () => { it('should inject the protoView into the shadowDom', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, element, var rootProtoView = ProtoView.createRootProtoView(pv, element,
someComponentDirective, new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); someComponentDirective, new DynamicProtoChangeDetector(), new NativeShadowDomStrategy());
var view = rootProtoView.instantiate(null); var view = rootProtoView.instantiate(null, null);
view.hydrate(new Injector([]), null, null); view.hydrate(new Injector([]), null, null);
expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi'); expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi');
}); });
@ -723,3 +724,17 @@ class TestProtoElementInjector extends ProtoElementInjector {
return super.instantiate(parent, host, events); return super.instantiate(parent, host, events);
} }
} }
class FakeVmTurnZone extends VmTurnZone {
constructor() {
super({enableLongStackTrace: false});
}
run(fn) {
fn();
}
runOutsideAngular(fn) {
fn();
}
}

View File

@ -71,7 +71,7 @@ export function main() {
parentView = createView([dom.childNodes[0]]); parentView = createView([dom.childNodes[0]]);
protoView = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); protoView = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector(), new NativeShadowDomStrategy());
elementInjector = new ElementInjector(null, null, null, null); elementInjector = new ElementInjector(null, null, null, null);
viewPort = new ViewPort(parentView, insertionElement, protoView, elementInjector); viewPort = new ViewPort(parentView, insertionElement, protoView, elementInjector, null);
customViewWithOneNode = createView([el('<div>single</div>')]); customViewWithOneNode = createView([el('<div>single</div>')]);
customViewWithTwoNodes = createView([el('<div>one</div>'), el('<div>two</div>')]); customViewWithTwoNodes = createView([el('<div>one</div>'), el('<div>two</div>')]);
}); });
@ -216,7 +216,7 @@ export function main() {
new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); new DynamicProtoChangeDetector(), new NativeShadowDomStrategy());
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindTextNode(0, parser.parseBinding('foo', null)); pv.bindTextNode(0, parser.parseBinding('foo', null));
fancyView = pv.instantiate(null); fancyView = pv.instantiate(null, null);
}); });
it('hydrating should update rootElementInjectors and parent change detector', () => { it('hydrating should update rootElementInjectors and parent change detector', () => {

View File

@ -0,0 +1,78 @@
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, el} from 'angular2/test_lib';
import {EventManager, EventManagerPlugin} from 'angular2/src/core/events/event_manager';
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
import {DOM, Element} from 'angular2/src/facade/dom';
export function main() {
describe('EventManager', () => {
it('should delegate event bindings to plugins', () => {
var element = el('<div></div>');
var handler = (e) => e;
var plugin = new FakeEventManagerPlugin(['click']);
var manager = new EventManager([plugin], new FakeVmTurnZone());
manager.addEventListener(element, 'click', handler);
expect(MapWrapper.get(plugin._eventHandlers, 'click')).toBe(handler);
});
it('should delegate event bindings to the first plugin supporting the event', () => {
var element = el('<div></div>');
var clickHandler = (e) => e;
var dblClickHandler = (e) => e;
var plugin1= new FakeEventManagerPlugin(['dblclick']);
var plugin2 = new FakeEventManagerPlugin(['click', 'dblclick']);
var manager = new EventManager([plugin1, plugin2], new FakeVmTurnZone());
manager.addEventListener(element, 'click', clickHandler);
manager.addEventListener(element, 'dblclick', dblClickHandler);
expect(MapWrapper.contains(plugin1._eventHandlers, 'click')).toBe(false);
expect(MapWrapper.get(plugin2._eventHandlers, 'click')).toBe(clickHandler);
expect(MapWrapper.contains(plugin2._eventHandlers, 'dblclick')).toBe(false);
expect(MapWrapper.get(plugin1._eventHandlers, 'dblclick')).toBe(dblClickHandler);
});
it('should fall back to native events when no plugin can handle the event', () => {
var element = el('<div></div>');
var dispatchedEvent = DOM.createMouseEvent('click');
var receivedEvent = null;
var handler = (e) => { receivedEvent = e; };
var plugin = new FakeEventManagerPlugin(['dblclick']);
var manager = new EventManager([plugin], new FakeVmTurnZone());
manager.addEventListener(element, 'click', handler);
DOM.dispatchEvent(element, dispatchedEvent);
expect(receivedEvent).toBe(dispatchedEvent);
});
});
}
class FakeEventManagerPlugin extends EventManagerPlugin {
_supports: List<string>;
_eventHandlers: Map;
constructor(supports: List<string>) {
super();
this._supports = supports;
this._eventHandlers = MapWrapper.create();
}
supports(eventName: string): boolean {
return ListWrapper.contains(this._supports, eventName);
}
addEventListener(element: Element, eventName: string, handler: Function) {
MapWrapper.set(this._eventHandlers, eventName, handler);
}
}
class FakeVmTurnZone extends VmTurnZone {
constructor() {
super({enableLongStackTrace: false});
}
run(fn) {
fn();
}
runOutsideAngular(fn) {
fn();
}
}

View File

@ -26,7 +26,7 @@ export function main() {
function createView(pv) { function createView(pv) {
component = new TestComponent(); component = new TestComponent();
view = pv.instantiate(null); view = pv.instantiate(null, null);
view.hydrate(new Injector([]), null, component); view.hydrate(new Injector([]), null, component);
cd = view.changeDetector; cd = view.changeDetector;
} }

View File

@ -24,7 +24,7 @@ export function main() {
function createView(pv) { function createView(pv) {
component = new TestComponent(); component = new TestComponent();
view = pv.instantiate(null); view = pv.instantiate(null, null);
view.hydrate(new Injector([]), null, component); view.hydrate(new Injector([]), null, component);
cd = view.changeDetector; cd = view.changeDetector;
} }

View File

@ -20,7 +20,7 @@ export function main() {
function createView(pv) { function createView(pv) {
component = new TestComponent(); component = new TestComponent();
view = pv.instantiate(null); view = pv.instantiate(null, null);
view.hydrate(new Injector([]), null, component); view.hydrate(new Injector([]), null, component);
cd = view.changeDetector; cd = view.changeDetector;
} }

View File

@ -19,7 +19,7 @@ export function main() {
function createView(pv) { function createView(pv) {
component = new TestComponent(); component = new TestComponent();
view = pv.instantiate(null); view = pv.instantiate(null, null);
view.hydrate(new Injector([]), null, component); view.hydrate(new Injector([]), null, component);
cd = view.changeDetector; cd = view.changeDetector;
} }

View File

@ -30,7 +30,7 @@ export function main() {
new NativeShadowDomStrategy()); new NativeShadowDomStrategy());
compiler.compile(componentType, el(template)).then((pv) => { compiler.compile(componentType, el(template)).then((pv) => {
var view = pv.instantiate(null); var view = pv.instantiate(null, null);
view.hydrate(new Injector([]), null, context); view.hydrate(new Injector([]), null, context);
detectChanges(view); detectChanges(view);
callback(view); callback(view);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,38 @@
import {bootstrap, Component, TemplateConfig} from 'angular2/core';
import {reflector} from 'angular2/src/reflection/reflection';
import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabilities';
@Component({
selector: 'gestures-app',
template: new TemplateConfig({
url: 'template.html'
})
})
class GesturesCmp {
swipeDirection: string;
pinchScale: number;
rotateAngle: number;
constructor() {
this.swipeDirection = '-';
this.pinchScale = 1;
this.rotateAngle = 0;
}
onSwipe(event) {
this.swipeDirection = event.deltaX > 0 ? 'right' : 'left';
}
onPinch(event) {
this.pinchScale = event.scale;
}
onRotate(event) {
this.rotateAngle = event.rotation;
}
}
export function main() {
reflector.reflectionCapabilities = new ReflectionCapabilities();
bootstrap(GesturesCmp);
}

View File

@ -0,0 +1,15 @@
<style type="text/css">
.row {
height: 33%;
text-align: center;
border-bottom: 1px solid black;
margin: 0;
padding: 0;
-webkit-user-select: none;
}
</style>
<div class="row" (swipe)="onSwipe($event)">Swipe (direction = {{swipeDirection}})</div>
<div class="row" (pinch)="onPinch($event)">pinch (scale = {{pinchScale}})</div>
<div class="row" (rotate)="onRotate($event)">Rotate (angle = {{rotateAngle}})</div>
<div class="row"></div>