listojs
Version:
a package for restaurant management
774 lines (717 loc) • 30.1 kB
JavaScript
/* listo_guest.js
14.11.2019 5:14:31,91 */
/*-----------------------------------*/
/* guest.js */
module = "guest.js";
release = "none";
// functional parameters:
let idtable = 0;
let idwaiter = 0;
let guestCount = 0;
let loginState = parseInt(sessionStorage.getItem("customerLoginCheck"), 10);
const mandatoryIds = ["box0", "box1", "box2", "box3", "orderForm", "main-content", "languageDiv", "idbill", "idwaiter", "idtable",
"username", "userpass", "addProductText", "inputSelectRole", "waiterName", "tableName", "customerLoginCheck", "text1",
"text28", "text30", "text33", "text47", "text72", "text92", "text603", "text714", "text741", "closeOrder", "confirmOrder",
"startOrder", "openMenu", "showCart", "showStatus"];
// visual parameters:
let scrollingCustomized = false;
let productDetailsCustomized = false;
let waiterName;
let tableName;
let scrollingDuration = 250;
let shownCategoryLevel = 1;
/*
Include this for language-selection into a <script>-tag at top of every customer-theme:
const updateGuestTexts = function () {
showSubCategories(0, "");
showCart();
showDeliveryStatus();
setGuestTexts();
};
const languageSelect = {
pre: "<div><span><p>",
suf: "</p></span></div>",
callback: updateGuestTexts,
styleClass:"",
targetDiv:"main-content",
languages: [{
id: "en",
name: "English"
}, {
id: "de",
name: "Deutsch"
}, {
id: "it",
name: "Italiano"
}]
};
*/
function checkLayoutPermission() {
apiCall_listorante_public_layouts(function (data) {
let allowed = false;
console.log("Check permission of use for theme-Id '" + themeID + "':");
for (let i = 0; i < data.count; i++) {
let checkedID = parseInt(data.rows[i].s[0], 10);
console.log((1 + i) + ": Customer has permission to use theme '" + checkedID + "': " + JSON.stringify(data.rows[i].s));
if (checkedID === parseInt(themeID, 10)) {
console.log("Permission for theme '" + themeID + "' found.");
allowed = true;
//break;
}
}
if (!allowed) {
const txt = "Sorry! The customer with CID '" + customerID + "' is not allowed to use this theme...";
document.getElementsByTagName("body")[0].innerHTML = "<h1>" + txt + "</h1>";
console.error(txt);
}
}, function (err) {
showHttpErrorMessage("main-content", err);
})
}
checkLayoutPermission();
function verifyMandatoryClass(id, className) {
try {
const classNameAttribute = document.getElementById(id).getAttribute("class");
if (!classNameAttribute) {
console.error("Element with mandatory id '" + id + "' has no class-attribute (Requires '" + className + "')");
return false;
}
const coIdx = classNameAttribute.indexOf(className);
if (coIdx < 0) {
console.error("Element with mandatory id '" + id + "' is missing the mandatory class-attribute '" + className + "'");
return false;
}
} catch (err) {
console.error("Element with mandatory id '" + id + "' is missing or not properly configured: " + err.message);
return false;
}
return true;
}
function verifyTheme() {
debug("Checking theme...", release);
showRelease(module);
let check;
let check2;
let check3;
let ok = true;
for (let id = 0; id < mandatoryIds.length; id++) {
try {
check = document.getElementById(mandatoryIds[id]).innerText;
check2 = document.getElementById(mandatoryIds[id]).innerHTML;
check3 = document.getElementById(mandatoryIds[id]).value;
debug2(module, "Check-results", release, [mandatoryIds[id], check, check2, check3]);
if (mandatoryIds[id] === "inputSelectRole") {
if (parseInt(check, 10) !== 3) {
console.error("ERROR: Mandatory element with id '" + mandatoryIds[id] + "' must have value 3.");
ok = false;
}
}
} catch (err) {
ok = false;
if (!check) {
console.error("ERROR: Mandatory element with id '" + mandatoryIds[id] + "' is missing in index.html." +
JSON.stringify(err));
} else {
console.error("Unexpected error: " + JSON.stringify(err));
console.error("Mandatory element with id '" + mandatoryIds[id] + "' is probably missing in index.html.");
}
}
}
ok = ok && verifyMandatoryClass("closeOrder", "close-order") &&
verifyMandatoryClass("confirmOrder", "submit-order") &&
verifyMandatoryClass("startOrder", "start-order") &&
verifyMandatoryClass("openMenu", "get-products") &&
verifyMandatoryClass("showCart", "show-cart") &&
verifyMandatoryClass("showStatus", "show-deliverystatus");
if (!ok) {
console.error("Errors in this theme were detected. It is not properly configured and must not be used.");
}
}
verifyTheme();
function setLoginState(state) {
/*
* loginState:
* 0/NaN : user is not logged in
* 1 : user is logged in
* 2 : there are items in the list
* */
sessionStorage.setItem("customerLoginCheck", state);
loginState = parseInt(sessionStorage.getItem("customerLoginCheck"), 10);
}
function scrollToCustomerBox(divBoxName) {
if (scrollingCustomized && typeof guestScroll === "function") {
guestScroll(divBoxName);
} else {
standardScroll(divBoxName);
}
};
function standardScroll(boxName) {
$('html, body').animate({
scrollTop: $("#" + boxName).offset().top
}, scrollingDuration);
};
function setGuestTexts() {
debug("Setting texts...", release);
setElemText(1);
setElemText(6);
setElemText(12);
setElemText(30);
setElemText(33);
setElemText(35);
setElemText(47);
setElemText(72);
setElemText(92);
setElemText(603);
setElemText(714);
setElemText(741);
apiCall_listorante_public_settings("ADD_PRODUCT_TEXT_" + locale, function (data) {
const addHtml = document.getElementById("addProductText").innerHTML;
if (addHtml) {
$("#addProductText").html(addHtml);
debug("addProductText=" + addHtml.toString(), release);
} else {
const addTxt = document.getElementById("addProductText").innerText;
if (addTxt > 3) {
$("#addProductText").text(addTxt);
debug("addProductText=" + addTxt, release);
} else {
try {
$("#addProductText").text(data.rows[0].s[0]);
} catch (err) {
try {
$("#addProductText").text(getTextById(740));
} catch (err2) {
try {
$("#addProductText").text("+++");
} catch (err3) {
debug("#addProductText not found.", release, err, err2, err3);
}
}
}
}
}
}, function (err) {
debug("Setting 'ADD_PRODUCT_TEXT_" + locale + "' could not be retrieved", release, err);
$("#addProductText").text("+");
});
}
apiCall_listorante_public_settings("SHOWCATLEV", function (data) {
shownCategoryLevel = data.rows[0].s[0];
debug2(module, "shown category level", RELEASE, [shownCategoryLevel]);
}, function (err) {
debug2(module, "could not retrieve setting 'SHOWCATLEV'", [err]);
});
setGuestTexts();
document.getElementById("username").hidden = true;
function guest0_categories(category, categoryText, data) {
let box0 = categoryHeader();
let errArray = [];
let levelCount = 0;
for (let i = 0; i < data.count; i++) {
if (parseInt(data.rows[i].s[3], 10) <= parseInt(shownCategoryLevel, 10)) {
levelCount++;
}
}
for (let i = 0; i < data.count; i++) {
try {
if (parseInt(data.rows[i].s[3], 10) <= parseInt(shownCategoryLevel, 10)) {
box0 += categoryItem(i, data.rows[i].s, levelCount);
}
} catch (err) {
debug2("guest", "Erroneous value in row " + i + " of category-data.", release, [data.rows[i].s]);
for (let j = 0; j < data.rows[i].s.length; j++) {
try {
errArray[j] = data.rows[i].s[j];
} catch (err2) {
errArray[j] = "";
}
}
try {
box0 += categoryItem(i, errArray, levelCount);
} catch (err3) {
debug2("guest", "Erroneous value in row " + i + " could not be replaced.", release, [err3]);
}
}
}
box0 += categoryBottom();
return box0;
}
;
function guest1_product(data) {
let box1 = productsHeader();
let errArray = [];
for (let i = 0; i < data.count; i++) {
try {
box1 += productItem(i, data.rows[i].s, data.count);
} catch (err) {
debug2("guest", "Erroneous value in row " + i + " of product-data.", release, [data.rows[i].s]);
for (let j = 0; j < data.rows[i].s.length; j++) {
try {
errArray[j] = data.rows[i].s[j];
} catch (err2) {
errArray[j] = "";
}
}
try {
box1 += productItem(i, errArray, data.count);
} catch (err3) {
debug2("guest", "Erroneous value in row " + i + " could not be replaced.", release, [err3]);
}
}
}
box1 += productsBottom();
return box1;
};
function guest2_cart(data) {
let total = "";
let box2 = cartHeader();
let errArray = [];
for (let i = 0; i < data.count; i++) {
try {
if (data.rows[i].s[1].trim().startsWith("TOTAL")) {
total = data.rows[i].s;
} else {
box2 += cartItem(i, data.rows[i].s);
}
} catch (err) {
debug2("guest", "Erroneous value in row " + i + " of cart-data.", release, [data.rows[i].s]);
for (let j = 0; j < data.rows[i].s.length; j++) {
try {
errArray[j] = data.rows[i].s[j];
} catch (err2) {
errArray[j] = "";
}
}
try {
box2 += cartItem(i, errArray);
} catch (err3) {
debug2("guest", "Erroneous value in row " + i + " could not be replaced.", release, [err3]);
}
}
}
box2 += cartItem("", total);
box2 += cartBottom();
return box2;
};
function guest3_orderstatus(data) {
let box3 = deliveryHeader();
let errArray = [];
for (let i = 0; i < data.count; i++) {
try {
box3 += deliveryItem(i, data.rows[i].s);
} catch (err) {
debug2("guest", "Erroneous value in row " + i + " of delivery-status-data.", release, [data.rows[i].s]);
for (let j = 0; j < data.rows[i].s.length; j++) {
try {
errArray[j] = data.rows[i].s[j];
} catch (err2) {
errArray[j] = "";
}
}
try {
box3 += orderStatusItem(i, errArray);
} catch (err3) {
debug2("guest", "Erroneous value in row " + i + " could not be replaced.", release, [err3]);
}
}
}
box3 += deliveryBottom();
return box3;
};
function showSubCategories(category, categoryText) {
// CALL-ID: listorante.Public.4
apiCall_listorante_public_category(category, function (data) {
document.getElementById("box0").innerHTML = guest0_categories(category, categoryText, data);
}, function (err) {
showHttpErrorMessage("main-content", err);
});
}
function showProductsOfCategory(catid) {
if (catid == 0)
document.getElementById("box1").innerHTML = "";
//else
document.getElementById("box0").hidden = false;
document.getElementById("box1").hidden = false;
apiCall_listorante_public_product(catid, function (data) {
document.getElementById("box1").innerHTML = guest1_product(data);
}, function (err) {
showHttpErrorMessage("main-content", err);
});
}
function addproduct(idbillnumber, prodid, tablenumber, waiternumber) {
apiCall_listorante_guest_addtoproductlist(idbillnumber, prodid, tablenumber,
waiternumber, function (data) {
debug("add to product list, data:", release, data);
const qty = data[4].rows[0].s[1];
if (qty < 3) {
$("#h3ID").remove();
const h3Tag = document.createElement("h3");
h3Tag.setAttribute("id", "h3ID");
const txt = getTextById(38) + qty;
h3Tag.innerHTML = "<strong>" + txt + "</strong>";
$(this).append(h3Tag);
$("#h3ID").fadeOut(7500);
}
}, function (err) {
debug("addproduct debug: idbillnumber,idproduct,idtable,idwaiter", release, idbillnumber, prodid, tablenumber, waiternumber);
showHttpErrorMessage("box1", err);
scrollToCustomerBox("box1");
});
}
function showProductDetails(idproduct) {
if (productDetailsCustomized && typeof customized_ProductDetails === "function"
) {
customized_ProductDetails(idproduct);
} else {
standard_productDetails(idproduct, "box1");
}
}
function standard_productDetails(idproduct, div) {
apiCall_listorante_public_getproductattributes(idproduct, function (data) {
let details = "<div>";
for (let i = 0; i < data.count; i++) {
if (i == 0) {
details += "<h3>" + data.rows[i].s[1] + "</h3>";
details += "<p>" + data.rows[i].s[4] + ": " + data.rows[i].s[5] + "</p>";
} else {
details += "<p>" + data.rows[i].s[4] + ": " + data.rows[i].s[5] + "</p>";
}
}
details += "</div>";
document.getElementById(div).innerHTML = details;
}, function (err) {
showHttpErrorMessage("main-content", err);
});
};
function deleteOrderFromList(idorder, idbillnumber) {
apiCall_listorante_guest_delitem(idorder, function (data) {
debug2("guest", "delete item", release, [idorder, idbillnumber, data]);
showCart(idbillnumber);
showDeliveryStatus(idbillnumber);
}, function errHandling(err) {
showHttpErrorMessage("box3", err);
});
scrollToCustomerBox("box3");
}
function showDeliveryStatus(idbill) {
document.getElementById("box0").hidden = true;
document.getElementById("box1").hidden = true;
document.getElementById("box2").hidden = true;
document.getElementById("box3").hidden = false;
apiCall_listorante_guest_deliverystatus(idbill, function (data) {
guest3_orderstatus(data);
document.getElementById("box3").innerHTML = guest3_orderstatus(data);
}, function (err) {
showHttpErrorMessage("main-content", err);
});
};
function showCart(bill) {
document.getElementById("box0").hidden = true;
document.getElementById("box1").hidden = true;
document.getElementById("box3").hidden = true;
document.getElementById("box2").hidden = false;
apiCall_listorante_guest_showcart(bill, function (data) {
guest2_cart(data);
document.getElementById("box2").innerHTML = guest2_cart(data);
}, function (err) {
showHttpErrorMessage("main-content", err);
});
};
function submitorder(bill) {
apiCall_listorante_guest_sendorder(bill, function (data) {
debug2("guest", "submit Order", "d16af_5", [data]);
showDeliveryStatus(bill);
}, function (err) {
showHttpErrorMessage("main-content", err);
});
};
function initializeOrders() {
/*
* loginState:
* NaN : user is not logged in
* 0 : user comes from login right now
* 1 : user is logged in
* 2 : there are items in the list
* */
if (loginState === 0) {
// Create a new bill number:
apiCall_listorante_guest_getguesttable(function (data) {
const thetableID = data.rows[0].s[0];
apiCall_listorante_guest_createbill(thetableID, function (data) {
debug("Create new bill for customer", release, data);
const newbill = data[4].rows[0].s[1];
document.getElementById("idbill").innerText = newbill;
apiCall_listorante_guest_billinfo(newbill, function (data) {
const release = RELEASE;
debug("Info of bill " + newbill + ":", release, data);
// Set loginState=1, so if page is refreshed, we still keep the
// assigned values for idbill, waitername etc.
setLoginState("1");
// Initialize values to be displayed:
idwaiter = data.rows[0].s[0];
idtable = data.rows[0].s[1];
waiterName = data.rows[0].s[2];
guestCount = data.rows[0].s[3];
tableName = data.rows[0].s[4];
debug2(module, "intitialization", RELEASE, [data]);
//Store the values:
sessionStorage.setItem("idwaiter", idwaiter);
sessionStorage.setItem("idtable", idtable);
sessionStorage.setItem("waitername", waiterName);
sessionStorage.setItem("tableName", tableName);
sessionStorage.setItem("idbill", newbill);
if (parseInt("" + customerID, 10) > 0) {
sessionStorage.setItem("theCustomerID", customerID);
}
document.getElementById("idwaiter").innerText = idwaiter;
document.getElementById("idtable").innerText = idtable;
document.getElementById("waiterName").innerText = waiterName;
document.getElementById("tableName").innerText = tableName;
document.getElementById("idbill").innerText = newbill;
document.getElementById("username").hidden = true;
},
function (err) {
setLoginState("")
showHttpErrorMessage("box0", err);
});
}, function (err) {
setLoginState("")
showHttpErrorMessage("box0", err);
});
}, function (err) {
setLoginState("")
debug("Seems an error but this is OK: guest is not logged in yet", release, err);
});
} else if (loginState > 0) {
idwaiter = sessionStorage.getItem("idwaiter");
document.getElementById('idwaiter').innerText = idwaiter;
idtable = sessionStorage.getItem("idtable");
document.getElementById('idtable').innerText = idtable;
waiterName = sessionStorage.getItem("waiterName");
document.getElementById('waiterName').innerText = waiterName;
tableName = sessionStorage.getItem("tableName");
document.getElementById('tableName').innerText = tableName;
idbill = sessionStorage.getItem("idbill");
document.getElementById('idbill').innerText = idbill;
} else {//customer has been logged out
document.getElementById("idwaiter").innerText = "";
document.getElementById("idtable").innerText = "";
document.getElementById("waiterName").innerText = "";
document.getElementById("tableName").innerText = "";
document.getElementById("idbill").innerText = "";
setLoginState("");
}
}
function userLogout() {
/*
* loginState:
* 0/NaN : user is not logged in
* 1 : user is logged in
* 2 : there are items in the list
* */
debug2("guest", "loginState in userLogout()", "d16af_5", [loginState]);
sessionStorage.removeItem("theCustomerID");
const logoutPath = getAPIServerPath() + customerID + "/1/logout";
const xhttp = new XMLHttpRequest();
xhttp.open("POST", logoutPath, true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send("token=" + bearerCookie);
xhttp.onreadystatechange = function () {
debug2("guest", "logoutPath,xhttp,loginState,bearerCookie", "" + release, [logoutPath, xhttp, loginState, bearerCookie]);
if (xhttp.readyState == 4) {
if (xhttp.status >= 400) {
debug2("guest", "ERROR: logout-status ", "" + release, [xhttp, loginState]);
} else {
window.open("../" + customerTheme + "/index.html?cust=" + customerID, "_self");
}
}
};
}
// Display this only as soon as the user tries to order an item
document.getElementById("userpass").style.visibility = "hidden";
initializeOrders();
showSubCategories(0, "");
/*-----------------------------------*/
/* guestevents.js */
release = "xxd16af_5";
module = "guestevents.js";
showRelease(module);
document.getElementById("text1").hidden = true;
document.getElementById("text741").hidden = true;
jQuery(document).on('click', '.submit-order', function () {
const bill = document.getElementById("idbill").innerText;
document.getElementById("tableName").innerText = "//Delete this text. loginState = " + loginState;
if (loginState && loginState === 2) {
submitorder(bill);
setLoginState("1");
document.getElementById("tableName").innerText = "//Change this text. 'Ordered items were submitted'. loginState = " + loginState;
scrollToCustomerBox("box0");
document.getElementById("text1").hidden = true;
document.getElementById("text741").hidden = true;
} else if (loginState && loginState === 1) {
document.getElementById("tableName").innerText = "//Change this text. 'No items in list'. loginState = " + loginState;
scrollToCustomerBox("box0");
} else {
document.getElementById("username").hidden = false;
scrollToCustomerBox("username");
document.getElementById("tableName").innerText = "//TODO: change text. Order is not active yet. Please click on 'start order'. loginState = " + loginState;
}
});
jQuery(document).on('click', '.get-products', function () {
const cat = $(this).attr('data-category');
const cattext = $(this).attr('data-categoryText');
debug("Category " + cat + " clicked (" + cattext + ")", release);
document.getElementById("tableName").innerText = "//TODO delete this text. loginState = " + loginState;
showSubCategories(cat, cattext);
showProductsOfCategory(cat);
scrollToCustomerBox("box0");
});
jQuery(document).on('click', '.add-product', function () {
const prod = $(this).attr('data-product');
const bill = document.getElementById("idbill").innerText;
const _idwaiter = document.getElementById("idwaiter").innerText;
const _idtable = document.getElementById("idtable").innerText;
debug('loginState: ' + loginState, release);
debug('idbill: ' + bill, release);
document.getElementById("tableName").innerText = "//TODO delete this text. loginState = " + loginState;
/*
* loginState:
* NaN : user is not logged in
* 0 : user comes from login, this should never occur here
* 1 : user is logged in
* 2 : there are items in the list
* */
if (loginState && (loginState === 1 || loginState === 2)) {
debug("Adding product with id " + prod + " and waiter " + _idwaiter + " and idtable " + _idtable, release, prod, _idwaiter, _idtable);
addproduct(bill, prod, _idtable, _idwaiter);
if (loginState === 1) {
setLoginState("2");
}
$("#h3ID").remove();
const h3Tag = document.createElement("h3");
h3Tag.setAttribute("id", "h3ID");
const txt = document.getElementById("addProductText").innerHTML;
h3Tag.innerHTML = txt;
$(this).append(h3Tag);
$("#h3ID").fadeOut(2500);
document.getElementById("text1").hidden = false;
document.getElementById("text741").hidden = false;
} else {
const infoText = getTextById(739);// Text is "Order not yet opened. Please insert your table number first."
document.getElementById("tableName").innerText = infoText + " [TODO delete this text part. LoginState = " + loginState + "]";
document.getElementById("username").hidden = false;
scrollToCustomerBox("username");
}
});
jQuery(document).on('click', '.delete-product', function () {
// TODO change loginState to "1" if all products were ordered
const idorder = $(this).attr('data-order');
const bill = document.getElementById("idbill").innerText;
debug2("guestevent", "delete item", release, [idorder, bill]);
deleteOrderFromList(idorder, bill);
});
jQuery(document)
.on(
'click',
'.product-overview',
function () {
const idprod = $(this).attr('data-product');
showProductDetails(idprod);
document.getElementById("main-content").innerText = "Create Product Overview";
});
jQuery(document)
.on(
'click',
'.show-cart',
function () {
const idbill = document.getElementById("idbill").innerText;
showCart(idbill);
scrollToCustomerBox("box2");
});
jQuery(document)
.on(
'click',
'.show-deliverystatus',
function () {
const idbill = document.getElementById("idbill").innerText;
showDeliveryStatus(idbill);
scrollToCustomerBox("box3");
});
jQuery(document).on(
"click",
".send-notification",
function () {
const idnote = $(this).attr('data-notification');
const idwaiter = document.getElementById("idwaiter").innerText;
apiCall_listorante_guest_sendnotification(idnote, idwaiter,
function (data) {
showNotifications();
}, function (err) {
showHttpErrorMessage("main-content", err);
});
});
jQuery(document).on('click', '.start-order', function (event) {
/*
* loginState:
* 0/NaN : user is not logged in
* 1 : user is logged in
* 2 : there are items in the list
* */
const userName = $('#username').val();
debug2("guestevents", "start new order", "d16af_5", ["userName:\n\t" + userName, "event:\n\t" + event, "loginState: " + loginState]);
if (loginState && loginState === 2) {
document.getElementById("tableName").innerText = "//TODO change text. There are still un-confirmed items. loginState = " + loginState;
scrollToCustomerBox("orderForm");
} else if (loginState && loginState === 1) {
document.getElementById("tableName").innerText = "//TODO change text. Order is already open. loginState = " + loginState;
scrollToCustomerBox("box0");
} else {
scrollToCustomerBox("username");
document.getElementById("username").hidden = false;
setLoginState("");
if (userName && userName.length > 1) {
apiCall_listorante_public_settings('GUESTPASS', function (data) {
const passwordString = data.rows[0].s[0];
const pass = userName + passwordString;
// login() will redirect to index.html with loginState 0
// initializeOrder sets the value to 1
if (passwordString) {
setLoginState("0");
login(userName, pass, customerTheme);
//after login, page will be redirected to index.html
} else {
document.getElementById("tableName").innerText = "//TODO delete text. Login failed. LoginState = " + loginState + " pass=" + pass;
}
}, function (err) {
debug("ERROR:", release, err);
});
} else {
document.getElementById("tableName").innerText = "//TODO change text. Please insert a table-number. LoginState = " + loginState;
}
}
});
jQuery(document).on('click', '.close-order', function (event) {
/*
* loginState:
* NaN : user is not logged in
* 0 : this value should never occur here
* 1 : user is logged in
* 2 : there are items in the list
* */
debug2("guestevents", "close order", "d16af_5", ["event:\n\t" + event, "loginState: " + loginState]);
if (loginState && loginState === 2) {
scrollToCustomerBox("orderForm");
document.getElementById("tableName").innerText = "//TODO: change text. 'There are still items in list.'" + loginState;
} else if (loginState && loginState === 1) {
setLoginState("");
// userLogout() will redirect to index.html
userLogout();
} else if (!loginState) {
document.getElementById("username").hidden = false;
scrollToCustomerBox("username");
document.getElementById("tableName").innerText = "//TODO: change text. Order is already closed." + loginState;
} else {
document.getElementById("tableName").innerText = "//TODO: delete text. Unexpected state:" + loginState;
}
});
/*---------------END OF LISTO_GUEST.JS--------------------*/