Implementing the Ionic SplitPane component

April 1, 2017, 9:46 pm Categories:

Categories

The SplitPane component is one of the more interesting additions to Ionic 2 that allows for the creation of multi-view layouts similar to what you might experience, for example, when using the Mail or Settings applications on the iPad.

With the inclusion of this component it's now possible to create Ionic 2 apps that display additional UI options - such as menus or similar interactive components - as the viewport increases (I.e. when viewed on tablet or desktop devices - otherwise, by default on smaller viewports, these will be hidden from view).

Before we continue though the SplitPane component has a few requirements that you need to be aware of:

  • It must be wrapped around the root component
  • The main content area for a SplitPane component can be either an <ion-nav> or <ion-tabs> component but NOT an <ion-menu> component

The SplitPane will typically expand on screen sizes larger than 768px although this is able to be overridden programmatically (we'll discuss this later on).

What we'll be creating

With the previous considerations covered I want to take you through using the SplitPane component to create the following application for a fictitious digital agency (hence the type of menu options being displayed):

As you can see from the above screen capture the SplitPane component, when viewed on a desktop browser, displays both a side menu and the page content.

However when the application is scaled down within the browser (or viewed on a mobile device) the side menu is no longer displayed (instead a menu icon in the left hand side of the header bar allows the side menu to be accessed by the user):

As you can see it's a fairly simple application for what we'll be building in this tutorial and consists of the following resources:

  • SplitPane component
  • MenuController Component
  • List/Item components
  • Ion Icons

Nothing terribly complicated here but let's see how it all fits together.

Ready?

Okay - let's get started then!

Generating the application

From your command line utility (I.e. Terminal on Mac OS X or the Command Prompt in Windows) navigate to where you would store/organise your digital projects and use the Ionic CLI to create the following Ionic 2 project imaginatively titled split-pane:

ionic start split-pane blank --v2

Once created we'll need to change into the root of the split-pane project directory and generate the following pages:

ionic g page about
ionic g page locations
ionic g page contact
ionic g page projects
ionic g page services

These pages will subsequently be able to be accessed from the side menu of the SplitPane component (but we'll come to that shortly).

With the basic skeleton for the project created we can now move onto configuring the root module for the application.

Inside the /split-pane/src/app/app.module.ts file import and add the recently generated page components to the declarations and entryComponents sections of the @NgModule configuration object like so:

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { AboutPage } from '../pages/about/about';
import { ContactPage } from '../pages/contact/contact';
import { LocationsPage } from '../pages/locations/locations';
import { ProjectsPage } from '../pages/projects/projects';
import { ServicesPage } from '../pages/services/services';

import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    AboutPage,
    ContactPage,
    LocationsPage,
    ProjectsPage,
    ServicesPage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    AboutPage,
    ContactPage,
    LocationsPage,
    ProjectsPage,
    ServicesPage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

With these configurations in place we can now move onto the actual coding for implementing the SplitPane functionality.

Setting up the Root component navigation

As mentioned earlier the SplitPane component needs to be wrapped around the root component for an Ionic 2 project which means that the logic for managing the interactions with the side menu are probably best handled here too (within the /split-pane/src/app/app.component.ts file).

Doing so presents one HUGE problem though - within an Ionic 2 application navigation controllers are children of the root component which means that the NavController component CAN'T be injected for use.

As the official docs state:

You can’t inject NavController because any components that are navigation controllers 
are children of the root component so they aren’t available to be injected.

Hmm, NOT good.

This situation would seem to make using links from the side menu for the SplitPane component impossible wouldn't you say?

Not when you can use Angular's handy ViewChild class which allows parent components to interact with child components and make use of their methods.

In this context the ViewChild class will allow the NavController module to be injected for use with the root component by:

  1. Adding a template reference variable to the ion-nav component
  2. Using the @ViewChild decorator to 'hook' into the ion-nav component and manage the application navigation

How this works is fairly simple (as Angular, thankfully, performs all the background heavy lifting for managing that logic).

First we begin by importing the ViewChild and NavController modules into the root component:

import { Component, ViewChild } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';

Then we use the @ViewChild decorator to bind to a template reference variable (in this instance named content) and link this to a NavController object like so:

@ViewChild('content') _NAV : NavController

This now allows the parent component to access the NavController module and its methods to subsequently manage the navigation of child components from the application's side menu.

Finally, within the root component HTML file - /split-pane/src/app/app.component.html - the template reference variable named content is added to the ion-nav component as follows (prefixed with a #):

<ion-nav [root]="rootPage" main #content></ion-nav>

That now resolves the handling of navigation from the root component using the NavController module which means we can move onto implementing the side menu content and functionality for the SplitPane interface.

This will involve:

  • Importing the MenuController component
  • Defining arrays for handling the different sections of the navigation menu
  • Populating those arrays with data
  • Providing methods for navigating to different pages within the application

Let's go through this step by step.

First we import the MenuController:

import { MenuController, NavController, Platform } from 'ionic-angular';

Following from this we then define the arrays, towards the top of the root component class, that will store the names and links for each separate section of the side menu:

public services            : Array<{ name : string, icon : string, link : any}>    = [];
public communications      : Array<{ name : string, icon : string, link : any}>    = [];
public information         : Array<{ name : string, icon : string, link : any}>    = [];

Then, within the constructor for the root component, the MenuController module is initialised and the actual content for each array is subsequently configured:

constructor( public platform      : Platform, 
             public statusBar     : StatusBar, 
             public splashScreen  : SplashScreen,
             private _MENU        : MenuController) 
{
   this.services = [
   { 
      name : 'Website Design & Development', 
      icon : 'construct',
      link : ServicesPage
   },
   { 
      name : 'Content Management', 
      icon : 'folder-open',
      link : ServicesPage
   },
   { 
      name : 'iOS Apps', 
      icon : 'logo-apple',
      link : ServicesPage
   },
   { 
      name : 'Android Apps', 
      icon : 'logo-android',
      link : ServicesPage
   },
   { 
      name : 'Window Apps', 
      icon : 'logo-windows',
      link : ServicesPage
   }];



   this.information = [
   {
      name : 'Our Projects', 
      icon : 'cube',
      link : ProjectsPage
   },
   {
      name : 'Our Story', 
      icon : 'archive',
      link : AboutPage
   },
   {
      name : 'Our Team', 
      icon : 'people',
      link : AboutPage
   }];



   this.communications = [
   {
      name : 'Get In Touch', 
      icon : 'call',
      link : ContactPage
   },
   {
      name : 'Our Locations', 
      icon : 'pin',
      link : LocationsPage
   },
   {
      name : 'Instagram', 
      icon : 'logo-instagram',
      link : ContactPage
   },
   {
      name : 'Tweets', 
      icon : 'logo-twitter',
      link : ContactPage
   }];

Each array simply contains the name for each separate menu link, a specific ion-icon associated with that menu link and the actual component that the link will allow the user to navigate to.

Finally the following methods handle the actual navigation, courtesy of the NavController module (which was associated with the _NAV property through the @ViewChild decorator earlier), to the different pages of the application:

sectionToLoad(page)
{
   let link    : string     = page.link,
       section : string     = page.name;

   this._NAV.setRoot(link, section);
   this._MENU.close();
}



goHome()
{
   this._NAV.setRoot(HomePage);
}

The close method of the MenuController module is called within both of the above methods to ensure the side menu component is hidden once a page is navigated to.

That now completes the scripting for the root component - the script being shown in full below:

import { Component, ViewChild } from '@angular/core';
import { MenuController, NavController, Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

import { HomePage } from '../pages/home/home';
import { AboutPage } from '../pages/about/about';
import { ContactPage } from '../pages/contact/contact';
import { GalleryPage } from '../pages/gallery/gallery';
import { LocationsPage } from '../pages/locations/locations';
import { ProjectsPage } from '../pages/projects/projects';
import { ServicesPage } from '../pages/services/services';


@Component({
  templateUrl: 'app.html'
})
export class MyApp {
   @ViewChild('content') _NAV        : NavController
   rootPage                          : any = HomePage;


   public services                   : Array<{ name : string, icon : string, link : any}>    = [];
   public communications             : Array<{ name : string, icon : string, link : any}>    = [];
   public information                : Array<{ name : string, icon : string, link : any}>    = [];


   constructor( public platform      : Platform, 
                public statusBar     : StatusBar, 
                public splashScreen  : SplashScreen,
                private _MENU        : MenuController) 
   {


      /**
       * 
       * Services object for populating the application side menu
       * with content for the Services section links 
       */
        this.services = [
        { 
            name : 'Website Design & Development', 
            icon : 'construct',
            link : ServicesPage
         },
         { 
            name : 'Content Management', 
            icon : 'folder-open',
            link : ServicesPage
         },
         { 
            name : 'iOS Apps', 
            icon : 'logo-apple',
            link : ServicesPage
         },
         { 
            name : 'Android Apps', 
            icon : 'logo-android',
            link : ServicesPage
         },
         { 
            name : 'Window Apps', 
            icon : 'logo-windows',
            link : ServicesPage
         },
      ];



      /**
       * 
       * Information object for populating the application side menu
       * with content for the About Us section links 
       */
        this.information = [
         {
            name : 'Our Projects', 
            icon : 'cube',
            link : ProjectsPage
         },
         {
            name : 'Our Story', 
            icon : 'archive',
            link : AboutPage
         },
         {
            name : 'Our Team', 
            icon : 'people',
            link : AboutPage
         }
      ];



      /**
       * 
       * Communications object for populating the application side menu
       * with content for the Communications section links 
       */
        this.communications = [
         {
            name : 'Get In Touch', 
            icon : 'call',
            link : ContactPage
         },
         {
            name : 'Our Locations', 
            icon : 'pin',
            link : LocationsPage
         },
         {
            name : 'Instagram', 
            icon : 'logo-instagram',
            link : ContactPage
         },
         {
            name : 'Tweets', 
            icon : 'logo-twitter',
            link : ContactPage
         }
      ];




      platform.ready()
      .then(() => 
      {
         // Okay, so the platform is ready and our plugins are available.
         // Here you can do any higher level native things you might need.
         statusBar.styleDefault();
         splashScreen.hide();
      });
   }



   /**
    * 
    * Determine which page to load
    * @param page   required         The page component object 
    */
   sectionToLoad(page)
   {
      let link      : string     = page.link,
          section : string     = page.name;

      this._NAV.setRoot(link, section);
      this._MENU.close();
   }



   goHome()
   {
      this._NAV.setRoot(HomePage);
      this._MENU.close();
   }
}

Now let's move onto working with the SplitPane component itself.

Structuring the component HTML

At its most basic level the SplitPane component would be structured as follows:

<ion-split-pane>

   <!--  our side menu  -->
   <ion-menu [content]="content">

   </ion-menu>

   <!-- the main content -->
   <ion-nav [root]="rootPage" main #content></ion-nav>

</ion-split-pane>

Notice the property binding of content in the <ion-menu> markup?

The value for this is associated with the template reference variable named content on the <ion-nav> component.

This combination of property binding and template reference variable allows the menu to target the main content area for loading content into - which is exactly what we need.

You may also have noticed an attribute of main attached to the above <ion-nav> component. This attribute of main allows the SplitPane component to make that the central component on larger screen sizes.

Controlling the SplitPane display

Remember earlier when I stated that the SplitPane component would typically begin to expand on viewport sizes above 768 pixels? Remember that I also stated this was able to be overridden?

This can be accomplished using the when input which accepts any valid CSS media query value like so:

<ion-split-pane when="(min-width: 500px)">
...
</ion-split-pane>

Alternatively a boolean value can be passed into the when input instead:

<ion-split-pane [when]="isDesktop">
...
</ion-split-pane>

Which would be generated from within the component TypeScript class like so:

class MyClass {
   public isDesktop : boolean = false;
   constructor(){}

}

Optionally some pre-defined media queries could be used instead:

<!-- Pre-defined options include:
     1. "xs"
     2. "sm"
     3. "md"
     4. "lg"
     5."xl" 
-->
<ion-split-pane when="lg">
...
</ion-split-pane>

For the purpose of this tutorial I'm simply going to pass a CSS media query with a hard coded value into the when input to control the display of the SplitPane component.

Crafting the application markup

Now that we understand how to implement and control the display of the SplitPane component let's wrap things up by adding the necessary HTML for our application:

<ion-split-pane when="(min-width: 1000px)">

   <!--  our side menu  -->
   <ion-menu [content]="content">
      <ion-header>
         <ion-toolbar>
            <ion-title>Menu</ion-title>
         </ion-toolbar>
      </ion-header>

      <ion-content>
         <ion-list>

            <ion-item-group>
               <ion-item (click)="goHome()">
                  <ion-icon name="home" item-left></ion-icon>
                  Home
               </ion-item>
            </ion-item-group>


            <ion-item-group>
               <ion-item-divider color="light">Services</ion-item-divider>
                  <ion-item 
                     *ngFor="let service of services" 
                     (click)="sectionToLoad(service)">
                     <ion-icon name="{{ service.icon }}" item-left></ion-icon>
                     {{ service.name }}
                  </ion-item>
            </ion-item-group>



            <ion-item-group>
               <ion-item-divider color="light">About Us</ion-item-divider>
                <ion-item 
                   *ngFor="let info of information" 
                   (click)="sectionToLoad(info)">
                   <ion-icon name="{{ info.icon }}" item-left></ion-icon>
                   {{ info.name }}
                </ion-item>
            </ion-item-group>



            <ion-item-group>
               <ion-item-divider color="light">Communication</ion-item-divider>
                  <ion-item 
                  *ngFor="let comms of communications" 
                  (click)="sectionToLoad(comms)">
                   <ion-icon name="{{ comms.icon }}" item-left></ion-icon>
                   {{ comms.name }}
                </ion-item>
            </ion-item-group>



         </ion-list>
      </ion-content>
   </ion-menu>


  <!-- the main content -->
  <ion-nav [root]="rootPage" main #content></ion-nav>
</ion-split-pane>

This should be fairly self-explanatory but I'll quickly break down the following key aspects.

The SplitPane component is designed to display the side menu when the application is accessed on platforms with a viewport width of 1000 pixels or greater:

<ion-split-pane when="(min-width: 1000px)">

Linking back to the HomePage component is accomplished with the following HTML:

<ion-item-group>
   <ion-item (click)="goHome()">
      <ion-icon name="home" item-left></ion-icon>
      Home
   </ion-item>
</ion-item-group>

While each menu section, and its corresponding options, is generated programmatically (drawing its data from the arrays that we defined and populated earlier in the tutorial) as shown in the following snippet:

<ion-item-group>
   <ion-item-divider color="light">Services</ion-item-divider>
   <ion-item *ngFor="let service of services" (click)="sectionToLoad(service)">
      <ion-icon name="{{ service.icon }}" item-left></ion-icon>
      {{ service.name }}
   </ion-item>
</ion-item-group>

Final Step(s)

In order to access the side menu component on viewport widths smaller than 1000 pixels the following <ion-header> snippet will need to be added to each and every component view template contained within the /split-pane/src/pages directory:

<ion-header>

   <ion-navbar>
      <button 
         ion-button 
         menuToggle>
         <ion-icon name="menu"></ion-icon>
      </button>
      <ion-title >Add some title here or generate it dynamically</ion-title>
   </ion-navbar>

</ion-header>

<ion-content padding>
   ...
</ion-content>

Without this simple addition you are NOT going to be able to access the side menu on those smaller viewport widths.

I'm sure you don't need telling that this would NOT make for a good user experience!

Testing times ahead

Now that the coding is complete all that remains is to actually test the application in a desktop browser using the following command via the Ionic CLI:

ionic serve

Once published the application should be rendered to the browser and able to be navigated like so:

With the menu for the SplitPane component being automatically hidden on viewports with a width smaller than 1000 pixels (as shown in the first screen capture below).

When this happens the side menu is able to be accessed through the header bar menu button (as shown in the second screen capture below).

Such a simple but effective UI component isn't it?

Now we have the capacity to build applications that behave smoothly and fluidly across both mobile and desktop platforms which, when you think about it, is actually pretty incredible.

In Summary

In this relatively basic tutorial I've demonstrated how the SplitPane component can be used to generate an application with a side menu that is revealed when displayed on viewports with a width equal to or larger than 1000px.

How you use this component in your projects though is dictated by what you need to accomplish.

I can think of the following applications where implementing a multi-view layout would be ideal:

  • Dictionary or Glossary of terms
  • Image gallery viewer
  • File upload module

I'm sure you can think up of further usage case scenarios and I'd be interested to see what you can create using this component.

Feel free to share your thoughts, findings or suggestions in the comment section below.

If you enjoyed what you've read here then please sign up to my mailing list and, if you haven't done so already, take a look at my e-book: Mastering Ionic for further information about using UI components within Ionic.

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top