Інтерактивний web-застосунок: приклад SPA з offline-режимом і підключенням через Modbus TCP
Усім привіт. У попередній статті ми розглянули приклад Full-stack проєкту зі складовими Front-end та Back-end. Сьогодні глибше розглянемо Front-end з offline-режимом на прикладі проєктування web-застосунку, використовуючи архітектуру single-page application (SPA) або односторінкового інтерфейсу. Розберемо його структуру, переваги даної архітектури, автономність. Долучимо до проєкту спеціальні API для роботи з сокетами, з’єднаємось з пристроєм по протоколу Modbus для отримання даних. Свого роду GUI на SVG, JavaScript та CSS. Інтерфейс і застосунок для роботи з Modbus TCP дивіться більш детально за посиланням.
Реалізований проєкт підпадає під тип архітектури як односторінковий застосунок SPA, який дає можливість взаємодіяти з ним без перезавантаження сторінки та швидкої появи контенту після первинного завантаження. Основним його недоліком може бути поява проблем з користувацьким інтерфейсах у застарілих браузерах.

Як правило, web-інтерфейс — це клієнт-серверний застосунок, де є браузер як клієнт і вебсервер. Логіка роботи застосунку розподіляється між сервером і клієнтом. Для обміну інформацією існує канал, а дані зберігаються локально або в хмарі. Також передбачається робота як з сервером для обміну інформації, так і в режимі offline, на основі попередньо прописаної логіки і внесеної інформації.
Складові проєкту та функціонал
В цій статті детально розглянута Front-end частина на SVG з додаванням CSS, JavaScript та спеціальних API chrome. В інтерфейсі реалізовано інтерактивність у вигляді виділення компонентів при наведенні, обробка подій по натисненню на кнопку, відкриття додаткових вікон. Також спливаючі підказки, включення/відключення частин схем, коригування тексту та опису на ходу, редагування схеми у вигляді видалення та додавання компонентів, графічна бібліотека, drug and drop компонентів, з’єднання по протоколу ModbusTCP та Websocket, аналіз повідомлень та інші функції. Нижче розберемо основні з них, на прикладі функціоналу розглянемо їх використання.
Масштабування та деталізація компонентів інтерфейсу
Для деталізації обрана масштабована векторна графіка SVG, яка виступає основним форматом сторінки. Основна перевага в зручності використання в інтернеті, масштабування та підтримці в веб-браузерах на різних платформах, офісних програмах, графічних редакторах. Як приклад на малюнку нижче збільшення частини схеми, без втрати якості.

Відносна простота промальовування інтерфейсу
Для створення графіки підійдуть як спеціалізовані редактори (Photoshop, Adobe Illustrator, inkscape), так і звичайний блокнот для простих геометричних фігур. Наприклад, нижче опис прямокутника в SVG, де задається ширина, висота, початкові координати крайнього лівого кута зверху та стиль, тобто колір фігури, товщина ліній та інше:
<rect width="150" height="150" x="10" y="10" style="fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;stroke-opacity:0.9"/>
Інтерактивність
Взаємодія користувача з інтерфейсом. Даний функціонал досягається шляхом поєднання javaScript, CSS та SVG. Наприклад, натискання на кнопку «Повідомлення» відкриває вікно з наявними повідомленнями, нижче скрин та код.

