CartoDB/cartodb20

View on GitHub
lib/assets/javascripts/builder/components/scroll/scroll-view.js

Summary

Maintainability
D
2 days
Test Coverage
var CoreView = require('backbone/core-view');
var Ps = require('perfect-scrollbar');
var template = require('./scroll.tpl');
var MutationObserver = window.MutationObserver;

module.exports = CoreView.extend({
  tagName: 'div',
  className: 'ScrollView',

  initialize: function (opts) {
    if (!opts.createContentView) throw new Error('A factory createContentView function is required');
    this.options = opts || {};
    this._type = opts.type || 'vertical'; // vertical or horizontal
    this._maxScroll = 0;
    this._bindedCheckShadows = this._checkShadows.bind(this);
  },

  render: function () {
    this.clearSubViews();
    this._html();

    var view = this.options.createContentView.call(this);
    this._contentContainer().append(view.render().el);
    this.addView(view);
    this._applyScroll();
    this._updateScrollWhenExist();
    return this;
  },

  _html: function () {
    this.$el.html(template({
      type: this._type
    }));

    (this._type === 'horizontal') && this.$el.addClass('ScrollView--horizontal');
  },

  _contentContainer: function () {
    return this.$('.js-content');
  },

  _wrapperContainer: function () {
    return this.$('.js-wrapper');
  },

  _updateScrollWhenExist: function () {
    // Phantom doesn't provide this api for window.
    if (!MutationObserver) return;

    // even with the changes in PS, we need to check when this element is added to the dom
    // in order to trigger manually an update.
    var element = document.body;
    var self = this._wrapperContainer().get(0);
    var onMutationObserver = function () {
      if (element.contains(self)) {
        Ps.update(self);
        observer.disconnect();
      }
    };

    var observer = new MutationObserver(onMutationObserver);
    onMutationObserver();

    var config = { subtree: true, childList: true };
    observer.observe(element, config);
  },

  _applyScroll: function () {
    Ps.initialize(this._wrapperContainer().get(0), {
      wheelSpeed: 2,
      wheelPropagation: true,
      stopPropagationOnClick: false,
      minScrollbarLength: 20,
      suppressScrollX: this._type === 'vertical',
      suppressScrollY: this._type === 'horizontal'
    });

    this._bindScroll();
  },

  _bindScroll: function () {
    this._wrapperContainer()
      .on('ps-dom-change', this._bindedCheckShadows)
      .on('ps-x-reach-start', this._bindedCheckShadows)
      .on('ps-x-reach-end', this._bindedCheckShadows)
      .on('ps-y-reach-start', this._bindedCheckShadows)
      .on('ps-y-reach-end', this._bindedCheckShadows)
      .on('ps-scroll-x', this._bindedCheckShadows)
      .on('ps-scroll-y', this._bindedCheckShadows);
  },

  _unbindScroll: function () {
    this._wrapperContainer()
      .off('ps-dom-change', this._bindedCheckShadows)
      .off('ps-x-reach-start', this._bindedCheckShadows)
      .off('ps-x-reach-end', this._bindedCheckShadows)
      .off('ps-y-reach-start', this._bindedCheckShadows)
      .off('ps-y-reach-end', this._bindedCheckShadows)
      .off('ps-scroll-x', this._bindedCheckShadows)
      .off('ps-scroll-y', this._bindedCheckShadows);
  },

  _checkShadows: function () {
    var currentPos;
    var max;
    var width;
    var height;
    var maxPos;

    if (this._type === 'horizontal') {
      currentPos = this._wrapperContainer().scrollLeft();
      max = this._wrapperContainer().get(0).scrollWidth;
      width = this._wrapperContainer().outerWidth();
      maxPos = max - width;

      this.$('> .js-leftShadow').toggleClass('is-visible', currentPos > 0);
      this.$('> .js-rightShadow').toggleClass('is-visible', currentPos < maxPos);
    } else {
      currentPos = this._wrapperContainer().scrollTop();
      max = this._wrapperContainer().get(0).scrollHeight;
      height = this._wrapperContainer().outerHeight();
      maxPos = max - height;

      // We use the direct descendant selector to avoid affect others nested view with scroll
      this.$('> .js-topShadow').toggleClass('is-visible', currentPos > 0);
      this.$('> .js-bottomShadow').toggleClass('is-visible', currentPos < maxPos);
    }
  },

  destroyScroll: function () {
    if (this._wrapperContainer().length) {
      this._unbindScroll();
      Ps.destroy(this._wrapperContainer().get(0));
    }
  },

  clean: function () {
    this.destroyScroll();
    CoreView.prototype.clean.apply(this, arguments);
  }
});