Development of OTRS features

As you know, we are developing the SPA. Therefore, once the application loads, you can perform all the operations without page refresh. All interactions with the server are performed using AJAX calls. Now, we'll make use of the AngularJS concepts that we have covered in the first section. We'll cover the following scenarios:

  • A page that will display a list of restaurants. This will also be our home page.
  • Search restaurants.
  • Restaurant details with reservation options.
  • Login (not from the server, but used for displaying the flow).
  • Reservation confirmation.

For the home page, we will create index.html and a template that will contain the restaurant listing in the middle section or the content area.

Home page/restaurant list page

The home page is the main page of any web application. To design the home page, we are going to use the Angular-UI bootstrap rather than the actual bootstrap. Angular-UI is an Angular version of the bootstrap. The home page will be divided into three sections:

  • The header section will contain the app name, search restaurants form, and user name at top-right corner.
  • The content or middle section will contain the restaurant listing which will have the restaurant name as the link. This link will point to the restaurant details and reservation page.
  • The footer section will contain the app name with the copyright mark.

You must be interested in viewing the home page before designing or implementing it. Therefore, let us first see how it will look like once we have our content ready:

Home page/restaurant list page

OTRS home page with restaurants listing

Now, to design our home page, we need to add following four files:

  • index.html: Our main HTML file
  • app.js: Our main AngularJS module
  • restaurants.js: The restaurants module that also contains the restaurant Angular service
  • restaurants.html: The HTML template that will display the list of restaurants

index.html

First, we'll add the ./app/index.html in our project workspace. The contents of index.html will be as explained here onwards.

Note

I have added comments in between the code to make the code more readable and make it easier to understand.

index.html is divided into many parts. We'll discuss a few of the key parts here. First, we will see how to address old Internet Explorer versions. If you want to target the Internet Explorer browser versions greater than 8 or IE version 9 onwards, then we need to add following block that will prevent JavaScript rendering and give the no-js output to the end-user.

<!--[if lt IE 7]>      <html lang="en" ng-app="otrsApp" class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html lang="en" ng-app="otrsApp" class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html lang="en" ng-app="otrsApp" class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html lang="en" ng-app="otrsApp" class="no-js"> <!--<![endif]-->

Then, after adding a few meta tags and the title of the application, we'll also define the important meta tag viewport. The viewport is used for responsive UI designs.

The width property defined in the content attribute controls the size of the viewport. It can be set to a specific number of pixels like width = 600 or to the special value device-width value which is the width of the screen in CSS pixels at a scale of 100%.

The initial-scale property controls the zoom level when the page is first loaded. The maximum-scale, minimum-scale, and user-scalable properties control how users are allowed to zoom the page in or out.

        <meta name="viewport" content="width=device-width, initial-scale=1">

In the next few lines, we'll define the style sheets of our application. We are adding normalize.css and main.css from HTML5 boilerplate code. We are also adding our application's customer CSS app.css. Finally, we are adding the bootstrap 3 CSS. Apart from the customer app.css, other CSS are referenced in it. There is no change in these CSS files.

<link rel="stylesheet" href="bower_components/html5-boilerplate/dist/css/normalize.css">
        <link rel="stylesheet" href="bower_components/html5-boilerplate/dist/css/main.css">
        <link rel="stylesheet" href="public/css/app.css">
        <link data-require="bootstrap-css@*" data-server="3.0.0" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />

Then we'll define the scripts using the script tag. We are adding the modernizer, Angular, Angular-route, and our own developed custom JavaScript file app.js. We have already discussed Angular and Angular-UI. app.js will be discussed in the next section.

Modernizer allows web developers to use new CSS3 and HTML5 features while maintaining a fine level of control over browsers that don't support them. Basically, modernizer performs the next generation feature detection (checking the availability of those features) while the page loads in the browser and reports the results. Based on these results you can detect what are the latest features available in the browser and based on that you can provide an interface to the end user. If the browser does not support a few of the features then an alternate flow or UI is provided to the end user.

We are also adding the bootstrap templates which are written in JavaScript using the ui-bootstrap-tpls javascript file.

        <script src="bower_components/html5-boilerplate/dist/js/vendor/modernizr-2.8.3.min.js"></script>
        <script src="bower_components/angular/angular.min.js"></script>
        <script src="bower_components/angular-route/angular-route.min.js"></script>
        <script src="app.js"></script>
      <script data-require="[email protected]" data-semver="0.5.0" src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>