Для реалізації даного функціоналу у властивостях компонента <text> графіки SVG прописуємо id=«serviceTurnOffOnMessageName". Нижче код самої кнопки в SVG, де для відображення її наступні компоненти rect та text згруповані через тег <g>. Компонент rect описує фігуру, text — текст в середині фігури та підказка при наведенні:
<g transform="translate(10,180)" id="grserviceTurnOffOnMessage" onclick="showId(this)" class="pointer" > <rect x="0" y="0" width="120" height="20" rx="5" id="serviceTurnOffOnMessage" onclick="clickedMesage(evt)"/> <text x="5" y="15" id="serviceTurnOffOnMessageName" name="name program" nameId="nameId" value="value" nameCAN="nameCan" title="title" onclick="clickedMesage(evt)">Повідомлення.</text> </g>
Своєю чергою, JavaScript обробляє подію click, в даному випадку по тексту в середині даної фігури. Через порівняння необхідного «id» підв’язуємо до кнопки функцію.
document.getElementById('Capa_1').addEventListener('click' , function(e){
if (e.target.id == 'serviceTurnOffOnMessageName'){
console.log("turn on/off window message");
serviceTurnOffWindowMessage();
}
}
Функція змінює колір тексту кнопки та активує вікно (прямокутник) з доступними повідомленнями по id=’WindowMessage’ через зміну стилю style.display="block".
var promptWindowsMessage = false;
function serviceTurnOffWindowMessage(){
if(!promptWindowsMessage){
document.getElementById('serviceTurnOffOnMessageName').style.fill = 'green';
document.getElementById('WindowMessage').style.display="block";
promptWindowsMessage = true;
}else{
document.getElementById('serviceTurnOffOnMessageName').style.fill = 'red';
document.getElementById('WindowMessage').style.display="none";
promptWindowsMessage = false;
}
}
Для виділення компонента при наведені використовується CSS. Змінюємо яскравість кольору за допомогою властивості fill-opacity: 0.5. При цьому сам текстовий компонент має мати атрибут class: <text class=«draggable"...../>. Нижче код CSS та скрин зміни кольору компоненту при наведенні на нього.
.draggable:hover{
fill-opacity: 0.5;
}

Інформативність
Даний функціонал досягається через спливаючу підказку ToolTip або прописаний код на JavaScript. В SVG підказка додається через додавання в згрупований компонент тега <title>. Нижче скрин наведення курсору на текстовий елемент «N-макс» та його код.

<g id="gr9idN-макс" transform="translate(450,237)" onclick="showId(this)" class="styleUst"> <text class="draggable" onclick="clickedUst(evt)" name="_r.N_Max" nameCAN="N-макс" value="" id="9idN-макс">N-макс=1.23ном</text> <title>N-макс.: Уставка. Максимально допустима швидкість двигуна. За замовченням 1.23ном. A7-Ред.Устав. -> Захист</title> </g>
Для варіанту на мобільному пристрої, де відсутній курсор і наявний тачскрин, на поміч приходить JavaScript. Нижче на малюнку виводиться підказка у вигляді прямокутника з текстом.

Для даного варіанту реалізації підказки використаємо об’єкт <foreignObject>, який містить елементи з іншого простору імен XML. Цей елемент описується як шаблон для виклику і розміщення в ньому підказки будь-якого компоненту.
<foreignObject width="300" height="100" id="tooltip" class="tooltip" font-weight="bold" font-size="16" style="position: absolute; display: none; stroke-width:1; background-color: lime; overflow: auto;"> tooltip </foreignObject>
По кліку на компонент, який є згрупованим і містить атрибут onclick="showId(this)", викликається функція showId(this). Яка шукає тег <title>, копіює її зміст в шаблон підказки foreignObject через глобальну зміну toolTipContent.
function showId(g) {
var str = g.querySelector('title').textContent;
toolTipContent=str;
}
В спільному обробнику події по кліку перекладаємо підказку в шаблон за його id="tooltip".
let tooltip = document.getElementById("tooltip");
tooltipM.innerHTML = toolTipContent;
Графічне редагування
Використовуючи переваги масштабованої векторної графіки, можна доволі легко створювати необхідні компоненти та поповнювати бібліотеку як графічних компонентів, так і текстових . В ході роботи є можливість корегувати схему, додавши функції на JavaScript.
Для коригування графіки в ході роботи використовуємо клонування компонента зі створенням або зміною атрибутів transform та id. Нижче код клонування та скрин. Функція виконує отримання графічного компонента по id, його клонування, задання координат для атрибута translate та задання id.
var Root=document.documentElement
function clone(id){
var G=document.getElementById(id)
var NewG=G.cloneNode(true)
var move="translate("+340+","+388+")"
NewG.setAttributeNS(null,"transform",move);
NewG.setAttributeNS(null,"id",id);
NewG.querySelector('rect').setAttribute("id", id);
Root.appendChild(NewG);
}

Для додавання на схему текстового компоненту використовуємо метод createElementNS() для створення тегів <g>, <text>. Також створюємо необхідні атрибути за допомогою setAttribute. Нижче приклад коду SVG текстового компоненту і функція його створення doCreatetextComp, а також скрин:
<g id="gr57idLf#-макс" transform="translate(927,560)" onclick="showId(this)" class="styleUst"> <text class="draggable" onclick="clickedUst(evt)" id="57idLf#-макс" ">Lf#-макс=150</text> <title>Lf#-макс : Уставка…</title> </g>
Функція створює теги та атрибути текстового елементу.
function doCreateTextComp(){
var svg = document.getElementById('Capa_1');
var ns ='http://www.w3.org/2000/svg';
var group = document.createElementNS( ns,"g");
group.setAttribute("id","gr57idLf#-макс" );
group.setAttribute('transform','translate(400,460)');
group.setAttribute('onclick','showId(this)');
group.setAttribute('class','styleText');
var innerText = document.createElementNS(ns,"text");
innerText.setAttribute('font-family','Verdana');
innerText.setAttribute('font-size','10');
innerText.setAttribute('fill','black');
innerText.setAttribute('class','draggable');
innerText.setAttribute('onclick','clickedText(evt)');
innerText.setAttribute("id", "57idLf#-макс");
innerText.setAttribute('style','stroke-width:1');
innerText.textContent = "Lf#-макс=150";
var innerTitle = document.createElementNS(ns,"title");
innerTitle.textContent = "Lf#-макс : Уставка…";
group.appendChild(innerText);
group.appendChild(innerTitle);
svg.appendChild(group);
}

Обмін даними та аналіз
Для аналізу функціональної схеми або логіки роботи велике значення відіграє реальний стан пристрою, об’єкта управління, процесу тощо. Якщо пристрій має інтерфейс, наприклад ModbusTCP, то маємо змогу зчитати/записати дані, використовуючи розширення chrome. Для цього необхідно API chrome для роботи з сокетами, бібліотека ModbusTCP та об’єкт runtime.Port для обміну даними між web-застосунком і розширенням. Через безпекові обмеження ми не маємо доступу Api tcp chrome, безпосередньо з сторінки інтерфейсу, тому з протоколом ModbusTCP оперуємо через кастомне web-розширення (застосунок).
Для спілкування з пристроєм достатньо знати словник об’єктів (object dictionary) пристрою. Через вікно налаштувань задаємо параметри відповідно до протоколу: номер параметру, кількість параметрів, час зчитування та ідентифікатор розширення.
Нижче скрин словника об’єктів пристрою та інтерфейс з вікном налаштувань початкових параметрів (data settings), завантаженням даних до застосунку (receiving data) та їх парсинг для аналізу (Parsing for analysis).


Функціонал в інтерфейсі реалізується через створення вікон, масиву параметрів та runtime.Port для обміну інформації між застосунками. Нижче частина коду, що описує даний процес. Параметри або змінні для звертання до пристрою, реалізовані як об’єкт Map, що зберігає пари ключ-значення. Нижче приклад масиву.
const massiveParametrMapLink = {"SysInf":0,"E":1,"Ud":2,"T-fSec":3,"r0_02":4,"N#":5,
"N#R":6,"N":7,"r0_03":8,"r0_04":9,"Id#":10,"Id#R":11,"Id":12,"Id2":13,"Ids":14,……………. };
За допомогою парсингу поділяємо відповідь на корисні дані: id та значення параметру. Порівнюємо подібні id та передаємо їм значення змінних. Нижче функція створення з’єднання з розширенням по запиту його id — IdPluginChrome, та налаштовування прослуховування порту на прийом повідомлень.
var IdPluginChrome="ofgdgleiageppdjalldkclmjkkfnlefa";
function LinkApplicationTwo(){
if (port == null || dataStart != "successs" ){
port = chrome.runtime.connect(IdPluginChrome);
port.onMessage.addListener(function (event) {
if (event.data.includes("dataValueTCP")){
console.log(event.data)
dataPortTCP=event.data;
DataTCPConvertion();
}
}
}
}
Вікно налаштувань працює через функцію writeDataPluginChrome(), де за необхідності змінюємо параметри через запис даних, а саме: host (IP), port, початкова адреса (start), кількість змінних (count), id розширення (IdPluginTCPIP) та час оновлення інформації (TimeDataTCPIP).
function writeDataPluginChrome(){
var initialData = "initialData";
initialData+= " host " + document.getElementById('AdressTCPIP').textContent;
initialData+= " port " + document.getElementById('PortTCPIP').textContent;
initialData+= " start " + document.getElementById('AdressValueTCPIP').textContent;
initialData+= " count " + document.getElementById('CountValueTCPIP').textContent;
if ( dataStart == "successs" ){
port.postMessage({type: 'PYCHAT_SCREEN_SHARE_PING', text: initialData});
}else confirm("Спочатку з'єднай web-додатки кнопкою LinkApp для запису host, port, start, count ");
IdPluginChrome = document.getElementById('IdPluginTCPIP').textContent;
timeRefreshDataTCPModbus = parseInt(document.getElementById('TimeDataTCPIP').textContent);
}
Оновлення даних здійснюється через метод setInterval(), який повторно викликає функцію або виконує фрагмент коду з фіксованою затримкою часу між кожним викликом, в даному випадку через зміну refreshTCPDataSocket. Функція TCPDataSocket() відсилає запит port.postMessage({type: ’PYCHAT_SCREEN_SHARE_PING’, text: ’TCPLink’}); розширенню, який в свою чергу опитує кінцевий пристрій.
if(promptTCPDataSocket && dataStart == "successs"){
refreshTCPDataSocket = setInterval(function() {
console.log("TCPDataSocket read data with CM3");
TCPDataSocket();
}, timeRefreshDataTCPModbus );
}else clearInterval(refreshTCPDataSocket);
На основі отримання даних в реальному часі та інформативності параметрів на схемі можемо проводити аналіз роботи системи.
Висновок
Використовуючи переваги SVG, JavaScript and CSS, можна легко побудувати застосунок, який не потребує попереднього встановлення. Який відкривається на більшості пристроїв (комп’ютер, планшет, мобільний), що підтримують браузери на основі Chromium, наприклад Chrome, Edge. Мати під рукою інформативну карту, схему, незважаючи на статус підключення до інтернету — online чи offline. Змінювати та вносити для себе важливі нотатки. Аналізувати роботу, керуючись візуальним провідником та вибором необхідної частини схеми.
4 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів