import {Component, ElementRef, Optional, NgZone} from 'angular2/core';
import {Ion} from '../ion';
import {IonicApp} from '../app/app';
import {Config} from '../../config/config';
import {raf, transitionEnd} from '../../util/dom';
import {ViewController} from '../nav/view-controller';
import {Animation} from '../../animations/animation';
import {ScrollTo} from '../../animations/scroll-to';
/**
* @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!
*
* ```
*
*/
@Component({
selector: 'ion-content',
template:
'' +
'' +
'' +
''
})
export class Content extends Ion {
private _padding: number = 0;
private _onScroll: any;
private _scrollTo: ScrollTo;
/**
* @private
*/
scrollElement: HTMLElement;
constructor(
private _elementRef: ElementRef,
private _config: Config,
private _app: IonicApp,
private _zone: NgZone,
@Optional() viewCtrl: ViewController
) {
super(_elementRef);
if (viewCtrl) {
viewCtrl.setContent(this);
viewCtrl.setContentRef(_elementRef);
}
}
/**
* @private
*/
ngOnInit() {
let self = this;
self.scrollElement = self._elementRef.nativeElement.children[0];
self._onScroll = function(ev) {
self._app.setScrolling();
};
if (self._config.get('tapPolyfill') === true) {
self._zone.runOutsideAngular(function() {
self.scrollElement.addEventListener('scroll', self._onScroll);
});
}
}
/**
* @private
*/
ngOnDestroy() {
this.scrollElement.removeEventListener('scroll', this._onScroll.bind(this));
this.scrollElement = null;
}
/**
* @private
* Adds the specified scroll handler to the content' scroll element.
*
* ```ts
* @Page({
* template: ``
* )}
* export class MyPage{
* constructor(app: IonicApp){
* this.app = app;
* }
* // Need to wait until the component has been initialized
* ngAfterViewInit() {
* // Here 'my-content' is the ID of my ion-content
* this.content = this.app.getComponent('my-content');
* this.content.addScrollEventListener(this.myScroll);
* }
* myScroll() {
* console.info('They see me scrolling...');
* }
* }
* ```
* @param {Function} handler The method you want perform when scrolling
* @returns {Function} A function that removes the scroll handler.
*/
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.scrollElement) { return; }
// ensure we're not creating duplicates
this.scrollElement.removeEventListener(type, handler);
this.scrollElement.addEventListener(type, handler);
return () => {
this.scrollElement.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.scrollElement;
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);
});
}
setTimeout(next, 100);
}
onScrollElementTransitionEnd(callback: Function) {
transitionEnd(this.scrollElement, callback);
}
/**
* Scroll to the specified position.
*
* ```ts
* @Page({
* template: `
*
* `
* )}
* export class MyPage{
* constructor(app: IonicApp){
* this.app = app;
* }
* // Need to wait until the component has been initialized
* ngAfterViewInit() {
* // Here 'my-content' is the ID of my ion-content
* this.content = this.app.getComponent('my-content');
* }
* scrollTo() {
* 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 ms.
* @param {TODO} tolerance TODO
* @returns {Promise} Returns a promise when done
*/
scrollTo(x: number, y: number, duration: number, tolerance?: number): Promise {
if (this._scrollTo) {
this._scrollTo.dispose();
}
this._scrollTo = new ScrollTo(this.scrollElement);
return this._scrollTo.start(x, y, duration, tolerance);
}
/**
* Scroll to the specified position.
*
* ```ts
* @Page({
* template: `
*
* `
* )}
* export class MyPage{
* constructor(app: IonicApp){
* this.app = app;
* }
* // Need to wait until the component has been initialized
* ngAfterViewInit() {
* // Here 'my-content' is the ID of my ion-content
* this.content = this.app.getComponent('my-content');
* }
* scrollTop() {
* this.content.scrollTop();
* }
* }
* ```
* @returns {Promise} Returns a promise when done
*/
scrollToTop() {
if (this._scrollTo) {
this._scrollTo.dispose();
}
this._scrollTo = new ScrollTo(this.scrollElement);
return this._scrollTo.start(0, 0, 300, 0);
}
getScrollTop(): number {
return this.getNativeElement().scrollTop;
}
addCssClass(className: string) {
this.getNativeElement().classList.add(className);
}
removeCssClass(className: string) {
this.getNativeElement().classList.remove(className);
}
setScrollElementStyle(prop: string, val: any) {
this.scrollElement.style[prop] = val;
}
/**
* @private
* 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.scrollElement;
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) {
if (newPadding > this._padding) {
console.debug('content addScrollPadding', newPadding);
this._padding = newPadding;
this.scrollElement.style.paddingBottom = newPadding + 'px';
}
}
}