We can also add style to the head tag as shown in the following. This style allows drop-down menus to work.

        <style>
            div.navbar-collapse.collapse {
                display: block;
                overflow: hidden;
                max-height: 0px;
                -webkit-transition: max-height .3s ease;
                -moz-transition: max-height .3s ease;
                -o-transition: max-height .3s ease;
                transition: max-height .3s ease;
            }
            div.navbar-collapse.collapse.in {
                max-height: 2000px;
            }
        </style>

In the body tag we are defining the controller of the application using the ng-controller attribute. While the page loads, it tells the controller the name of the application to Angular.

    <body ng-controller="otrsAppCtrl">

Then, we define the header section of the home page. In the header section, we'll define the application title, Online Table Reservation System. Also, we'll define the search form that will search the restaurants.

<!-- BEGIN HEADER -->
        <nav class="navbar navbar-default" role="navigation">

            <div class="navbar-header">
                <a class="navbar-brand" href="#">
                    Online Table Reservation System
                </a>
            </div>
            <div class="collapse navbar-collapse" ng-class="!navCollapsed && 'in'" ng-click="navCollapsed = true">
                <form class="navbar-form navbar-left" role="search" ng-submit="search()">
                    <div class="form-group">
                        <input type="text" id="searchedValue" ng-model="searchedValue" class="form-control" placeholder="Search Restaurants">
                    </div>
                    <button type="submit" class="btn btn-default" ng-click="">Go</button>
                </form>
        <!-- END HEADER -->

Then, in the next section, the middle section, includes where we actually bind the different views, marked with actual content comments. The ui-view attribute in div gets its content dynamically from Angular such as restaurant details, restaurant list, and so on. We have also added a warning dialog and spinner to the middle section that will be visible as and when required.

        <div class="clearfix"></div>
        <!-- BEGIN CONTAINER -->
        <div class="page-container container">
            <!-- BEGIN CONTENT -->
            <div class="page-content-wrapper">
                <div class="page-content">
                    <!-- BEGIN ACTUAL CONTENT -->
                    <div ui-view class="fade-in-up"></div>
                    <!-- END ACTUAL CONTENT -->
                </div>
            </div>
            <!-- END CONTENT -->
        </div>
        <!-- loading spinner -->
        <div id="loadingSpinnerId" ng-show="isSpinnerShown()" style="top:0; left:45%; position:absolute; z-index:999">
            <script type="text/ng-template" id="alert.html">
                <div class="alert alert-warning" role="alert">
                <div ng-transclude></div>
                </div>
            </script>
            <uib-alert type="warning" template-url="alert.html"><b>Loading...</b></uib-alert>
        </div>
        <!-- END CONTAINER -->

The final section of the index.html is the footer. Here, we are just adding the static content and copyright text. You can add whatever content you want here.

        <!-- BEGIN FOOTER -->
        <div class="page-footer">
            <hr/><div style="padding: 0 39%">&copy; 2016 Online Table Reservation System</div>
        </div>
        <!-- END FOOTER -->
    </body>
</html>

app.js

app.js is our main application file. Because we have defined it in index.html, it gets loaded as soon as our index.html is called.

Tip

We need to take care that we do not mix, route (URI) with REST endpoints. Routes represents the state/view of the SPA.

As we are using the Edge Server (Proxy Server), everything will be accessible from it including our REST endpoints. External applications including the UI will use the Edge Server host to access the application. You can configure it in some global constants file and then use it wherever it is required. This will allow you to configure the REST host at a single place and use it at other places.

'use strict';
/*
This call initializes our application and registers all the modules, which are passed as an array in the second argument.
*/
var otrsApp = angular.module('otrsApp', [
    'ui.router',
    'templates',
    'ui.bootstrap',
    'ngStorage',
    'otrsApp.httperror',
    'otrsApp.login',
    'otrsApp.restaurants'
])
/*
  Then we have defined the default route /restaurants
*/
        .config([
            '$stateProvider', '$urlRouterProvider',
            function ($stateProvider, $urlRouterProvider) {
                $urlRouterProvider.otherwise('/restaurants');
            }])
