Source: Components/scrollable.js

define(["lib/jquery", "lib/underscore", "common/util/check", "common/util/binding_list"], function( $, _, check, BindingList) {

    "use strict";

    // scrolls of less than this many pixels will be ignored
    var SCROLL_THRESHOLD = 5;

    /**
     * Makes an element scrollable.
     *
     * @param  {string | DOMElement | jQuery} el
     *   Scrollable element. Can be a CSS selector, a DOM element, or a
     *   jQuery object.
     *
     * @param  {Number} [numFingers]
     *   The number of fingers used to scroll. Defaults to 3.
     *
     * @class Represents a scrollable element.
     *
     * @property {Boolean} enabled Whether this Scrollable is enabled.
     *
     * @name Lens.Components.Scrollable
     */
    var Scrollable = function(el, numFingers) {
        check(el, check.Match.Elementish);
        check(numFingers, check.Match.Optional(Number));


        var self = {};

        var enabled = true;

        var scrollSubscribers = BindingList();

        var eventHandler = function(evt) {
                if(!self.enabled) {
                    return;
                }

                var touches = evt.targetTouches;

                if(touches.length >= numFingers) {
                    var startScrollY = el.scrollTop();
                    var startScrollX = el.scrollLeft();

                    // keep track of previous dx and dy so we can respect the threshold
                    var prevDy = touches.dy;
                    var prevDx = touches.dx;

                    // bind to the mover handler
                    touches.moved(function() {
                        if(Math.abs(prevDy - touches.dy) > SCROLL_THRESHOLD) {
                            el.scrollTop(startScrollY - touches.dy);
                            prevDy = touches.dy;
                            scrollSubscribers.callAll();
                        }
                        if(Math.abs(prevDx - touches.dx) > SCROLL_THRESHOLD) {
                            el.scrollLeft(startScrollX - touches.dx);
                            prevDx = touches.dx;
                            scrollSubscribers.callAll();
                        }
                    });

                    evt.stopPropagation();
                }
            };

        Object.defineProperty(self, "enabled", { get: function() { return enabled; } });

        /**
         * Enables the scrollable. Scrollables start out enabled, so you only
         * need to call this if you've disabled the Scrollable with `disable`.
         */
        self.enable = function() {
            el = $(el);
            el[0].addEventListener("lens:touchstart", eventHandler, true);
            enabled = true;
        };

        /**
         * Disables the scrollable. The scrollable will no longer move when
         * touched.
         */
        self.disable = function() {
            el = $(el);
            el[0].removeEventListener("lens:touchstart", eventHandler, true);
            enabled = false;
        };

        /**
         * Registers a function to be called whenever scrolling occurs.
         *
         * @param  {Function} callback
         *     The function to call. Gets one {@link Scrollable} as an
         *     argument.
         *
         * @return {Binding} A Binding that allows this handler to be cleared.
         */
        self.onScroll = function(callback) {
            check(callback, Function);
            return scrollSubscribers.add(callback);
        };

        if(!numFingers) {
            numFingers = 3;
        }
        
        $(function(){
            self.enable();
        });

        Object.freeze(self);
        return self;
    };

    Lens._addMember(Scrollable, "Scrollable", Lens.Components);
    return Scrollable;
});