Cross-platform mobile development has traditionally meant dedicating teams and resources to native platform development. Now, however, there is a growing number of tools and frameworks that streamline cross-platform development, making it possible for small teams or even a single developer to create amazing apps that run on all major mobile devices. At a high level, there are couple ways to approach cross-platform mobile development with technologies like web, hybrid, interpretation and cross compilation. This article explores web and hybrid development where Javascript handles application logic and data binding.
Further in the article I will show you how to build a mobile application backed by Javascript that pulls down your Chatter feed from Salesforce and further steps you need to take to get the application running on your mobile device.
JavaScript Libraries and Frameworks
There are three very important architectural patterns - MVC (Model-View-Controller), MVP (Model-View-Presenter) and MVVM (Model-View-ViewModel). In the past, these patterns have been heavily used for structuring desktop and server-side applications but it's only been in recent years that come to being applied to Javascript.
MVC is an architectural design pattern that encourages improved application organization through a separation of concerns. It enforces the isolation of business data (Models) from user interfaces (Views), with a third component (Controllers) (traditionally) managing logic, user-input and coordinating both the models and views.
There is a growing list of JavaScript libraries and frameworks adopting MV* patterns. Those that are widely known include Backbone,AngularJS, Knockout and Ember. I would encourage reading this blog post that explains the main framework differences and best use cases. I have personally found Backbone to be one of my favorites mainly because the flexibility that the framework offers and strong community support. In essence Backbone is a Javascript client-side framework that helps to organize your code and makes it easier for you to develop single-page applications. Use Backbone to make your Javascript more organized and structured, and the logic (the data—model—and the presentation) sufficiently decoupled.
For the presentation layer we will use a lightweight mobile framework called Lungo that will make our demo application look more native than web based. You can read more about Lungo and look at some of the examples over at their website.
On the deployment side we will leverage build.phongap.com that will allow us to compile applications for most of the popular mobile platforms simply by uploading our HTML, Javscript and CSS files.
Demo App Prerequisites
Now to get going with the application build and before we start writing any code, we will need to download all the required frameworks and their dependencies, I have listed them below and marked where they are going to be needed within the application design.
- UI - Our mobile UI will come from a library called Lungo that makes it feel more like a native application. Lungo has a dependency onQuo, so make sure you include that in the project.
- Salesforce Integration - Later in the build we will be looking at Salesforce integration with the help of forcetk and forcetk.ui.
- Application Logic - Backbone will be our MV* framework that will take care of our application logic and data binding. Head over to Backbonejs.org and download the framework and it dependencies, in this case Underscore.js and JQuery.
- Device Functionality - And finally on the deployment side build.phongap.com will help us to get the application on our device.
Figure 1 illustrates how this looks from a high level design.
Building the App
Lets dive into the app (source code available over at GitHub). We’ll need a root project folder containing css and js subfolders, so go ahead and create these. We’ll start out with the following HTML page:
<html>
<head>
<meta charset="utf-8">
<title>Chatter Backbone</title>
<meta name="HandheldFriendly" content="True">
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="format-detection" content="telephone=no">
<meta http-equiv="cleartype" content="on">
<!-- Lungo Stylesheet -->
<link rel="stylesheet" href="css/libs/lungo/lungo.css">
<link rel="stylesheet" href="css/libs/lungo/theme.lungo.css" id="theme-stylesheet">
<link rel="stylesheet" href="css/libs/lungo/lungo.icon.css">
<!-- App Stylesheet -->
<link rel="stylesheet" href="css/app.css">
</head>
<body class='app'>
<!-- Lungo dependencies -->
<script src="js/libs/lungo/quo.debug.js"></script>
<script src="js/libs/lungo/lungo.js"></script>
<!-- App dependencies -->
<script src="js/libs/jquery.js"></script>
<script src="js/libs/underscore.js"></script>
<script src="js/libs/backbone.js"></script>
<!-- LungoJS - Sandbox App -->
<script>
Lungo.init({
name: 'Chatter Backbone'
});
</script>
<!-- App Javascript -->
<script src="js/app.js"></script>
</body>
</html>
Here we need to include all of the required libraries and dependencies for Lungo and Backbone.
We will be writing the Javascript code in a single file app.js. Typically you would split these into subfolders and modules. Let's create our backbone models and collections that will represent chatter feedItem and feedComment objects.
var FeedItem = Backbone.Model.extend({});
var FeedItems = Backbone.Collection.extend({
model: FeedItem
});
var FeedComment = Backbone.Model.extend({});
var FeedComments = Backbone.Collection.extend({
model: FeedComment
});
To create a model in Backbone, simply extend the Backbone.Model class using the extend() method same for collection. Like a model, a collection is a Backbone class that we extend to add custom functionality specific to our application. We use the model property to tell the collection what class each item in the collection should be built from.
Now create the views responsible for displaying application data in an HTML page.
var FeedItemView = Backbone.View.extend({
template: _.template($("#feed_item_template").html()),
render: function() {
//Append Feed Item to DOM
this.$el.append(this.template(this.model.toJSON()));
//Check if there are any comments
_.each(this.model.get('comments')['comments'], function (item) {
console.log(item);
feed_comment_view = new FeedCommentView({
model: new FeedComment(item)
});
this.$('#feed_comment_placeholder').append(feed_comment_view.render().el);
}, this);
return this;
}
});
This view handles displaying an individual feed item. Just like models and collections, views have anextend() method used to extend theBackbone.View class. You will set several instance properties in your view, like the template below, and the render function that will append the feed item and it’s comments to the html page.
Now we need to create the feed comment view that we have referenced in our previous feed item view.
var FeedCommentView = Backbone.View.extend({
template: _.template($("#feed_comment_template").html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
This view handles displaying of individual feedItem comments.
Our current views represent each individual feedItem and feedComment so they are mapped to a model on a 1:1 basis. But the feed item view isn’t self-rendering and we haven’t invoked it yet. What we need is a view that maps 1:1 to our feeditem collection, a master view that will render the right number of feedItem views to display each of our feedItems and subsequently our feedComments.
var FeedView = Backbone.View.extend({
el: '#main-article',
tagName: 'ul',
template: _.template($("#feed_template").html()),
initialize: function(){
//Render Views
this.render();
this.render_feed_items();
},
render: function() {
this.$el.html(this.template({'next_page_url':this.options.next_page_url}));
return this;
},
render_feed_items: function() {
_.each(this.collection.models, function (item) {
feed_item_view = new FeedItemView({
model: item
});
this.$('#feed_item_placeholder').append(feed_item_view.render().el);
}, this);
}
});
Now would probably be an appropriate time to look at Underscore’s built-in micro-templating facilities. Underscore provides thetemplate() method as we saw to consume and interpolate templates. To the HTML page we should add the templates that we will use;
<script id="feed_template" type="text/template">
<ul class="feed" id="feed_item_placeholder"></ul>
</script>
<script id="feed_item_template" type="text/template">
<li class="thumb">
<img src="<%= actor.photo.smallPhotoUrl %>?oauth_token=<%= sf_token %>" class="icon">
<strong><%= actor.name %></strong>
<small><%= body.text %></small>
</li>
<div style='margin-left:20px;' id="feed_comment_placeholder"></div>
</script>
<script id="feed_comment_template" type="text/template">
<li class="" >
<img src="<%= user.photo.smallPhotoUrl %>?oauth_token=<%= sf_token %>" class="icon">
<strong style='font-size:13px;'><%= user.name %></strong>
<small style='font-size:12px;'><%= body.text %></small>
</li>
</script>
Here we are just pulling out the data about who created the post, their profile image and the post body. You may have noticed I am using a sf_token variable in the image source. As a convenience you can add a global variable that has your salesforce session, so that user images can be retrieved from salesforce. (This is definitely not best practice, but will do for the demo).
Just to make sure everything is working we can plug in some test data. One easy way is to get a chatter feed from workbench. Login to workbench with one of your orgs and go to https://workbench.developerforce.com/restExplorer.php?url=/services/data/v27.0/chatter/feeds/news/me/feed-items&autoExec=1
This url will take you to your chatter news feed data. Click on the show raw response and copy the JSON data into a new file called news_feed.js, assign it a global variable news_feed and place it in the js folder. Now in your app.js file get the JSON data and pass it into your feed view.
var feed = new FeedItems(news_feed['items']);
var feed_view = new FeedView({
collection: feed,
});
This is what you should see when you launch the index.html file in your browser.
Now that we have the test JSON working, let's try and get our actual chatter news feed. To speed things up we can leverage two great JavaScript libraries for AJAX interaction with Salesforce, namely forcetk.js and forcetk.ui, they have some great examples on their github repositories, that's what we will be using here. Lets add them to our index.html file.
<script src="js/libs/forcetk.js"></script>
<script src="js/libs/forcetk.ui.js"></script>
Now we need to include Salesforce login capability to our application using the Forcetk.ui example. Right after the successful authentication response we will call Salesforce Chatter API to get our news feed with forcetk AJAX method:
function display_feed(feed_items){
var feed = new FeedItems(feed_items['items']); //For user and group feeds call - /services/data/v27.0/chatter/feeds/record/0F9x000000009dUCAQ/feed-items
var feed_view = new FeedView({
collection: feed,
});
};
//Salesforce login
function login(){
// Salesforce login URL
var loginURL = 'https://login.salesforce.com/',
// Consumer Key from Setup | Develop | Remote Access
consumerKey = 'Your Consumer Key',
// Callback URL from Setup | Develop | Remote Access
callbackURL = 'https://login.salesforce.com/services/oauth2/success',
// Instantiating forcetk ClientUI
ftkClientUI = new forcetk.ClientUI(loginURL, consumerKey, callbackURL,
function forceOAuthUI_successHandler(forcetkClient) { // successCallback
sf_token = forcetkClient.sessionId;
forcetkClient.ajax('/v27.0/chatter/feeds/news/me/feed-items',
function(data){
display_feed(data);
},
function(error){
console.log(error);
},
'GET',
{},
false
);
},
function forceOAuthUI_errorHandler(error) { // errorCallback
console.log(error);
}
);
// Initiating login process
ftkClientUI.login();
}
Now all we need to do is add a login button to the index.html file, between the article tag, so that it will be replaced by the chatter feed when you successfully authenticate with Salesforce.
<section id="main" data-transition="">
<header id="main-nav" data-title="Chatter">
</header>
<article id="main-article" class="active list indented scroll">
<button id="btnLogin" onclick="login()">Login</button>
</article>
</section>
Lets run our app!
Deploying as a Hybrid App
We can take this one step further and deploy it to our mobile phones as a hybrid application. We will be using Phonegap Build for this, Phonegap Build enables developers to either upload their code, or point Phonegap build at a Git or SVN repository, and Phonegap Build performs a cloud-based compilation, providing us with URLs and QR codes to download device-specific application binaries.
To do this we will need to add a configuration fille to our project and include two phonegap libraries, phonegap.js and childBrowser.js(to enable salesforce login, as the popup feature of browsers is not available on mobile).
Let's create the config.xml file in our root directory:
<?xml version="1.0" encoding="UTF-8" ?>
<widget xmlns = "http://www.w3.org/ns/widgets"
xmlns:gap = "http://phonegap.com/ns/1.0"
id = "com.phonegap.chatterBackboneLungo"
version = "0.0.1">
<name>Chatter Backbone Lungo</name>
<description>
Chatter Backbone Lungo Mobile Demo.
</description>
<author href="http://solutionrock.com" email="rumdumdum@gmail.com">
Richard Ozols
</author>
<preference name="webviewbounce" value="false"/>
<feature name="http://plugins.phonegap.com/ChildBrowser/3.0.4" />
</widget>
Here we are defining some general information about our application, but the main focus points are the childBrowser' plugin and additionally we can disable the bounce effect that mobile browsers have for a more native feel. More details can be found here(https://build.phonegap.com/docs/config-xml).
Now we just need to include the phonegap libraries in our index.html file, before all the other js files.
<!-- Phonegap -->
<script type="text/javascript" charset="utf-8" src="phonegap.js"></script>
<script type="text/javascript" charset="utf-8" src="childbrowser.js"></script>
And that is it from a coding aspect.
To get the app on our mobile device, navigate to PhoneGap and create an account or login. After you have created a new app and uploaded your source code or provided a link to your gitHub repo, you will need to sign your app for certain platforms before you can install it, signing details can be found here(https://build.phonegap.com/docs).
Now you can download the app directly by scanning the QR code from your phone.
Conclusion
The great thing I find really useful about backbone and other MV* frameworks is the reuse of logic, so you might want to take this example and turn it into a desktop based app or use it as a module within an existing page, all you would need to do is update underscores template html contents and just reuse almost all of your javascript code.
So rather than building separate applications for all the different platforms out there, cross-platform development tools and frameworks make it possible to build just one application and deploy it to multiple platforms with services like build.phonegap.com. This is a major timesaver, to say the very least. Developers looking for ways to streamline their workflow and get their apps quickly to end users find a way to make cross-platform development a reality in their app development workflow.
If you want to learn more about backbone, BackboneTutorials.com will help you to get started, if you are someone who wants to understand how to better structure a backbone application then Marrionette and Backbone Baoilerplate are great sourcs for that. Explore the mobile packs recently published by Salesforce that make integration between mobile apps and Salesforce seamless.