Build A Fast Account Rolodex in Visualforce Using KnockoutJS and CSS

Learn to create a mobile-oriented Visualforce page that leverages custom CSS and KnockoutJS to build an easy-to-use contact editor that facilitates easy and quick editing of all contacts on an account.

Guest Post: Matt Lacey is a Co-Founder of S. P. Keasey Trading Co and Proximity Insight. Salesforce and Force.com MVP. Proposer and moderator of Salesforce StackExchange. Co-host of Code Coverage. Blogger at laceysnr.com Fan of many things including games development, snowboarding and Atari computers.

In this post we’re going to create a mobile-oriented Visualforce page that leverages custom CSS and KnockoutJS to build an easy-to-use contact editor that facilitates easy and quick editing of all contacts on an account. It is especially suited to mobile environments since it uses remoting rather than standard Visualforce actions to read and write data.

Step 1: Create a Visualforce Page That Uses the Account Standard Controller

Create a new Visualforce page and give it a sensible name, such as ‘Rolodex’, and replace the initial code with the following. As you can see we’re hiding the standard UI, declaring that we don’t want the standard CSS, and specifying that we want to use the Account standard controller.

<apex:page standardController="Account" showHeader="false" sidebar="false" standardStylesheets="false">
  <h1><apex:outputText value="{!Account}"/></h1>
</apex:page>

Make the page easy to access by creating a new custom button and putting it on the Account layout. If you’re comfortable with this process, add it and move on to Step 2. If you’re not sure how to add a custom button, then navigate to Setup -> Customize -> Accounts -> Buttons, Links, and Actions and click New Button or Link. Fill in the button details as shown below and click Save. Your Visualforce page will be available because we used the Account standard controller.

 


Finally, navigate to the Accounts tab and choose an Account. Then click Edit Layout to open the page layout editor, drag the new custom button into the custom buttons area, and click Save.

If you click the button you’ll now see your exciting new page that shows the Account ID. Time to make it a bit more interesting!

Step 2: Getting Started With KnockoutJS

KnockoutJS uses a Model-View-View Model pattern, which means everything is split neatly into three areas. The View is HTML/Visualforce markup with some extra parameters, it displays data and sends commands to the View Model. The View Model is all code, represents the data for the View, and provides the operations that can be performed on it. In KnockoutJS, as the name implies, the View Model is implemented in Javascript. Data that is stored by the application comes under the Model component (as with the more commonly found MVC framework), and in this scenario that consists not only of our SObjects in the database, but also anything outside of the View Model itself. So in this case we include our Apex backend as part of the model.

The three basic things we need to do to start with are to create a View Model in Javascript, create a View using HTML, and then use KnockoutJS to work to hook the two together. Before doing anything else, we need to include the KnockoutJS source in our page, and to make that easy we’ll use a CDN (Content Delivery Network) hosted version. So add the following line to the Rolodex page above right after the <apex:page> opening tag:

<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"/>

Now we’ll add a simple View Model. We’ll include some Javascript directly in the page, which will be the start of our Rolodex View Model (you can choose to put this in a Static Resource of course, but it can be easier during development to keep everything in one place). For now we’ll just add two fields to our model, and populate them with values from the account record using standard Visualforce notation, which should be familiar:

<script type="text/javascript">
  function rolodexModel()
  {
    this.accountId = '{!Account.Id}';
    this.accountName = '{!Account.Name}';
  }
</script>

The next task is to sort out the View part of the equation, the part that displays the data provided by the View Model. The View in this tutorial is essentially all of the markup in the page, so replace the old line with the <h1> tags with the following code.

<div>
  <h1 data-bind="text: accountName"></h1>
</div>

As you can probably guess, KnockoutJS uses a special attribute called data-bind to bind parts of the View to the data in the View Model. KnockoutJS provides different types of binding to deal with various use cases, such as input, output, looping, etc. In this case, we’re using the text binding to use the accountName property of the View Model as the content for the <h1> tags.

If you’ve viewed the page at this point, you won’t have seen anything on the screen, and that’s because we haven’t connected to the View Model to it’s View. We do this with a call to the applyBindings() method provided by KnockoutJS. This method takes one parameter, which is an instance of the View Model. We want this link to be created when the page has loaded, so we’ll create a quick one-line function and assign it to window.onload right before the closing script tag.

