pre.code
Any good Content Management System (CMS) will have five key functionalities.
The ability to:
In this training module, we will implement these functionalities.
All the CRUD operations (view, add, edit, and delete) will be done on a single page app (SPA) and without page reload or refresh. The front end work is done with HTML, CSS, AngularJS, and jQuery. The back-end work is done with a PHP database using PDO (PHP Data Objects) extensions to help connect to the database and perform the select, insert, update, and delete operations.
|
C |
R |
U |
D |
---|---|---|---|---|
CRUD Operations |
Create |
Read |
Update |
Delete |
App Functions |
Add |
View |
Edit |
Delete |
SQL Statements |
INSERT (INTO) |
SELECT (fetch) |
UPDATE |
DELETE |
All operations, functions, and statements deal with getting and setting records from a database. (e.g., create record, add record, insert record, etc.). Sometimes you see the acronym SCRUD where the “S” represents Search (e.g., search or filter a record or records from a database) |
This triaining uses PDO instead of mysql_ * and mysqli_: Remember, PDO is the way to GO!!!
Except from https://forums.phpfreaks.com/
Switching from the old mysql_* functions to the new mysqli_* functions takes a lot more than adding an “i” everywhere.
Or adding connection arguments.You first have to unlearn plenty of wrong practices: Your code has SQL injection
vulnerabilities all over the place, and printing error messages on the screen isn't very smart either.
It gives attackers valuable information about your system, and it makes legitimate users think your website is fudged up. Then you need to actually learn mysqli. The old extension represented the technology of the 90s, mysqli is a database
interface for the 21st century and often takes a very different approach. For example, passing data to queries is
now implemented with prepared statements, which provides much better protection against SQL injection attacks. mysqli
also supports exceptions to properly indicate errors. Unfortunately, mysqli is fairly difficult to learn, especially when you don't like to read manuals. A much better
alternative is the PDO extension. Since you haven't invested any time into mysqli yet, now would be a great time to
jump straight to PDO.
SQL injection (SQLi) refers to an injection attack wherein an attacker can execute arbitrary SQL statements by tricking a web application in processing an attacker’s input as part of an SQL statement. This post will focus on how to prevent SQL injection vulnerabilities within PHP applications and fix them.
CAUTION: SQLi should not be confused with my_sqli extensions.
SeePrevent SQL injection vulnerabilities in PHP applications and fix them for an excellent article and examples on SQL injection and a tool that you can use to check for vulnerbility.
Before we get started we will download the necessary exercise files that we need to use in our project. This file contain the following assets:
It is best to start by creating a database with a few records in it before you create a database driven application or web site. To save time, we have a script for the database here.
Now, let's set up both the site and the testing server.
class DB {CAUTION: When you don't have any credential, you will see the following message at the bottom of the phpMyAdmin screen, "Your configuration file contains settings (root with no password) that correspond to the default MySQL privileged account. Your MySQL server is running with this default, is open to intrusion, and you really should fix this security hole by setting a password for user 'root'."
// Database credentials
private $dbHost = 'localhost';
private $dbUsername = 'root';
private $dbPassword = '';
private $dbName = 'employeedirectoryajs';
public $db;
class DB {
// Database credentials
private $dbHost = 'localhost';
private $dbUsername = 'CorneliusChopin';
private $dbPassword = 'admin';
private $dbName = 'employeedirectoryajs';
public $db;
First, let’s create content that is needed for our application by creating the HTML framework and then adding the static view components.
Let’s first create a blank framework and tweak it for mobile devices if necessary.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Employee Directory</title>
</head>
<body>
</body>
</html>
CODE EXPLANATION:
- The lang attribute is used to define the default language for the app.
- the viewport attribute is used to set the device width and scale.
Now that we have the basic HTML framework established, let’s add some static view components to it.
<body> <div class="container">
<div class="row"> <div class="panel panel-default users-content"> </div>
</div>
</div>
</body>
CODE EXPLANATION:
- This container BootStrap class is used to "contain" any BootStrap related content.
- The row BootStrap class is used to create a "row" within the Bootstrap container
mentioned in the previous bullet. - The panel BootStrap is used to ...(REPLACE PANEL WITH CARD IN BOOTSTRAP 4)
<body> <div class="container">
<div class="row">
<div class="panel panel-default users-content">
<div class="panel-heading">EMPLOYEE DIRECTORY<a>ADD EMPLOYEE</a></div> <table border="1px" class="table table-striped">
<tr>
<th width="5%">#</th>
<th width="20%">Name</th>
<th width="30%">Email</th>
<th width="20%">Phone</th>
<th width="14%">Created</th>
<th width="10%"></th>
</tr>
<tr>
<td>index</td>
<td>name</td>
<td>email</td>
<td>phone</td>
<td>created</td>
<td>
<a>Edit</a>
<a>Delete</a>
</td>
</tr>
</table> </div>
</div>
</div>
</body>
CODE EXPLANATION:
- The first <tr> tag holds static <td> text content.
- The second <tr> tag holds static placeholder content.
<body> <div class="container">
<div class="row">
<div class="panel panel-default users-content">
<div class="panel-heading">EMPLOYEE DIRECTORY<a>ADD EMPLOYEE</a></div> <table border="1px" class="table table-striped">
<tr>
<th width="5%">#</th>
<th width="20%">Name</th>
<th width="30%">Email</th>
<th width="20%">Phone</th>
<th width="14%">Created</th>
<th width="10%"></th>
</tr>
<tr>
<td>{{$index+1}}</td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
<td>{{user.phone}}</td>
<td>{{user.created}}</td>
<td>
<a>Edit</a>
<a>Delete</a>
</td>
</tr>
</table> </div>
</div>
</div>
</body>
CODE EXPLANATION:
- The $index+1 will be used to create the row number from an array later.
- Remember, an array is zero-based indexed so a "+1" is added to start the first row number as 1.
Now let's implement the necessary frameworks and CSS file to "style" our application.
Now that we have a header and table established, let's add some BootStrap CSS goodness
WHY: To make the HTML page, header, and table look more appealing.
<title>Employee Directory</title> <!-- Bootstrap CSS -----------------> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"> <!-- jQuery and Bootstrap frameworks ----------------->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
Now that we have some HTML to work with, let's add the AngularJS framework to create an app. The following functions are used to handle the CRUD operations:
<!-- AngularJS framework ------------------------------>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
<!-- php and js files use for app ------------>
<script src="app.js"></script> <script src="action.php"></script> </head>
CODE EXPLANATION: - The action.php needs to be updated if you are creating a different app. The only lines you need to
add or change corresponds to the columns from the database. See example below:
$userData = array(
'name' => $_POST['data']['name'],
'email' => $_POST['data']['email'],
'phone' => $_POST['data']['phone']
CAUTION: DO NOT include a comma at the end of the LAST data property.
EX: 'phone' => $_POST['data']['phone'],
CAUTION: The newer version of AngularJS framework (version 1.6.4 or higher) will
not work with this app. This will be fixed later.
<body data-ng-app="employeeDirectoryApp">
<div class="container" data-ng-controller="employeeDirectoryCtrl">
<div class="row">
CODE EXPLANTION:
- The two directives are used to provide "hooks" the AngularJS framework
to make these tags behave like an app and a controller, respectively.
// AngularJS module ------------------------------
var app = angular.module('employeeDirectoryApp',[]);
// AngularJS controller --------------------------
app.controller('employeeDirectoryCtrl', function($scope) {
"use strict";
$scope.message = "Hello World";
}); // End of controller code block
<div class="container" data-ng-controller="employeeDirectoryCtrl">
{{message}}
Now that we know that our app is working, let's create the Master View so that we can "see" data from the database get populated in the table.
<body data-ng-app="employeeDirectoryApp">
<div class="container" data-ng-controller="employeeDirectoryCtrl" data-ng-init="getRecords()">
CODE EXPLANATION:
- When the page is first loaded, the getRecords() function defined by ng-init is initialized
which will fetch the records from the database.
<tr data-ng-repeat="user in users | orderBy:'-created'">
<td>{{$index +1}}</td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
<td>{{user.phone}}</td>
<td>{{user.created}}</td>
<td>
<a>Edit</a>
<a>Delete</a>
</td>
</tr>
CODE EXPLANATION:
- The AngularJS data-ng-repeat directive loops through the JSON object and create rows for
the table dynamically.
- The "user in users" is used to loop through the users array.
- The "| orderBy: " is an AngularJS filter that will sort the record by date created. There is
a created column in the employeedirectoryajs database.
// AngularJS module ------------------------------
var app = angular.module('employeeDirectoryApp',[]);
// AngularJS controller --------------------------
app.controller('employeeDirectoryCtrl', function($scope, $http) {
"use strict";
$scope.users = [];
$scope.tempUserData = {};
// function to get records from the database
$scope.getRecords = function(){
$http.get('action.php', {
params:{
'type':'view'
}
}).success(function(response){
if(response.status === 'OK'){
$scope.users = response.records;
}
});
};
}); // End of controller code block
CODE EXPLANATION:
- The $scope.users = [] represents an empty array.
- The $scope.tempUserData = {} representes an empty object - The getRecord() method is used to get (retrieve) the action.php file and if successful create the users array of objects.
Now that we see a list of default records from the database in the table, let's see how we can add additional functionality to Add, Update, and Delete records from the database.
Let's create the form that is needed to add and update records from the database.
<div class="panel-body formData"> <form class="form" name="userForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" name="name"/> </div> <div class="form-group"> <label for="email">Email</label> <input type="text" class="form-control" name="email"/> </div> <div class="form-group"> <label for="phone">Phone</label> <input type="text" class="form-control" name="phone"/> </div> <a>SAVE</a> <a>UPDATE</a> <a>CANCEL</a> </form> </div> <table>
<div class="panel-body formData"> <form class="form" name="userForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" name="name" data-ng-model="tempUserData.name"/> </div> <div class="form-group"> <label for="email">Email</label> <input type="text" class="form-control" name="email" data-ng-model="tempUserData.email"/> </div> <div class="form-group"> <label for="phone">Phone</label> <input type="text" class="form-control" name="phone" data-ng-model="tempUserData.phone"/> </div> <a>SAVE</a> <a>UPDATE</a> <a>CANCEL</a> </form> </div> <table>
CODE EXPLANATION:
- The data-ng-model directives are used to "bind" the value IN each of the <input> fields with the application data with the same name.
Now let's add some classes, directives and JavaScript to each of the buttons:
<div class="panel-body formData"> <form class="form" name="userForm"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" name="name" data-ng-model="tempUserData.name"/> </div> <div class="form-group"> <label for="email">Email</label> <input type="text" class="form-control" name="email" data-ng-model="tempUserData.email"/> </div> <div class="form-group"> <label for="phone">Phone</label> <input type="text" class="form-control" name="phone" data-ng-model="tempUserData.phone"/> </div> <a href="javascript:void(0);" class="btn btn-success" data-ng-hide="tempUserData.id" data-ng-click="addUser()">SAVE</a> <a href="javascript:void(0);" class="btn btn-success" data-ng-hide="!tempUserData.id" data-ng-click="updateUser()">UPDATE</a> <a href="javascript:void(0);" class="btn btn-success" onclick="$('.formData').slideUp();">CANCEL</a> </form> </div> <table>
CODE EXPLANATION:
- While it is not best practice to embed JavaScript code in an HTML tag (e.g., onclick="$('.formData').slideUp();"),
for simply animation this is not much of an issue. - Since the button is not use for its intended purpose (to go to another page), the javascript:void(0) method is used to suppress its functionality. - The data-ng-hide directives are used to show the SAVE button and hide the UPDATE button. Notice the NOT operation (e.g., !tempUserData.id)
- The data-ng-click directives are event handlers. Their code will be written later for each of these buttons click events.
<body> <div class="container">
<div class="row">
<div class="panel panel-default users-content">
<div class="panel-heading">EMPLOYEE DIRECTORY
<a href="javascript:void(0);" class="glyphicon glyphicon-plus" onclick="$('.formData').slideToggle();">ADD EMPLOYEE</a></div> <table border="1px" class="table table-striped">
<tr>
<th width="5%">#</th>
<th width="20%">Name</th>
<th width="30%">Email</th>
<th width="20%">Phone</th>
<th width="14%">Created</th>
<th width="10%"></th>
</tr>
<tr>
<td>{{$index +1}}</td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
<td>{{user.phone}}</td>
<td>{{user.created}}</td>
<td>
<a href="javascript:void(0);" class="glyphicon glyphicon-edit" data-ng-click="editUser(user)"></a>
<a href="javascript:void(0);" class="glyphicon glyphicon-trash" data-ng-click="deleteUser(user)"></a>
</td>
</tr>
</table> </div>
</div>
</div>
</body>
CODE EXPLANATION:
- The class="glyphicon glyphicon-edit" and class="glyphicon glyphicon-trash" are a set of BootStrap classes used
to define the edit and delete icons.
- The data-ng-click directives will be used to create functions with the same name in code for the click events of these buttons.
- The glyphicons have been depreciated in BootStrap 4. Framework and code will be updated later.
Currently, if you click the CANCEL button, the form will slide up so that it will be hidden or if you click the ADD EMPLOYEE button it will slide up or down as well.
We want the form to be hidden when the app initially opens.
<div class="panel-body none formData"> <form class="form" name="userForm">
CAUTION: Be careful to place the class on the <div> tag and NOT the <form> tag.
Before we create the add, edit, and delete functionalities, we need to create a "helper" function to assist us with these operations.
// function to insert or update user data to the database $scope.saveUser = function(type){ var data = $.param({'data':$scope.tempUserData,'type':type}); var config = {headers:{'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'}}; $http.post("action.php", data, config).success(function(response){ if(response.status === 'OK'){ if(type === 'edit'){ $scope.users[$scope.index].id = $scope.tempUserData.id; $scope.users[$scope.index].name = $scope.tempUserData.name; $scope.users[$scope.index].email = $scope.tempUserData.email; $scope.users[$scope.index].phone = $scope.tempUserData.phone; $scope.users[$scope.index].created = $scope.tempUserData.created; }else{ $scope.users.push({ id:response.data.id, name:response.data.name, email:response.data.email, phone:response.data.phone, created:response.data.created }); } $scope.userForm.$setPristine(); $scope.tempUserData = {}; $('.formData').slideUp(); $scope.messageSuccess(response.msg); }else{ $scope.messageError(response.msg); } }); }; }); // End of controller code block
CODE EXPLANATION:
- The saveUser() method is used to pass the data and config arguments along with action.php into the $http.post() method.
- If the type is edit, prepopulate the users array. Othewise (else statement), push (save) the data into the users array.
- The $setPristine() method is used to make the input fields in the form as they were never touched and then have the
form slide up and show the success message. Otherwise (else statement), display error message.
Now that we can view employees data from the database, let's see how we can add an employee to the database.
// function to add user data $scope.addUser = function(){ $scope.saveUser('add'); }; }); // End of controller code block
CODE EXPLANATION:
- When a user click on the ADD EMPLOYEE button, it will invoke the addUser() method which in turn
invoke the saveUser() method.
// function to edit user data $scope.editUser = function(user){ $scope.tempUserData = { id:user.id, name:user.name, email:user.email, phone:user.phone, created:user.created }; $scope.index = $scope.users.indexOf(user); $('.formData').slideDown(); }; }); // End of controller code block
CODE EXPLANATION:
- The editUser() method is used to populate the tempUserData with property/value pairs.
// function to update user data $scope.updateUser = function(){ $scope.saveUser('edit'); }; }); // End of controller code block
CODE EXPLANATION:
- Like the addUser() method, when a user click on the UPDATE EMPLOYEE button, it will invoke the updateUser() method which in turn
invoke the saveUser() method.
// function to delete user data from the database $scope.deleteUser = function(user){ var conf = confirm('Are you sure you want to delete this employee?'); if(conf === true){ var data = $.param({'id': user.id,'type':'delete'}); var config = {headers : {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;'}}; $http.post("action.php",data,config).success(function(response){ if(response.status === 'OK'){ var index = $scope.users.indexOf(user); $scope.users.splice(index,1); $scope.messageSuccess(response.msg); }else{ $scope.messageError(response.msg); } }); } }; }); // End of controller code block
CODE EXPLANATION:
- The confirm() method is assigned to the message variable so that it can be used in the "if"
statement to determine if the OK or the Cancel button has been pressed. If the OK button is
pressed, it will return true and the first part of the "if" statement will be executed.
If the Cancel button is pressed, it will return false and the second part of the "if" statement
(the else statement) will be executed.
- The conf variable is created so that it can be passed into the confirm() method. The message
could have been written inside of the confirm dialog box but it is best practice to create a
variable for a long statement and then use that variable as an argument to make the code easier
to read and update.
- The (conf === true) conditional statement could have been written simply as (conf)
because it default to true.
While optional, we will create the functionality to display a message if the result of a given operation was successful or not.
<div class="alert alert-danger none"><p></p></div> <div class="alert alert-success none"><p></p></div> <div class="panel-body none formData"> <form class="form" name="userForm">
CODE EXPLANATION:
- There are empty <p> tags nested inside of the <div> tags that will be used by the CSS
for to target them from the code (e.g., $('.alert-success > p').html(msg);)
- The class alert-danger (red) will be used for the error message background color
- The class alert-success (green) will be used for the success message background color
- The none class is used to hide both of the <div> tags when the app first load.
// function to display success message $scope.messageSuccess = function(msg){ $('.alert-success > p').html(msg); $('.alert-success').show(); $('.alert-success').delay(2000).slideUp(function(){ $('.alert-success > p').html(''); }); }; // function to display error message $scope.messageError = function(msg){ $('.alert-danger > p').html(msg); $('.alert-danger').show(); $('.alert-danger').delay(2000).slideUp(function(){ $('.alert-danger > p').html(''); }); }; }); // End of controller code block
CODE EXPLANATION:
- Both of these functions (messageSuccess and messageError) works basically the same.
- However, the success function target the div tag with an alert attribute of success and a child <p> tag.
- Whereas, the error function target the div tag with an alert attribute of danger and a child <p> tag.
- The div tag is delayed for two seconds before it slideUp and the HTML content is set to an empty string ("").
Another key feature of a CMS is the ability to be able to perform a search of records. While we could perform a database search, we will use AngularJS to perform a search with a AngularJS filter instead:
<p style="margin:5px"><strong>Search:</strong>
<input type="text" data-ng-model="searchword" placeholder="(e.g., Name, Email)"></p>
<table border="1px" class="table table-striped">
<tr data-ng-repeat="user in users | orderBy:'-created' | filter:searchword track by $index">
While the app is basically complete, there are some things that we can do to enhance it both aesthetically and functionally.
These enhancements are optional and you can add them as you see fit:
Currently the Delete function works; however, we will improve on it by adding a personalized delete message.
// function to delete user data from the database
$scope.deleteUser = function(user){
var employee_name = $scope.users[$scope.users.indexOf(user)].name;
var conf = confirm('Are you sure you want to delete ' + employee_name + '?');
Let's give the ADD EMPLOYEE button a little more style.
<div class="row">
<div class="panel panel-default users-content">
<div class="panel-heading">EMPLOYEE DIRECTORY<a href="javascript:void(0);" class="btn btn-success
glyphicon glyphicon-plus" onclick="$('.formData').slideToggle();"> ADD EMPLOYEE</a></div>
.glyphicon-plus{float: right; font-size:10px;}
Currently, the app is done with an HTML page (e.g., index.html). It is important to note that ALL of the PHP script is done in other pure PHP pages (e.g., DB.php and action.php) so it was not necessary to have the app created with a *.php extension since there were NO PHP script on THIS page. However, we would like to do a simply PHP script to create a dynamic copyright notice so we will have to convert this page to a *.php page.
</table>
<p class="copyright"><strong>© 2011-<?php echo (date('Y')); ?> by RMCS. All rights reserved.</strong></p>
CODE EXPLANATION:
- The PHP script (e.g., <?php echo (date('Y')); ?>) is EMBEDDED within a HTML tag.
- The PHP date('Y') method is used to return the current year.
/* Copyright Notice -------------------------- */
.copyright {
padding-top:5px;
padding-left:5px;
text-align:center;
color:gray;
}
Now, let's write some HTML, CSS and JavaScript to keep track of how many employees are listed in the table. We'll write some code to update the total number of employees when:
</table>
<p class="totalEmployees">Total Employees: {{totalUsers}}</p>
<p class="copyright"><strong>© 2011-<?php echo (date('Y')); ?> by RMCS. All rights reserved.</strong></p>
/* Employee Total -------------------------- */
.totalEmployees {margin-left: 10px; font-size: 20px;}
.totalEmployees span {font-weight:bold; color:red;}
$scope.users = [];
$scope.tempUserData = {};
$scope.totalUsers = 0;
CODE EXPLANATION:
- The $scope.totalUsers variable is created OUTSIDE of the method (function) that we will use it in
to make is GLOBAL. If you add it just to the function, it will be LOCAL to that function. By creating
it outside of the function allow it to be used repeatably later.
// function to get records from the database
$scope.getRecords = function(){
$http.get('action.php', {
params:{
'type':'view'
}
}).success(function(response){
if(response.status === 'OK'){
$scope.users = response.records;
$scope.totalUsers = $scope.users.length;
}
});
};
CODE EXPLANATION:
- The $scope.totalUsers variable was added within the getRecords() method to get the
total number of employees when the app first loads.
else{
$scope.users.push({
id:response.data.id,
name:response.data.name,
email:response.data.email,
phone:response.data.phone,
created:response.data.created
});
$scope.totalUsers = $scope.users.length;
}
CODE EXPLANATION:
- The $scope.totalUsers variable was added within the saveUser() method to UPDATE the
total number of employees when an employee is ADDED.
if(response.status === 'OK'){
var index = $scope.users.indexOf(user);
$scope.users.splice(index,1);
$scope.messageSuccess(response.msg);
$scope.totalUsers = $scope.users.length;
}else{
$scope.messageError(response.msg);
}
});
}
CODE EXPLANATION:
- The $scope.totalUsers variable was added within the deleteUser() method to UPDATE the
total number of employees when an employee is DELETED.
Let's add some code to display another message if ALL of the employees are deleted from the table.
</table>
<p><strong>Employees Total:</strong> {{totalNumberEmployees}}
<span style="color:red; font-weight: bold" data-ng-hide="hideMessage">
> All employees have been deleted.</span></p>
<p class="copyright"><strong>© 2011-<?php echo (date('Y')); ?> by RMCS. All rights reserved.</strong></p>
$scope.users = [];
$scope.tempUserData = {};
$scope.totalUsers = 0;
$scope.hideMessage = true;
if(response.status === 'OK'){
var index = $scope.users.indexOf(user);
$scope.users.splice(index,1);
$scope.messageSuccess(response.msg);
$scope.totalUsers = $scope.users.length;
$scope.totalUsers = 0; // TEST ONLY, DELETE AFTERWARD
if($scope.totalUsers === 0)
{
$scope.hideMessage = false;
}
}else{
CODE EXPLANATION:
- The $scope.totalUsers = 0; statement was added to test the "if" statement without having to delete
ALL of the employees in the current table.
Currently, the table is "hugging" the edge of the panel. Let's give the panel some "breathing room" and make the table more responsive.
/* Style Panel ---------------------------- */
.panel{padding:20px;}
<table border="1px" class="table table-striped table-responsive">
When you create a form, you should always validate particular form elements to determine if required form elements have been filled out (e.g., required) or that the correct format is given (e.g., email address). There are a host of techniques on how to validate form elements.
<form data-ng-submit="addUser()" class="form" name="userForm">
CODE EXPLANATION:
- The data-ng-submit="addUser()" directive is used to submit the form when the SAVE button is pressed.
CAUTION: Ensure the required keyword is OUTSIDE of the double quote.
<form data-ng-submit="validateForm()" class="form" name="userForm">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" name="name" data-ng-model="tempUserData.name" required/>
<span data-ng-show="userForm.name.$touched && userForm.name.$invalid" style="color:red">
Full name is required.</span>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="text" class="form-control" name="email" data-ng-model="tempUserData.email" required/> <span data-ng-show="userForm.email.$touched && userForm.email.$invalid" style="color:red">
Email is required.</span>
</div>
<div class="form-group">
<label for="phone">Phone</label>
<input type="text" class="form-control" name="phone" data-ng-model="tempUserData.phone" required/> <span data-ng-show="userForm.phone.$touched && userForm.phone.$invalid" style="color:red">
Phone is required.</span>
</div>
<!--<a href="javascript:void(0);" class="btn btn-success" data-ng-hide="tempUserData.id" data-ng-click="addUser()">SAVE</a> -->
<input type="submit" value="SAVE" class="btn btn-success" data-ng-hide="tempUserData.id"/>
<a href="javascript:void(0);" class="btn btn-success" data-ng-hide="!tempUserData.id" data-ng-click="updateUser()">UPDATE</a>
<a href="javascript:void(0);" class="btn btn-success" onclick="$('.formData').slideUp();">CANCEL</a>
</form>
<!--<a href="javascript:void(0);" class="btn btn-success" data-ng-hide="tempUserData.id" data-ng-click="addUser()">SAVE</a> -->
<input type="submit" value="SAVE" class="btn btn-success" data-ng-hide="tempUserData.id"/>
CODE EXPLANATION:
- Notice that we replaced the button with an input field which is a self-closing tag.
While all text is great, let's add some thumbnails to make our app a little more appealing.
<table border="1px" class="table table-striped table-responsive">
<tr>
<th width="2%">#</th>
<th width="10%">Image</th>
<th width="20%">Name</th>
<th width="20%">Email</th>
<th width="20%">Phone</th>
<th width="14%">Created</th>
<th width="10%"></th>
</tr>
<tr data-ng-repeat="user in users | orderBy:'-created' | filter:searchword track by $index">
<td>{{$index+1}}</td>
<td>{{user.image}}</td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
<td>{{user.phone}}</td>
<td>{{user.created}}</td>
<td>
<a href="javascript:void(0);" class="glyphicon glyphicon-edit" data-ng-click="editUser(user)"></a>
<a href="javascript:void(0);" class="glyphicon glyphicon-trash" data-ng-click="deleteUser(user)"></a>
</td>
</tr>
</table>
<td>{{$index+1}}</td>
<td><img width="50px" height="50px" src="images/ann_ricoh.jpg"></td>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
<td>{{user.phone}}</td>
<td>{{user.created}}</td>
<td>
/* Text and imagesin table and the header -------- */
.table.table-striped tr td {
vertical-align: middle;
}
.table.table-striped tr td img {
border: 1px solid gray;
padding: 2px;
}.row .panel.panel-default.users-content .panel-heading {
height: 55px;
}
<td><img width = "50px" height = "50px" src="images/{{user.image}}"></td>
While the a drop-down menu can be populated MANUALLY, it is best practice to DYNIMICALLY populate a menu if the SAME menu will be used on MULTIPLE pages. This way, if there are any changes (additions or deletions) to the menu, it can be done from one table in the database and all of the pages will reflect the change.
While the app is small, there is little to no need for pagination. However, as the app grows, it would be need to add pagination (e.g., 1 to 10 of 100).
Currently, the images has to be manually added to the images folder so it can be available when you enter a new employee. It would be better if you could upload the image to the image folder during the Add or Edit function.