257 lines
8 KiB
HTML
257 lines
8 KiB
HTML
<!--
|
|
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
Code distributed by Google as part of the polymer project is also
|
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
-->
|
|
|
|
<!--
|
|
`core-scroll-threshold` is a utility element that listens for `scroll` events from a
|
|
scrollable region and fires events to indicate when the scroller has reached a pre-defined
|
|
limit, specified in pixels from the upper and lower bounds of the scrollable region.
|
|
|
|
This element may wrap a scrollable region and will listen for `scroll` events bubbling
|
|
through it from its children. In this case, care should be taken that only one scrollable
|
|
region with the same orientation as this element is contained within. Alternatively,
|
|
the `scrollTarget` property can be set/bound to a non-child scrollable region, from which
|
|
it will listen for events.
|
|
|
|
Once a threshold has been reached, a `lower-trigger` or `upper-trigger` event will
|
|
be fired, at which point the user may perform actions such as lazily-loading more data
|
|
to be displayed. After any work is done, the user must then clear the threshold by
|
|
calling the `clearUpper` or `clearLower` methods on this element, after which it will
|
|
begin listening again for the scroll position to reach the threshold again assuming
|
|
the content in the scrollable region has grown. If the user no longer wishes to receive
|
|
events (e.g. all data has been exhausted), the threshold property in question (e.g.
|
|
`lowerThreshold`) may be set to a falsy value to disable events and clear the associated
|
|
triggered property.
|
|
|
|
Example:
|
|
|
|
<core-scroll-threshold id="threshold" lowerThreshold="500"
|
|
on-lower-trigger="{{loadMore}}" lowerTriggered="{{spinnerShouldShow}}">
|
|
</core-scroll-threshold>
|
|
|
|
...
|
|
|
|
loadMore: function() {
|
|
this.asyncLoadStuffThen(function() {
|
|
this.$.threshold.clearLower();
|
|
}.bind(this));
|
|
}
|
|
|
|
@group Polymer Core Elements
|
|
@element core-scroll-threshold
|
|
@homepage github.io
|
|
-->
|
|
|
|
<!--
|
|
Fired when `upperTriggered` becomes `true`.
|
|
|
|
@event upper-trigger
|
|
-->
|
|
<!--
|
|
Fired when `lowerTriggered` becomes `true`.
|
|
|
|
@event lower-trigger
|
|
-->
|
|
|
|
<link rel="import" href="../polymer/polymer.html">
|
|
|
|
<polymer-element name="core-scroll-threshold">
|
|
|
|
<script>
|
|
|
|
Polymer({
|
|
|
|
publish: {
|
|
|
|
/**
|
|
* When set, the given element is observed for scroll position. When undefined,
|
|
* children can be placed inside and element itself can be used as the scrollable
|
|
* element.
|
|
*
|
|
* @attribute scrollTarget
|
|
* @type Element
|
|
* @default null
|
|
*/
|
|
scrollTarget: null,
|
|
|
|
/**
|
|
* Orientation of the scroller to be observed (`v` for vertical, `h` for horizontal)
|
|
*
|
|
* @attribute orient
|
|
* @type boolean
|
|
* @default 'v'
|
|
*/
|
|
orient: 'v',
|
|
|
|
/**
|
|
* Distance from the top (or left, for horizontal) bound of the scroller
|
|
* where the "upper trigger" will fire.
|
|
*
|
|
* @attribute upperThreshold
|
|
* @type integer
|
|
* @default null
|
|
*/
|
|
upperThreshold: null,
|
|
|
|
/**
|
|
* Distance from the bottom (or right, for horizontal) bound of the scroller
|
|
* where the "lower trigger" will fire.
|
|
*
|
|
* @attribute lowerThreshold
|
|
* @type integer
|
|
* @default null
|
|
*/
|
|
lowerThreshold: null,
|
|
|
|
/**
|
|
* Read-only value that tracks the triggered state of the upper threshold
|
|
*
|
|
* @attribute upperTriggered
|
|
* @type boolean
|
|
* @default false
|
|
*/
|
|
upperTriggered: false,
|
|
|
|
/**
|
|
* Read-only value that tracks the triggered state of the lower threshold
|
|
*
|
|
* @attribute lowerTriggered
|
|
* @type boolean
|
|
* @default false
|
|
*/
|
|
lowerTriggered: false
|
|
|
|
},
|
|
|
|
observe: {
|
|
'upperThreshold lowerThreshold scrollTarget orient': 'setup'
|
|
},
|
|
|
|
ready: function() {
|
|
this._boundScrollHandler = this.checkThreshold.bind(this);
|
|
},
|
|
|
|
detached: function() {
|
|
// Remove listener for any previous scroll target
|
|
if (this._scrollTarget) {
|
|
this._scrollTarget.removeEventListener('scroll', this._boundScrollHandler);
|
|
}
|
|
},
|
|
|
|
setup: function() {
|
|
var target = this.scrollTarget || this;
|
|
|
|
// Remove listener for any previous scroll target
|
|
if (this._scrollTarget && (this._scrollTarget != target)) {
|
|
this._scrollTarget.removeEventListener('scroll', this._boundScrollHandler);
|
|
}
|
|
|
|
// Add listener for new scroll target
|
|
if (target) {
|
|
this._scrollTarget = target;
|
|
this._scrollTarget.addEventListener('scroll', this._boundScrollHandler);
|
|
}
|
|
|
|
// If we're listening on ourself, make us auto in case someone put
|
|
// content inside
|
|
this.style.overflow = (target == this) ? 'auto' : null;
|
|
|
|
// Setup extents based on orientation
|
|
this.scrollPosition = (this.orient == 'v') ? 'scrollTop' : 'scrollLeft';
|
|
this.sizeExtent = (this.orient == 'v') ? 'offsetHeight' : 'offsetWidth';
|
|
this.scrollExtent = (this.orient == 'v') ? 'scrollHeight' : 'scrollWidth';
|
|
|
|
// Clear trigger state if user has cleared the threshold
|
|
if (!this.upperThreshold) {
|
|
this.upperTriggered = false;
|
|
}
|
|
if (!this.lowerThreshold) {
|
|
this.lowerTriggered = false;
|
|
}
|
|
},
|
|
|
|
checkThreshold: function(e) {
|
|
var top = this._scrollTarget[this.scrollPosition];
|
|
if (!this.upperTriggered && this.upperThreshold !== null) {
|
|
if (top < this.upperThreshold) {
|
|
|
|
// No longer fire scroll events if there's no lower threshold or it
|
|
// has been triggered.
|
|
if (this.lowerThreshold === null || this.lowerTriggered) {
|
|
this._scrollTarget.removeEventListener('scroll', this._boundScrollHandler);
|
|
}
|
|
|
|
this.upperTriggered = true;
|
|
this.fire('upper-trigger');
|
|
}
|
|
}
|
|
if (!this.lowerTriggered && this.lowerThreshold !== null) {
|
|
var bottom = top + this._scrollTarget[this.sizeExtent];
|
|
var size = this._scrollTarget[this.scrollExtent];
|
|
if ((size - bottom) < this.lowerThreshold) {
|
|
|
|
// No longer fire scroll events if there's no upper threshold or it
|
|
// has been triggered.
|
|
if (this.upperThreshold === null || this.upperTriggered) {
|
|
this._scrollTarget.removeEventListener('scroll', this._boundScrollHandler);
|
|
}
|
|
|
|
this.lowerTriggered = true;
|
|
this.fire('lower-trigger');
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clear the upper threshold, following an `upper-trigger` event.
|
|
*
|
|
* @method clearUpper
|
|
*/
|
|
clearUpper: function(waitForMutation) {
|
|
if (waitForMutation) {
|
|
this._waitForMutation(function() {
|
|
this.clearUpper();
|
|
}.bind(this));
|
|
} else {
|
|
this.async(function() {
|
|
this.upperTriggered = false;
|
|
this._scrollTarget.addEventListener('scroll', this._boundScrollHandler);
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clear the lower threshold, following a `lower-trigger` event.
|
|
*
|
|
* @method clearLower
|
|
*/
|
|
clearLower: function(waitForMutation) {
|
|
if (waitForMutation) {
|
|
this._waitForMutation(function() {
|
|
this.clearLower();
|
|
}.bind(this));
|
|
} else {
|
|
this.async(function() {
|
|
this.lowerTriggered = false;
|
|
this._scrollTarget.addEventListener('scroll', this._boundScrollHandler);
|
|
});
|
|
}
|
|
},
|
|
|
|
_waitForMutation: function(listener) {
|
|
var observer = new MutationObserver(function(mutations) {
|
|
listener.call(this, observer, mutations);
|
|
observer.disconnect();
|
|
}.bind(this));
|
|
observer.observe(this._scrollTarget, {attributes: true, childList: true, subtree: true});
|
|
}
|
|
|
|
});
|
|
|
|
</script>
|
|
</polymer-element>
|