diff --git a/bin/run-tests.js b/bin/run-tests.js index 7d46ca226ea..f3b937039d8 100755 --- a/bin/run-tests.js +++ b/bin/run-tests.js @@ -184,9 +184,15 @@ function generateEachPackageTests() { testFunctions.push(function() { return run('package=' + packageName); }); + if (packages[packageName].requiresJQuery === false) { + testFunctions.push(function() { + return run('package=' + packageName + '&jquery=none'); + }); + } testFunctions.push(function() { return run('package=' + packageName + '&enableoptionalfeatures=true'); }); + }); } diff --git a/lib/packages.js b/lib/packages.js index 5d87c84e3ca..825a494f3dd 100644 --- a/lib/packages.js +++ b/lib/packages.js @@ -1,14 +1,14 @@ module.exports = function() { var packages = { - 'container': { trees: null, requirements: ['ember-utils'], isTypeScript: true, vendorRequirements: ['@glimmer/di'] }, - 'ember-environment': { trees: null, requirements: [], skipTests: true }, - 'ember-utils': { trees: null, requirements: [] }, - 'ember-console': { trees: null, requirements: [], skipTests: true }, - 'ember-metal': { trees: null, requirements: ['ember-environment', 'ember-utils'], vendorRequirements: ['backburner'] }, - 'ember-debug': { trees: null, requirements: [] }, - 'ember-runtime': { trees: null, vendorRequirements: ['rsvp'], requirements: ['container', 'ember-environment', 'ember-console', 'ember-metal'] }, + 'container': { trees: null, requirements: ['ember-utils'], isTypeScript: true, vendorRequirements: ['@glimmer/di'], requiresJQuery: false }, + 'ember-environment': { trees: null, requirements: [], skipTests: true, requiresJQuery: false }, + 'ember-utils': { trees: null, requirements: [], requiresJQuery: false }, + 'ember-console': { trees: null, requirements: [], skipTests: true, requiresJQuery: false }, + 'ember-metal': { trees: null, requirements: ['ember-environment', 'ember-utils'], vendorRequirements: ['backburner'], requiresJQuery: false }, + 'ember-debug': { trees: null, requirements: [], requiresJQuery: false }, + 'ember-runtime': { trees: null, vendorRequirements: ['rsvp'], requirements: ['container', 'ember-environment', 'ember-console', 'ember-metal'], requiresJQuery: false }, 'ember-views': { trees: null, requirements: ['ember-runtime'], skipTests: true }, - 'ember-extension-support': { trees: null, requirements: ['ember-application'] }, + 'ember-extension-support': { trees: null, requirements: ['ember-application'], requiresJQuery: false }, 'ember-testing': { trees: null, requirements: ['ember-application', 'ember-routing'], testing: true }, 'ember-template-compiler': { trees: null, @@ -27,10 +27,10 @@ module.exports = function() { ] }, 'ember-routing': { trees: null, vendorRequirements: ['router', 'route-recognizer'], - requirements: ['ember-runtime', 'ember-views'] }, - 'ember-application': { trees: null, vendorRequirements: ['dag-map'], requirements: ['ember-routing'] }, + requirements: ['ember-runtime', 'ember-views'], requiresJQuery: false }, + 'ember-application': { trees: null, vendorRequirements: ['dag-map'], requirements: ['ember-routing'], requiresJQuery: false }, 'ember': { trees: null, requirements: ['ember-application'] }, - 'internal-test-helpers': { trees: null }, + 'internal-test-helpers': { trees: null, requiresJQuery: false }, 'ember-glimmer': { trees: null, @@ -43,7 +43,7 @@ module.exports = function() { '@glimmer/wire-format', '@glimmer/node' ], - testingVendorRequirements: [] + testingVendorRequirements: [], } }; diff --git a/packages/ember-application/tests/system/application_test.js b/packages/ember-application/tests/system/application_test.js index eebac7d26bd..8338ef92214 100644 --- a/packages/ember-application/tests/system/application_test.js +++ b/packages/ember-application/tests/system/application_test.js @@ -35,14 +35,13 @@ import { } from 'internal-test-helpers'; moduleFor('Ember.Application, autobooting multiple apps', class extends ApplicationTestCase { - constructor() { - jQuery('#qunit-fixture').html(` + get fixture() { + return `
HI
HI
- `); - super(); + `; } get applicationOptions() { @@ -212,7 +211,7 @@ moduleFor('Ember.Application, default resolver with autoboot', class extends Def } [`@test Minimal Application initialized with just an application template`]() { - jQuery('#qunit-fixture').html(''); + this.setupFixture(''); this.runTask(() => this.createApplication()); this.assertInnerHTML('Hello World'); } @@ -235,14 +234,14 @@ moduleFor('Ember.Application, autobooting', class extends AutobootApplicationTes super.teardown(); } - [`@test initialized application goes to initial route`](assert) { + [`@test initialized application goes to initial route`]() { this.runTask(() => { this.createApplication(); this.addTemplate('application', '{{outlet}}'); this.addTemplate('index', '

Hi from index

'); }); - assert.equal(this.$('h1').text(), 'Hi from index'); + this.assertText('Hi from index'); } [`@test ready hook is called before routing begins`](assert) { @@ -289,10 +288,10 @@ moduleFor('Ember.Application, autobooting', class extends AutobootApplicationTes // need to make some assertions about the created router let router = this.application.__deprecatedInstance__.lookup('router:main'); assert.equal(router instanceof Router, true, 'Router was set from initialize call'); - assert.equal(this.$('h1').text(), 'Hello!'); + this.assertText('Hello!'); } - [`@test Application Controller backs the appplication template`](assert) { + [`@test Application Controller backs the appplication template`]() { this.runTask(() => { this.createApplication(); this.addTemplate('application', '

{{greeting}}

'); @@ -300,7 +299,7 @@ moduleFor('Ember.Application, autobooting', class extends AutobootApplicationTes greeting: 'Hello!' })); }); - assert.equal(this.$('h1').text(), 'Hello!'); + this.assertText('Hello!'); } [`@test enable log of libraries with an ENV var`](assert) { @@ -320,8 +319,12 @@ moduleFor('Ember.Application, autobooting', class extends AutobootApplicationTes this.runTask(() => this.createApplication()); assert.equal(messages[1], 'Ember : ' + VERSION); - assert.equal(messages[2], 'jQuery : ' + jQuery().jquery); - assert.equal(messages[3], 'my-lib : ' + '2.0.0a'); + if (jQuery) { + assert.equal(messages[2], 'jQuery : ' + jQuery().jquery); + assert.equal(messages[3], 'my-lib : ' + '2.0.0a'); + } else { + assert.equal(messages[2], 'my-lib : ' + '2.0.0a'); + } libraries.deRegister('my-lib'); } diff --git a/packages/ember-application/tests/system/bootstrap-test.js b/packages/ember-application/tests/system/bootstrap-test.js index 3cd58e6418a..28a814d3353 100644 --- a/packages/ember-application/tests/system/bootstrap-test.js +++ b/packages/ember-application/tests/system/bootstrap-test.js @@ -1,19 +1,17 @@ import { assign } from 'ember-utils'; -import { jQuery } from 'ember-views'; import { moduleFor, DefaultResolverApplicationTestCase } from 'internal-test-helpers'; moduleFor('Ember.Application with default resolver and autoboot', class extends DefaultResolverApplicationTestCase { - constructor() { - jQuery('#qunit-fixture').html(` + get fixture() { + return `
- `); - super(); + `; } get applicationOptions() { @@ -25,6 +23,6 @@ moduleFor('Ember.Application with default resolver and autoboot', class extends ['@test templates in script tags are extracted at application creation'](assert) { this.runTask(() => this.createApplication()); - assert.equal(this.$('#app').text(), 'Hello World!'); + assert.equal(document.getElementById('app').textContent, 'Hello World!'); } }); diff --git a/packages/ember-application/tests/system/dependency_injection/custom_resolver_test.js b/packages/ember-application/tests/system/dependency_injection/custom_resolver_test.js index fa5a4733aad..0663de5f831 100644 --- a/packages/ember-application/tests/system/dependency_injection/custom_resolver_test.js +++ b/packages/ember-application/tests/system/dependency_injection/custom_resolver_test.js @@ -26,9 +26,8 @@ moduleFor('Ember.Application with extended default resolver and autoboot', class }); } - [`@test a resolver can be supplied to application`](assert) { + [`@test a resolver can be supplied to application`]() { this.runTask(() => this.createApplication()); - assert.equal(this.$('h1').text(), 'Fallback'); + this.assertText('Fallback'); } - }); diff --git a/packages/ember-application/tests/system/initializers_test.js b/packages/ember-application/tests/system/initializers_test.js index ab0d0e1659b..3acd2d1069c 100644 --- a/packages/ember-application/tests/system/initializers_test.js +++ b/packages/ember-application/tests/system/initializers_test.js @@ -1,15 +1,12 @@ import { assign } from 'ember-utils'; import { moduleFor, AutobootApplicationTestCase } from 'internal-test-helpers'; import { Application } from 'ember-application'; -import { jQuery } from 'ember-views'; moduleFor('Ember.Application initializers', class extends AutobootApplicationTestCase { - constructor() { - jQuery('#qunit-fixture').html(` -
ONE
+ get fixture() { + return `
ONE
TWO
- `); - super(); + `; } get applicationOptions() { diff --git a/packages/ember-application/tests/system/instance_initializers_test.js b/packages/ember-application/tests/system/instance_initializers_test.js index 71521493e81..434ebd38e44 100644 --- a/packages/ember-application/tests/system/instance_initializers_test.js +++ b/packages/ember-application/tests/system/instance_initializers_test.js @@ -1,15 +1,12 @@ import { assign } from 'ember-utils'; import { moduleFor, AutobootApplicationTestCase } from 'internal-test-helpers'; import { Application, ApplicationInstance } from 'ember-application'; -import { jQuery } from 'ember-views'; moduleFor('Ember.Application instance initializers', class extends AutobootApplicationTestCase { - constructor() { - jQuery('#qunit-fixture').html(` -
ONE
+ get fixture() { + return `
ONE
TWO
- `); - super(); + `; } get applicationOptions() { diff --git a/packages/ember-application/tests/system/visit_test.js b/packages/ember-application/tests/system/visit_test.js index a6dd203969f..ba99b9f2152 100644 --- a/packages/ember-application/tests/system/visit_test.js +++ b/packages/ember-application/tests/system/visit_test.js @@ -12,7 +12,6 @@ import Engine from '../../system/engine'; import { Route } from 'ember-routing'; import { Component, helper } from 'ember-glimmer'; import { compile } from 'ember-template-compiler'; -import { jQuery } from 'ember-views'; function expectAsyncError() { RSVP.off('error'); @@ -29,6 +28,13 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { return super.createApplication(options, Application.extend()); } + assertEmptyFixture(message) { + this.assert.strictEqual( + document.getElementById('qunit-fixture').children.length, 0, + `there are no elements in the fixture element ${message ? message : ''}` + ); + } + // This tests whether the application is "autobooted" by registering an // instance initializer and asserting it never gets run. Since this is // inherently testing that async behavior *doesn't* happen, we set a @@ -274,10 +280,7 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { [`@test visit() returns a promise that resolves when the view has rendered`](assert) { this.addTemplate('application', `

Hello world

`); - assert.strictEqual( - this.$().children().length, 0, - 'there are no elements in the fixture element' - ); + this.assertEmptyFixture(); return this.visit('/').then(instance => { assert.ok( @@ -285,7 +288,7 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { 'promise is resolved with an ApplicationInstance' ); assert.equal( - this.$('h1').text(), 'Hello world', + this.element.textContent, 'Hello world', 'the application was rendered once the promise resolves' ); }); @@ -296,20 +299,15 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { this.addTemplate('application', '

Hello world

'); - assert.strictEqual( - this.$().children().length, 0, - 'there are no elements in the fixture element' - ); + this.assertEmptyFixture(); return this.visit('/', { shouldRender: false }).then(instance => { assert.ok( instance instanceof ApplicationInstance, 'promise is resolved with an ApplicationInstance' ); - assert.strictEqual( - this.$().children().length, 0, - 'there are still no elements in the fixture element after visit' - ); + + this.assertEmptyFixture('after visit'); }); } @@ -318,10 +316,7 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { this.addTemplate('application', '

Hello world

'); - assert.strictEqual( - this.$('#qunit-fixture').children().length, 0, - 'there are no elements in the fixture element' - ); + this.assertEmptyFixture(); return this.visit('/', { shouldRender: true }).then(instance => { assert.ok( @@ -329,7 +324,7 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { 'promise is resolved with an ApplicationInstance' ); assert.strictEqual( - this.$().children().length, 1, + document.querySelector('#qunit-fixture').children.length, 1, 'there is 1 element in the fixture element after visit' ); }); @@ -352,20 +347,15 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { let BlogMap = function() {}; this.add('route-map:blog', BlogMap); - assert.strictEqual( - this.$('#qunit-fixture').children().length, 0, - 'there are no elements in the fixture element' - ); + this.assertEmptyFixture(); return this.visit('/blog', { shouldRender: false }).then(instance => { assert.ok( instance instanceof ApplicationInstance, 'promise is resolved with an ApplicationInstance' ); - assert.strictEqual( - this.$().children().length, 0, - 'there are still no elements in the fixture element after visit' - ); + + this.assertEmptyFixture('after visit'); }); } @@ -398,10 +388,7 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { let BlogMap = function() {}; this.add('route-map:blog', BlogMap); - assert.strictEqual( - this.$('#qunit-fixture').children().length, 0, - 'there are no elements in the fixture element' - ); + this.assertEmptyFixture(); return this.visit('/blog', { isInteractive: false }).then(instance => { assert.ok( @@ -409,7 +396,7 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { 'promise is resolved with an ApplicationInstance' ); assert.strictEqual( - this.$().find('p').text(), 'Dis cache money', + this.element.querySelector('p').textContent, 'Dis cache money', 'Engine component is resolved' ); }); @@ -439,14 +426,11 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { let BlogMap = function() {}; this.add('route-map:blog', BlogMap); - assert.strictEqual( - this.$().children().length, 0, - 'there are no elements in the fixture element' - ); + this.assertEmptyFixture(); return this.visit('/blog', { shouldRender: true }).then(() => { assert.strictEqual( - this.$().find('p').text(), 'Dis cache money', + this.element.querySelector('p').textContent, 'Dis cache money', 'Engine component is resolved' ); }); @@ -475,14 +459,11 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { let BlogMap = function() {}; this.add('route-map:blog', BlogMap); - assert.strictEqual( - this.$().children().length, 0, - 'there are no elements in the fixture element' - ); + this.assertEmptyFixture(); return this.visit('/blog', { shouldRender: true }).then(() => { assert.strictEqual( - this.$().text(), 'turnt up', + this.element.textContent, 'turnt up', 'Engine component is resolved' ); }); @@ -576,18 +557,21 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { } })); - let $foo = jQuery('
').appendTo('#qunit-fixture'); - let $bar = jQuery('
').appendTo('#qunit-fixture'); + let fixtureElement = document.querySelector('#qunit-fixture'); + let foo = document.createElement('div'); + let bar = document.createElement('div'); + fixtureElement.appendChild(foo); + fixtureElement.appendChild(bar); let data = encodeURIComponent(JSON.stringify({ name: 'Godfrey' })); let instances = []; return RSVP.all([ this.runTask(() => { - return this.application.visit(`/x-foo?data=${data}`, { rootElement: $foo[0] }); + return this.application.visit(`/x-foo?data=${data}`, { rootElement: foo }); }), this.runTask(() => { - return this.application.visit('/x-bar', { rootElement: $bar[0] }); + return this.application.visit('/x-bar', { rootElement: bar }); }) ]).then(_instances => { instances = _instances; @@ -598,28 +582,28 @@ moduleFor('Ember.Application - visit()', class extends ApplicationTestCase { assert.ok(xBarInitCalled); assert.ok(xBarDidInsertElementCalled); - assert.equal($foo.find('h1').text(), 'X-Foo'); - assert.equal($foo.find('p').text(), 'Hello Godfrey, I have been clicked 0 times (0 times combined)!'); - assert.ok($foo.text().indexOf('X-Bar') === -1); + assert.equal(foo.querySelector('h1').textContent, 'X-Foo'); + assert.equal(foo.querySelector('p').textContent, 'Hello Godfrey, I have been clicked 0 times (0 times combined)!'); + assert.ok(foo.textContent.indexOf('X-Bar') === -1); - assert.equal($bar.find('h1').text(), 'X-Bar'); - assert.equal($bar.find('button').text(), 'Join 0 others in clicking me!'); - assert.ok($bar.text().indexOf('X-Foo') === -1); + assert.equal(bar.querySelector('h1').textContent, 'X-Bar'); + assert.equal(bar.querySelector('button').textContent, 'Join 0 others in clicking me!'); + assert.ok(bar.textContent.indexOf('X-Foo') === -1); this.runTask(() => { - $foo.find('x-foo').click(); + this.click(foo.querySelector('x-foo')); }); - assert.equal($foo.find('p').text(), 'Hello Godfrey, I have been clicked 1 times (1 times combined)!'); - assert.equal($bar.find('button').text(), 'Join 1 others in clicking me!'); + assert.equal(foo.querySelector('p').textContent, 'Hello Godfrey, I have been clicked 1 times (1 times combined)!'); + assert.equal(bar.querySelector('button').textContent, 'Join 1 others in clicking me!'); this.runTask(() => { - $bar.find('button').click(); - $bar.find('button').click(); + this.click(bar.querySelector('button')); + this.click(bar.querySelector('button')); }); - assert.equal($foo.find('p').text(), 'Hello Godfrey, I have been clicked 1 times (3 times combined)!'); - assert.equal($bar.find('button').text(), 'Join 3 others in clicking me!'); + assert.equal(foo.querySelector('p').textContent, 'Hello Godfrey, I have been clicked 1 times (3 times combined)!'); + assert.equal(bar.querySelector('button').textContent, 'Join 3 others in clicking me!'); }).finally(() => { this.runTask(() => { diff --git a/packages/ember-glimmer/tests/integration/application/engine-test.js b/packages/ember-glimmer/tests/integration/application/engine-test.js index aef0ac2f0cb..138ed5403c3 100644 --- a/packages/ember-glimmer/tests/integration/application/engine-test.js +++ b/packages/ember-glimmer/tests/integration/application/engine-test.js @@ -311,7 +311,10 @@ moduleFor('Application test: engine rendering', class extends ApplicationTest { this.setupAppAndRoutableEngine(hooks); return this.visit('/blog', { shouldRender: false }).then(() => { - this.assertText(''); + assert.strictEqual( + document.getElementById('qunit-fixture').children.length, 0, + `there are no elements in the qunit-fixture element` + ); this.assert.deepEqual(hooks, [ 'application - application', diff --git a/packages/ember-template-compiler/tests/system/bootstrap-test.js b/packages/ember-template-compiler/tests/system/bootstrap-test.js index cdd3b880fe6..4d85435abe5 100644 --- a/packages/ember-template-compiler/tests/system/bootstrap-test.js +++ b/packages/ember-template-compiler/tests/system/bootstrap-test.js @@ -16,8 +16,6 @@ import { AbstractTestCase } from 'internal-test-helpers'; -const { trim } = jQuery; - let component, fixture; function checkTemplate(templateName, assert) { @@ -84,7 +82,7 @@ moduleFor('ember-templates: bootstrap', class extends AbstractTestCase { assert.ok(template, 'template with name funkyTemplate available'); // This won't even work with Ember templates - assert.equal(trim(template({ name: 'Tobias' })), 'Tobias'); + assert.equal(template({ name: 'Tobias' }).trim(), 'Tobias'); } ['@test duplicated default application templates should throw exception'](assert) { diff --git a/packages/ember-views/lib/system/event_dispatcher.js b/packages/ember-views/lib/system/event_dispatcher.js index 70d7eebf0fc..165e067ea2a 100644 --- a/packages/ember-views/lib/system/event_dispatcher.js +++ b/packages/ember-views/lib/system/event_dispatcher.js @@ -11,6 +11,7 @@ import jQuery from './jquery'; import ActionManager from './action_manager'; import fallbackViewRegistry from '../compat/fallback-view-registry'; +const HAS_JQUERY = jQuery !== undefined; const ROOT_ELEMENT_CLASS = 'ember-application'; const ROOT_ELEMENT_SELECTOR = `.${ROOT_ELEMENT_CLASS}`; @@ -150,6 +151,8 @@ export default EmberObject.extend({ until: '2.17.0' } ); + + this._eventHandlers = Object.create(null); }, /** @@ -164,26 +167,51 @@ export default EmberObject.extend({ @method setup @param addedEvents {Object} */ - setup(addedEvents, rootElement) { - let event; + setup(addedEvents, _rootElement) { + let event, rootElement; let events = this._finalEvents = assign({}, get(this, 'events'), addedEvents); - if (isNone(rootElement)) { - rootElement = get(this, 'rootElement'); - } else { - set(this, 'rootElement', rootElement); + if (!isNone(_rootElement)) { + set(this, 'rootElement', _rootElement); } - rootElement = jQuery(rootElement); + let rootElementSelector = get(this, 'rootElement'); + if (HAS_JQUERY) { + rootElement = jQuery(rootElementSelector); + assert(`You cannot use the same root element (${rootElement.selector || rootElement[0].tagName}) multiple times in an Ember.Application`, !rootElement.is(ROOT_ELEMENT_SELECTOR)); + assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', !rootElement.closest(ROOT_ELEMENT_SELECTOR).length); + assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.find(ROOT_ELEMENT_SELECTOR).length); + + rootElement.addClass(ROOT_ELEMENT_CLASS); - assert(`You cannot use the same root element (${rootElement.selector || rootElement[0].tagName}) multiple times in an Ember.Application`, !rootElement.is(ROOT_ELEMENT_SELECTOR)); - assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', !rootElement.closest(ROOT_ELEMENT_SELECTOR).length); - assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.find(ROOT_ELEMENT_SELECTOR).length); + if (!rootElement.is(ROOT_ELEMENT_SELECTOR)) { + throw new TypeError(`Unable to add '${ROOT_ELEMENT_CLASS}' class to root element (${rootElement.selector || rootElement[0].tagName}). Make sure you set rootElement to the body or an element in the body.`); + } + } else { + if (typeof rootElementSelector !== 'string') { + rootElement = rootElementSelector; + } else { + rootElement = document.querySelector(rootElementSelector); + } - rootElement.addClass(ROOT_ELEMENT_CLASS); + assert(`You cannot use the same root element (${get(this, 'rootElement') || rootElement.tagName}) multiple times in an Ember.Application`, !rootElement.classList.contains(ROOT_ELEMENT_CLASS)); + assert('You cannot make a new Ember.Application using a root element that is a descendent of an existing Ember.Application', (() => { + let target = rootElement.parentNode; + do { + if (target.classList.contains(ROOT_ELEMENT_CLASS)) { + return false; + } - if (!rootElement.is(ROOT_ELEMENT_SELECTOR)) { - throw new TypeError(`Unable to add '${ROOT_ELEMENT_CLASS}' class to root element (${rootElement.selector || rootElement[0].tagName}). Make sure you set rootElement to the body or an element in the body.`); + target = target.parentNode; + } while(target && target.nodeType === 1); + + return true; + })()); + assert('You cannot make a new Ember.Application using a root element that is an ancestor of an existing Ember.Application', !rootElement.querySelector(ROOT_ELEMENT_SELECTOR)); + + rootElement.classList.add(ROOT_ELEMENT_CLASS); + + assert(`Unable to add '${ROOT_ELEMENT_CLASS}' class to root element (${get(this, 'rootElement') || rootElement.tagName}). Make sure you set rootElement to the body or an element in the body.`, rootElement.classList.contains(ROOT_ELEMENT_CLASS)); } let viewRegistry = this._getViewRegistry(); @@ -217,45 +245,119 @@ export default EmberObject.extend({ return; } - rootElement.on(`${event}.ember`, '.ember-view', function(evt, triggeringManager) { - let view = viewRegistry[this.id]; - let result = true; + if (HAS_JQUERY) { + rootElement.on(`${event}.ember`, '.ember-view', function(evt, triggeringManager) { + let view = viewRegistry[this.id]; + let result = true; - let manager = self.canDispatchToEventManager ? self._findNearestEventManager(view, eventName) : null; + let manager = self.canDispatchToEventManager ? self._findNearestEventManager(view, eventName) : null; - if (manager && manager !== triggeringManager) { - result = self._dispatchEvent(manager, evt, eventName, view); - } else if (view) { - result = self._bubbleEvent(view, evt, eventName); - } + if (manager && manager !== triggeringManager) { + result = self._dispatchEvent(manager, evt, eventName, view); + } else if (view) { + result = self._bubbleEvent(view, evt, eventName); + } - return result; - }); + return result; + }); + + rootElement.on(`${event}.ember`, '[data-ember-action]', evt => { + let attributes = evt.currentTarget.attributes; + let handledActions = []; + + for (let i = 0; i < attributes.length; i++) { + let attr = attributes.item(i); + let attrName = attr.name; + + if (attrName.lastIndexOf('data-ember-action-', 0) !== -1) { + let action = ActionManager.registeredActions[attr.value]; + + // We have to check for action here since in some cases, jQuery will trigger + // an event on `removeChild` (i.e. focusout) after we've already torn down the + // action handlers for the view. + if (action && action.eventName === eventName && handledActions.indexOf(action) === -1) { + action.handler(evt); + // Action handlers can mutate state which in turn creates new attributes on the element. + // This effect could cause the `data-ember-action` attribute to shift down and be invoked twice. + // To avoid this, we keep track of which actions have been handled. + handledActions.push(action); + } + } + } + }); + } else { + let viewHandler = (target, event) => { + let view = viewRegistry[target.id]; + let result = true; + + if (view) { + result = this._bubbleEvent(view, event, eventName); + } + + return result; + }; + + let actionHandler = (target, event) => { + let actionId = target.getAttribute('data-ember-action'); + let actions = ActionManager.registeredActions[actionId]; + + // In Glimmer2 this attribute is set to an empty string and an additional + // attribute it set for each action on a given element. In this case, the + // attributes need to be read so that a proper set of action handlers can + // be coalesced. + if (actionId === '') { + let attributes = target.attributes; + let attributeCount = attributes.length; + + actions = []; + + for (let i = 0; i < attributeCount; i++) { + let attr = attributes.item(i); + let attrName = attr.name; - rootElement.on(`${event}.ember`, '[data-ember-action]', evt => { - let attributes = evt.currentTarget.attributes; - let handledActions = []; - - for (let i = 0; i < attributes.length; i++) { - let attr = attributes.item(i); - let attrName = attr.name; - - if (attrName.lastIndexOf('data-ember-action-', 0) !== -1) { - let action = ActionManager.registeredActions[attr.value]; - - // We have to check for action here since in some cases, jQuery will trigger - // an event on `removeChild` (i.e. focusout) after we've already torn down the - // action handlers for the view. - if (action && action.eventName === eventName && handledActions.indexOf(action) === -1) { - action.handler(evt); - // Action handlers can mutate state which in turn creates new attributes on the element. - // This effect could cause the `data-ember-action` attribute to shift down and be invoked twice. - // To avoid this, we keep track of which actions have been handled. - handledActions.push(action); + if (attrName.indexOf('data-ember-action-') === 0) { + actions = actions.concat(ActionManager.registeredActions[attr.value]); + } } } - } - }); + + // We have to check for actions here since in some cases, jQuery will trigger + // an event on `removeChild` (i.e. focusout) after we've already torn down the + // action handlers for the view. + if (!actions) { + return; + } + + for (let index = 0; index < actions.length; index++) { + let action = actions[index]; + + if (action && action.eventName === eventName) { + return action.handler(event); + } + } + }; + + let handleEvent = this._eventHandlers[event] = (event) => { + let target = event.target; + + do { + if (viewRegistry[target.id]) { + if (viewHandler(target, event) === false) { + event.preventDefault(); + event.stopPropagation(); + break; + } + } else if (target.hasAttribute('data-ember-action')) { + actionHandler(target, event); + break; + } + + target = target.parentNode; + } while(target && target.nodeType === 1); + }; + + rootElement.addEventListener(event, handleEvent); + } }, _getViewRegistry() { @@ -298,8 +400,26 @@ export default EmberObject.extend({ }, destroy() { - let rootElement = get(this, 'rootElement'); - jQuery(rootElement).off('.ember', '**').removeClass(ROOT_ELEMENT_CLASS); + let rootElementSelector = get(this, 'rootElement'); + let rootElement; + if (rootElementSelector.nodeType) { + rootElement = rootElementSelector; + } else { + rootElement = document.querySelector(rootElementSelector); + } + + if (!rootElement) { return; } + + if (HAS_JQUERY) { + jQuery(rootElementSelector).off('.ember', '**'); + } else { + for (let event in this._eventHandlers) { + rootElement.removeEventListener(event, this._eventHandlers[event]); + } + } + + rootElement.classList.remove(ROOT_ELEMENT_CLASS); + return this._super(...arguments); }, diff --git a/packages/ember/tests/helpers/link_to_test.js b/packages/ember/tests/helpers/link_to_test.js index f33f2cc122c..a29ffa57e6c 100644 --- a/packages/ember/tests/helpers/link_to_test.js +++ b/packages/ember/tests/helpers/link_to_test.js @@ -654,7 +654,7 @@ moduleFor('The {{link-to}} helper - nested routes and link-to arguments', class