I looked around for a good popover library for Ember. I couldn't find one I liked (and was compatible with Ember 1.13 and Glimmer), so I whipped up a little ditty:
// app/pop-over/component.js
import $ from "jquery";
import Ember from "ember";
const { Component, run } = Ember;
const ESC = 27;
export default Component.extend({
isVisible: false,
didInitAttrs() {
this._closeOnClickOut = this._closeOnClickOut.bind(this);
this._closeOnEsc = this._closeOnEsc.bind(this);
},
didRender() {
run.next(() => {
const method = this.get('isVisible') && !this.get('isDestroyed') ? 'on' : 'off';
$(window)
[method]('click', this._closeOnClickOut)
[method]('keyup', this._closeOnEsc);
});
},
_close() {
if (this.get('isDestroyed')) { return; }
this.set('isVisible', false);
this.sendAction('close');
}
_closeOnClickOut(e) {
const clickIsInside = this.$().find(e.target).length > 0;
if (!clickIsInside) { this._close(); }
},
_closeOnEsc(e) {
if (e.keyCode === ESC) { this._close(); }
}
});
If you don't like the component reaching out to $(window)
to attach the click handler, you could add an invisible scrim behind the popover contents and attach to that. There's not much you can do about the keyup handler, though, since window (or document) is the only good place to catch it.
The Ember.run.next
made this a little difficult to test. My first attempt was
test('popover works', function(assert) {
this.set('isOpen', false);
render(`
<span class='outside'>Outside</span>
{{#pop-over isVisible=isOpen}}
<span class='inside'>Inside</span>
{{/pop-over}}
`);
assert.equal(this.$('.inside').is(':visible'), false)
Ember.run(this, 'set', 'isOpen', true);
assert.equal(this.$('.inside').is(':visible'), true);
Ember.run(this.$('.outside'), 'click');
assert.equal(this.$('.inside').is(':visible'), false);
});
Unfortunately, that runs the click
before the click handler is added. Luckily, ember-qunit lets you return a Promise from your test
. Thus,
test('popover works', function(assert) {
this.set('isOpen', false);
render(`
<span class='outside'>Outside</span>
{{#pop-over isVisible=isOpen}}
<span class='inside'>Inside</span>
{{/pop-over}}
`);
assert.equal(this.$('.inside').is(':visible'), false)
Ember.run(this, 'set', 'isOpen', true);
assert.equal(this.$('.inside').is(':visible'), true);
return new Ember.RSVP.Promise((resolve) => {
Ember.run.next(() => {
Ember.run(this.$('.outside'), 'click');
assert.equal(this.$('.inside').is(':visible'), false);
resolve();
});
});
});
This code is released under the MIT License, copyright James A Rosen 2015.
@cibernox yup! Updating now!