mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
383 lines
10 KiB
TypeScript
383 lines
10 KiB
TypeScript
import {Component, ElementRef, Optional, NgZone, ChangeDetectionStrategy, ViewEncapsulation} from 'angular2/core';
|
|
|
|
import {Ion} from '../ion';
|
|
import {IonicApp} from '../app/app';
|
|
import {Config} from '../../config/config';
|
|
import {Keyboard} from '../../util/keyboard';
|
|
import {raf, nativeTimeout, transitionEnd} from '../../util/dom';
|
|
import {ViewController} from '../nav/view-controller';
|
|
import {ScrollView} from '../../util/scroll-view';
|
|
|
|
/**
|
|
* @name Content
|
|
* @description
|
|
* The Content component provides an easy to use content area with
|
|
* some useful methods to control the scrollable area.
|
|
*
|
|
* The content area can also implement pull-to-refresh with the
|
|
* [Refresher](../../scroll/Refresher) component.
|
|
*
|
|
* @usage
|
|
* ```html
|
|
* <ion-content>
|
|
* Add your content here!
|
|
* </ion-content>
|
|
* ```
|
|
*
|
|
* To get a reference to the content component from a Page's logic,
|
|
* you can use Angular's `@ViewChild` annotation:
|
|
*
|
|
* ```ts
|
|
* import {ViewChild} from 'angular2/core';
|
|
* import {Content} from 'ionic-angular';
|
|
*
|
|
* @Page({...}
|
|
* export class MyPage{
|
|
* @ViewChild(Content) content: Content;
|
|
*
|
|
* scrollToTop() {
|
|
* this.content.scrollToTop();
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
*/
|
|
@Component({
|
|
selector: 'ion-content',
|
|
template:
|
|
'<scroll-content>' +
|
|
'<ng-content></ng-content>' +
|
|
'</scroll-content>' +
|
|
'<ng-content select="ion-fixed"></ng-content>' +
|
|
'<ng-content select="ion-refresher"></ng-content>',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
encapsulation: ViewEncapsulation.None,
|
|
})
|
|
export class Content extends Ion {
|
|
private _padding: number = 0;
|
|
private _inputPolling: boolean = false;
|
|
private _scroll: ScrollView;
|
|
private _scLsn: Function;
|
|
private _scrollEle: HTMLElement;
|
|
|
|
constructor(
|
|
private _elementRef: ElementRef,
|
|
private _config: Config,
|
|
private _app: IonicApp,
|
|
private _keyboard: Keyboard,
|
|
private _zone: NgZone,
|
|
@Optional() viewCtrl: ViewController
|
|
) {
|
|
super(_elementRef);
|
|
|
|
if (viewCtrl) {
|
|
viewCtrl.setContent(this);
|
|
viewCtrl.setContentRef(_elementRef);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ngOnInit() {
|
|
let self = this;
|
|
self._scrollEle = self._elementRef.nativeElement.children[0];
|
|
|
|
self._zone.runOutsideAngular(function() {
|
|
self._scroll = new ScrollView(self._scrollEle);
|
|
|
|
if (self._config.getBoolean('tapPolyfill')) {
|
|
self._scLsn = self.addScrollListener(function() {
|
|
self._app.setScrolling();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ngOnDestroy() {
|
|
this._scLsn && this._scLsn();
|
|
this._scroll && this._scroll.destroy();
|
|
this._scrollEle = this._scLsn = null;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
addScrollListener(handler) {
|
|
return this._addListener('scroll', handler);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
addTouchStartListener(handler) {
|
|
return this._addListener('touchstart', handler);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
addTouchMoveListener(handler) {
|
|
return this._addListener('touchmove', handler);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
addTouchEndListener(handler) {
|
|
return this._addListener('touchend', handler);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
addMouseDownListener(handler) {
|
|
return this._addListener('mousedown', handler);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
addMouseUpListener(handler) {
|
|
return this._addListener('mouseup', handler);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
addMouseMoveListener(handler) {
|
|
return this._addListener('mousemove', handler);
|
|
}
|
|
|
|
private _addListener(type: string, handler: any): Function {
|
|
if (!this._scrollEle) { return; }
|
|
|
|
// ensure we're not creating duplicates
|
|
this._scrollEle.removeEventListener(type, handler);
|
|
this._scrollEle.addEventListener(type, handler);
|
|
|
|
return () => {
|
|
if (this._scrollEle) {
|
|
this._scrollEle.removeEventListener(type, handler);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Call a method when scrolling has stopped
|
|
* @param {Function} callback The method you want perform when scrolling has ended
|
|
*/
|
|
onScrollEnd(callback: Function) {
|
|
let lastScrollTop = null;
|
|
let framesUnchanged = 0;
|
|
let _scrollEle = this._scrollEle;
|
|
|
|
function next() {
|
|
let currentScrollTop = _scrollEle.scrollTop;
|
|
if (lastScrollTop !== null) {
|
|
|
|
if (Math.round(lastScrollTop) === Math.round(currentScrollTop)) {
|
|
framesUnchanged++;
|
|
} else {
|
|
framesUnchanged = 0;
|
|
}
|
|
|
|
if (framesUnchanged > 9) {
|
|
return callback();
|
|
}
|
|
}
|
|
|
|
lastScrollTop = currentScrollTop;
|
|
|
|
raf(() => {
|
|
raf(next);
|
|
});
|
|
}
|
|
|
|
nativeTimeout(next, 100);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onScrollElementTransitionEnd(callback: Function) {
|
|
transitionEnd(this._scrollEle, callback);
|
|
}
|
|
|
|
/**
|
|
* Scroll to the specified position.
|
|
*
|
|
* ```ts
|
|
* import {ViewChild} from 'angular2/core';
|
|
* import {Content} from 'ionic-angular';
|
|
*
|
|
* @Page({
|
|
* template: `<ion-content>
|
|
* <button (click)="scrollTo()">Down 500px</button>
|
|
* </ion-content>`
|
|
* )}
|
|
* export class MyPage{
|
|
* @ViewChild(Content) content: Content;
|
|
*
|
|
* scrollTo() {
|
|
* // set the scrollLeft to 0px, and scrollTop to 500px
|
|
* // the scroll duration should take 200ms
|
|
* this.content.scrollTo(0, 500, 200);
|
|
* }
|
|
* }
|
|
* ```
|
|
* @param {number} x The x-value to scroll to.
|
|
* @param {number} y The y-value to scroll to.
|
|
* @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`.
|
|
* @returns {Promise} Returns a promise which is resolved when the scroll has completed.
|
|
*/
|
|
scrollTo(x: number, y: number, duration: number = 300): Promise<any> {
|
|
return this._scroll.scrollTo(x, y, duration);
|
|
}
|
|
|
|
/**
|
|
* Scroll to the top of the content component.
|
|
*
|
|
* ```ts
|
|
* import {ViewChild} from 'angular2/core';
|
|
* import {Content} from 'ionic-angular';
|
|
*
|
|
* @Page({
|
|
* template: `<ion-content>
|
|
* <button (click)="scrollToTop()">Scroll to top</button>
|
|
* </ion-content>`
|
|
* )}
|
|
* export class MyPage{
|
|
* @ViewChild(Content) content: Content;
|
|
*
|
|
* scrollToTop() {
|
|
* this.content.scrollToTop();
|
|
* }
|
|
* }
|
|
* ```
|
|
* @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`.
|
|
* @returns {Promise} Returns a promise which is resolved when the scroll has completed.
|
|
*/
|
|
scrollToTop(duration: number = 300) {
|
|
return this.scrollTo(0, 0, duration);
|
|
}
|
|
|
|
/**
|
|
* Get the `scrollTop` property of the content's scrollable element.
|
|
* @returns {number}
|
|
*/
|
|
getScrollTop(): number {
|
|
return this._scroll.getTop();
|
|
}
|
|
|
|
/**
|
|
* Set the `scrollTop` property of the content's scrollable element.
|
|
* @param {number} top
|
|
*/
|
|
setScrollTop(top: number) {
|
|
this._scroll.setTop(top);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
jsScroll(onScrollCallback: Function): Function {
|
|
return this._scroll.jsScroll(onScrollCallback);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
addCssClass(className: string) {
|
|
this.getNativeElement().classList.add(className);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
removeCssClass(className: string) {
|
|
this.getNativeElement().classList.remove(className);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
setScrollElementStyle(prop: string, val: any) {
|
|
this._scrollEle.style[prop] = val;
|
|
}
|
|
|
|
/**
|
|
* Returns the content and scroll elements' dimensions.
|
|
* @returns {object} dimensions The content and scroll elements' dimensions <br>
|
|
* <code>number</code> dimensions.contentHeight content offsetHeight <br>
|
|
* <code>number</code> dimensions.contentTop content offsetTop <br>
|
|
* <code>number</code> dimensions.contentBottom content offsetTop+offsetHeight <br>
|
|
* <code>number</code> dimensions.contentWidth content offsetWidth <br>
|
|
* <code>number</code> dimensions.contentLeft content offsetLeft <br>
|
|
* <code>number</code> dimensions.contentRight content offsetLeft + offsetWidth <br>
|
|
* <code>number</code> dimensions.scrollHeight scroll scrollHeight <br>
|
|
* <code>number</code> dimensions.scrollTop scroll scrollTop <br>
|
|
* <code>number</code> dimensions.scrollBottom scroll scrollTop + scrollHeight <br>
|
|
* <code>number</code> dimensions.scrollWidth scroll scrollWidth <br>
|
|
* <code>number</code> dimensions.scrollLeft scroll scrollLeft <br>
|
|
* <code>number</code> dimensions.scrollRight scroll scrollLeft + scrollWidth <br>
|
|
*/
|
|
getContentDimensions() {
|
|
let _scrollEle = this._scrollEle;
|
|
let parentElement = _scrollEle.parentElement;
|
|
|
|
return {
|
|
contentHeight: parentElement.offsetHeight,
|
|
contentTop: parentElement.offsetTop,
|
|
contentBottom: parentElement.offsetTop + parentElement.offsetHeight,
|
|
|
|
contentWidth: parentElement.offsetWidth,
|
|
contentLeft: parentElement.offsetLeft,
|
|
contentRight: parentElement.offsetLeft + parentElement.offsetWidth,
|
|
|
|
scrollHeight: _scrollEle.scrollHeight,
|
|
scrollTop: _scrollEle.scrollTop,
|
|
scrollBottom: _scrollEle.scrollTop + _scrollEle.scrollHeight,
|
|
|
|
scrollWidth: _scrollEle.scrollWidth,
|
|
scrollLeft: _scrollEle.scrollLeft,
|
|
scrollRight: _scrollEle.scrollLeft + _scrollEle.scrollWidth,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Adds padding to the bottom of the scroll element when the keyboard is open
|
|
* so content below the keyboard can be scrolled into view.
|
|
*/
|
|
addScrollPadding(newPadding: number) {
|
|
if (newPadding > this._padding) {
|
|
console.debug('content addScrollPadding', newPadding);
|
|
|
|
this._padding = newPadding;
|
|
this._scrollEle.style.paddingBottom = newPadding + 'px';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
clearScrollPaddingFocusOut() {
|
|
if (!this._inputPolling) {
|
|
this._inputPolling = true;
|
|
|
|
this._keyboard.onClose(() => {
|
|
this._padding = 0;
|
|
this._scrollEle.style.paddingBottom = '';
|
|
this._inputPolling = false;
|
|
this.addScrollPadding(0);
|
|
}, 200, Infinity);
|
|
}
|
|
}
|
|
|
|
}
|