Popup
import Popup, { determineOffCanvasType } from '@bbc/front-end-kit/js/components/Popup';
Introduction
The Popup component manages a javascript dialog dom element so we can easily create popups, provide them with content and manage their animations easily. It extends from Component so it comes with all the benefits of that class and its parents.
Why dialog?
The power of the dialog element lies in the fact that it can be added anywhere and at any level of the DOM tree. Dialog will always be rendered in a shadow dom element that can be found at the bottom of the DOM tree. No issues with indexes or having content related popups be spread accross the DOM tree to bypass tha index and context issues of the elements.
Why the Popup class?
THe popup clas adds extra structure around the dialog component so that it can be easily extended and adapted to custom situations while also still focussing on consistent code structure and maintainability. Thanks to this class it is easy to add extra animations using other libraries like gsap
Getting started
Javascript
The class can both be extended or initialised directly. As always it is advised to extend into a child class of the specific type of popup that is meant to be managed. Example: A popup containing a login, could be extended to a class called LoginPopup
class LoginPopup extends Popup {
constructor(args = {}) {
super(args);
}
// some extra methods / getters & setters specific to this type of popup
get isChecking() {
return this._isChecking
}
}
When initialising:
const loginPopup = new LoginPopup({
$el: $loginPopupElement
});
DOM Element
The DOM element should resemble the following structure.
<dialog class="popup">
<div class="popup__container">
<!-- popup content here -->
<button class="popup__close-btn">Close</button>
</div>
</dialog>
Attribute initialisation
The Popup component is also able to look for it's triggers by itself during construction. Set a data-popup attribute with a value (id/key) on the element of the popup and the popup will automatically look for triggers in the dom that use a data-popup-trigger attribute with the same value (id/key). Every trigger that matches the query will be registered as a trigger of the instance an thus will toggle toe popup when it is clicked.
<!-- triggers -->
<a href="#" data-popup-trigger="my-popup">Anchor trigger</a>
<button data-popup-trigger="my-popup">Button trigger</button>
<div data-popup-trigger="my-popup">Div trigger</div>
<!-- the popup they will trigger -->
<dialog class="popup" data-popup="my-popup">
<div class="popup__container">
<!-- popup content here -->
<button class="popup__close-btn">Close</button>
</div>
</dialog>
Animations
The Popup component allows the build-in animations to be overridden during initialization. The custom show/hide methods can be passed using the animation object as a parameter of the c
Override the animations
Simply during initialisation:
const popup = new Popup({
animations: {
show: myCustomShowAnimation,
hide: myCustomHideAnimation
}
})
Although it is advised to extend from Popup first and override the args object of the constructor before it is passed to the Popup constructor
class CustomPopup extends Popup {
constructor (args = {}) {
// override args parameters
args.animations = args.animations || {};
args.animations.show = myCustomShowAnimation;
args.animations.hide = myCustomHideAnimation;
// run super constructor
super(args);
}
}
Animation method requirements
Custom animation methods require to return a Promise that resolves when the animation is completes. This lets the Popup know when the animation is complete so that the logic that is waiting can continue.
The method will be called using the instance as scope (this) and receives the options as parameter comming from the show/hide/toggle methods
function myCustomShowAnimation (options = {}) {
return new Promise(resolve => {
// the custom animation using gsap in this case
gsap.to(this.$el, {
opacity: 1,
onComplete: resolve
})
///
})
}
Off-canvas behavior
The default built-in animation for the popup is a simple fade in. But it also incorporates more Off-Canvas like animations that can simply be triggered by adding the right css class to the DOM element it manages. Using css it will also make sure it sticks to the desired side of the screen like off-canvas elements are required to do. Possible classes to trigger this behavior.
--oc-top: Triggers the animation to slide in from the top of the screen--oc-right: Triggers the animation to slide in from the right of the screen--oc-bottom: Triggers the animation to slide in from the bottom of the screen--oc-left: Triggers the animation to slide in from the left of the screen
<!-- popup used as mobile navigation that needs to slide in from the right -->
<dialog class="popup --oc-right --mobile-nav">
...
</dialog>
API
constructor(args = {})
The constructor method initialises the new Popup instance.
Parameters
args: The configuration object used by the constructor to initialise the Popup in the desired way.$triggers: List of DOM elements that will be used to toggle the state of the Popup (DOM element or list of DOM elements)isVisible(Boolean, defaultfalse): Sets the initial visibility state of the popup. Applied instantly without animation.quitOnEscape: Flag to allow the Popup to hide when the user presses ESC buttonanimations: Object that allows the built-in show/hide animations to be overridden with different methodsshow: Method that will be used to show the popuphide: Method that will be used to hide the popup
- ... Component properties
Methods
show (options)
Show the popup using either a built-in animation or a custom animation defined during initilisation
Parameters
options: Object with extra customisations during runtime that will be applied while the show state is triggeredinstant: skips the animation if true (default: false)
Tips
If true is passed instead of an options object, it will be interpreted as a value for the instant property of the options object. The animation will be skipped
Return
Returns a Promise that resolves when the transition is done.
hide (options)
Hide the popup using either a built-in animation or a custom animation defined during initilisation
Parameters
options: Object with extra customisations during runtime that will be applied while the hide state is triggeredinstant: skips the animation if true (default: false)
Tips
If true is passed instead of an options object, it will be interpreted as a value for the instant property of the options object. The animation will be skipped
Return
Returns a Promise that resolves when the transition is done.
toggle (options)
Toggles the popup depending on it's current state using either a built-in animation or a custom animation defined during initilisation. It will use the corresponding show/hide method behind the scenes.
Parameters
options: Object with extra customisations during runtime that will be applied while the hide state is triggeredinstant: skips the animation if true (default: false)show(Boolean, optional): Force show (true) or hide (false) instead of toggling.
Tips
If true is passed instead of an options object, it will be interpreted as a value for the instant property of the options object. The animation will be skipped
Return
Returns undefined. Use show() or hide() directly if you need to await the animation Promise.
addTrigger (...triggers)
Registers extra DOM element as triggers to toggle the Poppup on click.
Parameters
...triggers: The DOM element to register. Every parameter passed will be registered as trigger. Parmeters may also be arrays of triggers.
Return
Return undefined
// single element
popup.addTrigger($trigger)
// multiple elments over parameters
popup.addTrigger($trigger1, $trigger2, $trigger3)
// array of triggers
popup.addTrigger([$trigger1, $trigger2, $trigger3])
// combination of both
popup.addTrigger($trigger1, [$trigger2, $trigger3], $trigger4)
removeTrigger (...$triggers)
Unregisters one or more DOM elements as triggers and removes their click listeners.
Warning
The bound listener reference is removed via removeEventListener, but the source notes this is still work-in-progress and may not fully decouple all listener edge cases. A console warning is logged for each removed trigger.
Parameters
...$triggers: The DOM element(s) to unregister. Accepts the same spread/array forms asaddTrigger.
Exported methods
determineOffCanvasType()
Inspects this._$el's classlist and returns the off-canvas direction ('top', 'right', 'bottom', 'left') or null if none of the --oc-* classes are present. Called internally by the hide animation to determine slide direction.
Getters & Setters
isVisible
Returns the current visibility state of the Popup.
true: visiblefalse: not visible
isTransitioning
Returns the state of the animation it is currently performing
true: transitioning/animatingfalse: idle (not animating)
$triggers
Returns the list of triggers that are registered to toggle the popup on click.
Events
Popup dispatches events using the EventDispatcher's api since Component extends from EventDispatcher
before-show
The moment before the show animation starts. isTransitioning and isVisible remain false
after-show
The moment right after the show animation ends. isTransitioning back to false, isVisible is now true
before-hide
The moment before the hide animation starts. isTransitioning is still false, isVisible is still true
after-hide
The moment right after the hide animation ends. isTransitioning and isvisible are back to false
trigger-click
Fires when the user clicks one of the registered triggers, before the toggle is processed. Access via e.detail:
e.detail.$trigger: The trigger element that was clicked.e.detail.originalEvent: The originalclickevent.
close-click
Fires when the user clicks the .popup__close-btn element, before hide() is called. Access via e.detail:
e.detail.$closeButton: The close button element that was clicked.e.detail.originalEvent: The originalclickevent.
escape
Fires when the user presses Escape (and quitOnEscape is true), before hide() is called. Access via e.detail:
e.detail.originalEvent: The originalkeyupevent.
Private properties
_$triggers
Array of registered triggers the instance is listening to so it can be toggled when they are clicked.
_$closeBtn
The close button DOM element of the popup instance
_quitOnEscape
Flag used to determine if the popup should listen to keyup events on the escape key to close itself.
_isVisible
Flag to track the visibility state of the popup instance
_isTransitioning
Flag to track if the popup instance is transitioning to a new state.
_animations
Object containing the animations the instance uses for the show/hide transitions It contains 2 properties:
show: The show animation functionhide: The hide animation function These methods can be set using the constructor.
External dependencies
- GSAP: Greensock's animation library for the built-in animations
- lodash: For some quick and usefull utility methods like:
flattenDeep: Flattens a multilevel array to a single level one.
Todo
- Add the possibility to define the off-canvas type through the constructor
- Create a toggleable class that will provide the toggle logic for both Popup and
Tab - Add a method to the instance to request the instance to do a new search for triggers in the DOM and maybe clear out triggers that were removed.