/*
	This functions controls the flow of the application and handles the events.
*/
        .controller('otrsAppCtrl', function ($scope, $injector, restaurantService) {
            var controller = this;

            var AjaxHandler = $injector.get('AjaxHandler');
            var $rootScope = $injector.get('$rootScope');
            var log = $injector.get('$log');
            var sessionStorage = $injector.get('$sessionStorage');
            $scope.showSpinner = false;
/*
	This function gets called when the user searches any restaurant. It uses the Angular restaurant service that we'll define in the next section to search the given search string.
*/
            $scope.search = function () {
                $scope.restaurantService = restaurantService;
                restaurantService.async().then(function () {
                    $scope.restaurants = restaurantService.search($scope.searchedValue);
                });
            }
/*
	When the state is changed, the new controller controls the flows based on the view and configuration and the existing controller is destroyed. This function gets a call on the destroy event.
*/
            $scope.$on('$destroy', function destroyed() {
                log.debug('otrsAppCtrl destroyed');
                controller = null;
                $scope = null;
            });

            $rootScope.fromState;
            $rootScope.fromStateParams;
            $rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromStateParams) {
                $rootScope.fromState = fromState;
                $rootScope.fromStateParams = fromStateParams;
            });

            // utility method
            $scope.isLoggedIn = function () {
                if (sessionStorage.session) {
                    return true;
                } else {
                    return false;
                }
            };

            /* spinner status */
            $scope.isSpinnerShown = function () {
                return AjaxHandler.getSpinnerStatus();
            };

        })
/*
	This function gets executed when this object loads. Here we are setting the user object which is defined for the root scope.
*/
        .run(['$rootScope', '$injector', '$state', function ($rootScope, $injector, $state) {
                $rootScope.restaurants = null;
                // self reference
                var controller = this;
                // inject external references
                var log = $injector.get('$log');
                var $sessionStorage = $injector.get('$sessionStorage');
                var AjaxHandler = $injector.get('AjaxHandler');

                if (sessionStorage.currentUser) {
                    $rootScope.currentUser = $sessionStorage.currentUser;
                } else {
                    $rootScope.currentUser = "Guest";
                    $sessionStorage.currentUser = ""
                }
            }])

restaurants.js

restaurants.js represents an Angular service for our app which we'll use for the restaurants. We know that there are two common uses of services – organizing code and sharing code across apps. Therefore, we have created a restaurants service which will be used among different modules like search, list, details, and so on.

Note

Services are singleton objects, which are lazily instantiated by the AngularJS service factory.

The following section initializes the restaurant service module and loads the required dependencies.

angular.module('otrsApp.restaurants', [
    'ui.router',
    'ui.bootstrap',
    'ngStorage',
    'ngResource'
])

In the configuration, we are defining the routes and state of the otrsApp.restaurants module using UI-Router.

First we define the restaurants state by passing the JSON object containing the URL that points the router URI, the template URL that points to the HTML template that display the restaurants state, and the controller that will handle the events on the restaurants view.

On top of the restaurants view (route - /restaurants), a nested state restaurants.profile is also defined that will represent the specific restaurant. For example, /restaurant/1 would open and display the restaurant profile (details) page of a restaurant which is represented by Id 1. This state is called when a link is clicked in the restaurants template. In this ui-sref="restaurants.profile({id: rest.id})" rest represents the restaurant object retrieved from the restaurants view.

Notice that the state name is 'restaurants.profile' which tells the AngularJS UI Router that the profile is a nested state of the restaurants state.

        .config([
            '$stateProvider', '$urlRouterProvider',
            function ($stateProvider, $urlRouterProvider) {
                $stateProvider.state('restaurants', {
                    url: '/restaurants',
                    templateUrl: 'restaurants/restaurants.html',
                    controller: 'RestaurantsCtrl'
                })
                        // Restaurant show page
                        .state('restaurants.profile', {
                            url: '/:id',
                            views: {
                                '@': {
                                    templateUrl: 'restaurants/restaurant.html',
                                    controller: 'RestaurantCtrl'
                                }
                            }
                        });
            }])

