From 156fdc35ed551977f5c015e408fd72e866fea703 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Sat, 9 Apr 2016 21:54:31 -0500 Subject: [PATCH] feat(platform): add backbutton event --- ionic/config/bootstrap.ts | 4 +- ionic/platform/platform.ts | 192 +++++++++++++-------------- ionic/platform/registry.ts | 39 ++++-- ionic/platform/test/platform.spec.ts | 73 ++-------- 4 files changed, 139 insertions(+), 169 deletions(-) diff --git a/ionic/config/bootstrap.ts b/ionic/config/bootstrap.ts index 9dccff432e..5db6aaa329 100644 --- a/ionic/config/bootstrap.ts +++ b/ionic/config/bootstrap.ts @@ -33,7 +33,7 @@ export function ionicProviders(args: any = {}) { platform.setUrl(window.location.href); platform.setUserAgent(window.navigator.userAgent); platform.setNavigatorPlatform(window.navigator.platform); - platform.load(); + platform.load(config); config.setPlatform(platform); let clickBlock = new ClickBlock(); @@ -45,7 +45,7 @@ export function ionicProviders(args: any = {}) { bindEvents(window, document, platform, events); // prepare the ready promise to fire....when ready - platform.prepareReady(config); + platform.prepareReady(); return [ IonicApp, diff --git a/ionic/platform/platform.ts b/ionic/platform/platform.ts index 36e86c6b01..067bea8a11 100644 --- a/ionic/platform/platform.ts +++ b/ionic/platform/platform.ts @@ -1,6 +1,7 @@ -import {getQuerystring, assign} from '../util/util'; -import {ready, windowDimensions, flushDimensionCache} from '../util/dom'; import {Config} from '../config/config'; +import {EventEmitter} from 'angular2/core'; +import {getQuerystring} from '../util/util'; +import {ready, windowDimensions, flushDimensionCache} from '../util/dom'; /** * @name Platform @@ -25,7 +26,7 @@ import {Config} from '../config/config'; */ export class Platform { private _platforms: Array; - private _versions: any = {}; + private _versions: {[name: string]: PlatformVersion} = {}; private _dir: string; private _lang: string; private _url: string; @@ -35,14 +36,8 @@ export class Platform { private _onResizes: Array = []; private _readyPromise: Promise; private _readyResolve: any; - private _engineReady: any; private _resizeTm: any; - /** - * @private - */ - platformOverride: string; - constructor(platforms = []) { this._platforms = platforms; this._readyPromise = new Promise(res => { this._readyResolve = res; } ); @@ -124,7 +119,7 @@ export class Platform { /** - * Returns an object containing information about the paltform + * Returns an object containing information about the platforms. * * ``` * import {Platform} from 'ionic-angular'; @@ -132,8 +127,7 @@ export class Platform { * @Page({...}) * export MyPage { * constructor(platform: Platform) { - * this.platform = platform; - * console.log(this.platform.versions()); + * console.log(platform.versions()); * } * } * ``` @@ -142,12 +136,7 @@ export class Platform { * @returns {object} An object with various platform info * */ - versions(platformName: string): any { - if (arguments.length) { - // get a specific platform's version - return this._versions[platformName]; - } - + versions(): {[name: string]: PlatformVersion} { // get all the platforms that have a valid parsed version return this._versions; } @@ -155,7 +144,7 @@ export class Platform { /** * @private */ - version(): any { + version(): PlatformVersion { for (let platformName in this._versions) { if (this._versions[platformName]) { return this._versions[platformName]; @@ -165,7 +154,11 @@ export class Platform { } /** - * Returns a promise when the platform is ready and native functionality can be called + * Returns a promise when the platform is ready and native functionality + * can be called. If the app is running from within a web browser, then + * the promise will resolve when the DOM is ready. When the app is running + * from an application engine such as Cordova, then the promise + * will resolve when Cordova triggers the `deviceready` event. * * ``` * import {Platform} from 'ionic-angular'; @@ -173,39 +166,37 @@ export class Platform { * @Page({...}) * export MyPage { * constructor(platform: Platform) { - * this.platform = platform; - * this.platform.ready().then(() => { + * platform.ready().then(() => { * console.log('Platform ready'); * // The platform is now ready, execute any native code you want * }); * } * } * ``` - * @returns {promise} Returns a promsie when device ready has fired + * @returns {promise} */ ready(): Promise { + // this is the default if it's not replaced by the engine + // if there was no custom ready method from the engine + // then use the default DOM ready return this._readyPromise; } /** * @private */ - prepareReady(config: Config) { - let self = this; + triggerReady() { + this._readyResolve(); + } - function resolve() { - self._readyResolve(config); - } - - if (this._engineReady) { - // the engine provide a ready promise, use this instead - this._engineReady(resolve); - - } else { - // there is no custom ready method from the engine - // use the default dom ready - ready(resolve); - } + /** + * @private + */ + prepareReady() { + // this is the default prepareReady if it's not replaced by the engine + // if there was no custom ready method from the engine + // then use the default DOM ready + ready(this.triggerReady.bind(this)); } /** @@ -276,31 +267,30 @@ export class Platform { // Methods meant to be overridden by the engine // ********************************************** // Provided NOOP methods so they do not error when - // called by engines (the browser) doesn't provide them - /** - * @private - */ - on() {} - /** - * @private - */ - onHardwareBackButton() {} - /** - * @private - */ - registerBackButtonAction() {} + // called by engines (the browser)that do not provide them + /** * @private */ exitApp() {} + + // Events meant to be triggered by the engine + // ********************************************** + /** * @private */ - fullScreen() {} + backButton: EventEmitter = new EventEmitter(); + /** * @private */ - showStatusBar() {} + pause: EventEmitter = new EventEmitter(); + + /** + * @private + */ + resume: EventEmitter = new EventEmitter(); // Getter/Setter Methods @@ -427,21 +417,21 @@ export class Platform { /** * @private */ - static register(platformConfig) { + static register(platformConfig: PlatformConfig) { platformRegistry[platformConfig.name] = platformConfig; } /** * @private */ - static registry(): any { + static registry() { return platformRegistry; } /** * @private */ - static get(platformName: string): any { + static get(platformName: string): PlatformConfig { return platformRegistry[platformName] || {}; } @@ -513,15 +503,13 @@ export class Platform { /** * @private */ - load(platformOverride?: string) { - let rootPlatformNode = null; - let engineNode = null; + load(config: Config) { + let rootPlatformNode: PlatformNode; + let enginePlatformNode: PlatformNode; let self = this; - this.platformOverride = platformOverride; - // figure out the most specific platform and active engine - let tmpPlatform = null; + let tmpPlatform: PlatformNode; for (let platformName in platformRegistry) { tmpPlatform = this.matchPlatform(platformName); @@ -532,7 +520,7 @@ export class Platform { if (tmpPlatform.isEngine) { // because it matched then this should be the active engine // you cannot have more than one active engine - engineNode = tmpPlatform; + enginePlatformNode = tmpPlatform; } else if (!rootPlatformNode || tmpPlatform.depth > rootPlatformNode.depth) { // only find the root node for platforms that are not engines @@ -553,20 +541,13 @@ export class Platform { if (rootPlatformNode) { // check if we found an engine node (cordova/node-webkit/etc) - if (engineNode) { + if (enginePlatformNode) { // add the engine to the first in the platform hierarchy // the original rootPlatformNode now becomes a child // of the engineNode, which is not the new root - engineNode.child = rootPlatformNode; - rootPlatformNode.parent = engineNode; - rootPlatformNode = engineNode; - - // add any events which the engine would provide - // for example, Cordova provides its own ready event - let engineMethods = engineNode.methods(); - engineMethods._engineReady = engineMethods.ready; - delete engineMethods.ready; - assign(this, engineMethods); + enginePlatformNode.child = rootPlatformNode; + rootPlatformNode.parent = enginePlatformNode; + rootPlatformNode = enginePlatformNode; } let platformNode = rootPlatformNode; @@ -585,12 +566,14 @@ export class Platform { platformNode = rootPlatformNode; while (platformNode) { + platformNode.initialize(this, config); + // set the array of active platforms with // the last one in the array the most important - this._platforms.push(platformNode.name()); + this._platforms.push(platformNode.name); // get the platforms version if a version parser was provided - this._versions[platformNode.name()] = platformNode.version(this); + this._versions[platformNode.name] = platformNode.version(this); // go to the next platform child platformNode = platformNode.child; @@ -605,7 +588,7 @@ export class Platform { /** * @private */ - matchPlatform(platformName: string): any { + private matchPlatform(platformName: string): PlatformNode { // build a PlatformNode and assign config data to it // use it's getRoot method to build up its hierarchy // depending on which platforms match @@ -640,24 +623,24 @@ function insertSuperset(platformNode: PlatformNode) { } } - +/** + * @private + */ class PlatformNode { - private c; + private c: PlatformConfig; parent: PlatformNode; child: PlatformNode; + name: string; isEngine: boolean; depth: number; constructor(platformName: string) { this.c = Platform.get(platformName); + this.name = platformName; this.isEngine = this.c.isEngine; } - name(): string { - return this.c.name; - } - settings(): any { return this.c.settings || {}; } @@ -666,22 +649,15 @@ class PlatformNode { return this.c.superset; } - methods(): any { - return this.c.methods || {}; - } - isMatch(p: Platform): boolean { - if (p.platformOverride && !this.isEngine) { - return (p.platformOverride === this.c.name); - - } else if (!this.c.isMatch) { - return false; - } - - return this.c.isMatch(p); + return this.c.isMatch && this.c.isMatch(p) || false; } - version(p: Platform): any { + initialize(platform: Platform, config: Config) { + this.c.initialize && this.c.initialize(platform, config); + } + + version(p: Platform): PlatformVersion { if (this.c.versionParser) { let v = this.c.versionParser(p); if (v) { @@ -699,7 +675,7 @@ class PlatformNode { getRoot(p: Platform): PlatformNode { if (this.isMatch(p)) { - let parents = this.getSubsetParents(this.name()); + let parents = this.getSubsetParents(this.name); if (!parents.length) { return this; @@ -742,5 +718,23 @@ class PlatformNode { } -let platformRegistry = {}; -let platformDefault = null; +let platformRegistry: {[name: string]: PlatformConfig} = {}; +let platformDefault: string = null; + +export interface PlatformConfig { + name?: string; + isEngine?: boolean; + initialize?: Function; + isMatch?: Function; + superset?: string; + subsets?: string[]; + settings?: any; + versionParser?: any; +} + +export interface PlatformVersion { + str?: string; + num?: number; + major?: number; + minor?: number +} diff --git a/ionic/platform/registry.ts b/ionic/platform/registry.ts index 3d650c2831..c4ae49ab93 100644 --- a/ionic/platform/registry.ts +++ b/ionic/platform/registry.ts @@ -1,4 +1,5 @@ import {Platform} from './platform'; +import {Config} from '../config/config'; import {windowLoad} from '../util/dom'; const win: any = window; @@ -166,16 +167,38 @@ Platform.register({ Platform.register({ name: 'cordova', isEngine: true, - methods: { - ready: function(resolve) { - function isReady() { - doc.removeEventListener('deviceready', isReady); - resolve(); - } + initialize: function(p: Platform, config: Config) { + + // prepare a custom "ready" for cordova "deviceready" + p.prepareReady = function() { + // 1) ionic bootstrapped windowLoad(function() { - doc.addEventListener('deviceready', isReady); + // 2) window onload triggered or completed + doc.addEventListener('deviceready', () => { + // 3) cordova deviceready event triggered + + // add cordova listeners to fire platform events + doc.addEventListener('backbutton', function() { + p.backButton.emit(null); + }); + // doc.addEventListener('pause', function() { + // p.pause.emit(null); + // }); + // doc.addEventListener('resume', function() { + // p.resume.emit(null); + // }); + + // cordova has fully loaded and we've added listeners + p.triggerReady(); + }); }); - } + }; + + // cordova has its own exitApp method + p.exitApp = function() { + win.navigator.app.exitApp(); + }; + }, isMatch(): boolean { return !!(win.cordova || win.PhoneGap || win.phonegap); diff --git a/ionic/platform/test/platform.spec.ts b/ionic/platform/test/platform.spec.ts index bfff65d1b3..0c4d906d39 100644 --- a/ionic/platform/test/platform.spec.ts +++ b/ionic/platform/test/platform.spec.ts @@ -5,22 +5,13 @@ export function run() { it('should set core as the fallback', () => { let platform = new Platform(); platform.setUserAgent('idk'); - platform.load(); + platform.load(null); expect(platform.is('android')).toEqual(false); expect(platform.is('ios')).toEqual(false); expect(platform.is('core')).toEqual(true); }); - it('should set android via platformOverride, despite ios user agent', () => { - let platform = new Platform(); - platform.setUserAgent(IPAD_UA); - platform.load('android'); - - expect(platform.is('android')).toEqual(true); - expect(platform.is('ios')).toEqual(false); - }); - it('should get case insensitive querystring value', () => { let platform = new Platform(); platform.setUrl('/?KEY=value'); @@ -35,48 +26,10 @@ export function run() { expect(platform.query('key')).toEqual('value'); }); - it('should set ios via platformOverride, despite android querystring', () => { - let platform = new Platform(); - platform.setUrl('/?ionicplatform=android'); - platform.load('ios'); - - expect(platform.is('android')).toEqual(false); - expect(platform.is('windows')).toEqual(false); - expect(platform.is('ios')).toEqual(true); - }); - - it('should set windows via platformOverride, despite android querystring', () => { - let platform = new Platform(); - platform.setUrl('/?ionicplatform=android'); - platform.load('windows'); - - expect(platform.is('android')).toEqual(false); - expect(platform.is('windows')).toEqual(true); - expect(platform.is('ios')).toEqual(false); - }); - - it('should set ios via platformOverride', () => { - let platform = new Platform(); - platform.load('ios'); - - expect(platform.is('android')).toEqual(false); - expect(platform.is('windows')).toEqual(false); - expect(platform.is('ios')).toEqual(true); - }); - - it('should set android via platformOverride', () => { - let platform = new Platform(); - platform.load('android'); - - expect(platform.is('android')).toEqual(true); - expect(platform.is('windows')).toEqual(false); - expect(platform.is('ios')).toEqual(false); - }); - it('should set windows via querystring', () => { let platform = new Platform(); platform.setUrl('/?ionicplatform=windows'); - platform.load(); + platform.load(null); expect(platform.is('mobile')).toEqual(true); expect(platform.is('android')).toEqual(false); @@ -87,7 +40,7 @@ export function run() { it('should set ios via querystring', () => { let platform = new Platform(); platform.setUrl('/?ionicplatform=ios'); - platform.load(); + platform.load(null); expect(platform.is('mobile')).toEqual(true); expect(platform.is('android')).toEqual(false); @@ -99,7 +52,7 @@ export function run() { let platform = new Platform(); platform.setUrl('/?ionicplatform=windows'); platform.setUserAgent(ANDROID_UA); - platform.load(); + platform.load(null); expect(platform.is('android')).toEqual(false); expect(platform.is('windows')).toEqual(true); @@ -110,7 +63,7 @@ export function run() { let platform = new Platform(); platform.setUrl('/?ionicplatform=ios'); platform.setUserAgent(ANDROID_UA); - platform.load(); + platform.load(null); expect(platform.is('android')).toEqual(false); expect(platform.is('windows')).toEqual(false); @@ -120,7 +73,7 @@ export function run() { it('should set android via querystring', () => { let platform = new Platform(); platform.setUrl('/?ionicplatform=android'); - platform.load(); + platform.load(null); expect(platform.is('android')).toEqual(true); expect(platform.is('windows')).toEqual(false); @@ -131,7 +84,7 @@ export function run() { let platform = new Platform(); platform.setUrl('/?ionicplatform=android'); platform.setUserAgent(IPHONE_UA); - platform.load(); + platform.load(null); expect(platform.is('android')).toEqual(true); expect(platform.is('windows')).toEqual(false); @@ -141,7 +94,7 @@ export function run() { it('should set windows via user agent', () => { let platform = new Platform(); platform.setUserAgent(WINDOWS_UA); - platform.load(); + platform.load(null); expect(platform.is('mobile')).toEqual(true); expect(platform.is('windows')).toEqual(true); @@ -152,7 +105,7 @@ export function run() { it('should set windows8 via user agent', () => { let platform = new Platform(); platform.setUserAgent(WINDOWS8_UA); - platform.load(); + platform.load(null); expect(platform.is('mobile')).toEqual(true); expect(platform.is('windows')).toEqual(true); @@ -163,7 +116,7 @@ export function run() { it('should set windows7 via user agent', () => { let platform = new Platform(); platform.setUserAgent(WINDOWS7_UA); - platform.load(); + platform.load(null); expect(platform.is('mobile')).toEqual(true); expect(platform.is('windows')).toEqual(true); @@ -174,7 +127,7 @@ export function run() { it('should set android via user agent', () => { let platform = new Platform(); platform.setUserAgent(ANDROID_UA); - platform.load(); + platform.load(null); expect(platform.is('mobile')).toEqual(true); expect(platform.is('windows')).toEqual(false); @@ -185,7 +138,7 @@ export function run() { it('should set iphone via user agent', () => { let platform = new Platform(); platform.setUserAgent(IPHONE_UA); - platform.load(); + platform.load(null); expect(platform.is('mobile')).toEqual(true); expect(platform.is('windows')).toEqual(false); @@ -198,7 +151,7 @@ export function run() { it('should set ipad via user agent', () => { let platform = new Platform(); platform.setUserAgent(IPAD_UA); - platform.load(); + platform.load(null); expect(platform.is('mobile')).toEqual(true); expect(platform.is('windows')).toEqual(false);