Inventory Manager Using Angularjs Mysql Php

Cover image

Last tutorial we created a database helper class for PHP RESTful API. In this tutorial we will use that database helper class to build a simple inventory/product manager application. We will add, update, delete, activate, deactivate products from inventory. Some AngularJS directives will make our job easy.

Features

  • Truely single page web application
  • Showcase the awesome power of angularjs directives
  • Animations make the user interaction much enjoyable
  • Has extensive power to build a large inventory management application over this framework

Live Demo

How to use

  • Download the project file from the download link provided above
  • Import the database file “products.sql” into MySQL database
  • Add your database settings to the file “config.php”

There will be 3 directives essential for this simple application

  • form-element [ Form element templates ]
  • only-numbers [ This directive will restrict users from entering alphabets in a number field ]
  • animate-on-change [ Animates a particular product when it is updated ]

In this project I ignored the security part of the web application. Please do add some security features before implementing into production.

Modules used

  • AngularJS Bootstrap UI modal (for product edit)
  • underscore.js (for some helper javascript functions)
  • PHP Slim to create our data provider / API

Requirement Specification

  • Add/Edit/Delete new products to inventory
  • Activate/De-activate
  • Filter list of products at client side

Application Structure

api [ This serves as our ReSTFul data provider ]

libs [ The PHPSlim library ]

v1 [ Our API version 1 ]

.htaccess [ Converts urls to friendly urls for our API]

dbHelper.php [ The helper functions to connect to MySQL Database ]

config.php [ Database credentials and configurations ]

index.php [ The starting point of the API ]

app

app.js [ The starting point of our AngularJS web application ]

productsCtrl.js [ Products are controlled from here ]

data.js [ The middleware to connect to our API]

directives.js [ Some essential AngularJS directives ]

css

bootstrap.min.css

custom.css

font-awesome.min.css

js [ Required javascript libraries for application]

angular.min.js

angular-route.min.js

angular-animate.min.js

bootstrap.min.js

jquery.min.js

ui-bootstrap-tpls-0.11.2.min.js

underscore.min.js

partials [ Partial pages for products list and product edit ]

products.html [ List of products ]

productEdit.html [ Product edit template ]

index.php [ This page is called when our application starts and everything starts from here ]

Let's use our database helper function and create the API for database interactions

  • api/v1/dbHelper.php – Database helper functions created in previous tutorial for database access
  • api/v1/config.php – Database configurations [userid, password, server name]
  • api/v1/index.php – Select, Add, update, delete products

api/v1/index.php

<?php
require '.././libs/Slim/Slim.php';
require_once 'dbHelper.php';
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();
$app = \Slim\Slim::getInstance();
$db = new dbHelper();
/**
* Database Helper Function templates
*/
/*
select(table name, where clause as associative array)
insert(table name, data as associative array, mandatory column names as array)
update(table name, column names as associative array, where clause as associative array, required columns as array)
delete(table name, where clause as array)
*/
// Products
$app->get('/products', function() {
global $db;
$rows = $db->select("products","id,sku,name,description,price,mrp,stock,image,packing,status",array());
echoResponse(200, $rows);
});
$app->post('/products', function() use ($app) {
$data = json_decode($app->request->getBody());
$mandatory = array('name');
global $db;
$rows = $db->insert("products", $data, $mandatory);
if($rows["status"]=="success")
$rows["message"] = "Product added successfully.";
echoResponse(200, $rows);
});
$app->put('/products/:id', function($id) use ($app) {
$data = json_decode($app->request->getBody());
$condition = array('id'=>$id);
$mandatory = array();
global $db;
$rows = $db->update("products", $data, $condition, $mandatory);
if($rows["status"]=="success")
$rows["message"] = "Product information updated successfully.";
echoResponse(200, $rows);
});
$app->delete('/products/:id', function($id) {
global $db;
$rows = $db->delete("products", array('id'=>$id));
if($rows["status"]=="success")
$rows["message"] = "Product removed successfully.";
echoResponse(200, $rows);
});
function echoResponse($status_code, $response) {
global $app;
$app->status($status_code);
$app->contentType('application/json');
echo json_encode($response,JSON_NUMERIC_CHECK);
}
$app->run();
?>

