secondary views

Anatomy of a Cordova application navigation system (part IV): the secondary views

Secondary views, the subject of this fourth part of the article dedicated to a navigation system for a Cordova application, are not actually strictly related to the navigation system. I realized too late that secondary views would have deserved a new specific article for them only. But since in the first part of the article I had promised to tell you about them, well, I want to keep my promise. And after all, users must be able to navigate to secondary views as well, so, even if our top bar is not directly involved, we’ll see that our sidebar menu will be 🙂

The underlying logic

As I said, secondary views will allow us to open a lot of “pages” to perform specific tasks: view a large picture, fill out a form, read product’s details. Usually, we can open any of these secondary views using a menu item or tapping a button in one of the main pages or even tapping a button in another secondary view. So, we can definitely have several secondary views already opened at a given time and we can expect that we can go back to the previous opened view just swiping to the left, or tapping a specific back button and obviously using our phone’s back button.

This is not automatically managed by Cordova, so we have to manually write the code to manage secondary views and the navigation between them. In order to do hat we’ll build a simple array where we’re gonig to store the opened secondary views. In addition we’ll create two functions to enter and to leave secondary views: enterSecondaryView() and exitSecondaryView(). The former has several tasks to perform:

  • it will set the animation used to enter the secondary view
  • it will add the id of the secondary view to the array secondaryViews[]
  • finally it will optionally perform some specific action (tipically, load some data from the database)

The exitSecondaryView() will perform the analogue actions to manage the exit process:

  • it will set the animation appropriately
  • it will remove the view’s id from the secondaryViews[] array
  • it will optionally perform any action we need exiting the specific secondary view

That said about the logic, in the markup we’ll create specific divs (as many as we need for our application) giving them the “secondary-view” class. As we’ll see, the related css is very simple. Go on with some code.

The HTML markup

The basic html markup for a secondary view is really simple:

 
<div id="view_a" class="secondary-view"> 
    <div class="secondary-view-toolbar"> 
        <div class="navbar" data-role="navbar"> 
            <ul class="cfx-menu">
                <li class="back-button unique-item">
                    <a href="#" class="back">
                         <i class="material-icons">arrow_back</i>
                    </a>
                </li> 
            </ul> 
        </div> 
    </div> 
    <div class="page-wrapper"> 
         <h4>view_a</h4> 
          <div class="content-wrapper"> 
          </div> 
     </div> 
</div> 

As you can see, nothing special here: we create our div and immediately after we put a second div to host a header where we place a menu with a unique button, the back-button which will allow us to exit the view. Then the page wrapper will host a page title and the content wrapper. Obviously, this is just the basic structure: you can add more buttons in the secondary view header.

So let’s add some other secondary view and some fake content. The secondary view section of our html file will be like this:

<div id="view_a" class="secondary-view">
	<div class="secondary-view-toolbar">
		<div class="navbar" data-role="navbar">
			<ul class="cfx-menu">
				<li class="back-button"><a href="#" class="back"><i class="material-icons">arrow_back</i><br></a></li>
			</ul>
		</div>
	</div>
	<div class="page-wrapper">
		<h4>2 view_a</h4>
		<div class="content-wrapper">
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
		</div>
	</div>
</div>		


<div id="view_b" class="secondary-view">
	<div class="secondary-view-toolbar">
		<div class="navbar" data-role="navbar">
			<ul class="cfx-menu">
				<li class="back-button"><a href="#" class="back"><i class="material-icons">arrow_back</i><br></a></li>
			</ul>
		</div>
	</div>
	<div class="page-wrapper">
		<h4>2 view_b</h4>
		<div class="content-wrapper">
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<a href="#" class="view_c">view_c</a>
		</div>
	</div>
</div>		
<div id="view_c" class="secondary-view">
	<div class="secondary-view-toolbar">
		<div class="navbar" data-role="navbar">
			<ul class="cfx-menu">
				<li class="back-button"><a href="#" class="back"><i class="material-icons">arrow_back</i><br></a></li>
			</ul>
		</div>
	</div>
	<div class="page-wrapper">
		<h4>2 view_c</h4>
		<div class="content-wrapper">
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
			<p>Some content</p>
		</div>
	</div>
</div>

Now we have something to play with 🙂

The styles

For the basic layout described above, the styles are quite simple:

.secondary-view{
	background: #fff !important;
	position: absolute;
	left: 0;
	top: 0;
	width: 100%;
	height: auto;
	min-height: 100%;
	z-index: 4;
        display: none;
	-webkit-box-shadow: 2px 0px 4px 0px rgba(50, 50, 50, 0.75);
	-moz-box-shadow:    2px 0px 4px 0px rgba(50, 50, 50, 0.75);
	box-shadow:         2px 0px 4px 0px rgba(50, 50, 50, 0.75)	;
}
.secondary-view-toolbar{
	background: #689F38 !important;
	width: 100%;
	height: 50px;
	margin-top: 0;
	color: #fff;
}
.page-wrapper{
	padding: 30px 10px 0 10px !important;
}