window.onload = function() { ko.applyBindings(new rolodexModel()) };

Navigate to the page from an account using the custom button created in Step 1 and you should see the account’s name on the screen. If not, check your browser’s developer tools console for any Javascript errors. The page is pretty plain at this point, so we’ll add some basic CSS to make it a bit more attractive.

<style>
  body
  {
    font-family: sans-serif;
    color: #666;
  }

  .panel
  {
    border-radius: 0.5em;
    border: 1px solid #ccc;
    padding: 1em;
  }

  .record
  {
    background-color: #eee;
    border-radius: 0.5em;
    padding: 0.25em 1em 0.25em 1em;
    cursor: pointer; /* Just to be nice to desktop users 😉 */
    margin-top: 0.5em;
  }

  input, button
  {
    font-size: 1.5em;
  }

  input, label
  {
    display: block;
  }

  input
  {
    margin-bottom: 0.5em;
  }
</style>

No rocket science here, and while you might want to consider using Bootstrap or something similar to get easy, mobile friendly, styling, I wanted to keep the source of this tutorial page as free from clutter as possible.

Step 3: Building On The Basics

Now that we’ve got a the framework in place, we’ll start expanding our setup, and modify the page so that it lists all of the contacts associated with an account (with a sensible limit of 100 for example purposes). We could just do this with Visualforce binding, but to keep things nice and clean we’ll load the contacts using Javascript remoting, with a very simple Apex class providing the list of contacts with some chosen fields.

Before making further changes to the page, we’ll first create the Apex class that will provide data from our model. In the Salesforce Developer Console choose File -> New -> Apex Class and then specify a name, e.g. ‘RolodexRemoting’. Replace the default class content with this code, which is a relatively standard starting point for an extension controller, with the small exception that the class has global scope, which is required for remoting classes. The constructor doesn’t have anything in it’s body as we’re not going to use it, we merely need this constructor so that Visualforce will let us use the class as an extension controller alongside the Account standard controller the page uses at present.

global class RolodexRemoting
{
  global RolodexRemoting(ApexPages.StandardController sc) {}
}

Next, we need a remoting method that returns the contacts and fields we’re interested in. For this exercise this is a one-liner, and simply returns the result of a SOQL query as a list of Contact records. It is static (as all remoting methods must be), and pulls the ID of the account being used from the page parameters.

@RemoteAction
global static List<Contact> LoadContacts()
{
  Id accountId = ApexPages.currentPage().getParameters().get('id');
  return [select Id, Name, Email, Phone
          from Contact where AccountId = :accountId
          order by LastName asc];
}

Once the class has been saved, switch back to the page source and add our new Apex class as an extension controller using the extensions attribute in the opening page tag:

<apex:page standardController="Account" extensions="RolodexRemoting" showHeader="false" sidebar="false" standardStylesheets="false">

Now we need to modify the View Model so that it pulls a list of contacts from the controller, but to do that we’ll need somewhere to store them within it. Obviously an array is required since we’re dealing with a list, but because this will be something that changes over time (using remoting means we’ll load the contacts after the page has loaded) we need to use what’s known in the land of KnockoutJS as an observable array. Observables and observable arrays are mechanisms provided KnockoutJS that create dynamic, two-way bindings between the View and View Model. This means if a value changes in the View Model it will be reflected in the View, and vice versa. Modify the View Model so that it looks like this:

function rolodexModel()
{
  var self = this;
  self.accountId = '{!Account.Id}';
  self.accountName =  '{!Account.Name}';
  self.contacts = ko.observableArray();
}

Note: A property has been created called self so that in any code added to the model we have an easy way to reference the model itself. Using this can get particularly tricky in Javascript when dealing with callbacks and other idioms, and you’ll likely have seen this technique used elsewhere.

Assigning values to observables is easy, the observables themselves are Javascript functions, and you pass the new value as a parameter; so for example, if we had an observable called age, defined using self.age = ko.observable;, it could be set to a value such as 40 by doing self.age(40);. The same goes for observable arrays. So to set an array called people we could do self.people([‘Alice’, ‘Bob’, ‘Carol’]);.

