Tutorial

Ionic Split Panel Component

Recently the Ionic team released a preview of the split panel component. In this blog post, I want to take a look at it in a bit more depth. For those not familiar with this visual design pattern, it is very similar to the side menu layout. The main difference is that menu usually remains onscreen. Gmail is a great example of this layout. As Ionic begins to expand from the mobile space into progressive web apps and even the desktop, this layout is a common pattern.
Since this component is similar to the side menu layout, let’s scaffold our app using that template.
$ ionic start splitPanelDemo sidemenu --v2
Once the project is ready, go ahead and change the working directory to splitPanelDemo. Since the component is still under development, we need to swap out the release version of Ionic for the nightly build.
$ npm install --save ionic-angular@2.0.1-201702161925
With our copy of Ionic replaced, go ahead and open app.html. There are just a few things we need to do to convert our side menu template to use the split panel layout.
< ion-split-panel >
  < ion-menu [content]="content" when="xs" >
    < ion-header >
      < ion-toolbar >
        < ion-title >Menu< /ion-title >
      < /ion-toolbar >
    < /ion-header >

    < ion-content >
      < ion-list >
        < button menuClose ion-item *ngFor="let p of pages"             (click)="openPage(p)" >
        {{p.title}}
      < /button >
      < /ion-list >
    < /ion-content >

  < /ion-menu >

  < ion-nav main [root]="rootPage" #content swipeBackEnabled="false" >< /ion-nav >
< /ion-split-panel >
We need to tell the component when the ‘menu’ should be displayed. The typical UX flow is to have the split pane hide when the screen or viewport become reduced. The component has the following breakpoints defined:
label min-width
xs 0px
sm 576px
md 768px
lg 992px
xl 1200px
never
By passing one of these strings to our when attribute we can control when our menu is shown. Another item to note, the min-width our split panel is 270px and it set to be no larger than 28% of the viewport. All these values are defined within the components SASS files.
< ion-menu [content]="content" when="xs" >
The last adjustment we need to denote the ‘main‘ content for the split pane. For this, just include the main directive to
< ion-nav main [root]="rootPage" #content swipeBackEnabled="false" >
 Running $ ionic serve, will produce this:
screencapture-localhost-8100-1487963384735

The sidemenu template being rendered as a split panel layout.

If you include menuToggle on the header of the main pages, the split panel will understand that directive and use it when the split panel is hidden.
Now, this initial sample is not much more than sidemenu with the expose-aside-when value that was available in v1. Let’s explore a more complex sample.
In this sample, we will enable the split panel to have its own navigation stack that it independent of the main content’s navigation stack. This was a design pattern that I was never able to build using Ionic v1. I had several app ideas that would have been a perfect match for it (yes, I am starting to flesh those apps out now).
First, let’s generate a collection of new pages using the Ionic generate command:
$ ionic g page Main
$ ionic g page SideNav
$ ionic g page SideNav2
$ ionic g page View1
 Next, make sure you update app.module.ts to import these new views. In addition, take note of setting both the root and sideRoot variables, as well as setting myApp within the @NgModule.
import { NgModule, ErrorHandler, Component } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MainPage } from '../pages/main/main';
import { SideNavPage } from '../pages/side-nav/side-nav';
import { SideNav2Page } from '../pages/side-nav2/side-nav2';
import { View1Page } from '../pages/view1/view1';

@Component({
 templateUrl: 'app.html'
})
export class myApp {
 root = MainPage;
 sideRoot = SideNavPage;
}

@NgModule({
 declarations: [
 myApp,
 MainPage,
 SideNavPage,
 SideNav2Page,
 View1Page
 ],
 imports: [
 IonicModule.forRoot(myApp)
 ],
 bootstrap: [IonicApp],
 entryComponents: [
 myApp,
 MainPage,
 SideNavPage,
 SideNav2Page,
 View1Page
 ],
 providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
})
export class AppModule {}
Next, let’s adjust the app.html file for our new structure. Instead of directly defining the content of the split panel, we are now just including an element and setting its root property to sideRoot. We will use this reference to populate the content.
< ion-split-panel when="sm" >

  < ion-menu [content]="content" >
    < ion-nav [root]="sideRoot" >< /ion-nav >
  < /ion-menu >

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

