// custom scrollbars module jcf.addModule({ name:'customscroll', selector:'div.scrollable-area', defaultOptions: { alwaysPreventWheel: false, enableMouseWheel: true, captureFocus: false, handleNested: true, alwaysKeepScrollbars: false, autoDetectWidth: false, scrollbarOptions: {}, focusClass:'scrollable-focus', wrapperTag: 'div', autoDetectWidthClass: 'autodetect-width', noHorizontalBarClass:'noscroll-horizontal', noVerticalBarClass:'noscroll-vertical', innerWrapperClass:'scrollable-inner-wrapper', outerWrapperClass:'scrollable-area-wrapper', horizontalClass: 'hscrollable', verticalClass: 'vscrollable', bothClass: 'anyscrollable' }, replaceObject: function(){ this.initStructure(); this.refreshState(); this.addEvents(); }, initStructure: function(){ // set scroll type this.realElement.jcf = this; if(jcf.lib.hasClass(this.realElement, this.options.bothClass) || jcf.lib.hasClass(this.realElement, this.options.horizontalClass) && jcf.lib.hasClass(this.realElement, this.options.verticalClass)) { this.scrollType = 'both'; } else if(jcf.lib.hasClass(this.realElement, this.options.horizontalClass)) { this.scrollType = 'horizontal'; } else { this.scrollType = 'vertical'; } // autodetect horizontal width if(jcf.lib.hasClass(this.realElement,this.options.autoDetectWidthClass)) { this.options.autoDetectWidth = true; } // init dimensions and build structure this.realElement.style.position = 'relative'; this.realElement.style.overflow = 'hidden'; // build content wrapper and scrollbar(s) this.buildWrapper(); this.buildScrollbars(); }, buildWrapper: function() { this.outerWrapper = document.createElement(this.options.wrapperTag); this.outerWrapper.className = this.options.outerWrapperClass; this.realElement.parentNode.insertBefore(this.outerWrapper, this.realElement); this.outerWrapper.appendChild(this.realElement); // autosize content if single child if(this.options.autoDetectWidth && (this.scrollType === 'both' || this.scrollType === 'horizontal') && this.realElement.children.length === 1) { var tmpWidth = 0; this.realElement.style.width = '99999px'; tmpWidth = this.realElement.children[0].offsetWidth; this.realElement.style.width = ''; if(tmpWidth) { this.realElement.children[0].style.width = tmpWidth+'px'; } } }, buildScrollbars: function() { if(this.scrollType === 'horizontal' || this.scrollType === 'both') { this.hScrollBar = new jcf.plugins.scrollbar(jcf.lib.extend(this.options.scrollbarOptions,{ vertical: false, spawnClass: this, holder: this.outerWrapper, range: this.realElement.scrollWidth - this.realElement.offsetWidth, size: this.realElement.offsetWidth, onScroll: jcf.lib.bind(function(v) { this.realElement.scrollLeft = v; },this) })); } if(this.scrollType === 'vertical' || this.scrollType === 'both') { this.vScrollBar = new jcf.plugins.scrollbar(jcf.lib.extend(this.options.scrollbarOptions,{ vertical: true, spawnClass: this, holder: this.outerWrapper, range: this.realElement.scrollHeight - this.realElement.offsetHeight, size: this.realElement.offsetHeight, onScroll: jcf.lib.bind(function(v) { this.realElement.scrollTop = v; },this) })); } this.outerWrapper.style.width = this.realElement.offsetWidth + 'px'; this.outerWrapper.style.height = this.realElement.offsetHeight + 'px'; this.resizeScrollContent(); }, resizeScrollContent: function() { var diffWidth = this.realElement.offsetWidth - jcf.lib.getInnerWidth(this.realElement); var diffHeight = this.realElement.offsetHeight - jcf.lib.getInnerHeight(this.realElement); this.realElement.style.width = Math.max(0, this.outerWrapper.offsetWidth - diffWidth - (this.vScrollBar ? this.vScrollBar.getScrollBarSize() : 0)) + 'px'; this.realElement.style.height = Math.max(0, this.outerWrapper.offsetHeight - diffHeight - (this.hScrollBar ? this.hScrollBar.getScrollBarSize() : 0)) + 'px'; }, addEvents: function() { // enable mouse wheel handling if(!jcf.isTouchDevice && this.options.enableMouseWheel) { jcf.lib.event.add(this.outerWrapper, 'mousewheel', this.onMouseWheel, this); } // add touch scroll on block body if(jcf.isTouchDevice || navigator.msPointerEnabled) { this.outerWrapper.style.msTouchAction = 'none'; jcf.lib.event.add(this.realElement, jcf.eventPress, this.onScrollablePress, this); } // handle nested scrollbars if(this.options.handleNested) { var el = this.realElement, name = this.name; while(el.parentNode) { if(el.parentNode.jcf && el.parentNode.jcf.name == name) { el.parentNode.jcf.refreshState(); } el = el.parentNode; } } }, onMouseWheel: function(e) { if(this.scrollType === 'vertical' || this.scrollType === 'both') { return this.vScrollBar.doScrollWheelStep(e.mWheelDelta) === false ? false : !this.options.alwaysPreventWheel; } else { return this.hScrollBar.doScrollWheelStep(e.mWheelDelta) === false ? false : !this.options.alwaysPreventWheel; } }, onScrollablePress: function(e) { if(e.pointerType !== e.MSPOINTER_TYPE_TOUCH) return; this.preventFlag = true; this.origWindowScrollTop = jcf.lib.getScrollTop(); this.origWindowScrollLeft = jcf.lib.getScrollLeft(); this.scrollableOffset = jcf.lib.getOffset(this.realElement); if(this.hScrollBar) { this.scrollableTouchX = (jcf.isTouchDevice ? e.changedTouches[0] : e).pageX; this.origValueX = this.hScrollBar.getScrollValue(); } if(this.vScrollBar) { this.scrollableTouchY = (jcf.isTouchDevice ? e.changedTouches[0] : e).pageY; this.origValueY = this.vScrollBar.getScrollValue(); } jcf.lib.event.add(this.realElement, jcf.eventMove, this.onScrollableMove, this); jcf.lib.event.add(this.realElement, jcf.eventRelease, this.onScrollableRelease, this); }, onScrollableMove: function(e) { if(this.vScrollBar) { var difY = (jcf.isTouchDevice ? e.changedTouches[0] : e).pageY - this.scrollableTouchY; var valY = this.origValueY-difY; this.vScrollBar.scrollTo(valY); if(valY < 0 || valY > this.vScrollBar.options.range) { this.preventFlag = false; } } if(this.hScrollBar) { var difX = (jcf.isTouchDevice ? e.changedTouches[0] : e).pageX - this.scrollableTouchX; var valX = this.origValueX-difX; this.hScrollBar.scrollTo(valX); if(valX < 0 || valX > this.hScrollBar.options.range) { this.preventFlag = false; } } if(this.preventFlag) { e.preventDefault(); } }, onScrollableRelease: function() { jcf.lib.event.remove(this.realElement, jcf.eventMove, this.onScrollableMove); jcf.lib.event.remove(this.realElement, jcf.eventRelease, this.onScrollableRelease); }, refreshState: function() { if(this.options.alwaysKeepScrollbars) { if(this.hScrollBar) this.hScrollBar.scrollBar.style.display = 'block'; if(this.vScrollBar) this.vScrollBar.scrollBar.style.display = 'block'; } else { if(this.hScrollBar) { if(this.getScrollRange(false)) { this.hScrollBar.scrollBar.style.display = 'block'; this.resizeScrollContent(); this.hScrollBar.setRange(this.getScrollRange(false)); } else { this.hScrollBar.scrollBar.style.display = 'none'; this.realElement.style.width = this.outerWrapper.style.width; } jcf.lib.toggleClass(this.outerWrapper, this.options.noHorizontalBarClass, this.hScrollBar.options.range === 0); } if(this.vScrollBar) { if(this.getScrollRange(true) > 0) { this.vScrollBar.scrollBar.style.display = 'block'; this.resizeScrollContent(); this.vScrollBar.setRange(this.getScrollRange(true)); } else { this.vScrollBar.scrollBar.style.display = 'none'; this.realElement.style.width = this.outerWrapper.style.width; } jcf.lib.toggleClass(this.outerWrapper, this.options.noVerticalBarClass, this.vScrollBar.options.range === 0); } } if(this.vScrollBar) { this.vScrollBar.setRange(this.realElement.scrollHeight - this.realElement.offsetHeight); this.vScrollBar.setSize(this.realElement.offsetHeight); this.vScrollBar.scrollTo(this.realElement.scrollTop); } if(this.hScrollBar) { this.hScrollBar.setRange(this.realElement.scrollWidth - this.realElement.offsetWidth); this.hScrollBar.setSize(this.realElement.offsetWidth); this.hScrollBar.scrollTo(this.realElement.scrollLeft); } }, getScrollRange: function(isVertical) { if(isVertical) { return this.realElement.scrollHeight - this.realElement.offsetHeight; } else { return this.realElement.scrollWidth - this.realElement.offsetWidth; } }, getCurrentRange: function(scrollInstance) { return this.getScrollRange(scrollInstance.isVertical); }, onCreateModule: function(){ if(jcf.modules.select) { this.extendSelect(); } if(jcf.modules.selectmultiple) { this.extendSelectMultiple(); } if(jcf.modules.textarea) { this.extendTextarea(); } }, onModuleAdded: function(module){ if(module.prototype.name == 'select') { this.extendSelect(); } if(module.prototype.name == 'selectmultiple') { this.extendSelectMultiple(); } if(module.prototype.name == 'textarea') { this.extendTextarea(); } }, extendSelect: function() { // add scrollable if needed on control ready jcf.modules.select.prototype.onControlReady = function(obj){ if(obj.selectList.scrollHeight > obj.selectList.offsetHeight) { obj.jcfScrollable = new jcf.modules.customscroll({ alwaysPreventWheel: true, replaces:obj.selectList }); } } // update scroll function var orig = jcf.modules.select.prototype.scrollToItem; jcf.modules.select.prototype.scrollToItem = function(){ orig.apply(this); if(this.jcfScrollable) { this.jcfScrollable.refreshState(); } } }, extendTextarea: function() { // add scrollable if needed on control ready jcf.modules.textarea.prototype.onControlReady = function(obj){ obj.jcfScrollable = new jcf.modules.customscroll({ alwaysKeepScrollbars: true, alwaysPreventWheel: true, replaces: obj.realElement }); } // update scroll function var orig = jcf.modules.textarea.prototype.refreshState; jcf.modules.textarea.prototype.refreshState = function(){ orig.apply(this); if(this.jcfScrollable) { this.jcfScrollable.refreshState(); } } }, extendSelectMultiple: function(){ // add scrollable if needed on control ready jcf.modules.selectmultiple.prototype.onControlReady = function(obj){ //if(obj.optionsHolder.scrollHeight > obj.optionsHolder.offsetHeight) { obj.jcfScrollable = new jcf.modules.customscroll({ alwaysPreventWheel: true, replaces:obj.optionsHolder }); //} } // update scroll function var orig = jcf.modules.selectmultiple.prototype.scrollToItem; jcf.modules.selectmultiple.prototype.scrollToItem = function(){ orig.apply(this); if(this.jcfScrollable) { this.jcfScrollable.refreshState(); } } // update scroll size? var orig2 = jcf.modules.selectmultiple.prototype.rebuildOptions; jcf.modules.selectmultiple.prototype.rebuildOptions = function(){ orig2.apply(this); if(this.jcfScrollable) { this.jcfScrollable.refreshState(); } } } }); // scrollbar plugin jcf.addPlugin({ name: 'scrollbar', defaultOptions: { size: 0, range: 0, moveStep: 6, moveDistance: 50, moveInterval: 10, trackHoldDelay: 900, holder: null, vertical: true, scrollTag: 'div', onScroll: function(){}, onScrollEnd: function(){}, onScrollStart: function(){}, disabledClass: 'btn-disabled', VscrollBarClass:'vscrollbar', VscrollStructure: '