The upshot of this is that because we our Apex method returns a list of contacts, we can simply assign the result straight to our contacts observable array! Before the closing brace of the View Model, add the code snippet below. This calls the method provided by the extension controller (LoadContacts), which uses a callback function as the second parameter. If you’ve not used them before, callback functions are functions that are run by the Visualforce remoting framework when the request to the server has completed. The resulting parameter will be an array of contact records, and we use this data without modification.

RolodexRemoting.LoadContacts(self.accountId, function(result, event)
{
  self.contacts(result);
});

Note: This code is not used as a named Javascript function, but will instead be run direclty when the rolodexModel() instance is created in our window.onload function.

For our last trick in this step, we’ll make sure these contacts we’re loading are displayed on the screen as a list under the account name. To loop over the contacts we need to use another Knockout binding type. This time we’re going to use the foreach binding. This can be applied to any element (in this case a div) and then the contents of that element, including bindings, are repeated and evaluated for each item in the array (very similar to the the Visualforce element <apex:repeat>).

Add the following code after the <h1> tags and reload the page, you should see the account name as before, and then after a short delay the list of contacts will appear when the remoting call completes.

<div data-bind="foreach: contacts">
  <div>
    <h2 data-bind="text: Name"/>
    <span data-bind="text: 'Phone: ' + $data.Phone"/>
    <span data-bind="text: 'Email: ' + $data.Email"/>
  </div>
</div>

In this code, the Name bound to the text for the <h2> element is the name of the field to display from the contact. For the phone number and email fields, we’re using a slightly different syntax. Inside of a foreach loop, $data allows you to refer to the actual array entry itself (i.e. the contact), and referencing the fields this way will prevent an error if either of those fields are blank for a contact in the list, showing ‘undefined’ instead.

At this point, your complete Visualforce page code should look like this:

<apex:page standardController="Account" extensions="RolodexRemoting" showHeader="false" sidebar="false" standardStylesheets="false">
  <style>
    body
    {
      font-family: sans-serif;
      color: #666;
    }

    .panel
    {
      border-radius: 0.5em;
      border: 1px solid #ccc;
      padding: 1em;
    }

    .record
    {
      background-color: #eee;
      border-radius: 0.5em;
      padding: 0.25em 1em 0.25em 1em;
      cursor: pointer; /* Just to be nice to desktop users 😉 */
      margin-top: 0.5em;
    }

    input, button, label
    {
      font-size: 1.5em;
    }

    input, label
    {
      display: block;
    }

    input
    {
      margin-bottom: 0.5em;
    }
  </style>

  <script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"/>
  <script type="text/javascript">

    // This is the ViewModel
    function rolodexModel()
    {
      var self = this;
      self.accountId = '{!Account.Id}';
      self.accountName =  '{!Account.Name}';
      self.contacts = ko.observableArray();

      RolodexRemoting.LoadContacts(self.accountId, function(result, event)
      {
        self.contacts(result);
      });
    }

    window.onload = function() { ko.applyBindings(new rolodexModel()) };
  </script>

  <!-- Everything below here is the View -->
  <div>
    <h1 data-bind="text: accountName"></h1>
    <div data-bind="foreach: contacts">
      <!-- Everything in here is repeated for each contact -->
      <div>
        <h2 data-bind="text: Name"/>
        <span data-bind="text: 'Phone: ' + $data.Phone"/>
        <span data-bind="text: 'Email: ' + $data.Email"/>
      </div>
    </div>
  </div>
</apex:page>

Step 4: Taking Action

Now that the list of contacts is being displayed, we’ll look at leveraging some more KnockoutJS bindings to make things a bit more interactive. We’ll add a new panel that is displayed instead of the list when a contact is clicked, allowing their details to be edited easily. Hopefully this is where the power of KnockoutJS and it’s simplicity will become readily apparent. We’ll need a property to reference the currently selected contact, and as we’ll want the UI to respond when a contact is selected, that property will be an observable, so add this to the View Model:

self.selected = ko.observable();

To provide a selection mechanism, we need to create an action on the View Model that can be called from the View. So after the remoting call we’ll create a new function assigned to a property on the View Model. This is short and sweet. It simply takes a contact as a parameter, and assigns it to the selected property that we just created:

self.selectContact = function(contact)
{
  self.selected(contact);
};