< /ion-split-panel >

Since both of these containers had their own navigation stack, we can move through our application independently. Let’s give our various pages some content first so we can see all this in action.

Change side-nav.html to:

< ion-header >

< ion-navbar >
< ion-title >Components< /ion-title >
< /ion-navbar >

< /ion-header >


< ion-content >
< ion-list >
< button menuClose ion-item *ngFor="let p of pages" (click)="displaySubNav(p)" >
{{p.title}}
< /button >
< /ion-list >
< /ion-content >

and side-nav.ts to

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { SideNav2Page } from '../side-nav2/side-nav2';

@Component({
 selector: 'page-side-nav',
 templateUrl: 'side-nav.html'
})

export class SideNavPage {
 pages: Array<{ title: string, component: any }>;

 constructor(public navCtrl: NavController, public navParams: NavParams) {
 this.pages = [
{ title: 'Action Sheets', component: SideNav2Page },
{ title: 'Alerts', component: SideNav2Page },
{ title: 'Badges', component: SideNav2Page },
{ title: 'Buttons', component: SideNav2Page },
{ title: 'Cards', component: SideNav2Page },
{ title: 'Checkbox', component: SideNav2Page },
{ title: 'DateTime', component: SideNav2Page },
{ title: 'FABs', component: SideNav2Page },
{ title: 'Gestures', component: SideNav2Page },
{ title: 'Grid', component: SideNav2Page },
{ title: 'Icons', component: SideNav2Page },
{ title: 'Inputs', component: SideNav2Page },
{ title: 'Lists', component: SideNav2Page },
{ title: 'Loading', component: SideNav2Page },
{ title: 'Menus', component: SideNav2Page },
{ title: 'Modals', component: SideNav2Page },
{ title: 'Navigation', component: SideNav2Page },
{ title: 'Popover', component: SideNav2Page },
{ title: 'Radio', component: SideNav2Page },
{ title: 'Range', component: SideNav2Page },
{ title: 'Searchbar', component: SideNav2Page },
{ title: 'Segment', component: SideNav2Page },
{ title: 'Select', component: SideNav2Page },
{ title: 'Slides', component: SideNav2Page },
{ title: 'Tabs', component: SideNav2Page },
{ title: 'Toast', component: SideNav2Page },
{ title: 'Toggle', component: SideNav2Page },
{ title: 'Toolbar', component: SideNav2Page }
];

}

 displaySubNav(thePage:any) {
  this.navCtrl.push(thePage.component);
 }
}

Next, let’s change the side-nav2.html file to be this:

< ion-header >

 < ion-navbar >
  < ion-title >Details< /ion-title >
 < /ion-navbar >

< /ion-header >

< ion-content >
 < p >New content< /p >
< /ion-content >

We don’t need to change the side-nav2.ts for this simple demo. Let’s change our main.html file this:

< ion-header >

 < ion-navbar >
  < ion-title >Main< /ion-title >
 < /ion-navbar >

< /ion-header >

< ion-content padding >
 < button ion-button primary (click)="goNewView()" >Go View 1< /button >
< /ion-content >

and the main.ts to:

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { View1Page } from '../view1/view1';

@Component({
 selector: 'page-main',
 templateUrl: 'main.html'
})
export class MainPage {

constructor(public navCtrl: NavController, public navParams: NavParams) { }

ionViewDidLoad() {
 console.log('ionViewDidLoad MainPage');
 }

goNewView() {
 this.navCtrl.push(View1Page);
 }
}

Save the files, and try it out.

split-panel-demo

Independent Ionic navigation in the split panel layout.

The source code for this demo is available at my GitHub Repo.

Learn PhoneGap Build: The Basics

473880-636021952776340473_338x600_thumb

PhoneGap and its open-source sister Apache Cordova simplify cross-platform app development. You can code an app once, and then compile it to run anywhere: iOS, Android, or Windows Phone. PhoneGap Build is the cloud-based version, which allows you to take apps built with HTML, CSS, and JavaScript and compile them into native, store-ready mobile apps. All without any SDKs.

In this course, Chris Griffith introduces the PhoneGap ecosystem and the basics of PhoneGap Build. He shows how to set up an account for development and create, configure, and compile your first project with PhoneGap Build. Once you’ve mastered these fundamentals, Chris shows how to extend your app plugins, debug your app, and then prep it for release in the App Store, Google Play store, or Windows Store.

