Make 'Product Add' page
This commit is contained in:
parent
7d2ae0b2d2
commit
16b4d686e3
|
@ -5,12 +5,14 @@ use ProductList\Http\Request;
|
|||
use ProductList\Http\RequestHandler;
|
||||
use ProductList\Http\Route;
|
||||
|
||||
$request = new Request($_SERVER);
|
||||
$request = new Request($_SERVER, $_GET, $_POST);
|
||||
$handler = new RequestHandler($request);
|
||||
|
||||
$handler->registerRoutes([
|
||||
new Route('GET', 'products', ['ProductList\View\Product', 'list']),
|
||||
new Route('DELETE', 'products', ['ProductList\View\Product', 'delete']),
|
||||
new Route('GET', 'test', ['ProductList\View\Product', 'test']),
|
||||
new Route('GET', 'product', ['ProductList\View\Product', 'get']),
|
||||
new Route('DELETE', 'product', ['ProductList\View\Product', 'delete']),
|
||||
new Route('POST', 'product', ['ProductList\View\Product', 'post']),
|
||||
new Route('GET', 'add-product', function() { readfile('static/add-product.html'); }),
|
||||
new Route('GET', '', function() { readfile('static/index.html'); }),
|
||||
]);
|
||||
|
|
|
@ -5,7 +5,8 @@ class Request
|
|||
{
|
||||
private $method;
|
||||
private $uri;
|
||||
private $queryString;
|
||||
private $queryParams;
|
||||
private $formParams;
|
||||
|
||||
public function getMethod()
|
||||
{
|
||||
|
@ -17,19 +18,25 @@ class Request
|
|||
return $this->uri;
|
||||
}
|
||||
|
||||
public function getQueryString()
|
||||
public function getQueryParams()
|
||||
{
|
||||
return $this->queryString;
|
||||
return $this->queryParams;
|
||||
}
|
||||
|
||||
public function __construct(array $params)
|
||||
public function getFormParams()
|
||||
{
|
||||
$uri_base = trim($params['REQUEST_URI'], '?'.$params['QUERY_STRING']);
|
||||
return $this->formParams;
|
||||
}
|
||||
|
||||
public function __construct(array $params, array $queryParams, array $formParams)
|
||||
{
|
||||
$uri_base = strtok($params['REQUEST_URI'], '?');
|
||||
$uri_base = trim(urldecode($uri_base), '/');
|
||||
$this->uri = explode('/', $uri_base);
|
||||
|
||||
$this->method = $params['REQUEST_METHOD'];
|
||||
|
||||
parse_str($params['QUERY_STRING'], $this->queryString);
|
||||
$this->queryParams = $queryParams;
|
||||
$this->formParams = $formParams;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,12 @@ class RequestHandler
|
|||
{
|
||||
foreach ($this->routes as $route) {
|
||||
if ($route->matches($this->request)) {
|
||||
$route->execute($this->request);
|
||||
try {
|
||||
$route->execute($this->request);
|
||||
} catch (\Exception $e) {
|
||||
http_response_code(500);
|
||||
echo $e->getMessage();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,21 +3,25 @@ namespace ProductList\View;
|
|||
|
||||
use ProductList\Http\Request;
|
||||
use ProductList\Model\Product as ProductModel;
|
||||
use ProductList\Model\DVD;
|
||||
use ProductList\Model\Furniture;
|
||||
use ProductList\Model\Book;
|
||||
use ProductList\Exception\NotFoundException;
|
||||
|
||||
class Product
|
||||
class Product extends View
|
||||
{
|
||||
public static function list(Request $request)
|
||||
public static function get(Request $request)
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(ProductModel::selectAll());
|
||||
}
|
||||
|
||||
public static function delete(Request $request)
|
||||
{
|
||||
$queryString = $request->getQueryString();
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
if (array_key_exists('id', $queryString)) {
|
||||
$ids = explode(',', $queryString['id']);
|
||||
if (array_key_exists('id', $queryParams)) {
|
||||
$ids = explode(',', $queryParams['id']);
|
||||
$ids = array_map('intval', $ids);
|
||||
|
||||
foreach($ids as $id) {
|
||||
|
@ -44,7 +48,70 @@ class Product
|
|||
}
|
||||
}
|
||||
|
||||
public static function add(Request $request)
|
||||
public static function post(Request $request)
|
||||
{
|
||||
$params= $request->getFormParams();
|
||||
$expected = [
|
||||
'sku',
|
||||
'name',
|
||||
'price',
|
||||
'productType',
|
||||
'weight',
|
||||
'size',
|
||||
'height',
|
||||
'width',
|
||||
'length'
|
||||
];
|
||||
if (self::expectArgs($expected, $params)) {
|
||||
$product = null;
|
||||
$type = $params['productType'];
|
||||
|
||||
switch($type) {
|
||||
case 'dvd':
|
||||
$product = new DVD(
|
||||
$params['sku'],
|
||||
$params['name'],
|
||||
$params['price'],
|
||||
$params['size'],
|
||||
);
|
||||
break;
|
||||
case 'furniture':
|
||||
$product = new Furniture(
|
||||
$params['sku'],
|
||||
$params['name'],
|
||||
$params['price'],
|
||||
$params['height'],
|
||||
$params['width'],
|
||||
$params['length'],
|
||||
);
|
||||
break;
|
||||
case 'book':
|
||||
$product = new Book(
|
||||
$params['sku'],
|
||||
$params['name'],
|
||||
$params['price'],
|
||||
$params['weight'],
|
||||
);
|
||||
break;
|
||||
default:
|
||||
http_response_code(400);
|
||||
echo "Invalid 'productType' value '$type'";
|
||||
return;
|
||||
}
|
||||
|
||||
//try {
|
||||
$product->insert();
|
||||
//} catch (\Exception $e) {
|
||||
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
public static function test(Request $request)
|
||||
{
|
||||
$params= $request->getQueryParams();
|
||||
if (self::expectArgs(['testarg'], $params)) {
|
||||
echo var_dump($params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
namespace ProductList\View;
|
||||
|
||||
abstract class View
|
||||
{
|
||||
protected static function expectArgs(array $args, array $provided) {
|
||||
$keys = array_keys($provided);
|
||||
|
||||
$missing = array_diff($args, $keys);
|
||||
|
||||
if (count($missing) > 0) {
|
||||
http_response_code(400);
|
||||
|
||||
$missing = join("', '", $missing);
|
||||
|
||||
echo "Missing parameters '$missing'";
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,83 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="static/index.css"/>
|
||||
<title>Product Add</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<h1 id="title">Product Add</h1>
|
||||
<div id="buttons">
|
||||
<a href="/"><button>Save</button></a>
|
||||
<a href="/"><button>Cancel</button></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="static/stylesheet.css"/>
|
||||
<title>Product Add</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<h1 id="title">Product Add</h1>
|
||||
<div id="buttons">
|
||||
<div class="header-button"><button type="submit" form="product_form" id="save">Save</button></div>
|
||||
<div class="header-button"><a href="/"><button>Cancel</button></a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="main">
|
||||
<form id="product_form">
|
||||
<div id="basic-info" class="form-section">
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="sku">SKU</label></td>
|
||||
<td><input type="text" id="sku" name="sku" required></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="name">Name</label></td>
|
||||
<td><input type="text" id="name" name="name" required></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="price">Price ($)</label></td>
|
||||
<td><input type="number" id="price" name="price" required></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="type-switcher" class="form-section">
|
||||
<label for="productType">Type Switcher</label>
|
||||
<select name="productType" id="productType">
|
||||
<option value="dvd">DVD</option>
|
||||
<option value="furniture">Furniture</option>
|
||||
<option value="book">Book</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="attribute" class="form-section">
|
||||
<div id="dvd" class="hidden">
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="size">Size (MB)</label></td>
|
||||
<td><input type="text" id="size" name="size"></td>
|
||||
</tr>
|
||||
</table>
|
||||
Please, provide size
|
||||
</div>
|
||||
<div id="furniture" class="hidden">
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="height">Height (CM)</label></td>
|
||||
<td><input type="text" id="height" name="height"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="width">Width (CM)</label></td>
|
||||
<td><input type="text" id="width" name="width"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="length">Length (CM)</label></td>
|
||||
<td><input type="text" id="length" name="length"></td>
|
||||
</tr>
|
||||
</table>
|
||||
Please, provide dimensions
|
||||
</div>
|
||||
<div id="book" class="hidden">
|
||||
<table>
|
||||
<tr>
|
||||
<td><label for="weight">Weight (KG)</label></td>
|
||||
<td><input type="text" id="weight" name="weight"></td>
|
||||
</tr>
|
||||
</table>
|
||||
Please, provide weight
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="static/add-product.js"></script>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
let currentTypeForm = document.getElementById($('#productType').val());
|
||||
currentTypeForm.querySelectorAll('input').forEach(input => input.required = true);
|
||||
currentTypeForm.classList.remove('hidden');
|
||||
|
||||
$('#productType').on('change', e => {
|
||||
const newTypeForm = document.getElementById(e.target.value);
|
||||
|
||||
currentTypeForm.classList.add('hidden');
|
||||
newTypeForm.classList.remove('hidden');
|
||||
|
||||
currentTypeForm.querySelectorAll('input').forEach(input => input.required = false);
|
||||
newTypeForm.querySelectorAll('input').forEach(input => input.required = true);
|
||||
|
||||
currentTypeForm = newTypeForm;
|
||||
});
|
||||
|
||||
$('#product_form').on('submit', e=> {
|
||||
e.preventDefault();
|
||||
|
||||
$.ajax(
|
||||
'product',
|
||||
{
|
||||
method: 'POST',
|
||||
data: $('#product_form').serializeArray(),
|
||||
success: _ => window.location.href = '/',
|
||||
error: jqXHR => alert(jqXHR.responseText),
|
||||
}
|
||||
);
|
||||
});
|
|
@ -1,20 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="static/index.css"/>
|
||||
<title>Product List</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<h1 id="title">Product List</h1>
|
||||
<div id="buttons">
|
||||
<a href="add-product"><button>ADD</button></a>
|
||||
<button id="delete-product-btn">MASS DELETE</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="products">
|
||||
</div>
|
||||
</body>
|
||||
<script src="static/index.js"></script>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" href="static/stylesheet.css"/>
|
||||
<title>Product List</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header">
|
||||
<h1 id="title">Product List</h1>
|
||||
<div id="buttons">
|
||||
<div class="header-button"><a href="add-product"><button>ADD</button></a></div>
|
||||
<div class="header-button"><button id="delete-product-btn">MASS DELETE</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="products">
|
||||
</div>
|
||||
</body>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="static/index.js"></script>
|
||||
</html>
|
||||
|
|
|
@ -1,45 +1,40 @@
|
|||
const productsList = document.getElementById('products');
|
||||
|
||||
const loadItems = () => {
|
||||
const xhttp = new XMLHttpRequest();
|
||||
$.ajax(
|
||||
'product',
|
||||
{
|
||||
success: data => {
|
||||
const boxes = data.map(product =>
|
||||
`<div class="product">
|
||||
<input type="checkbox" class="delete-checkbox" value="${product.id}">
|
||||
<p>
|
||||
${product.sku}<br>
|
||||
${product.name}<br>
|
||||
${product.price} $<br>
|
||||
${product.attribute}
|
||||
</p>
|
||||
</div>`
|
||||
);
|
||||
|
||||
xhttp.onload = function() {
|
||||
const products = JSON.parse(this.responseText);
|
||||
|
||||
const boxes = products.map(product =>
|
||||
`<div class="product">
|
||||
<input type="checkbox" class="delete-checkbox" value="${product.id}">
|
||||
<p>
|
||||
${product.sku}<br>
|
||||
${product.name}<br>
|
||||
${product.price} $<br>
|
||||
${product.attribute}
|
||||
</p>
|
||||
</div>`
|
||||
)
|
||||
|
||||
productsList.innerHTML = boxes.join('\n');
|
||||
}
|
||||
|
||||
xhttp.open('GET', 'products', true);
|
||||
xhttp.send();
|
||||
$('#products').html(boxes.join('\n'));
|
||||
},
|
||||
error: jqXHR => alert(jqXHR.responseText),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
loadItems();
|
||||
|
||||
const deleteSelected = () => {
|
||||
const checkboxes = document.querySelectorAll('input[class="delete-checkbox"]:checked');
|
||||
let values = [];
|
||||
checkboxes.forEach(checkbox => values.push(checkbox.value));
|
||||
$('#delete-product-btn').on('click', () => {
|
||||
let values = [];
|
||||
const checkboxes = document.querySelectorAll('input[class="delete-checkbox"]:checked');
|
||||
checkboxes.forEach(checkbox => values.push(checkbox.value));
|
||||
|
||||
const xhttp = new XMLHttpRequest();
|
||||
|
||||
xhttp.onload = function() {
|
||||
loadItems();
|
||||
}
|
||||
|
||||
xhttp.open('DELETE', `products?id=${values.join(',')}`, true);
|
||||
xhttp.send();
|
||||
}
|
||||
|
||||
const deleteButton = document.getElementById('delete-product-btn');
|
||||
deleteButton.addEventListener('click', deleteSelected);
|
||||
$.ajax(
|
||||
`product?id=${values.join(',')}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
success: loadItems,
|
||||
error: jqXHR => alert(jqXHR.responseText),
|
||||
}
|
||||
)
|
||||
});
|
||||
|
|
|
@ -14,16 +14,15 @@ body {
|
|||
margin: 0 1em;
|
||||
}
|
||||
|
||||
#buttons a {
|
||||
.header-button {
|
||||
align-self: center;
|
||||
margin: 0 2em;
|
||||
}
|
||||
|
||||
#buttons button {
|
||||
font-family: cursive;
|
||||
padding: .5em 1em;
|
||||
font-size: 1rem;
|
||||
margin: 0 2em;
|
||||
align-self: center;
|
||||
background-color: white;
|
||||
border: 2px solid black;
|
||||
box-shadow: 2px 2px black;
|
||||
|
@ -56,3 +55,23 @@ body {
|
|||
position: absolute;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#main {
|
||||
margin: 2em;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin: 2em;
|
||||
}
|
||||
|
||||
td:nth-child(2) {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
#productType {
|
||||
margin-left: 2em;
|
||||
}
|
Loading…
Reference in New Issue