codescience-resource-reatured-image-generic

How to use Angular Bootstrap SF1 with custom directives.

Let’s start off by explaining what Angular Bootstrap SF1 is. Angular is an MVC (Model View Control) Javascript framework by Google for creating single-page web applications. Bootstrap SF1 is a CSS library based on Twitter Bootstrap, but tailored to look just like Salesforce1. Let’s see what happens when the two libraries are used together. For this post, I am using Bower to install the libraries and I have provided links to everything used below. So lets begin by installing Angular and Bootstrap SF1.

bower install bootstrap-sf1
bower install angular

Next we need a simple html page that uses Angular and Bootstrap SF1.

<html ng-app="csBlog">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
        <title>CodeScience Blog Post</title>
        <link rel="stylesheet" href="bower_components/bootstrap-sf1/dist/css/bootstrap-namespaced.css" />
        <script src="bower_components/angular/angular.js"></script>
        <script src="app.js"></script>
    </head>
    <body class="bootstrap" ng-controller="MainCtrl">
        <sfdc-header header=""></sfdc-header>
        <sfdc-account account=""></sfdc-account>
    </body>
</html>

As you can see, this page has two custom directives added <sfdc-header> and <sfdc-account>. This is where Angular comes into play. Both of these custom tags we will create in Angular use Bootstrap SF1 for the styling of these directives. Next we will create the two html snippets that we will use for these directives. In this post, I am using the namespaced version of Bootstrap SF1, which means I have added the class=”Bootstrap” to the body tag of the html page. This allows the snippets to the custom tags of the CSS library while also keeping all of the elements named spaced to avoid conflicts with Salesforce1 CSS.

<!-- Save this file as sfdc_header.html under a folder called templates -->
<div class="page-header page-header-anchor context-account">
<div class="container">
    <h1>{{header}}</h1>
</div>
</div>
<!-- Save this file as sfdc_account.html under a folder called templates -->
<div class="col-md-4">
<div class="card context-account">
<div class="card-heading">
    <a href="{{account.account_data.domain}}">{{account.account_data.name}} ({{account.account_data.id}})</a>
</div>
    <ul class="card-detail ">
        <li>{{account.location_data.address}}</li>
        <li>{{account.location_data.city}}, {{account.location_data.state}}</li>
    </ul>
</div>
</div>

Next, we will create the app.js, which is the Angular application. Once this is created in the root of your project your folder structure should resemble this. Screen Shot 2014-07-09 at 9.42.36 AM

var app = angular.module('csBlog', []);
app.controller('MainCtrl', function(GetJsonFactory, $scope) {
    GetJsonFactory.fetch().then(function(data) {
        $scope.accounts = data;
    });
})
app.directive('sfdcAccount', function() {
    return {
        restrict: 'E',
        scope:{
            account:"="
        },
        templateUrl: 'templates/sfdc_account.html'
    };
})
app.directive('sfdcHeader', function() {
    return {
        restrict: 'E',
        scope:{
            header:"="
        },
        templateUrl: 'templates/sfdc_header.html'
    };
})
app.factory('GetJsonFactory', function($q, $timeout, $http) {
    var Accounts = {
        fetch: function(callback) {
            var deferred = $q.defer();
            $timeout(function() {
                $http.get('data.json').success(function(data) {
                    deferred.resolve(data);
                });
            }, 30);
        return deferred.promise;
    }
};
    return Accounts;
});

Note: This post is only covering directives of Angular if you are not familiar with Angular please take a look at the links below. So now, an Angular module is created and csBlog points to the application on <html ng-app=”csBlog”>. For this post, I am skipping the controller and factory methods, but will cover these in later posts. But for now, let’s just say the GetJsonFactory is simply returning the data.json file to $scope.data so we can have data to work with. The data in the json file looks like this.

{
    "Codescience": {
        "account_data": {
            "id": 123,
            "domain": "http://codescience.com",
            "name": "Codescience"
        },
        "location_data": {
            "address": "101 Street",
            "city": "Chattanooga",
            "state": "TN",
            "zip": "30720"
        }
    },
    "Salesforce": {
        "account_data": {
            "id": 234,
            "domain": "http://salesforce.com",
            "name": "Salesforce"
        },
        "location_data": {
            "address": "102 Any Street",
            "city": "San Francisco",
            "state": "CA",
            "zip": "94105"
        }
    }
}

Since we have a factory method and a controller to call from, let’s look at how we use this data in a directive. If you notice earlier in the post, I had you create a snippet for sfdc_accounts.html that had {{account.account_data.name}} inside of the href and tags. This is how we will inject the data from $scope into the directive.

app.directive('sfdcAccount', function() {
    return {
        restrict: 'E',
        scope:{
            account:"="
        },
        templateUrl: 'templates/sfdc_account.html'
    };
})
app.directive('sfdcHeader', function() {
    return {
        restrict: 'E',
        scope:{
            header:"="
        },
        templateUrl: 'templates/sfdc_header.html'
    };
})

In our directives we have three things happening

  1. restrict
  2. scope
  3. templateUrl

Restrict tells Angular to look for an html element in camelCase form i.e. ‘sfdcAccount’ and will look for <sfdc-account>. Scope is the isolated scope of the directive to which we will pass the data for it to use. We name this scope here, i.e. account:”=” by using the = sign this is two-way binding from the directive and scope. TemplateUrl is the path to the html snippet that will be parsed and inject the data from the isolated scope. This is also where Bootstrap SF1 comes into play. By separating our html snippets into sections, we can use the styling of Bootstrap SF1 while also making a reusable component that can be used anywhere in our application. Now we have a directive and we have data saved to the scope. But how do we pass the data to the directive? Simple, you remember what you named your isolated scope inside of the directive? account:”=” Lets look at the <sfdc-header> first since it is only expecting a string named header on it’s scope.

<sfdc-header header="'CSBlog'"></sfdc-header>

Since our directive for sfdc-header is expecting a string named “header” on it’s scope all we need to do is add an attribute to the html element named the same as we did in our directives scope header: “=” and then pass the string as the value of this attribute header=” ‘CSBlog’ “. Now we have a custom Angular directive using the Bootstrap SF1 styling that can be reused or we can pair with Visualforce like this.

<sfdc-header header="'{! $CurrentPage.Name }'"></sfdc-header>

That was a lot to cover in one post. but you should have working directives styled to Salesforce1. In my next post, I will cover how to package the libraries using resource bundles and create a Visualforce page that can be displayed in Salesforce1.   Links: Bower Angularjs Bootstrap SF1