In the next code section, we are defining the restaurant service using the Angular factory service type. This restaurant service on load fetches the list of restaurants from the server using a REST call. It provides a list and searches restaurant operations and restaurant data.

        .factory('restaurantService', function ($injector, $q) {
            var log = $injector.get('$log');
            var ajaxHandler = $injector.get('AjaxHandler');
            var deffered = $q.defer();
            var restaurantService = {};
            restaurantService.restaurants = [];
            restaurantService.orignalRestaurants = [];
            restaurantService.async = function () {
                ajaxHandler.startSpinner();
                if (restaurantService.restaurants.length === 0) {
                    ajaxHandler.get('/api/restaurant')
                            .success(function (data, status, headers, config) {
                                log.debug('Getting restaurants');
                                sessionStorage.apiActive = true;
                                log.debug("if Restaurants --> " + restaurantService.restaurants.length);
                                restaurantService.restaurants = data;
                                ajaxHandler.stopSpinner();
                                deffered.resolve();
                            })
                            .error(function (error, status, headers, config) {
                                restaurantService.restaurants = mockdata;
                                ajaxHandler.stopSpinner();
                                deffered.resolve();
                            });
                    return deffered.promise;
                } else {
                    deffered.resolve();
                    ajaxHandler.stopSpinner();
                    return deffered.promise;
                }
            };
            restaurantService.list = function () {
                return restaurantService.restaurants;
            };
            restaurantService.add = function () {
                console.log("called add");
                restaurantService.restaurants.push(
                        {
                            id: 103,
                            name: 'Chi Cha's Noodles',
                            address: '13 W. St., Eastern Park, New County, Paris',
                        });
            };
            restaurantService.search = function (searchedValue) {
                ajaxHandler.startSpinner();
                if (!searchedValue) {
                    if (restaurantService.orignalRestaurants.length > 0) {
                        restaurantService.restaurants = restaurantService.orignalRestaurants;
                    }
                    deffered.resolve();
                    ajaxHandler.stopSpinner();
                    return deffered.promise;
                } else {
                    ajaxHandler.get('/api/restaurant?name=' + searchedValue)
                            .success(function (data, status, headers, config) {
                                log.debug('Getting restaurants');
                                sessionStorage.apiActive = true;
                                log.debug("if Restaurants --> " + restaurantService.restaurants.length);
                                if (restaurantService.orignalRestaurants.length < 1) {
                                    restaurantService.orignalRestaurants = restaurantService.restaurants;
                                }
                                restaurantService.restaurants = data;
                                ajaxHandler.stopSpinner();
                                deffered.resolve();
                            })
                            .error(function (error, status, headers, config) {
                                if (restaurantService.orignalRestaurants.length < 1) {
                                    restaurantService.orignalRestaurants = restaurantService.restaurants;
                                }
                                restaurantService.restaurants = [];
                                restaurantService.restaurants.push(
                                        {
                                            id: 104,
                                            name: 'Gibsons - Chicago Rush St.',
                                            address: '1028 N. Rush St., Rush & Division, Cook County, Paris'
                                        });
                                restaurantService.restaurants.push(
                                        {
                                            id: 105,
                                            name: 'Harry Caray's Italian Steakhouse',
                                            address: '33 W. Kinzie St., River North, Cook County, Paris',
                                        });
                                ajaxHandler.stopSpinner();
                                deffered.resolve();
                            });
                    return deffered.promise;
                }
            };
            return restaurantService;
        })

In the next section of the restaurants.js module, we'll add two controllers that we defined for the restaurants and restaurants.profile states in the routing configuration. These two controllers are RestaurantsCtrl and RestaurantCtrl that handle the restaurants state and the restaurants.profiles states respectively.

RestaurantsCtrl is pretty simple in that it loads the restaurants data using the restaurants service list method.

        .controller('RestaurantsCtrl', function ($scope, restaurantService) {
            $scope.restaurantService = restaurantService;
            restaurantService.async().then(function () {
                $scope.restaurants = restaurantService.list();
            });
        })

RestaurantCtrl is responsible for showing the restaurant details of a given ID. This is also responsible for performing the reservation operations on the displayed restaurant. This control will be used when we design the restaurant details page with reservation options.

        .controller('RestaurantCtrl', function ($scope, $state, $stateParams, $injector, restaurantService) {
            var $sessionStorage = $injector.get('$sessionStorage');
            $scope.format = 'dd MMMM yyyy';
            $scope.today = $scope.dt = new Date();
            $scope.dateOptions = {
                formatYear: 'yy',
                maxDate: new Date().setDate($scope.today.getDate() + 180),
                minDate: $scope.today.getDate(),
                startingDay: 1
            };

            $scope.popup1 = {
                opened: false
            };
            $scope.altInputFormats = ['M!/d!/yyyy'];
            $scope.open1 = function () {
                $scope.popup1.opened = true;
            };
            $scope.hstep = 1;
            $scope.mstep = 30;

            if ($sessionStorage.reservationData) {
                $scope.restaurant = $sessionStorage.reservationData.restaurant;
                $scope.dt = new Date($sessionStorage.reservationData.tm);
                $scope.tm = $scope.dt;
            } else {
                $scope.dt.setDate($scope.today.getDate() + 1);
                $scope.tm = $scope.dt;
                $scope.tm.setHours(19);
                $scope.tm.setMinutes(30);
                restaurantService.async().then(function () {
                    angular.forEach(restaurantService.list(), function (value, key) {
                        if (value.id === parseInt($stateParams.id)) {
                            $scope.restaurant = value;
                        }
                    });
                });
            }
            $scope.book = function () {
                var tempHour = $scope.tm.getHours();
                var tempMinute = $scope.tm.getMinutes();
                $scope.tm = $scope.dt;
                $scope.tm.setHours(tempHour);
                $scope.tm.setMinutes(tempMinute);
                if ($sessionStorage.currentUser) {
                    console.log("$scope.tm --> " + $scope.tm);
                    alert("Booking Confirmed!!!");
                    $sessionStorage.reservationData = null;
                    $state.go("restaurants");
                } else {
                    $sessionStorage.reservationData = {};
                    $sessionStorage.reservationData.restaurant = $scope.restaurant;
                    $sessionStorage.reservationData.tm = $scope.tm;
                    $state.go("login");
                }
            }
        })

