ux-doubleScroll.js

! ux-angularjs-datagrid v.1.4.11 (c) 2016, Obogo https://github.com/obogo/ux-angularjs-datagrid License: MIT.

(function (exports, global) {
if (typeof define === "function" && define.amd) {
  define(exports);
} else if (typeof module !== "undefined" && module.exports) {
  module.exports = exports;
} else {
  global.ux = exports;
}

exports.datagrid.events.DOUBLE_SCROLL_SCROLL_TO_TOP = "datagrid:doubleScrollScrollToTop";

exports.datagrid.events.DOUBLE_SCROLL_SCROLL_TO_BOTTOM = "datagrid:doubleScrollScrollToBottom";


Allow a header to scroll out before scrolling the content. Nested scrollers.

angular.module("ux").directive("uxDoubleScroll", [ "$window", function($window) {
    return {
        link: function(scope, element, attr) {
            var result = exports.logWrapper("doubleScroll", {}, "red", function() {
                exports.util.apply(scope.$emit, scope, arguments);
            }), el = element[0], selector = scope.$eval(attr.uxDoubleScroll), target, targetOffset = scope.$eval(attr.targetOffset) || 0, dynamicOffset = scope.$eval(attr.dynamicOffset), targetPadding = scope.$eval(attr.targetPadding), grid, // reference to the datagrid instance
            myScroll, // iScroll for the doubleScroll.
            scrollModel, // the grid scrollModel.
            enabled, unwatchRender, unwatchOffset, lastOffsetTop = 0, lastOffsetHeight, intv, lastY = 0, momentum = 0, gridScrollIntv, useIScroll = detectIScroll();
            function detectIScroll() {
                var addons = element[0].querySelectorAll(".datagrid")[0].attributes.getNamedItem("data-addons");
                if (addons && addons.value.match(/iScrollAddon/)) {
                    return true;
                }
                return false;
            }
            function setup() {
                element[0].style.overflowY = "auto";
                element[0].style.overflowX = "hidden";
                updateScrollModel();
                updateTarget();
                scope.doubleScroll = result;
                scope.$on(exports.datagrid.events.RESIZE, checkOffsetChange);
                if (useIScroll) {
                    setupIScroll();
                } else {
                    setupNativeScroll();
                }
                unwatchOffset = scope.$watch(onWatchOffset);
            }
            function onWatchOffset() {

wait until a render is all done. Then check the heights.

                clearTimeout(intv);
                intv = setTimeout(function() {
                    if (checkOffsetChange()) {
                        onWatchOffset();
                    }
                }, 100);
            }
            function setupNativeScroll() {
                element[0].addEventListener("scroll", onScroll, true);
                if (target && grid.isReady()) {

if this exists the ready event should have already been fired.

                    checkOffsetChange();
                    onScroll(null);
                } else {
                    unwatchRender = scope.$on(exports.datagrid.events.ON_LISTENERS_READY, function() {
                        unwatchRender();
                        updateTarget();
                        checkOffsetChange();
                        onScroll(null);
                        enable();
                        if (grid.scrollHistory && grid.scrollHistory.getCurrentScroll()) {
                            disable();
                        }
                        scope.$on(exports.datagrid.events.ON_RENDER_AFTER_DATA_CHANGE, function() {
                            if (enabled && grid.getContentHeight() && grid.getContentHeight() < grid.getViewportHeight()) {
                                grid.scrollModel.removeTouchEvents();
                            }
                        });
                    });
                }
                scope.$on(exports.datagrid.events.ON_SCROLL_STOP, function() {
                    updateScrollModel();
                    clearInterval(gridScrollIntv);
                    if (grid.values.scroll <= 0) {
                        gridScrollIntv = setInterval(function() {
                            if (element[0].scrollTop > .1) {
                                element[0].scrollTop *= .1;
                            } else {
                                element[0].scrollTop = 0;
                                clearInterval(gridScrollIntv);
                            }
                        }, 10);
                    } else {
                        element[0].scrollTop = element[0].scrollHeight - element[0].offsetHeight;
                    }
                });
            }
            function setupIScroll() {
                myScroll = new IScroll(element[0], {
                    bounce: false,
                    mouseWheel: true,
                    bindToWrapper: true,
                    click: true
                });
                myScroll.on("scrollStart", function() {
                    if (enabled) {
                        checkOffsetChange();
                        updateLastScroll(scrollModel.iScroll, myScroll);
                    }
                });
                myScroll.on("scrollEnd", function() {
                    var sm = scrollModel || updateScrollModel();

if we are at the end. We need to enable to grid scroller.

                    if (myScroll.y <= myScroll.maxScrollY) {
                        disable();
                    }
                });
                scope.$on(exports.datagrid.events.ON_SCROLL_START, function() {
                    var sm = scrollModel || updateScrollModel();
                    if (sm.iScroll) {
                        sm.iScroll.options.bounce = false;
                    }
                    if (!enabled && sm.iScroll.y >= 0) {
                        enable();
                    } else if (enabled && sm.iScroll.enabled) {
                        sm.iScroll.disable();
                    }
                });
                scope.$on(exports.datagrid.events.ON_SCROLL_STOP, function() {
                    if (!enabled && scrollModel.iScroll.y >= 0) {
                        enable();
                    }
                });
                scope.$on(exports.datagrid.events.AFTER_SCROLL_HISTORY_INIT_SCROLL, function() {
                    if (scrollModel.scrollHistory && scrollModel.scrollHistory.getCurrentScroll()) {
                        myScroll.enable();
                        myScroll.scrollTo(0, myScroll.maxScrollY);
                    }
                });
                unwatchRender = scope.$on(exports.datagrid.events.ON_LISTENERS_READY, function() {

it needs to start off with the target disabled.

                    unwatchRender();
                    updateScrollModel();
                    updateTarget();
                    checkOffsetChange();
                    unwatchRender = scope.$on(exports.datagrid.events.ON_AFTER_RENDER, function() {
                        var sm = scrollModel || updateScrollModel();
                        unwatchRender();
                        checkOffsetChange();
                        enable();
                        if (grid.scrollHistory && grid.scrollHistory.getCurrentScroll()) {
                            disable();
                        }
                    });
                });
                function updateLastScroll(myIScroll, otherIScroll) {
                    lastY = myIScroll.y;
                    momentum = 0;
                    clearInterval(gridScrollIntv);
                    gridScrollIntv = setInterval(function() {
                        var style = window.getComputedStyle(element[0].children[0]), y;
                        if (!style) {
                            clearInterval(gridScrollIntv);
                        } else {
                            y = parseInt(style.webkitTransform.match(/(\-?\d+)\)$/)[1], 10);
                            if (y !== lastY) {
                                momentum = y - lastY;
                                lastY = y;
                                if (y === otherIScroll.maxScrollY) {
                                    clearInterval(gridScrollIntv);
                                    gridScrollIntv = setTimeout(function() {
                                        clearTimeout(gridScrollIntv);
                                        disable();
                                        myIScroll.scrollBy(0, momentum * 5, 500);
                                    }, 0);
                                }
                            }
                        }
                    }, 10);
                }
            }
            function enable() {
                result.log("enable doubleScroll disable scroll");
                if (useIScroll && !enabled) {
                    if (scrollModel && scrollModel.iScroll && (!grid.scrollHistory || !grid.scrollHistory.getCurrentScroll())) {
                        result.log("  scroll grid to 0");
                        scrollModel.iScroll.scrollTo(0, 0);
                        scrollModel.iScroll.disable();
                    }
                    myScroll.enable();
                    if (grid.getContentHeight() > grid.getViewportHeight()) {
                        myScroll.scrollTo(0, 0);
                    }
                } else if (!enabled) {
                    element[0].scrollTop = 0;
                    target.scrollTop = 0;
                }
                enabled = true;
            }
            function disable() {
                result.log("disable doubleScroll enable scroll");
                if (useIScroll && enabled) {
                    myScroll.scrollTo(0, myScroll.maxScrollY);
                    myScroll.disable();
                    scrollModel.iScroll.enable();
                    scrollModel.iScroll.scrollBy(0, -1);
                } else if (!useIScroll) {
                    element[0].scrollTop = element[0].scrollHeight - element[0].offsetHeight;
                }
                enabled = false;
            }
            function updateTarget() {
                if (!target) {
                    target = element[0].querySelector(selector);
                }
            }
            function updateScrollModel() {
                var s = scope.$$childHead;
                while (s) {
                    if (s.datagrid) {
                        grid = s.datagrid;
                        scrollModel = s.datagrid && s.datagrid.scrollModel;
                        return scrollModel;
                    }
                    s = s.$$nextSibling;
                }
                return scrollModel;
            }
            function onScroll(event) {
                result.log("onScroll");
                updateTarget();
                if (target) {
                    if (el.scrollTop + el.offsetHeight < el.scrollHeight) {
                        if (target.style.overflowY !== "hidden") {
                            result.log("  target.overflowY = hidden");
                            target.style.overflowY = "hidden";
                        }
                    } else if (target.style.overflowY !== "auto") {
                        result.log("  target.overflowY = auto");
                        target.style.overflowY = "auto";
                    }
                } else {
                    throw new Error(selector ? 'selector "' + selector + '" did not select any objects' : "double scroll requires a selector.");
                }
            }
            function checkOffsetChange() {
                var offsetTop = calculateOffsetTop(), offsetHeight = element[0].offsetHeight;
                if (lastOffsetTop !== offsetTop || lastOffsetHeight !== offsetHeight) {
                    if (onSizeChange(offsetTop)) {
                        lastOffsetTop = offsetTop;
                        lastOffsetHeight = offsetHeight;
                        return true;
                    }
                } else if (!enabled && target && target.children[0] && target.children[0].offsetHeight + targetOffset < offsetHeight) {
                    enable();
                }
                return false;
            }
            function calculateOffsetTop() {
                var cpStyle, paddingTop, paddingBottom, content = element.children(), children = content.children(), i = 0, len = children.length, offsetTop = 0;
                while (i < len) {
                    if (children[i] === target || children[i].contains(target)) {
                        break;
                    }

TODO: need to add padding and margin as well.

                    cpStyle = $window.getComputedStyle(children[i]);
                    if (targetPadding !== false) {
                        paddingTop = parseInt(cpStyle.paddingTop, 10);
                        paddingBottom = parseInt(cpStyle.paddingBottom, 10);
                    } else {
                        paddingTop = 0;
                        paddingBottom = 0;
                    }
                    offsetTop += children[i].offsetHeight + paddingTop + paddingBottom;
                    i += 1;
                }
                if (dynamicOffset) {
                    targetOffset = offsetTop;
                }
                return offsetTop;
            }
            function onSizeChange(offsetTop) {
                result.log("onSizeChange");
                if (target) {
                    var s = angular.element(target).scope(), content = element.children(), elHeight = element[0].offsetHeight, contentHeight;
                    contentHeight = offsetTop + elHeight - targetOffset;
                    target.style.height = elHeight - targetOffset + "px";
                    content[0].style.height = contentHeight + "px";
                    s.datagrid.updateHeights();
                    if (useIScroll) {
                        myScroll.refresh();
                        if (s.datagrid.scrollHistory && !s.datagrid.scrollHistory.isComplete()) {
                            myScroll.scrollTo(0, myScroll.maxScrollY);
                        }
                    }
                    return true;
                }
                updateTarget();
                return false;
            }
            result.resize = function resize(height) {
                result.log("resize");
                if (height !== undefined) {
                    element[0].style.height = height + "px";
                }
                checkOffsetChange();
            };
            result.scrollToTop = function() {
                enable();
            };
            result.scrollToBottom = function() {
                disable();
            };
            scope.$on("$destroy", function() {
                unwatchOffset();
                clearTimeout(intv);
                clearInterval(gridScrollIntv);
                result.destroyLogger();
                result = null;
                if (useIScroll) {
                    myScroll.destroy();
                    myScroll = null;
                } else {
                    element[0].removeEventListener("scroll", onScroll);
                }
            });
            setup();
        }
    };
} ]);
}(this.ux = this.ux || {}, function() {return this;}()));