Skip to content

Instantly share code, notes, and snippets.

@jamesarosen
Last active July 19, 2016 07:10
Show Gist options
  • Save jamesarosen/78920121b982e841a31c5bbbeb0b6650 to your computer and use it in GitHub Desktop.
Save jamesarosen/78920121b982e841a31c5bbbeb0b6650 to your computer and use it in GitHub Desktop.
my-popover
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'Ember Twiddle'
});
<h1>Welcome to {{appName}}</h1>
<br>
<nav>
{{#my-popover as |close|}}
--&gt; Open &lt;--
{{else}}
<div>
<label>
I stay
<input value=0 />
</label>
</div>
<a data-my-popover-close>Close</a>
{{/my-popover}}
</nav>
<h3>Popover Demo</h3>
Try:
<ul>
<li tabindex=0>Hovering over the target, then the popover, then away. Notice the brief delay before the popover closes. This is to give the user time to move the mouse from target to popover in case they are not directly adjacent (as they are not here).</li>
<li>Opening the popover, focusing on the input within, and then mousing out (without clicking). Notice how the popover stays open as long as it retains focus.</li>
<li>Clicking items inside the popover. Notice that the label and input don't close the popover, but the link does.</li>
<li>Tabbing through the focusable elements on the page. Notice that when you focus out of the popover there is no delay in closing.</li>
</ul>
NB: this popover doesn't use <a href='https://github.com/yapplabs/ember-wormhole' target='_blank'>ember-wormhole</a>, but it probably should.
import Ember from 'ember';
const { $, run } = Ember;
const HIDE_DELAY = 400;
export default Ember.Component.extend({
classNames: 'my-popover',
isOpen: false,
click({ target }) {
// CRUFT: we cannot yield block params to {{else}} blocks,
// so we can't do
//
// {{#my-popover}}
// Open
// {{else as |close|}}
// <a {{action close}}>Close</a>
// {{/my-popover}}
//
// Thus, to tell the containing popover to close on click,
// an element within the popover marks itself with
// [data-my-popover-close].
if ($(target).is('[data-my-popover-close]')) {
this.set('isOpen', false);
}
},
didInsertElement() {
this.hideObserver = ({ target, data: hideDelay }) => {
if (this.isDestroyed) return;
const $this = this.$();
if ($this == null) return;
const $target = $(target);
const keepOpen = $this.find(':focus').length > 0 ||
$this.is($target).length > 0 ||
$this.find($target).length > 0;
if (keepOpen) {
run.cancel(this.hideCallback);
this.hideCallback = null;
} else {
this.hideCallback = this.hideCallback || run.later(this, 'set', 'isOpen', false, hideDelay);
}
};
$('body')
.on('click.my-popover', 0, this.hideObserver)
.on('focusin.my-popover', 0, this.hideObserver)
.on('mouseover.my-popover', HIDE_DELAY, this.hideObserver);
},
willDestroyElement() {
$('body')
.off('click.my-popover', this.hideObserver)
.off('focusin.my-popover', this.hideObserver)
.off('mouseover.my-popover', this.hideObserver);
},
actions: {
open() {
this.set('isOpen', true);
},
close() {
this.set('isOpen', false);
}
}
});
<span tabindex=0 class='my-popover__target' onclick={{action 'open'}} onmouseover={{action 'open'}} onfocus={{action 'open'}}>
{{yield (action 'close')}}
</span>
{{#if isOpen}}
<div class='my-popover__content'>
{{yield (action 'close') to="inverse"}}
</div>
{{/if}}
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
}
.my-popover__target {
cursor: pointer;
}
.my-popover__target:focus {
color: red;
text-decoration: underline;
}
.my-popover__content {
background: white;
border: 1px solid grey;
border-radius: 5px;
margin-top: 5px;
padding: 5px;
position: absolute;
z-index: 100;
}
{
"version": "0.10.1",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": true,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "2.6.0",
"ember-data": "2.6.1",
"ember-template-compiler": "2.6.0"
},
"addons": {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment