Before we start I need to clarify something, Navigation pattern used in OnsenUI is usually better known as a master-detail pattern so from now on I will call it like that. In computer user interface design, a master–detail interface displays a master list and the details for the currently selected item. The original motivation for master-detail was old 1980s 80-character-wide display size, which also makes it a great pattern for modern smartphones and tablets.
 
In practice, master-detail screen pattern can be shown in vertical or horizontal form. Mobile development accepts only horizontal form, what used to be vertical form is now known as Slide menu pattern. Master-detail pattern (layout) is a good choice when the user needs to see more information in the master list than just a few identifiers, or when the master view is comprised of a set of items that each has additional details.
 
 

Note: If this tutorial was helpful, need further clarification, something is not working or do you have a request for another Ionic post? Furthermore, if you don't like something about this blog, if something is bugging you, don't like how I'm doing stuff here, again leave me a comment below. I'm here to help you, I expect the same from you. Feel free to comment below, subscribe to my blog, mail me to dragan.gaic@gmail.com, or follow and mention me on twitter (@gajotres). Thanks and have a nice day!

PS. If you want my help, if possible (even if it takes you some time to do that), create a working example I can play with. Use Plunker for AngularJS based questions or jsFiddle for jQuery/jQuery Mobile based questions.


 

Table of Contents

Example

 
Demo
 
Embede working example:
 
OnsenUI Navigation Pattern
 
HTML
 
<html>

<head>
  <script src="https://code.angularjs.org/1.3.0/angular.js"></script>
  <script src="http://onsenui.io/OnsenUI/build/js/onsenui.js"></script>
  <link rel="stylesheet" href="https://cdn.rawgit.com/OnsenUI/OnsenUI/1.2.2/build/css/onsenui.css" />
  <link rel="stylesheet" href="https://cdn.rawgit.com/OnsenUI/OnsenUI/1.2.2/build/css/onsen-css-components.css" />
</head>

<body ng-controller="ListCtrl">

  <ons-navigator animation="slide" var="app.navi">
    <ons-page>
      <ons-toolbar class="toolbar-black">
        <div class="center">Search the Movie Database</div>
      </ons-toolbar>
      <input type="search" class="search-input search-movies-input" placeholder="Search..." style="width: 100%;height: 45px;" ng-model="movie.name" ng-change="searchMovieDB()">
      
      <ons-list>
        <ons-list-item modifier="chevron" class="list-item-container" ng-repeat="movie in movies" ng-click="showDetail(movie.id)">
          
          <ons-row>
            <ons-col width="95px">
              <img ng-src="https://image.tmdb.org/t/p/w92{{movie.poster_path}}" onerror="this.src = 'https://www.ginesisnatural.com/images/no_image.jpg';" class="thumbnail">
            </ons-col>
            <ons-col>
              <div class="name">{{movie.original_title}}</div>
              <div class="desc">{{movie.release_date}}</div>
            </ons-col>
            <ons-col width="40px"></ons-col>
          </ons-row> 
          
        </ons-list-item>
        
      </ons-list>          

    </ons-page>   
  </ons-navigator>    
   
  <ons-template id="view.html">
    <ons-page ng-controller="ViewCtrl">
      <ons-toolbar>
        <div class="left" style="line-height: 44px">
          <ons-back-button>Back</ons-back-button>
        </div>        
        <div class="center">Second Page</div>
      </ons-toolbar>
  
      <div class="columns">
          <div class="pin">
            
              <div class="pin-header">
                <img src="https://cdn0.iconfinder.com/data/icons/iconsweets2/40/movie_film.png">
                <h2>{{movie.original_title}}</h2>
                <p>{{movie.release_date}}</p>
              </div>   
            
              <img ng-src="https://image.tmdb.org/t/p/w92{{movie.poster_path}}" width="200px" />
              <p>
                Average Vote: <strong>{{movie.vote_average}}</strong> (Votes: {{movie.vote_count}})
              </p>
          </div>
      </div>
      
    </ons-page>
  </ons-template>
  
</body>
</html>
 
JavaScript
 
var module = ons.bootstrap('my-app', ['onsen']);

module.factory('Movies', function($http) {
  var cachedData;
 
  function getData(moviename, callback) {
 
    var url = 'http://api.themoviedb.org/3/',
      mode = 'search/movie?query=',
      name = '&query=' + encodeURI(moviename),
      key = '&api_key=470fd2ec8853e25d2f8d86f685d2270e';
 
    $http.get(url + mode + key + name).success(function(data) {
 
      cachedData = data.results;
      callback(data.results);
    });
  }
 
  return {
    list: getData,
    find: function(name, callback) {
      console.log(name);
      var movie = cachedData.filter(function(entry) {
        return entry.id == name;
      })[0];
      callback(movie);
    }
  };
 
});

module.controller('ListCtrl', function($scope, Movies) {
  $scope.movie = {
    name: 'Batman'
  }
  
  $scope.searchMovieDB = function() {
 
    Movies.list($scope.movie.name, function(movies) {
      $scope.movies = movies;
    });
     
  };
  
  $scope.showDetail = function(id){ // 3
    app.navi.pushPage("view.html", { animation: "lift", movieid: id });
  }
  
  $scope.searchMovieDB();
  
});

module.controller('ViewCtrl', function($scope, Movies) {
  var page = app.navi.getCurrentPage();
  Movies.find(page.options.movieid, function(movie) {
    $scope.movie = movie;
  });  
});
 
CSS
 
.toolbar-black {
  border-color: #111 !important;
  background-color: #444444 !important;
}

