How to add a new tab in the admin panel

In this tutorial we will describe how to add a new simple tab (mock) to admin panel.

Create a module

Let’s create a new admin.mock directory in frontend/src/modules. Module contains templates directory for html templates and minimum 3 files.

  • MockController.js - responsible for controlling views,
  • MockService.js - providing logic or data,
  • module.js - entry point for module, defining routing, controllers, services etc.

Create module.js with content:

import MockController from './MockController';
import MockService from './MockService';

const MODULE_NAME = 'admin.mock';

angular.module(MODULE_NAME, [])
    .config($stateProvider => {
        $stateProvider
            .state('admin.mock-list', {
                url: "/mock-list",
                views: {
                    'extendTop@': {
                        templateUrl: 'templates/mock-list-extend-top.html',
                        controller: 'MockController',
                        controllerAs: 'MockCtrl'
                    },
                    'main@': {
                        templateUrl: require('./templates/mock-list.html'),
                        controller: 'MockController',
                        controllerAs: 'MockCtrl'
                    },
                    'extendBottom@': {
                        templateUrl: 'templates/mock-list-extend-bottom.html',
                        controller: 'MockController',
                        controllerAs: 'MockCtrl'
                    }
                }
            })
    })
    .run(($templateCache, $http) => {
        let catchErrorTemplate = () => {
            throw `${MODULE_NAME} has missing template`
        };
        $templateCache.put('templates/mock-list-extend-bottom.html', '');
        $templateCache.put('templates/mock-list-extend-top.html', '');

        $http.get(`templates/mock-list.html`)
            .then(
                response => {
                    $templateCache.put('templates/mock-list.html', response.data);
                }
            )
            .catch(catchErrorTemplate);
    })
    .controller('MockController', MockController)
    .service('MockService', MockService);

try {
    window.OpenLoyaltyConfig.modules.push(MODULE_NAME);
} catch (err) {
    throw `${MODULE_NAME} will not be registered`
}

And finally we have to register our module in frontend/src/appAdmin.js by adding:

...
require('./modules/admin.mock/module.js');
...
angular.module('OpenLoyalty', [
...
  'admin.mock'
...
]

Service and controller

Create service MockService.js with content:

export default class MockService {

    constructor(Restangular) {
        this.Restangular = Restangular;
    }

    getLevels(params) {
        return this.Restangular.all('level').getList(params);
    }
}

MockService.$inject = ['Restangular'];

Create controller MockController.js with content:

export default class MockController {
    constructor($scope, MockService, Flash, NgTableParams, $q, ParamsMap, $filter, DataService, PaginationSettings) {
        this.MockService = MockService;
        this.$scope = $scope;
        this.Flash = Flash;
        this.PaginationSettings = PaginationSettings;
        this.NgTableParams = NgTableParams;
        this.ParamsMap = ParamsMap;
        this.$q = $q;
        this.$filter = $filter;
        this.config = DataService.getConfig();

        this.loaderStates = {
            levelList: true,
        }
    }

    getData() {
        let self = this;

        self.tableParams = new self.NgTableParams({
            count: self.config.perPage
        }, {
            getData: function (params) {
                let dfd = self.$q.defer();

                self.loaderStates.levelList = true;
                self.MockService.getLevels(self.ParamsMap.params(params.url()))
                    .then(
                        res => {
                            self.$scope.levels = res;
                            let realTotal = res.total;
                            params.realTotal = () => realTotal;
                            params.total(self.PaginationSettings.getTotal(res.total));
                            self.loaderStates.levelList = false;
                            self.loaderStates.coverLoader = false;
                            dfd.resolve(res);
                        },
                        () => {
                            let message = self.$filter('translate')('xhr.get_levels_list.error');
                            self.Flash.create('danger', message);
                            self.loaderStates.levelList = false;
                            self.loaderStates.coverLoader = false;
                            dfd.reject();
                        }
                    );

                return dfd.promise;
            }
        });
    }
}

MockController.$inject = ['$scope', 'MockService', 'Flash', 'NgTableParams', '$q', 'ParamsMap', '$filter', 'DataService', 'PaginationSettings'];

Templates

Create template mock-list.js in templates directory with content:

<box-loader loading="MockCtrl.loaderStates.coverLoader" cover="1" class="cover" delay="100"></box-loader>

<div class="heading" ng-init="MockCtrl.getData()">
    <h1>{{ "mock.heading" | translate }}</h1>
</div>
<div style="clear:both;"></div>

<div class="client-list box">
    <div class="box-title">
        <h1 class="text-left">{{ "mock.list" | translate }}</h1>
    </div>
    <div class="box-content">
        <box-loader loading="MockCtrl.loaderStates.mockList"></box-loader>
        <table ng-table="MockCtrl.tableParams" class="default" template-pagination="templatePagination.html">
            <tr ng-repeat="row in $data">
                <td data-title="'mock.name'|translate" sortable="'name'">
                    <span ng-bind="row.name"></span>
                </td>
                <td data-title="'mock.description'|translate"
                    sortable="'description'"
                >
                    <span ng-bind="row.description || ('global.not_set'|translate)"></span>
                </td>
                <td data-title="'Actions'">
                    <button type="button" class="button  button-secondary tiny"
                            ui-sref="admin.edit-mock({levelId: row.id})">
                        <i class="fa fa-pencil" aria-hidden="true"></i>
                    </button>
                </td>
            </tr>
        </table>
    </div>
</div>

The last point is adding link to a navigation menu on left side. Let’s open file frontend/src/modules/admin.partials/templates/left-nav.html and add:

<li>
    <a href="#"><i class="fa fa-tachometer" aria-hidden="true"></i>{{ "nav.mock" | translate }}</a>
    <ul class="menu vertical nested">
        <li><a ui-sref="admin.mock-list">{{ "nav.mock" | translate }}</a></li>
    </ul>
</li>