Now our API is ready. Lets start building our application. In our index.html (start page) of our application we will put the follwing div where all the partial pages will be served.

<div id="ng-view" ng-view=""></div>

partials/products.html

Now we will display the list of products and add some simple animations for product change, the angular way

Products

<h4 class="blog-post-title">Products</h4>
<hr/>
<button type="button" class="btn btn-danger fa fa-plus" ng-click="open(product);">&nbsp;Add New Product</button>
<div class="table-responsive">
<div class="panel panel-primary">
<div class="panel-heading">List of products
<div class="sw-search" >
<div class="nav-search" id="nav-search">
Filter: <span class="input-icon">
<input placeholder="Filter products list ..." class="nav-search-input" ng-model="filterProduct" ng-change="resetLimit();" autocomplete="off" type="text" style="width:300px;" focus>
<i class="search-icon fa fa-search nav-search-icon"></i>
</span>
</div>
</div>
</div>
<div class="panel-body">
<table class="table table-striped">
<tr ng-show="products.length==0"><td style="vertical-align:middle;"><i class="fa fa-ban fa-3x"></i>&nbsp;No data found</td></tr>
<tr ng-hide="products.length>-1"><td style="vertical-align:middle;"><i class="fa fa-cog fa-3x fa-spin"></i>&nbsp;Loading</td></tr>
<tr><th ng-repeat="c in columns">{{c.text}}</th></tr>
<tr ng-repeat="c in products | filter:filterProduct | orderBy:'-id'" id="{{c.id}}" animate-on-change='c.packing + c.stock + c.price + c.description' ng-animate=" 'animate'">
<td>{{c.id}}</td><td>{{c.name}}</td><td>{{c.price}}</td><td>{{c.stock}}</td><td>{{c.packing}}</td><td>{{c.description}}</td>
<td>
<button class="btn" ng-class="{Active:'btn-success', Inactive:''}[c.status]" ng-click="changeProductStatus(c);">{{c.status}}</button>
</td>
<td style="width:100px">
<div class="btn-group">
<button type="button" class="btn btn-default fa fa-edit" ng-click="open(c);"></button>
<button type="button" class="btn btn-danger fa fa-trash-o" ng-click="deleteProduct(c);"></button>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>

partials/productEdit.html

We are successful displaying the products which has got activate/deactivate and delete button. We are going to embed our products into textboxes for editing

<button type="button" class="close" ng-click="cancel();">
<i class="fa fa-times-circle-o" style="margin:10px;color:blue;"></i>
</button>
<div class="modal-header">
<h3 class="modal-title">Edit product [ID: {{product.id}}]</h3>
</div>
<div class="modal-body">
<form name="product_form" class="form-horizontal" role="form" novalidate>
<form-element label="NAME" mod="product">
<input type="text" class="form-control" name="name" placeholder="NAME" ng-model="product.name" ng-disabled="product.id" focus/>
</form-element>
<form-element label="DESCRIPTION" mod="product">
<textarea class="form-control" name="description" placeholder="DESCRIPTION" ng-model="product.description">{{product.description}}</textarea>
</form-element>
<form-element label="PRICE" mod="product">
<input type="text" name="price" class="form-control" placeholder="PRICE" ng-model="product.price" only-numbers/>
<small class="errorMessage" ng-show="product_form.price.$dirty && product_form.price.$invalid"> Enter the price.</small>
</form-element>
<form-element label="STOCK" mod="product">
<input type="text" name="stock" class="form-control" placeholder="STOCK" ng-model="product.stock" only-numbers/>
<small class="errorMessage" ng-show="product_form.stock.$dirty && product_form.stock.$invalid"> Enter the available stock.</small>
</form-element>
<form-element label="PACKING" mod="product">
<input type="text" name="packing" class="form-control" placeholder="PACKING" ng-model="product.packing"/>
<small class="errorMessage" ng-show="product_form.packing.$dirty && product_form.packing.$invalid"> Enter the Packing.</small>
</form-element>
<div class="space"></div>
<div class="space-4"></div>
<div class="modal-footer">
<form-element label="">
<div class="text-right">
<a class="btn btn-sm" ng-click="cancel()"><i class="ace-icon fa fa-reply"></i>Cancel</a>
<button ng-click="saveProduct(product);"
ng-disabled="product_form.$invalid || enableUpdate"
class="btn btn-sm btn-primary"
type="submit">
<i class="ace-icon fa fa-check"></i>{{buttonText}}
</button>
</div>
</form-element>
</div>
</form>
</div>