Duration: 1 hr, 24 minutes

Check it out!

Ionic 2 Full Swipe

Note: I will be updating this to RC0 shortly. I do know there is a minor issue with the button’s width not responding to the expanded space.

While working on my upcoming book on the Ionic framework from O’Reilly, I was reading over the blog post about some new additions to the <ion-item-sliding> component supporting a full swipe gesture. I hopped over to the docs to see if they explained how to enable it. Unfortunately, they did not have anything posted yet, so I began to deconstruct it from the code sample. Here is the sample:

<ion-item-sliding>
  <ion-item> Item 1 </ion-item>
  <ion-item-options side="left">
    <button>Mail</button>
  </ion-item-options>
  <ion-item-options side="right" (ionSwipe)="saveItem(item)">
    <button danger>
      Trash
    </button>  
    <button secondary expandable (click)="saveItem(item)">  
       Save
    </button>  
  </ion-item-options> 
</ion-item-sliding>

To support this interaction, two things must be added to our<ion-item-sliding>. First, we need to add the expandable directive to the button.

<button secondary expandable (click)=”saveItem(item)”>

This allows for the visual feedback of the button growing as the user swipes across the item.

swipe_demo

Typically this is attached to the item that is closest to the end of the row. To listen for the swipe gesture, we only need to include an ionSwipe event listener to the <ion-item-options> component. This listener should call the same function as if the user tapped on the item. Here is a simple Plunker showcasing it.

Learn Apache Cordova: The Basics Released!

lynda-cordova

Apache Cordova is the open-source version of PhoneGap, the leading tool for cross-platform app development. It’s a write-once, run-anywhere solution specifically designed for mobile. But to ensure a smooth cross-platform workflow, it helps to know some setup and configuration basics. In this course, Chris Griffith introduces Apache Cordova and the PhoneGap ecosystem, including the two command-line interface (CLI) tools and the PhoneGap desktop app. He shows how to set up your local system and how to create, configure, and build your first project with the Cordova CLI. Once you’ve mastered the fundamentals, Chris shows how to extend your app with native and third-party plugins that enable features such as QR code detection and geolocation, and debug your app, preview it in an emulator or on an actual device, and then prep it for release in the Apple Store or on Google Play.

Duration: 1 hr, 24 minutes

Check it out!

Centering Gallery Elements

Recently, I was rebuilding one of my websites to be responsive. It is important to take advantage of the capabilities of modern browsers and be responsive, to support the growing use of the site on mobile devices. One portion of the design was a set of image gallery pages. These pages are pretty straight-forward, up to 5 columns of photos per row centered within the width of the page. As the window width reduces, the number of columns reduces. Again, nothing out of the ordinary with this design.

figure1

Block version of the design

 

I began by centering the containing div by setting the CSS properties of margin-left and the margin-right values to “auto”. That positioned the containing div correctly and it responded as expected as the browser width was changed. However, as the width of the window reduced, the thumbnails would begin to start to flow down to the next row, but they would display centered within the containing div. Not exactly the design I was looking for.

Centered elements

Centered elements

I applied a float:left to the thumbnails, and my centering last row was fixed! However, it introduced a new problem–the thumbnails no longer appeared to be centered as a group within the page. Adding the float to the thumbnails introduced extra spacing between the last column and the right boundary of the containing div.

Highlighting the margin right issue

Highlighting the margin right issue

I knew that had to be solved, so I began looking at many of my favorite tutorial sites, and found nothing. Lots of great samples of gallery pages, but all using the full width of browser in their samples or the thumbnail sizes would adapt to their widths. Nothing seemed to fit my design.

Next, I decided to look at using Flexbox. I had made the choice to only support “modern” browsers, so I knew that I did not need to worry too much about compatibility issues. But the same issues remained as I experimented with the various flex layout options; the last row items would be centered, or the gap on the right existed. Back to the drawing board!

Then I got to thinking about how the layout was computed. Each thumbnail would take the same fixed amount of space. Each would position itself to the left due to setting the float property to left. If the thumbnail did not have enough space within the width of the container, it would move to the next row. Float 101, right? But how did it know the space it had? I only defined the containing div to have a max-width and to center itself, and the width would vary based on the width of the window. It then struck me that if the width of the containing div matched the number of allowable columns, then there would not be extra space along the right.

