From 8844671c8d6a3d6ed093f9a9741989a8e3e63f51 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 9 Feb 2015 15:11:31 +0100 Subject: [PATCH] feat(EventManager): implement the EventManager --- modules/angular2/src/core/application.js | 21 +++-- .../src/core/compiler/element_injector.js | 3 +- modules/angular2/src/core/compiler/view.js | 34 +++----- .../angular2/src/core/compiler/viewport.js | 7 +- .../angular2/src/core/events/event_manager.js | 66 ++++++++++++++ .../angular2/src/core/events/hammer_common.js | 52 +++++++++++ .../src/core/events/hammer_gestures.dart | 86 +++++++++++++++++++ .../src/core/events/hammer_gestures.es6 | 37 ++++++++ modules/angular2/src/facade/collection.dart | 1 + modules/angular2/src/facade/collection.es6 | 3 + .../angular2/test/core/application_spec.js | 2 +- .../core/compiler/element_injector_spec.js | 2 +- .../test/core/compiler/integration_spec.js | 4 +- .../pipeline/element_binder_builder_spec.js | 2 +- .../shadow_dom_emulation_integration_spec.js | 2 +- .../angular2/test/core/compiler/view_spec.js | 65 ++++++++------ .../test/core/compiler/viewport_spec.js | 4 +- .../test/core/events/event_manager_spec.js | 78 +++++++++++++++++ .../angular2/test/directives/foreach_spec.js | 2 +- modules/angular2/test/directives/if_spec.js | 2 +- .../test/directives/non_bindable_spec.js | 2 +- .../angular2/test/directives/switch_spec.js | 2 +- .../angular2/test/forms/integration_spec.js | 2 +- modules/examples/src/gestures/index.html | 32 +++++++ modules/examples/src/gestures/index.js | 38 ++++++++ modules/examples/src/gestures/template.html | 15 ++++ 26 files changed, 495 insertions(+), 69 deletions(-) create mode 100644 modules/angular2/src/core/events/event_manager.js create mode 100644 modules/angular2/src/core/events/hammer_common.js create mode 100644 modules/angular2/src/core/events/hammer_gestures.dart create mode 100644 modules/angular2/src/core/events/hammer_gestures.es6 create mode 100644 modules/angular2/test/core/events/event_manager_spec.js create mode 100644 modules/examples/src/gestures/index.html create mode 100644 modules/examples/src/gestures/index.js create mode 100644 modules/examples/src/gestures/template.html diff --git a/modules/angular2/src/core/application.js b/modules/angular2/src/core/application.js index 4dd015248b..58e9201501 100644 --- a/modules/angular2/src/core/application.js +++ b/modules/angular2/src/core/application.js @@ -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 {XHR} from 'angular2/src/core/compiler/xhr/xhr'; 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; @@ -58,7 +60,7 @@ function _injectorBindings(appComponentType) { }, [appComponentAnnotatedTypeToken, appDocumentToken]), bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement, - appComponentAnnotatedType, strategy) => { + appComponentAnnotatedType, strategy, eventManager) => { return compiler.compile(appComponentAnnotatedType.type, null).then( (protoView) => { 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 angular application. Thus the context and lightDomInjector are // empty. - var view = appProtoView.instantiate(null); + var view = appProtoView.instantiate(null, eventManager); view.hydrate(injector, null, new Object()); return view; }); }, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken, - ShadowDomStrategy]), + ShadowDomStrategy, EventManager]), bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector, [appViewToken]), bind(appComponentType).toFactory((rootView) => rootView.elementInjectors[0].getComponent(), [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,12 +112,12 @@ export function bootstrap(appComponentType: Type, bindings=null, givenBootstrapE // TODO(rado): prepopulate template cache, so applications with only // index.html and main.js are possible. - var appInjector = _createAppInjector(appComponentType, bindings); + var appInjector = _createAppInjector(appComponentType, bindings, zone); PromiseWrapper.then(appInjector.asyncGet(appViewToken), (rootView) => { // retrieve life cycle: may have already been created if injected in root component - var lc=appInjector.get(LifeCycle); + var lc=appInjector.get(LifeCycle); lc.registerWith(zone, rootView.changeDetector); lc.tick(); //the first tick that will bootstrap the app @@ -126,10 +132,11 @@ export function bootstrap(appComponentType: Type, bindings=null, givenBootstrapE 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); var mergedBindings = isPresent(bindings) ? ListWrapper.concat(_injectorBindings(appComponentType), bindings) : _injectorBindings(appComponentType); + ListWrapper.push(mergedBindings, bind(VmTurnZone).toValue(zone)); return _rootInjector.createChild(mergedBindings); } diff --git a/modules/angular2/src/core/compiler/element_injector.js b/modules/angular2/src/core/compiler/element_injector.js index d502c8e83e..b03af51a2d 100644 --- a/modules/angular2/src/core/compiler/element_injector.js +++ b/modules/angular2/src/core/compiler/element_injector.js @@ -497,8 +497,7 @@ export class ElementInjector extends TreeNode { if (isPresent(this._eventCallbacks)) { var callback = MapWrapper.get(this._eventCallbacks, dep.eventEmitterName); if (isPresent(callback)) { - var locals = MapWrapper.create(); - return ProtoView.buildInnerCallback(callback, view, locals); + return ProtoView.buildInnerCallback(callback, view); } } return (_) => {}; diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index b38587ab7e..f836be0292 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -16,6 +16,7 @@ import {Content} from './shadow_dom_emulation/content_tag'; import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom'; import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ViewPool} from './view_pool'; +import {EventManager} from 'angular2/src/core/events/event_manager'; const NG_BINDING_CLASS = '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. - instantiate(hostElementInjector: ElementInjector):View { - if (this._viewPool.length() == 0) this._preFillPool(hostElementInjector); + instantiate(hostElementInjector: ElementInjector, eventManager: EventManager):View { + if (this._viewPool.length() == 0) this._preFillPool(hostElementInjector, eventManager); 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++) { - 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 elementsWithBindingsDynamic; if (this.isTemplateElement) { @@ -387,7 +388,7 @@ export class ProtoView { var bindingPropagationConfig = null; if (isPresent(binder.componentDirective)) { var strategy = this.shadowDomStrategy; - var childView = binder.nestedProtoView.instantiate(elementInjector); + var childView = binder.nestedProtoView.instantiate(elementInjector, eventManager); view.changeDetector.addChild(childView.changeDetector); lightDom = strategy.constructLightDom(view, childView, element); @@ -402,7 +403,8 @@ export class ProtoView { var viewPort = null; if (isPresent(binder.templateDirective)) { 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); } @@ -416,7 +418,8 @@ export class ProtoView { if (isPresent(binder.events)) { MapWrapper.forEach(binder.events, (expr, 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); } - static _addNativeEventListener(element: Element, eventName: string, expr: AST, view: View) { + static buildInnerCallback(expr:AST, view:View) { 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) => { // Most of the time the event will be fired only when the view is // in the live document. However, in a rare circumstance the // view might get dehydrated, in between the event queuing up and // firing. if (view.hydrated()) { - MapWrapper.set(locals, `$event`, event); + MapWrapper.set(locals, '$event', event); var context = new ContextWithVariableBindings(view.context, locals); expr.eval(context); } diff --git a/modules/angular2/src/core/compiler/viewport.js b/modules/angular2/src/core/compiler/viewport.js index bc2cd31e0b..5067925e7d 100644 --- a/modules/angular2/src/core/compiler/viewport.js +++ b/modules/angular2/src/core/compiler/viewport.js @@ -5,6 +5,7 @@ import {BaseException} from 'angular2/src/facade/lang'; import {Injector} from 'angular2/di'; import {ElementInjector} from 'angular2/src/core/compiler/element_injector'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; +import {EventManager} from 'angular2/src/core/events/event_manager'; export class ViewPort { parentView: View; @@ -12,12 +13,13 @@ export class ViewPort { defaultProtoView: ProtoView; _views: List; _lightDom: any; + _eventManager: EventManager; elementInjector: ElementInjector; appInjector: Injector; hostElementInjector: ElementInjector; constructor(parentView: View, templateElement: Element, defaultProtoView: ProtoView, - elementInjector: ElementInjector, lightDom = null) { + elementInjector: ElementInjector, eventManager: EventManager, lightDom = null) { this.parentView = parentView; this.templateElement = templateElement; this.defaultProtoView = defaultProtoView; @@ -28,6 +30,7 @@ export class ViewPort { this._views = []; this.appInjector = null; this.hostElementInjector = null; + this._eventManager = eventManager; } hydrate(appInjector: Injector, hostElementInjector: ElementInjector) { @@ -70,7 +73,7 @@ export class ViewPort { if (!this.hydrated()) throw new BaseException( 'Cannot create views on a dehydrated view port'); // 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); return this.insert(newView, atIndex); } diff --git a/modules/angular2/src/core/events/event_manager.js b/modules/angular2/src/core/events/event_manager.js new file mode 100644 index 0000000000..ae6e2aa401 --- /dev/null +++ b/modules/angular2/src/core/events/event_manager.js @@ -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; + _zone: VmTurnZone; + + constructor(plugins: List, 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"; + } +} diff --git a/modules/angular2/src/core/events/hammer_common.js b/modules/angular2/src/core/events/hammer_common.js new file mode 100644 index 0000000000..13270f2fd1 --- /dev/null +++ b/modules/angular2/src/core/events/hammer_common.js @@ -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); + } +} diff --git a/modules/angular2/src/core/events/hammer_gestures.dart b/modules/angular2/src/core/events/hammer_gestures.dart new file mode 100644 index 0000000000..f5404f6080 --- /dev/null +++ b/modules/angular2/src/core/events/hammer_gestures.dart @@ -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; + } +} diff --git a/modules/angular2/src/core/events/hammer_gestures.es6 b/modules/angular2/src/core/events/hammer_gestures.es6 new file mode 100644 index 0000000000..c4d10397ed --- /dev/null +++ b/modules/angular2/src/core/events/hammer_gestures.es6 @@ -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); + }); + }); + }); + } +} diff --git a/modules/angular2/src/facade/collection.dart b/modules/angular2/src/facade/collection.dart index 6fd090ae7f..72a7202e13 100644 --- a/modules/angular2/src/facade/collection.dart +++ b/modules/angular2/src/facade/collection.dart @@ -60,6 +60,7 @@ class MapWrapper { // TODO: how to export StringMap=Map as a type? class StringMapWrapper { static HashMap create() => new HashMap(); + static bool contains(Map map, key) => map.containsKey(key); static get(Map map, key) => map[key]; static void set(Map map, key, value) { map[key] = value; diff --git a/modules/angular2/src/facade/collection.es6 b/modules/angular2/src/facade/collection.es6 index bd5572e996..d62dc87346 100644 --- a/modules/angular2/src/facade/collection.es6 +++ b/modules/angular2/src/facade/collection.es6 @@ -40,6 +40,9 @@ export class StringMapWrapper { // http://jsperf.com/ng2-object-create-null return { }; } + static contains(map, key) { + return map.hasOwnProperty(key); + } static get(map, key) { return map.hasOwnProperty(key) ? map[key] : undefined; } diff --git a/modules/angular2/test/core/application_spec.js b/modules/angular2/test/core/application_spec.js index a8da189bae..827c620331 100644 --- a/modules/angular2/test/core/application_spec.js +++ b/modules/angular2/test/core/application_spec.js @@ -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, [ testBindings, bind("appBinding").toValue("BoundValue") diff --git a/modules/angular2/test/core/compiler/element_injector_spec.js b/modules/angular2/test/core/compiler/element_injector_spec.js index 4b094d6a4f..8b39532e11 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.js +++ b/modules/angular2/test/core/compiler/element_injector_spec.js @@ -410,7 +410,7 @@ export function main() { }); 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)); expect(inj.get(ViewPort)).toEqual(viewPort); diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index 451b9ae98e..9be81b64e1 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -37,7 +37,7 @@ export function main() { var view, ctx, cd; function createView(pv) { ctx = new MyComp(); - view = pv.instantiate(null); + view = pv.instantiate(null, null); view.hydrate(new Injector([]), null, ctx); cd = view.changeDetector; } @@ -66,7 +66,7 @@ export function main() { }); it('should consume directive watch expression change.', (done) => { - var tpl = + var tpl = '
' + '
' + '
' + diff --git a/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js b/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js index 8687dce898..06d92c2d36 100644 --- a/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js +++ b/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js @@ -83,7 +83,7 @@ export function main() { function instantiateView(protoView) { evalContext = new Context(); - view = protoView.instantiate(null); + view = protoView.instantiate(null, null); view.hydrate(new Injector([]), null, evalContext); changeDetector = view.changeDetector; } diff --git a/modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js b/modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js index 427b68e79c..e7bb6aea87 100644 --- a/modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js +++ b/modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js @@ -345,7 +345,7 @@ class MyComp { } function createView(pv) { - var view = pv.instantiate(null); + var view = pv.instantiate(null, null); view.hydrate(new Injector([]), null, {}); return view; } diff --git a/modules/angular2/test/core/compiler/view_spec.js b/modules/angular2/test/core/compiler/view_spec.js index 7635b93b8d..5ffae5c9a4 100644 --- a/modules/angular2/test/core/compiler/view_spec.js +++ b/modules/angular2/test/core/compiler/view_spec.js @@ -15,7 +15,8 @@ import {Injector} from 'angular2/di'; import {View} from 'angular2/src/core/compiler/view'; import {ViewPort} from 'angular2/src/core/compiler/viewport'; 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 @IMPLEMENTS(ViewPort) @@ -43,9 +44,9 @@ export function main() { describe('view', function() { var parser, someComponentDirective, someTemplateDirective; - function createView(protoView) { + function createView(protoView, eventManager: EventManager = null) { var ctx = new MyEvaluationContext(); - var view = protoView.instantiate(null); + var view = protoView.instantiate(null, eventManager); view.hydrate(null, null, ctx); return view; } @@ -60,7 +61,7 @@ export function main() { var view; beforeEach(() => { var pv = new ProtoView(el('
'), new DynamicProtoChangeDetector(), null); - view = pv.instantiate(null); + view = pv.instantiate(null, null); }); it('should be dehydrated by default', () => { @@ -81,7 +82,7 @@ export function main() { var fakeView = new 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) { @@ -126,7 +127,7 @@ export function main() { it('should collect the root node in the ProtoView element', () => { var pv = new ProtoView(templateAwareCreateElement('
'), new DynamicProtoChangeDetector(), null); - var view = pv.instantiate(null); + var view = pv.instantiate(null, null); view.hydrate(null, null, null); expect(view.nodes.length).toBe(1); expect(view.nodes[0].getAttribute('id')).toEqual('1'); @@ -140,7 +141,7 @@ export function main() { pv.bindElement(null); 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); expect(view.bindElements.length).toEqual(1); expect(view.bindElements[0]).toBe(view.nodes[0]); @@ -152,7 +153,7 @@ export function main() { pv.bindElement(null); 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); expect(view.bindElements.length).toEqual(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(2, parser.parseBinding('b', null)); - var view = pv.instantiate(null); + var view = pv.instantiate(null, null); view.hydrate(null, null, null); expect(view.textNodes.length).toEqual(2); expect(view.textNodes[0]).toBe(view.nodes[0].childNodes[0]); @@ -182,7 +183,7 @@ export function main() { pv.bindElement(null); pv.bindTextNode(0, parser.parseBinding('b', null)); - var view = pv.instantiate(null); + var view = pv.instantiate(null, null); view.hydrate(null, null, null); expect(view.textNodes.length).toEqual(1); 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(), new NativeShadowDomStrategy()); pv.instantiateInPlace = true; - var view = pv.instantiate(null); + var view = pv.instantiate(null, null); view.hydrate(null, null, null); expect(view.nodes[0]).toBe(template); }); @@ -206,7 +207,7 @@ export function main() { var template = el('
') var view = new ProtoView(template, new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()) - .instantiate(null); + .instantiate(null, null); view.hydrate(null, null, null); expect(view.nodes[0]).not.toBe(template); }); @@ -226,7 +227,7 @@ export function main() { new DynamicProtoChangeDetector(), null); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); - var view = pv.instantiate(null); + var view = pv.instantiate(null, null); view.hydrate(null, null, null); expect(view.elementInjectors.length).toBe(1); expect(view.elementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true); @@ -239,7 +240,7 @@ export function main() { pv.bindElement(protoParent); pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective])); - var view = pv.instantiate(null); + var view = pv.instantiate(null, null); view.hydrate(null, null, null); expect(view.elementInjectors.length).toBe(2); expect(view.elementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true); @@ -257,7 +258,7 @@ export function main() { var hostProtoInjector = new ProtoElementInjector(null, 0, []); var hostInjector = hostProtoInjector.instantiate(null, null, null); 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.hostElementInjector).toBeNull(); }); @@ -271,7 +272,7 @@ export function main() { var hostProtoInjector = new ProtoElementInjector(null, 0, []); 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.hostElementInjector).toBe(hostInjector); }); @@ -286,7 +287,7 @@ export function main() { pv.bindElement(protoParent); pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective])); - var view = pv.instantiate(null); + var view = pv.instantiate(null, null); view.hydrate(null, null, null); expect(view.rootElementInjectors.length).toBe(1); 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, 2, [AnotherDirective])); - var view = pv.instantiate(null); + var view = pv.instantiate(null, null); view.hydrate(null, null, null); expect(view.rootElementInjectors.length).toBe(2) expect(view.rootElementInjectors[0].get(SomeDirective) instanceof SomeDirective).toBe(true); @@ -321,7 +322,7 @@ export function main() { function createNestedView(protoView) { ctx = new MyEvaluationContext(); - var view = protoView.instantiate(null); + var view = protoView.instantiate(null, null); view.hydrate(new Injector([]), null, ctx); return view; } @@ -439,7 +440,7 @@ export function main() { var view, ctx, called, receivedEvent, dispatchedEvent; function createViewAndContext(protoView) { - view = createView(protoView); + view = createView(protoView, new EventManager([], new FakeVmTurnZone())); ctx = view.context; called = 0; receivedEvent = null; @@ -458,7 +459,7 @@ export function main() { var pv = new ProtoView(el('
'), new DynamicProtoChangeDetector(), null); pv.bindElement(new TestProtoElementInjector(null, 0, [])); - pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null)); + pv.bindEvent('click', parser.parseBinding('callMe($event)', null)); return pv; } @@ -493,7 +494,7 @@ export function main() { var pv = new ProtoView(el('
'), new DynamicProtoChangeDetector(), null); 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); var dir = view.elementInjectors[0].get(EventEmitterDirective); @@ -611,7 +612,7 @@ export function main() { it('should create the root component when instantiated', () => { var rootProtoView = ProtoView.createRootProtoView(pv, element, someComponentDirective, new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); - var view = rootProtoView.instantiate(null); + var view = rootProtoView.instantiate(null, null); view.hydrate(new Injector([]), null, 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', () => { var rootProtoView = ProtoView.createRootProtoView(pv, element, someComponentDirective, new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); - var view = rootProtoView.instantiate(null); + var view = rootProtoView.instantiate(null, null); view.hydrate(new Injector([]), null, null); expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi'); }); @@ -723,3 +724,17 @@ class TestProtoElementInjector extends ProtoElementInjector { return super.instantiate(parent, host, events); } } + +class FakeVmTurnZone extends VmTurnZone { + constructor() { + super({enableLongStackTrace: false}); + } + + run(fn) { + fn(); + } + + runOutsideAngular(fn) { + fn(); + } +} diff --git a/modules/angular2/test/core/compiler/viewport_spec.js b/modules/angular2/test/core/compiler/viewport_spec.js index f8e792e997..6f8c448410 100644 --- a/modules/angular2/test/core/compiler/viewport_spec.js +++ b/modules/angular2/test/core/compiler/viewport_spec.js @@ -71,7 +71,7 @@ export function main() { parentView = createView([dom.childNodes[0]]); protoView = new ProtoView(el('
hi
'), new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); 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('
single
')]); customViewWithTwoNodes = createView([el('
one
'), el('
two
')]); }); @@ -216,7 +216,7 @@ export function main() { new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); 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', () => { diff --git a/modules/angular2/test/core/events/event_manager_spec.js b/modules/angular2/test/core/events/event_manager_spec.js new file mode 100644 index 0000000000..95b17fd024 --- /dev/null +++ b/modules/angular2/test/core/events/event_manager_spec.js @@ -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('
'); + 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('
'); + 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('
'); + 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; + _eventHandlers: Map; + constructor(supports: List) { + 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(); + } +} diff --git a/modules/angular2/test/directives/foreach_spec.js b/modules/angular2/test/directives/foreach_spec.js index bf0cbaee70..bccb69d44c 100644 --- a/modules/angular2/test/directives/foreach_spec.js +++ b/modules/angular2/test/directives/foreach_spec.js @@ -26,7 +26,7 @@ export function main() { function createView(pv) { component = new TestComponent(); - view = pv.instantiate(null); + view = pv.instantiate(null, null); view.hydrate(new Injector([]), null, component); cd = view.changeDetector; } diff --git a/modules/angular2/test/directives/if_spec.js b/modules/angular2/test/directives/if_spec.js index a04a76f179..2ebf3f3d1d 100644 --- a/modules/angular2/test/directives/if_spec.js +++ b/modules/angular2/test/directives/if_spec.js @@ -24,7 +24,7 @@ export function main() { function createView(pv) { component = new TestComponent(); - view = pv.instantiate(null); + view = pv.instantiate(null, null); view.hydrate(new Injector([]), null, component); cd = view.changeDetector; } diff --git a/modules/angular2/test/directives/non_bindable_spec.js b/modules/angular2/test/directives/non_bindable_spec.js index 36f416ec37..4c63974f0f 100644 --- a/modules/angular2/test/directives/non_bindable_spec.js +++ b/modules/angular2/test/directives/non_bindable_spec.js @@ -20,7 +20,7 @@ export function main() { function createView(pv) { component = new TestComponent(); - view = pv.instantiate(null); + view = pv.instantiate(null, null); view.hydrate(new Injector([]), null, component); cd = view.changeDetector; } diff --git a/modules/angular2/test/directives/switch_spec.js b/modules/angular2/test/directives/switch_spec.js index 56be4d2b38..897c136d7a 100644 --- a/modules/angular2/test/directives/switch_spec.js +++ b/modules/angular2/test/directives/switch_spec.js @@ -19,7 +19,7 @@ export function main() { function createView(pv) { component = new TestComponent(); - view = pv.instantiate(null); + view = pv.instantiate(null, null); view.hydrate(new Injector([]), null, component); cd = view.changeDetector; } diff --git a/modules/angular2/test/forms/integration_spec.js b/modules/angular2/test/forms/integration_spec.js index 013c32f945..864eb85e12 100644 --- a/modules/angular2/test/forms/integration_spec.js +++ b/modules/angular2/test/forms/integration_spec.js @@ -30,7 +30,7 @@ export function main() { new NativeShadowDomStrategy()); compiler.compile(componentType, el(template)).then((pv) => { - var view = pv.instantiate(null); + var view = pv.instantiate(null, null); view.hydrate(new Injector([]), null, context); detectChanges(view); callback(view); diff --git a/modules/examples/src/gestures/index.html b/modules/examples/src/gestures/index.html new file mode 100644 index 0000000000..2b9bcebff8 --- /dev/null +++ b/modules/examples/src/gestures/index.html @@ -0,0 +1,32 @@ + + + + + Hammer.JS + + + + + + + + + $SCRIPTS$ + + diff --git a/modules/examples/src/gestures/index.js b/modules/examples/src/gestures/index.js new file mode 100644 index 0000000000..6e82ec6e2f --- /dev/null +++ b/modules/examples/src/gestures/index.js @@ -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); +} diff --git a/modules/examples/src/gestures/template.html b/modules/examples/src/gestures/template.html new file mode 100644 index 0000000000..6c0ef24330 --- /dev/null +++ b/modules/examples/src/gestures/template.html @@ -0,0 +1,15 @@ + + +
Swipe (direction = {{swipeDirection}})
+
pinch (scale = {{pinchScale}})
+
Rotate (angle = {{rotateAngle}})
+