All the above partial pages will be controlled by our controller productsCtrl. Here we put the business logic of adding, deleting, updating products as well as the user interactions

app.controller('productsCtrl', function ($scope, $modal, $filter, Data) {
$scope.product = {};
Data.get('products').then(function(data){
$scope.products = data.data;
});
$scope.changeProductStatus = function(product){
product.status = (product.status=="Active" ? "Inactive" : "Active");
Data.put("products/"+product.id,{status:product.status});
};
$scope.deleteProduct = function(product){
if(confirm("Are you sure to remove the product")){
Data.delete("products/"+product.id).then(function(result){
$scope.products = _.without($scope.products, _.findWhere($scope.products, {id:product.id}));
});
}
};
$scope.open = function (p,size) {
var modalInstance = $modal.open({
templateUrl: 'partials/productEdit.html',
controller: 'productEditCtrl',
size: size,
resolve: {
item: function () {
return p;
}
}
});
modalInstance.result.then(function(selectedObject) {
if(selectedObject.save == "insert"){
$scope.products.push(selectedObject);
$scope.products = $filter('orderBy')($scope.products, 'id', 'reverse');
}else if(selectedObject.save == "update"){
p.description = selectedObject.description;
p.price = selectedObject.price;
p.stock = selectedObject.stock;
p.packing = selectedObject.packing;
}
});
};
$scope.columns = [
{text:"ID",predicate:"id",sortable:true,dataType:"number"},
{text:"Name",predicate:"name",sortable:true},
{text:"Price",predicate:"price",sortable:true},
{text:"Stock",predicate:"stock",sortable:true},
{text:"Packing",predicate:"packing",reverse:true,sortable:true,dataType:"number"},
{text:"Description",predicate:"description",sortable:true},
{text:"Status",predicate:"status",sortable:true},
{text:"Action",predicate:"",sortable:false}
];
});
app.controller('productEditCtrl', function ($scope, $modalInstance, item, Data) {
$scope.product = angular.copy(item);
$scope.cancel = function () {
$modalInstance.dismiss('Close');
};
$scope.title = (item.id > 0) ? 'Edit Product' : 'Add Product';
$scope.buttonText = (item.id > 0) ? 'Update Product' : 'Add New Product';
var original = item;
$scope.isClean = function() {
return angular.equals(original, $scope.product);
}
$scope.saveProduct = function (product) {
product.uid = $scope.uid;
if(product.id > 0){
Data.put('products/'+product.id, product).then(function (result) {
if(result.status != 'error'){
var x = angular.copy(product);
x.save = 'update';
$modalInstance.close(x);
}else{
console.log(result);
}
});
}else{
product.status = 'Active';
Data.post('products', product).then(function (result) {
if(result.status != 'error'){
var x = angular.copy(product);
x.save = 'insert';
x.id = result.data;
$modalInstance.close(x);
}else{
console.log(result);
}
});
}
};
});