We have also added a few of the filters in the restaurants.js module to format the date and time. These filters perform the following formatting on the input data:

  • date1: Returns the input date in 'dd MMM yyyy' format, for example 13-Apr-2016
  • time1: Returns the input time in 'HH:mm:ss' format, for example 11:55:04
  • dateTime1: Returns the input date and time in 'dd MMM yyyy HH:mm:ss' format, for example 13-Apr-2016 11:55:04

In the following code snippet we've applied these three filters:

        .filter('date1', function ($filter) {
            return function (argDate) {
                if (argDate) {
                    var d = $filter('date')(new Date(argDate), 'dd MMM yyyy');
                    return d.toString();
                }
                return "";
            };
        })
        .filter('time1', function ($filter) {
            return function (argTime) {
                if (argTime) {
                    return $filter('date')(new Date(argTime), 'HH:mm:ss');
                }
                return "";
            };
        })
        .filter('datetime1', function ($filter) {
            return function (argDateTime) {
                if (argDateTime) {
                    return $filter('date')(new Date(argDateTime), 'dd MMM yyyy HH:mm a');
                }
                return "";
            };
        });

restaurants.html

We need to add the templates that we have defined for the restaurants.profile state. As you can see in the template we are using the ng-repeat directive to iterate the list of objects returned by restaurantService.restaurants. The restaurantService scope variable is defined in the controller. 'RestaurantsCtrl' is associated with this template in the restaurants state.

<h3>Famous Gourmet Restaurants in Paris</h3>
<div class="row">
    <div class="col-md-12">
        <table class="table table-bordered table-striped">
            <thead>
                <tr>
                    <th>#Id</th>
                    <th>Name</th>
                    <th>Address</th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="rest in restaurantService.restaurants">
                    <td>{{rest.id}}</td>
                    <td><a ui-sref="restaurants.profile({id: rest.id})">{{rest.name}}</a></td>
                    <td>{{rest.address}}</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>

Search Restaurants

On the home page index.html we have added the search form in the header section that allows us to search restaurants. The Search Restaurants functionality will use the same files as described earlier. It makes use of the app.js (search form handler), restaurants.js (restaurant service), and restaurants.html to display the searched records.

Search Restaurants

OTRS home page with restaurants listing

Restaurant details with reservation option

Restaurant details with reservation option will be the part of the content area (middle section of the page). This will contain a breadcrumb at the top with restaurants as a link to the restaurant listing page, followed by the name and address of the restaurant. The last section will contain the reservation section containing date time selection boxes and reserve button.

This page will look like the following screenshot:

Restaurant details with reservation option

Restaurants Detail Page with Reservation Option

Here, we will make use of the same restaurant service declared in restaurants.js. The only change will be the template as described for the state restaurants.profile. This template will be defined using the restaurant.html.

restaurant.html

As you can see, the breadcrumb is using the restaurants route, which is defined using the ui-sref attribute. The reservation form designed in this template calls the book() function defined in the controller RestaurantCtrl using the directive ng-submit on the form submit.

