Responsive Design Drag and Drop

Sometimes we need drag and drop functionality on websites. Maybe we need to drag one element into another element, or just reorder something just by dragging.

I researched a lot of libraries open source available online, and came to conclusion that JQuery library for Draggable and Droppable is the best so far. Because its methods are very simple, or event handlers for drag and drop. Besides, many other libraries build on top of this JQuery library.

Also because it is very simple to use and implement.

Here is the source code for the HTML page I created. video follows it:

<html>
<!– DESIGN BY JINAN KORDAB –>
<!– DATE: APRIL 9, 2019 –>
<!– BOOTSTRAP RESPONSIVE DESIGN – FRONT END ONLY –>
<!– DEMO USAGE OFDRAG AND DROP FUNCTIONALITY. –>
<!– I FOUND THAT JAVASCRIPT IS THE BEST OPTIONS O FAR FOR HANDLING DRAG AND DROP SINCE THERE ARE MANY THIRD PARTY LIBRARIES AS WELL –>
<!– THIS PAGE SHOWS AN OPTIONS MENU,WITH BOOTSTRAP FOR RESPONSIVE DESIGN, WHEN DROPPED INTO THE MIDDLE CONTAINER,TRIGGERS THE droppable EVENT. –>
<!– INSIDE droppable EVENT,WE CAN ADD AJAX CALLS TO BACK END OT TO ASP.NET MVC CONTROLLERS AND ACTION RESULTS TO GET DATA FROMD ATA BASE –>
<!– YOU CAN USE THIS CODE AS YOU WISH –>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Jinan Kordab Draggable with Boostrap Example</title>
<link rel="stylesheet" href="bootstrap.min.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script&gt;
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script&gt;
<script src="bootstrap.min.js"></script>
<script>
$( function() {
$( "#draggable" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>TIPS DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#draggable2" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>SALES DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#draggable3" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>MONTHLY DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#draggable4" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>WEEKLY DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#draggable5" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>YTD DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#draggable6" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>OPTION ONE DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#draggable7" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>OPTION TWO DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#draggable8" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>OPTION THREE DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#draggable9" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>OPTION FOUR DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#draggable10" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>OPTION FIVE DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#draggable11" ).draggable({
opacity: 0.35,
containment: "#droppable",
revert: true,
helper: "clone",
helper: function( event ) {
return $( "<div class='badge badge-success'>OPTION SIX DETAILS</div>" );
},
snap: "#droppable",
start: function() {
},
drag: function() {
},
stop: function() {
}
});
$( "#droppable" ).droppable({
drop: function( event, ui ) {
var options = {};
var draggable = ui.draggable;
//alert(draggable.attr('id'));
if(draggable.attr('id') == "draggable")
{
$( "#draggable" ).effect( "bounce", options, 500, callback );
//$( "#draggable" ).parent().innerText = $( "#draggable" ).innerText;
}
else if(draggable.attr('id') == "draggable2")
{
$( "#draggable2" ).effect( "bounce", options, 500, callback );
//$( "#draggable2" ).parent().innerText = $( "#draggable" ).innerText;
}
else if(draggable.attr('id') == "draggable3")
{
$( "#draggable3" ).effect( "bounce", options, 500, callback );
//$( "#draggable3" ).parent().innerText = $( "#draggable" ).innerText;
}
else if(draggable.attr('id') == "draggable4")
{
$( "#draggable4" ).effect( "bounce", options, 500, callback );
//$( "#draggable4" ).parent().innerText = $( "#draggable" ).innerText;
}
else if(draggable.attr('id') == "draggable5")
{
$( "#draggable5" ).effect( "bounce", options, 500, callback );
//$( "#draggable5" ).parent().innerText = $( "#draggable" ).innerText;
}
else if(draggable.attr('id') == "draggable6")
{
$( "#draggable6" ).effect( "bounce", options, 500, callback );
//$( "#draggable6" ).parent().innerText = $( "#draggable" ).innerText;
}
else if(draggable.attr('id') == "draggable7")
{
$( "#draggable7" ).effect( "bounce", options, 500, callback );
//$( "#draggable7" ).parent().innerText = $( "#draggable" ).innerText;
}
else if(draggable.attr('id') == "draggable8")
{
$( "#draggable8" ).effect( "bounce", options, 500, callback );
//$( "#draggable8" ).parent().innerText = $( "#draggable" ).innerText;
}
else if(draggable.attr('id') == "draggable9")
{
$( "#draggable9" ).effect( "bounce", options, 500, callback );
//$( "#draggable9" ).parent().innerText = $( "#draggable" ).innerText;
}
else if(draggable.attr('id') == "draggable10")
{
$( "#draggable10" ).effect( "bounce", options, 500, callback );
//$( "#draggable10" ).parent().innerText = $( "#draggable" ).innerText;
}
else if(draggable.attr('id') == "draggable11")
{
$( "#draggable11" ).effect( "bounce", options, 500, callback );
//$( "#draggable11" ).parent().innerText = $( "#draggable" ).innerText;
}
}
});
} );
function callback() {
};
</script>
<style type="text/css">
.col-sm.border.rounded.border-warning:hover {
border: 1px solid #6c757d !important;
}
.block {
/* display: block; */
width: 100%;
height: 45px;
/* border: none; */
/* background-color: silver; */
/* padding: 0px 0px; */
/* font-size: 13px; */
/* cursor: pointer; */
/* text-align: center; */
/* color: white; */
font-weight: bold;
overflow: hidden;
}
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-light bg-info ">
<a class="navbar-brand text-white" href="#">LOGO</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active"><a class="nav-link text-white" href="#">HOME
<span class="sr-only">(current)</span></a> </li>
<li class="nav-item"><a class="nav-link text-white" href="#">CONTACT</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle text-white" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
SERVICES </a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">SERVICE A</a>
<a class="dropdown-item" href="#">SERVICE B</a>
<div class="dropdown-divider">
</div>
<a class="dropdown-item" href="#">SERVICE C</a> </div>
</li>
<li class="nav-item"><a class="nav-link disabled text-white" href="#">Disabled</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-warning my-2 my-sm-0 text-white" type="submit">
Search</button>
</form>
</div>
</nav>
<hr>
<div class="container-fluid" id="mainMenu">
<div class="row">
<div class="col-sm border rounded" style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important;">
<div id="draggable" class="btn btn-warning block" style="width:100%">
TIPS</div>
</div>
<div class="col-sm border rounded " style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important">
<div id="draggable2" class="btn btn-warning block" style="width:100%">
SALES</div>
</div>
<div class="col-sm border rounded" style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important">
<div id="draggable3" class="btn btn-warning block" style="width:100%">
MONTHLY</div>
</div>
<div class="col-sm border rounded" style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important">
<div id="draggable4" class="btn btn-warning block" style="width:100%">
WEEKLY</div>
</div>
<div class="col-sm border rounded" style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important">
<div id="draggable5" class="btn btn-warning block" style="width:100%">
YTD</div>
</div>
<div class="col-sm border rounded" style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important">
<div id="draggable6" class="btn btn-warning block" style="width:100%">
OPTION</div>
</div>
<div class="col-sm border rounded" style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important">
<div id="draggable7" class="btn btn-warning block" style="width:100%">
OPTION</div>
</div>
<div class="col-sm border rounded" style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important">
<div id="draggable8" class="btn btn-warning block" style="width:100%">
OPTION</div>
</div>
<div class="col-sm border rounded" style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important">
<div id="draggable9" class="btn btn-warning block" style="width:100%">
OPTION</div>
</div>
<div class="col-sm border rounded" style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important">
<div id="draggable10" class="btn btn-warning block" style="width:100%">
OPTION</div>
</div>
<div class="col-sm border rounded" style="padding-left:0px !important; padding-right:0px !important;padding-bottom:0px !important">
<div id="draggable11" class="btn btn-warning block" style="width:100%">
OPTION</div>
</div>
</div>
</div>
</div>
<div class="container-fluid" id="droppable" style="border:2px solid red;width:100%;height:98%;margin-top:5px">
</div>
</body>
</html>
view raw Draggable.html hosted with ❤ by GitHub
Drag and Drop for responsive design

