Skip to content

Commit 84f37cf

Browse files
committed
feat(backButton): register back button actions
1 parent d98f3c9 commit 84f37cf

File tree

3 files changed

+205
-69
lines changed

3 files changed

+205
-69
lines changed

src/platform/platform.ts

Lines changed: 126 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,28 @@ export class Platform {
4141
private _readyPromise: Promise<any>;
4242
private _readyResolve: any;
4343
private _resizeTm: any;
44-
private _zone: NgZone;
44+
private _bbActions: BackButtonAction[] = [];
45+
46+
zone: NgZone;
4547

4648
constructor(platforms: string[] = []) {
4749
this._platforms = platforms;
4850
this._readyPromise = new Promise(res => { this._readyResolve = res; } );
51+
52+
this.backButton.subscribe(() => {
53+
// the hardware back button event has been fired
54+
console.debug('hardware back button');
55+
56+
// decide which backbutton action should run
57+
this.runBackButtonAction();
58+
});
4959
}
5060

5161
/**
5262
* @private
5363
*/
5464
setZone(zone: NgZone) {
55-
this._zone = zone;
65+
this.zone = zone;
5666
}
5767

5868

@@ -212,7 +222,7 @@ export class Platform {
212222
* such as Cordova or Electron, then it uses the default DOM ready.
213223
*/
214224
triggerReady(readySource: string) {
215-
this._zone.run(() => {
225+
this.zone.run(() => {
216226
this._readyResolve(readySource);
217227
});
218228
}
@@ -232,14 +242,14 @@ export class Platform {
232242
}
233243

234244
/**
235-
* Set the app's language direction, which will update the `dir` attribute
236-
* on the app's root `<html>` element. We recommend the app's `index.html`
237-
* file already has the correct `dir` attribute value set, such as
238-
* `<html dir="ltr">` or `<html dir="rtl">`. This method is useful if the
239-
* direction needs to be dynamically changed per user/session.
240-
* [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
241-
* @param {string} dir Examples: `rtl`, `ltr`
242-
*/
245+
* Set the app's language direction, which will update the `dir` attribute
246+
* on the app's root `<html>` element. We recommend the app's `index.html`
247+
* file already has the correct `dir` attribute value set, such as
248+
* `<html dir="ltr">` or `<html dir="rtl">`. This method is useful if the
249+
* direction needs to be dynamically changed per user/session.
250+
* [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
251+
* @param {string} dir Examples: `rtl`, `ltr`
252+
*/
243253
setDir(dir: string, updateDocument: boolean) {
244254
this._dir = (dir || '').toLowerCase();
245255
if (updateDocument !== false) {
@@ -270,14 +280,14 @@ export class Platform {
270280
}
271281

272282
/**
273-
* Set the app's language and optionally the country code, which will update
274-
* the `lang` attribute on the app's root `<html>` element.
275-
* We recommend the app's `index.html` file already has the correct `lang`
276-
* attribute value set, such as `<html lang="en">`. This method is useful if
277-
* the language needs to be dynamically changed per user/session.
278-
* [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
279-
* @param {string} language Examples: `en-US`, `en-GB`, `ar`, `de`, `zh`, `es-MX`
280-
*/
283+
* Set the app's language and optionally the country code, which will update
284+
* the `lang` attribute on the app's root `<html>` element.
285+
* We recommend the app's `index.html` file already has the correct `lang`
286+
* attribute value set, such as `<html lang="en">`. This method is useful if
287+
* the language needs to be dynamically changed per user/session.
288+
* [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
289+
* @param {string} language Examples: `en-US`, `en-GB`, `ar`, `de`, `zh`, `es-MX`
290+
*/
281291
setLang(language: string, updateDocument: boolean) {
282292
this._lang = language;
283293
if (updateDocument !== false) {
@@ -302,125 +312,171 @@ export class Platform {
302312
// called by engines (the browser)that do not provide them
303313

304314
/**
305-
* @private
306-
*/
315+
* @private
316+
*/
307317
exitApp() {}
308318

309319
// Events meant to be triggered by the engine
310320
// **********************************************
311321

312322
/**
313-
* The back button event is emitted when the user presses the native
314-
* platform's back button, also referred to as the "hardware" back button.
315-
* This event is only emitted within Cordova apps running on Android and
316-
* Windows platforms. This event is not fired on iOS since iOS doesn't come
317-
* with a hardware back button in the same sense an Android or Windows device
318-
* does. It's important to note that this event does not emit when the Ionic
319-
* app's back button within the navbar is clicked, but this event is only
320-
* referencing the platform's hardware back button.
321-
*/
323+
* @private
324+
*/
322325
backButton: EventEmitter<Event> = new EventEmitter();
323326

324327
/**
325-
* The pause event emits when the native platform puts the application
326-
* into the background, typically when the user switches to a different
327-
* application. This event would emit when a Cordova app is put into
328-
* the background, however, it would not fire on a standard web browser.
329-
*/
328+
* The pause event emits when the native platform puts the application
329+
* into the background, typically when the user switches to a different
330+
* application. This event would emit when a Cordova app is put into
331+
* the background, however, it would not fire on a standard web browser.
332+
*/
330333
pause: EventEmitter<Event> = new EventEmitter();
331334

332335
/**
333-
* The resume event emits when the native platform pulls the application
334-
* out from the background. This event would emit when a Cordova app comes
335-
* out from the background, however, it would not fire on a standard web browser.
336-
*/
336+
* The resume event emits when the native platform pulls the application
337+
* out from the background. This event would emit when a Cordova app comes
338+
* out from the background, however, it would not fire on a standard web browser.
339+
*/
337340
resume: EventEmitter<Event> = new EventEmitter();
338341

342+
/**
343+
* The back button event is triggered when the user presses the native
344+
* platform's back button, also referred to as the "hardware" back button.
345+
* This event is only used within Cordova apps running on Android and
346+
* Windows platforms. This event is not fired on iOS since iOS doesn't come
347+
* with a hardware back button in the same sense an Android or Windows device
348+
* does.
349+
*
350+
* Registering a hardware back button action and setting a priority allows
351+
* apps to control which action should be called when the hardware back
352+
* button is pressed. This method decides which of the registered back button
353+
* actions has the highest priority and should be called.
354+
*
355+
* @param {Function} callback Called when the back button is pressed,
356+
* if this registered action has the highest priority.
357+
* @param {number} priority Set the priority for this action. Only the highest priority will execute. Defaults to `0`.
358+
* @returns {Function} A function that, when called, will unregister
359+
* the its back button action.
360+
*/
361+
registerBackButtonAction(fn: Function, priority: number = 0): Function {
362+
let action: BackButtonAction = {fn, priority};
363+
364+
this._bbActions.push(action);
365+
366+
// return a function to unregister this back button action
367+
return () => {
368+
let index = this._bbActions.indexOf(action);
369+
if (index > -1) {
370+
this._bbActions.splice(index, 1);
371+
}
372+
};
373+
}
374+
375+
/**
376+
* @private
377+
*/
378+
runBackButtonAction() {
379+
// decide which one back button action should run
380+
let winner: BackButtonAction = null;
381+
this._bbActions.forEach((action: BackButtonAction) => {
382+
if (!winner || action.priority >= winner.priority) {
383+
winner = action;
384+
}
385+
});
386+
387+
// run the winning action if there is one
388+
winner && winner.fn && winner.fn();
389+
}
390+
339391

340392
// Getter/Setter Methods
341393
// **********************************************
342394

343395
/**
344-
* @private
345-
*/
396+
* @private
397+
*/
346398
setUrl(url: string) {
347399
this._url = url;
348400
this._qs = getQuerystring(url);
349401
}
350402

351403
/**
352-
* @private
353-
*/
404+
* @private
405+
*/
354406
url(): string {
355407
return this._url;
356408
}
357409

358410
/**
359-
* @private
360-
*/
411+
* @private
412+
*/
361413
query(key: string): string {
362414
return (this._qs || {})[key];
363415
}
364416

365417
/**
366-
* @private
367-
*/
418+
* @private
419+
*/
368420
setUserAgent(userAgent: string) {
369421
this._ua = userAgent;
370422
}
371423

372424
/**
373-
* @private
374-
*/
425+
* @private
426+
*/
375427
userAgent(): string {
376428
return this._ua || '';
377429
}
378430

379431
/**
380-
* @private
381-
*/
432+
* @private
433+
*/
382434
setNavigatorPlatform(navigatorPlatform: string) {
383435
this._bPlt = navigatorPlatform;
384436
}
385437

386438
/**
387-
* @private
388-
*/
439+
* @private
440+
*/
389441
navigatorPlatform(): string {
390442
return this._bPlt || '';
391443
}
392444

393445
/**
394-
* @private
395-
*/
446+
* Gets the width of the platform's viewport using `window.innerWidth`.
447+
* Using this method is preferred since the dimension is a cached value,
448+
* which reduces the chance of multiple and expensive DOM reads.
449+
*/
396450
width(): number {
397451
return windowDimensions().width;
398452
}
399453

400454
/**
401-
* @private
402-
*/
455+
* Gets the height of the platform's viewport using `window.innerHeight`.
456+
* Using this method is preferred since the dimension is a cached value,
457+
* which reduces the chance of multiple and expensive DOM reads.
458+
*/
403459
height(): number {
404460
return windowDimensions().height;
405461
}
406462

407463
/**
408-
* @private
409-
*/
464+
* Returns `true` if the app is in portait mode.
465+
*/
410466
isPortrait(): boolean {
411467
return this.width() < this.height();
412468
}
413469

414470
/**
415-
* @private
416-
*/
471+
* Returns `true` if the app is in landscape mode.
472+
*/
417473
isLandscape(): boolean {
418474
return !this.isPortrait();
419475
}
420476

421477
/**
422-
* @private
423-
*/
478+
* @private
479+
*/
424480
windowResize() {
425481
let self = this;
426482
clearTimeout(self._resizeTm);
@@ -440,7 +496,6 @@ export class Platform {
440496

441497
/**
442498
* @private
443-
* @returns Unregister function
444499
*/
445500
onResize(cb: Function): Function {
446501
let self = this;
@@ -466,8 +521,8 @@ export class Platform {
466521
}
467522

468523
/**
469-
* @private
470-
*/
524+
* @private
525+
*/
471526
static registry() {
472527
return platformRegistry;
473528
}
@@ -782,3 +837,8 @@ export interface PlatformVersion {
782837
major?: number;
783838
minor?: number;
784839
}
840+
841+
interface BackButtonAction {
842+
fn: Function;
843+
priority: number;
844+
}

src/platform/registry.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,19 @@ Platform.register({
177177

178178
// add cordova listeners to emit platform events
179179
doc.addEventListener('backbutton', function(ev: Event) {
180-
p.backButton.emit(ev);
180+
p.zone.run(() => {
181+
p.backButton.emit(ev);
182+
});
181183
});
182184
doc.addEventListener('pause', function(ev: Event) {
183-
p.pause.emit(ev);
185+
p.zone.run(() => {
186+
p.pause.emit(ev);
187+
});
184188
});
185189
doc.addEventListener('resume', function(ev: Event) {
186-
p.resume.emit(ev);
190+
p.zone.run(() => {
191+
p.resume.emit(ev);
192+
});
187193
});
188194

189195
// cordova has its own exitApp method

0 commit comments

Comments
 (0)