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 * * Add your content here! * * ``` * * 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: '' + '' + '' + '' + '', 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: ` * * ` * )} * 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 { 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: ` * * ` * )} * 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 * {number} dimensions.contentHeight content offsetHeight * {number} dimensions.contentTop content offsetTop * {number} dimensions.contentBottom content offsetTop+offsetHeight * {number} dimensions.contentWidth content offsetWidth * {number} dimensions.contentLeft content offsetLeft * {number} dimensions.contentRight content offsetLeft + offsetWidth * {number} dimensions.scrollHeight scroll scrollHeight * {number} dimensions.scrollTop scroll scrollTop * {number} dimensions.scrollBottom scroll scrollTop + scrollHeight * {number} dimensions.scrollWidth scroll scrollWidth * {number} dimensions.scrollLeft scroll scrollLeft * {number} dimensions.scrollRight scroll scrollLeft + scrollWidth */ 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); } } }