Add Chart to ASP.NET MVC project.

Adding charts to your project, usually takes a little bit thinking. Because there are many open source plugins available, but choosing the right one that fits your needs, and the needs of your client could be tricky sometimes.

I choose Chart.js. It is lightweight JavaScript open source plugin available on GitHub because it has all basic functionality that I need, with a lot of easy customization options, including, for example, percentage display of your data, multiple data sets is a single chart, labeling plugin if you want, coloring choices, and nice hover style options.

It is also very simple to set its size to fit desktop, tablet, and mobile devices.

And as you might have guessed, mobile internet browsers are increasing day by day, with smart phones becoming the preferred choice for everyday busy users, and Chart.js does an amazing job of displaying our charts on mobile devices, in order for users to see clearly our data, and very fast.

In this project, I am going to use also Bootstrap.css, for presentation purposes, and for mobile and tablet visibility. So we are going to include only two references to two libraries: Bootstrap.css and Chart.js.

First, we will create a new ASP.NET MVC project, and in Models folder we will add new class called ChartModel.cs. The class is below:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace AddingChartToASPNetMVC.Models
{
public class ChartModel
{
//First chart points
public string firstchart_point_a;
public string firstchart_point_b;
public string firstchart_point_c;
//Second chart points – data set one
public string secondchart_ds1_point_a;
public string secondchart_ds1_point_b;
public string secondchart_ds1_point_c;
public string secondchart_ds1_point_e;
public string secondchart_ds1_point_f;
//Second chart points – data set two
public string secondchart_ds2_point_a;
public string secondchart_ds2_point_b;
public string secondchart_ds2_point_c;
public string secondchart_ds2_point_e;
public string secondchart_ds2_point_f;
}
}

view raw

ChartModel.cs

hosted with ❤ by GitHub

As you can see from above, our first chart has only one data set, with three data points. Our second chart, has two data sets with five data points each.

Now open _Layout.cshtml, which is our layout page for Index view, and add the code below:


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add chart in ASP.NET MVC</title>
<link href="~/Content/bootstrap.css" rel="stylesheet" />
<script src="~/Content/Chart.js"></script>
</head>
<body>
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Adding charts to ASP.NET MVC project</h1>
<p class="lead">
This page shows how to add two charts, PIE and BAR, with percentages display from actual values.
Bootstrap adds cross device capability, and Chart.js adds the actual charts. Labelling plugin for Chart.js from github is also used
to draw percent labels directly on the chart itself.
<br />
<h5><a href="https://thoughtsonprogramming.wordpress.com">Thoughts on Programming</a></h5>
</p>
</div>
</div>
@RenderBody()
</body>
</html>

view raw

_Layout.cshtml

hosted with ❤ by GitHub

As you can see from above code, we have a reference to bootstrap.css and Chart.js inside our project. Then we add jumbotron for presentation purposes, and add a title and description.

Next, and the our core functionality is our Index.cshtml view:


@model AddingChartToASPNetMVC.Models.ChartModel
@{
ViewBag.Title = "Home Page";
}
<div class="container">
<div class="row">
<div class="col-sm-6">
<div style="width:360px; height:300px;">
<canvas id="pieChart" style="width:360px; height:300px;"></canvas>
<script>
var canvas = document.getElementById("pieChart");
var ctx = canvas.getContext('2d');
Chart.defaults.global.defaultFontColor = 'black';
Chart.defaults.global.defaultFontSize = 12;
var theHelp = Chart.helpers;
var data = {
labels: ["VALUE A", "VALUE B", "VALUE C"],
datasets: [{
fill: true,
backgroundColor: [
'#ff6384',
'#9bbb59',
'#36a2eb'
],
data: [@Model.firstchart_point_a,@Model.firstchart_point_b, @Model.firstchart_point_c],
borderColor: ['#ff6384', '#9bbb59', '#36a2eb'],
borderWidth: [1, 1, 1]
}]
};
var options = {
title: {
display: true,
text: 'First Chart',
position: 'top'
}
};
// Chart declaration:
var myPieChart = new Chart(ctx, {
type: 'pie',
data: data,
options: options
});
//Plugin from githubExample:
//https://github.com/chartjs/Chart.js/blob/master/samples/data_labelling.html
Chart.plugins.register({
afterDatasetsDraw: function (chartInstance, easing) {
// To only draw at the end of animation, check for easing === 1
var ctx = chartInstance.chart.ctx;
var numberOfDatasets = 0;
chartInstance.data.datasets.forEach(function (dataset, i) {
numberOfDatasets = numberOfDatasets + 1;
});
if (numberOfDatasets == 1) {
chartInstance.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.getDatasetMeta(i);
if (!meta.hidden) {
meta.data.forEach(function (element, index) {
// Draw the text in black, with the specified font
ctx.fillStyle = 'black';
var fontSize = 15;
var fontStyle = 'normal';
var fontFamily = 'Helvetica Neue';
ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
// Just naively convert to string for now
var dataString;
if ((dataset.data[0] + dataset.data[1] + dataset.data[2]) == "0") {
dataString = 0;
}
else {
dataString = Math.round((dataset.data[index] * 100) / (dataset.data[0] + dataset.data[1] + dataset.data[2]));
}
// Make sure alignment settings are correct
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var padding = 5;
var position = element.tooltipPosition();
ctx.fillText(dataString + '%', position.x, position.y – (fontSize / 2) – padding);
});
}
});
}
}
});
</script>
</div>
</div>
<div class="col-sm-6">
<div style="width:365px; height:300px;text-align:center">
<canvas id="myChart2" style="width:365px; height:300px;"></canvas>
<script>
var ctx = document.getElementById("myChart2").getContext("2d");
var data = {
labels: ["LABEL 1", "LABEL 2", "LABEL 3", "LABEL 4", "LABEL 5"],
datasets: [{
label: "VALUE ONE",
backgroundColor: "#9bbb59",
data: [@Model.secondchart_ds1_point_a, @Model.secondchart_ds1_point_b, @Model.secondchart_ds1_point_c, @Model.secondchart_ds1_point_e, @Model.secondchart_ds1_point_f]
}, {
label: "VALUE 2",
backgroundColor: "#36a2eb",
data: [@Model.secondchart_ds2_point_a, @Model.secondchart_ds2_point_b, @Model.secondchart_ds2_point_c, @Model.secondchart_ds2_point_e, @Model.secondchart_ds2_point_f]
}]
};
var myBarChart = new Chart(ctx, {
type: 'bar',
data: data,
options: {
responsive: true,
animation: false,
legend: {
display: true
},
title: {
display: true,
text: 'Second Chart'
},
barValueSpacing: 20,
scales: {
yAxes: [{
ticks: {
min: 0,
}
}]
}
}
});
</script>
</div>
</div>
</div>
</div>

