Drag and drop items, fix stability for managing items

master
Eirik Th S 2021-04-23 02:25:03 +02:00
parent 09fee09a67
commit 646fb07646
4 changed files with 240 additions and 154 deletions

View File

@ -75,15 +75,21 @@ span.price {
}
.addItemForm input {
.addItemForm div.form-control {
transition: width 0.5s;
}
.addItemForm input:not([type="submit"],[type="image"]):focus {
/* .addItemForm div.form-control:not([type="submit"],[type="image"]):focus-within { */
.addItemForm div.form-control:focus-within {
width: 40%;
}
.addItemForm div.form-control input {
border: none;
}
.addItemForm input[type="image"] {
padding-left: 5px;
padding-right: 5px;
height: 100%;
}
.remItem {
@ -99,6 +105,8 @@ span.price {
/* filter color source: https://codepen.io/sosuke/full/Pjoqqp */
}
.addItemForm3 div:focus-within {
width: 80%;
.drag-over {
border-bottom: dashed 3px red;
border-bottom-width: 1px;
}

View File

@ -72,8 +72,8 @@ if(!empty($data) && isset($user_id)){
returns("Missing a value: $temp", 1);
}
if( addItem($data['storeID'], $data['name'], $data['price']) ){
returns();
if( ($itemID = addItem($data['storeID'], $data['name'], $data['price'])) !== false ){
returns($itemID);
}
else {
returns($db->error,1);
@ -82,11 +82,11 @@ if(!empty($data) && isset($user_id)){
else if($data['plan'] == "remItem"){
if(($temp = checkArgs(array("storeID"=>@$data['storeID'], "position"=>@$data['position'], "price"=>@$data['price']))) !== true){
if(($temp = checkArgs(array("storeID"=>@$data['storeID'], "itemID"=>@$data['itemID'], "price"=>@$data['price']))) !== true){
returns("Missing a value: $temp", 1);
}
if( remItem($data['storeID'], $data['position'], $data['price']) ){
if( remItem($data['storeID'], $data['itemID'], $data['price']) ){
returns();
}
else {
@ -96,13 +96,30 @@ if(!empty($data) && isset($user_id)){
}
}
// UPDATE ITEM AMOUNT
// UPDATE ITEM POSITION
else if($data['plan'] == 'moveItem'){
if(($temp = checkArgs(array("storeID"=>@$data['storeID'], "itemID"=>@$data['itemID'], "afterID"=>@$data['afterID']))) !== true){
returns("Missing a value: $temp", 1);
}
if( moveItem($data['storeID'], $data['itemID'], $data['afterID']) ){
returns();
}
returns($db->error,1);
}
else {
$sql = "SELECT * FROM plan_store WHERE `user_id` = '$user_id'";
$sql = "SELECT `plan_store_id`, `name`, `created` FROM plan_store WHERE `user_id` = '$user_id'";
$result = $db->query($sql);
while($stores = $result->fetch_assoc()){
if($result2 = $db->query("SELECT * FROM plan_store_item WHERE `plan_store_id` = '$stores[plan_store_id]'")){
if($result2 = $db->query("SELECT `plan_item_id`, `name`, `price`, `amount` FROM plan_store_item WHERE `plan_store_id` = '$stores[plan_store_id]' ORDER BY pos")){
$stores['items'] = [];
if($result2->num_rows > 0){
@ -252,24 +269,61 @@ function deleteStore($storeID, $storeName, $itemsLength){
function addItem($storeID, $name, $price){
global $db, $user_id;
$insertItemSQL = "INSERT INTO plan_store_item (`plan_store_id`, `name`, `price`) VALUES ((SELECT plan_store_id FROM plan_store WHERE `user_id` = '$user_id' AND `plan_store_id` = '$storeID'), '$name', $price);";
$verifyUserOwnershipSQL = "SELECT plan_store_id FROM plan_store WHERE `user_id` = '$user_id' AND plan_store_id = '$storeID'";
$insertItemSQL = "INSERT INTO plan_store_item (`plan_store_id`, `pos`, `name`, `price`)
SELECT ($verifyUserOwnershipSQL), count(0)+1, '$name', $price FROM plan_store_item WHERE plan_store_id = '$storeID';";
if($db->query($insertItemSQL)){
return $db->insert_id;
}
return false;
}
function remItem($storeID, $itemID, $price){
global $db, $user_id;
$verifyUserOwnershipSQL = "SELECT `plan_store_id` FROM plan_store WHERE `user_id` = '$user_id' AND `plan_store_id` = '$storeID'";
$findRowSql = "SELECT `plan_item_id` FROM plan_store_item WHERE `plan_store_id` = ($verifyUserOwnershipSQL) AND `plan_item_id` = '$itemID'";
$removeItemsql = "DELETE FROM plan_store_item WHERE `plan_item_id` = ($findRowSql) AND `price` = '$price';";
if($db->query($removeItemsql) && mysqli_affected_rows($db) > 0){
return true;
}
return false;
}
function remItem($storeID, $pos, $price){
function moveItem($storeID, $itemID, $afterID){
global $db, $user_id;
$verifyUserOwnershipSQL = "SELECT `plan_store_id` FROM plan_store WHERE `user_id` = '$user_id' AND `plan_store_id` = '$storeID'";
$findRowSql = "SELECT `plan_item_id` FROM plan_store_item WHERE `plan_store_id` = ($verifyUserOwnershipSQL) LIMIT $pos, 1";
$removeItemsql = "DELETE FROM plan_store_item WHERE `plan_item_id` = ($findRowSql) AND `price` = '$price';";
if($db->query($removeItemsql)){
if(mysqli_affected_rows($db) > 0){
$getStoreItemsSQL = "SELECT plan_item_id FROM plan_store_item WHERE `plan_store_id` = ($verifyUserOwnershipSQL) ORDER BY if(pos is \N,1,0), pos;";
if($getStoreItems = $db->query($getStoreItemsSQL)){
$position = 1;
$newQuery = "";
if($afterID == 0){
$newQuery .= "UPDATE plan_store_item SET pos = $position WHERE plan_item_id = $itemID;";
$position++;
}
while($row = $getStoreItems->fetch_assoc()){
if($row['plan_item_id'] != $itemID){
$newQuery .= "UPDATE plan_store_item SET pos = $position WHERE plan_item_id = $row[plan_item_id];";
$position++;
}
if($row['plan_item_id'] == $afterID){
$newQuery .= "UPDATE plan_store_item SET pos = $position WHERE plan_item_id = $itemID;";
$position++;
}
}
if($newQuery != "" && $db->multi_query($newQuery)){
return true;
}
return false;
}
return false;
}

View File

@ -17,13 +17,21 @@ class Store {
html += " <img src='../icon/x-circle.svg' class='removeStore' alt='remove store' data-toggle='tooltip' title='Remove store' tabindex=0 role='button' />";
html += " </div>";
html += " <div class='card-body'>";
html += " <ul class='list-group list-group-flush'>";
html += " <div class='draggable' style='width: 100%; height: 10px; border-width: 1px;'></div> <ul class='list-group list-group-flush'>";
html += " <li class='list-group-item emptyList'>No items added</li>";
html += " </ul>";
html += " <hr>";
html += " <form action='#!' class='form-row input-group input-group-sm addItemForm'>";
html += " <input type='text' class='form-control newItemName' placeholder='New Item Name' data-toggle='tooltip' title='New Item Name' aria-label='New Item Name'>";
html += " <input type='number' class='form-control newItemPrice' value='0' min='0' step='.01' data-toggle='tooltip' title='Price' aria-label='Price'>";
// html += " <input type='text' class='form-control newItemName' placeholder='New Item Name' data-toggle='tooltip' title='New Item Name' aria-label='New Item Name'>";
// html += " <input type='number' class='form-control newItemPrice' value='0' min='0' step='.01' data-toggle='tooltip' title='Price' aria-label='Price'>";
html += " <div class='form-control form-floating'>";
html += " <input type='text' id='newItemName0' class='form-control newItemName' placeholder='New Item Name' aria-label='New Item Name'>";
html += " <label for='newItemName0'>New Item Name</label>";
html += " </div>";
html += " <div class='form-control form-floating'>";
html += " <input type='number' id='newItemPrice0' class='form-control newItemPrice' value='0' min='0' step='.01' aria-label='Price'>";
html += " <label for='newItemPrice0'>Price</label>";
html += " </div>";
html += " <div class='input-group-append'>";
html += " <input type='image' class='form-control addItem' src='../icon/plus.svg' alt='+'>";
html += " </div>";
@ -35,11 +43,10 @@ class Store {
this.selector = $(html).appendTo("#stores");
let that = this;
this.selector.find(".addItemForm").on('submit', function(ev){
this.selector.find(".addItemForm").on('submit', ev => {
ev.preventDefault();
that.addItem($(this).find('.newItemName').val(), $(this).find('.newItemPrice').val())
this.addItem($(ev.target).find('.newItemName').val(), $(ev.target).find('.newItemPrice').val())
.done(json => {
$(this).find('.newItemPrice').val(0);
$(this).find('.newItemName').val("").focus();
@ -122,35 +129,25 @@ class Store {
}
let that = this;
return $.ajax({
method: "POST",
url: "do.php",
data: { plan: 'addItem', storeID: this.storeID, name: text, price: price },
dataType: 'JSON'
})
return ajaxReq({ plan: 'addItem', storeID: this.storeID, name: text, price: price })
.done(json => {
if(handleJsonErrors(json)){
return false;
}
// console.log("addItem return:", json);
return that.addItemHtml(text, price);
})
.fail(handleAjaxErrors);
return that.addItemHtml(text, price, json['data']);
});
}
return false;
}
addItemHtml(text, price){
let amount = 1;
addItemHtml(text, price, itemID, amount){
amount = amount || 1;
try {
price = Number(price);
this.items.push({ text: text, amount: amount, price: price });
this.items.push({ text: text, price: price, itemID: itemID, amount: amount });
let html = "\n";
html += "<li class='list-group-item'>";
html += "<li class='list-group-item draggable' id='item_"+itemID+"' data-itemID='"+itemID+"' draggable='true'>";
html += " <span style='float: left;'>"+text+"</span>";
/*
@ -165,7 +162,7 @@ class Store {
*/
html += " <span class='price'>"+price.toFixed(2)+" kr ";
// html += "<button class='remItem' data-price='"+price+"' data-toggle='tooltip' title='Remove item' style='background: url(../icon/dash.svg); height: 20px; width: 20px;'></button></span>";
html += "<img src='../icon/dash-circle.svg' alt='Remove item' class='remItem' data-price='"+price+"' style='height: 20px; width: 20px; cursor: pointer;'></span>";
html += "<img src='../icon/dash-circle.svg' alt='Remove item' class='remItem' data-itemID='"+itemID+"' data-price='"+price+"' style='height: 20px; width: 20px; cursor: pointer;'></span>";
html += "</li>";
this.selector.find("ul").append(html);
@ -180,6 +177,47 @@ class Store {
return false;
}
remItem(pos, itemID, price){
let that = this;
return $.ajax({
method: "POST",
url: "do.php",
data: { plan: 'remItem', storeID: this.storeID, itemID: itemID, price: price },
dataType: 'JSON'
})
.done(json => {
if(handleJsonErrors(json)){
return false;
}
// console.log("remItem return:", json);
return that.remItemHtml(itemID);
})
.fail(handleAjaxErrors);
}
remItemHtml(itemID){
// this.items.splice(pos,1);
for(let i = 0; i < this.items.length; i++){
if(this.items[i].itemID == itemID){
this.items.splice(i,1);
break;
}
}
this.selector.find('#item_'+itemID).remove();
// $(this.selector).find(".remItem").unbind().each((key, val) => {
// if(key+1 == pos){
// $(val).parent().parent().remove();
// }
// });
this.verify();
return true;
}
setItemAmount(pos, amount){
this.items[pos].amount = amount;
@ -187,57 +225,17 @@ class Store {
return true;
let that = this;
return $.ajax({
method: "POST",
url: "do.php",
data: { plan: 'remItem', storeID: this.storeID, position: pos, price: price },
dataType: 'JSON'
})
.done(json => {
if(handleJsonErrors(json)){
return false;
}
// console.log("remItem return:", json);
return that.remItemHtml(pos);
})
.fail(handleAjaxErrors);
// return ajaxReq({ plan: 'remItem', storeID: this.storeID, position: pos, price: price });
}
remItem(pos, price){
let that = this;
return $.ajax({
method: "POST",
url: "do.php",
data: { plan: 'remItem', storeID: this.storeID, position: pos, price: price },
dataType: 'JSON'
})
.done(json => {
if(handleJsonErrors(json)){
return false;
}
// console.log("remItem return:", json);
return that.remItemHtml(pos);
})
.fail(handleAjaxErrors);
setItemPosition(itemID, afterID){
afterID = afterID || 0;
if(itemID != afterID){
return ajaxReq({ plan: 'moveItem', storeID: this.storeID, itemID: itemID, afterID: afterID });
}
return false;
}
remItemHtml(pos){
this.items.splice(pos,1);
$(this.selector).find(".remItem").unbind().each((key, val) => {
if(key == pos){
$(val).parent().parent().remove();
}
});
this.verify();
return true;
}
verify(){
// UPDATE TOTAL PRICE
let total = 0;
@ -260,7 +258,7 @@ class Store {
if(newValue > 0){
$(this).parent().find('.itemAmount').val(newValue);
// UPDATE VALUE
that.setItemAmount(key, newValue);
that.setItemAmount(key+1, newValue);
}
});
});
@ -271,7 +269,7 @@ class Store {
if(newValue < 100){
$(this).parent().find('.itemAmount').val(newValue);
// UPDATE VALUE
that.setItemAmount(key, newValue);
that.setItemAmount(key+1, newValue);
}
});
});
@ -279,17 +277,61 @@ class Store {
$(val).unbind().on('change', function(){
let newValue = Number( console.log($(this).val()) );
// UPDATE VALUE
that.setItemAmount(key, newValue);
that.setItemAmount(key+1, newValue);
});
});
// DRAGGABLE
let draggingObj = null, dragPos = null;
this.selector.find('.draggable').unbind().on('dragstart dragover dragenter dragleave dragend', ev => {
// this.selector.find('.draggable').hammer().bind('swipe', ev => {
if(ev.type === 'dragover' || ev.type === 'dragenter'){
ev.preventDefault();
// console.log(ev);
$(ev.currentTarget).addClass('drag-over');
dragPos = ev.currentTarget;
}
else if(ev.type === 'dragstart'){
// console.log('drag start');
draggingObj = $(ev.target);
draggingObj.css('opacity', 0.2);
}
else if(ev.type === 'dragleave'){
$('.drag-over').removeClass('drag-over');
}
else if(ev.type === 'dragend'){
// console.log(draggingObj);
if(draggingObj === null){
console.warn('no object to move');
return;
}
// console.log("move object", draggingObj, "to", dragPos);
draggingObj.css('opacity', 1);
if( this.setItemPosition(draggingObj.attr('data-itemID'), $(dragPos).attr('data-itemID')) !== false){
if($(dragPos).attr('data-itemID') == null){
draggingObj.detach().insertAfter('.emptyList');
}
else {
draggingObj.detach().insertAfter(dragPos);
}
draggingObj = null;
}
$('.drag-over').removeClass('drag-over');
}
});
// BIND remove-buttons
$(this.selector).find(".remItem").unbind().each((key, val) => {
let that = this;
$(val).click(function(){
if($(this).hasClass("confirm")){
that.remItem(key, $(this).attr("data-price"));
that.remItem(key+1, $(this).attr('data-itemID'), $(this).attr("data-price"));
try {
$(this).tooltip('dispose');
}
@ -307,25 +349,7 @@ class Store {
});
}
// save(){
// $.ajax({
// method: "POST",
// url: "do.php",
// data: { plan: 'saveStore', storeName: this.title, items: this.items },
// dataType: 'JSON'
// })
// .done(json => {
// // json = JSON.parse(body);
// if(handleJsonErrors(json)){
// return;
// }
// // console.log(json);
// })
// .fail(handleAjaxErrors);
// }
// STORE MANIPULATION
removeStore(){
this.selector.remove();
if(this.storeID != null){
@ -347,53 +371,28 @@ class Store {
}
getStoreID(){
let that = this;
return $.ajax({
method: "POST",
url: "do.php",
data: { plan: 'saveStore', storeName: this.title },
dataType: 'JSON'
})
return ajaxReq({ plan: 'saveStore', storeName: this.title })
.done(json => {
if(handleJsonErrors(json)){
return false;
}
console.log("getStore:", json);
that.storeID = json['data'];
return that.storeID;
})
.fail(handleAjaxErrors);
});
}
rename(newName){
this.title = newName;
if(this.storeID !== null){
return $.ajax({
method: "POST",
url: "do.php",
data: { plan: 'renameStore', storeID: this.storeID, newName: newName },
dataType: 'JSON'
})
.done(json => {
if(handleJsonErrors(json)){
return false;
}
return true;
})
.fail(handleAjaxErrors);
return ajaxReq({ plan: 'renameStore', storeID: this.storeID, newName: newName });
}
//TODO:
console.log("No store-id");
// Return blank ajax as a false
return $.ajax();
}
}
var stores = [];
$("#addStore").click(ev => {
@ -428,7 +427,11 @@ function getStores(){
stores.push(new Store(json.data[store].name, json.data[store].plan_store_id));
for(const item in json.data[store].items){
stores[storeID].addItemHtml(json.data[store].items[item].name, json.data[store].items[item].price);
stores[storeID].addItemHtml(
json.data[store].items[item].name,
json.data[store].items[item].price,
json.data[store].items[item].plan_item_id,
json.data[store].items[item].amount);
}
}
})
@ -437,6 +440,22 @@ function getStores(){
getStores();
function ajaxReq( data ){
return $.ajax({
method: "POST",
url: "do.php",
data: data,
dataType: 'JSON'
})
.done(json => {
if(handleJsonErrors(json)){
return false;
}
return true;
})
.fail(handleAjaxErrors);
}
function handleJsonErrors(json){
if(typeof json.status != "undefined" && json.status != 0){

View File

@ -17,31 +17,36 @@ $sql = "CREATE OR REPLACE TABLE `user` (
`ckey` VARCHAR(220),
`ctime` VARCHAR(220),
PRIMARY KEY (`user_id`),
UNIQUE KEY `user_PK` (`user_email`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
UNIQUE KEY `user_Email` (`user_email`),
UNIQUE KEY `user_md5ID` (`md5_id`)
) DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
INSERT INTO `user` (full_name, user_name, user_email, user_level, pwd)
VALUES ('Admin', 'admin', 'admin@svagard.no', 5, 'cb4f8b8b16b0c41d1ece858548340b40dfe6c7fd7fad1ecce');
CREATE OR REPLACE TABLE plan_store (
plan_store_id INT auto_increment,
`plan_store_id` INT auto_increment,
`user_id` BIGINT(20) NOT NULL,
`name` varchar(100) NOT NULL,
created DATETIME DEFAULT CURRENT_DATE NOT NULL,
updated DATETIME NULL,
`created` DATETIME DEFAULT CURRENT_DATE NOT NULL,
PRIMARY KEY (plan_store_id),
CONSTRAINT plan_store_user_FK FOREIGN KEY (`user_id`) REFERENCES `user`(`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
) DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
INSERT INTO plan_store (user_id, name)
VALUES (1, 'Store 1');
CREATE OR REPLACE TABLE plan_store_item (
plan_item_id INT auto_increment NOT NULL,
plan_store_id INT NOT NULL,
`plan_item_id` INT auto_increment NOT NULL,
`plan_store_id` INT NOT NULL,
`pos` tinyint(3) unsigned,
`name` varchar(200) NOT NULL,
price decimal(8,2) NOT NULL,
`price` decimal(8,2) NOT NULL,
`amount` tinyint(3) unsigned DEFAULT 1,
PRIMARY KEY (plan_item_id),
CONSTRAINT plan_store_item_FK FOREIGN KEY (plan_store_id) REFERENCES `plan_store`(`plan_store_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
) DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
INSERT INTO plan_store_item (plan_store_id, name, price)
VALUES (1, 'Kake', 49.90),
(1, 'Kaffe', 24.90);