Eureka!

If I can set the containing div to match my column widths, that container will still be properly centered. So I calculated the computed widths for 2,3,4, and 5 columns of thumbnails. So, when the browser’s width reaches specific widths, the containing div’s width is fixed to match the allowable number of columns. With a fixed width instead of a dynamic width, it can be properly centered on the page. Design problem solved!
The resulting CSS looks like this

<style> 
 #photoMenu {
   margin-left: auto;
   margin-right: auto;
   height: auto;
   min-height: 220px;
 }
 
 .thumbnail {
   position: relative;
   border: 5px solid #ddd;
   float: left;
   margin: 20px;
   width: 160px;
 }
 
 .thumbnail img {
  max-width: 100%;
 }
 
 .thumbnail h3 {
   position: absolute;
   bottom: 0;
   left: 0;
   width: 100%;
   margin: 0;
   text-align: center;
   color: white;
   font: bold 1em/1.5em Verdana, Sans-Serif;
   background: rgb(0, 0, 0);
 }
 
 @media (min-width: 315px) {
   #photoMenu {
     background-color:darkred;
     width: 210px;
   }
 }
 
 @media (min-width: 525px) {
   #photoMenu {
     background-color: darkseagreen;
     width: 420px;
   }
 }
 
 @media (min-width: 735px) {
   #photoMenu {
     background-color: rebeccapurple;
     width: 630px;
   }
 }
 
 @media (min-width: 945px) {
   #photoMenu {
     background-color: bisque;
     width: 840px;
   }
 }
 
 @media (min-width: 1155px) {
   #photoMenu {
     background-color: aqua;
     width: 1050px;
   }
 }
 </style>

For each of the media query breakpoints, I added a background color just to highlight it. Here is what the HTML looks like:

<div id="photoMenu">
  <div class="thumbnail">
    <img src="thumbnail.png" alt="">
    <h3>Title</h3>
  </div>
  <div class="thumbnail">
    <img src="thumbnail.png" alt="">
    <h3>Title</h3>
  </div>
  <div class="thumbnail">
    <img src="thumbnail.png" alt="">
    <h3>Title</h3>
  </div>
  <div class="thumbnail">
    <img src="thumbnail.png" alt="">
    <h3>Title</h3>
  </div>
  <div class="thumbnail">
    <img src="thumbnail.png" alt="">
    <h3>Title</h3>
  </div>
  <div class="thumbnail">
    <img src="thumbnail.png" alt="">
    <h3>Title</h3>
  </div>
  <div class="thumbnail">
    <img src="thumbnail.png" alt="">
    <h3>Title</h3>
  </div>
</div>

You can see the completed sample here.

Prototyping with Bootstrap workshop at UXPA 2015

logo

I am excited to announce that I will be giving a workshop on Monday evening at the upcoming UXPA conference entitled: “Rapid HTML prototyping with Bootstrap”. Registration for the conference is not open yet, but there are only 10 slots available. I am really looking forward to leading this workshop. Hope to see you there!

Details at: http://uxpa2015.org/

Adobe MAX Lab Update: #2

This additional lab is short one, but will add event listeners in the event the device loses or gains network access. PhoneGap has event listeners for a variety of device events such as:

  • deviceready
  • pause
  • resume
  • online
  • offline
  • backbutton
  • batterycritical
  • batterylow
  • batterystatus
  • menubutton
  • searchbutton
  • startcallbutton
  • endcallbutton
  • volumedownbutton
  • volumeupbutton

We will add our event listeners within the device ready function to listen for both the change from being online to offline, as well as the reverse.

function onDeviceReady() {
   checkConnection();
   document.addEventListener("online", onOnline, false);
   document.addEventListener("offline", onOffline, false);
 }

The two functions simply toggle the network state variable based on the network state.

function onOnline(event){
   isConnected = true;
}
function onOffline(event){
   isConnected = false;
}

If while using our application, we have a network status change, the application can properly respond to it. This may not seem like much, but defensive programming techniques can improve the overall user experience of your application. We all know that the best user experience will often win in a crowded marketplace.

The project files are available on my GitHub repo.