|
@@ -1,34 +1,55 @@
|
|
|
// A simple tracker dependency that we invalidate every time the window is
|
|
|
// resized. This is used to reactively re-calculate the popup position in case
|
|
|
-// of a window resize.
|
|
|
-var windowResizeDep = new Tracker.Dependency();
|
|
|
-$(window).on('resize', function() { windowResizeDep.changed(); });
|
|
|
+// of a window resize. This is the equivalent of a "Signal" in some other
|
|
|
+// programming environments.
|
|
|
+let windowResizeDep = new Tracker.Dependency()
|
|
|
+$(window).on('resize', () => windowResizeDep.changed())
|
|
|
+
|
|
|
+window.Popup = new class {
|
|
|
+ constructor() {
|
|
|
+ // The template we use to render popups
|
|
|
+ this.template = Template.popup
|
|
|
+
|
|
|
+ // We only want to display one popup at a time and we keep the view object
|
|
|
+ // in this `Popup._current` variable. If there is no popup currently opened
|
|
|
+ // the value is `null`.
|
|
|
+ this._current = null
|
|
|
+
|
|
|
+ // It's possible to open a sub-popup B from a popup A. In that case we keep
|
|
|
+ // the data of popup A so we can return back to it. Every time we open a new
|
|
|
+ // popup the stack grows, every time we go back the stack decrease, and if
|
|
|
+ // we close the popup the stack is reseted to the empty stack [].
|
|
|
+ this._stack = []
|
|
|
+
|
|
|
+ // We invalidate this internal dependency every time the top of the stack
|
|
|
+ // has changed and we want to re-render a popup with the new top-stack data.
|
|
|
+ this._dep = new Tracker.Dependency()
|
|
|
+ }
|
|
|
|
|
|
-Popup = {
|
|
|
/// This function returns a callback that can be used in an event map:
|
|
|
///
|
|
|
/// Template.tplName.events({
|
|
|
/// 'click .elementClass': Popup.open("popupName")
|
|
|
- /// });
|
|
|
+ /// })
|
|
|
///
|
|
|
/// The popup inherit the data context of its parent.
|
|
|
- open: function(name) {
|
|
|
- var self = this;
|
|
|
- var popupName = name + 'Popup';
|
|
|
+ open(name) {
|
|
|
+ let self = this
|
|
|
+ const popupName = `${name}Popup`
|
|
|
|
|
|
- var clickFromPopup = function(evt) {
|
|
|
- return $(evt.target).closest('.js-pop-over').length !== 0;
|
|
|
- };
|
|
|
+ function clickFromPopup(evt) {
|
|
|
+ return $(evt.target).closest('.js-pop-over').length !== 0
|
|
|
+ }
|
|
|
|
|
|
return function(evt) {
|
|
|
- // If a popup is already openened, clicking again on the opener element
|
|
|
- // should close it -- and interupt the current `open` function.
|
|
|
+ // If a popup is already opened, clicking again on the opener element
|
|
|
+ // should close it -- and interrupt the current `open` function.
|
|
|
if (self.isOpen()) {
|
|
|
- var previousOpenerElement = self._getTopStack().openerElement;
|
|
|
+ let previousOpenerElement = self._getTopStack().openerElement
|
|
|
if (previousOpenerElement === evt.currentTarget) {
|
|
|
- return self.close();
|
|
|
+ return self.close()
|
|
|
} else {
|
|
|
- $(previousOpenerElement).removeClass('is-active');
|
|
|
+ $(previousOpenerElement).removeClass('is-active')
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -37,28 +58,28 @@ Popup = {
|
|
|
// if the popup has no parent, or from the parent `openerElement` if it
|
|
|
// has one. This allows us to position a sub-popup exactly at the same
|
|
|
// position than its parent.
|
|
|
- var openerElement;
|
|
|
+ let openerElement
|
|
|
if (clickFromPopup(evt)) {
|
|
|
- openerElement = self._getTopStack().openerElement;
|
|
|
+ openerElement = self._getTopStack().openerElement
|
|
|
} else {
|
|
|
- self._stack = [];
|
|
|
- openerElement = evt.currentTarget;
|
|
|
+ self._stack = []
|
|
|
+ openerElement = evt.currentTarget
|
|
|
}
|
|
|
|
|
|
- $(openerElement).addClass('is-active');
|
|
|
- evt.preventDefault();
|
|
|
+ $(openerElement).addClass('is-active')
|
|
|
+ evt.preventDefault()
|
|
|
|
|
|
// We push our popup data to the stack. The top of the stack is always
|
|
|
// used as the data source for our current popup.
|
|
|
self._stack.push({
|
|
|
- popupName: popupName,
|
|
|
+ popupName,
|
|
|
+ openerElement,
|
|
|
hasPopupParent: clickFromPopup(evt),
|
|
|
title: self._getTitle(popupName),
|
|
|
- openerElement: openerElement,
|
|
|
depth: self._stack.length,
|
|
|
offset: self._getOffset(openerElement),
|
|
|
- dataContext: this.currentData && this.currentData() || this
|
|
|
- });
|
|
|
+ dataContext: this.currentData && this.currentData() || this,
|
|
|
+ })
|
|
|
|
|
|
// If there are no popup currently opened we use the Blaze API to render
|
|
|
// one into the DOM. We use a reactive function as the data parameter that
|
|
@@ -70,18 +91,16 @@ Popup = {
|
|
|
// our internal dependency, and since we just changed the top element of
|
|
|
// our internal stack, the popup will be updated with the new data.
|
|
|
if (! self.isOpen()) {
|
|
|
- self.current = Blaze.renderWithData(self.template, function() {
|
|
|
- self._dep.depend();
|
|
|
- return _.extend(self._stack[self._stack.length - 1], {
|
|
|
- stack: self._stack
|
|
|
- });
|
|
|
- }, document.body);
|
|
|
+ self.current = Blaze.renderWithData(self.template, () => {
|
|
|
+ self._dep.depend()
|
|
|
+ return _.extend(self._getTopStack(), { stack: self._stack })
|
|
|
+ }, document.body)
|
|
|
|
|
|
} else {
|
|
|
- self._dep.changed();
|
|
|
+ self._dep.changed()
|
|
|
}
|
|
|
- };
|
|
|
- },
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// This function returns a callback that can be used in an event map:
|
|
|
///
|
|
@@ -89,119 +108,100 @@ Popup = {
|
|
|
/// 'click .elementClass': Popup.afterConfirm("popupName", function() {
|
|
|
/// // What to do after the user has confirmed the action
|
|
|
/// })
|
|
|
- /// });
|
|
|
- afterConfirm: function(name, action) {
|
|
|
- var self = this;
|
|
|
+ /// })
|
|
|
+ afterConfirm(name, action) {
|
|
|
+ let self = this
|
|
|
|
|
|
return function(evt, tpl) {
|
|
|
- var context = this.currentData && this.currentData() || this;
|
|
|
- context.__afterConfirmAction = action;
|
|
|
- self.open(name).call(context, evt, tpl);
|
|
|
- };
|
|
|
- },
|
|
|
+ let context = this.currentData && this.currentData() || this
|
|
|
+ context.__afterConfirmAction = action
|
|
|
+ self.open(name).call(context, evt, tpl)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/// The public reactive state of the popup.
|
|
|
- isOpen: function() {
|
|
|
- this._dep.changed();
|
|
|
- return !! this.current;
|
|
|
- },
|
|
|
+ isOpen() {
|
|
|
+ this._dep.changed()
|
|
|
+ return !! this.current
|
|
|
+ }
|
|
|
|
|
|
/// In case the popup was opened from a parent popup we can get back to it
|
|
|
/// with this `Popup.back()` function. You can go back several steps at once
|
|
|
/// by providing a number to this function, e.g. `Popup.back(2)`. In this case
|
|
|
/// intermediate popup won't even be rendered on the DOM. If the number of
|
|
|
/// steps back is greater than the popup stack size, the popup will be closed.
|
|
|
- back: function(n) {
|
|
|
- n = n || 1;
|
|
|
- var self = this;
|
|
|
- if (self._stack.length > n) {
|
|
|
- _.times(n, function() { self._stack.pop(); });
|
|
|
- self._dep.changed();
|
|
|
+ back(n = 1) {
|
|
|
+ if (this._stack.length > n) {
|
|
|
+ _.times(n, () => this._stack.pop())
|
|
|
+ this._dep.changed()
|
|
|
} else {
|
|
|
- self.close();
|
|
|
+ this.close()
|
|
|
}
|
|
|
- },
|
|
|
+ }
|
|
|
|
|
|
/// Close the current opened popup.
|
|
|
- close: function() {
|
|
|
+ close() {
|
|
|
if (this.isOpen()) {
|
|
|
- Blaze.remove(this.current);
|
|
|
- this.current = null;
|
|
|
+ Blaze.remove(this.current)
|
|
|
+ this.current = null
|
|
|
|
|
|
- var openerElement = this._getTopStack().openerElement;
|
|
|
- $(openerElement).removeClass('is-active');
|
|
|
+ let openerElement = this._getTopStack().openerElement
|
|
|
+ $(openerElement).removeClass('is-active')
|
|
|
|
|
|
- this._stack = [];
|
|
|
+ this._stack = []
|
|
|
}
|
|
|
- },
|
|
|
-
|
|
|
- // The template we use for every popup
|
|
|
- template: Template.popup,
|
|
|
-
|
|
|
- // We only want to display one popup at a time and we keep the view object in
|
|
|
- // this `Popup._current` variable. If there is no popup currently opened the
|
|
|
- // value is `null`.
|
|
|
- _current: null,
|
|
|
-
|
|
|
- // It's possible to open a sub-popup B from a popup A. In that case we keep
|
|
|
- // the data of popup A so we can return back to it. Every time we open a new
|
|
|
- // popup the stack grows, every time we go back the stack decrease, and if we
|
|
|
- // close the popup the stack is reseted to the empty stack [].
|
|
|
- _stack: [],
|
|
|
-
|
|
|
- // We invalidate this internal dependency every time the top of the stack has
|
|
|
- // changed and we want to render a popup with the new top-stack data.
|
|
|
- _dep: new Tracker.Dependency(),
|
|
|
+ }
|
|
|
|
|
|
// An utility fonction that returns the top element of the internal stack
|
|
|
- _getTopStack: function() {
|
|
|
- return this._stack[this._stack.length - 1];
|
|
|
- },
|
|
|
+ _getTopStack() {
|
|
|
+ return this._stack[this._stack.length - 1]
|
|
|
+ }
|
|
|
|
|
|
// We automatically calculate the popup offset from the reference element
|
|
|
// position and dimensions. We also reactively use the window dimensions to
|
|
|
// ensure that the popup is always visible on the screen.
|
|
|
- _getOffset: function(element) {
|
|
|
- var $element = $(element);
|
|
|
- return function() {
|
|
|
- windowResizeDep.depend();
|
|
|
- var offset = $element.offset();
|
|
|
- var popupWidth = 300 + 15;
|
|
|
+ _getOffset(element) {
|
|
|
+ let $element = $(element)
|
|
|
+ return () => {
|
|
|
+ windowResizeDep.depend()
|
|
|
+ const offset = $element.offset()
|
|
|
+ const popupWidth = 300 + 15
|
|
|
return {
|
|
|
left: Math.min(offset.left, $(window).width() - popupWidth),
|
|
|
- top: offset.top + $element.outerHeight()
|
|
|
- };
|
|
|
- };
|
|
|
- },
|
|
|
+ top: offset.top + $element.outerHeight(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
// We get the title from the translation files. Instead of returning the
|
|
|
// result, we return a function that compute the result and since `TAPi18n.__`
|
|
|
// is a reactive data source, the title will be changed reactively.
|
|
|
- _getTitle: function(popupName) {
|
|
|
- return function() {
|
|
|
- var translationKey = popupName + '-title';
|
|
|
+ _getTitle(popupName) {
|
|
|
+ return () => {
|
|
|
+ const translationKey = `${popupName}-title`
|
|
|
|
|
|
// XXX There is no public API to check if there is an available
|
|
|
// translation for a given key. So we try to translate the key and if the
|
|
|
// translation output equals the key input we deduce that no translation
|
|
|
// was available and returns `false`. There is a (small) risk a false
|
|
|
// positives.
|
|
|
- var title = TAPi18n.__(translationKey);
|
|
|
- return title !== translationKey ? title : false;
|
|
|
- };
|
|
|
+ const title = TAPi18n.__(translationKey)
|
|
|
+ return title !== translationKey ? title : false
|
|
|
+ }
|
|
|
}
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
// We close a potential opened popup on any left click on the document, or go
|
|
|
// one step back by pressing escape.
|
|
|
-var escapeActions = ['back', 'close'];
|
|
|
-_.each(escapeActions, function(actionName) {
|
|
|
- EscapeActions.register('popup-' + actionName,
|
|
|
- _.bind(Popup[actionName], Popup),
|
|
|
- _.bind(Popup.isOpen, Popup), {
|
|
|
+const escapeActions = ['back', 'close']
|
|
|
+_.each(escapeActions, (actionName) => {
|
|
|
+ EscapeActions.register(`popup-${actionName}`,
|
|
|
+ () => Popup[actionName](),
|
|
|
+ () => Popup.isOpen(),
|
|
|
+ {
|
|
|
noClickEscapeOn: '.js-pop-over',
|
|
|
- enabledOnClick: actionName === 'close'
|
|
|
+ enabledOnClick: actionName === 'close',
|
|
|
}
|
|
|
- );
|
|
|
-});
|
|
|
+ )
|
|
|
+})
|
|
|
|