Skip to content

Instantly share code, notes, and snippets.

@thorpj
Last active August 13, 2023 22:00
Show Gist options
  • Save thorpj/9f7164b1afa93d7e46f88895a5c7c5cc to your computer and use it in GitHub Desktop.
Save thorpj/9f7164b1afa93d7e46f88895a5c7c5cc to your computer and use it in GitHub Desktop.
StimulusJS Controller for popups using tippy.js.
import ApplicationController from './application_controller';
import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
// Inspired by https://chrislabarge.com/posts/stimulus-popup/
// NOTE: ApplicationController must have `this.element[this.identifier] = this;` in the connect() method.
/*
// Manually set content and trigger
// 'data-popup-trigger': :manual is optional. When no content is given, the controller isn't going to show a popup on mouseenter or click
%div.some-popup{ data: { controller: :popup, popup: { target: :trigger }}}
JS> document.querySelector('.some-popup').popup.show('Hello there')
// You could also manually trigger the below examples, without changing the content
JS> document.querySelector('.some-popup').popup.show()
// Text content
%btn.btn.btn-primary{ data: { 'popup-target': :trigger, action: 'click->popup#show', content: 'Text content'}} Click me
// HTML content
%btn.avatar.btn.btn-primary{ data: { 'popup-target': :trigger, action: 'click->popup#showExistingContent' } }
Click me
.popup{ 'data-popup-target': :content }
%span Hello
*/
export default class extends ApplicationController {
static targets = ['trigger', 'content'];
manualTrigger = 'manual';
defaultTimeout = 8000;
defaultPopupTrigger = 'mouseenter focus';
initialize() {
this.trigger = this.getTrigger();
this.content = this.getContent();
this.initPopup();
if (this.hasContentTarget) {
this.contentTarget.style.display = 'none';
}
}
showExistingContent(event) {
this.show();
}
show(content = null, timeout = this.defaultTimeout) {
if (content) {
this.setContent(content);
}
this.popup.show();
this.setTimeout(timeout);
}
hide() {
this.popup.hide();
}
setContent(content) {
this.popup.setContent(content);
}
setTimeout(timeout) {
clearTimeout(this.timeout); // Clear timeout from previous show action
if (timeout !== 0) {
this.timeout = setTimeout(() => {
this.popup.hide();
}, timeout);
}
}
initPopup() {
this.popup = tippy(this.trigger, {
content: this.content,
allowHTML: true,
trigger: this.getTriggerMethod(),
});
}
getTriggerMethod() {
if (this.trigger.dataset.popupTrigger) {
// E.g. 'data-popup-trigger': 'click'
return this.trigger.dataset.popupTrigger;
} else if (!this.content) {
// No content given, setContent and manually trigger only
return this.manualTrigger;
} else {
// Use default trigger(s)
return this.defaultPopupTrigger;
}
}
getContent() {
if (this.hasContentTarget) {
return this.contentTarget.innerHTML;
} else if (this.trigger.dataset.content) {
return this.trigger.dataset.content;
}
}
getTrigger() {
if (this.hasTriggerTarget) {
return this.triggerTarget;
} else {
return this.element;
}
}
}
@thorpj
Copy link
Author

thorpj commented May 22, 2021

Can be passed text content through a data attribute, can have HTML content nested under it using the 'data-popup-target': :content, or manually triggered using JS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment