Добрый день, коллеги!
Сегодня мы рассмотрим такую тему как трансформации объектов на странице.
Вращение объекта на странице — без преувеличения одна из самых полезных функций. Этот эффект, будучи простым в понимании, содержит некоторые хитрости, о которых не стоит забывать во время разработки приложения.
Вращение объекта достигается путём использования функции rotate() свойства transform. В отличие от функции перемещения, вращение не может быть описано пикселями, процентами и другими привычными единицами измерения. Для вращения объекта используются специальные «угловые» единицы, среди которых: deg — Градусы. Знакомая многим единица измерения, по которой можно указать, на какой угол повернуть объект относительно его начального положения . grad — Градианы. Отличительной особенностью такой единицы является количество град на один квадрант — 100. То есть полный круг описывается в 400 град. rad — Радианы. Мера, при которой за одну единицу принимается угол дуги, длина которой равна радиусу окружности. turn — Оборот. Количество оборотов окружности, где единица равна одному полному обороту.
Если пользоваться CSS, то функция выглядит следующим образом:
transform: rotate(15deg); // поворот на 15 градусов по часовой стрелке transform: translate(-50%, -50%); // перемещение на 50% вверх и 50% влево
Зная это мы можем написать функцию, которая будет менять угол и положение объекта в зависимости от внешних параметров.
Для дальнейших экспериментов поставим задачу - оживить индикатор от 0 до 100%, в качестве сигнала возьмем загрузку процессора и выведем информацию в виде красивого индикатора, который соберем сами из нескольких элементов.
Наш индикатор будет состоять из трех элементов: фон, стрелка и цифровой индикатор для большей наглядности.
Поскольку стрелка это отдельный элемент, то нам понадобится индикатор без стрелки, либо стрелку нужно удалить самостоятельно
Подойдет абсолютно любой фон в зависимости от величин, которые мы будем демонстрировать. Проценты будут измеряться в диапазоне от 0 до 100, поэтому я выбрал такой же индикатор.
Следующий элемент - стрелочка. Его я нарисовал самостоятельно, если будете выбирать ее в интернете - важно, чтобы у нее не было фона. Еще фон можно удалить самостоятельно. Подбирать размеры и расположение нет необходимости, ось вращения можно корректировать в редакторе.
Когда два элемента подготовлены - можно переходить в редактор динамических приложений.
Создаем контейнер, назначаем ему фон - сам индикатор. Настраиваем, чтобы фон был посередине и не повторялся.
Внутри этого контейнера будет еще два: первый - это стрелочка, ему также настраиваем фон, второй контейнер - цифровая индикация.
Я настроил сигнал на странице при помощи стандартного интерфейса, сигнал назвал "Processor". Теперь я могу вызвать его из любого места страницы.
Начнем с простого отображения сигнала, заодно проверим, что все правильно.
В контейнере, предназначенном для цифровой индикации создаем событие "Проигрывание в цикле".
Код:
//page id:C18FB6C5-962B-4EB6-9F78-716E2EABD2E3
//element id:0DE1C71A-E5AC-4698-AAE5-97AEC1E8E8BA
//LOOP_PLAY
function loop_0DE1C71A_E5A(){
try {
let elem = document.getElementById('0DE1C71A-E5AC-4698-AAE5-97AEC1E8E8BA');
let value = baseSdkUtils.data['Processor'].value;
elem.innerHTML = value;
} catch ( e ) {
}
setTimeout(loop_0DE1C71A_E5A, 1000);
}
loop_0DE1C71A_E5A();
Очень важный момент - у вас element id будет другим, если вы создаете приложение самостоятельно.
Теперь запускаем экземпляр приложения и видим значение сигнала.
Настало время заставить стрелку двигаться вместе со значением.
В поле html вставляем следующий код:
Этот элемент мы создаем для того, чтобы "откалибровать нашу стрелку.
Мы сразу увидим, что на нашей стрелке появилась точка - это и есть ось вращения. Изменяя элементы top и left - переместим точку в нужное нам место.
В данном случае transform: translate(-50%, -50%) - переворачивает систему координат для того чтобы как раз параметры top и left соответствовали оси вращения, которую мы уже будем задавать в событии.
Создаем событие "Проигрывание в цикле":
Код:
//page id:C18FB6C5-962B-4EB6-9F78-716E2EABD2E3
//element id:681DB916-6405-408C-8480-C5AEB30EF032
//LOOP_PLAY
function loop_681DB916_640(){
try {
let elem = document.getElementById('681DB916-6405-408C-8480-C5AEB30EF032');
let value = (baseSdkUtils.data['Processor'].value);
elem.style.transformOrigin = '79% 50%' - эти параметры совпадают с ранее выбранными left И top
Теперь рассмотрим предыдущую строчку:
elem.style.transform = 'rotate(' + (16 + (value * 1.48)) + 'deg)';
Мы используем функцию rotate, в качестве единиц измерения я выбрал градусы, а значит полный круг = 360.
Путем подбора вычисляем, что стрелка показывает на 0 при значении 16 градусов. На 100 она показывает при значении 164 градуса. Таким образом вся шкала составляет 164 - 16 = 148. Делим на 100% и получаем (16 + (value * 1.48)) - теперь стрелка показывает на нужный параметр.
Если вы делаете индикатор с другим фоном или стрелка у вас находится в другом месте (выше или ниже), то числа у вас будут другие.
Шаблон приложения:
export (14).zip (Размер: 362.35 KB / Загрузок: 4)
На последок небольшой бонус:
В скрипт, который мы создали для анимации стрелки добавим еще одну команду:
elem.style.transition = '1s';
И тогда изменение значения сигнала будет сопровождаться плавным переходом к следующему значению.
У свойства transition есть еще много атрибутов, позволяющих делать нелинейные анимации или более сложные.
Спасибо за внимание.
Для того чтобы оформить выгрузку в Excel уже существующей таблицы в динамическом приложении нужно обратиться к ней, перевести в UTF-8 и сформировать ссылку.
1) Возьмём таблицу html, ее можно создать готовую, или сделать самому, назначив id, наполнить ее всеми необходимыми данными, далее будем ее сохранять в xls.
2) сделаем обычную кнопку с надписью скачать и внутри нее создадим событие "нажата кнопка мыши"
Код:
$('#buttonID').click(function(event){
function exportTableToExcel(tableId, filename = 'the_data_you_needed.xls') {
let dataType = 'application/vnd.ms-excel';
let tableSelect = document.getElementById(tableId);
let tableHTML = encodeURIComponent(tableSelect.outerHTML.replace(/ or .*?>/g, '>'));
let link = document.createElement('a');
link.setAttribute('href', 'data:text/csv;charset=utf-8,%EF%BB%BF' + tableHTML);
link.download = filename;
link.click();
}
exportTableToExcel('tableID');
});
Вот и всё!
В этой функции подставляем:
buttonID - ID кнопки, которую нажимаем
tableID - ID таблицы, которую выгружаем
the_data_you_needed.xls - название файла, который получим
Задача: У нас есть стандартный отчет по работе оборудования, он нас устраивает, но при формировании этого отчета необходимо указывать дату и выбирать наш цех. Мы хотим приложение, в котором отчет будет открываться сразу за сегодняшнее число, за наш цех, и показывал всегда актуальную информацию, мы его повесим на телевизор в цеху и трогать не будем.
Решение:
Создаем пустое приложение из шаблона, или просто пустое приложение.
Добавляем пустой контейнер. Для удобства прописываем в поле данные еще один контейнер:
Код:
<div width = '100%' height = '100%' id = fullscreen></div>
Теперь посмотрим на отчет, который мы хотим видеть в приложении:
Посмотрим год непосредственно самого отчета и увидим откуда он берется
Для того чтобы вставить в приложение готовый отчет используется функция baseUtils.injectIntoPage,
ƒ injectIntoPage( url – берем из параметра injectedurl на том элементе, который хотим повторить, я взял отчет по загрузке и меняю в нем параметры, а так же дату , injectId – куда будем вставлять результат , checkAuth – нужно ли быть авторизованным для получения результата (если поставить false, то функция будет работать только там, где не требуется авторизация, в нашем случае используются данные из приложения, а у него есть пользователи) , callBack – далее все как обычно , callBackError)
Теперь рассмотрим атрибут injected_url, в нем написан адрес запроса, его же мы и будем использовать как параметр url.
/Winnum/views/pages/app/agw.jsp?rpc=winnum.views.pages.app.cnc.reports.calendar.TagPriorityWorkReport
&men=streamWorkReportsTable
&appid=winnum.org.app.WNApplicationInstance:8
&oid=winnum.org.domain.WNAdministrativeDomain%3A1
&oid_id=__ref_inputoid&oid_type=text_reference&oid_value2=
&date=03.05.2024
&date_id=__text_date_inputdate
&date_type=text-date
&date_value2=
&formid=report_settings_form
&count=2
&mode=yes Очевидно, что его можно исправить в таких аргументах как date, oid, appid.
oid и appid нас устраивает, осталось только сделать сегодняшний день и запустить отчет в цикле, обновляя его каждые пять минут. Значит создаем событие loop (проигрывание в цикле) и пишем:
Код:
//get today
var today = new Date();
var dd = today.getDate();
if (dd < 10){dd = '0' + dd}
var mm = today.getMonth() + 1;
if (mm < 10){mm = '0' + mm}
var yyyy = today.getFullYear();
var thisday = dd + '.' + mm + '.' + yyyy;
var appid = 'winnum.org.app.WNApplicationInstance:8';
var oid = 'winnum.org.domain.WNAdministrativeDomain%3A1';
var containerId = fullscreen.id;
В данном случае нужно прописать id нашего контейнера, центрального слоя и верхнего меню, для того чтобы все было "резиновым" и на своих местах. Еще в самом начале поменять id контейнера на window.
В результате получится приложение, которое открывает отчет по нагрузке за весь домен и обновляет его каждые 300 секунд, данные берутся из приложения 8. Чтобы получить свой цех - нужно поменять oid на соответствующую ему папку.
Во вложении самообновляемый отчет, который позволяет выбрать участок или весь цех, а так же выбрать приложение для построения графика из выпадающего списка
В процессе разработки динамического приложения появилась необходимость получать все статусы оборудования с учётом приоритетности за текущий день/смену в режиме реального времени. Используя метод getPriorityTagDuration() WINNUM JavaScript SDK получаю данные, которые не соответствуют данным в отчетах.
Например, в приложенном скриншоте №1 с данными полученные функцией getPriorityTagDuration() видны частые появления тега статуса "Станок выключен", хотя в приложенном скриншоте №2 видно, что в отчёте по работе оборудования за текущий день, статуса "Станок выключен" нет. Также не совпадают временные метки старта и окончания действия тегов, например, тег "М0 останов", см. скриншот №3.
В чём может быть причина таких расхождений? Или расчёт длительности тегов, выполняемый в функции getPriorityTagDuration() отличается от методов расчётов для стандартных отчётов? Или требуется какая-то настройка платформы для получения корректных данных?
В динамических приложениях довольно часто используются кнопки.
Кнопки позволяют передать контроль над элементами приложения пользователю, или действуют как ссылки на другие страницы.
В данном посте я хотел бы рассказать не только о том, как использовать кнопки, но и показать несколько визуальных приемов, которые помогут стилизовать приложение и улучшить восприятие.
Начнем с самого начала:
Создание кнопки
Вы можете создать кнопку, перейдя в инструменты и выбрав одну из готовых кнопок. В этом случае вы создадите элемент, у которого уже будет сгенерирован “id”, “class”, и несколько готовых параметров. В настройках элемента можно удобно для себя настроить цвет, фон, текст и рамку, однако кнопка будет статичной и пака еще ничего не делает при нажатии.
Есть и второй способ создать кнопку так, через html. Для того чтобы разместить html элемент для него нужен пустой контейнер, в поле данных мы создадим элемент <button>. Для того чтобы он мог быть не только учтен браузером, но и виден – нужен минимальный набор параметров, но, если оставить все в стандарте – можно просто вписать текст в элемент и его уже будет видно.
В данном случае применятся все встроенные библиотеки, которые могут на нее повлиять, и мы получим стандартную кнопку с надписью, при этом у нее даже будет анимация при наведении и нажатии (на самом деле просто изменение фона).
События
Все слушатели событий так же применимы и к кнопкам, и мы точно так же, как для любого другого элемента можем создать слушатель события через интерфейс, а можем прописать его в коде приложения в одном из подключенных файлов .js
Далее в примерах кода будем использовать id = "btn" для всех кнопок, если создавать событие через интерфейс, то id будет сгенерированным
Это открывает для нас возможность изменять стили или вызывать визуальные функции или анимации, которых в интернете бесчисленное количество с готовым кодом и примерами.
Но вернемся к стандартной кнопке, она сейчас просто нарисована и имеет стандартные настройки. Если мы еще раз посмотрим в настройки элемента, то увидим опцию, которая называется «стиль рамки». Последние два варианта выбора называются «выдавленная» и «вдавленная»
Если мы хотим создать эффект объема на кнопке, то нам нужно сделать ее выдавленной, а при нажатии она будет становиться вдавленной. Поэтому оставляем этот параметр в позиции «выдавленная».
За стиль рамки отвечает атрибут “border-style”, и значение, которое он принимает: outstet – выдавленная, inset – вдавленная.
Если кнопка создана через интерфейс, то у нее будет уникальный id, который повторится автоматически при добавлении события, а если вы сами написали кнопку, то обращаться к ней нужно по ее уникальному id.
Аналогичным образом работают и другие события, вот список событий для хорошо анимированной кнопки:
Под указателем мыши – mouseover
Вне указателя мыши – mouseout
Нажата кнопка мыши – mousedown
Отпущена кнопка мыши – mouseup
Активность кнопки
Активность кнопки задается ее атрибутом disabled. По умолчанию он выключен и кнопка активна.
У неактивной кнопки пропадает возможность взаимодействия, она перестанет нажиматься, перестанут работать события на нажатие, но события mouseover и mouseout продолжат работать, если не указать иное, например написать условие в самих событиях.
Для стилизации можно изменить ее цвет, или сделать полупрозрачной:
btn.style.opacity = "0.5";
Этого достаточно, чтобы кнопка выглядела приятнее, но это далеко не все возможности по стилизации кнопок. Так же как и для любого другого объекта применимы анимации, стили для кнопок можно применять с помощью css, так же существуют css анимации и многое другое. Но стоит отметить, что не все практики, применимые для веб-дизайна подойдут для использования в динамических приложениях, все таки стоит учитывать это при выборе анимации или слишком замысловатого стиля и сделать выбор в пользу более сдержанного и функционального дизайна.
Отредактировать ширину и высоту шкалы (откалибровать градусник). Редактировать нужно <div id = 'termometr'> подстроить параметры ширины и высоты шкалы, и ее расположение (top, left). Для того чтобы термометр растягивался вместе с контейнером, и шкала не съезжала все параметры должны быть указаны только в %.
Что касается красного круга - то это svg рисунок, он векторный, а значит может растягиваться без потери качества, rx/ry - радиусы эллипса, а позицию ему задает его контейнер, его также нужно выставить на нужное место.
Подключить к действующему сигналу для изменения шкалы градусника
Код:
function termoparameter(t,color){
document.getElementById('termometr').style.background = color;
document.getElementById('ellipce').style.fill = color;
document.getElementById('temperature').style.height = 50 - t + '%';
}
Параметр t - это и есть значение температуры, color - это цвет шкалы и эллипса. Значение сигнала можно получить как через интерфейс редактора, так и с помощью baseSdkUtils.service.WNConnectorHelper.getSignal, или вы получаете температуру каким-либо еще способом.
Вы можете использовать изображение, прикрепленное к этому посту, тогда настраивать ничего не нужно, только загрузить изображение в библиотеку и вставить код.
function setWidth() {
$('#541F4705-D07A-447F-B8AD-009CAFF336BD').css({
width: $('#FDBDB71A-029E-4C4F-9CB5-E4EEBA50771C').width()/2 + 'px', //ширина элемента
height: $('#FDBDB71A-029E-4C4F-9CB5-E4EEBA50771C').height()/2 + 'px', //высота элемента
top: '0px', // расстояние от верхнего края
left: $('#FDBDB71A-029E-4C4F-9CB5-E4EEBA50771C').width()/2 + 'px', // расстояние от левого края
overflow: 'hidden', // полоса прокрутки скрыта, элемент не выходит за край или обрезается
fontSize: $('#FDBDB71A-029E-4C4F-9CB5-E4EEBA50771C').height()/10 + 'px' // отдельно настраиваем размер шрифта
});
}
setWidth(); // устанавливаем высоту окна при первой загрузке страницы
$(window).resize( setWidth ); // обновляем при изменении размеров окна
Если есть необходимость настроить страницу так, чтобы при изменении размера окна она менялась, можно применить подобную функцию. Помимо этого можно вынести переменные в аргументы и применять одну функцию для всех элементов.
При создании резиновой верстки нужно чтобы все элементы имели размеры либо в процентах, либо в виде размера окна, умноженного на коэффициент, поскольку изначально все элементы имеют абсолютное позиционирование по умолчанию и размеры все задаются в пикселях. Пример готового универсального кода:
Код:
function setWidth( element, width , height , top , left , fontSize , over) {
// element - id html элемента
$('#' + element).css({
width: $( window ).width() * ( width /100) + 'px', // width ширина элемента %
height: $( window ).height() * ( height /100) + 'px', // height высота элемента %
top: $( window ).height() * ( top /100) + 'px', // top расстояние от верхнего края %
left: $( window ).width() * ( left /100) + 'px', // left расстояние от левого края %
overflow: over, // полоса прокрутки, элемент выходит за край или обрезается 'str'
fontSize: $( window ).height() * ( fontSize /100) + 'px' // fontSize отдельно настраиваем размер шрифта %
});
}
function setElastic() {
setWidth( 'mychart1', 50, 50, 0, 0, 5, 'hidden');
setWidth( 'mychart2', 50, 50, 0, 50, 5, 'hidden');
setWidth( 'mytable', 100, 50, 0, 0, 6, 'visible');
}
setElastic(); // устанавливаем высоту окна при первой загрузке страницы
$(window).resize( setElastic ); // обновляем при изменении размеров окна
Если в теге больше одного сигнала, то функция WNApplicationTagHelper.getLastTagValue возвращает значение только того сигнала, у которого стоит галочка "Определяет значение тега".
Если в теге заполнено поле "Значение при выполнении условий", то функция WNApplicationTagHelper.getLastTagValue возвращает то, что заполнено в этом поле.
Если нужно получать значения всех сигналов, то нужно последовательно выполнить getLastTagValue, затем получить имена сигналов тега через WNModelingHelper.getSignals, затем для каждого сигнала получить значения WNConnectorHelper.getSignal.
Ниже пример функции, которая вернет и текущее состояние тега, и значение, и значения каждого сигнала тега.
Код:
thisGetLastTagValue( appid, pid, tid, startTime, endTime, timeout_mills, function ( result ){
console.log ( result );
});
function thisGetLastTagValue( appid, pid, tid, startTime, endTime, timeout_mills, callBackFunc){
var result = {};
result.data = [];
baseSdkUtils.service.WNApplicationTagHelper.getLastTagValue(appid, pid, tid, endTime, timeout_mills, function( data ){
if ( baseSdkUtils.isSuccess( data ) ){
var item = data.getElementsByTagName('item')[ 0 ];
var tag_status = baseSdkUtils.decode(item.getAttribute('success'));
var tag_value = baseSdkUtils.decode(item.getAttribute('value'));
result.tagStatus = tag_status;
result.tagValue = tag_value;
thisGetSignals( pid , tid, function ( signals ){
if ( baseSdkUtils.isSuccess( signals ) ){
var index = 0;
for ( var i = 0; i < signals.getElementsByTagName('item').length; i++ ){
var item = signals.getElementsByTagName('item')[ i ];
var asciiPartNumber = baseSdkUtils.decode(item.getAttribute('asciiPartNumber'));
thisGetLastSignal( pid, asciiPartNumber, timeout_mills, function( signals_value ){
index++;
result.data.push( signals_value );
if ( index == signals.getElementsByTagName('item').length ){
if ( callBackFunc ){
callBackFunc.call( this, result );
}
}
});
}
}
});
}
});
}
function thisGetSignals( pid, tid, callBackFunct){
baseSdkUtils.service.WNModelingHelper.getSignals(
pid,
tid,
function( data ){
if ( callBackFunct ){
callBackFunct.call( this, data);
}
});
}
function thisGetLastSignal( pid, ascii, timeout_mills, callBackFunct){
var result = {};
var nDate = new Date();
baseSdkUtils.service.WNFactory.getPersistable( pid , function( product_data ){
if ( baseSdkUtils.isSuccess( product_data ) ){
var item = product_data.getElementsByTagName('item')[ 0 ];
var ProductUUID = baseSdkUtils.decode(item.getAttribute('ProductUUID'));
baseSdkUtils.service.WNConnectorHelper.getSignal(
ProductUUID,
ascii,
true,
true,
'',
'',
1,
function( data ){
if ( baseSdkUtils.isSuccess( data ) ){
var item = data.getElementsByTagName('item')[ 0 ];
result.value = baseSdkUtils.decode(item.getAttribute('value'));
result.event_time = baseSdkUtils.decode(item.getAttribute('event_time'));
result.signal_id = item.getAttribute('signal_id');
var event_time_date = Date.parse( result.event_time );
var diff = nDate.getTime() - event_time_date;
if ( diff > timeout_mills){
result.value = undefined;
}
if ( callBackFunct ){
callBackFunct.call( this, result);
}
}
}
);
}
});
}
Формат ответа функции thisGetLastTagValue следующий:
Функции WINNUM JavaScript SDK возвращает время сигналов в строку формата 'yyyy-MM-dd hh:mm: ss.SSS'.
При необходимости преобразовать в тип Date можно использовать Date.parse:
Код:
var currentTime = Date.parse( '2024-02-15 9:5:1' ); // вернет время в миллисекундах с 1970 года - 1707977101000
var currentDate = new Date( currentTime ); // вернет дату - Thu Feb 15 2024 09:05:01 GMT+0300 (Москва, стандартное время)
Функция baseSdkUtils.toDateObject дает некорректный результат, если часов, минут, или секунд меньше 10.
baseSdkUtils.toDateObject( '2024-02-15 9:5:1' ); // вернет ошибку