<div class="row">
<div class="row">
    <div class="col-md-12">
        <ol class="breadcrumb">
            <li><a ui-sref="restaurants">Restaurants</a></li>
            <li class="active">{{restaurant.name}}</li>
        </ol>
        <div class="bs-docs-section">
            <h1 class="page-header">{{restaurant.name}}</h1>
            <div>
                <strong>Address:</strong> {{restaurant.address}}
            </div>
            </br></br>
            <form ng-submit="book()">
                <div class="input-append date form_datetime">
                    <div class="row">
                        <div class="col-md-7">
                            <p class="input-group">
                                <span style="display: table-cell; vertical-align: middle; font-weight: bolder; font-size: 1.2em">Select Date & Time for Booking:</span>
                                <span style="display: table-cell; vertical-align: middle">
                                    <input type="text" size=20 class="form-control" uib-datepicker-popup="{{format}}" ng-model="dt" is-open="popup1.opened" datepicker-options="dateOptions" ng-required="true" close-text="Close" alt-input-formats="altInputFormats" />
                                </span>
                                <span class="input-group-btn">
                                    <button type="button" class="btn btn-default" ng-click="open1()"><i class="glyphicon glyphicon-calendar"></i></button>
                                </span>
                            <uib-timepicker ng-model="tm" ng-change="changed()" hour-step="hstep" minute-step="mstep"></uib-timepicker>
                            </p>
                        </div>
                    </div></div>
                <div class="form-group">
                    <button class="btn btn-primary" type="submit">Reserve</button>
                </div>
            </form></br></br>
        </div>
    </div>
</div>

Login page

When a user clicks on the Reserve button on the Restaurant Detail page after selecting the date and time of the reservation, the Restaurant Detail page checks whether the user is already logged in or not. If the user is not logged in, then the Login page displays. It looks like the following screenshot:

Login page

Login page

Note

We are not authenticating the user from the server. Instead, we are just populating the user name in the session storage and rootscope for implementing the flow.

Once the user logs in, the user is redirected back to same booking page with the persisted state. Then the user can proceed with the reservation. The Login page uses basically two files: login.html and login.js.

login.html

The login.html template consists of only two input fields, username and password, with the Login button and Cancel link. The Cancel link resets the form and the Login button submits the login form.

Here, we are using the LoginCtrl with the ng-controller directive. The Login form is submitted using the ng-submit directive that calls the submit function of LoginCtrl. Input values are first collected using the ng-model directive and then submitted using their respective properties - _email and _password.

<div ng-controller="LoginCtrl as loginC" style="max-width: 300px">
    <h3>Login</h3>
    <div class="form-container">
        <form ng-submit="loginC.submit(_email, _password)">
            <div class="form-group">
                <label for="username" class="sr-only">Username</label>
                <input type="text" id="username" class="form-control" placeholder="username" ng-model="_email" required autofocus />
            </div>
            <div class="form-group">
                <label for="password" class="sr-only">Password</label>
                <input type="password" id="password" class="form-control" placeholder="password" ng-model="_password" />
            </div>
            <div class="form-group">
                <button class="btn btn-primary" type="submit">Login</button>
                <button class="btn btn-link" ng-click="loginC.cancel()">Cancel</button>
            </div>
        </form>
    </div>
</div>

login.js

The login module is defined in the login.js that contains and loads the dependencies using the module function. The state login is defined with the help of the config function that takes the JSON object containing the url, controller, and templateUrl properties.

Inside the controller, we define the cancel and submit operations, which are called from the login.html template.

angular.module('otrsApp.login', [
    'ui.router',
    'ngStorage'
])
        .config(function config($stateProvider) {
            $stateProvider.state('login', {
                url: '/login',
                controller: 'LoginCtrl',
                templateUrl: 'login/login.html'
            });
        })
        .controller('LoginCtrl', function ($state, $scope, $rootScope, $injector) {
            var $sessionStorage = $injector.get('$sessionStorage');
            if ($sessionStorage.currentUser) {
                $state.go($rootScope.fromState.name, $rootScope.fromStateParams);
            }
            var controller = this;
            var log = $injector.get('$log');
            var http = $injector.get('$http');

            $scope.$on('$destroy', function destroyed() {
                log.debug('LoginCtrl destroyed');
                controller = null;
                $scope = null;
            });
            this.cancel = function () {
                $scope.$dismiss;
                $state.go('restaurants');
            }
            console.log("Current --> " + $state.current);
            this.submit = function (username, password) {
                $rootScope.currentUser = username;
                $sessionStorage.currentUser = username;
                if ($rootScope.fromState.name) {
                    $state.go($rootScope.fromState.name, $rootScope.fromStateParams);
                } else {
                    $state.go("restaurants");
                }
            };
        });

Reservation confirmation

Once the user is logged in and has clicked on the Reservation button, the restaurant controller shows the alert box with confirmation as shown in the following screenshot.

Reservation confirmation

Restaurants detail page with reservation confirmation

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset