| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 | 
							- // 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. This is the equivalent of a "Signal" in some other
 
- // programming environments (eg, elm).
 
- const 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();
 
-   }
 
-   /// 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(name) {
 
-     const self = this;
 
-     const popupName = `${name}Popup`;
 
-     function clickFromPopup(evt) {
 
-       return $(evt.target).closest('.js-pop-over').length !== 0;
 
-     }
 
-     return function(evt) {
 
-       // If a popup is already opened, clicking again on the opener element
 
-       // should close it -- and interrupt the current `open` function.
 
-       if (self.isOpen()) {
 
-         const previousOpenerElement = self._getTopStack().openerElement;
 
-         if (previousOpenerElement === evt.currentTarget) {
 
-           return self.close();
 
-         } else {
 
-           $(previousOpenerElement).removeClass('is-active');
 
-         }
 
-       }
 
-       // We determine the `openerElement` (the DOM element that is being clicked
 
-       // and the one we take in reference to position the popup) from the event
 
-       // 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.
 
-       let openerElement;
 
-       if (clickFromPopup(evt)) {
 
-         openerElement = self._getTopStack().openerElement;
 
-       } else {
 
-         self._stack = [];
 
-         openerElement = evt.currentTarget;
 
-       }
 
-       $(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,
 
-         openerElement,
 
-         hasPopupParent: clickFromPopup(evt),
 
-         title: self._getTitle(popupName),
 
-         depth: self._stack.length,
 
-         offset: self._getOffset(openerElement),
 
-         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
 
-       // return the the complete along with its top element and depends on our
 
-       // internal dependency that is being invalidated every time the top
 
-       // element of the stack has changed and we want to update the popup.
 
-       //
 
-       // Otherwise if there is already a popup open we just need to invalidate
 
-       // 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, () => {
 
-           self._dep.depend();
 
-           return { ...self._getTopStack(), stack: self._stack };
 
-         }, document.body);
 
-       } else {
 
-         self._dep.changed();
 
-       }
 
-     };
 
-   }
 
-   /// This function returns a callback that can be used in an event map:
 
-   ///   Template.tplName.events({
 
-   ///     'click .elementClass': Popup.afterConfirm("popupName", function() {
 
-   ///       // What to do after the user has confirmed the action
 
-   ///     }),
 
-   ///   });
 
-   afterConfirm(name, action) {
 
-     const self = this;
 
-     return function(evt, tpl) {
 
-       const context = this.currentData && this.currentData() || this;
 
-       context.__afterConfirmAction = action;
 
-       self.open(name).call(context, evt, tpl);
 
-     };
 
-   }
 
-   /// The public reactive state of the popup.
 
-   isOpen() {
 
-     this._dep.changed();
 
-     return Boolean(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(n = 1) {
 
-     if (this._stack.length > n) {
 
-       _.times(n, () => this._stack.pop());
 
-       this._dep.changed();
 
-     } else {
 
-       this.close();
 
-     }
 
-   }
 
-   /// Close the current opened popup.
 
-   close() {
 
-     if (this.isOpen()) {
 
-       Blaze.remove(this.current);
 
-       this.current = null;
 
-       const openerElement = this._getTopStack().openerElement;
 
-       $(openerElement).removeClass('is-active');
 
-       this._stack = [];
 
-     }
 
-   }
 
-   // An utility fonction that returns the top element of the internal stack
 
-   _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(element) {
 
-     const $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(),
 
-       };
 
-     };
 
-   }
 
-   // 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(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.
 
-       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.
 
- const escapeActions = ['back', 'close'];
 
- escapeActions.forEach((actionName) => {
 
-   EscapeActions.register(`popup-${actionName}`,
 
-     () => Popup[actionName](),
 
-     () => Popup.isOpen(),
 
-     {
 
-       noClickEscapeOn: '.js-pop-over',
 
-       enabledOnClick: actionName === 'close',
 
-     }
 
-   );
 
- });
 
 
  |