kendo.mobile.scroller.js 26.5 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
/** 
 * Kendo UI v2016.1.112 (http://www.telerik.com/kendo-ui)                                                                                                                                               
 * Copyright 2016 Telerik AD. All rights reserved.                                                                                                                                                      
 *                                                                                                                                                                                                      
 * Kendo UI commercial licenses may be obtained at                                                                                                                                                      
 * http://www.telerik.com/purchase/license-agreement/kendo-ui-complete                                                                                                                                  
 * If you do not own a commercial license, this file shall be governed by the trial license terms.                                                                                                      
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       
                                                                                                                                                                                                       

*/
(function (f, define) {
    define('kendo.mobile.scroller', [
        'kendo.fx',
        'kendo.draganddrop'
    ], f);
}(function () {
    var __meta__ = {
        id: 'mobile.scroller',
        name: 'Scroller',
        category: 'mobile',
        description: 'The Kendo Mobile Scroller widget enables touch friendly kinetic scrolling for the contents of a given DOM element.',
        depends: [
            'fx',
            'draganddrop'
        ]
    };
    (function ($, undefined) {
        var kendo = window.kendo, mobile = kendo.mobile, fx = kendo.effects, ui = mobile.ui, proxy = $.proxy, extend = $.extend, Widget = ui.Widget, Class = kendo.Class, Movable = kendo.ui.Movable, Pane = kendo.ui.Pane, PaneDimensions = kendo.ui.PaneDimensions, Transition = fx.Transition, Animation = fx.Animation, abs = Math.abs, SNAPBACK_DURATION = 500, SCROLLBAR_OPACITY = 0.7, FRICTION = 0.96, VELOCITY_MULTIPLIER = 10, MAX_VELOCITY = 55, OUT_OF_BOUNDS_FRICTION = 0.5, ANIMATED_SCROLLER_PRECISION = 5, RELEASECLASS = 'km-scroller-release', REFRESHCLASS = 'km-scroller-refresh', PULL = 'pull', CHANGE = 'change', RESIZE = 'resize', SCROLL = 'scroll', MOUSE_WHEEL_ID = 2;
        var ZoomSnapBack = Animation.extend({
            init: function (options) {
                var that = this;
                Animation.fn.init.call(that);
                extend(that, options);
                that.userEvents.bind('gestureend', proxy(that.start, that));
                that.tapCapture.bind('press', proxy(that.cancel, that));
            },
            enabled: function () {
                return this.movable.scale < this.dimensions.minScale;
            },
            done: function () {
                return this.dimensions.minScale - this.movable.scale < 0.01;
            },
            tick: function () {
                var movable = this.movable;
                movable.scaleWith(1.1);
                this.dimensions.rescale(movable.scale);
            },
            onEnd: function () {
                var movable = this.movable;
                movable.scaleTo(this.dimensions.minScale);
                this.dimensions.rescale(movable.scale);
            }
        });
        var DragInertia = Animation.extend({
            init: function (options) {
                var that = this;
                Animation.fn.init.call(that);
                extend(that, options, {
                    transition: new Transition({
                        axis: options.axis,
                        movable: options.movable,
                        onEnd: function () {
                            that._end();
                        }
                    })
                });
                that.tapCapture.bind('press', function () {
                    that.cancel();
                });
                that.userEvents.bind('end', proxy(that.start, that));
                that.userEvents.bind('gestureend', proxy(that.start, that));
                that.userEvents.bind('tap', proxy(that.onEnd, that));
            },
            onCancel: function () {
                this.transition.cancel();
            },
            freeze: function (location) {
                var that = this;
                that.cancel();
                that._moveTo(location);
            },
            onEnd: function () {
                var that = this;
                if (that.paneAxis.outOfBounds()) {
                    that._snapBack();
                } else {
                    that._end();
                }
            },
            done: function () {
                return abs(this.velocity) < 1;
            },
            start: function (e) {
                var that = this, velocity;
                if (!that.dimension.enabled) {
                    return;
                }
                if (that.paneAxis.outOfBounds()) {
                    that._snapBack();
                } else {
                    velocity = e.touch.id === MOUSE_WHEEL_ID ? 0 : e.touch[that.axis].velocity;
                    that.velocity = Math.max(Math.min(velocity * that.velocityMultiplier, MAX_VELOCITY), -MAX_VELOCITY);
                    that.tapCapture.captureNext();
                    Animation.fn.start.call(that);
                }
            },
            tick: function () {
                var that = this, dimension = that.dimension, friction = that.paneAxis.outOfBounds() ? OUT_OF_BOUNDS_FRICTION : that.friction, delta = that.velocity *= friction, location = that.movable[that.axis] + delta;
                if (!that.elastic && dimension.outOfBounds(location)) {
                    location = Math.max(Math.min(location, dimension.max), dimension.min);
                    that.velocity = 0;
                }
                that.movable.moveAxis(that.axis, location);
            },
            _end: function () {
                this.tapCapture.cancelCapture();
                this.end();
            },
            _snapBack: function () {
                var that = this, dimension = that.dimension, snapBack = that.movable[that.axis] > dimension.max ? dimension.max : dimension.min;
                that._moveTo(snapBack);
            },
            _moveTo: function (location) {
                this.transition.moveTo({
                    location: location,
                    duration: SNAPBACK_DURATION,
                    ease: Transition.easeOutExpo
                });
            }
        });
        var AnimatedScroller = Animation.extend({
            init: function (options) {
                var that = this;
                kendo.effects.Animation.fn.init.call(this);
                extend(that, options, {
                    origin: {},
                    destination: {},
                    offset: {}
                });
            },
            tick: function () {
                this._updateCoordinates();
                this.moveTo(this.origin);
            },
            done: function () {
                return abs(this.offset.y) < ANIMATED_SCROLLER_PRECISION && abs(this.offset.x) < ANIMATED_SCROLLER_PRECISION;
            },
            onEnd: function () {
                this.moveTo(this.destination);
                if (this.callback) {
                    this.callback.call();
                }
            },
            setCoordinates: function (from, to) {
                this.offset = {};
                this.origin = from;
                this.destination = to;
            },
            setCallback: function (callback) {
                if (callback && kendo.isFunction(callback)) {
                    this.callback = callback;
                } else {
                    callback = undefined;
                }
            },
            _updateCoordinates: function () {
                this.offset = {
                    x: (this.destination.x - this.origin.x) / 4,
                    y: (this.destination.y - this.origin.y) / 4
                };
                this.origin = {
                    y: this.origin.y + this.offset.y,
                    x: this.origin.x + this.offset.x
                };
            }
        });
        var ScrollBar = Class.extend({
            init: function (options) {
                var that = this, horizontal = options.axis === 'x', element = $('<div class="km-touch-scrollbar km-' + (horizontal ? 'horizontal' : 'vertical') + '-scrollbar" />');
                extend(that, options, {
                    element: element,
                    elementSize: 0,
                    movable: new Movable(element),
                    scrollMovable: options.movable,
                    alwaysVisible: options.alwaysVisible,
                    size: horizontal ? 'width' : 'height'
                });
                that.scrollMovable.bind(CHANGE, proxy(that.refresh, that));
                that.container.append(element);
                if (options.alwaysVisible) {
                    that.show();
                }
            },
            refresh: function () {
                var that = this, axis = that.axis, dimension = that.dimension, paneSize = dimension.size, scrollMovable = that.scrollMovable, sizeRatio = paneSize / dimension.total, position = Math.round(-scrollMovable[axis] * sizeRatio), size = Math.round(paneSize * sizeRatio);
                if (sizeRatio >= 1) {
                    this.element.css('display', 'none');
                } else {
                    this.element.css('display', '');
                }
                if (position + size > paneSize) {
                    size = paneSize - position;
                } else if (position < 0) {
                    size += position;
                    position = 0;
                }
                if (that.elementSize != size) {
                    that.element.css(that.size, size + 'px');
                    that.elementSize = size;
                }
                that.movable.moveAxis(axis, position);
            },
            show: function () {
                this.element.css({
                    opacity: SCROLLBAR_OPACITY,
                    visibility: 'visible'
                });
            },
            hide: function () {
                if (!this.alwaysVisible) {
                    this.element.css({ opacity: 0 });
                }
            }
        });
        var Scroller = Widget.extend({
            init: function (element, options) {
                var that = this;
                Widget.fn.init.call(that, element, options);
                element = that.element;
                that._native = that.options.useNative && kendo.support.hasNativeScrolling;
                if (that._native) {
                    element.addClass('km-native-scroller').prepend('<div class="km-scroll-header"/>');
                    extend(that, {
                        scrollElement: element,
                        fixedContainer: element.children().first()
                    });
                    return;
                }
                element.css('overflow', 'hidden').addClass('km-scroll-wrapper').wrapInner('<div class="km-scroll-container"/>').prepend('<div class="km-scroll-header"/>');
                var inner = element.children().eq(1), tapCapture = new kendo.TapCapture(element), movable = new Movable(inner), dimensions = new PaneDimensions({
                        element: inner,
                        container: element,
                        forcedEnabled: that.options.zoom
                    }), avoidScrolling = this.options.avoidScrolling, userEvents = new kendo.UserEvents(element, {
                        fastTap: true,
                        allowSelection: true,
                        preventDragEvent: true,
                        captureUpIfMoved: true,
                        multiTouch: that.options.zoom,
                        start: function (e) {
                            dimensions.refresh();
                            var velocityX = abs(e.x.velocity), velocityY = abs(e.y.velocity), horizontalSwipe = velocityX * 2 >= velocityY, originatedFromFixedContainer = $.contains(that.fixedContainer[0], e.event.target), verticalSwipe = velocityY * 2 >= velocityX;
                            if (!originatedFromFixedContainer && !avoidScrolling(e) && that.enabled && (dimensions.x.enabled && horizontalSwipe || dimensions.y.enabled && verticalSwipe)) {
                                userEvents.capture();
                            } else {
                                userEvents.cancel();
                            }
                        }
                    }), pane = new Pane({
                        movable: movable,
                        dimensions: dimensions,
                        userEvents: userEvents,
                        elastic: that.options.elastic
                    }), zoomSnapBack = new ZoomSnapBack({
                        movable: movable,
                        dimensions: dimensions,
                        userEvents: userEvents,
                        tapCapture: tapCapture
                    }), animatedScroller = new AnimatedScroller({
                        moveTo: function (coordinates) {
                            that.scrollTo(coordinates.x, coordinates.y);
                        }
                    });
                movable.bind(CHANGE, function () {
                    that.scrollTop = -movable.y;
                    that.scrollLeft = -movable.x;
                    that.trigger(SCROLL, {
                        scrollTop: that.scrollTop,
                        scrollLeft: that.scrollLeft
                    });
                });
                if (that.options.mousewheelScrolling) {
                    element.on('DOMMouseScroll mousewheel', proxy(this, '_wheelScroll'));
                }
                extend(that, {
                    movable: movable,
                    dimensions: dimensions,
                    zoomSnapBack: zoomSnapBack,
                    animatedScroller: animatedScroller,
                    userEvents: userEvents,
                    pane: pane,
                    tapCapture: tapCapture,
                    pulled: false,
                    enabled: true,
                    scrollElement: inner,
                    scrollTop: 0,
                    scrollLeft: 0,
                    fixedContainer: element.children().first()
                });
                that._initAxis('x');
                that._initAxis('y');
                that._wheelEnd = function () {
                    that._wheel = false;
                    that.userEvents.end(0, that._wheelY);
                };
                dimensions.refresh();
                if (that.options.pullToRefresh) {
                    that._initPullToRefresh();
                }
            },
            _wheelScroll: function (e) {
                if (!this._wheel) {
                    this._wheel = true;
                    this._wheelY = 0;
                    this.userEvents.press(0, this._wheelY);
                }
                clearTimeout(this._wheelTimeout);
                this._wheelTimeout = setTimeout(this._wheelEnd, 50);
                var delta = kendo.wheelDeltaY(e);
                if (delta) {
                    this._wheelY += delta;
                    this.userEvents.move(0, this._wheelY);
                }
                e.preventDefault();
            },
            makeVirtual: function () {
                this.dimensions.y.makeVirtual();
            },
            virtualSize: function (min, max) {
                this.dimensions.y.virtualSize(min, max);
            },
            height: function () {
                return this.dimensions.y.size;
            },
            scrollHeight: function () {
                return this.scrollElement[0].scrollHeight;
            },
            scrollWidth: function () {
                return this.scrollElement[0].scrollWidth;
            },
            options: {
                name: 'Scroller',
                zoom: false,
                pullOffset: 140,
                visibleScrollHints: false,
                elastic: true,
                useNative: false,
                mousewheelScrolling: true,
                avoidScrolling: function () {
                    return false;
                },
                pullToRefresh: false,
                messages: {
                    pullTemplate: 'Pull to refresh',
                    releaseTemplate: 'Release to refresh',
                    refreshTemplate: 'Refreshing'
                }
            },
            events: [
                PULL,
                SCROLL,
                RESIZE
            ],
            _resize: function () {
                if (!this._native) {
                    this.contentResized();
                }
            },
            setOptions: function (options) {
                var that = this;
                Widget.fn.setOptions.call(that, options);
                if (options.pullToRefresh) {
                    that._initPullToRefresh();
                }
            },
            reset: function () {
                if (this._native) {
                    this.scrollElement.scrollTop(0);
                } else {
                    this.movable.moveTo({
                        x: 0,
                        y: 0
                    });
                    this._scale(1);
                }
            },
            contentResized: function () {
                this.dimensions.refresh();
                if (this.pane.x.outOfBounds()) {
                    this.movable.moveAxis('x', this.dimensions.x.min);
                }
                if (this.pane.y.outOfBounds()) {
                    this.movable.moveAxis('y', this.dimensions.y.min);
                }
            },
            zoomOut: function () {
                var dimensions = this.dimensions;
                dimensions.refresh();
                this._scale(dimensions.fitScale);
                this.movable.moveTo(dimensions.centerCoordinates());
            },
            enable: function () {
                this.enabled = true;
            },
            disable: function () {
                this.enabled = false;
            },
            scrollTo: function (x, y) {
                if (this._native) {
                    this.scrollElement.scrollLeft(abs(x));
                    this.scrollElement.scrollTop(abs(y));
                } else {
                    this.dimensions.refresh();
                    this.movable.moveTo({
                        x: x,
                        y: y
                    });
                }
            },
            animatedScrollTo: function (x, y, callback) {
                var from, to;
                if (this._native) {
                    this.scrollTo(x, y);
                } else {
                    from = {
                        x: this.movable.x,
                        y: this.movable.y
                    };
                    to = {
                        x: x,
                        y: y
                    };
                    this.animatedScroller.setCoordinates(from, to);
                    this.animatedScroller.setCallback(callback);
                    this.animatedScroller.start();
                }
            },
            pullHandled: function () {
                var that = this;
                that.refreshHint.removeClass(REFRESHCLASS);
                that.hintContainer.html(that.pullTemplate({}));
                that.yinertia.onEnd();
                that.xinertia.onEnd();
                that.userEvents.cancel();
            },
            destroy: function () {
                Widget.fn.destroy.call(this);
                if (this.userEvents) {
                    this.userEvents.destroy();
                }
            },
            _scale: function (scale) {
                this.dimensions.rescale(scale);
                this.movable.scaleTo(scale);
            },
            _initPullToRefresh: function () {
                var that = this;
                that.dimensions.y.forceEnabled();
                that.pullTemplate = kendo.template(that.options.messages.pullTemplate);
                that.releaseTemplate = kendo.template(that.options.messages.releaseTemplate);
                that.refreshTemplate = kendo.template(that.options.messages.refreshTemplate);
                that.scrollElement.prepend('<span class="km-scroller-pull"><span class="km-icon"></span><span class="km-loading-left"></span><span class="km-loading-right"></span><span class="km-template">' + that.pullTemplate({}) + '</span></span>');
                that.refreshHint = that.scrollElement.children().first();
                that.hintContainer = that.refreshHint.children('.km-template');
                that.pane.y.bind('change', proxy(that._paneChange, that));
                that.userEvents.bind('end', proxy(that._dragEnd, that));
            },
            _dragEnd: function () {
                var that = this;
                if (!that.pulled) {
                    return;
                }
                that.pulled = false;
                that.refreshHint.removeClass(RELEASECLASS).addClass(REFRESHCLASS);
                that.hintContainer.html(that.refreshTemplate({}));
                that.yinertia.freeze(that.options.pullOffset / 2);
                that.trigger('pull');
            },
            _paneChange: function () {
                var that = this;
                if (that.movable.y / OUT_OF_BOUNDS_FRICTION > that.options.pullOffset) {
                    if (!that.pulled) {
                        that.pulled = true;
                        that.refreshHint.removeClass(REFRESHCLASS).addClass(RELEASECLASS);
                        that.hintContainer.html(that.releaseTemplate({}));
                    }
                } else if (that.pulled) {
                    that.pulled = false;
                    that.refreshHint.removeClass(RELEASECLASS);
                    that.hintContainer.html(that.pullTemplate({}));
                }
            },
            _initAxis: function (axis) {
                var that = this, movable = that.movable, dimension = that.dimensions[axis], tapCapture = that.tapCapture, paneAxis = that.pane[axis], scrollBar = new ScrollBar({
                        axis: axis,
                        movable: movable,
                        dimension: dimension,
                        container: that.element,
                        alwaysVisible: that.options.visibleScrollHints
                    });
                dimension.bind(CHANGE, function () {
                    scrollBar.refresh();
                });
                paneAxis.bind(CHANGE, function () {
                    scrollBar.show();
                });
                that[axis + 'inertia'] = new DragInertia({
                    axis: axis,
                    paneAxis: paneAxis,
                    movable: movable,
                    tapCapture: tapCapture,
                    userEvents: that.userEvents,
                    dimension: dimension,
                    elastic: that.options.elastic,
                    friction: that.options.friction || FRICTION,
                    velocityMultiplier: that.options.velocityMultiplier || VELOCITY_MULTIPLIER,
                    end: function () {
                        scrollBar.hide();
                        that.trigger('scrollEnd', {
                            axis: axis,
                            scrollTop: that.scrollTop,
                            scrollLeft: that.scrollLeft
                        });
                    }
                });
            }
        });
        ui.plugin(Scroller);
    }(window.kendo.jQuery));
    return window.kendo;
}, typeof define == 'function' && define.amd ? define : function (a1, a2, a3) {
    (a3 || a2)();
}));