/* * (c) Copyright Ascensio System SIA 2010-2015 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, * EU, LV-1021. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ (function (factory) { if (typeof define === "function" && define.amd) { define(["jquery"], factory); } else { if (typeof exports === "object") { factory(require("jquery")); } else { factory(jQuery); } } } (function ($) { var defaultSettings = { wheelSpeed: 10, wheelPropagation: false, minScrollbarLength: null, useBothWheelAxes: false, useKeyboard: true, suppressScrollX: false, suppressScrollY: false, scrollXMarginOffset: 0, scrollYMarginOffset: 0, includePadding: false, includeMargin: true }; var getEventClassName = (function () { var incrementingId = 0; return function () { var id = incrementingId; incrementingId += 1; return ".perfect-scrollbar-" + id; }; } ()); $.fn.perfectScrollbar = function (suppliedSettings, option) { return this.each(function () { var settings = $.extend(true, {}, defaultSettings), $this = $(this); if (typeof suppliedSettings === "object") { $.extend(true, settings, suppliedSettings); } else { option = suppliedSettings; } if (option === "update") { if ($this.data("perfect-scrollbar-update")) { $this.data("perfect-scrollbar-update")(); } return $this; } else { if (option === "destroy") { if ($this.data("perfect-scrollbar-destroy")) { $this.data("perfect-scrollbar-destroy")(); } return $this; } } if ($this.data("perfect-scrollbar")) { return $this.data("perfect-scrollbar"); } $this.addClass("ps-container"); var $scrollbarXRail = $("
").appendTo($this), $scrollbarYRail = $("").appendTo($this), $scrollbarX = $("").appendTo($scrollbarXRail), $scrollbarY = $("").appendTo($scrollbarYRail), scrollbarXActive, scrollbarYActive, containerWidth, containerHeight, contentWidth, contentHeight, scrollbarXWidth, scrollbarXLeft, scrollbarXBottom = parseInt($scrollbarXRail.css("bottom"), 10), scrollbarYHeight, scrollbarYTop, scrollbarYRight = parseInt($scrollbarYRail.css("right"), 10), scrollbarYRailHeight, eventClassName = getEventClassName(); var updateContentScrollTop = function (currentTop, deltaY) { var newTop = currentTop + deltaY, maxTop = scrollbarYRailHeight - scrollbarYHeight; if (newTop < 0) { scrollbarYTop = 0; } else { if (newTop > maxTop) { scrollbarYTop = maxTop; } else { scrollbarYTop = newTop; } } var scrollTop = parseInt(scrollbarYTop * (contentHeight - containerHeight) / (scrollbarYRailHeight - scrollbarYHeight), 10); $this.scrollTop(scrollTop); $scrollbarXRail.css({ bottom: scrollbarXBottom - scrollTop }); }; var updateContentScrollLeft = function (currentLeft, deltaX) { var newLeft = currentLeft + deltaX, maxLeft = containerWidth - scrollbarXWidth; if (newLeft < 0) { scrollbarXLeft = 0; } else { if (newLeft > maxLeft) { scrollbarXLeft = maxLeft; } else { scrollbarXLeft = newLeft; } } var scrollLeft = parseInt(scrollbarXLeft * (contentWidth - containerWidth) / (containerWidth - scrollbarXWidth), 10); $this.scrollLeft(scrollLeft); $scrollbarYRail.css({ right: scrollbarYRight - scrollLeft }); }; var getSettingsAdjustedThumbSize = function (thumbSize) { if (settings.minScrollbarLength) { thumbSize = Math.max(thumbSize, settings.minScrollbarLength); } return thumbSize; }; var updateScrollbarCss = function () { $scrollbarXRail.css({ left: $this.scrollLeft(), bottom: scrollbarXBottom - $this.scrollTop(), width: containerWidth, display: scrollbarXActive ? "inherit" : "none" }); if ($scrollbarYRail.hasClass("in-scrolling")) { $scrollbarYRail.css({ right: scrollbarYRight - $this.scrollLeft(), height: scrollbarYRailHeight, display: scrollbarYActive ? "inherit" : "none" }); } else { $scrollbarYRail.css({ top: $this.scrollTop(), right: scrollbarYRight - $this.scrollLeft(), height: scrollbarYRailHeight, display: scrollbarYActive ? "inherit" : "none" }); } $scrollbarX.css({ left: scrollbarXLeft, width: scrollbarXWidth }); $scrollbarY.css({ top: scrollbarYTop, height: scrollbarYHeight }); }; var updateBarSizeAndPosition = function () { containerWidth = settings.includePadding ? $this.innerWidth() : $this.width(); containerHeight = settings.includePadding ? $this.innerHeight() : $this.height(); scrollbarYRailHeight = containerHeight - (settings.includeMargin ? (parseInt($scrollbarYRail.css("margin-top")) + parseInt($scrollbarYRail.css("margin-bottom"))) : 0); contentWidth = $this.prop("scrollWidth"); contentHeight = $this.prop("scrollHeight"); if (!settings.suppressScrollX && containerWidth + settings.scrollXMarginOffset < contentWidth) { scrollbarXActive = true; scrollbarXWidth = getSettingsAdjustedThumbSize(parseInt(containerWidth * containerWidth / contentWidth, 10)); scrollbarXLeft = parseInt($this.scrollLeft() * (containerWidth - scrollbarXWidth) / (contentWidth - containerWidth), 10); } else { scrollbarXActive = false; scrollbarXWidth = 0; scrollbarXLeft = 0; $this.scrollLeft(0); } if (!settings.suppressScrollY && containerHeight + settings.scrollYMarginOffset < contentHeight) { scrollbarYActive = true; scrollbarYHeight = getSettingsAdjustedThumbSize(parseInt(scrollbarYRailHeight * containerHeight / contentHeight, 10)); scrollbarYTop = parseInt($this.scrollTop() * (scrollbarYRailHeight - scrollbarYHeight) / (contentHeight - containerHeight), 10); } else { scrollbarYActive = false; scrollbarYHeight = 0; scrollbarYTop = 0; $this.scrollTop(0); } if (scrollbarYTop >= scrollbarYRailHeight - scrollbarYHeight) { scrollbarYTop = scrollbarYRailHeight - scrollbarYHeight; } if (scrollbarXLeft >= containerWidth - scrollbarXWidth) { scrollbarXLeft = containerWidth - scrollbarXWidth; } updateScrollbarCss(); if (settings.onChange) { settings.onChange(this); } }; var bindMouseScrollXHandler = function () { var currentLeft, currentPageX; $scrollbarX.bind("mousedown" + eventClassName, function (e) { currentPageX = e.pageX; currentLeft = $scrollbarX.position().left; $scrollbarXRail.addClass("in-scrolling"); e.stopPropagation(); e.preventDefault(); }); $(document).bind("mousemove" + eventClassName, function (e) { if ($scrollbarXRail.hasClass("in-scrolling")) { updateContentScrollLeft(currentLeft, e.pageX - currentPageX); e.stopPropagation(); e.preventDefault(); } }); $(document).bind("mouseup" + eventClassName, function (e) { if ($scrollbarXRail.hasClass("in-scrolling")) { $scrollbarXRail.removeClass("in-scrolling"); } }); currentLeft = currentPageX = null; }; var bindMouseScrollYHandler = function () { var currentTop, currentPageY; $scrollbarY.bind("mousedown" + eventClassName, function (e) { currentPageY = e.pageY; currentTop = $scrollbarY.position().top; $scrollbarYRail.addClass("in-scrolling"); var margin = parseInt($scrollbarYRail.css("margin-top")); var rect = $scrollbarYRail[0].getBoundingClientRect(); $scrollbarYRail.css({ position: "fixed", left: rect.left, top: rect.top - margin }); e.stopPropagation(); e.preventDefault(); }); $(document).bind("mousemove" + eventClassName, function (e) { if ($scrollbarYRail.hasClass("in-scrolling")) { updateContentScrollTop(currentTop, e.pageY - currentPageY); e.stopPropagation(); e.preventDefault(); } }); $(document).bind("mouseup" + eventClassName, function (e) { if ($scrollbarYRail.hasClass("in-scrolling")) { $scrollbarYRail.removeClass("in-scrolling"); $scrollbarYRail.css({ position: "", left: "", top: "" }); updateScrollbarCss(); } }); currentTop = currentPageY = null; }; var shouldPreventDefault = function (deltaX, deltaY) { var scrollTop = $this.scrollTop(); if (deltaX === 0) { if (!scrollbarYActive) { return false; } if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= contentHeight - containerHeight && deltaY < 0)) { return !settings.wheelPropagation; } } var scrollLeft = $this.scrollLeft(); if (deltaY === 0) { if (!scrollbarXActive) { return false; } if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= contentWidth - containerWidth && deltaX > 0)) { return !settings.wheelPropagation; } } return true; }; var bindMouseWheelHandler = function () { settings.wheelSpeed /= 10; var shouldPrevent = false; $this.bind("mousewheel" + eventClassName, function (e, deprecatedDelta, deprecatedDeltaX, deprecatedDeltaY) { var deltaX = e.deltaX * e.deltaFactor || deprecatedDeltaX, deltaY = e.deltaY * e.deltaFactor || deprecatedDeltaY; if (e && e.target && (e.target.type === "textarea" || e.target.type === "input")) { e.stopImmediatePropagation(); e.preventDefault(); var scroll = $(e.target).scrollTop(), wheelDeltaY = 0; if (e.originalEvent) { if (e.originalEvent.wheelDelta) { wheelDeltaY = e.originalEvent.wheelDelta / -40; } if (e.originalEvent.deltaY) { wheelDeltaY = e.originalEvent.deltaY; } if (e.originalEvent.detail) { wheelDeltaY = e.originalEvent.detail; } } $(e.target).scrollTop(scroll - wheelDeltaY); return; } shouldPrevent = false; if (!settings.useBothWheelAxes) { $this.scrollTop($this.scrollTop() - (deltaY * settings.wheelSpeed)); $this.scrollLeft($this.scrollLeft() + (deltaX * settings.wheelSpeed)); } else { if (scrollbarYActive && !scrollbarXActive) { if (deltaY) { $this.scrollTop($this.scrollTop() - (deltaY * settings.wheelSpeed)); } else { $this.scrollTop($this.scrollTop() + (deltaX * settings.wheelSpeed)); } shouldPrevent = true; } else { if (scrollbarXActive && !scrollbarYActive) { if (deltaX) { $this.scrollLeft($this.scrollLeft() + (deltaX * settings.wheelSpeed)); } else { $this.scrollLeft($this.scrollLeft() - (deltaY * settings.wheelSpeed)); } shouldPrevent = true; } } } updateBarSizeAndPosition(); shouldPrevent = (shouldPrevent || shouldPreventDefault(deltaX, deltaY)); if (shouldPrevent) { e.stopPropagation(); e.preventDefault(); } }); $this.bind("MozMousePixelScroll" + eventClassName, function (e) { if (shouldPrevent) { e.preventDefault(); } }); }; var bindKeyboardHandler = function () { var hovered = false; $this.bind("mouseenter" + eventClassName, function (e) { hovered = true; }); $this.bind("mouseleave" + eventClassName, function (e) { hovered = false; }); var shouldPrevent = false; $(document).bind("keydown" + eventClassName, function (e) { if (!hovered || $(document.activeElement).is(":input,[contenteditable]")) { return; } var deltaX = 0, deltaY = 0; switch (e.which) { case 37: deltaX = -30; break; case 38: deltaY = 30; break; case 39: deltaX = 30; break; case 40: deltaY = -30; break; case 33: deltaY = 90; break; case 32: case 34: deltaY = -90; break; case 35: deltaY = -containerHeight; break; case 36: deltaY = containerHeight; break; default: return; } $this.scrollTop($this.scrollTop() - deltaY); $this.scrollLeft($this.scrollLeft() + deltaX); shouldPrevent = shouldPreventDefault(deltaX, deltaY); if (shouldPrevent) { e.preventDefault(); } }); }; var bindRailClickHandler = function () { var stopPropagation = function (e) { e.stopPropagation(); }; $scrollbarY.bind("click" + eventClassName, stopPropagation); $scrollbarYRail.bind("click" + eventClassName, function (e) { var halfOfScrollbarLength = parseInt(scrollbarYHeight / 2, 10), positionTop = e.pageY - $scrollbarYRail.offset().top - halfOfScrollbarLength, maxPositionTop = scrollbarYRailHeight - scrollbarYHeight, positionRatio = positionTop / maxPositionTop; if (positionRatio < 0) { positionRatio = 0; } else { if (positionRatio > 1) { positionRatio = 1; } } $this.scrollTop((contentHeight - containerHeight) * positionRatio); }); $scrollbarX.bind("click" + eventClassName, stopPropagation); $scrollbarXRail.bind("click" + eventClassName, function (e) { var halfOfScrollbarLength = parseInt(scrollbarXWidth / 2, 10), positionLeft = e.pageX - $scrollbarXRail.offset().left - halfOfScrollbarLength, maxPositionLeft = containerWidth - scrollbarXWidth, positionRatio = positionLeft / maxPositionLeft; if (positionRatio < 0) { positionRatio = 0; } else { if (positionRatio > 1) { positionRatio = 1; } } $this.scrollLeft((contentWidth - containerWidth) * positionRatio); }); }; var bindMobileTouchHandler = function () { var applyTouchMove = function (differenceX, differenceY) { $this.scrollTop($this.scrollTop() - differenceY); $this.scrollLeft($this.scrollLeft() - differenceX); updateBarSizeAndPosition(); }; var startCoords = {}, startTime = 0, speed = {}, breakingProcess = null, inGlobalTouch = false; $(window).bind("touchstart" + eventClassName, function (e) { inGlobalTouch = true; }); $(window).bind("touchend" + eventClassName, function (e) { inGlobalTouch = false; }); $this.bind("touchstart" + eventClassName, function (e) { var touch = e.originalEvent.targetTouches[0]; startCoords.pageX = touch.pageX; startCoords.pageY = touch.pageY; startTime = (new Date()).getTime(); if (breakingProcess !== null) { clearInterval(breakingProcess); } e.stopPropagation(); }); $this.bind("touchmove" + eventClassName, function (e) { if (!inGlobalTouch && e.originalEvent.targetTouches.length === 1) { var touch = e.originalEvent.targetTouches[0]; var currentCoords = {}; currentCoords.pageX = touch.pageX; currentCoords.pageY = touch.pageY; var differenceX = currentCoords.pageX - startCoords.pageX, differenceY = currentCoords.pageY - startCoords.pageY; applyTouchMove(differenceX, differenceY); startCoords = currentCoords; var currentTime = (new Date()).getTime(); var timeGap = currentTime - startTime; if (timeGap > 0) { speed.x = differenceX / timeGap; speed.y = differenceY / timeGap; startTime = currentTime; } e.preventDefault(); } }); $this.bind("touchend" + eventClassName, function (e) { clearInterval(breakingProcess); breakingProcess = setInterval(function () { if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) { clearInterval(breakingProcess); return; } applyTouchMove(speed.x * 30, speed.y * 30); speed.x *= 0.8; speed.y *= 0.8; }, 10); }); }; var bindScrollHandler = function () { $this.bind("scroll" + eventClassName, function (e) { updateBarSizeAndPosition(); }); }; var destroy = function () { $this.unbind(eventClassName); $(window).unbind(eventClassName); $(document).unbind(eventClassName); $this.data("perfect-scrollbar", null); $this.data("perfect-scrollbar-update", null); $this.data("perfect-scrollbar-destroy", null); $scrollbarX.remove(); $scrollbarY.remove(); $scrollbarXRail.remove(); $scrollbarYRail.remove(); $scrollbarX = $scrollbarY = containerWidth = containerHeight = contentWidth = contentHeight = scrollbarXWidth = scrollbarXLeft = scrollbarXBottom = scrollbarYHeight = scrollbarYTop = scrollbarYRight = null; }; var ieSupport = function (version) { $this.addClass("ie").addClass("ie" + version); var bindHoverHandlers = function () { var mouseenter = function () { $(this).addClass("hover"); }; var mouseleave = function () { $(this).removeClass("hover"); }; $this.bind("mouseenter" + eventClassName, mouseenter).bind("mouseleave" + eventClassName, mouseleave); $scrollbarXRail.bind("mouseenter" + eventClassName, mouseenter).bind("mouseleave" + eventClassName, mouseleave); $scrollbarYRail.bind("mouseenter" + eventClassName, mouseenter).bind("mouseleave" + eventClassName, mouseleave); $scrollbarX.bind("mouseenter" + eventClassName, mouseenter).bind("mouseleave" + eventClassName, mouseleave); $scrollbarY.bind("mouseenter" + eventClassName, mouseenter).bind("mouseleave" + eventClassName, mouseleave); }; var fixIe6ScrollbarPosition = function () { updateScrollbarCss = function () { $scrollbarX.css({ left: scrollbarXLeft + $this.scrollLeft(), bottom: scrollbarXBottom, width: scrollbarXWidth }); $scrollbarY.css({ top: scrollbarYTop + $this.scrollTop(), right: scrollbarYRight, height: scrollbarYHeight }); $scrollbarX.hide().show(); $scrollbarY.hide().show(); }; }; if (version === 6) { bindHoverHandlers(); fixIe6ScrollbarPosition(); } }; var supportsTouch = (("ontouchstart" in window) || window.DocumentTouch && document instanceof window.DocumentTouch); var initialize = function () { var ieMatch = navigator.userAgent.toLowerCase().match(/(msie) ([\w.]+)/); if (ieMatch && ieMatch[1] === "msie") { ieSupport(parseInt(ieMatch[2], 10)); } updateBarSizeAndPosition(); bindScrollHandler(); bindMouseScrollXHandler(); bindMouseScrollYHandler(); bindRailClickHandler(); if (supportsTouch) { bindMobileTouchHandler(); } if ($this.mousewheel) { bindMouseWheelHandler(); } if (settings.useKeyboard) { bindKeyboardHandler(); } $this.data("perfect-scrollbar", $this); $this.data("perfect-scrollbar-update", updateBarSizeAndPosition); $this.data("perfect-scrollbar-destroy", destroy); }; initialize(); return $this; }); }; }));