.toolbar-black .center {
  color: #fff !important;  
}
  
.search-movies-input {
  border-width: 0px !important;
  background-color: #fff !important;
}

.list-item-container {
  line-height: 1;
  padding: 15px 0px 0 15px !important;
}

.thumbnail {
  width: 80px;
  height: 120px;
}

.name {
  font-weight: 700;
  line-height: 16px;
  font-size: 15px;
  margin-bottom: 6px;
}

.desc {
  line-height: 1.2;
  font-size: 11px;
}

.columns {
  -webkit-column-count: 1;
  -webkit-column-gap: 10px;
  -webkit-column-fill: auto;
  -moz-column-count: 1;
  -moz-column-gap: 10px;
  -moz-column-fill: auto;
  column-count: 1;
  column-gap: 15px;
  column-fill: auto;
}

.pin {
  width: 96%;
  margin: 1%;
  display: inline-block;
  background: #FEFEFE;
  border: 2px solid #FAFAFA;
  box-shadow: 0 1px 2px rgba(34, 25, 25, 0.4);
  -webkit-column-break-inside: avoid;
  -moz-column-break-inside: avoid;
  column-break-inside: avoid;
  padding: 15px;
  padding-bottom: 5px;
  background: -webkit-linear-gradient(45deg, #FFF, #F9F9F9);
  opacity: 1;	
  -webkit-transition: all .2s ease;
  -moz-transition: all .2s ease;
  -o-transition: all .2s ease;
   transition: all .2s ease;
}

.pin img {
  width: 100%;
  border-bottom: 1px solid #ccc;
  padding-bottom: 15px;
  margin-bottom: 5px;
}

.pin-header {
  padding-left: 72px;
  min-height: 72px;
  border-color: #ddd;
  background-color: #fff;
  color: #444;
  position: relative;
  z-index: 2;
  display: block;
  margin: -1px;
  padding: 16px;
  border-width: 1px;
  border-style: solid;
  font-size: 16px;  
}

.pin-header img {
position: absolute;
  top: 16px;
  left: 16px;
  max-width: 40px;
  max-height: 40px;
  width: 100%;
  height: 100%;
  border-radius: 50%;  
  margin: 0;
  padding: 0;
  border: 0;
  vertical-align: baseline;
  font: inherit;
  font-size: 100%;  
}

.pin-header h2, .pin-header p {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin: 0 0 2px 80px !important;
  font-size: 16px;
  font-weight: normal;
  color: #000;
  font-weight: 500;
  font-family: "Helvetica Neue", "Roboto", sans-serif;
  line-height: 1.2;
  display: block;
  font-size: 1em;
}

.pin p {
  font: 12px/18px Arial, sans-serif;
  color: #333;
  margin: 0;
}

#columns:hover .pin:not(:hover) {
  opacity: 0.4;
}
 

Code overview

 
Like AngularJS, OnsenUI is all about MVC (or to be more precise Model–View–Whatever) so let’s discuss basic application details.
 
The first thing we need to do is to initialize our application, unlike AngularJS, OnsenUI doesn’t require defined ng-app directive.
 
We only need to initialize it through JavaScript:
 
var module = ons.bootstrap('my-app', ['onsen']);
 
For OnsenUI to work properly, we need to set the [‘onsen’] dependency, framework will not work without it.
 
This application will have two distinct pages, first one (Master) will act as a search page (list.html) while a second one (Detail) will act as a more details page (view.html). These pages have a parent page called index.html, OnsenUI doesn’t use AngularJS routing system so we will programmatically create our application navigation system (we will discuss this later). Both application pages (child pages) require a distinct controller:
 
module.controller('MainController', function($scope, Movies) {
 
module.controller('ViewCtrl', function($scope, Movies) {
 
…Ctrl under controller name is an Angular convention. but you can give it any name. For better future readability stick to this convention. Another important thing to remember is $scope. This is Angular model, it’s just a JavaScript object. Each $scope will hold associated page data. As you can see you don’t need to call set or get functions if you want to change something inside a $scope (like in Backbone case), just access it like any other JavaScript object:
 
$scope.movies = movies;
 
Our first HTML page (list.html) contains only two page structures (elements), our search input and an empty list holder template. Our second page (view.html) has only one card like template. This templete system is made to mimic HandlebarsJS template engine (or older {{mustache}} engine). For example this is a list holder template we are using in our example:
 
      <ons-list>
        <ons-list-item modifier="chevron" class="list-item-container" ng-repeat="movie in movies" ng-click="showDetail(movie.id)">
           
          <ons-row>
            <ons-col width="95px">
              <img ng-src="https://image.tmdb.org/t/p/w92{{movie.poster_path}}" onerror="this.src = 'https://www.ginesisnatural.com/images/no_image.jpg';" class="thumbnail">
            </ons-col>
            <ons-col>
              <div class="name">{{movie.original_title}}</div>
              <div class="desc">{{movie.release_date}}</div>
            </ons-col>
            <ons-col width="40px"></ons-col>
          </ons-row> 
           
        </ons-list-item>
         
      </ons-list> 
 
{{movie.original_title}} represents object movie -> parameter original_title.
 
Application we’re building is a perfect example of a master-detail pattern. The first page will hold a list of movies while another one will hold specific information about selected movie. It can’t get easier than that.
 
Now let’s talk about how this application works. First we need to write something into our search input, it looks like this:
 
<input type="search" class="search-input search-movies-input" placeholder="Search..." style="width: 100%;height: 45px;" ng-model="movie.name" ng-change="searchMovieDB()">
 
Continue Reading