Skip to content

Commit

Permalink
Merge pull request #149 from a15n/a15n/lazy-render-target-fix
Browse files Browse the repository at this point in the history
add level to #some-component, proper $target for lazy-render-wrapper, tests
  • Loading branch information
Duncan Walker authored Dec 31, 2016
2 parents 9682929 + 56eedd6 commit e97a29e
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 19 deletions.
81 changes: 62 additions & 19 deletions addon/components/lazy-render-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Ember from 'ember';
import layout from 'ember-tooltips/templates/components/lazy-render-wrapper';

const { computed, get, $ } = Ember;
export const targetEventNameSpace = 'target-lazy-render-wrapper';

// https://github.com/emberjs/rfcs/issues/168
// https://github.com/emberjs/ember.js/pull/12500
Expand Down Expand Up @@ -120,46 +121,88 @@ export default Ember.Component.extend({
_shouldShowOnRender: false,

event: 'hover', // hover, click, focus, none
entryInteractionEvents: computed('event', function() {
_lazyRenderEvents: computed('event', function() {
// the lazy-render wrapper will only render the tooltip when
// the $parent element is interacted with. This CP defines which
// events will trigger the rendering. We always include focusin
// to keep the component accessible.
let entryInteractionEvents = ['focusin'];
// the $target element is interacted with. This CP defines which
// events will trigger the rendering. Unless event="none" we always
// include focusin to keep the component accessible.
let event = this.get('event');

if (event === 'none') {
return [];
}

let _lazyRenderEvents = ['focusin'];

if (event === 'hover') {
entryInteractionEvents.push('mouseenter');
_lazyRenderEvents.push('mouseenter');
} else if (event === 'click') {
entryInteractionEvents.push('click');
_lazyRenderEvents.push('click');
}

return _lazyRenderEvents;
}),

/**
* A jQuery element that the _lazyRenderEvents will be
* attached to during didInsertElement and
* removed from during willDestroyElement
* @property $target
* @type jQuery element
* @default the parent jQuery element
*/
$target: computed('target', 'tetherComponentName', function() {
const target = this.get('target'); // #some-id
let $target;

if (target) {
$target = $(target);
} else if (this.get('tetherComponentName').indexOf('-on-component') >= 0) {
// TODO(Andrew) refactor this once we've gotten rid of the -on-component approach
// share the functionality with `onComponentTarget`
const targetView = this.get('parentView');

if (!targetView) {
console.warn('No targetView found');
return null;
} else if (!targetView.get('elementId')) {
console.warn('No targetView.elementId');
return null;
}

const targetViewElementId = targetView.get('elementId');
$target = $(`#${targetViewElementId}`);
} else {
$target = getParent(this);
}

return entryInteractionEvents;
return $target;
}),

didInsertElement() {
this._super(...arguments);

if (this.get('_shouldRender')) {
// if the tooltip _shouldRender then we don't need
// any special $parent event handling
// any special $target event handling
return;
}

const $parent = getParent(this);
let $target = this.get('$target');

if (this.get('event') === 'hover') {
// We've seen instances where a user quickly mouseenter and mouseleave the $parent.
// We've seen instances where a user quickly mouseenter and mouseleave the $target.
// By providing this event handler we ensure that the tooltip will only *show*
// if the user has mouseenter and not mouseleave immediately afterwards.
$parent.on('mouseleave.target-lazy-render-wrapper', () => {
$target.on(`mouseleave.${targetEventNameSpace}`, () => {
this.set('_shouldShowOnRender', false);
});
}

this.get('entryInteractionEvents').forEach((entryInteractionEvent) => {
$parent.on(`${entryInteractionEvent}.target-lazy-render-wrapper`, () => {
this.get('_lazyRenderEvents').forEach((entryInteractionEvent) => {
$target.on(`${entryInteractionEvent}.${targetEventNameSpace}`, () => {
if (this.get('_hasUserInteracted')) {
$parent.off(`${entryInteractionEvent}.target-lazy-render-wrapper`);
$target.off(`${entryInteractionEvent}.${targetEventNameSpace}`);
} else {
this.set('_hasUserInteracted', true);
this.set('_shouldShowOnRender', true);
Expand All @@ -184,11 +227,11 @@ export default Ember.Component.extend({
willDestroyElement() {
this._super(...arguments);

const $parent = getParent(this);
this.get('entryInteractionEvents').forEach((entryInteractionEvent) => {
$parent.off(`${entryInteractionEvent}.target-lazy-render-wrapper`);
const $target = this.get('$target');
this.get('_lazyRenderEvents').forEach((entryInteractionEvent) => {
$target.off(`${entryInteractionEvent}.${targetEventNameSpace}`);
});

$parent.off('mouseleave.target-lazy-render-wrapper');
$target.off(`mouseleave.${targetEventNameSpace}`);
},
});
6 changes: 6 additions & 0 deletions tests/dummy/app/templates/components/some-component.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{!-- an extra div is needed so that the -on-component tests
are actually grabbing the parentView, which is not necessarily
always the parent element --}}
<div>
{{yield}}
</div>
132 changes: 132 additions & 0 deletions tests/integration/components/render-events-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import Ember from 'ember';
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import { targetEventNameSpace } from 'ember-tooltips/components/lazy-render-wrapper';

moduleForComponent('tooltip-on-element', 'Integration | Component | event handlers', {
integration: true
});


function assertTargetHasLazyRenderEvents(assert, $target, eventType="hover") {
const eventsObject = $._data($target[0], 'events');

function assertEventExists(event) {
// for some reason mouseenter events are stored as mouseover in the eventsObject
// same for mouseleave -> mouse out
let eventObjectName = event;
if (event === 'mouseenter') {
eventObjectName = 'mouseover';
} else if (event === 'mouseleave') {
eventObjectName = 'mouseout';
}

assert.ok(eventsObject && eventsObject[eventObjectName],
`the eventsObject exists and should have ${eventObjectName} event`);

let eventHandler = eventsObject[eventObjectName][0];
assert.equal(eventHandler.origType, event,
`the eventHandler's origType property should equal ${event}`);

assert.ok(eventHandler.namespace.indexOf(targetEventNameSpace) >= 0,
'the eventHandler\'s namespace property be unique to ember-tooltips');
}

function assertNumEventsExist(num) {
// This function asserts that a certain number of event handlers are attached to an object.
// Event handlers are stored in arrays on the eventsObject like so...
// eventsObject = { focusin: [x, x], click: [x] } would equal 3 events

let numEvents = Object.keys(eventsObject || {}).reduce(function(n, keyName) {
return n + eventsObject[keyName].length;
}, 0);

assert.equal(num, numEvents,
`${num} events should exist`);
}

if (eventType === 'hover') {
assertNumEventsExist(3);
assertEventExists('focusin');
assertEventExists('mouseenter');
assertEventExists('mouseleave');
} else if (eventType === 'click') {
assertNumEventsExist(2);
assertEventExists('focusin');
assertEventExists('click');
} else if (eventType === 'focus') {
assertNumEventsExist(1);
assertEventExists('focusin');
} else if (eventType === 'none') {
assertNumEventsExist(0);
}
}


['hover', 'click', 'focus', 'none'].forEach(function(eventType) {
test(`lazy-render-wrapper correctly assigns event handlers when event=${eventType}`, function(assert) {

this.set('eventType', eventType);

this.render(hbs`{{tooltip-on-element event=eventType enableLazyRendering=true}}`);

const $target = this.$();

assertTargetHasLazyRenderEvents(assert, $target, eventType);

});
});


test('lazy-render-wrapper correctly assigns event handlers when target="some-id"', function(assert) {

this.render(hbs`
<div id="some-id"></div>
{{tooltip-on-element target="#some-id" enableLazyRendering=true}}
`);

const $target = this.$('#some-id');

assertTargetHasLazyRenderEvents(assert, $target);

});


test('lazy-render-wrapper correctly assigns event handlers when -on-component', function(assert) {

this.render(hbs`
{{#some-component}}
{{tooltip-on-component enableLazyRendering=true}}
{{/some-component}}
`);

const $target = this.$('.some-component');

assertTargetHasLazyRenderEvents(assert, $target);

});


test('lazy-render-wrapper removes event handlers when -on-component is destroyed', function(assert) {

this.render(hbs`
{{#some-component}}
{{#unless deleteTooltip}}
{{tooltip-on-component enableLazyRendering=true}}
{{/unless}}
{{/some-component}}
`);

const $target = this.$('.some-component');
const done = assert.async();

assertTargetHasLazyRenderEvents(assert, $target);

this.set('deleteTooltip', true);

Ember.run.later(() => {
assertTargetHasLazyRenderEvents(assert, $target, 'none');
done();
}, 1000);

});

0 comments on commit e97a29e

Please sign in to comment.