Building A Content Management System Using The MEAN Stack - 6 (Front-End Development)
Repository
https://github.com/nodejs/node
What Will I Learn
The codebase for this tutorial is based on MEANie an open source content management system by Jason Watmore.
This is the fifth tutorial in the series of tutorials on building a content management system using the MEAN technology.
In the earlier tutorials we worked on all the components needed for the backend to run including the server, controllers and services.
Additionally we started working on the client side of our application particularly the admin
area.
In the last tutorial we created the posts
view for the admin area.
In this tutorial we will work on the pages
, redirects
and account
view for our cms.
By the end of this tutorial we will have completed work on some of the features for the admin section of the application.
Mainly we will create a user interface to display the list of pages and redirects from the admin dashboard in the application.
We will also create forms to be used in creating and editing new posts, pages, redirects and user account details.
We will add directives and functions to make the layout functional and communicate with the API.
N.B;- LINK TO THE EARLIER TUTORIALS IN THIS SERIES CAN BE FOUND AT THE END OF THIS POST
Requirements
- NodeJS and NPM,
- Angular
- MongoDB
- Code Editor
Difficulty
- Intermediate
Tutorial Contents
Pages
Let's go and create a new directory pages
in the admin
directory.
To create a view template for the list of pages stored in the database we'll first of all add a new file index.view.html
.
In this file we'll have the following code
<div class="posts-area">
<div class="posts-area-header row">
<div class="col l6 m6">
<h5>Pages</h5>
</div>
<div class="col l6 m6 new-post-button">
<a href="#/pages/add" class="waves-effect waves-light btn right indigo lighten-2 add"><i class="icon ion-ios-add"></i></a>
</div>
</div>
<div class="row">
<div class="col l6 m6" ng-repeat="page in vm.pages">
<div class="card">
<div class="card-content">
<p class="card-title">Page Title: <br> <span class="center">{{page.title}}</span></p>
<p>Published <i ng-if="page.publish" class="icon ion-md-checkmark checkmark right" /></p>
</div>
<div class="card-action">
<a href="#/pages/edit/{{page._id}}" class="btn indigo lighten-2 edit-btn"><i class="icon ion-md-create"></i></a>
</div>
</div>
</div>
</div>
</div>
In the pages
view template all pages and accompanying details are displayed in a card.
In the card we have a paragraph displaying the page title bounded by {{page.title}}
.
The card action area holds a button that allows the admin edit the content of each page.
To make the template functional we need to add the controller file.
Create a new file in the pages
directory index.controller.js
. Inthe controller file add the following code
(function () {
'use strict';
angular
.module('app')
.controller('Pages.IndexController', Controller);
function Controller(PageService) {
var vm = this;
vm.pages = [];
initController();
function initController() {
vm.loading = true;
PageService.GetAll()
.then(function (pages) {
vm.loading = false;
vm.pages = pages;
});
}
}
})();
In this controller file initController()
goes to the PageService
module and runs the GetAll()
function which returns a list of all pages stored in the database and stores them as an array in vm.pages
.
Intside the admin
directory there is a services
directory which was created in an earlier tutorial, in there create a page.service.js
file.
Paste the following code in that file\
(function () {
'use strict';
angular
.module('app')
.factory('PageService', Service);
function Service(DataService) {
var service = DataService('/api/pages');
return service;
}
})();
The PageService
module grabs all the date from the api/pages
directory and serves it to the pages controller module in the client
directory.
Below is a screenshot of the pages
view.
We also need a add-edit
view to handle the addition and editing of page contents.
In the pages
directory add a new file add-edit.view.html
. In this file paste the following code
<div class="add-post-area container">
<h5>Page Details</h5>
<form ng-if="!vm.loading">
<div class="input-field">
<input placeholder="Title" id="title" type="text" ng-model="vm.page.title" />
</div>
<div class="input-field">
<input placeholder="Slug" type="text" id="slug" ng-model="vm.page.slug" />
<a class="generate-slug btn waves waves-effect indigo lighten-2" ng-click="vm.page.slug = (vm.page.title | slugify)">Generate from title</a>
</div>
<div class="input-field">
<textarea placeholder="Summary" id="summary" ng-model="vm.page.description" rows="4"></textarea>
</div>
<div class="input-field">
<textarea ng-model="vm.page.body" wysiwyg></textarea>
</div>
<p>
<label>
<input type="checkbox" ng-model="vm.page.publish" />
<span>Publish</span>
</label>
</p>
<div class="center">
<a class="btn waves waves-effect indigo lighten-2" ng-click="vm.savePage()">SAVE</a>
<a class="btn waves waves-effect indigo lighten-2" href="#/pages">CANCEL</a>
<a class="btn waves waves-effect indigo lighten-2" ng-click="vm.deletePage()" ng-show="vm.page._id">DELETE</a>
</div>
</form>
</div>
In the file created above we have a form with several input fields with each input having its own ng-model
directive.
The field accepting the page title has the ng-model="vm.page.title"
.
For the page slug we have the ng-model="vm.page.slug"
.
Page summary has ng-model="vm.page.description"
.
Page body has ng-model="vm.page.body"
.
We also add three buttons at the end.
The first button will handle saving the added page contents to the database. That operation is indicated through the included directive ng-click="vm.savePage()"
.
The second button will cancel any ongoing process in the page edit area and return the user to the index.view.html
front end for the pages view.
The third button is responsible for deleting an existing page from the database and contains two included directives ng-click="vm.deletePage()"
and ng-show="vm.page._id"
ng-click="vm.deletePage()"
handles the page deletion aspect while ng-show="vm.page._id"
will make sure that the button only displays if the page being edited was already existing on the database before that time.
To add the controller for the add-edit
template we need to create a new file add-edit.controller.js
.
The code for the add-edit
controller
(function () {
'use strict';
angular
.module('app')
.controller('Pages.AddEditController', Controller);
function Controller($stateParams, $location, $filter, PageService, AlertService) {
var vm = this;
vm.page = {};
vm.savePage = savePage;
vm.deletePage = deletePage;
initController();
function initController() {
vm.loading = 0;
if ($stateParams._id) {
vm.loading += 1;
PageService.GetById($stateParams._id)
.then(function (page) {
vm.loading -= 1;
vm.page = page;
});
} else {
// initialise with defaults
vm.page = {
publish: true
};
}
}
function savePage() {
PageService.Save(vm.page)
.then(function () {
AlertService.Success('Page saved', true);
$location.path('/pages');
})
.catch(function (error) {
AlertService.Error(error);
});
}
function deletePage() {
PageService.Delete(vm.page._id)
.then(function () {
AlertService.Success('Page deleted', true);
$location.path('/pages');
})
.catch(function (error) {
AlertService.Error(error);
});
}
}
})();
savePage()
will grab all the data provided in the form and saves them in the vm.page
array initialized earlier in the file.
The values in vm.page
are then sent to the /pages
api in the server directory to be processed and stored in the database.
deletePage()
will delete the page matching the id
provided by the vm.page._id
variable.
Screenshot of the add-edit
view after completion.
Redirects
We are going to create a view for the redirect module, in order to do that we are going to create a new directory in the admin directory and name it redirects
.
In the redirects
directory we'll create a new file index.view.html
and add the following code
<div class="posts-area">
<div class="posts-area-header row">
<div class="col l6 m6">
<h5>301 Redirects</h5>
</div>
<div class="col l6 m6 new-post-button">
<a href="#/redirects/add" class="waves-effect waves-light btn right indigo lighten-2 add">
<i class="icon ion-ios-add"></i>
</a>
</div>
</div>
<div class="row">
<div class="col l6 m6" ng-repeat="redirect in vm.redirects">
<div class="card">
<div class="card-content">
<p>Redirect From: <br> <span class="center">{{redirect.from}}</span></p>
<p>Redirect To: <br> <span class="center">{{redirect.to}}</span></p>
</div>
<div class="card-action">
<a href="#/redirects/edit/{{redirect._id}}" class="btn indigo lighten-2 edit-btn"><i class="icon ion-md-create"></i></a>
</div>
</div>
</div>
</div>
</div>
The redirect template is rendered through a column with a directive ng-repeat="redirect in vm.redirects"
which translates to for every redirect in vm.redirects repeat the following.
Each redirect is displayed in a card with the following details
{{redirect.from}}
outputs the page the redirect is coming from.{{redirect.to}}
outputs the page being redirected to.
And also we have a button in the card action section that allows the user to edit an existing redirect.
The link in the card action area points to href="#/redirects/edit/{{redirect._id}}"
.
The redirect controller will be contained in the index.controller.js
file.
Code for the redirect controller
(function () {
'use strict';
angular
.module('app')
.controller('Redirects.IndexController', Controller);
function Controller(RedirectService) {
var vm = this;
vm.redirects = [];
initController();
function initController() {
vm.loading = true;
RedirectService.GetAll()
.then(function (redirects) {
vm.loading = false;
vm.redirects = redirects;
});
}
}
})();
In the redirect controller the Controller()
method grabs all the redirects stored in the database through the RedirectService
module and stores them as an array in vm.redirects
.
The service file for the redirect view is stored in the services
directory inside the admin
directory.
The name for this file redirect.service.js
. The code or the RedirectService
module
(function () {
'use strict';
angular
.module('app')
.factory('RedirectService', Service);
function Service(DataService) {
var service = DataService('/api/redirects');
return service;
}
})();
The function Service()
pulls all needed data from /api/redirects
and stores all the values gotten in service
for them to be used by the redirect controller.
Below is a screenshot of the redirect html template
The template file for the add-edit
view of the redirect add-edit.view.html
would contain the following code
<div class="add-edit-form-area container">
<h5>301 Redirect Details</h5>
<form ng-if="!vm.loading">
<div class="input-field">
<input placeholder="From (e.g. '/old-url')" id="from" type="text" ng-model="vm.redirect.from" required />
</div>
<div class="input-field">
<input placeholder="To (e.g. '/new-url')" type="text" id="to" ng-model="vm.redirect.to" />
</div>
<div class="center">
<a class="btn waves waves-effect indigo lighten-2" ng-click="vm.saveRedirect()">SAVE</a>
<a class="btn waves waves-effect indigo lighten-2" href="#/redirects">CANCEL</a>
<a class="btn waves waves-effect indigo lighten-2" ng-click="vm.deleteRedirect()" ng-show="vm.redirect._id">DELETE</a>
</div>
</form>
</div>
The form has two input fields, the first input field with ng-model="vm.redirect.from"
will accept the URL of the page the redirect is coming form as input.
The second input field with ng-model="vm.redirect.to"
will accept the URL of the page being redirected to as input.
To add the controller for the add-edit
template create a new file add-edit.controller.js
The code for the add-edit.controller.js
(function () {
'use strict';
angular
.module('app')
.controller('Redirects.AddEditController', Controller);
function Controller($stateParams, $location, $filter, RedirectService, AlertService) {
var vm = this;
vm.redirect = {};
vm.saveRedirect = saveRedirect;
vm.deleteRedirect = deleteRedirect;
initController();
function initController() {
vm.loading = 0;
if ($stateParams._id) {
vm.loading += 1;
RedirectService.GetById($stateParams._id)
.then(function (redirect) {
vm.loading -= 1;
vm.redirect = redirect;
});
}
}
function saveRedirect() {
RedirectService.Save(vm.redirect)
.then(function () {
AlertService.Success('Redirect saved', true);
$location.path('/redirects');
})
.catch(function (error) {
AlertService.Error(error);
});
}
function deleteRedirect() {
RedirectService.Delete(vm.redirect._id)
.then(function () {
AlertService.Success('Redirect deleted', true);
$location.path('/redirects');
})
.catch(function (error) {
AlertService.Error(error);
});
}
}
})();
The method Controller()
has three functions.
initController()
initializes all available redirect provided by the RedirectService()
module for them to be operated on by other functions in the redirect controller.
saveRedirect()
will save a new redirect added from the html template through the RedirectService()
.
deleteRedirect()
will delete a new redirect added from the html template through the RedirectService()
.
Screenshot of the add-edit
view
Account Update
This view will allow users to edit and update their account details from the admin front end.
The code for this view is contained in the account
directory created in the admin
directory.
Inside the account directory we have the html template in the index.view.html
file
<div class="add-edit-form-area container">
<h5>My Account</h5>
<form method="post">
<div class="input-field">
<input placeholder="Username" id="username" type="text" ng-model="vm.user.username" required />
</div>
<div class="input-field">
<input placeholder="Password" type="text" id="password" ng-model="vm.user.password" />
</div>
<div class="center">
<a class="btn waves waves-effect indigo lighten-2" ng-click="vm.saveUser()">UPDATE</a>
</div>
</form>
</div>
The form above has two input fields, the first field accepts the new username to be updated in the database with the ng-model="vm.user.username"
.
The second field accepts the new password to be updated in the database with the ng-model="vm.user.password"
.
We also have a button with the ng-click="vm.saveUser()"
directive which handles saving the updated user data to the database.
The accounts controller is contained in the index.controller.js
file created in the accounts
directory.
Code for the accounts controller
(function () {
'use strict';
angular
.module('app')
.controller('Account.IndexController', Controller);
function Controller($window, UserService, AlertService) {
var vm = this;
vm.user = null;
vm.saveUser = saveUser;
vm.deleteUser = deleteUser;
initController();
function initController() {
// get current user
UserService.GetCurrent().then(function (user) {
vm.user = user;
});
}
function saveUser() {
UserService.Update(vm.user)
.then(function () {
AlertService.Success('User updated');
})
.catch(function (error) {
AlertService.Error(error);
});
}
function deleteUser() {
UserService.Delete(vm.user._id)
.then(function () {
// log user out
$window.location = '/login';
})
.catch(function (error) {
AlertService.Error(error);
});
}
}
})();
In the account controller we have the Controller()
method which is the main function and contains three functions.
initController()
gets the current user from the database through the current UserService
module.
saveUser()
replaces/updates the user details in the database with the details provided in the html template form through the UserService
module.
deleteUser()
deletes the current user from the database through the UserSevice
module.
UserService
is located in the services
directory in the user.service.js
files
The following is the code for the user.service.js
file
(function () {
'use strict';
angular
.module('app')
.factory('UserService', Service);
function Service($http, $q) {
var service = {};
service.GetCurrent = GetCurrent;
service.Update = Update;
return service;
function GetCurrent() {
return $http.get('/api/users/current').then(handleSuccess, handleError);
}
function Update(user) {
return $http.put('/api/users/' + user._id, user).then(handleSuccess, handleError);
}
// private functions
function handleSuccess(res) {
return res.data;
}
function handleError(res) {
return $q.reject(res.data);
}
}
})();
The main function in user.service.js
file is Service()
and it contains four other functions
GetCurrent()
runs an GET
request and returns the details of the current user.
Update()
runs a POST
request to update the user details in the database with the new user
data provided.
handleSuccess()
runs if either of the two functions above is successfully and it returns the data request in either functions.
handleError()
runs if either of the two functions above runs into an error during execution.
Screenshot of the Account details update template
In the next tutorial we will work on other service modules required for the admin section. Also we will create the directives and filters needed for this module.
Curriculum
Building A Content Management System Using The MEAN Stack - 2(Create Controller Modules 1)
Building A Content Management System Using The MEAN Stack - 3 (Create Controller Modules 2)
Building A Content Management System Using The MEAN Stack - 4 (Create Services Modules)
Building A Content Management System Using The MEAN Stack - 5 (Front-End Development)
I thank you for your contribution. Here is my thought;
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
To view those questions and the relevant answers related to your post, click here.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]
Thank you for your review, @yokunjon!
So far this week you've reviewed 1 contributions. Keep up the good work!
As a follower of @followforupvotes this post has been randomly selected and upvoted! Enjoy your upvote and have a great day!
Hey, @gotgame!
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!
Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!