Skip to content
Snippets Groups Projects
lightbox.js 14.3 KiB
Newer Older
// refs 16504: copied from bootstrap3 to set an overlay, see below - GG
/*global grecaptcha, recaptchaOnLoad, resetCaptcha, VuFind */
VuFind.register('lightbox', function Lightbox() {
  // State
  var _originalUrl = false;
  var _currentUrl = false;
  var _lightboxTitle = '';
  var refreshOnClose = false;
  var _modalParams = {};
  // Elements
  var _modal, _modalBody, _clickedButton = null;
  // Utilities
  function _storeClickedStatus() {
    _clickedButton = this;
  }
  function _html(content) {
    _modalBody.html(content);
    // Set or update title if we have one
    if (_lightboxTitle !== '') {
      var h2 = _modalBody.find('h2:first-child');
      if (h2.length === 0) {
        h2 = $('<h2/>').prependTo(_modalBody);
      }
      h2.text(_lightboxTitle);
      _lightboxTitle = '';
    }
    _modal.modal('handleUpdate');
  }
  function _emit(msg, _details) {
    var details = _details || {};
    var event;
    try {
      event = new CustomEvent(msg, {
        detail: details,
        bubbles: true,
        cancelable: true
      });
    } catch (e) {
      // Fallback to document.createEvent() if creating a new CustomEvent fails (e.g. IE 11)
      event = document.createEvent('CustomEvent');
      event.initCustomEvent(msg, true, true, details);
    }
    return document.dispatchEvent(event);
  }

  // Public: Present an alert
  function showAlert(message, _type) {
    var type = _type || 'info';
    _html('<div class="flash-message alert alert-' + type + '">' + message + '</div>'
        + '<button class="btn btn-primary" data-dismiss="modal">' + VuFind.translate('close') + '</button>');
    _modal.modal('show');
  }
  function flashMessage(message, _type) {
    var type = _type || 'info';
    _modalBody.find('.flash-message,.fa.fa-spinner').remove();
    _modalBody.find('h2:first-of-type')
      .after('<div class="flash-message alert alert-' + type + '">' + message + '</div>');
  }
  function close() {
    _modal.modal('hide');
  }

  /**
   * Update content
   *
   * Form data options:
   *
   * data-lightbox-ignore = do not submit this form in lightbox
   */
  // function declarations to avoid style warnings about circular references
  var _constrainLink;
  var _formSubmit;
  function render(content) {
    if (typeof content !== "string") {
      return;
    }
    // Isolate successes
    var htmlDiv = $('<div/>').html(content);
    var alerts = htmlDiv.find('.flash-message.alert-success:not([data-lightbox-ignore])');
    if (alerts.length > 0) {
      var msgs = alerts.toArray().map(function getSuccessHtml(el) {
        return el.innerHTML;
      }).join('<br/>');
      var href = alerts.find('.download').attr('href');
      if (typeof href !== 'undefined') {
        location.href = href;
        close();
      } else {
        showAlert(msgs, 'success');
      }
      return;
    }
    // Deframe HTML
    var finalHTML = content;
    if (content.match('<!DOCTYPE html>')) {
      finalHTML = htmlDiv.find('.main > .container').html();
    }
    // Fill HTML
    _html(finalHTML);
    VuFind.modal('show');
    // Attach capturing events
    _modalBody.find('a').click(_constrainLink);
    // Handle submit buttons attached to a form as well as those in a form. Store
    // information about which button was clicked here as checking focused button
    // doesn't work on all browsers and platforms.
    _modalBody.find('[type=submit]').click(_storeClickedStatus);

    var forms = _modalBody.find('form:not([data-lightbox-ignore])');
    for (var i = 0; i < forms.length; i++) {
      $(forms[i]).on('submit', _formSubmit);
    }
    // Select all checkboxes
    $('#modal').find('.checkbox-select-all').change(function lbSelectAllCheckboxes() {
      $(this).closest('.modal-body').find('.checkbox-select-item').prop('checked', this.checked);
    });
    $('#modal').find('.checkbox-select-item').change(function lbSelectAllDisable() {
      $(this).closest('.modal-body').find('.checkbox-select-all').prop('checked', false);
    });
    // Recaptcha
    recaptchaOnLoad();
  }

  var _xhr = false;
  // Public: Handle AJAX in the Lightbox
  function ajax(obj) {
    if (_xhr !== false) {
      return;
    }
    if (_originalUrl === false) {
      _originalUrl = obj.url;
    }
    // Add lightbox GET parameter
    if (!obj.url.match(/layout=lightbox/)) {
      var parts = obj.url.split('#');
      obj.url = parts[0].indexOf('?') < 0
        ? parts[0] + '?'
        : parts[0] + '&';
      obj.url += 'layout=lightbox';
      if (_currentUrl) {
        obj.url += '&lbreferer=' + encodeURIComponent(_currentUrl);
      }
      obj.url += parts.length < 2 ? '' : '#' + parts[1];
    }
    _xhr = $.ajax(obj);
    _xhr.always(function lbAjaxAlways() { _xhr = false; })
      .done(function lbAjaxDone(content, status, jq_xhr) {
        var errorMsgs = [];
        if (jq_xhr.status !== 205) {
          var testDiv = $('<div/>').html(content);
          errorMsgs = testDiv.find('.flash-message.alert-danger:not([data-lightbox-ignore])');
          // Place Hold error isolation
          if (obj.url.match(/\/Record\/.*(Hold|Request)\?/)) {
            if (errorMsgs.length && testDiv.find('.record').length) {
              var msgs = errorMsgs.toArray().map(function getAlertHtml(el) {
                return el.innerHTML;
              }).join('<br/>');
              showAlert(msgs, 'danger');
              return false;
            }
          }
        }
        // Close the lightbox after deliberate login provided that:
        // - is a form
        // - catalog login for holds
        // - or that matches login/create account
        // - not a failed login
        if (
          obj.method && (
            obj.url.match(/catalogLogin/)
            || obj.url.match(/MyResearch\/(?!Bulk|Delete|Recover)/)
          ) && errorMsgs.length === 0
        ) {

          var eventResult = _emit('VuFind.lightbox.login', {
            originalUrl: _originalUrl,
            formUrl: obj.url
          });
          if (_originalUrl.match(/UserLogin/) || obj.url.match(/catalogLogin/)) {
            if (eventResult) {
              VuFind.refreshPage();
            }
            return false;
          } else {
            VuFind.lightbox.refreshOnClose = true;
          }
          _currentUrl = _originalUrl; // Now that we're logged in, where were we?
        }
        if (jq_xhr.status === 205) {
          VuFind.refreshPage();
          return;
        }

        /* remove save search icon by disabled one if save request succeeded */
        if (_originalUrl !== undefined && _originalUrl.match(/SaveSearch\?save=/)) {
          $("#save-search").toggleClass("hidden");
          $("#saved-search").toggleClass("hidden");
        }

        render(content);
      })
      .fail(function lbAjaxFail(deferred, errorType, msg) {
        showAlert(VuFind.translate('error_occurred') + '<br/>' + msg, 'danger');
      });
    return _xhr;
  }
  function reload() {
    ajax({ url: _currentUrl || _originalUrl });
  }

  /**
   * Evaluate a callback
   */
  function _evalCallback(callback, event, data) {
    if ('function' === typeof window[callback]) {
      return window[callback](event, data);
    }
    var parts = callback.split('.');
    if (typeof window[parts[0]] === 'object') {
      var obj = window[parts[0]];
      for (var i = 1; i < parts.length; i++) {
        if (typeof obj[parts[i]] === 'undefined') {
          obj = false;
          break;
        }
        obj = obj[parts[i]];
      }
      if ('function' === typeof obj) {
        return obj(event, data);
      }
    }
    console.error('Lightbox callback function not found.');
    return null;
  }

  /**
   * Modal link data options
   *
   * data-lightbox-href = go to this url instead
   * data-lightbox-ignore = do not open this link in lightbox
   * data-lightbox-post = post data
   * data-lightbox-title = Lightbox title (overrides any title the page provides)
   */
  _constrainLink = function constrainLink(event) {
    if (typeof $(this).data('lightboxIgnore') != 'undefined'
      || typeof this.attributes.href === 'undefined'
      || this.attributes.href.value.charAt(0) === '#'
      || this.href.match(/^[a-zA-Z]+:[^/]/) // ignore resource identifiers (mailto:, tel:, etc.)
    ) {
      return true;
    }
    if (this.href.length > 1) {
      event.preventDefault();
      var obj = {url: $(this).data('lightboxHref') || this.href};
      if ("string" === typeof $(this).data('lightboxPost')) {
        obj.type = 'POST';
        obj.data = $(this).data('lightboxPost');
      }
      _lightboxTitle = $(this).data('lightboxTitle') || '';
      _modalParams = $(this).data();
      VuFind.modal('show');
      ajax(obj);
      _currentUrl = this.href;
      return false;
    }
  };

  /**
   * Handle form submission.
   *
   * Form data options:
   *
   * data-lightbox-onsubmit = on submit, run named function
   * data-lightbox-onclose  = on close, run named function
   * data-lightbox-title = Lightbox title (overrides any title the page provides)
   *
   * Submit button data options:
   *
   * data-lightbox-ignore = do not handle clicking this button in lightbox
   */
  _formSubmit = function formSubmit(event) {
    // Gather data
    var form = event.target;
    var data = $(form).serializeArray();
    // Check for recaptcha
    if (typeof grecaptcha !== 'undefined') {
      var recaptcha = $(form).find('.g-recaptcha');
      if (recaptcha.length > 0) {
        data.push({ name: 'g-recaptcha-response', value: grecaptcha.getResponse(recaptcha.data('captchaId')) });
      }
    }
    // Force layout
    data.push({ name: 'layout', value: 'lightbox' }); // Return in lightbox, please
    // Add submit button information
    var submit = $(_clickedButton);
    _clickedButton = null;
    var buttonData = { name: 'submit', value: 1 };
    if (submit.length > 0) {
      if (typeof submit.data('lightbox-close') !== 'undefined') {
        close();
        return false;
      }
      if (typeof submit.data('lightbox-ignore') !== 'undefined') {
        return true;
      }
      buttonData.name = submit.attr('name') || 'submit';
      buttonData.value = submit.attr('value') || 1;
    }
    data.push(buttonData);
    // Special handlers
    // On submit behavior
    if ('string' === typeof $(form).data('lightboxOnsubmit')) {
      var ret = _evalCallback($(form).data('lightboxOnsubmit'), event, data);
      // return true or false to send that to the form
      // return null or anything else to continue to the ajax
      if (ret === false || ret === true) {
        return ret;
      }
    }
    // onclose behavior
    if ('string' === typeof $(form).data('lightboxOnclose')) {
      document.addEventListener('VuFind.lightbox.closed', function lightboxClosed(e) {
        this.removeEventListener('VuFind.lightbox.closed', arguments.callee);
        _evalCallback($(form).data('lightboxOnclose'), e, form);
      }, false);
    }
    // refs 16504: set an overlay - GG
    // Loading
    var overlay = '<div class="facet-loading-overlay">'
        + '<span class="facet-loading-overlay-label"><i class="modal-loading fa fa-spinner fa-spin fa-3x"></i>'
        + "</span></div>";
    // refs 16504: set an overlay END - GG
    _modalBody.prepend(overlay);
    // Prevent multiple submission of submit button in lightbox
    if (submit.closest(_modal).length > 0) {
      submit.attr('disabled', 'disabled');
    }
    // Store custom title
    _lightboxTitle = submit.data('lightboxTitle') || $(form).data('lightboxTitle') || '';
    // Get Lightbox content
    ajax({
      url: $(form).attr('action') || _currentUrl,
      method: $(form).attr('method') || 'GET',
      data: data
    }).done(function recaptchaReset() {
      resetCaptcha($(form));
    });

    VuFind.modal('show');
    return false;
  };

  // Public: Attach listeners to the page
  function bind(el) {
    var target = el || document;
    $(target).find('a[data-lightbox]')
      .unbind('click', _constrainLink)
      .on('click', _constrainLink);
    $(target).find('form[data-lightbox]')
      .unbind('submit', _formSubmit)
      .on('submit', _formSubmit);

    // Handle submit buttons attached to a form as well as those in a form. Store
    // information about which button was clicked here as checking focused button
    // doesn't work on all browsers and platforms.
    $('form[data-lightbox]').each(function bindFormSubmitsLightbox(i, form) {
      $(form).find('[type=submit]').click(_storeClickedStatus);
      $('[type="submit"][form="' + form.id + '"]').click(_storeClickedStatus);
    });

    // Display images in the lightbox
    $('[data-lightbox-image]', el).each(function lightboxOpenImage(i, link) {
      $(link).unbind("click", _constrainLink);
      $(link).bind("click", function lightboxImageRender(event) {
        event.preventDefault();
        var url = link.dataset.lightboxHref || link.href || link.src;
        var imageCheck = $.ajax({
          url: url,
          method: "HEAD"
        });
        imageCheck.done(function lightboxImageCheckDone(content, status, jq_xhr) {
          if (
            jq_xhr.status === 200 && 
            jq_xhr.getResponseHeader("content-type").substr(0, 5) === "image"
          ) {
            render('<div class="lightbox-image"><img src="' + url + '"/></div>');
          } else {
            location.href = url;
          }
        });
      });
    });
  }

  function reset() {
    _html(VuFind.translate('loading') + '...');
    _originalUrl = false;
    _currentUrl = false;
    _lightboxTitle = '';
    _modalParams = {};
  }
  function init() {
    _modal = $('#modal');
    _modalBody = _modal.find('.modal-body');
    _modal.on('hide.bs.modal', function lightboxHide() {
      if (VuFind.lightbox.refreshOnClose) {
        VuFind.refreshPage();
      }
      this.setAttribute('aria-hidden', true);
      _emit('VuFind.lightbox.closing');
    });
    _modal.on('hidden.bs.modal', function lightboxHidden() {
      VuFind.lightbox.reset();
      _emit('VuFind.lightbox.closed');
    });

    VuFind.modal = function modalShortcut(cmd) {
      if (cmd === 'show') {
        _modal.modal($.extend({ show: true }, _modalParams)).attr('aria-hidden', false);
      } else {
        _modal.modal(cmd);
      }
    };
    bind();
  }

  // Reveal
  return {
    // Properties
    refreshOnClose: refreshOnClose,

    // Methods
    ajax: ajax,
    alert: showAlert,
    bind: bind,
    close: close,
    flashMessage: flashMessage,
    reload: reload,
    render: render,
    // Reset
    reset: reset,
    // Init
    init: init
  };
});