// Last commit: 1f0c355 (2013-09-18 11:01:11 -0400) (function() { var get = Ember.get, set = Ember.set; function samePosition(a, b) { return a && b && a.x === b.x && a.y === b.y; } function positionElement() { var element, position, _position; Ember.instrument('view.updateContext.positionElement', this, function() { element = get(this, 'element'); position = get(this, 'position'); _position = this._position; if (!position || !element) { return; } // TODO: avoid needing this by avoiding unnecessary // calls to this method in the first place if (samePosition(position, _position)) { return; } this._parentView.applyTransform(element, position.x, position.y); this._position = position; }, this); } Ember.ListItemViewMixin = Ember.Mixin.create({ init: function(){ this._super(); this.one('didInsertElement', positionElement); }, classNames: ['ember-list-item-view'], _position: null, _positionDidChange: Ember.observer(positionElement, 'position'), _positionElement: positionElement }); })(); (function() { var get = Ember.get, set = Ember.set; var backportedInnerString = function(buffer) { var content = [], childBuffers = buffer.childBuffers; Ember.ArrayPolyfills.forEach.call(childBuffers, function(buffer) { var stringy = typeof buffer === 'string'; if (stringy) { content.push(buffer); } else { buffer.array(content); } }); return content.join(''); }; function willInsertElementIfNeeded(view) { if (view.willInsertElement) { view.willInsertElement(); } } function didInsertElementIfNeeded(view) { if (view.didInsertElement) { view.didInsertElement(); } } function rerender() { var element, buffer, context, hasChildViews; element = get(this, 'element'); if (!element) { return; } context = get(this, 'context'); // releases action helpers in contents // this means though that the ListViewItem itself can't use classBindings or attributeBindings // need support for rerender contents in ember this.triggerRecursively('willClearRender'); if (this.lengthAfterRender > this.lengthBeforeRender) { this.clearRenderedChildren(); this._childViews.length = this.lengthBeforeRender; // triage bug in ember } if (context) { buffer = Ember.RenderBuffer(); buffer = this.renderToBuffer(buffer); // check again for childViews, since rendering may have added some hasChildViews = this._childViews.length > 0; if (hasChildViews) { this.invokeRecursively(willInsertElementIfNeeded, false); } element.innerHTML = buffer.innerString ? buffer.innerString() : backportedInnerString(buffer); set(this, 'element', element); this.transitionTo('inDOM'); if (hasChildViews) { this.invokeRecursively(didInsertElementIfNeeded, false); } } else { element.innerHTML = ''; // when there is no context, this view should be completely empty } } /** The `Ember.ListViewItem` view class renders a [div](https://developer.mozilla.org/en/HTML/Element/div) HTML element with `ember-list-item-view` class. It allows you to specify a custom item handlebars template for `Ember.ListView`. Example: ```handlebars ``` ```javascript App.ListView = Ember.ListView.extend({ height: 500, rowHeight: 20, itemViewClass: Ember.ListItemView.extend({templateName: "row_item"}) }); ``` @extends Ember.View @class ListItemView @namespace Ember */ Ember.ListItemView = Ember.View.extend(Ember.ListItemViewMixin, { updateContext: function(newContext){ var context = get(this, 'context'); Ember.instrument('view.updateContext.render', this, function() { if (context !== newContext) { this.set('context', newContext); if (newContext instanceof Ember.ObjectController) { this.set('controller', newContext); } } }, this); }, rerender: function () { Ember.run.scheduleOnce('render', this, rerender); }, _contextDidChange: Ember.observer(rerender, 'context', 'controller') }); })(); (function() { var get = Ember.get, set = Ember.set; Ember.ReusableListItemView = Ember.View.extend(Ember.ListItemViewMixin, { init: function(){ this._super(); this.set('context', Ember.ObjectProxy.create()); }, isVisible: Ember.computed('context.content', function(){ return !!this.get('context.content'); }), updateContext: function(newContext){ var context = get(this, 'context.content'); if (context !== newContext) { if (this.state === 'inDOM') { this.prepareForReuse(newContext); } set(this, 'context.content', newContext); } }, prepareForReuse: Ember.K }); })(); (function() { var el = document.createElement('div'), style = el.style; var propPrefixes = ['Webkit', 'Moz', 'O', 'ms']; function testProp(prop) { if (prop in style) return prop; var uppercaseProp = prop.charAt(0).toUpperCase() + prop.slice(1); for (var i=0; i'); this._super(buffer); buffer.push(''); }, willInsertElement: function() { if (!this.get("height") || !this.get("rowHeight")) { throw "A ListView must be created with a height and a rowHeight."; } this._super(); }, /** @private Sets inline styles of the view: - height - width - position - overflow - -webkit-overflow - overflow-scrolling Called while attributes binding. @property {Ember.ComputedProperty} style */ style: Ember.computed('height', 'width', function() { var height, width, style, css; height = get(this, 'height'); width = get(this, 'width'); css = get(this, 'css'); style = ''; if (height) { style += 'height:' + height + 'px;'; } if (width) { style += 'width:' + width + 'px;'; } for ( var rule in css ){ if (css.hasOwnProperty(rule)) { style += rule + ':' + css[rule] + ';'; } } return style; }), /** @private Performs visual scrolling. Is overridden in Ember.ListView. @method scrollTo */ scrollTo: function(y) { throw 'must override to perform the visual scroll and effectively delegate to _scrollContentTo'; }, /** @private Internal method used to force scroll position @method scrollTo */ _scrollTo: Ember.K, /** @private @method _scrollContentTo */ _scrollContentTo: function(y) { var startingIndex, endingIndex, contentIndex, visibleEndingIndex, maxContentIndex, contentIndexEnd, contentLength, scrollTop; scrollTop = max(0, y); Ember.instrument('view._scrollContentTo', { scrollTop: scrollTop, content: get(this, 'content'), startingIndex: this._startingIndex(), endingIndex: min(max(get(this, 'content.length') - 1, 0), this._startingIndex() + this._numChildViewsForViewport()) }, function () { contentLength = get(this, 'content.length'); set(this, 'scrollTop', scrollTop); maxContentIndex = max(contentLength - 1, 0); startingIndex = this._startingIndex(); visibleEndingIndex = startingIndex + this._numChildViewsForViewport(); endingIndex = min(maxContentIndex, visibleEndingIndex); this.trigger('scrollYChanged', y); if (startingIndex === this._lastStartingIndex && endingIndex === this._lastEndingIndex) { return; } this._reuseChildren(); this._lastStartingIndex = startingIndex; this._lastEndingIndex = endingIndex; }, this); }, /** @private Computes the height for a `Ember.ListView` scrollable container div. You must specify `rowHeight` parameter for the height to be computed properly. @property {Ember.ComputedProperty} totalHeight */ totalHeight: Ember.computed('content.length', 'rowHeight', 'columnCount', 'bottomPadding', function() { var contentLength, rowHeight, columnCount, bottomPadding; contentLength = get(this, 'content.length'); rowHeight = get(this, 'rowHeight'); columnCount = get(this, 'columnCount'); bottomPadding = get(this, 'bottomPadding'); return ((ceil(contentLength / columnCount)) * rowHeight) + bottomPadding; }), /** @private @method _prepareChildForReuse */ _prepareChildForReuse: function(childView) { childView.prepareForReuse(); }, /** @private @method _reuseChildForContentIndex */ _reuseChildForContentIndex: function(childView, contentIndex) { var content, context, newContext, childsCurrentContentIndex, position, enableProfiling; content = get(this, 'content'); enableProfiling = get(this, 'enableProfiling'); position = this.positionForIndex(contentIndex); set(childView, 'position', position); set(childView, 'contentIndex', contentIndex); if (enableProfiling) { Ember.instrument('view._reuseChildForContentIndex', position, function(){}, this); } newContext = content.objectAt(contentIndex); childView.updateContext(newContext); }, /** @private @method positionForIndex */ positionForIndex: function(index){ var elementWidth, width, columnCount, rowHeight, y, x; elementWidth = get(this, 'elementWidth') || 1; width = get(this, 'width') || 1; columnCount = get(this, 'columnCount'); rowHeight = get(this, 'rowHeight'); y = (rowHeight * floor(index/columnCount)); x = (index % columnCount) * elementWidth; return { y: y, x: x }; }, /** @private @method _childViewCount */ _childViewCount: function() { var contentLength, childViewCountForHeight; contentLength = get(this, 'content.length'); childViewCountForHeight = this._numChildViewsForViewport(); return min(contentLength, childViewCountForHeight); }, /** @private Returns a number of columns in the Ember.ListView (for grid layout). If you want to have a multi column layout, you need to specify both `width` and `elementWidth`. If no `elementWidth` is specified, it returns `1`. Otherwise, it will try to fit as many columns as possible for a given `width`. @property {Ember.ComputedProperty} columnCount */ columnCount: Ember.computed('width', 'elementWidth', function() { var elementWidth, width, count; elementWidth = get(this, 'elementWidth'); width = get(this, 'width'); if (elementWidth) { count = floor(width / elementWidth); } else { count = 1; } return count; }), /** @private Fires every time column count is changed. @event columnCountDidChange */ columnCountDidChange: Ember.observer(function(){ var ratio, currentScrollTop, proposedScrollTop, maxScrollTop, scrollTop, lastColumnCount, newColumnCount, element; lastColumnCount = this._lastColumnCount; currentScrollTop = get(this, 'scrollTop'); newColumnCount = get(this, 'columnCount'); maxScrollTop = get(this, 'maxScrollTop'); element = get(this, 'element'); this._lastColumnCount = newColumnCount; if (lastColumnCount) { ratio = (lastColumnCount / newColumnCount); proposedScrollTop = currentScrollTop * ratio; scrollTop = min(maxScrollTop, proposedScrollTop); this._scrollTo(scrollTop); set(this, 'scrollTop', scrollTop); } if (arguments.length > 0) { // invoked by observer Ember.run.schedule('afterRender', this, syncListContainerWidth); } }, 'columnCount'), /** @private Computes max possible scrollTop value given the visible viewport and scrollable container div height. @property {Ember.ComputedProperty} maxScrollTop */ maxScrollTop: Ember.computed('height', 'totalHeight', function(){ var totalHeight, viewportHeight; totalHeight = get(this, 'totalHeight'); viewportHeight = get(this, 'height'); return max(0, totalHeight - viewportHeight); }), /** @private Computes the number of views that would fit in the viewport area. You must specify `height` and `rowHeight` parameters for the number of views to be computed properly. @method _numChildViewsForViewport */ _numChildViewsForViewport: function() { var height, rowHeight, paddingCount, columnCount; height = get(this, 'height'); rowHeight = get(this, 'rowHeight'); paddingCount = get(this, 'paddingCount'); columnCount = get(this, 'columnCount'); return (ceil(height / rowHeight) * columnCount) + (paddingCount * columnCount); }, /** @private Computes the starting index of the item views array. Takes `scrollTop` property of the element into account. Is used in `_syncChildViews`. @method _startingIndex */ _startingIndex: function() { var scrollTop, rowHeight, columnCount, calculatedStartingIndex, contentLength, largestStartingIndex; contentLength = get(this, 'content.length'); scrollTop = get(this, 'scrollTop'); rowHeight = get(this, 'rowHeight'); columnCount = get(this, 'columnCount'); calculatedStartingIndex = floor(scrollTop / rowHeight) * columnCount; largestStartingIndex = max(contentLength - 1, 0); return min(calculatedStartingIndex, largestStartingIndex); }, /** @private @event contentWillChange */ contentWillChange: Ember.beforeObserver(function() { var content; content = get(this, 'content'); if (content) { content.removeArrayObserver(this); } }, 'content'), /**), @private @event contentDidChange */ contentDidChange: Ember.observer(function() { addContentArrayObserver.call(this); syncChildViews.call(this); }, 'content'), /** @private @property {Function} needsSyncChildViews */ needsSyncChildViews: Ember.observer(syncChildViews, 'height', 'width', 'columnCount'), /** @private Returns a new item view. Takes `contentIndex` to set the context of the returned view properly. @param {Number} contentIndex item index in the content array @method _addItemView */ _addItemView: function(contentIndex){ var itemViewClass, childView; itemViewClass = get(this, 'itemViewClass'); childView = this.createChildView(itemViewClass); this.pushObject(childView); }, /** @private Intelligently manages the number of childviews. @method _syncChildViews **/ _syncChildViews: function(){ var itemViewClass, startingIndex, childViewCount, endingIndex, numberOfChildViews, numberOfChildViewsNeeded, childViews, count, delta, index, childViewsLength, contentIndex; if (get(this, 'isDestroyed') || get(this, 'isDestroying')) { return; } childViewCount = this._childViewCount(); childViews = this.positionOrderedChildViews(); startingIndex = this._startingIndex(); endingIndex = startingIndex + childViewCount; numberOfChildViewsNeeded = childViewCount; numberOfChildViews = childViews.length; delta = numberOfChildViewsNeeded - numberOfChildViews; if (delta === 0) { // no change } else if (delta > 0) { // more views are needed contentIndex = this._lastEndingIndex; for (count = 0; count < delta; count++, contentIndex++) { this._addItemView(contentIndex); } } else { // less views are needed forEach.call( childViews.splice(numberOfChildViewsNeeded, numberOfChildViews), removeAndDestroy, this ); } this._scrollContentTo(get(this, 'scrollTop')); // if _scrollContentTo short-circuits, we still need // to call _reuseChildren to get new views positioned // and rendered correctly this._reuseChildren(); this._lastStartingIndex = startingIndex; this._lastEndingIndex = this._lastEndingIndex + delta; }, /** @private @method _reuseChildren */ _reuseChildren: function(){ var contentLength, childViews, childViewsLength, startingIndex, endingIndex, childView, attrs, contentIndex, visibleEndingIndex, maxContentIndex, contentIndexEnd, scrollTop; scrollTop = get(this, 'scrollTop'); contentLength = get(this, 'content.length'); maxContentIndex = max(contentLength - 1, 0); childViews = this._childViews; childViewsLength = childViews.length; startingIndex = this._startingIndex(); visibleEndingIndex = startingIndex + this._numChildViewsForViewport(); endingIndex = min(maxContentIndex, visibleEndingIndex); this.trigger('scrollContentTo', scrollTop); contentIndexEnd = min(visibleEndingIndex, startingIndex + childViewsLength); for (contentIndex = startingIndex; contentIndex < contentIndexEnd; contentIndex++) { childView = childViews[contentIndex % childViewsLength]; this._reuseChildForContentIndex(childView, contentIndex); } }, /** @private @method positionOrderedChildViews */ positionOrderedChildViews: function() { return this._childViews.sort(sortByContentIndex); }, arrayWillChange: Ember.K, /** @private @event arrayDidChange */ // TODO: refactor arrayDidChange: function(content, start, removedCount, addedCount) { var index, contentIndex; if (this.state === 'inDOM') { // ignore if all changes are out of the visible change if( start >= this._lastStartingIndex || start < this._lastEndingIndex) { index = 0; // ignore all changes not in the visible range // this can re-position many, rather then causing a cascade of re-renders forEach.call( this.positionOrderedChildViews(), function(childView) { contentIndex = this._lastStartingIndex + index; this._reuseChildForContentIndex(childView, contentIndex); index++; }, this ); } syncChildViews.call(this); } } }); })(); (function() { var get = Ember.get, set = Ember.set; /** The `Ember.ListView` view class renders a [div](https://developer.mozilla.org/en/HTML/Element/div) HTML element, with `ember-list-view` class. The context of each item element within the `Ember.ListView` are populated from the objects in the `Element.ListView`'s `content` property. ### `content` as an Array of Objects The simplest version of an `Ember.ListView` takes an array of object as its `content` property. The object will be used as the `context` each item element inside the rendered `div`. Example: ```javascript App.contributors = [{ name: 'Stefan Penner' }, { name: 'Alex Navasardyan' }, { name: 'Rey Cohen'}]; ``` ```handlebars {{#collection Ember.ListView contentBinding="App.contributors" height=500 rowHeight=50}} {{name}} {{/collection}} ``` Would result in the following HTML: ```html
Stefan Penner
Alex Navasardyan
Rey Cohen
``` By default `Ember.ListView` provides support for `height`, `rowHeight`, `width`, `elementWidth`, `scrollTop` parameters. Note, that `height` and `rowHeight` are required parameters. ```handlebars {{#collection Ember.ListView contentBinding="App.contributors" height=500 rowHeight=50}} {{name}} {{/collection}} ``` If you would like to have multiple columns in your view layout, you can set `width` and `elementWidth` parameters respectively. ```handlebars {{#collection Ember.ListView contentBinding="App.contributors" height=500 rowHeight=50 width=500 elementWidth=80}} {{name}} {{/collection}} ``` ### extending `Ember.ListView` Example: ```handlebars {{view App.ListView contentBinding="content"}} ``` ```javascript App.ListView = Ember.ListView.extend({ height: 500, width: 500, elementWidth: 80, rowHeight: 20, itemViewClass: Ember.ListItemView.extend({templateName: "row_item"}) }); ``` @extends Ember.ContainerView @class ListView @namespace Ember */ Ember.ListView = Ember.ContainerView.extend(Ember.ListViewMixin, { css: { position: 'relative', overflow: 'scroll', '-webkit-overflow-scrolling': 'touch', 'overflow-scrolling': 'touch' }, applyTransform: Ember.ListViewHelper.applyTransform, _scrollTo: function(scrollTop) { var element = get(this, 'element'); if (element) { element.scrollTop = scrollTop; } }, didInsertElement: function() { var that, element; that = this, element = get(this, 'element'); this._updateScrollableHeight(); this._scroll = function(e) { that.scroll(e); }; Ember.$(element).on('scroll', this._scroll); }, willDestroyElement: function() { var element; element = get(this, 'element'); Ember.$(element).off('scroll', this._scroll); }, scroll: function(e) { Ember.run(this, this.scrollTo, e.target.scrollTop); }, scrollTo: function(y){ var element = get(this, 'element'); this._scrollTo(y); this._scrollContentTo(y); }, totalHeightDidChange: Ember.observer(function () { Ember.run.scheduleOnce('afterRender', this, this._updateScrollableHeight); }, 'totalHeight'), _updateScrollableHeight: function () { if (this.state === 'inDOM') { this.$('.ember-list-container').css({ height: get(this, 'totalHeight') }); } } }); })(); (function() { var fieldRegex = /input|textarea|select/i, hasTouch = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch, handleStart, handleMove, handleEnd, handleCancel, startEvent, moveEvent, endEvent, cancelEvent; if (hasTouch) { startEvent = 'touchstart'; handleStart = function (e) { var touch = e.touches[0], target = touch && touch.target; // avoid e.preventDefault() on fields if (target && fieldRegex.test(target.tagName)) { return; } bindWindow(this.scrollerEventHandlers); this.willBeginScroll(e.touches, e.timeStamp); e.preventDefault(); }; moveEvent = 'touchmove'; handleMove = function (e) { this.continueScroll(e.touches, e.timeStamp); }; endEvent = 'touchend'; handleEnd = function (e) { // if we didn't end up scrolling we need to // synthesize click since we did e.preventDefault() // on touchstart if (!this._isScrolling) { synthesizeClick(e); } unbindWindow(this.scrollerEventHandlers); this.endScroll(e.timeStamp); }; cancelEvent = 'touchcancel'; handleCancel = function (e) { unbindWindow(this.scrollerEventHandlers); this.endScroll(e.timeStamp); }; } else { startEvent = 'mousedown'; handleStart = function (e) { if (e.which !== 1) return; var target = e.target; // avoid e.preventDefault() on fields if (target && fieldRegex.test(target.tagName)) { return; } bindWindow(this.scrollerEventHandlers); this.willBeginScroll([e], e.timeStamp); e.preventDefault(); }; moveEvent = 'mousemove'; handleMove = function (e) { this.continueScroll([e], e.timeStamp); }; endEvent = 'mouseup'; handleEnd = function (e) { unbindWindow(this.scrollerEventHandlers); this.endScroll(e.timeStamp); }; cancelEvent = 'mouseout'; handleCancel = function (e) { if (e.relatedTarget) return; unbindWindow(this.scrollerEventHandlers); this.endScroll(e.timeStamp); }; } function handleWheel(e) { this.mouseWheel(e); e.preventDefault(); } function bindElement(el, handlers) { el.addEventListener(startEvent, handlers.start, false); el.addEventListener('mousewheel', handlers.wheel, false); } function unbindElement(el, handlers) { el.removeEventListener(startEvent, handlers.start, false); el.removeEventListener('mousewheel', handlers.wheel, false); } function bindWindow(handlers) { window.addEventListener(moveEvent, handlers.move, true); window.addEventListener(endEvent, handlers.end, true); window.addEventListener(cancelEvent, handlers.cancel, true); } function unbindWindow(handlers) { window.removeEventListener(moveEvent, handlers.move, true); window.removeEventListener(endEvent, handlers.end, true); window.removeEventListener(cancelEvent, handlers.cancel, true); } Ember.VirtualListScrollerEvents = Ember.Mixin.create({ init: function() { this.on('didInsertElement', this, 'bindScrollerEvents'); this.on('willDestroyElement', this, 'unbindScrollerEvents'); this.scrollerEventHandlers = { start: bind(this, handleStart), move: bind(this, handleMove), end: bind(this, handleEnd), cancel: bind(this, handleCancel), wheel: bind(this, handleWheel) }; return this._super(); }, bindScrollerEvents: function() { var el = this.get('element'), handlers = this.scrollerEventHandlers; bindElement(el, handlers); }, unbindScrollerEvents: function() { var el = this.get('element'), handlers = this.scrollerEventHandlers; unbindElement(el, handlers); unbindWindow(handlers); } }); function bind(view, handler) { return function (evt) { handler.call(view, evt); }; } function synthesizeClick(e) { var point = e.changedTouches[0], target = point.target, ev; if (target && fieldRegex.test(target.tagName)) { ev = document.createEvent('MouseEvents'); ev.initMouseEvent('click', true, true, e.view, 1, point.screenX, point.screenY, point.clientX, point.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null); return target.dispatchEvent(ev); } } })(); (function() { /*global Scroller*/ var max = Math.max, get = Ember.get, set = Ember.set; function updateScrollerDimensions(target) { var width, height, totalHeight; target = target || this; width = get(target, 'width'); height = get(target, 'height'); totalHeight = get(target, 'totalHeight'); target.scroller.setDimensions(width, height, width, totalHeight); target.trigger('scrollerDimensionsDidChange'); } /** VirtualListView @class VirtualListView @namespace Ember */ Ember.VirtualListView = Ember.ContainerView.extend(Ember.ListViewMixin, Ember.VirtualListScrollerEvents, { _isScrolling: false, _mouseWheel: null, css: { position: 'relative', overflow: 'hidden' }, init: function(){ this._super(); this.setupScroller(); }, _scrollerTop: 0, applyTransform: Ember.ListViewHelper.apply3DTransform, setupScroller: function(){ var view, y; view = this; view.scroller = new Scroller(function(left, top, zoom) { if (view.state !== 'inDOM') { return; } if (view.listContainerElement) { view.applyTransform(view.listContainerElement, 0, -top); view._scrollerTop = top; view._scrollContentTo(top); } }, { scrollingX: false, scrollingComplete: function(){ view.trigger('scrollingDidComplete'); } }); view.trigger('didInitializeScroller'); updateScrollerDimensions(view); }, scrollerDimensionsNeedToChange: Ember.observer(function() { Ember.run.once(this, updateScrollerDimensions); }, 'width', 'height', 'totalHeight'), didInsertElement: function() { this.listContainerElement = this.$('> .ember-list-container')[0]; }, willBeginScroll: function(touches, timeStamp) { this._isScrolling = false; this.trigger('scrollingDidStart'); this.scroller.doTouchStart(touches, timeStamp); }, continueScroll: function(touches, timeStamp) { var startingScrollTop, endingScrollTop, event; if (this._isScrolling) { this.scroller.doTouchMove(touches, timeStamp); } else { startingScrollTop = this._scrollerTop; this.scroller.doTouchMove(touches, timeStamp); endingScrollTop = this._scrollerTop; if (startingScrollTop !== endingScrollTop) { event = Ember.$.Event("scrollerstart"); Ember.$(touches[0].target).trigger(event); this._isScrolling = true; } } }, endScroll: function(timeStamp) { this.scroller.doTouchEnd(timeStamp); }, // api scrollTo: function(y, animate) { if (animate === undefined) { animate = true; } this.scroller.scrollTo(0, y, animate, 1); }, // events mouseWheel: function(e){ var inverted, delta, candidatePosition; inverted = e.webkitDirectionInvertedFromDevice; delta = e.wheelDeltaY * (inverted ? 0.8 : -0.8); candidatePosition = this.scroller.__scrollTop + delta; if ((candidatePosition >= 0) && (candidatePosition <= this.scroller.__maxScrollTop)) { this.scroller.scrollBy(0, delta, true); } return false; } }); })(); (function() { })();