diff --git a/modules/core/src/compiler/view.js b/modules/core/src/compiler/view.js index 564ae93526..fe11cf9723 100644 --- a/modules/core/src/compiler/view.js +++ b/modules/core/src/compiler/view.js @@ -373,6 +373,22 @@ export class ProtoView { if (isPresent(elementInjector)) { preBuiltObjects[i] = new PreBuiltObjects(view, new NgElement(element), viewPort, lightDom); } + + // events + if (isPresent(binder.events)) { + // TODO(rado): if there is directive at this element that injected an + // event emitter for that eventType do not attach the handler. + MapWrapper.forEach(binder.events, (expr, eventName) => { + DOM.on(element, eventName, (event) => { + if (event.target === element) { + // TODO(rado): replace with + // expr.eval(new ContextWithVariableBindings(view.context, {'$event': event})); + // when eval with variable bindinds works. + expr.eval(view.context); + } + }); + }); + } } view.init(elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings, diff --git a/modules/core/test/compiler/view_spec.js b/modules/core/test/compiler/view_spec.js index d27e3727aa..bea7b82d54 100644 --- a/modules/core/test/compiler/view_spec.js +++ b/modules/core/test/compiler/view_spec.js @@ -422,6 +422,37 @@ export function main() { }); }); + describe('event handlers', () => { + var view, ctx, called; + + function createViewAndContext(protoView) { + view = createView(protoView); + ctx = view.context; + called = 0; + ctx.callMe = () => called += 1; + } + + function dispatchClick(el) { + DOM.dispatchEvent(el, DOM.createMouseEvent('click')); + } + + it('should fire on non-bubbling native events', () => { + var pv = new ProtoView(createElement('
'), + new ProtoRecordRange()); + pv.bindElement(null); + pv.bindEvent('click', parser.parseBinding('callMe()', null)); + createViewAndContext(pv); + + dispatchClick(view.nodes[0]); + dispatchClick(view.nodes[0].firstChild); + + // the bubbled event does not execute the expression. + // It is trivially passing on webkit browsers due to + // https://bugs.webkit.org/show_bug.cgi?id=122755 + expect(called).toEqual(1); + }); + }); + describe('react to record changes', () => { var view, cd, ctx; @@ -602,6 +633,7 @@ class MyEvaluationContext { foo:string; a; b; + callMe; constructor() { this.foo = 'bar'; }; diff --git a/modules/examples/e2e_test/hello_world/hello_world_spec.es6 b/modules/examples/e2e_test/hello_world/hello_world_spec.es6 index 8a58a38075..5612e55a5c 100644 --- a/modules/examples/e2e_test/hello_world/hello_world_spec.es6 +++ b/modules/examples/e2e_test/hello_world/hello_world_spec.es6 @@ -9,7 +9,7 @@ describe('hello world', function () { it('should greet', function() { browser.get(URL); - expect(getShadowText('hello-app')).toBe('hello world!'); + expect(getGreetingText('hello-app')).toBe('hello world!'); }); }); @@ -19,12 +19,12 @@ describe('hello world', function () { it('should greet', function() { browser.get(URL); - expect(getShadowText('hello-app')).toBe('hello world!'); + expect(getGreetingText('hello-app')).toBe('hello world!'); }); }); }); -function getShadowText(selector) { - return browser.executeScript('return document.querySelector("'+selector+'").shadowRoot.textContent'); -} \ No newline at end of file +function getGreetingText(selector) { + return browser.executeScript('return document.querySelector("'+selector+'").shadowRoot.firstChild.textContent'); +} diff --git a/modules/examples/src/hello_world/index_common.js b/modules/examples/src/hello_world/index_common.js index 2e3ee8402a..b2950e4d51 100644 --- a/modules/examples/src/hello_world/index_common.js +++ b/modules/examples/src/hello_world/index_common.js @@ -20,7 +20,8 @@ import {bootstrap, Component, Decorator, TemplateConfig, NgElement} from 'core/c // The template for the component. // Expressions in the template (like {{greeting}}) are evaluated in the // context of the HelloCmp class below. - inline: `{{greeting}} world!`, + inline: `
{{greeting}} world!
+ `, // All directives used in the template need to be specified. This allows for // modularity (RedDec can only be used in this template) // and better tooling (the template can be invalidated if the attribute is @@ -33,6 +34,9 @@ class HelloCmp { constructor(service: GreetingService) { this.greeting = service.greeting; } + changeGreeting() { + this.greeting = 'howdy'; + } } // Decorators are light-weight. They don't allow for templates, or new diff --git a/modules/facade/src/dom.dart b/modules/facade/src/dom.dart index 1b247a2016..afcf329a58 100644 --- a/modules/facade/src/dom.dart +++ b/modules/facade/src/dom.dart @@ -33,7 +33,15 @@ class DOM { return el.querySelectorAll(selector); } static on(element, event, callback) { - element.addEventListener(event, callback); + // due to https://code.google.com/p/dart/issues/detail?id=17406 + // addEventListener misses zones so we use element.on. + element.on[event].listen(callback); + } + static dispatchEvent(el, evt) { + el.dispatchEvent(evt); + } + static createMouseEvent(eventType) { + return new MouseEvent(eventType, canBubble: true); } static getInnerHTML(el) { return el.innerHtml; diff --git a/modules/facade/src/dom.es6 b/modules/facade/src/dom.es6 index aa06c390ef..8f1148acdf 100644 --- a/modules/facade/src/dom.es6 +++ b/modules/facade/src/dom.es6 @@ -24,6 +24,14 @@ export class DOM { static on(el, evt, listener) { el.addEventListener(evt, listener, false); } + static dispatchEvent(el, evt) { + el.dispatchEvent(evt); + } + static createMouseEvent(eventType) { + var evt = new MouseEvent(eventType); + evt.initEvent(eventType, true, true); + return evt; + } static getInnerHTML(el) { return el.innerHTML; }