In order to minimize our style rules, we have to slightly change what we did to design our top bar. In that part of the article we had put all rules for the top bar menu under the #cfx-topbar-menu id. Now we have to use several of those rules even for our secondary views’ headers, so let’s add to our list #cfx-topbar-menu the new class .cfx-menu. Then in our stylesheet, replace the following rules:

#cfx-topbar-menu{
	list-style: none;
	position: relative;
	padding: 0;
	width: 100%;
}
#cfx-topbar-menu li{
	width: 19% !important;
	display: inline-block;
	text-align: center;
	vertical-align: middle;
}
#cfx-topbar-menu li a{
	display: block;
	background: transparent !important;
	text-decoration: none;
	margin-top: .3em;
	color: white;
}

with the following ones

.cfx-menu{
	list-style: none;
	position: relative;
	padding: 0;
	width: 100%;
}
.cfx-menu li{
	width: 19% !important;
	display: inline-block;
	text-align: center;
	vertical-align: middle;
}
.cfx-menu li a{
	display: block;
	background: transparent !important;
	text-decoration: none;
	margin-top: .3em;
	color: white;
}

In addition we have to change the rule

#cfx-topbar .material-icons{
	font-size: 32px;
}

to

.cfx-menu .material-icons{
	font-size: 32px;
}

Finally, let’s add a rule specific to the back icon:

.cfx-menu li.back-button {
	text-align: left;
	padding-left: 10px;
}

This way, we can use the cfx-menu class even for the secondary view’s headers without duplicating css rules.

If you want to see the result, just comment out the display property in the .secondary-view rule in order to let it visible, connect your phone and run the app. You should see something like this

The secondary view
The secondary view

The javascript

Finally we can look at something interesting. Javascript code will allow us to enter a secondary view, to exit it, to animate the way we enter and exit and to use the phone hard back button to navigate back through opened secondary views exting them in sequence.

Here the functions to enter secondary views:

var secondaryViews = [];
var activeView;
function enterSecondaryView(id) {
	activeView = id;
	var animation = 'slideInLeft';
	processSecondaryViewsArrayOnEnter();
	$(document).scrollTop(0);
	doEnterAction();
	$('#' + activeView).css({'display': 'block'}).addClass('animated ' + animation);
	setTimeout(function () {
		$('#' + activeView).removeClass('animated ' + animation);
		if (secondaryViews.length > 1) {
			$('#' + secondaryViews[secondaryViews.length - 2]).hide();
		}
		activeView = '';
	}, 1000);
}
function processSecondaryViewsArrayOnEnter() {
	if ($.inArray(activeView, secondaryViews) == -1) {
		secondaryViews.push(activeView);
	}
}
function doEnterAction() {
	if (activeView == 'view_a') {
		//here your specific stuff
	} else if (activeView == 'view_b') {
		//here your specific stuff
	}
}

Let’s see these functions one by one.

First of all we create the secondaryViews array. Then we create a variable to make visible to all required methods the id of the secondary view we want to enter: activeView.

The enterSecondaryView() method accepts only one parameter, the id of the secondary view we want to enter to.

The first thing this method does is to assign the value of its parameter id to the variable activeView. Then it creates the variable “animation” and sets its value to “slideInLeft”. If you want to change the animation style, this is a good place to do it (to see the available options take a look at he file css/animate.css). We could also prepare a specific function to set different animation accordingly to the specific secondary view we are entering, but I let this to you: here we’ll keep things simple.

Once the animation is set, the method calls the processSecondaryViewsArrayOnEnter() function: okay, the name is really ridicously long, but it is clear what this function does, isn’t it? Jump to the method implementation and you’ll see the code: if the secondary view we want to enter is not already in the array secondaryViews, its id is added to the array. This way, we can keep track of the views the user opens and of their exact sequence.

Then we have the option to perform some “entry action” (tipically to load some data from a database) in order to fill our view with some dynamic content.

Finally, the animation is managed in a way similar to the one we have used for our side menu; we also reset the variable activeView to an empty value.

Below you find the code for the process inverse which will exit the current active view.

function exitSecondaryView(id, sender) {
	activeView = id;
	var animation = 'slideOutLeft';
	processSecondaryViewsArrayOnLeave();
	doLeaveAction();
	$('#' + activeView).addClass('animated ' + animation);
	setTimeout(function () {
		$(document).scrollTop(0);
		$('#' + activeView).css('display', 'none').removeClass('animated ' + animation);
		activeView = '';
	}, 1000);
}
function processSecondaryViewsArrayOnLeave() {
	if (secondaryViews.length > 1) {
		$('#' + secondaryViews[secondaryViews.length - 2]).show();
	}
	secondaryViews.pop();
}
function doLeaveAction() {
	if (activeView == 'view_a') {
		//do stuff
	} else if (activeView == 'view_b') {
		//do stuff
	}
}

