Keyboard-navigation ++
parent
370055e3cc
commit
2fe526da09
|
@ -0,0 +1,13 @@
|
|||
[system]
|
||||
template=default
|
||||
projectRoot=
|
||||
debug=true
|
||||
|
||||
[security]
|
||||
pepper=IAmBadAtSecurity
|
||||
|
||||
[database]
|
||||
host=localhost
|
||||
user=
|
||||
pass=
|
||||
database=
|
|
@ -4,6 +4,15 @@
|
|||
|
||||
define("DATABASE", Config::get('database', "database"));
|
||||
|
||||
const OBLIGATORY = 0x01;
|
||||
|
||||
abstract class DataTypes {
|
||||
const TEXT = 0x01;
|
||||
const INTEGER = 0x02;
|
||||
const BOOLEAN = 0x03;
|
||||
const DOUBLE = 0x04;
|
||||
}
|
||||
|
||||
abstract class Model {
|
||||
protected static $database = DATABASE;
|
||||
protected static $table;
|
||||
|
@ -15,15 +24,40 @@ abstract class Model {
|
|||
|
||||
public function __construct($id = 0){
|
||||
foreach (static::$fields as $field => $type){
|
||||
switch ($type){
|
||||
case "INT":
|
||||
$this->{$field} = 0;
|
||||
break;
|
||||
case "VARCHAR":
|
||||
case "TEXT":
|
||||
default:
|
||||
$this->{$field} = "";
|
||||
break;
|
||||
|
||||
if(is_array($type)){
|
||||
$datatype = $type[0];
|
||||
$obligatory = isset($type[1]) && $type[1] == OBLIGATORY;
|
||||
|
||||
// Skip default-value if field is not obligatory
|
||||
if(!$obligatory){ continue; }
|
||||
|
||||
switch ($datatype){
|
||||
case DataTypes::INTEGER:
|
||||
case DataTypes::BOOLEAN:
|
||||
$this->$field = 0;
|
||||
break;
|
||||
case DataTypes::DOUBLE:
|
||||
$this->$field = 0.00;
|
||||
break;
|
||||
case DataTypes::TEXT:
|
||||
default:
|
||||
$this->$field = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// DEPRECATED METHOD:
|
||||
switch ($type){
|
||||
case "INT":
|
||||
$this->{$field} = 0;
|
||||
break;
|
||||
case "VARCHAR":
|
||||
case "TEXT":
|
||||
default:
|
||||
$this->{$field} = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,9 +80,9 @@ abstract class Model {
|
|||
} catch (DatabaseException $e) {
|
||||
$this->errors[] = $e;
|
||||
}
|
||||
}
|
||||
|
||||
$this->initialValues = $this->asArray();
|
||||
$this->initialValues = $this->asArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,19 +192,26 @@ abstract class Model {
|
|||
|
||||
$newFields = [];
|
||||
$newValues = [];
|
||||
$allExceptFirstFields = [];
|
||||
$allExceptFirstValues = [];
|
||||
$allExceptFirstField = [];
|
||||
$allExceptFirstValue = [];
|
||||
|
||||
$fieldCount = 0;
|
||||
foreach(static::$fields as $field => $type){
|
||||
$obligatory = !is_array($type) || isset($type[1]) && $type[1] == OBLIGATORY;
|
||||
|
||||
// Skip non-obligatory fields if they are not set.
|
||||
if(is_array($type) && !isset($this->{ $field }) && !$obligatory){
|
||||
continue;
|
||||
}
|
||||
|
||||
$currValue = $this->{ $field };
|
||||
if(!empty($this->initialValues) && $currValue != $this->initialValues[$field]){
|
||||
if(!empty($this->initialValues) && (!$obligatory || $currValue != $this->initialValues[$field])){
|
||||
$newFields[] = $field;
|
||||
$newValues[] = $currValue;
|
||||
}
|
||||
if($fieldCount > 0){
|
||||
$allExceptFirstFields[] = $field;
|
||||
$allExceptFirstValues[] = $currValue;
|
||||
$allExceptFirstField[] = $field;
|
||||
$allExceptFirstValue[] = $currValue;
|
||||
}
|
||||
|
||||
$fieldCount++;
|
||||
|
@ -186,12 +227,12 @@ abstract class Model {
|
|||
$updateSets[] = "`$field` = ?";
|
||||
}
|
||||
|
||||
DB::query(sprintf('UPDATE %s.%s SET %s WHERE %s LIMIT 1', static::$database, static::$table, implode(', ', $updateSets), "`" . array_keys(static::$fields)[0] . "` = ?"), array_merge($newValues, [$this->{array_keys(static::$fields)[0]}]));
|
||||
DB::queryPreview(sprintf('UPDATE %s.%s SET %s WHERE %s LIMIT 1', static::$database, static::$table, implode(', ', $updateSets), "`" . array_keys(static::$fields)[0] . "` = ?"), array_merge($newValues, [$this->{array_keys(static::$fields)[0]}]));
|
||||
}
|
||||
else {
|
||||
$sql = sprintf('INSERT INTO %s.%s (%s) VALUE (%s)', static::$database, static::$table, implode(', ', $newFields), implode(', ', array_fill(0, count($newFields), '?')));
|
||||
elseif(!empty($allExceptFirstValue)){
|
||||
$sql = sprintf('INSERT INTO %s.%s (%s) VALUE (%s)', static::$database, static::$table, implode(', ', $allExceptFirstField), implode(', ', array_fill(0, count($allExceptFirstField), '?')));
|
||||
|
||||
DB::query($sql, $newValues);
|
||||
DB::query($sql, $allExceptFirstValue);
|
||||
$insertid = DB::insert_id();
|
||||
$this->{ array_keys(static::$fields)[0] } = $insertid;
|
||||
$this->isLoaded = true;
|
||||
|
@ -201,6 +242,14 @@ abstract class Model {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function delete(){
|
||||
if($this->isLoaded()){
|
||||
Utils::debug($this);
|
||||
exit;
|
||||
$sql = "DELETE FROM ";
|
||||
}
|
||||
}
|
||||
|
||||
public function asArray(): array {
|
||||
$arr = [];
|
||||
foreach ($this as $key => $val){
|
||||
|
|
|
@ -12,19 +12,9 @@ class PlanStore extends Model {
|
|||
"state" => [ 'planning', 'shopping', 'closed' ]
|
||||
];
|
||||
|
||||
public static function getUserSpaces(){
|
||||
$spaces = array_merge(
|
||||
static::get([ 'owner_id' => Auth::currentUserId() ]),
|
||||
PlanSpaceMember::get([ 'member_id' => Auth::currentUserId() ])
|
||||
);
|
||||
public $items = [];
|
||||
|
||||
foreach ($spaces as $s){
|
||||
if($s->space_name == ""){
|
||||
$spaceOwner = User::get(['user_id' => $s->owner_id])[0];
|
||||
$s->space_name = $spaceOwner->full_name != "" ? sprintf('%ss space', $spaceOwner->full_name ) : "A users space";;
|
||||
}
|
||||
}
|
||||
|
||||
return $spaces;
|
||||
public function getItems(){
|
||||
$this->items = PlanStoreItem::get(['plan_store_id' => $this->plan_store_id]);
|
||||
}
|
||||
}
|
|
@ -2,28 +2,39 @@
|
|||
|
||||
//namespace models;
|
||||
|
||||
class PlanSpace extends Model {
|
||||
protected static $table = "plan_space";
|
||||
class PlanStoreItem extends Model {
|
||||
protected static $table = "plan_store_item";
|
||||
protected static $fields = [
|
||||
"space_id" => "INT",
|
||||
"space_name" => "VARCHAR",
|
||||
"owner_id" => "INT",
|
||||
"space_type" => [ 'STORE', 'CHECK', 'CALORIES' ]
|
||||
"plan_item_id" => [ Datatypes::INTEGER, OBLIGATORY ],
|
||||
"plan_store_id" => [ Datatypes::INTEGER, OBLIGATORY ],
|
||||
"pos" => [ Datatypes::INTEGER ],
|
||||
"name" => [ Datatypes::TEXT, OBLIGATORY ],
|
||||
"price" => [ Datatypes::DOUBLE, OBLIGATORY ],
|
||||
"amount" => [ DataTypes::DOUBLE ],
|
||||
"checked" => [ DataTypes::BOOLEAN ]
|
||||
];
|
||||
|
||||
public static function getUserSpaces(){
|
||||
$spaces = array_merge(
|
||||
static::get([ 'owner_id' => Auth::currentUserId() ]),
|
||||
PlanSpaceMember::get([ 'member_id' => Auth::currentUserId() ])
|
||||
);
|
||||
|
||||
foreach ($spaces as $s){
|
||||
if($s->space_name == ""){
|
||||
$spaceOwner = User::get(['user_id' => $s->owner_id])[0];
|
||||
$s->space_name = $spaceOwner->full_name != "" ? sprintf('%ss space', $spaceOwner->full_name ) : "A users space";;
|
||||
public function __set($name, $value){
|
||||
if(in_array($name, array_keys(static::$fields))){
|
||||
$fieldDesc = static::$fields[$name];
|
||||
switch ($fieldDesc[0]){
|
||||
case DataTypes::DOUBLE:
|
||||
$this->$name = (double) $value;
|
||||
break;
|
||||
case DataTypes::INTEGER:
|
||||
$this->$name = (int) $value;
|
||||
break;
|
||||
case DataTypes::BOOLEAN:
|
||||
$this->$name = (bool) $value;
|
||||
break;
|
||||
case DataTypes::TEXT:
|
||||
default:
|
||||
$this->$name = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $spaces;
|
||||
else {
|
||||
$this->$name = $value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
namespace models;
|
||||
|
||||
class Product extends Model {
|
||||
// protected static $database = "i18n";
|
||||
protected static $table = "product";
|
||||
protected static $fields = [
|
||||
"product_id" => "INT",
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
<link rel='stylesheet' href='{{ pr }}/css/bootstrap.min.css' type='text/css' />
|
||||
<link rel='stylesheet' href='{{ pr }}/css/index.css' type='text/css' />
|
||||
|
||||
<link rel='apple-touch-icon' sizes='180x180' href='{{ pr }}/apple-touch-icon.png'>
|
||||
<link rel='icon' type='image/png' sizes='32x32' href='{{ pr }}/favicon-32x32.png'>
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='{{ pr }}/favicon-16x16.png'>
|
||||
<link rel='apple-touch-icon' sizes='180x180' href='{{ pr }}/favicon/apple-touch-icon.png'>
|
||||
<link rel='icon' type='image/png' sizes='32x32' href='{{ pr }}/favicon/favicon-32x32.png'>
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='{{ pr }}/favicon/favicon-16x16.png'>
|
||||
<link rel='manifest' href='{{ pr }}/site.webmanifest'>
|
||||
<link rel='mask-icon' href='{{ pr }}/favicon/safari-pinned-tab.svg' color='#5bbad5'>
|
||||
<meta name='msapplication-TileColor' content='#da532c'>
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
|
||||
Redirect Permanent /favicon.ico /favicon.png
|
||||
Redirect Permanent /favicon.ico /favicon/favicon.ico
|
|
@ -27,10 +27,10 @@ abstract class Api {
|
|||
$this->message = $e;
|
||||
}
|
||||
|
||||
if(empty($this->data)){
|
||||
$this->success = false;
|
||||
$this->message = "No data"; // Todo: Specify missing fields in output
|
||||
}
|
||||
// if(empty($this->data)){
|
||||
// $this->success = false;
|
||||
// $this->message = "No data"; // Todo: Specify missing fields in output
|
||||
// }
|
||||
|
||||
if($this->success){
|
||||
try {
|
||||
|
@ -51,8 +51,8 @@ abstract class Api {
|
|||
$returns['data'] = $this->result ?? $this->message;
|
||||
|
||||
if(DEBUG){
|
||||
$returns['debug'][] = debug_backtrace();
|
||||
$returns['debug']["methods"] = $this->methods;
|
||||
// $returns['debug'][] = debug_backtrace();
|
||||
// $returns['debug']["methods"] = $this->methods;
|
||||
}
|
||||
|
||||
echo json_encode( $returns );
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
class AddStoreItemApi extends Api {
|
||||
|
||||
protected $methods = [
|
||||
"POST" => [
|
||||
[
|
||||
"name" => "store_id",
|
||||
"keyword" => "store_id",
|
||||
"type" => VERIFY_STRING
|
||||
],
|
||||
[
|
||||
"name" => "name",
|
||||
"keyword" => "name",
|
||||
"type" => VERIFY_STRING
|
||||
],
|
||||
[
|
||||
"name" => "price",
|
||||
"keyword" => "price",
|
||||
"type" => VERIFY_STRING
|
||||
],
|
||||
[
|
||||
"name" => "amount",
|
||||
"keyword" => "amount",
|
||||
"type" => VERIFY_STRING
|
||||
],
|
||||
[
|
||||
"name" => "product_id",
|
||||
"keyword" => "store_id",
|
||||
"type" => VERIFY_STRING
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
function execute(){
|
||||
try {
|
||||
$storeitem = new PlanStoreItem();
|
||||
$storeitem->plan_store_id = $this->data['store_id'];
|
||||
$storeitem->name = $this->data['name'];
|
||||
$storeitem->price = $this->data['price'] ?? 0;
|
||||
$storeitem->amount = $this->data['amount'] ?? 1;
|
||||
|
||||
$this->result = $storeitem->save();
|
||||
|
||||
$this->success = true;
|
||||
$this->message = "OK";
|
||||
}
|
||||
catch (DatabaseException $e){
|
||||
$this->success = false;
|
||||
$this->message = "Error: " . $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$request = new AddStoreItemApi();
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
class AddStoreItemApi extends Api {
|
||||
|
||||
protected $methods = [
|
||||
"POST" => [
|
||||
[
|
||||
"name" => "store_id",
|
||||
"keyword" => "store_id",
|
||||
"type" => VERIFY_STRING
|
||||
],
|
||||
[
|
||||
"name" => "plan_item_id",
|
||||
"keyword" => "plan_item_id",
|
||||
"type" => VERIFY_STRING
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
function execute(){
|
||||
try {
|
||||
$storeitem = new PlanStoreItem($this->data['plan_item_id']);
|
||||
|
||||
if($storeitem->isLoaded()){
|
||||
$storeitem->delete();
|
||||
}
|
||||
|
||||
$this->success = true;
|
||||
$this->message = "OK";
|
||||
}
|
||||
catch (DatabaseException $e){
|
||||
$this->success = false;
|
||||
$this->message = "Error: " . $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$request = new AddStoreItemApi();
|
||||
|
|
@ -24,6 +24,7 @@ class GetStoresApi extends Api {
|
|||
*/
|
||||
function execute(){
|
||||
$stores = PlanStore::get(['space_id' => $this->data['space'] ]);
|
||||
array_map(function($store){ $store->getItems(); }, $stores);
|
||||
|
||||
$this->result = $stores;
|
||||
|
||||
|
|
|
@ -74,6 +74,14 @@ body {
|
|||
background-color: #3331;
|
||||
}
|
||||
|
||||
.pending {
|
||||
color: #6c757d;
|
||||
font-weight: lighter;
|
||||
}
|
||||
.error {
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
|
||||
#stores .card-header {
|
||||
font-weight: bold;
|
||||
height: 38px;
|
||||
|
@ -116,13 +124,13 @@ span.recipeItemAmount::after {
|
|||
}
|
||||
|
||||
|
||||
.list-group-item:focus-within .itemButtons,
|
||||
.list-group-item:hover .itemButtons
|
||||
.list-group-item:focus-within .itemButtons
|
||||
/*.list-group-item:hover .itemButtons*/
|
||||
{
|
||||
height: 45px;
|
||||
}
|
||||
ul.storePlanState .list-group-item:focus-within .itemAmountText,
|
||||
ul.storePlanState .list-group-item:hover .itemAmountText
|
||||
ul.storePlanState .list-group-item:focus-within .itemAmountText
|
||||
/*ul.storePlanState .list-group-item:hover .itemAmountText*/
|
||||
{
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -6,7 +6,7 @@ require_once '../../Router.php';
|
|||
header("Content-Type: application/json");
|
||||
|
||||
//if(!checkLogin()){
|
||||
if(Auth::checkLogin()){
|
||||
if(!Auth::checkLogin()){
|
||||
returns("Not logged in",2);
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ $returns = [];
|
|||
foreach([$_GET, $_POST] as $request){
|
||||
if(!empty($request)){
|
||||
foreach($request as $key => $value){
|
||||
if(($data[$key] = filter($value)) === false){
|
||||
if(($data[$key] = Utils::filter($value)) === false){
|
||||
print_r($value);
|
||||
echo "Failed to sanitize: `".$key."`: ".$value." \t-\t type: ".gettype($value)."\n";
|
||||
}
|
||||
|
|
194
www/plan/plan.js
194
www/plan/plan.js
|
@ -60,10 +60,9 @@ class Store {
|
|||
this.selector.find(".addItemForm").on('submit', ev => {
|
||||
ev.preventDefault();
|
||||
|
||||
this.addItem($(ev.target).find('.newItemName').val(), $(ev.target).find('.newItemPrice').val()).done(json => {
|
||||
this.selector.find('.newItemPrice').val(0);
|
||||
this.selector.find('.newItemName').val("").focus();
|
||||
});
|
||||
this.addItem($(ev.target).find('.newItemName').val(), $(ev.target).find('.newItemPrice').val());
|
||||
this.selector.find('.newItemPrice').val(0);
|
||||
this.selector.find('.newItemName').val("").focus();
|
||||
});
|
||||
|
||||
|
||||
|
@ -292,10 +291,25 @@ class Store {
|
|||
return $.ajax();
|
||||
}
|
||||
|
||||
let newItemElement = this.addItemHtml(text, price, 0, amount);
|
||||
|
||||
let that = this;
|
||||
return ajaxReq({ plan: 'addItem', storeID: this.storeID, name: text, price: price, amount: amount })
|
||||
.done(json => {
|
||||
return that.addItemHtml(text, price, json['data'], amount);
|
||||
return qAPI('plan/storeitem/add', 'POST', {
|
||||
store_id: this.storeID,
|
||||
name: text,
|
||||
price: price,
|
||||
amount: amount
|
||||
}).done(json => {
|
||||
console.log(json);
|
||||
newItemElement.removeClass('pending');
|
||||
|
||||
if(json.status){
|
||||
this.itemsObj[json.result] = { text: text, price: price, itemID: json.result, amount: amount };
|
||||
newItemElement.data('itemid', json.result);
|
||||
}
|
||||
else {
|
||||
newItemElement.addClass('error');
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -307,12 +321,26 @@ class Store {
|
|||
|
||||
text = insertLinks(text);
|
||||
|
||||
if(typeof this.pending === "undefined"){
|
||||
this.pending = [];
|
||||
}
|
||||
|
||||
let pending = false;
|
||||
if(itemID === 0){
|
||||
let nextPendingItem = this.pending.length;
|
||||
itemID = "p" + nextPendingItem;
|
||||
this.pending.push(itemID);
|
||||
pending = true;
|
||||
}
|
||||
|
||||
try {
|
||||
price = Number(price);
|
||||
this.itemsObj[itemID] = { text: text, price: price, itemID: itemID, amount: amount, checked: checked };
|
||||
|
||||
|
||||
let pendingClass = pending ? " pending" : "";
|
||||
|
||||
let html =
|
||||
"<li tabindex='0' class='list-group-item draggable"+(checked?' checkedItem':'')+"' id='item_"+itemID+"' data-itemid='"+itemID+"' style='height: 100%; min-width: 200px;'>" +
|
||||
"<li tabindex='0' class='list-group-item draggable"+(checked?' checkedItem':'') + pendingClass + "' id='item_"+itemID+"' data-itemid='"+itemID+"' style='height: 100%; min-width: 200px;'>" +
|
||||
" <div style='display: flex;'>" + // draggable='true'
|
||||
//" <span style='float: left; margin-right: 0px; width: 32px; margin-left: -22px;'><svg xmlns='http://www.w3.org/2000/svg' fill='gray' class='bi bi-grip-vertical' viewBox='0 0 16 16' height='32' width='32'> <path d='M7 2a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM7 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM7 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm-3 3a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm-3 3a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0z'></path></svg></span>" +
|
||||
" <div style='flex: 10px; display: none; margin-right: 5px;' class='checkItems'><input type='checkbox'"+(checked?" checked":"")+"></div>" +
|
||||
|
@ -320,7 +348,7 @@ class Store {
|
|||
|
||||
" <div style='flex: 35%;' class='priceWrapper'>" +
|
||||
" <span class='price'>"+(price*amount).toFixed(2)+"</span>" +
|
||||
" <img src='../icon/dash-circle.svg' alt='Remove item' class='remItem ariaButton' data-itemID='"+itemID+"' data-price='"+price+"' style='margin-right: 3px;' tabindex='0' role='button'>" +
|
||||
" <img src='../icon/dash-circle.svg' alt='Remove item' class='remItem ariaButton' data-price='"+price+"' style='margin-right: 3px;' tabindex='0' role='button'>" +
|
||||
" </div>" +
|
||||
" </div>" +
|
||||
|
||||
|
@ -349,7 +377,8 @@ class Store {
|
|||
|
||||
"</li>\n";
|
||||
|
||||
this.selector.find("ul.storeItems").append(html);
|
||||
let newItem = $(html);
|
||||
newItem.appendTo( this.selector.find("ul.storeItems") );
|
||||
this.selector.find(".emptyList").hide();
|
||||
this.selector.find('#item_'+itemID+' .checkItems input').off().on('click', ev => {
|
||||
if(this.checkItem(itemID)){
|
||||
|
@ -359,7 +388,7 @@ class Store {
|
|||
}
|
||||
});
|
||||
this.verify();
|
||||
return true;
|
||||
return newItem;
|
||||
}
|
||||
catch(e){
|
||||
alert("Something failed. Try again.");
|
||||
|
@ -389,29 +418,38 @@ class Store {
|
|||
return newChecked;
|
||||
}
|
||||
|
||||
remItem(itemID, price){
|
||||
remItem(itemID){
|
||||
let that = this;
|
||||
|
||||
if(this.state === "planning"){
|
||||
// if(this.state === "planning"){
|
||||
|
||||
return ajaxReq({ plan: 'remItem', storeID: this.storeID, itemID: itemID, price: price })
|
||||
.done(json => {
|
||||
// console.log("remItem return:", json);
|
||||
return that.remItemHtml(itemID);
|
||||
this.remItemHtml(itemID);
|
||||
|
||||
// return ajaxReq({ plan: 'remItem', storeID: this.storeID, itemID: itemID, price: price })
|
||||
return qAPI('plan/storeitem/delete', 'POST',{ store_id: this.storeID, plan_item_id: itemID })
|
||||
.always(json => {
|
||||
if(json.success){
|
||||
return that.remItemHtml(itemID, true);
|
||||
}
|
||||
else {
|
||||
setTimeout(() => {
|
||||
that.selector.find('#item_'+itemID).show().addClass('error');
|
||||
},10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
remItemHtml(itemID){
|
||||
// for(let i = 0; i < this.items.length; i++){
|
||||
// if(this.items[i].itemID === itemID){
|
||||
// this.items.splice(i,1);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
if(delete this.itemsObj[itemID]){
|
||||
this.selector.find('#item_'+itemID).remove();
|
||||
}
|
||||
remItemHtml(itemID, remove){
|
||||
remove = remove || false;
|
||||
|
||||
if(remove){
|
||||
if(delete this.itemsObj[itemID]){
|
||||
this.selector.find('#item_'+itemID).remove();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.selector.find('#item_'+itemID).fadeOut();
|
||||
}
|
||||
|
||||
this.verify();
|
||||
|
@ -571,7 +609,7 @@ class Store {
|
|||
// console.log("remItem", $(this).hasClass("confirm"), $(this));
|
||||
if($(this).hasClass("confirm")){
|
||||
|
||||
that.remItem($(this).attr('data-itemid'), $(this).attr("data-price"));
|
||||
that.remItem($(this).closest('.list-group-item').data('itemid'), $(this).attr("data-price"));
|
||||
try {
|
||||
$(this).tooltip('dispose');
|
||||
}
|
||||
|
@ -760,10 +798,6 @@ function qAPI( path, method, data ){
|
|||
method = method || 'GET';
|
||||
data = data || {};
|
||||
|
||||
// if(typeof spaceID !== "undefined" && spaceID !== 0){
|
||||
// data.space = spaceID;
|
||||
// }
|
||||
|
||||
return $.ajax({
|
||||
method: method,
|
||||
url: "/api/v2/" + path,
|
||||
|
@ -792,10 +826,10 @@ function handleJsonErrors(json){
|
|||
}
|
||||
function handleAjaxErrors(jqxhr, textStatus, error){
|
||||
if(textStatus === "parsererror" && jqxhr.responseText !== ""){
|
||||
alert("An error occured:\n"+jqxhr.responseText);
|
||||
alert("An error occurred:\n"+jqxhr.responseText);
|
||||
}
|
||||
else {
|
||||
alert("An error occured:\n"+error);
|
||||
alert("An error occurred:\n"+error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -827,4 +861,90 @@ function insertLinks(text){
|
|||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
// KEYBOARD ACCESSIBILITY
|
||||
class MoveFocus {
|
||||
constructor(elem) {
|
||||
this.elem = elem;
|
||||
// Note: Glitchy with duplicated IDs - which there currently are as of 2022-10-01.
|
||||
this.tabindex = $.map( $("*[tabindex]"), (item, key) => { if( this.elem.is(item) ){ return key; } });
|
||||
}
|
||||
|
||||
prev = () => {
|
||||
if(typeof this.tabindex === "object" && this.tabindex[0] > 0){
|
||||
$("*[tabindex]")[ this.tabindex[0] - 1 ].focus();
|
||||
}
|
||||
}
|
||||
next = () => {
|
||||
if(typeof this.tabindex === "object" && this.tabindex[0] >= 0){
|
||||
$("*[tabindex]")[ this.tabindex[0] + 1 ].focus();
|
||||
}
|
||||
}
|
||||
|
||||
prevStore = () => {
|
||||
this.elem.closest('.store').prev().find('.list-group-item:not(.emptyList)').first().focus();
|
||||
}
|
||||
nextStore = () => {
|
||||
this.elem.closest('.store').next().find('.list-group-item:not(.emptyList)').first().focus();
|
||||
}
|
||||
nextForm = () => {
|
||||
this.elem.parent().parent().find('.addItemForm input').first().focus();
|
||||
}
|
||||
prevItemRow = () => {
|
||||
this.elem.prev('.list-group-item').focus();
|
||||
}
|
||||
nextItemRow = () => {
|
||||
this.elem.next('.list-group-item').focus();
|
||||
}
|
||||
}
|
||||
|
||||
$( document ).on( 'keydown', '.store', ev => {
|
||||
// if(ev.target === ev.currentTarget){
|
||||
let target = $(ev.target);
|
||||
if(ev.target.tagName === "INPUT"){ return; }
|
||||
|
||||
let move = new MoveFocus(target);
|
||||
|
||||
let key = "";
|
||||
key += ev.ctrlKey ? "ctrl+" : '';
|
||||
key += ev.shiftKey ? "shift+" : '';
|
||||
key += ev.key.toLowerCase();
|
||||
|
||||
switch (key){
|
||||
case 'arrowup':
|
||||
case 'w':
|
||||
move.prevItemRow()
|
||||
break;
|
||||
case 'arrowleft':
|
||||
case 'a':
|
||||
move.prev();
|
||||
break;
|
||||
case 'arrowdown':
|
||||
case 's':
|
||||
move.nextItemRow();
|
||||
break;
|
||||
case 'arrowright':
|
||||
case 'd':
|
||||
move.next();
|
||||
break;
|
||||
case 'shift+arrowleft':
|
||||
case 'shift+a':
|
||||
move.prevStore();
|
||||
break;
|
||||
case 'shift+arrowright':
|
||||
case 'shift+d':
|
||||
move.nextStore();
|
||||
break;
|
||||
case 'shift+s':
|
||||
case 'shift+arrowdown':
|
||||
move.nextForm();
|
||||
break;
|
||||
default:
|
||||
// console.log(key, ev.key, ev);
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
// }
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue