142 lines
4.3 KiB
JavaScript
142 lines
4.3 KiB
JavaScript
|
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);
|
||
|
}
|
||
|
});
|