To see it in action we just have to add some event handler to our side menu:

$(document).on('click', '.view_a', function (e) {
	e.preventDefault();
	exitMenu();
	enterSecondaryView('view_a');
});
$(document).on('click', '.view_b', function (e) {
	e.preventDefault();
	exitMenu();
	enterSecondaryView('view_b');
});
$(document).on('click', '.view_c', function (e) {
	e.preventDefault();
	exitMenu();
	enterSecondaryView('view_c');
});

Done! This way we are able to open secondary views both from the side menu and from a link we have placed in the view_b. Run the app and enjoy!

Swipe to exit a secondary view

If we want our users be able to exit secondary views just swiping, we just need to add a few line of code to attach swipe event handler to our secondary views:

$(".secondary-view").swipe({
	swipeLeft: function () {
		var activeView = $(this).attr('id');
		exitSecondaryView(activeView);
	}
	threshold: 200,
	excludedElements: "label, button, input, select, textarea, .noSwipe"
});

Easy, isn’t it?

The phone back button

The last touch is to slightly change he code of the method onBackKeyDown() in order to be able to use it even to navigate back through opened secondary views. First, we need to chek if some secondary view is currently opened: if not, the code to manage the carousel views and the exiting app will be executed; otherwise, we just have to get the currently active secondary view and call the method exitSecondaryView() passing the correct parameter.

var lastTimeBackPress = 0;
var timePeriodToExit = 2000;
function onBackKeyDown(e) {
	e.preventDefault();
	e.stopPropagation();
	if (secondaryViews.length == 0) {
		console.log('sv length is 0');
		var page = $('.item.active').attr('id');
		if (page == 'page1') {
			if (new Date().getTime() - lastTimeBackPress < timePeriodToExit) {
				navigator.app.exitApp();
			} else {
				window.plugins.toast.showWithOptions({
					message: "Press again to exit.",
					duration: "short", // which is 2000 ms. "long" is 4000. Or specify the nr of ms yourself.
					position: "bottom",
					addPixelsY: -40  // added a negative value to move it up a bit (default 0)
				});
				lastTimeBackPress = new Date().getTime();
			}
		} else {
			$('.carousel').carousel('prev');
		}
	}else{
		var activeView = secondaryViews[secondaryViews.length - 1];
		exitSecondaryView(activeView);
	}
}

That’s all folks! If you have some comment, criticism or suggestions, I’ll be happy to hear you.

Here you can download the full source code:

Full Source Code

On to the next 🙂


Part 1Part 2Part 3Part 4


3 thoughts on “Anatomy of a Cordova application navigation system (part IV): the secondary views”

  1. Hello,

    Finished the tutorial. The UX is brilliant. Thanks again.

    Just one more thing (I sound like Columbo!): Everything is working except for the “back-button” on the secondary views.

    The anchor inside each view’s line item is not being triggered.

    Each line item has class=”back-button” and the css looks like this:

    .cfx-menu li.back-button {
    text-align: left;
    padding-left: 10px;
    }

    Any suggestions?

    Many thanks.

  2. Hello,

    I’ve found two issues after completing Part IV of this tutorial.

    One is a simple fix, the other I have drawn a complete blank on:

    ISSUE-1: Cannot close app by double-tap on first view.

    The test for “page1” in the onBackKeyDown(e) function, returns “undefined”.
    Fix:
    Change:
    var page = $(‘.item.active’) …
    to:
    var page = $(‘.carousel-item.active’) …

    ISSUE-2: Top-left “back-button” on secondary view does not work.

    To try and fix I have replaced class=”back” with class=”view_a” on “view_b”.

    This displays “view_a” but there is no sliding animation, and I don’t know what to substitute the “back” class for on the first secondary view”view_a”.

    Could you please post the corrected code?

    This is the final issue I can find. After this is fixed the tutorial is functionally complete.

    Many thanks.

    1. Hello Paul. Sorry for later response but I have some issue here in Tenerife: Corona virus is dstroyng my business because almost all my clients work in tourism, so I have some difficult to follow up my little blog.
      Anyway, I confess I don0’t understand your issues. I’ve just installed Cordova on my system, connected my phone and typed “cordova run” to get the sample app running on my Redmi 7… Both hard and soft back buttons work as expected, both side menus slide in smoothly, swipe left and right work fine…. I’m using exactly the code I sent you vie email. And I’ll make it available or download soon, I promise 🙂
      Let’s discuss about the issue you have found but currently I’m not able to replicate it. And trust me, I didn’t change a comma in the code….

Leave a Comment

Your email address will not be published. Required fields are marked *