Earlier this week we introduced newer Mobile Packs, Mobile SDK 2.0, Mobile Templates and Mobile App Gallery — all designed to make it easier and faster for you to create mobile apps on the Salesforce Platform. One of the newer mobile packs is based on Knockout (aka KnockoutJS). In this blog I’m going to walk you through how it works. If you’d like to get it up and running and try it out, follow the step-by-step instructions in this “Getting Started.”
What is Knockout and Why Knockout?
Knockout is one of the more popular JavaScript frameworks that aims to simplify and organize JavaScript app development by using a pattern/architecture they call Model-View-ViewModel (“MVVM”). The main idea is to separate Models and Views by taking care of all the bindings b/w Models and Views within the framework itself so that the we-the-developers don’t have to (i.e. write JQuery code to bind to DOM and listen to events and call JS functions etc). It also comes with several other features like templates, dependency tracking etc to help easily build modular apps.
To understand it better, let’s take a look at the below picture from their tutorial (click on it to see it in full screen).
How Knockout Works (High Level):
1. Let’s say you have some text-labels, edit fields, or buttons in your HTML that are supposed to receive or send data from/to a JavaScript function(which in turn talks to server), you simply ‘describe‘ that information (i.e. click should call some function, edit field is waiting for some text etc) using data-bind attribute. Such HTML called as “View” in Knockout. For example:
data-bind="text:firstName" //for a text-label to display data
or data-bind="click:callSomeFunction" //for a button to call some function
or data-bind="value:lastName" // edit-field to send or receive data
.
2. Then create a JS class and make model’s properties (like firstName, lastName etc), instance properties of this JS class (like this.firstName, this.lastName, this.someButtonFunction etc). In KO, this class called a ViewModel class. For Example:
function AppViewModel() {
this.firstName = ko.observable("Bert"); //Bounce any change to this model data back to view
this.capitalizeLastName = function() {
var currentVal = this.lastName(); // Read the current value
this.lastName(currentVal.toUpperCase()); // Write back a modified value
};
}
3. Finally, ask KO bind an object of ViewModel class with the actual View by calling:
ko.applyBindings(new AppViewModel());
After step #3, Knockout will take care of updating views when model data changes and updating the models when there is change in the views without us having to do all those things. That’s the gist but for more please go through their excellent tutorials here.
Mobile Pack for Knockout
Mobile pack for Knockout is essentially a set of example ‘Contacts’ apps that shows how to use our Mobile SDK and KnockoutJS framework and easily talk to Salesforce backend. They are ‘single-page’ apps but with multiple views (list view, details view and edit view). They use Twitter Bootstrap as a UI framework and come in various versions depending on deployment and code locations (Heroku, Visualforce and Cordova-Hybrid).
Architecture
To understand where KnockoutJS, Twitter Bootstrap etc fits in, let’s take a look at the overall architecture.
As you can see from the above pictures, you can create 2 kinds of apps (with at least 6 different variations) using the Mobile Pack:
1. A mobile web app that runs in the browser.
1.1 Hosted on Heroku (using Node.js server)
1.2 Hosted on Visualforce.
2. A hybrid app using Mobile Packs (this is excluding pure-native Mobile SDKs).
2.1 iOS cordova hybrid app with files locally stored (on-the-device)
2.2 iOS cordova hybrid app with files served from Visualforce
2.1 Android cordova hybrid app with files locally stored (on-the-device)
2.2 Android cordova hybrid app with files served from Visualforce.
Javascript Libraries and Frameworks Used by Knockout Mobile Pack Apps
Irrespective of what kind of Mobile Pack app, they all more or less use the following set of files:
OSS libraries:
- boostrap*.css – Twitter Bootstrap as a UI framework (for look and feel)
- knockout-2.3.0.js – Knockout as a JavaScript framework (code organization and modularity)
- sammy-latest-min.js – Sammy.js (as routing library to make it single-page app; Knockout doesn’t natively support it)
- jquery.min.js – JQuery for dom manipulation via Knockout
- underscore.js – Underscore.js for JavaScript utilities.
- cordova-2.3.0.js (Hybrid only) – Apache Cordova JS library that bridges native and JavaScript
Salesforce OSS libraries:
- knockout-force.js – A bridge between your app and smartsync.js to perform CRUD operations
- smartsync.js – A library thats a wrapper to mobilesdk.forcetk.js and helps sync data between app and Salesforce.
- mobilesdk.forcetk.js – A popular Salesforce REST api library that actually does the AJAX calls to Salesforce.
- cordova.force.js (Hybrid only) – A Cordova extension to help with OAuth and Salesforce smart-store.
- forcetk.ui.js (mobile web only) — An authentication library that helps with OAuth, login and logout of mobile-web apps.
App’s own JS files:
- init.js – Bootstraps and initializes the app
- app.js – Main app where all the main app business logics and ViewModels reside.
Creating a ‘single-page’ Contacts App
Before we go ahead let’s remember that our example Contacts app (Mobile Web – Heroku Bootrtrap Node.js) is designed as a ‘single-page’ app. This means once the app’s index page is loaded, it’s never reloaded but instead different ‘views’ (list view, details view etc) are simply added and removed to existing DOM by simply changing hash or anchor part of the url.
To achieve this we use Sammy.js’s routing and some tricks that can be described in 3 parts:
1. First, we create a div container to hold views like below:
<!– This is the main container where different views will be inserted (by SammyJS) –>
<div id=”mainContainer” class=”container”></div>
2. Then, we create ‘routes’ in SammyJS for each views like below:
function sammyRoutes(koApp) {
var sammyApp = Sammy(function () {
// if "#/" route, render home.html
// and set new ViewModel for '/'(LoginViewModel)
this.get('/', function () {
this.render('/partials/home.html').replace('#mainContainer');
koApp.setViewModelByRoute("/");
}
});
// if "#/contacts" route, render list.html & set ContactListViewModel
this.get('/contacts', function () {
//replace and render contacts list view
});
//so on
}
3. And finally, reapply Knockout binding after a new-view is injected (so that Knockout now works for this new view).
//Note: bind to 'changed' event and reapply bindings if mainContainer has changed
//This is required to essentially wait until new view is swapped before applying bindings.
this.bind('changed', function () {
var mainContainer = document.getElementById('mainContainer');
if (mainContainer && mainContainer.childNodes.length > 0) {
ko.cleanNode(mainContainer); //clean previous bindings
ko.applyBindings(koApp.currentViewModel, mainContainer); //reapply new view's binding
}
});
Defining a Salesforce Object
One of the first things you want to do when playing around with mobile packs is to describe a Salesforce object so that you can perform CRUD operations on it. Thankfully knockout-force.js library provides KnockoutForceObjectFactory to make this trivial. And further help perform CRUD and search operations on top of that object. For example, in our contacts app we can create a Contact-class by setting fields, type, orderBy etc. like below:
/************************************
* Create a Contact "CLASS" from KnockoutForceObjectFactory by setting SOQL parameters.
***********************************/
var Contact = (function () {
var objDesc = {
type: 'Contact',
fields: ['FirstName', 'LastName', 'Title', 'Phone', 'Email', 'Id', 'Account.Name'],
where: '',
orderBy: 'LastName',
limit: 20
};
return KnockoutForceObjectFactory(objDesc, SFConfig);
})();
And you can then perform CRUD operations like:
Contact.query(soql) or Contact.save()
or Contact.get({id:myContactId}) etc
Contacts App Work Flow – Bringing it All Together
Now that you are familiar with the architecture and various parts of the app, let’s take a quick look at the work-flow to understand how it all comes together.
Step 1: When the app loads, the below JavaScript snippet gets populated with Salesforce client_id (consumer key) and app_url (app’s Heroku url) by Node.js server.
//Get configuration from environment variables (via heroku or localhost env)
var configFromEnv = {};
configFromEnv.client_id = '';
configFromEnv.app_url = '';
Step 2: Index.html page is rendered (in Cordova version, then native Salesforce OAuth is displayed).
Step 3: Once the user signs in using OAuth, after the callback, it ultimately calls ‘initApp’ function in init.js. This simply initializes everything with real Salesforce user’s authentication information and redirects to ‘/contacts’ route.
this.get('/callback:cbInfo', function () {
koApp.knockoutForce.oauthCallback(document.location.href);
location.hash = '/contacts';
});
Step 4: Once /contacts route is triggered, the below code sets the list view and also sets the ViewModel via koApp.setViewModelByRoute(‘/contacts’)
this.get('/contacts', function () {
if (!koApp.knockoutForce.authenticated()) {
location.hash = '/login';
} else {
koApp.setViewModelByRoute("/contacts");
this.render('/partials/contact/list.html').replace('#mainContainer');
}
});
Step 5: The change in route also triggers ‘changed’ event in Sammy.js and calls ko.applyBindings on currentViewModel
//Note: bind to 'changed' event and reapply bindings if mainContainer has changed
//This is required to essentially wait until new view is swapped before applying bindings.
this.bind('changed', function () {
var mainContainer = document.getElementById('mainContainer');
var logoutDiv = document.getElementById('logoutDiv');
if (mainContainer && mainContainer.childNodes.length > 0) {
ko.cleanNode(mainContainer);
ko.applyBindings(koApp.currentViewModel, mainContainer);
}
});
Step 6: By this step, the view is already changed to ‘list’ view and all the bindings are applied. But if you take a look at the ContactListViewModel, you will see that the minute it became ‘current’ viewModel (due to step 4), it makes an AJAX call to Salesforce to get list of contacts. And once those Contacts are returned by the server, Knockout will do its magic of automatically updating the views.
function ContactListViewModel() {
...
...
Contact.query(function (data) {
self.formatAndSetContacts(data.records);
}, errCB);
...
}
Step 7: After this point steps 2 through 6 is repeated with different ViewModels and routes for other views and user actions.
Next Steps
If you have read till this point hopefully you have gotten an understanding of how to build Knockout based single-page mobile apps. Now if you haven’t already, I highly encourage you to go through Knockout’s tutorials and take a look at source code for other variations (3 of them) in Knockout Mobile Pack samples github repo.
– Raja Rao DV (@rajaraodv)