From the code above, we need to put our canvas tag inside another outer div, that is in order for it to fit properly when viewed on small devices:

<div style=width:360px; height:300px;>

The next important javascript variable inside script tags is data. It holds the actual points that we want to present:

data: [@Model.secondchart_ds1_point_a, @Model.secondchart_ds1_point_b, @Model.secondchart_ds1_point_c, @Model.secondchart_ds1_point_e, @Model.secondchart_ds1_point_f]

Those are model values that we set in the home controller.And this is our home controller:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace AddingChartToASPNetMVC.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
AddingChartToASPNetMVC.Models.ChartModel myChartModel = new Models.ChartModel();
//Data points to feed our first chart
myChartModel.firstchart_point_a = "60";
myChartModel.firstchart_point_b = "500";
myChartModel.firstchart_point_c = "30";
//Data points to feed First Dataset of our Second Chart
myChartModel.secondchart_ds1_point_a = "10";
myChartModel.secondchart_ds1_point_b = "20";
myChartModel.secondchart_ds1_point_c = "30";
myChartModel.secondchart_ds1_point_e = "40";
myChartModel.secondchart_ds1_point_f = "50";
//Data points to feed Second Dataset of our Second Chart
myChartModel.secondchart_ds2_point_a = "15";
myChartModel.secondchart_ds2_point_b = "25";
myChartModel.secondchart_ds2_point_c = "37";
myChartModel.secondchart_ds2_point_e = "48";
myChartModel.secondchart_ds2_point_f = "53";
return View("Index",myChartModel);
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
}

As you can see from the code above, we set our points in the Index Action result, and return our Model to our view, that we created earlier:

return View(Index,myChartModel);

Also, in order to draw our data points on the chart itself, each percentage in its according place on the Pie chart, we need to use plugin, as shown above. To draw only on first chart, our first chart has only one data set, so we check for this condition inside our plugin as such:

First we initiate a variable to hold number of data sets, because this plugin loops for each chart on the page, thus letting us know how much data sets each chart has:

var numberOfDatasets = 0;

And here we get the actual number of data sets:

chartInstance.data.datasets.forEach(function (dataset, i) {numberOfDatasets = numberOfDatasets + 1;});

Since we know that our Pie chart has only one data set, we can check for its condition here:

if (numberOfDatasets == 1) {

And, accordingly, if the condition is true, continue to draw the percentages. The percentages, we need to generate by ourselves too, they are not included. To do this, we compute each percentage and put it in our datastring variable as such:

dataString = Math.round((dataset.data[index] * 100) / (dataset.data[0] + dataset.data[1] + dataset.data[2]));

And then display the dataString  on the chart itself with appended % string as such:

ctx.fillText(dataString + ‘%’, position.x, position.y (fontSize / 2) padding);

And in the end we get the following Pie chart, with percentages:

PIE-CHART

And , because we specified the data sets condition, the second chart we get it with no percentages, as such:

BAR-CHART

Also, working video of the project:

 

The source code of the project is on GitHub:

https://github.com/thoughtsonprogramming/AddingChartToASPNetMVC

Subscription with rates form – Custom Step Wizard

Code on GitHub:

https://github.com/thoughtsonprogramming/SubscriptionFormUIUX

I was asked to write a new user subscription form, for a newspaper. I decided to make it cross browser, mobile friendly to capture bigger and wider audience, and cross platform. I will post images first, then show how to implement it.

It is pure Java Script, with bootstrap for cross platform. Why ? In order for it to be fast, no page hanging, attractive and yet simple.

Below are the images, with different sizes for mobile, desktop, and browser with zoomed viewport:

This slideshow requires JavaScript.

Download source code from my google drive here:

https://drive.google.com/file/d/1-VQ_PSZ9LFjF0ZpclfqNohvMG2-wuHY-/view?usp=sharing

And working video, you can watch it directly also on google drive here:

https://drive.google.com/file/d/1_KH1r9kmDbuVtv1AExWxbr3eNEJCaDPF/view?usp=sharing

It is SPA or Single Page Application.

Distribution Outlet App using Database First Approach

This project is also vailable on GitHub:

https://github.com/thoughtsonprogramming/MyDistributionOutlet

I enjoy creating databases, it trains my analytical part of my mind. Besides, when creating it working with someone else, there is a lot of room for improvement, from hearing someone else’s opinion, because business rules are set there, at least the most of them.

I also used to work in a distribution warehouse center, where big trucks would come in, products in a boxes unloaded with help of workers and put on wooden square plates,those plates put on Jigger Lifts or a Forklift and moved to their correct location temporary storage in the warehouse.

Jigger Lift

JiggerLift

Forklift

forklift-truck

Then, workers would make new carton boxes for new orders, that will be delivered to different shops and outlets, label them based on clients name,and put in a line.

Carton Boxes

cartonbox

In other place in the same warehouse, a special space is dedicated for ORDERS. Orders are printed on paper by warehouse manager, with different product names and quantity per order and per client perhaps, on a single sheet.And workers would go and get all the boxes needed for a single order from different sections of the warehouse, and bring them there.

Then those boxes, per order, would be distributed to the boxes that are in  a long line.

Once the box in that line is full, it is closed, sealed with regular scotch paper, and again put on a wooden plate for order sending. Now I am intentionally not using word shipment here not to confuse you,  developer and programmer, with something else. I will tell you later why.

As you might have guessed, inventory management systems are so many here and there, and almost every store who has a warehouse has some kind of tracking mechanism for his merchandise.

This particular example is not IMS ( inventory management system ). It is more like general distribution warehouse outlet software program, that warehouse managers, and their co-managers use to track, label, and instantly count products that come in from supplier, and at the same day maybe, leave the warehouse.

I am going to use SHIPPING, only related to SUPPLIER’s coming products to our warehouse, because some may confuse shipment with shipments to the client. In fact some use shipping referencing shipping to clients their orders, but we are not going to do this because later it gets very confusing what is SHIPPING and what is SHIPMENT, the definition of those words I mean.

We will use Database First approach, because this design is available to our disposal, it is convenient for our situation, it is necessary actually for our warehouse distribution outlet because in this environment, there are many nuances that are better off handled from the beginning in the database logic.

We will do the heavy thinking in the database layer, design our logical and real database, clarify all definitions, set our own definitions and standards, and then do the simple and light front end design.

So we are going to separate our app into three layers, back end, which is database design and implementation, front end, which is a web page, bootstrap built from beginning to accommodate of-course mobiles of all sorts, as well as browsers of all sorts, and then we are going to jump a little bit into our middle layer implementation which handles such logic as copying a product, adding new product and so on.

In short this is what is going to happen:

SUPPLIER is going to send us a SHIPMENTof the products that we need

Our distribution warehouse app is going to store all our products, label them, name them, order them and copy them,because copying helps us later on creating new or similar products by simply selecting from existing copied one and configuring it a bit.

Our clients are going to make ORDERS . We will make our order and send it to client, making revenue and selling products.

To start designing our database, I think it is a good idea to keep up with the chain. At least this helps me not to forget where our database objects are.

If we keep up with the chain, then we can save a lot of time designing this, finish quickly, and hand it to the client ASAP.

Ok. Chain !

Let’s go !

We will begin with SUPPLIER. I will not bore you with details, as you might have guessed, all tables have IDs, Primary Keys,  and essential columns that describe and relate ONLY to SUPPLIER object and definition.

So, we will need those columns:


supplierID bigint
supplierName nvarchar(MAX)
countryID bigint
supplierProvince nvarchar(MAX)
supplierCity nvarchar(MAX)
supplierPostalCode nvarchar(50)
supplierTelephone nvarchar(MAX)
supplierFax nvarchar(MAX)
supplierEmail nvarchar(MAX)
supplierStreet nvarchar(MAX)
supplierUnitNumber nvarchar(MAX)

As you can see, we put only nouns that describe SUPPLIER, his country, fax number, tel number, his address. Country ID is foreign key to Country table, which in our chain we will create next.

So, the Country table and object which we will use to refer our supplier to , or in other words, one country can have many suppliers, or there could be many suppliers from one single country. Therefore, we need to put one to many relationship from COUNTRY table to SUPPLIER table. Therefore, our SUPPLIER table will have foreign key countryID relating to countryID Primary Key in COUNTRY table. Here is COUNTRY table:


countryID bigint
countryName nvarchar(MAX)
countryAbbreviation nvarchar(MAX)
countryFlag nvarchar(MAX)

So below are our two tables that we have just created: SUPPLIER and COUNTRY. We need this to see how to create relationship one to many from COUNTRY to SUPPLIER:

SUPPLIER-TBL

COUNTRY-TBL

To create one to many relationships in SSMS from COUNTRY to SUPPLIER , always start with Foreign Key table, which is SUPPLIER in our case. Right click on it, go to Design, in the white space to the right, right click, and you will see those options below, click on relationships, as shown below:

RelationshipsWindow01

Once you click on relationships, we see the following window in SSMS:

RelationshipsWindow02

Please click on Add to the left, a new relationship is added, then to the left click on ellipsis next to Tables And Columns Specific to see this window, to configure foreign key column(many) and primary key column (one)  in one to many relationship.

RelationshipsWindow03

Click OK. So, as you can see, we have configured one to many relationship from COUNTRY table to SUPPLIER table. And so on . I am not going to repeat this again because we will do the same for other tables in our Chain of tables.

It is also very important to normalize our database to first normal form, or at least close to first normal form, meaning that we need to eliminate repeating values in rows completely. It is not impossible if you try hard enough.

Then, we create another table called SHIPMENT.It will hold products that our SUPPLIER sends us. Remember, as mentioned earlier, SHIPMENT in our case is NOT orders to our clients, but it is the shipment that our supplier sends us with products that we asked. We will refer to shipment to our clients as ORDER, inside our middle tier layer later on.

Shipment Table:


shipmentID bigint
countryID bigint
shipmentCity nvarchar(MAX)
shipmentProvince nvarchar(MAX)

Now, to achieve first normal form between SHIPMENT and SUPPLIER, we need to create new table, intermediate, between SHIPMENT and SUPPLIER called SHIPMENT_SUPPLIER. And, of-course, as you may have guessed, we will add relations to join those two tables, on our third table. So, we will add one to many relationship from SUPPLIER to SHIPMENT_SUPPLIER and from SHIPMENT to SHIPMENT_SUPPLIER, where :

  1. SUPPLIER  PK  -> SHIPMENT_SUPPLIER FK
  2. SHIPMENT PK -> SHIPMENT_SUPPLIER FK

Now we are done joining shipment to supplier.

Next, we need details about our shipment, like shipment date , shipment arrival date and so on. So we will add new table that describes our shipment in more detail. We will call this table SHIPMENT_DETAIL:


shipmentDetailsID bigint
shipmentID bigint
shipmentDate datetime
shipmentEstimatedArrivalDate datetime
shipmentArrivalDate datetime
shipmentMethod nvarchar(MAX)
numberOfProducts bigint

And we need to add one to many relationships from SHIPMENT table to SHIPMENT_DETAIL table

  1. SHIPMENT PK -> SHIPMENT_DETAIL FK

Now this is our first part of our database, or first general section if you wish, that deals with suppliers shipments and its details, and keeping track of them.

The second part of our database design, will have to do of-course with PRODUCT, that is stored in our distribution warehouse on shelves, rotated around here  and there, assembled and packed in cardboard boxes, and then sent to clients and their ORDERS.

Let’s create PRODUCT table, simple table with three columns. The details of PRODUCT we will separate to another table called PRODUCT_DESCRIPTION and the details of each product like size, price and weight yet go into third table called PRODUCT_DESCRIPTION_DETAIL.As you can see this is also a small CHAIN of three tables.

So, to summarize our second part of our database design, we need three tables that describe PRODUCT:

  1. PRODUCT
  2. PRODUCT_DESCRIPTION
  3. PRODUCT_DESCRIPTION_DETAIL

PRODUCT:


productID bigint
productName nvarchar(MAX)
productSerialNumber nvarchar(MAX)

PRODUCT_DESCRIPTION:


productDescriptionID bigint
productID bigint
productDescLong nvarchar(MAX)
prodctCopied bit

Now add one to many relationship from PRODUCT to PRODUCT_DESCRIPTION on corresponding productID fields in both tables. They do not need to be named the same, as long as they have exactly the same data type.

  1. PRODUCT  PK on productID  (one)  -> PRODUCT_DESCRIPTION  FK on productID  (many)

Now add third table:

PRODUCT_DESCRIPTION_DETAIL:


productDescriptionDetailsID bigint
productDescriptionID bigint
productColor nvarchar(MAX)
productWeight nvarchar(MAX)
productSize nvarchar(MAX)
productPrice nvarchar(MAX)

And add one to many relationship in our CHAIN from PRODUCT_DESCRIPTION to PRODUCT_DESCRIPTION_DETAIL:

  1. PRODUCT_DESCRIPTION on productDescriptionID PK (one)  –> PRODUCT_DESCRIPTION_DETAIL  on  productDescriptionID FK (many)

So the second part is done. But we are missing something. Those two parts, the SUPPLIER and SHIPMENT part , and PRODUCT part are not linked. They are separate clouds in the sky, floating, not related in any way.

So we will relate them by creating new table called SHIPMENT_PRODUCT_DETAIL, which will join the two sections through, and using two tables from each section corresponding:

  1. SHIPMENT_DETAIL (SECTION ONE)
  2. PRODUCT (SECTION TWO)

So here is the table SHIPMENT_PRODUCT_DETAIL:


shipmentProductDetailsID bigint
shipmentDetailsID bigint
productID bigint

And now we create two relationshipsto those two tables:

  1. SHIPMENT_DETAIL   on  PK  shipmentDetailsID(one)  –> SHIPMENT_PRODUCT_DETAIL  on  FK shipmentDetailsID (many)
  2. PRODUCT  on PK  productID (one) –> SHIPMENT_PRODUCT_DETAIL  on FK productID (many)

After finishing our third part, we review everything, all database logic, and we see that something is missing.

What is missing is BOXING DETAILS, and how much of each product boxes has arrived, and how much tiny boxes inside big boxes has arrived, and how many pieces are there in each of these sub -boxes.

Now, to add all those details to PRODUCT table would not be fair, because logically, it is shipment related matter, but at the same time,it is also product related matter.

So to settle this dispute, we create new table called: PRODUCT_BOXING_DETAILS and create one to many relationship from our last created table: SHIPMENT_PRODUCT_DETAIL  which joins our so called two database design theoretical sections.

PRODUCT_BOXING_DETAILS:


productBoxingDetailsID bigint
shipmentProductDetailsID bigint
numberOfBoxes bigint
numberOfPacks bigint
numberOfProducts bigint
numberOfProductsPerPack bigint
numberOfPacksPerBox bigint

And create one to many relationship from SHIPMENT_PRODUCT_DETAIL   to PRODUCT_BOXING_DETAILS

  1. SHIPMENT_PRODUCT_DETAIL on shipmentProductDetailsID  PK (one)  –> PRODUCT_BOXING_DETAILS on shipmentProductDetailsID  FK (many)

As you can see, all those design issues happen in theory, because database thinks otherwise. Of-course, one can design his database in whatever way he wants.

Also, it is a good idea to set NON-CLUSTERED indexes on columns that we might use a lot. This will help speed up our SELECT Queries and BULK SELECT. You can as well combine CLUSTERED with NON-CLUSTERED indexes, depending on your needs of-course. There are also third party open source plugins for SSMS that let you do execution plans and at the end literally suggest what INDEX with what columns and what options to add, which will speed your query pretty much visible even to the naked eye, which is amazing because everyone is obsessed with speeding up his SQL queries.Tell me otherwise.

Below is the image of our created Distribution Outlet Database .

Distribution-DB-Diagram

And SQL for this database is below, I named this database WH01:


USE [WH01]
GO
/****** Object: Table [dbo].[COUNTRY] Script Date: 2017-02-05 16:51:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[COUNTRY](
[countryID] [bigint] IDENTITY(1,10) NOT NULL,
[countryName] [nvarchar](max) NOT NULL,
[countryAbbreviation] [nvarchar](max) NOT NULL,
[countryFlag] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_COUNTRY] PRIMARY KEY CLUSTERED
(
[countryID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object: Table [dbo].[PRODUCT] Script Date: 2017-02-05 16:51:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[PRODUCT](
[productID] [bigint] IDENTITY(1,10) NOT NULL,
[productName] [nvarchar](max) NOT NULL,
[productSerialNumber] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_PRODUCT] PRIMARY KEY CLUSTERED
(
[productID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object: Table [dbo].[PRODUCT_BOXING_DETAILS] Script Date: 2017-02-05 16:51:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[PRODUCT_BOXING_DETAILS](
[productBoxingDetailsID] [bigint] IDENTITY(1,10) NOT NULL,
[shipmentProductDetailsID] [bigint] NOT NULL,
[numberOfBoxes] [bigint] NOT NULL,
[numberOfPacks] [bigint] NOT NULL,
[numberOfProducts] [bigint] NOT NULL,
[numberOfProductsPerPack] [bigint] NOT NULL,
[numberOfPacksPerBox] [bigint] NOT NULL,
CONSTRAINT [PK_PRODUCT_BOXING_DETAILS] PRIMARY KEY CLUSTERED
(
[productBoxingDetailsID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[PRODUCT_DESCRIPTION] Script Date: 2017-02-05 16:51:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[PRODUCT_DESCRIPTION](
[productDescriptionID] [bigint] IDENTITY(1,10) NOT NULL,
[productID] [bigint] NOT NULL,
[productDescLong] [nvarchar](max) NOT NULL,
[prodctCopied] [bit] NOT NULL,
CONSTRAINT [PK_PRODUCT_DESCRIPTION] PRIMARY KEY CLUSTERED
(
[productDescriptionID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object: Table [dbo].[PRODUCT_DESCRIPTION_DETAIL] Script Date: 2017-02-05 16:51:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[PRODUCT_DESCRIPTION_DETAIL](
[productDescriptionDetailsID] [bigint] IDENTITY(1,10) NOT NULL,
[productDescriptionID] [bigint] NOT NULL,
[productColor] [nvarchar](max) NOT NULL,
[productWeight] [nvarchar](max) NOT NULL,
[productSize] [nvarchar](max) NOT NULL,
[productPrice] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_PRODUCT_DESCRIPTION_DETAILS] PRIMARY KEY CLUSTERED
(
[productDescriptionDetailsID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object: Table [dbo].[SHIPMENT] Script Date: 2017-02-05 16:51:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SHIPMENT](
[shipmentID] [bigint] IDENTITY(1,10) NOT NULL,
[countryID] [bigint] NULL,
[shipmentCity] [nvarchar](max) NULL,
[shipmentProvince] [nvarchar](max) NULL,
CONSTRAINT [PK_SHIPMENT] PRIMARY KEY CLUSTERED
(
[shipmentID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object: Table [dbo].[SHIPMENT_DETAIL] Script Date: 2017-02-05 16:51:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SHIPMENT_DETAIL](
[shipmentDetailsID] [bigint] IDENTITY(1,10) NOT NULL,
[shipmentID] [bigint] NOT NULL,
[shipmentDate] [datetime] NULL,
[shipmentEstimatedArrivalDate] [datetime] NULL,
[shipmentArrivalDate] [datetime] NULL,
[shipmentMethod] [nvarchar](max) NULL,
[numberOfProducts] [bigint] NULL,
CONSTRAINT [PK_SHIPMENT_DETAILS] PRIMARY KEY CLUSTERED
(
[shipmentDetailsID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
/****** Object: Table [dbo].[SHIPMENT_PRODUCT_DETAIL] Script Date: 2017-02-05 16:51:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SHIPMENT_PRODUCT_DETAIL](
[shipmentProductDetailsID] [bigint] IDENTITY(1,10) NOT NULL,
[shipmentDetailsID] [bigint] NULL,
[productID] [bigint] NULL,
CONSTRAINT [PK_SHIPMENT_PRODUCT_DETAILS] PRIMARY KEY CLUSTERED
(
[shipmentProductDetailsID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[SHIPMENT_SUPPLIER] Script Date: 2017-02-05 16:51:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SHIPMENT_SUPPLIER](
[shipmentSupplierID] [bigint] IDENTITY(1,10) NOT NULL,
[shipmentID] [bigint] NOT NULL,
[supplierID] [bigint] NOT NULL,
CONSTRAINT [PK_SHIPMENT_SUPPLIER] PRIMARY KEY CLUSTERED
(
[shipmentSupplierID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
/****** Object: Table [dbo].[SUPPLIER] Script Date: 2017-02-05 16:51:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SUPPLIER](
[supplierID] [bigint] IDENTITY(1,10) NOT NULL,
[supplierName] [nvarchar](max) NOT NULL,
[countryID] [bigint] NOT NULL,
[supplierProvince] [nvarchar](max) NOT NULL,
[supplierCity] [nvarchar](max) NOT NULL,
[supplierPostalCode] [nvarchar](50) NOT NULL,
[supplierTelephone] [nvarchar](max) NOT NULL,
[supplierFax] [nvarchar](max) NOT NULL,
[supplierEmail] [nvarchar](max) NOT NULL,
[supplierStreet] [nvarchar](max) NOT NULL,
[supplierUnitNumber] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_SUPPLIER] PRIMARY KEY CLUSTERED
(
[supplierID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[PRODUCT_BOXING_DETAILS] WITH CHECK ADD CONSTRAINT [FK_PRODUCT_BOXING_DETAILS_SHIPMENT_PRODUCT_DETAIL] FOREIGN KEY([shipmentProductDetailsID])
REFERENCES [dbo].[SHIPMENT_PRODUCT_DETAIL] ([shipmentProductDetailsID])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[PRODUCT_BOXING_DETAILS] CHECK CONSTRAINT [FK_PRODUCT_BOXING_DETAILS_SHIPMENT_PRODUCT_DETAIL]
GO
ALTER TABLE [dbo].[PRODUCT_DESCRIPTION] WITH CHECK ADD CONSTRAINT [FK_PRODUCT_DESCRIPTION_PRODUCT] FOREIGN KEY([productID])
REFERENCES [dbo].[PRODUCT] ([productID])
GO
ALTER TABLE [dbo].[PRODUCT_DESCRIPTION] CHECK CONSTRAINT [FK_PRODUCT_DESCRIPTION_PRODUCT]
GO
ALTER TABLE [dbo].[PRODUCT_DESCRIPTION_DETAIL] WITH CHECK ADD CONSTRAINT [FK_PRODUCT_DESCRIPTION_DETAIL_PRODUCT_DESCRIPTION] FOREIGN KEY([productDescriptionID])
REFERENCES [dbo].[PRODUCT_DESCRIPTION] ([productDescriptionID])
GO
ALTER TABLE [dbo].[PRODUCT_DESCRIPTION_DETAIL] CHECK CONSTRAINT [FK_PRODUCT_DESCRIPTION_DETAIL_PRODUCT_DESCRIPTION]
GO
ALTER TABLE [dbo].[SHIPMENT] WITH CHECK ADD CONSTRAINT [FK_SHIPMENT_COUNTRY] FOREIGN KEY([countryID])
REFERENCES [dbo].[COUNTRY] ([countryID])
GO
ALTER TABLE [dbo].[SHIPMENT] CHECK CONSTRAINT [FK_SHIPMENT_COUNTRY]
GO
ALTER TABLE [dbo].[SHIPMENT] WITH CHECK ADD CONSTRAINT [FK_SHIPMENT_SHIPMENT] FOREIGN KEY([shipmentID])
REFERENCES [dbo].[SHIPMENT] ([shipmentID])
GO
ALTER TABLE [dbo].[SHIPMENT] CHECK CONSTRAINT [FK_SHIPMENT_SHIPMENT]
GO
ALTER TABLE [dbo].[SHIPMENT_DETAIL] WITH CHECK ADD CONSTRAINT [FK_SHIPMENT_DETAIL_SHIPMENT] FOREIGN KEY([shipmentID])
REFERENCES [dbo].[SHIPMENT] ([shipmentID])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[SHIPMENT_DETAIL] CHECK CONSTRAINT [FK_SHIPMENT_DETAIL_SHIPMENT]
GO
ALTER TABLE [dbo].[SHIPMENT_PRODUCT_DETAIL] WITH CHECK ADD CONSTRAINT [FK_SHIPMENT_PRODUCT_DETAIL_PRODUCT] FOREIGN KEY([productID])
REFERENCES [dbo].[PRODUCT] ([productID])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[SHIPMENT_PRODUCT_DETAIL] CHECK CONSTRAINT [FK_SHIPMENT_PRODUCT_DETAIL_PRODUCT]
GO
ALTER TABLE [dbo].[SHIPMENT_PRODUCT_DETAIL] WITH CHECK ADD CONSTRAINT [FK_SHIPMENT_PRODUCT_DETAIL_SHIPMENT_DETAIL] FOREIGN KEY([shipmentDetailsID])
REFERENCES [dbo].[SHIPMENT_DETAIL] ([shipmentDetailsID])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[SHIPMENT_PRODUCT_DETAIL] CHECK CONSTRAINT [FK_SHIPMENT_PRODUCT_DETAIL_SHIPMENT_DETAIL]
GO
ALTER TABLE [dbo].[SHIPMENT_SUPPLIER] WITH CHECK ADD CONSTRAINT [FK_SHIPMENT_SUPPLIER_SHIPMENT] FOREIGN KEY([shipmentID])
REFERENCES [dbo].[SHIPMENT] ([shipmentID])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[SHIPMENT_SUPPLIER] CHECK CONSTRAINT [FK_SHIPMENT_SUPPLIER_SHIPMENT]
GO
ALTER TABLE [dbo].[SHIPMENT_SUPPLIER] WITH CHECK ADD CONSTRAINT [FK_SHIPMENT_SUPPLIER_SUPPLIER] FOREIGN KEY([supplierID])
REFERENCES [dbo].[SUPPLIER] ([supplierID])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[SHIPMENT_SUPPLIER] CHECK CONSTRAINT [FK_SHIPMENT_SUPPLIER_SUPPLIER]
GO
ALTER TABLE [dbo].[SUPPLIER] WITH CHECK ADD CONSTRAINT [FK_SUPPLIER_COUNTRY] FOREIGN KEY([countryID])
REFERENCES [dbo].[COUNTRY] ([countryID])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[SUPPLIER] CHECK CONSTRAINT [FK_SUPPLIER_COUNTRY]
GO
ALTER TABLE [dbo].[SUPPLIER] WITH CHECK ADD CONSTRAINT [FK_SUPPLIER_SUPPLIER] FOREIGN KEY([supplierID])
REFERENCES [dbo].[SUPPLIER] ([supplierID])
GO
ALTER TABLE [dbo].[SUPPLIER] CHECK CONSTRAINT [FK_SUPPLIER_SUPPLIER]
GO

view raw

WH01db.sql

hosted with ❤ by GitHub

Once database is done, we will go to Front End design, as mentioned in the beginning of this article, and create ASP.NET MVC project, add entity framework for our database, generate some test data,create nice cross device and cross browser design, and run our project first page.

We will use Bootstrap Cards. Below is picture of bootstrap card, captured and edited from my mobile phone,since cards fit similar information on the same sheet called Card.

I am not surprised if we will start programming using phones in the future as well .

But first lets populate our database with some test data:

Test Data:

At the end of this post, a link to download Visual Studio Solution
will be provided. There a file named WH01testdata.sql inside the
project has SQL to add data to our database.

Using Visual Studio 2015, let’s create new ASP.NET MVC with  WebAPI support new project, name it MyDistributionOutlet,as shown in the image below:DistributionCreateProjectMVC

The next step is to add EntityFramework to our newly created WH01 database.

Now, of-course you can build data access layer within your client app or program that will use your database. It does not need to be Entity Framework.But for me it is easier to use LINQ to SQL because it saves time, security against SQL injection built in, like parameterized query and even better, direct conversion to IEnumerable and easy direct way of manipulating and working with IQueryable, and of course transaction like approach to objects with the ability to commit multiple ADD operations at the same time, for example, with single commit.

So it is convenient as you can see, that’s why I use it.

To be honest with you, I am only going to write first page of searching product, the logic behind it, and copying a product functionality, in case you want to add similar product later on.

Now remember, all this is taking place in the context of our Distribution Outlet and people who are going to be using it there or “actors “.

The rest of the code I will let you write it on your own, like making a new ORDER object, saving it, and preparing to print it for the end user.

You can use this code as you wish, it is open source and with no license at all.

I will put the download link for the code and working video in the end,as with my other blog posts.

We will use Visual Studio 2012. Just for a change. You can use any version, but the process of creation ASP.NET MVC WEB API project is a little bit different.

Please create new ASP.NET WEB API Project and name it MyDistributionOutlet.

In the Views –> Shared directory, in _Layout.cshtml page, in the header, add the following links:

  1. https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css
  2. <!– HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries –>
    <!– WARNING: Respond.js doesn’t work if you view the page via file:// –>
    <!–[if lt IE 9]>
    https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js
    https://oss.maxcdn.com/respond/1.4.2/respond.min.js
    <![endif]–>
  3. https://code.jquery.com/jquery-3.1.1.js
  4. https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js

And in the <body> tag, right after it,  and before @RenderBody() add our newly designed SEARCH and NAVIGATION Cross Device, mobile and desktop menu: by the way, I spent quite a while designing this menu because I did not want anything there, and also I wanted and needed text box to be the same size on desktop and also when zoomed on mobile viewport:

 


<nav role="navigation" class="navbar navbar-default">
<div class="navbar-header">
<button type="button" data-target="#navbarCollapse" data-toggle="collapse" class="navbar-toggle">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/DOutlet/Index" class="navbar-brand">Outlet D1</a>
</div>
<form role="search" class="navbar-form navbar-left" action="/Home/Search" name="searchForm" method="post">
<div class="form-group" style="margin-left: 15px; margin-right: 15px;">
<div class="input-group">
<input type="text" class="form-control" id="searchDialog" name="searchDialog" placeholder="Search" style="height: 36px" />
<div class="input-group-addon">
<button type="submit">Search</button>
</div>
</div>
<div class="well well-sm">
<div class="checkbox">
<label>
<input type="checkbox" name="cbProduct">
PRODUCT
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="cbShipment">
SHIPMENT
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="cbGridview">
Grid view
</label>
</div>
</div>
</div>
</form>
<div id="navbarCollapse" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="dropdown">
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Options<b class="caret"></b></a>
<ul role="menu" class="dropdown-menu">
<li><a href="/Home/NewProduct">Add new product</a></li>
<li><a href="/Home/NewShipment">Make new order</a></li>
<li><a href="#">Change product status</a></li>
<li class="divider"></li>
<li><a href="#">Trash</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Login</a></li>
</ul>
</div>
</nav>

view raw

WH01Nav.cshtml

hosted with ❤ by GitHub

Run the project in debug or release mode, and you will see those two images respectively:

DESKTOP VIEW:

doutletdesktopview

DESKTOP VIEW

MOBILE VIEW:

doutletmobileview

MOBILE IPHONE 8 VIEW

ZOOMED AND MINIMIZED BROWSER VIEWPORT VIEW:

doutletzoomedbrowserviewportview

BROWSER ZOOMED VIEW

As you can see, HTML5 Rocks !  Anyways, this is just front page when the app starts. You can also add login capability so the start screen would be your desired login screen, but it is up to you to add it or not.

This menu contains search box, where you can type product name and get all products by that or similar name, also, you can select Grid View, and instead of default Cards view you will have regular gridview with sub menu for supplier for each product.

The shipment or search by shipment is intentionally left for you on your choice to implement or not.

We will continue by our plan, if you scroll back to the beginning of this post, I said that we will first do database design, which is back end, then switch completely to front end design, (what we are doing now).So we will continue thinking in those terms and not jump around here and there.

Next in our front end design, we need a search page that will display our search results. Lets design it ! Here is the code for search page that we have  designed once the user clicks on search button, goes to search Action Result (ActionResult Search), which in its turn will return GridSearch view OR SearchProduct view respectively.

GridSearch view will display our search results in a grid view, and SearchProduct view will display our results in Cards layout. You can as well, mix,and create views with traditional web forms.

Since both of these views will contain our Model data as in Model View Controller, we need a model, we will create two classes in Models folder:

  1. SearchProductModel
  2. SearchProductModelList

doutlettwoclassesModels

And at the top of GridSearch view and at the top of SearchProduct.aspx page we will add the following:

<%@ Page Language=”C#” MasterPageFile=”~/Views/Shared/GeneralMaster.master” Inherits=”System.Web.Mvc.ViewPage<MyDistributionOutlet.Models.SearchProductModelList>” %>

Notice the inherits directive. This is how we pass the model to ASP.NET web page.

Also, we used master page, in order not to put search box on each page.

OK,Then we add new folder called EntityFramework, and add to it EDMX or ADO.NET Entity Data Model, and call it WH01Entities

Now switch to Controllers folder as per Model View Controller and in our HomeController add Search ActionResult, as shown below:


[HttpPost]
public ActionResult Search(FormCollection w_formCollection)
{
string searchTerm = w_formCollection["searchDialog"].ToString();
string byProduct = w_formCollection["cbProduct"] == null ? "off" : "on";
string byShipment = w_formCollection["cbShipment"] == null ? "off" : "on";
string gView = w_formCollection["cbGridview"] == null ? "off" : "on";
SearchProductModelList spml = new SearchProductModelList();
List<SearchProductModel> sp = new List<SearchProductModel>();
using (var searchPhrase = new WH01Entities())
{
if (byProduct == "on" && byShipment == "on")
{
}
//only search products
else if (byProduct == "on" && byShipment == "off")
{
var searchProductresults = from i in searchPhrase.PRODUCT_DESCRIPTION_DETAIL
where i.PRODUCT_DESCRIPTION.PRODUCT.productName.Contains(searchTerm)
select new SearchProductModel()
{
m_productId = i.PRODUCT_DESCRIPTION.PRODUCT.productID,
m_productName = i.PRODUCT_DESCRIPTION.PRODUCT.productName,
m_productSerialNumber = i.PRODUCT_DESCRIPTION.PRODUCT.productSerialNumber,
m_productDescriptionLong = i.PRODUCT_DESCRIPTION.productDescLong,
m_productPrice = i.productPrice,
m_productSize = i.productSize,
m_productColor = i.productColor,
m_productWeight = i.productWeight,
};
List<SearchProductModel> pmltemp = new List<SearchProductModel>();
pmltemp = searchProductresults.ToList();
foreach (var searchProduct in pmltemp)
{
var supplierForProductQuery = from s in searchPhrase.SHIPMENT_PRODUCT_DETAIL
where (s.productID == searchProduct.m_productId)
select new SearchProductModel()
{
m_supplierId = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.supplierID).FirstOrDefault(),
m_supplierName = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.supplierName).FirstOrDefault(),
m_supplierCity = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.supplierCity).FirstOrDefault(),
m_supplierCountry = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.COUNTRY.countryName).FirstOrDefault(),
m_supplierEmail = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.supplierEmail).FirstOrDefault(),
m_supplierFax = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.supplierFax).FirstOrDefault(),
m_supplierPostalCode = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.supplierPostalCode).FirstOrDefault(),
m_supplierProvince = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.supplierProvince).FirstOrDefault(),
m_supplierStreet = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.supplierStreet).FirstOrDefault(),
m_supplierTelephone = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.supplierTelephone).FirstOrDefault(),
m_supplierUnitNumber = s.SHIPMENT_DETAIL.SHIPMENT.SHIPMENT_SUPPLIER.Select(sup => sup.SUPPLIER.supplierUnitNumber).FirstOrDefault(),
m_shipmentCountry = s.SHIPMENT_DETAIL.SHIPMENT.COUNTRY.countryName,
m_shipmentCity = s.SHIPMENT_DETAIL.SHIPMENT.shipmentCity,
m_shipmentProvince = s.SHIPMENT_DETAIL.SHIPMENT.shipmentProvince,
m_shipmentDate = s.SHIPMENT_DETAIL.shipmentDate,
m_shipmentEstimatedArrivalDate = s.SHIPMENT_DETAIL.shipmentEstimatedArrivalDate,
m_shipmentArrivalDate = s.SHIPMENT_DETAIL.shipmentArrivalDate,
m_shipmentMethod = s.SHIPMENT_DETAIL.shipmentMethod,
m_shipmentNumberOfProductsGeneral = s.SHIPMENT_DETAIL.numberOfProducts,
m_shipmentNumberOfBoxes = searchPhrase.PRODUCT_BOXING_DETAILS.Where(d => d.shipmentProductDetailsID.Equals(s.shipmentProductDetailsID)).Select(xo => xo.numberOfBoxes).FirstOrDefault(),
m_shipmentNumberofPacks = searchPhrase.PRODUCT_BOXING_DETAILS.Where(d => d.shipmentProductDetailsID.Equals(s.shipmentProductDetailsID)).Select(xo => xo.numberOfPacks).FirstOrDefault(),
m_shipmentNumberOfProductsDetail = searchPhrase.PRODUCT_BOXING_DETAILS.Where(d => d.shipmentProductDetailsID.Equals(s.shipmentProductDetailsID)).Select(xo => xo.numberOfProducts).FirstOrDefault(),
m_shipmentNumberOfProductsPerPack = searchPhrase.PRODUCT_BOXING_DETAILS.Where(d => d.shipmentProductDetailsID.Equals(s.shipmentProductDetailsID)).Select(xo => xo.numberOfProductsPerPack).FirstOrDefault(),
m_shipmentNumberOfPacksPerBox = searchPhrase.PRODUCT_BOXING_DETAILS.Where(d => d.shipmentProductDetailsID.Equals(s.shipmentProductDetailsID)).Select(xo => xo.numberOfPacksPerBox).FirstOrDefault()
};
var query = supplierForProductQuery.ToList();
//foreach (var suppplierShipment in pmltemp2)
//{
searchProduct.m_supplierId = Convert.ToInt32(query.Select(m => m.m_supplierId).FirstOrDefault());
searchProduct.m_supplierName = query.Select(m => m.m_supplierName).FirstOrDefault();
searchProduct.m_supplierCity = query.Select(m => m.m_supplierCity).FirstOrDefault();
searchProduct.m_supplierCountry = query.Select(m => m.m_supplierCountry).FirstOrDefault();
searchProduct.m_supplierEmail = query.Select(m => m.m_supplierEmail).FirstOrDefault();
searchProduct.m_supplierFax = query.Select(m => m.m_supplierFax).FirstOrDefault();
searchProduct.m_supplierPostalCode = query.Select(m => m.m_supplierPostalCode).FirstOrDefault();
searchProduct.m_supplierProvince = query.Select(m => m.m_supplierProvince).FirstOrDefault();
searchProduct.m_supplierStreet = query.Select(m => m.m_supplierStreet).FirstOrDefault();
searchProduct.m_supplierTelephone = query.Select(m => m.m_supplierTelephone).FirstOrDefault();
searchProduct.m_supplierUnitNumber = query.Select(m => m.m_supplierUnitNumber).FirstOrDefault();
searchProduct.m_shipmentCountry = query.Select(m => m.m_shipmentCountry).FirstOrDefault();
searchProduct.m_shipmentCity = query.Select(m => m.m_shipmentCity).FirstOrDefault();
searchProduct.m_shipmentProvince = query.Select(m => m.m_shipmentProvince).FirstOrDefault();
searchProduct.m_shipmentDate = query.Select(m => m.m_shipmentDate).FirstOrDefault();
searchProduct.m_shipmentEstimatedArrivalDate = query.Select(m => m.m_shipmentEstimatedArrivalDate).FirstOrDefault();
searchProduct.m_shipmentArrivalDate = query.Select(m => m.m_shipmentArrivalDate).FirstOrDefault();
searchProduct.m_shipmentMethod = query.Select(m => m.m_shipmentMethod).FirstOrDefault();
searchProduct.m_shipmentNumberOfProductsGeneral = query.Select(m => m.m_shipmentNumberOfProductsGeneral).FirstOrDefault();
searchProduct.m_shipmentNumberOfBoxes = query.Select(m => m.m_shipmentNumberOfBoxes).FirstOrDefault();
searchProduct.m_shipmentNumberofPacks = query.Select(m => m.m_shipmentNumberofPacks).FirstOrDefault();
searchProduct.m_shipmentNumberOfProductsDetail = query.Select(m => m.m_shipmentNumberOfProductsDetail).FirstOrDefault();
searchProduct.m_shipmentNumberOfProductsPerPack = query.Select(m => m.m_shipmentNumberOfProductsPerPack).FirstOrDefault();
searchProduct.m_shipmentNumberOfPacksPerBox = query.Select(m => m.m_shipmentNumberOfPacksPerBox).FirstOrDefault();
sp.Add(searchProduct);
}
}
//only searchshipments
else if (byShipment == "on" && byProduct == "off")
{
}
spml.listofsearchproducts = sp;
return gView == "on" ? View("GridSearch", spml) : View("SearchProduct", spml);
}
}

Also, as I mentioned before, we will add functionality to COPY a product. Copying a product is nothing but adding a prodctCopied field in PRODUCT_DESCRIPTION  table.

We will add this code below in our HomeController:


public ActionResult CopyProduct(FormCollection s_formCollection)
{
var ModelToReturn = TempData["currModel"];
var prodId = Convert.ToInt32(s_formCollection["hprodid"]);
var UpdateContext = new EntityFramework.WH01Entities();
var quwry = from pdet in UpdateContext.PRODUCT_DESCRIPTION
where pdet.productID == prodId
select pdet;
foreach (PRODUCT_DESCRIPTION pd in quwry)
{
pd.prodctCopied = true;
}
UpdateContext.SaveChanges();
return View("SearchProduct", ModelToReturn);
}

The test data and database creation script is available inside the project folder once you download the solution, in test-data.sql  file and wh01-tables.sql respectively.

So once we do all this, and fill in some test data,we will see those pages:

DESKTOP CARDS DEFAULT RESULT (AFTER SEARCH CLICK)

doutlet_after_search_desktop

DESKTOP CARDS MOBILE RESULT (AFTER SEARCH CLICK)

doutlet_after_search_MOBILE

And card list continues down below if you scroll down.

And:

DESKTOP ZOOMED HTML 5 VIEW PORT:

doutlet_after_search_BROWSER_ZOOM_PORT

Also, if you scroll the list will go down and down, with the cards list that our search returned.

The GRID VIEW page:

doutlet_after_search_GRIDVIEW

It contains sub-report, and EDIT functionality on the same page.

Edit option:

doutlet_after_search_GRIDVIEW_EDIT

Thank you,

🙂