Selecting a contact is one thing, but since we’re going to switch the UI when a contact is selected, we’d better make sure we have a way to clear that selection also:

self.clearSelection = function()
{
  self.selected(null);
};

Now that we’ve made some the changes to the View Model, we’ll modify the View to add a panel that will show the selected contact’s details for editing. This goes after the main div, and includes no less than three new binding types: it uses visible so that it is only displayed when selected has a “non-falsey” value, with binding to specify what part of the View Model this part of the View should use, and the value of the binding to tie properties to input field values.

In this markup, Name, Phone, and Email are not properties of the overall View Model, but of a specific contact, which is why we use the binding to indicate that the contents of this div should use the selected contact property as its data source. As before, $data is used so that blank values don’t cause any problems.

<div data-bind="visible: selected, with: selected">
  <h1 data-bind="text: Name"/>
  <label>Phone:</label>
  <input data-bind="value: $data.Phone"/>
  <label>Email:</label>
  <input data-bind="value: $data.Email"/>
</div>

To hide the other panel while editing, we’ll modify it to also use the visible binding, but we want the opposite effect, i.e. if a contact is selected, then don’t show it. To use Javascript expressions in a binding (as opposed to using a view model property directly), we just call the property as a function:

<div data-bind="visible: !selected()">

We’ve got a method to select a contact, and a way of displaying that contact, but no way of calling the method as of yet. For this we can simply use the click binding to call the selectContact function, and by default it will pass the current item when used in a foreach. $root is required because we’re working with repeated elements, and in the repeated content our main binding is to a contact, not the overall View Model (hence why one Name binding in the source works for each contact record).

<div data-bind="click: $root.selectContact">
  <h2 data-bind="text: Name"/>
</div>

Refresh your page and click a contact. When you do the list should disappear and the new editing panel should be displayed, pre-filled where values exist. Unfortunately, we’re now stuck on this view so a couple of buttons wouldn’t go amiss. Since we’ve already got a function to clear the selected contact, it should come as no surprise that adding a Cancel button is trivial, so add the following button, reload the page, and now you should be able to go forward and back between the list of contacts and the editing panel.

<button data-bind="click: $root.clearSelection">Cancel</button>

Our page would be a lot more useful if the user could actually save their changes. First, we’ll add a new method to our Apex remoting class to save a record (using the ever handy upsert):

@RemoteAction
global static void SaveContact(Contact c)
{
  upsert c;
}

Second, we'll add the saveContact function to the View Model that the button should fire:

self.saveContact = function(contact)
{
  RolodexRemoting.SaveContact(contact, function(result, event)
  {
    // clear the selection after saving
    self.selected(null);
  });
};

And then we’ll add a Save button to the edit panel:

<button data-bind="click: $root.saveContact">Save</button>

Last but not least, we need to make sure the list of contacts is upated with the new details that we just stored, and while we’re at it, we might as well refresh the list from the database in case anybody else has been busy in the mean time. To do this we can simply change our old direct remoting call to be named function in the View Model:

self.loadContacts = function()
{
  RolodexRemoting.LoadContacts(self.accountId, function(result, event)
  {
    self.contacts(result);
  });
}

// still call it on load!
self.loadContacts()

… and then we can simply call that function from the saveContact function, so that complete it looks like this:

self.saveContact = function(contact)
{
  RolodexRemoting.SaveContact(contact, function(result, event)
  {
    // clear the selection after saving
    self.selected(null);
    self.loadContacts();
  });
};

Now once you reload the page you should be able to edit all of the contacts on an Account quickly and easily!


 

 

Wrap Up

This has been a head-long charge into the world of using KnockoutJS on the Salesforce1 Platform and there’s much more that could be said with regards to mobile styling, supporting more dynamic pages (field sets work well with Knockout!) and more, but hopefully it’s piqued your interest and given you the impetus to explore this great framework further. To learn more about using KnockoutJS there really is no greater resource than it’s own documentation which is written in a concise, organized, and easy-to-consume manner. In addition there is a great tutorial on building a more involved single page app, which is also well worth checking out.

Happy coding!

 

Published
February 24, 2015

Leave your comments...

Build A Fast Account Rolodex in Visualforce Using KnockoutJS and CSS