Building the frontend of our CMS

All this while, we have been working on the backend and admin sections of the CMS.

Now, we will work on the frontend, the public-facing side of the website.

As the public-facing side of the website needs to have a neat layout with a logo, navigation bar, content area, footer, and so on, we are going to tweak the index page layout.

Update the angcms/public/index.html file with the upcoming changes.

As we would like to control some application-level settings such as the logo, footer, and so on, we first bind AppCtrl to the <body> tag, as shown in the following code:

<body ng-controller="AppCtrl">

Next, we add the following markup:

<div admin-login class="col-md-3 pull-right"></div>
<div class="container">
    <header>
        <img ng-src="{{site.logo}}">
    </header>
    <div message-flash></div>
    <div class="row">
        <div class="col-md-3" nav-bar></div>
        <div class="col-md-6" ng-view></div>
    </div>
    <footer>{{site.footer}}</footer>
</div>

As you can see from the markup, we are calling in two directives: admin-login, which will display a welcome message to the logged-in user, and nav-bar, which will show relevant navigation links on the left-hand side of the window.

We also plan to have a scope object called site and are displaying the site logo and site footer on this template.

The next step is to create our AppCtrl function in our controller, which is done as follows:

.controller('AppCtrl', ['$scope','AuthService','flashMessageService','$location',function($scope,AuthService,flashMessageService,$location) {
        $scope.site = {
            logo: "img/angcms-logo.png",
            footer: "Copyright 2014 Angular CMS"
        };
    }
])

Refresh the page and notice the logo and footer. Needless to say, ensure that you have a logo named angcms-logo.png present in the img folder.

Building our navigation bar directive

We would like our navigation bar to display the links for all the pages created via the admin. We would like these links to be displayed in a sequence based on their menuIndex values.

We would also like this directive to display the admin menu links when the user is in the admin section.

With these goals in mind, let’s create our directive in the directives.js file as follows:

directive('navBar', [
  function() {
    return {
      controller: function($scope, pagesFactory, $location) {
        var path = $location.path().substr(0, 6);
        if (path == "/admin") {
          $scope.navLinks = [{
            title: 'Pages',
            url: 'admin'
          }, {
            title: 'Site Settings',
            url: 'admin/site-settings'
          }, ];
        } else {
          pagesFactory.getPages().then(
            function(response) {
              $scope.navLinks = response.data;
            }, function() {

            });
          }
        },
        templateUrl: 'partials/directives/nav.html'

      };
  }
])

What we are doing here is using $location.path, we are trying to see whether the user is in the admin section or on the frontend, and based on this, we are populating the navLinks scope object with the relevant menu links.

Next, let’s create the template for this directive. Create a new file named nav.html in angcms/public/partials/directives/nav.html, and add the following code:

<ul class="nav-links">
<li ng-repeat="nav in navLinks | orderBy:'menuIndex'"> <a href="/{{nav.url}}">{{nav.title}}</a>
</li>
</ul>

As you see, we are using ng-repeat to list out our entire page menu and ordering it with the help of menuIndex.

Building the admin-login directive

The next directive that we’ll build is the admin login, which will display the Welcome <username> message and have additional links to jump to the admin or log out.

Let’s add the following directive to the directives.js file:

.directive('adminLogin', [
  function() {
    return {
      controller: function($scope, $cookies) {
        $scope.loggedInUser = $cookies.loggedInUser;
      },
      templateUrl: 'partials/directives/admin-login.html'
    };
  }
]);

The controller code is straightforward, and it simply assigns the loggedInUser value from the cookie to the scope object.

We will create it’s template as a new file in partials/directives/admin-login.html as follows:

<div ng-if=loggedInUser>
    Welcome {{loggedInUser}} |  <a href="admin/pages">My Admin</a> | <a href ng-click='logout()'>Logout</a>
</div>

Next, we will quickly write the code for the logout method. As this directive is within the scope of AppCtrl, we will write this method within the AppCtrl function as follows:

$scope.logout = function() {
  AuthService.logout().then(
    function() {

      $location.path('/admin/login');
      flashMessageService.setMessage("Successfully logged out");

    }, function(err) {
        console.log('there was an error tying to logout');
    });
};

Displaying the content of a page

The last and most crucial step of this entire project is to display the actual content of the selected page.

This will require us to create a new route that will accept route params. Let’s get this done first in our public/js/app.js file as follows:

$routeProvider.when('/:url', {
    templateUrl: 'partials/page.html',
    controller: 'PageCtrl'
});

Next, let’s create the partials view as a new file called partials/page.html with the following content:

<h1>{{pageContent.title}}</h1>
<div ng-bind-html="pageContent.content"></div>

We are using the ng-bind-html directive here so that the HTML content is rendered correctly instead of it spitting out the raw HTML as it is.

Next, let’s create our PageCtrl function in controllers.js as follows:

.controller('PageCtrl',  [ '$scope','pagesFactory', '$routeParams ', function($scope, pagesFactory, $routeParams) {
    var url = $routeParams.url;
    pagesFactory.getPageContent(url).then(
      function(response) {
        $scope.pageContent = response.data; 
      }, function() {
        console.log('error fetching data');
      });
    }]);

Save the file, refresh the site, and hit any of the frontend links. You’ll get an error in your console; you will see something like the following screenshot:

Displaying the content of a page
So, what went wrong here? What is $sce?

Note

One of the coolest things about testing angularjs apps in Google Chrome is whenever there is an error message, angularjs has a hyperlink that will take you directly to the site that explains what the error is.

By reading up on the link, you’ll get to know that the Strict Contextual Escaping (SCE) mode of angularjs is turned on by default, and angularjs feels that the HTML markup on the content of our CMS pages is unsafe. To overcome this, we will need to explicitly tell $sce to trust our content. We do this in our controller by adding the following highlighted lines to the PageCtrl function:

.controller('PageCtrl', ['$scope','pagesFactory', '$routeParams', '$sce', function($scope, pagesFactory, $routeParams,$sce) {
      var url = $routeParams.url;
      pagesFactory.getPageContent(url).then(
        function(response) {
          $scope.pageContent = {};
          $scope.pageContent.title = response.data.title;
          $scope.pageContent.content = $sce.trustAsHtml(response.data.content);

        }, function() {
            console.log('error fetching data');
    });
}])

Save the file and refresh any of the page URLs. Now, you should be able to see the title and page contents with the HTML formatting.

Setting the default home page

Now, our public-facing frontend is working quite well with all the nav links, content, and so on. However, when you launch the site for the first time or hit http://localhost:3000/, we land up with a blank screen.

To overcome this, we will make sure that our site always has a page titled Home.

Then, in the page controller, we will simply add the following highlighted line, which will set the default value of the URL to home in case we don’t find a URL param in the current route; we will add this to the PageCtrl function:

        var url = $routeParams.url;
        if(!url) url="home";

Now, the home page will load by default for the preceding URL link. Alternatively, you can also set the $routeProvider redirect in the public/js/app.js file to, say, the following:

$routeProvider.otherwise({redirectTo: '/home'});

 

wrapping up

We went full stack, right from coding our backend by building REST APIs to saving and reading data from the database. We also built the angularjs frontend that interacts with these backend APIs.

The key takeaways from this Tutorial are as follows:

  • Building backend web services using Node.js, MongoDB, and ExpressJS
  • Securing API using sessions
  • Making angularjs and ExpressJS work together and build routes that span across both the systems
  • Authenticating on the client side using Interceptors
  • Integrating third-party modules
  • Using custom filters to format and store data
  • Building a custom module for a global notification system