Files
seweb/client/jsFiles/SEAWebClientCommunication.js

481 lines
18 KiB
JavaScript

// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// % COMMUNICATION
var timeoutID; // We need this ID to reset the timer every 30 seconds
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// Server Sent Event
function buildUpdateConnection() {
// Establishes server-sent-event-connection, which is used for all sorts of
// updates and exists as long as the client is running.
// Executed at program start (see also SEAWebClientMain.js).
var path = "http://" + hostPort + "/update?" + window.clientTags;
if (debugCommunication) {
console.log("%cto server (SSE): " + path,
"color:white;background:lightblue");
}
try {
var src = new EventSource(path);
} catch (e) {
console.log(e)
alertify.prompt("NETWORK ERROR",
"Failed to establish connection to data-server at the given address!"
+ "Try to enter HOST and PORT of the data-server manually!",
hostPort, function(evt, value) {
hostPort = value;
buildUpdateConnection();
}, function() {
})
}
src.onmessage = function(e) {
var message = JSON.parse(e.data);
if (message) {
handleUpdateMessage(src, message);
}
};
src.onerror = function(e) {
console.log(e);
console.log('EVTSRC error')
alertify.prompt("NETWORK ERROR",
"Failed to establish connection to data-server at the given address!"
+ "Try to enter HOST and PORT of the data-server manually!",
hostPort, function(evt, value) {
hostPort = value;
buildUpdateConnection();
}, function() {
})
src.close();
};
}
function handleUpdateMessage(src, message) {
// Handles incoming SSE-messages depending on type of message.
if (debugCommunication > 1) {
console.log("%cfrom server (SSE): " + message.type,
"color:white;background:lightgray", message);
}
// console.log(message.type, "at", new Date())
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// <---------------------------------------------------------------------------------BUG!!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
resetTimer(src);
switch (message.type) {
// id-message: Confirms establishment of SSE-connection and determines
// specific ID of the client
case "id":
clientID = message.id;
if ("device" in message) {
if (message.device == "_inst_select") {
window.clientTitle = "select instrument";
console.log('IDselect')
pushInitCommand("getblock?path=_inst_select&", "instrument selection");
sizeChange();
} else {
if (message.instrument) {
window.instrument = message.instrument;
}
if (message.device) {
window.device = message.device;
}
window.clientTitle = window.instrument + " " + window.device;
// console.log('loadBlocks', message);
loadFirstBlocks();
}
document.title = clientTitle;
} else {
document.title = clientTitle + " " + message.title;
}
var header = document.getElementById("header");
header.style.width = 'auto';
let instrument = document.getElementById("instrument");
let device = document.getElementById("device");
instrument.style.width = 'auto'
device.style.width = 'auto'
instrument.innerHTML = message.instrument
device.innerHTML = message.device
// console.log('ID', initCommands);
nextInitCommand();
break;
// console-update-message: Confirms a command.
case "command":
var histories = document.getElementsByClassName("history");
for (var i = 0; i < histories.length; i++) {
var line = document.createElement('div');
line.innerHTML = htmlEscape(message.line);
line.style.color = "cornflowerblue";
if (message.origin == "self") {
line.style.fontWeight = "bold";
}
histories[i].appendChild(document.createElement('br'));
histories[i].appendChild(line);
histories[i].scrollTop = histories[i].scrollHeight;
}
var cmd = htmlEscape(message.line);
if (commandHistory.indexOf(cmd) === -1) {
commandHistory.unshift(cmd);
}
break;
// console-update-message: Confirms execution of command.
case "reply":
var histories = document.getElementsByClassName("history");
for (var i = 0; i < histories.length; i++) {
var line = document.createElement('div');
line.innerHTML = htmlEscape(message.line);
line.style.color = "black";
if (message.origin == "self") {
line.style.fontWeight = "bold";
} else if (message.origin == "async") {
line.style.color = "green";
if (!showAsync) continue;
}
histories[i].appendChild(line);
histories[i].scrollTop = histories[i].scrollHeight;
}
break;
// graph-message: Evokes redraw of graphics.
case "graph":
alert('obsolete code "graph" called')
console.log("graph");
createCharts2(message.graph);
break;
// redraw.message: Communicates changes requiring reset of a whole group
case "redraw": // SHOULD BE TESTED
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// SPECIAL CASE: is message.title = "main possible? It should evoke
// redraw of client
// special meaning: s < 0: replace slides in all swiper instances
reqJSON(-1, "http://" + hostPort + "/getblock?path=" + message.path
+ "&id=" + clientID, successHandler, errorHandler);
break;
// graph-update-message:
case "graph-update":
//if (getUpdatesGraphics) {
//timeServer = message.time;
updateCharts2(message.graph);
//}
break;
// update-message: Communicates change of values.
case "update":
if (debugCommunication > 1) {
console.log(message);
}
handleUpdate(message, src);
break;
}
}
function htmlEscape(str) {
str = "" + str;
if (!str) return "";
return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g,
'&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function resetTimer(src) {
// Executed every time a heartbeat-message is obtained.
// If no heartbeat-messages are obtained for a certain amount of time,
// an error-message is thrown.
clearTimeout(timeoutID);
timeoutID = setTimeout(function(src) {
console.log("timeout");
alertify.error("connection lost");
if (src) {
src.close();
}
}, 60000);
}
function handleUpdate(message, src) {
// Handles changes of parameter-values
for (var i = 0; i < message.updates.length; i++) {
let component = message.updates[i];
// Check for status updates
if (component.name.split(":")[1] == 'status') {
updateStatus(component);
}
// Check for target updates in the module block
if (component.name.split(":")[1] == 'target') {
updateTarget(component);
}
updateValue(component);
}
}
function updateTarget(component) {
let matches = document.getElementsByName(component.name);
let elem = matches[0]; // Should be the only match
// elem.value = component.value;
let row = elem.closest('div');
row.classList.remove('row-waiting-for-answer');
elem.actualValue = component.value;
if(elem.__ctype__ == 'input') {
resizeTextfield(elem);
}
}
function updateStatus(component) {
let matches = document.getElementsByName(component.name);
let status_icon = matches[0];
let row = status_icon.closest(".row");
let right = row.lastChild;
let statusCode = component.statuscode;
// Update status info, visible when mouse cursor is hovering over status icon
let status_info = document.getElementsByName(component.name.split(":")[0] + '-info')[0];
if(status_info) {
status_info.innerHTML = component.formatted;
}
status_icon.classList.remove('icon-status-disabled', 'icon-status-idle', 'icon-status-warn', 'icon-status-busy', 'icon-status-error');
row.classList.remove('row-disabled');
right.classList.remove = 'col-right-disabled';
switch (statusCode) {
case 0:
status_icon.classList.add('icon-status-disabled');
row.classList.add('row-disabled');
right.classList.add = 'col-right-disabled';
break;
case 1:
status_icon.classList.add('icon-status-idle');
break;
case 2:
status_icon.classList.add('icon-status-warn');
break;
case 3:
status_icon.classList.add('icon-status-busy');
break;
case 4:
status_icon.classList.add('icon-status-error');
break;
}
}
function updateValue(component) {
let matches = document.getElementsByName(component.name);
for (var j = 0; j < matches.length; j++) {
let elem = matches[j];
let type = elem.__ctype__; // -> Show Dom-Properties
if (type == "rdonly") {
let text = htmlEscape(component.formatted);
if (text) {
elem.innerHTML = text;
}
} else if (type == "input") {
let row = elem.closest('div');
row.classList.remove('row-waiting-for-answer');
elem.actualValue = component.value;
resizeTextfield(elem);
} else if (type == "checkbox") {
let row = elem.closest('div');
row.classList.remove('row-waiting-for-answer');
if (component.value == 'False' || component.value == 'false' || component.value == 0) {
elem.checked = false;
} else {
elem.checked = true;
}
} else if (type == "enum") {
let row = elem.closest('div');
row.classList.remove('row-waiting-for-answer');
// let options = elem.childNodes;
// for (var j = 0; j < options.length; j++) {
// if (options[j].label == component.value) {
// elem.value = j + 1;
// }
// }
} else if (type == "none") {
// pushbutton (e.g. stop command)
let row = elem.closest('div');
row.classList.remove('row-waiting-for-answer');
}
}
}
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// XMLHttpRequest
function reqJSON(s, url, successHandler, errorHandler) {
var xhr = typeof XMLHttpRequest != 'undefined' ? new XMLHttpRequest()
: new ActiveXObject('Microsoft.XMLHTTP');
if (debugCommunication) {
console.log("%cto server (reqJSON): %s",
"color:white;background:darkgreen", url);
}
xhr.open('get', url, true);
xhr.onreadystatechange = function() {
// console.log(xhr)
var status;
var data;
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate
if (xhr.readyState == 4) { // `DONE`
status = xhr.status;
if (status == 200) {
data = JSON.parse(xhr.responseText);
successHandler && successHandler(s, data);
} else {
errorHandler && errorHandler(status);
}
}
};
xhr.send();
}
function reqJSONPOST(s, url, parameters, successHandler, errorHandler) {
var xhr = typeof XMLHttpRequest != 'undefined' ? new XMLHttpRequest()
: new ActiveXObject('Microsoft.XMLHTTP');
if (debugCommunication) {
console.log("%cto server (reqJSONPOST): %s",
"color:white;background:lightgreen", url);
}
xhr.open('post', url, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
// console.log(xhr)
var status;
var data;
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate
if (xhr.readyState == 4) { // `DONE`
status = xhr.status;
if (status == 200) {
data = JSON.parse(xhr.responseText);
successHandler && successHandler(s, data);
} else {
errorHandler && errorHandler(status);
}
}
};
xhr.send(parameters);
}
function successHandler(s, message) {
// Handles incoming XMLHttp-messages depending on type of message.
if (debugCommunication) {
console.log("%cfrom server (reqJSON): " + message.type,
"color:white;background:dimgray", message);
}
switch (message.type) {
// Response to a "getblock"-server-request.
case "draw":
if (message.path == "main") {
// Happens only initially or at device change.
appendToGridElement(1, message.title, "main", createContent(message));
// appendToGridElement(2, "", "parameters", createContent({components:[]}));
} else {
// In the module-block a parameter was selected
showParams = true;
// -> write parameter-block to grid-element2
isl = appendToGridElement(2, message.title, 'parameters', createContent(message));
adjustGrid();
if (nColumns == 1 || nColumns == 2 || nColumns == 3) {
document.getElementsByClassName('icon-close-container')[0].innerHTML = '<img class = "icon-main icon-close" src="res/icon_close.png">';
}
}
nextInitCommand();
// Request for updates.
if (getUpdates) {
reqJSON(s, "http://" + hostPort + "/updateblock?path="
+ message.path + "&id=" + clientID, successHandler,
errorHandler);
}
break;
// Response to a "update-block"-server-request.
case "accept-block":
break;
// Response to a "console"-server-request.
case "accept-console":
// draw console only to the last grid-element
appendToGridElement(3, "console", "console",createContentConsole(3));
nextInitCommand();
// send empty command in order to trigger getting history
reqJSON(0, "http://" + hostPort + "/sendcommand?command=&id=" + clientID, successHandler,
errorHandler);
break;
// Response to a "gettime"-server-request.
case "time":
timeRange = message.time;
/*createGraphics();
// By default mostleft swiper-instance shows graphics.
// Update time-selection. (see also SEAWebClientGraphics.js)
var select = document.getElementsByClassName("select-time")[0];
begin = timeRange[0] - timeRange[1];
select.value = begin;
// Server-request for variable-list.*/
// console.log('TIME', timeRange)
reqJSONPOST(0, "http://" + hostPort + "/getvars",
"time=" + timeRange[0] + ',' + timeRange[1]
+ "&userconfiguration=" + JSON.stringify(getFormattedUserConfigurationFromLocalStorage())
+ "&id=" + clientID, successHandler, errorHandler);
break;
// Response to a "getvars"-server-request.
case "var_list":
//blocks = message.blocks;
/*var varlist = [];
for (var i = 0; i < blocks.length; i++) {
for (var j = 0; j < blocks[i].curves.length; j++) {
varlist.push(blocks[i].curves[j].name);
}
}
// Update graphics
if (varlist.length > 0) {
reqJSON(0, "http://" + hostPort + "/graph?time=" + timeRange
+ "&variables=" + varlist + "&id=" + clientID,
successHandler, errorHandler);
} else {
nextInitCommand();
}*/
// graphs.receivedVars(message.blocks);
document.getElementById("device").innerHTML = message.device
graphs.initGraphs(message.blocks);
nextInitCommand();
break;
// Response to a "graph"-server-request.
case "graph-draw":
// obsolete?
if (debugCommunication) {
console.log("graph-draw", message);
}
alert('obsolete code graph-draw called')
createCharts2(message.graph);
nextInitCommand();
// Request for updates.
reqJSON(s, "http://" + hostPort + "/updategraph?id=" + clientID,
successHandler, errorHandler);
break;
// Response to a "updategraph"-server-request.
case "accept-graph":
break;
case "accept-command":
if (message.result) {
updateValue(message.result);
}
break;
case "error":
console.log("%cError-Message received!", "color:white;background:red");
console.log(message);
break;
default:
break;
}
}
function errorHandler(status) {
if (debugCommunication) {
console.log("error", status);
}
}