233 lines
8.9 KiB
TypeScript

// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo
import { Component, ViewChild, Input, ElementRef, OnInit, Optional, OnDestroy } from '@angular/core';
import { NavController, Nav, ViewController } from 'ionic-angular';
import { Subscription } from 'rxjs';
/**
* Directive to create a split view layout.
*
* @description
* To init/change the right pane contents (content pane), inject this component in the master page.
* @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
* Then use the push function to load.
*
* Accepts the following params:
*
* @param {string|boolean} [when] When the split-pane should be shown. Can be a CSS media query expression, or a shortcut
* expression. Can also be a boolean expression. Check split-pane component documentation for more information.
*
* Example:
*
* <core-split-view [when]="lg">
* <ion-content><!-- CONTENT TO SHOW ON THE LEFT PANEL (MENU) --></ion-content>
* </core-split-view>
*/
@Component({
selector: 'core-split-view',
templateUrl: 'core-split-view.html'
})
export class CoreSplitViewComponent implements OnInit, OnDestroy {
@ViewChild('detailNav') detailNav: Nav;
@Input() when?: string | boolean = 'md';
protected isEnabled = false;
protected masterPageName = '';
protected masterPageIndex = 0;
protected loadDetailPage: any = false;
protected element: HTMLElement; // Current element.
protected detailsDidEnterSubscription: Subscription;
protected masterCanLeaveOverridden = false;
protected originalMasterCanLeave: Function;
// Empty placeholder for the 'detail' page.
detailPage: any = null;
constructor(@Optional() private masterNav: NavController, element: ElementRef) {
this.element = element.nativeElement;
}
/**
* Component being initialized.
*/
ngOnInit(): void {
// Get the master page name and set an empty page as a placeholder.
this.masterPageName = this.masterNav.getActive().component.name;
this.masterPageIndex = this.masterNav.indexOf(this.masterNav.getActive());
this.emptyDetails();
this.handleCanLeave();
}
/**
* Get the details NavController. If split view is not enabled, it will return the master nav.
*
* @return {NavController} Details NavController.
*/
getDetailsNav(): NavController {
if (this.isEnabled) {
return this.detailNav;
} else {
return this.masterNav;
}
}
/**
* Get the master NavController.
*
* @return {NavController} Master NavController.
*/
getMasterNav(): NavController {
return this.masterNav;
}
/**
* Handle ionViewCanLeave functions in details page. By default, this function isn't captured by Ionic when
* clicking the back button, it only uses the one in the master page.
*/
handleCanLeave(): void {
// Listen for the didEnter event on the details nav to detect everytime a page is loaded.
this.detailsDidEnterSubscription = this.detailNav.viewDidEnter.subscribe((detailsViewController: ViewController) => {
const masterViewController = this.masterNav.getActive();
if (this.masterCanLeaveOverridden) {
// We've overridden the can leave of the master page for a previous details page. Restore it.
masterViewController.instance.ionViewCanLeave = this.originalMasterCanLeave;
this.originalMasterCanLeave = undefined;
this.masterCanLeaveOverridden = false;
}
if (detailsViewController && detailsViewController.instance && detailsViewController.instance.ionViewCanLeave) {
// The details page defines a canLeave function. Check if the master page also defines one.
if (masterViewController.instance.ionViewCanLeave) {
// Master page also defines a canLeave function, store it because it will be overridden.
this.originalMasterCanLeave = masterViewController.instance.ionViewCanLeave;
}
// Override the master canLeave function so it also calls the details canLeave.
this.masterCanLeaveOverridden = true;
masterViewController.instance.ionViewCanLeave = (): Promise<any> => {
// Always return a Promise.
return Promise.resolve().then(() => {
if (this.originalMasterCanLeave) {
// First call the master canLeave.
const result = this.originalMasterCanLeave();
if (typeof result == 'boolean' && !result) {
// User cannot leave, return a rejected promise so the details canLeave isn't executed.
return Promise.reject(null);
} else {
return result;
}
}
}).then(() => {
// User can leave the master page. Check if he can also leave the details page.
return detailsViewController.instance.ionViewCanLeave();
});
};
}
});
}
/**
* Check if both panels are shown. It depends on screen width.
*
* @return {boolean} If split view is enabled.
*/
isOn(): boolean {
return this.isEnabled;
}
/**
* Push a page to the navigation stack. It will decide where to load it depending on the size of the screen.
*
* @param {any} page The component class or deeplink name you want to push onto the navigation stack.
* @param {any} params Any NavParams you want to pass along to the next view.
*/
push(page: any, params?: any): void {
if (this.isEnabled) {
this.detailNav.setRoot(page, params);
} else {
this.loadDetailPage = {
component: page,
data: params
};
this.masterNav.push(page, params);
}
}
/**
* Set the details panel to default info.
*/
emptyDetails(): void {
this.loadDetailPage = false;
this.detailNav.setRoot('CoreSplitViewPlaceholderPage');
}
/**
* Splitpanel visibility has changed.
*
* @param {Boolean} isOn If it fits both panels at the same time.
*/
onSplitPaneChanged(isOn: boolean): void {
this.isEnabled = isOn;
if (this.masterNav && this.detailNav) {
(isOn) ? this.activateSplitView() : this.deactivateSplitView();
}
}
/**
* Enable the split view, show both panels and do some magical navigation.
*/
activateSplitView(): void {
const currentView = this.masterNav.getActive(),
currentPageName = currentView.component.name;
if (this.masterNav.getPrevious() && this.masterNav.getPrevious().component.name == this.masterPageName) {
if (currentPageName != this.masterPageName) {
// CurrentView is a 'Detail' page remove it from the 'master' nav stack.
this.masterNav.pop();
// And add it to the 'detail' nav stack.
this.detailNav.setRoot(currentView.component, currentView.data);
} else if (this.loadDetailPage) {
// MasterPage is shown, load the last detail page if found.
this.detailNav.setRoot(this.loadDetailPage.component, this.loadDetailPage.data);
}
this.loadDetailPage = false;
}
}
/**
* Disabled the split view, show only one panel and do some magical navigation.
*/
deactivateSplitView(): void {
const detailView = this.detailNav.getActive(),
currentPageName = detailView.component.name;
if (currentPageName != 'CoreSplitViewPlaceholderPage') {
// Current detail view is a 'Detail' page so, not the placeholder page, push it on 'master' nav stack.
this.masterNav.insert(this.masterPageIndex + 1, detailView.component, detailView.data);
}
}
/**
* Component being destroyed.
*/
ngOnDestroy(): void {
this.detailsDidEnterSubscription && this.detailsDidEnterSubscription.unsubscribe();
}
}