Клиентская часть(мобильный и веб-клиент)

Для всех видов клиентов соблюдаются единые подходы и единый синтаксис, но все же, учитывая разные задачи, которые возлагаются на клиенты и разный функционал есть отличия между мобильным и веб-клиентом.

Возможности и отличия веб-клиента и мобильного клиента

С узлами можно работать напрямую на сервере, используя веб-клиент, которой непосредственно подключен к БД узлов и работает с сервером узлов напрямую через локальное хранение и исполнение кода обработчиков сразу на сервере. И также можно работать через мобильное приложение на Android, причем при работе через Андроид-приложение (мобильный клиент) по умолчанию узлы скачиваются локально и обработчики узлов выполняются также локально на устройстве. Но при наличии связи это происходит мгновенно, образуя что называется «псевдо-онлайн». Однако при отсутствии связи узлы также продолжают функционировать - им не нужен сервер. Отсылку данных с мобильного клиента на сервер разработчик организует сам, как вариант, это может быть исполнение метода узла на сервере и передача ему данных.

Таким образом можно сказать, что главное отличие веб-клиента от мобильного клиента заключается в том что веб-клиент работает только чисто онлайн, мобильный - в основном локально, независимо от связи с сервером.

Репозиторий

_images/repo_web.png

Для того чтобы начать работать с конфигурацией, ее нужно сохранить локально для клиента в репозиторий. Это касается и веб-клиента, не смотря на то, что он чисто онлайн.

И мобильный и веб-клиент работают со своим набором конфигураций - репозиторием. Для веб-клиента, пользователь может установить те конфигурации, для которых у него есть доступ. Работа с обработчиками на сервере имеет особенность - при сохранении конфигурации, файл обработчиков конфигурации генерируется и клиент работает с ним непосредственно (т.е. не достает каждый раз из репозитория), при этом сама конфигурация храниться в репозитории и клиент работает с ней, в случае изменений надо обновить ее в клиенте. Такой подход позволяет отлаживать обработчики.

Работа с несколькими комнатами и регистрация в комнатах

В то время как мобильный клиент может быть подключен только к одной комнате, веб-клиент может регистрировать изменения в разных комнатах (мобильный клиент все же может использовать команду register для разных комнат). У каждого класса на закладке Миграция можно определить альяс комнаты по умолчанию, а в разделе конфигурации «Комнаты» можно сопоставить псевдонимам реальные комнаты конкретного инстанса, тогда для каждого класса изменения будут уходить в свою комнату к которой подключена своя группа мобильных клиентов.

_images/migration.png

На вкладке Миграция можно задать регистрацию при записи - при записи объекта в клиентах будет происходить регистрация в комнате по умолчанию для класса (для веб-клиента) или в комнате из настроек (для мобильного клиента).

Также можно включить «Команда для регистрации» - будет добавлена кнопка «Регистраци» в блоке стандартных команд.

Обложки для мобильного и веб-клиента

_images/covers.png

Узлы отображаются в разделах, в списках узлов и им нужно некое представление для пользователя, нужно выводить какие то данные. Помимо списков разделов узлы могут быть представлены в других элементах - списках, полях выбора и т.д.

У узла в классе есть поле Обложка (Cover), в ней можно задать макет, по общим правилам разметки (общие правила для экранов, обложек, диалогов и т.д.) и он будет применяться ко всем узлам.

Но для веб клиента можно задать особую обложку «Особая обложка для веб-клиента» - по тем же принципам, просто она будет отлична от мобильного клиента.

Также для веб-клиента можно задать обложку для табличного представления клиента в списке (в списке узлов есть переключатель вида) в формате Загловок|ключ

И наконец, для каждого узла в _data можно задать непосредственно обложку для конкретного узла с помощью свойства _cover. Это имеет наивысший приоритет и перекрывает предыдущие способы. С помощью _cover можно делать свои обложки для каждого узла, т.е. играться не только содержанием, но и формой.

Активные обложки

_images/active_cover.png

В обложках можно выводить не только данные или статическую разметку, но и активные элементы, генерирующие события (например кнопки, галочки). Особенность заключается в том, что эти события направляются в тот узел, к которому принадлежит обложка. Т.е. события обработаются непосредсвенно в этом узле, даже если это например список узлов, в каком то другом узле.

Стандартные команды

В клиентах можно разместить «Стандартные команды» включив в классе галочку «Использовать стандартные команды». Их можно сделать и вручную - элементами и обработчиками, но так проще.

Становятся доступны:

  • Сохранить и закрыть

  • Сохранить

  • Регистрация (если настроена)

  • Удалить

  • Закрыть

Пользователи

Для веб-клиента можно завести пользователей и разграничить их доступ в разделе Пользователи. По умолчанию, единственный пользователь имеет полный доступ. Вообще управлять доступом можно только с уровнем доступа Конфигуратор.

Сейчас доступно:

  • доступ на уровне модулей: веб-клиент, API (имеется ввиду прием REST-запросов), Конфигуратор.

  • доступ к конфигурациям - можно выбрать конфигурации, доступные пользователю. Влияет на доступность выбора конфигураций, при добавлении в Репозиторий, т.е. на список выбора конфигураций

Будет доступно (планируется):

  • доступ на уровне классов

  • доступ на уровне узлов (RLS)

Работа с аппаратным сканером штрихкодов

Для получения событий onBarcode на мобильном устройстве и десктопе используются разные подходы.

На мобильном клиенте (в случае работы на терминале сбора данных, ТСД) используется соединение с программой-драйвером штрихкодов через подписку на события сканера. В настройках надо указать имя события, переменную, и, опционально, длину поля (если программа-драйвер выгружает данные не в виде строки, а в виде байт-массива).

_images/barcode_web.png

В веб-клиенте на компьютере пользователя необходимо запустить драйвер, а в браузере настроить соединение с драйвером (драйвер поднимает веб-сокет как сервер локально на компьютере, браузер является его клиентом). Драйвер работает как сервис, резидентно, с запуском из командной строки.

Программу можно скомпиллировать pyinstaller под нужную ОС, скачав с GitHub или просто запустить под Python, установив зависимости. Либо можно скачать с exe-файл.

Параметры задаются в командной строке. По умолчанию настроен перехват в режиме эмуляции клавиатуры. Можно задать хост и порт.

Параметр –keyboard по умолчанию:

barcode_scanner_ws --keyboard --host 127.0.0.1 --port 8765

Для подключения через COM-порт:

barcode_scanner_ws --serial COM3 --baud 9600 --host 127.0.0.1 --port 8765

Общие функции мобильного и веб-клиента

Общее устройство интерфейса и событий

Все визуальные узлы (узлы данных и пользовательские процессы) располагаются в разделах.

_images/sections.png

Разделы задаются в секции конфигурации Разделы в виде ИД и заголовка. Также для раздела можно прописать команды, которые будут показаны в виде кнопок снизу раздела в виде списка через запятую <Заголовок команды>|<ид команды>. При нажатии на кнопку генерируется общее событие onStartMenuCommand в котором в качестве параметра передается ИД команды Класс узла в свою очередь относится к разделу через свойство Код раздела где нужно выбрать существующий раздел

Все узлы в разделах могут иметь обложку или так называемую обложку по умолчанию. Обложка задается в виде стандартной разметки (см. далее) в классе узла, но также может быть задана в _data (свойство «_cover») – таким образом узлы одного класса могут иметь разную разметку. И также может переопределяться для конкретного узла методом SetCover

_images/cover.png

При нажатии на узел в разделе открывается форма узла и генерируется событие onShow в котором можно прописать обработчик для отрисовки экрана, иначе он может оказаться пустым. С этого экрана пользователь может уйти, а потом вернуться (например открыв другой узел в другом экране), поэтому в таких случаях, в случае возврата на экран генерируется событие onResume в котором можно прописать тоже самое что и в onShow либо что то особенное.

Принципы разметки экрана и других визуальных форм.

Доступность: мобильный клиент и веб-клиент

В системе используется 2 альтернативных подхода к разметке, которые можно комбинировать. По умолчанию (будем называть это основной подход) используется принцип разметки «по строкам» т.е. список вертикальных «строк», каждая строка которого – это массив элементов в горизонтали (каждый элемент – визуальный объект какого то типа type). Понятно, что такой подход не исчерпывающий, но для большинства бизнес-приложений его хватает. Высоту и поведение как строк и элементов при желании можно настроить. Альтернативой этому можно считать разметку контейнерами – вертикальными, горизонтальными и вертикальными/горизонтальными скроллами. Причем оба подхода можно смешивать, а можно например просто в 1ю строку добавить контейнер и весь экран выстроить на контейнерах (полностью перешагнуть через разметку строками не удастся – разметка все равно должна иметь хотя бы 1 строку).

Для разметки экрана, обложки узла, диалога, элемента списка применяется единый подход в виде строки JSON-макета (в параметрах python-методов это внутренние типы list/dict) такого вида:

[ #общий вертикальный список сверху-вниз
[{"type":},{"type":}], #горизонтальная строка  элементов
[{"type":},{"type"":…}], #горизонтальная строка  элементов
...
]

Пример:

Layout1 =[
[{"type":"Table","id":"l1","value":table_data,"layout":table_layout }],
[{"type":"Button","id":"btn_repl","caption":"@caption"}]
]
В «строках» располагаются визуальные элементы. Каждый визуальный элемент имеет свойства:
  • «type» (string)- тип элемента,

  • «id» (string)- идентификатор элемента. Для активных элементов (с которыми взаимодействует пользователь) как правило генерируется событие со свойством listener, равным id элемента. Также по id можно обратиться к элементу и задать его свойства

  • «visible» (int) – видимость элемента: 1 – видим, 0- невидим (но занимает место на экране), -1- невидим (и не занимает место на экране)

  • «w» – вес элемента. Вес элемента по горизонтали/вертикали (в зависимости от родителя-контейнера) относительно других элементов. По умолчанию w=1. Пример: [{"type":"Button","id":"back","caption":"Back"},{"type":"Button","id":"next","caption":"Forward","w":2}] #кнопка next в два раза шире, потому что у нее w=2 back w=1 по умолчанию). В сочетании с высотой/шириной можно управлять разметкой. Зависит от поведения родительского контейнера. Если контейнер «по размеру элементов» то вес не имеет смысла, имеет смысл толкьо если «на всю ширину» или 0.

  • «width» – ширина. Может задаваться как в относительных числовых размерах, так и в виде абсолютных значений: -1 – на весь контейнер, -2 – по высоте элементов. Зависит от поведения корневого контейнера. Если контейнер имеет измерение «по ширине элементов», то «на всю ширину» не имеет смысла

  • «height» – высота. Может задаваться как в относительных числовых размерах, так и в виде абсолютных значений: -1 – на весь контейнер, -2 – по высоте элементов. Зависит от поведения корневого контейнера. Если контейнер имеет измерение «по ширине элементов», то «на всю ширину» не имеет смысла

  • padding - внутренние отступы

  • gravity - выравнивание по горизонтали. Значения: left, center, rigth

Правила по умолчанию такие:

  • Корневой вертикальный список растянут на весь экран без прокрутки.

  • Все элементы в строке по умолчанию имеют высоту «по высоте элементов», а сама строка растянута по ширине «на весь контейнер» (т.е. по горизонтали от края до края экрана) и не имеет веса.

  • Но можно поменять свойство горизонтальной строки с помощью добавления элемента «Parameters» с свойствами «w»,»width» и «height» – т.е. установить для горизонтальной строки (она является контейнером) вес ширину и высоту. Это важно, если требуется более сложная разметка.

Например в этой разметке нижняя строка занимает всю оставшуюся высоту, за счет того, что у нее вес = 1, тогда, в элементе Input в этой строке имеет смысл применить высоту = -1 (на всю высоту)

[
 [{"type":"Input","id":"title","caption":"Subject","value":"@title"}],
 [{"type":"Parameters","w":1},{"type":"Input","height":-1,"id":"body","caption":"Text","value":"@body","input_type":"multiline"}]
]

Сама горизонтальная строка является контейнером, но в нее также можно вписывать другие контейнеры. В принципе можно полностью отказаться от разметки строками и вывести только 1 строку с высотой – на весь экран, а внутри нее сделать все элементами -контейнерами.

Контейнеры нужны как для разметки так и для того, чтобы собрать несколько элементов в группу и управлять ими вместе (например видимостью)

Предупреждение

Крайне важно помнить – если вы используете контейнеры, у элементов обязательно надо задавать ширину и высоту. У самих контейнеров как правило тоже. И также возможно Parameters у «строки»

У контейнеров есть свойства:

  • Общие свойства (тип, вес, ширина, высота)

  • Свойство value – список элементов. Для Card в виде общей разметки «строками» (массив массивов)–[[]], а для остальных контейнеров – в виде массива/списка ( [] ) элементов. Которе соответственно выстраиваются по горизонтали или вертикали в зависимости от типа контейнера.

Существуют такие варианты контейнеров:

  • «VerticalLayout» – вертикальный не визуальный контейнер без прокрутки

  • «HorizontalLayout» – горизонтальный не визуальный контейнер без прокрутки

  • «VerticalScroll» – вертикальная прокрутка. Имейте ввиду, что прокрутка «уничтожает» свойства элемента «на всю высоту»

  • «HorizontalScroll» – горизонтальная прокрутка

  • «Card» – визуальный контейнер «карточка» служит для визуального оформления как элементов списка, так и группировки элементов на экране.

Статическая и динамическая разметка формы узла

Разметка узла может быть задана через команду Show (т.е. нужно сделать метод и подцепить его на событие onShow), переопределена другими динамическими методами. Этоназывается динамическая разметка Но иногда удобнее не делать этого, а просто указать в карточке класса. Это удобно если экран один. Если у узла есть какие то переключающиеся экраны, то их все равно придется выводить командой Show.Такая разметка называется статическая и есть несколько вариантов как ее задать:

  • Задать в классе узла непосредственно в виде разметки (JSON). Больше ничего не надо делать - узел при открытии отрисуется по этой разметке.

  • Создать элемент разметки (раздел «Общая разметка») и использовать его в классе. Там можно выбрать из имеющихся вариантов

  • В _data узла поместить разметку в ключ _layout. У этого варианта - наивысший приоритет.

Виды визуальных объектов и их уникальные свойства

Надпись

Доступность: мобильный клиент и веб-клиент

Вывод текстовой строки.

«type» : «Text»

«value»: строковая константа либо ссылка на переменную с префиксом @. Можно использовать html разметку (как и во всех остальных надписях)

«text_color» – цвет текста в HEX-формате (пример #F54927)

«radius» - закругление фона (число)

«stroke» - обводка линией (число)

«background» - цвет фона в HEX-формате (пример #F54927)

«size» – размер (целое число)

Картинка

_images/picture.png

Вывод картинки

«type» : «Picture»

«value» – абсолютный путь к файлу. Константа либо ссылка на переменную _data с префиксом @

Пример:

my_layout = [
[{"type":"Picture","value":"@image_path"}]
]

Слайдер изображений

_images/qs_photo_notes_imageslider.png

Вывод картинки

«type» : «ImageSlider»

«value» – список имен файлов (для мобильной - абсольюный путь, для веб-имя файла) либо ссылка на переменную в _data, содержащую такой список

«captions» - список заголовков (необязательный) либо ссылка на такой список

«cover» - (для web) - подстраивает пропорции под размер элемента

Пример:

my_slider =  { "type": "ImageSlider", "value": "@pic_files_web", "width": 100, "height": 100 , "cover":true, "captions":["1","2","3"]}

Особенности работы с изображениями в мобильном и веб-клиенте

Изображение (файл, медиафайл) всегда храниться на диске.

В веб-клиенте: в папке UserFiles<uid конфигурации> сервера

В мобильном : во внутренней папке приложения, связанной с конфигурацией

Ссылка на него (в элементе Picture, ImageSlider) задается в виде имени файла, но по разному – в мобильном – это абсолютный путь, в веб-клиенте – имя файла (он сам добавляет путь).

На сервере, разместив файл каким либо образом в папке конфигурации (помимо обработчиков, этом можно сделать через галереи Медиа и Файлы или просто через API) обратиться в элементе к нему можно через имя файла

На сервере, для примера реализовано два роута для работы с картинками:

POST /api/userfiles/<config_uid>/images принимает список файлов и сохраняет их в нужном формате. Пример обращения описан в Быстром старте

GET /api/userfiles/<config_uid>/raw/<path:filename> - получить картику по имени файла

Поле HTML

Доступность: мобильный клиент и веб-клиент

Вывод html-документа

«type» : «HTML»

«value» – строка в формате HTML

Кнопка

Доступность: мобильный клиент и веб-клиент

Вывод кнопки. При нажатии на кнопку экрана генерируется события с listener=<id элемента> . При использовании в списках см. раздел Активные элементы списков

«type» : «Button»

«caption» – надпись на кнопке

«small» - кнопка в виде маленькой круглой кнопки

«background» – цвет фона кнопки в HEX-формате

Пример: {"type":"Button","id":"btn_update","caption":"Simple button"}

Список нижних кнопок

Доступность: мобильный клиент

_images/bottom_buttons.png

Выводит горизонтальный массив кнопок внизу экрана. Только для размещения в экране.

«type» : «BottomButtons»

«value» – список кнопок с id. Параметры каждой кнопки такие же как у Button

Пример: {"type":"BottomButtons","id":"bottom","value":[{"type":"Button","id":"back","background":"#F54927","caption":"Back"},{"type":"Button","id":"next","background":"#25a018","caption":"Forward","w":2}]}

Переключатель

Доступность: мобильный клиент и веб-клиент

Элемент переключателя. Генерирует событие и пишет в переменную (id элемента) текущее значение переключателя.

«type»: «Switch»

«caption» –(строка) заголовок элемента

«value» –(булево) значение элемента для инициализации – в виде константы или ссылки на переменную. Как правило сюда ставят ссылку на id

Пример:

{"type":"Switch","caption":"Setting 1","id":"sw1","value":"@sw1"}

Галочка

Доступность: мобильный клиент и веб-клиент

Переключатель

Элемент переключателя. Генерирует событие и пишет в переменную (id элемента) текущее значение переключателя.

«type» : «CheckBox»

«caption» –(строка) заголовок элемента

«value» –(булево) значение элемента для инициализации – в виде константы или ссылки на переменную. Как правило сюда ставят ссылку на id

Пример:

{"type":"CheckBox","caption":"Setting 2","id":"cb1","value":"@cb1"}

Списки

Доступность: мобильный клиент и веб-клиент

_images/tables.png

Выводит различные списки элементов. В качестве наполнения можно использовать как список, созданный в обработчике так и датасет и список узлов класса. В элементах возможно размещение активных элементов. Для более красивого оформления элементы списка можно упаковать в Card. По умолчанию макет элемента – автогенерируемый, но его можно переопределить как для всего списка, так и для любого элемента.

«type»: «Table»

«value» – источник данных списка. Может быть определен как просто «список словарей» в python что делает его хорошо совместимым с NoSQL такими как Pelican например

Пример такого определения:

table_data = [{"name": "element 1", "key":"el1"},{"name": "element 2", "key":"el2"}]
{"type":"Table","id":"l1","value":table_data}

Либо (если используются датасеты) может быть указана ссылка на датасет:

{"type":"Table","id":"l2","value":"goods" }

Либо, если используется свойство nodes_source можно вывести список узлов (тогда при нажатии будет открываться узел).Это может быть поиск (отбор) узлов или просто какой то произвольный список. Узлы перечисляются в виде uid-ов. Преобразовать список объектов в список uid-ов можно с помощью функции to_uid

Пример (вывод списка всех узлов класса Note):

{"type":"Table","id":"t1","nodes_source":True,"value":to_uid(Note.get_all())}

«layout» – макет списка в целом в стандартном «строчном» формате.

Пример:

table_layout = [
       [{"type":"Text","value":"@_id","bold":True}],
       [{"type":"Text","value":"@name"},{"type":"Text","value":"@barcode"}]
       ]
{"type":"Table","id":"l1","value":table_data, "layout": table_layout }

_layout в элементе списка – делает то же что и layout только для конкретного элемента списка. Это свойство просто добавляется в источник данных:

table_data = [{"name": "element 1", "key":"el1","_layout":layout2},{"name": "element 2", "key":"el2"}]

_background в элементе списка – раскрашивает фон в заданный HEX-цвет

table_data = [{"name": "element 1", "key":"el1","_background":"#701705" },{"name": "element 2", "key":"el2"}]

«columns_count» – количество столбцов таблицы. Выводит список в виде нескольких столбцов

«horizontal» – горизонтальная ориентация списка.

«search_enabled» – включает режим поиска

Для датасета своя секция настроек поиска dataset_search в которой есть method(метод поиска), keys(поля поиска) и min_length (необязательно) минимальная длина с которой начинается поиск.

Метод может быть:

  • text - для обычного поиска по вхождению строки

  • levenshtein - для нечеткого поиска по расстоянию Левенштейна (будут выведены результаты в порядке убывания точности, с отбором >75, сама точность добавляется в записи в поле _confidence

Также для датасета всегда включена пагинация (невидимая) но можно настроить размер страницы – свойство page_size

Пример:

dataset_search = {"dataset":"goods","keys":"name","method":"text","min_length":2}
self.Show([
                   [ {"type":"Table","id":"l2","value":"goods" ,"search_enabled":True,"dataset_search":dataset_search}]
       ])

Вывод списка в виде таблицы

Доступность: мобильный клиент и веб-клиент

_images/flat_table.png

В объекте Table можно определить свойство table=true и использовать свойство table_header c заголовками в формате ["Заголовок|Ключ"] либо ["Заголовок|Ключ|Вес"] тогда список будет выведен в виде плоской таблицы со столбцами. В остальном все тоже самое - используются те же источники (в случае источников - узлов будет выведена не обложка а поля в соответствии с table_header)

Пример:

{"type":"Table","id":"tab4","value":lines,"table":True,
   "table_header":["#|n","Position|position","Qty|qty"]}

Список подчиненных узлов

Доступность: мобильный клиент и веб-клиент

_images/children.png

Выводит иерархический список подчиненных узлов и их потомков

«type» : «NodeChildren»

Без параметров

Закладки/Страницы

_images/tabs.png

Доступность: мобильный клиент и веб-клиент

«type»: «Tabs»

Группирует данные по закладкам (объектам «type»:»Tab»). Внутри каждой закладки – контейнер с о обычной строчной разверткой

Общий синтаксис такой: {«type»:»Tabs»,»value»:[

{«type»:»Tab»,»id»:»tab1»,»caption»:»Base elements»,»layout»:tab1_layout}, {«type»:»Tab»,»id»:»tab12»,»caption»:»List as Table»,»layout»:tab2_layout}

]}

У таб есть одно свойство - value - список закладок Tab

У каждой Tab есть:

  • id

  • caption

  • layout - собстввенно, содержимое страницы

Пример

tab1_layout = [
 [{"type":"Spinner","id":"my_spinner","caption":"my select:","value":"@my_spinner", "dataset":spinner_dataset}],
 [{"type":"NodeInput","id":"ni","value":"@ni","caption":"Заметка","dataset":"Note"}]
]
lines = [
 {"n":1,"position":"Position #1","qty":100},
 {"n":2,"position":"Position #2","qty":120},
 {"n":3,"position":"Position #3","qty":10}
]
tab2_layout = [
 [{"type":"Table","id":"tab4","value":lines,"table":True,
   "table_header":["#|n|1","Position|position|7","Qty|qty|1"]}]
]

self.Show([[{"type":"Tabs","value":[
 {"type":"Tab","id":"tab1","caption":"Base elements","layout":tab1_layout},
 {"type":"Tab","id":"tab12","caption":"List as Table","layout":tab2_layout}
]}]])

Доступность: мобильный клиент и веб-клиент

_images/inputs.png

Вся палитра полей ввода (кроме полей датасета, которые настраиваются аналогично) имеет тип Input – обычные поля, многострочные, выпадающие списки и т.д. Все введенные символы сразу записываются в _data узла. Если стоит автосохранение в классе узла то узел сразу же сохраняется при этом.

«type» : «Input»

«input_type» тип значений ввода (по умолчанию – просто текст, input_type можно не указывать):

  • NUMBER – числа. Причем числа, при записи в переменную автоматически распознаются на float/integer в зависимости от наличия дробной части

  • PASSWORD – ввод пароля (закрывается звездочками)

  • MULTILINE – многострочный текст

  • DATE – дата. При выборе даты в _data попадает 2 значения – представление даты (в переменную=id) и в _d<id элемента> - дата в формате ISO

«events» – флаг, подключающий генерацию событий при вводе в поле ввода

«value» – отображаемое значение по умолчанию

«caption» заголовок поля

«spinner_mode» – режим выпадающего списка. Необходимо задать в таком случае значение свойства source – список значений в виде строки с разделителем «;»

«autocomplete» -режим подбора по первым символам. Также должен быть задан source

_images/inputs.png

Доступность: мобильный клиент и веб-клиент

«type» : «Spinner»

Элемент позволяет отобразить датасет в виде выпадающего списка и получить выбранное значение.

Датасет элемента задается в виде списка словарей с обязательными полями:
  • _id ключ варианта

  • _view представление выбранного значения

Остальные поля могут быть любые, они не отображаются но участвуют в результате

В переменную, равную id возвращается _id из выбранного элемента. Этим же _id можно инициализировать элемент.

Пример:

spinner_dataset = [{"_id":"item1","_view":"item 1","some_key1":"some_value1"},
{"_id":"item2","_view":"item 2","some_key1":"some_value2"},
{"_id":"item3","_view":"item 3","some_key1":"some_value3"}]

{"type":"Spinner","id":"my_spinner","caption":"my select:","value":"@my_spinner", "dataset":spinner_dataset}
_images/node_input.png

Доступность: мобильный клиент и веб-клиент

«type» : «NodeInput»

«dataset» – имя класса узлов. При выборе будет открыта форма выбора с поиском, сортировкой, видимостью и обложками, заданными для данного класса.

Элемент позволяет выбрать узел в качестве значения в каком то другом узле, то есть указать ссылку на узел.

Значение возвращается/устанавливается в универсальном формате ссылки на узел <class_name>$<node_id>

Доступность: мобильный клиент и веб-клиент

Используются для выбора конкретного значения датасета на форме – элемента справочника, документа. Удобство заключается в том, что выбирается ссылка на объект по которой можно получить объект целиком или представление объекта. На картинке в разделе Поля ввода Production - поле датасета, в котором отображается имя продукции и код - такой задан шаблон

Представление объекта датасета задается в поле Шаблон записи в датасете. Там также можно использовать html

«type» : «DatasetField»

«dataset» – имя датасета

«spinner» – режим выбора из списка (исключает режим автоподстановки)

«autocomplete» – режим автоподстановки.

«caption» – заголовок поля

«value» – значение по умолчанию. Задается в виде ссылки на элемент датасета <имя датасета>$<id элемента датасета>

Доступность: мобильный клиент и веб-клиент

В разметка можно просто указать строку вместо объекта с type=Text, тогда будет выведена строка или , если указан префикс @ значение из _data, ["Hello world"]

Как вариант можно использовать строковую конструкцию <заголовок>|<значение> тогда также будет выведено Строкове значение но в виде своеобразной Card с заголовком ["title|Hello world"]

Подобные конструкции можно использовать для упрощения, вместо Text, Card

Активные элементы списков (в Таблица)

Доступность: мобильный клиент и веб-клиент

В разметке элементов списка можно использовать не только надписи и картинки но и некоторые активные элементы, например, кнопки. При взаимодействии с ними в узел поступают несколько другие (расширенные) данные чем при размещении в экране. Разработчик получает имя списка, имя активного элемента, позицию и значение (для элементов ввода значения)

Например Кнопка (Button). Переменные, при событии нажатия (onInput):

listener – поступает в формате <id таблицы>_input<id элемента>

<id таблицы>_input_position - в эту переменную возвращается позиция элемента списка, в котором произошло нажатие

Если в _data есть key тогда также возвращается ключ:

<id таблицы>_input_key - значение key элемента

Для полей ввода – CheckBox/Switch/Input добавляется еще само введенное значение в переменную _data - <id таблицы>_input<id элемента>

Доступны активные элементы:

  • Кнопка

  • Поля ввода (Input)

  • Галочка

  • Переключатель

Функции/методы UI/UX мобильного клиента

Методы узла (на устройстве)

Метод отрисовки экрана Show

Доступность: мобильный клиент и веб-клиент

Show(layout) отрисовка экрана на странице узла. Этот метод «очищает холст» и выводит разметку , принципы которой описаны в разделе «Разметка экрана». Как минимум его надо прописать в обработчике onShow чтобы что то появилось. Также он может быть вызван в других обработчиках чтобы обновить экран с новыми данными, перерисовать. Хотя для этого есть более экономная команда UpdateView, но иногда перерисовать целиком – проще.

self.Show([
 [{"type":"Input","id":"title","caption":"Subject","value":"@title"}],
 [{"type":"Parameters","w":1},{"type":"Input","height":-1,"id":"body","caption":"Text","value":"@body","input_type":"multiline"}]
])

Метод подключения механизмов экрана PlugIn

Доступность: мобильный клиент и веб-клиент(только указанные)

PlugIn(elements) подключает несколько элементов к экрану. Это и аппаратные механизмы, такие как аппаратный сканер и визуальные элементы вне разметки (иначе бы они были выведеные в Show). Параметр команды – одномерный массив объектов (в python- список) с объектами вида {«type»:»type_of_element»,»id»:»element_id»}.

Внимание

Команда очищает все элементы перед доабавлением (self.PlugIn([])- все очистит), поэтому в ней надо сразу перечислить все элементы.

Типы объектов(ключ type) бывают следующие:

**CameraBarcodeScannerButton* (только мобильный) – экранный элемент сканирования штрих-кода. Это кнопка на экране которая вызывает камеру для сканирования. При сканировании генеририруется событие с listener=<ИД элемента> и в _data[<ИД элемента>] будет помещен штрихкод

Пример:

self.PlugIn([{"type":"CameraBarcodeScannerButton", "id":"barcode_cam"}])

BarcodeScanner – подключение перехвата штрихкода с аппаратного сканера (для терминалов сбора данных). Аппаратный сканер должен быть настроен в Настройках в разделе Настройка аппаратного сканера. Должна быть активирована подписка на события аппаратного сканера и указаны данные, которые через Intent отдает ПО вашего сканера, а также в самом сканере надо настроить чтобы он отдавал штрихкоды в Intent broadcast а не в клавиатуру например. При сканировании генерируется событие с listener=<ИД элемента> и в _data[<ИД элемента>] будет помещен штрихкод

FloatingButton (только мобильный) – добавляет плавающую кнопку (их может быть несколько) справа внизу экрана. У этого элемента, помимо id (по которому как у всех элементов генерируется событие) есть поле caption для вывода текста кнопки и также есть поля, позволяющие настроить иконку svg (подробности смотри в разделе «svg-иконки»)

Пример:

self.PlugIn([{"type":"FloatingButton","id":"add_child","caption":"Add <b>line</b>"}])

ToolbarButton (только мобильный) – добавляет кнопку в тулбаре. У этого элемента, помимо id (по которому как у всех элементов генерируется событие) есть поле caption для вывода текста кнопки и также есть поля, позволяющие настроить иконку svg (подробности смотри в разделе «svg-иконки»)

Пример:

self.PlugIn([{"type":"ToolbarButton","id":"pin","caption":"Save","svg":svg2,"svg_size":24,"svg_color":"#FFFFFF"} ]])

PhotoButton (только мобильный) – открывает камеру для фотографирования. Снимок сохраняется в файл, путь к файлу – в _data[<ИД элемента>]. Если подключена MediaGallery то снимок автоматически добавляется в галерею. Автоматическое добавление можно отключить поместив флаг OFFAutoupdateMediaGallery в _data узла. Тогда фото будет перехватываться в обработчике и с ним можно проводить какую то обработку перед добавлением в галерею.

Пример ручной обработки

def CaptureImage(self, input_data=None):
       gallery_array = self._data["pic_files"]
       base64 = getBase64FromImageFile(this._data["result_file"],50,50) #ни к чему не ведет, просто получили кроп в base64
       gallery_array.append(this._data["result_file"])
       toast(self._data["result_file"])
       UpdateMediaGallery(gallery_array )

       return True,{}

GalleryButton (мобильный, ожидается на веб) – открывает камеру для прикрепления медиафайла из галереи. Снимок сохраняется в файл, путь к файлу – в _data[<ИД элемента>]. Аналогичен PhotoButton.

MediaGallery (мобильный, ожидается на веб) – галерея массива меддиафайлов внизу экрана с возможностью отркытия, удаления. По ключу = id элемента хранится массив путей к файлам, который отображается в галерее. С ним по умолчанию автоматически взаимодействуют PhotoButton и GalleryButton но также этот массив можно редактировать из обработчика (например поместить туда фото). Также доступно удаление пользователем. После удаления генерируется событие <имя_переменной>_delete в котором можно например забрать измененный массив файлов галереи.

_images/inputs.png

Метод открытия узла в интефейсе _open()

Доступность: мобильный клиент и веб-клиент

_open(method=None) – открывает форму узла, как если бы его открыл пользователь. По умолчанию сработает onShow и метод, прописанный в конфигурации, но его можно переопределить в параметре method

Метод обновления элементов на экране UpdateView

Доступность: мобильный клиент

UpdateView(id,element=None) – команда для выборочного обновления элементов. Рекомендуется использовать для высоконагруженных интерфейсов (например ActiveCV)

Работает в нескольких режимах:

  1. Просто перерисовывает элемент по id. Это имеет смысле если значение элемента задано переменной (через @) а не константой. Пример: self.UpdateView("btn_repl",None)

  2. Меняет свойства элемента. Пример: self.UpdateView("btn_repl",{"background":"#C82909"})

  3. Заменяет элемент на другой элемент. Можно например проделывать это с контейнерами. Пример : self.UpdateView("btn_repl",{"type":"Input","id":"inp1","caption":"New input---"})

Общие методы UI/UX для клиента

SetTitle (title) (мобильный клиент) – устанавливает заголовок окна узла

RefreshTab() (мобильный клиент) – обновляет текущую вкладку(раздел), из которой открыт узел. Имеет смысл когда узел добавили/изменили и возвращаемся в список

CloseNode() (мобильный клиент) – закрывает форму узла

RunGPS/ StopGPS (мобильный клиент) – команды запуска/остановки GPS. Может потребоваться разрешение от пользователя. Запущенный GPS считывает данные с устройства через промежутки времени и первые данные будут не сразу, а также может меняться точность, поэтому для чтения данных GetLocation лучше использовать таймер

GetLocation (мобильный клиент) – получает данные GPS в формате сериализованного в строку JSON-объекта с полями:

altitude – высота latitude – широта longitude – долгота accuracy – точность, в метрах provider – провайдер данных

ScanBarcode(listener) (мобильный клиент) – запускает сканирование камерой, как если бы пользователь нажал на кнопку сканера, подключенную через PlugIn. По результату возвращается событие, указанное в параметре.

Dialog(id,title,yes_caption=»»,no_caption=»»,layout=None) (мобильный и веб-клиент) – метод для показа как простых диалогов, так и диалогов с разметкой. Минимально – только заголовок, также можно переопределить 2 кнопки и сделать любую разметку layout по общим правилам разметки.

_images/dialog.png

Возвращает событие с listener=<id>_positive/<id>_negative в зависимости от того, какую кнопку нажал пользователь. Если диалог с разметкой, и там есть какие то инпуты, то данные пишутся прямо в _data

Примеры:

def simple_dialog(self, input_data=None):
       Dialog("dlg1","To be or not to be?","To be","Not to be")

       return True,{}


def layout_dialog(self, input_data=None):
       Dialog("dlg2","Enter quantity","Ok",None,[[{"type":"Input","id":"qty_dialog","caption":"Quantity","input_type":"number"}]])

       return True,{}


def input(self, input_data=None):
       if self._data.get("listener") == "dlg1_positive":
         toast("To be")
       elif self._data.get("listener") == "dlg1_negative":
         toast("Not to be")
       elif self._data.get("listener") == "dlg2_positive":
         toast(str(self._data.get("qty_dialog") ))

AddTimer(key, period)/ StopTimer(key) – запускает/останавливает таймер с нужным ключом.

Пример:

AddTimer("my_timer",5)

ShowProgressButton/ HideProgressButton – рисует колесико прогресс-бара на кнопке и делает ее неактивной пока не будет выполнено HideProgressButton

Пример:

def worker():
   time.sleep(2)
   HideProgressButton("button1")

ShowProgressButton("button1")
t = threading.Thread(target=worker)
t.start()

ShowProgressGlobal/ HideProgressGlobal (мобильный клиент) – показывает общий (закрывающий весь экран) прогресс-бар/ снимает его.

SetCover(node,layout) – устанавливает новый макет для узла. Это именно макет, а не данные. Если у вас значения в макете заданы через @ - они и так обновятся.

UpdateMediaGallery() (мобильный клиент) – обновляет галерею. Нужно выполнять после какиз либо действий с составом галереи.

Функции модуля android (мобильный клиент)

Интерфейсные команды:

  • toast(String toast) – вывести сообщение Андроид

  • message(String text) – вывести сообщение

  • speak(String text) – произнести текст (TTS engine)

  • listen() – запустить ожидание распознавания речи

  • vibrate() и vibrate(int duration) – вибрация и вибрация заданной длительности

  • beep()/beep(int tone)/ beep(int tone,int beep_duration,int beep_volume) – звуковой сигнал, т.ч. с возможностью выбрать тон (от 1 до 99), продолжительность и громкость (по умолчанию – 100)

  • notification(String message)/ notification (String message,String title)/ notification(String message,String title,int number) – уведомление в шторке уведомлений. Number – идентификатор уведомления, по которому к нему можно потом обратиться, чтобы либо убрать, либо перезаписать (обновить)

  • notification_progress(String message,String title,int number,int progress) – уведомление с прогресс-баром (от 0 до 100) notification_cancel(int number) – скрыть уведомление

Некоторые общие свойства элементов

html текст в надписях

Везде, где в интерфейсе присутствует текст, можно использовать html-теги для разметки текста. Например Привет <b>мир</b>

svg-иконки

_images/svg.png

Для элементов, где предусмотрены иконки, можно использовать иконки svg. Способ такой:

  1. Скачиваете и сохраняете svg-файл

  2. Открываете файл текстовым редактором и копируете текст в переменную

  3. Можно использовать эту переменную в свойстве элемента svg (также можно установить размер и цвет svg_size и svg_color)

svg1 = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="m720-120 160-160-56-56-64 64v-167h-80v167l-64-64-56 56 160 160ZM560 0v-80h320V0H560ZM240-160q-33  0-56.5-23.5T160-240v-560q0-33 23.5-56.5T240-880h280l240 240v121h-80v-81H480v-200H240v560h240v80H240Zm0-80v-560 560Z"/></svg>'
self.PlugIn([
         {"type":"FloatingButton","id":"add","svg":svg1,"svg_size":48,"svg_color":"#000000"}
   ])

Функции для преобразования изображений и галереи

getBase64FromImageFile(path_to_image) - возращает строку base64 из файла.

convertImageFilesToBase64Array(paths_to_images_array) - удобно для конвератции «Галереи» в одну строку: конвентирует массив путей в массив строк base64

saveBase64ToFile (base64_string) - сохраняет base64 строку во временный файл и возвращает путь к нему

convertBase64ArrayToFilePaths - сохраняет массив строк base64 в масисив путей (временных файлов), подходящий для использования в Галерее.

Общие события клиента. Обработка событий приложения.

Некоторые события возникают не в узлах, а в целом в приложении, например событие при запуске платформы, при сканировании штрихкода вне узла и т.д. Они обрабатываются по тому же принципу что и события узлов – то есть имеют назначенное событие и обработчик, только не в структуре узла, а в структуре конфигурации на вкладке Общие события (в конфигурации это раздел CommonEvents)

Обработчики python для подобных событий, соответственно прописываются не в классах узлов, а просто как функции в коде обработчиков. Некоторые события также содержат данные, они передаются в input_data в соответствующий ключ/

Пример:

def onBarcode(input_data):
   toast(input_data.get("barcode"))
   return True,{}

Они должны иметь параметр input_data типа словарь.

Перечень типов общих событий (мобильный клиент):

  • onLaunch – событие при загрузке конфигурации (либо при перезапуске). Без параметров.

  • onTimer – событие таймера. В input_data ключ “timer_key” – ключ сработавшего таймера

  • onJSONFile – событие открытия JSON-файла приложением (через Открыть или Поделиться). В input_data ключ «content» – содержимое файла в виде строки

  • onTextFile – событие открытия текстового файла приложением (через Открыть или Поделиться). В input_data ключ «content» – содержимое файла в виде строки

  • onBarcode – событие штрихкода, сканируемого через ScanBarcode т.е. вне узла, из главного меню

  • onStartMenuCommand – нажатие на команду в разделе конфигурации (те команды, что добавляются через Разделы конфигурации)

  • onDialogResult – событие диалога, вызванного из главного меню. Т.к. диалог вызывается с определенным идентификатором то в result возвещается либо result_positive либо result_negative а в ключ result_data возвращается данные из элементов ввода диалога, если таковые были.

Перечень типов общих событий (веб-клиент):

  • onBarcode – событие штрихкода, сканируемого через ScanBarcode т.е. вне узла, из главного меню

  • onStartMenuCommand – нажатие на команду в разделе конфигурации (те команды, что добавляются через Разделы конфигурации)

  • onDialogResult – событие диалога, вызванного из главного меню. Т.к. диалог вызывается с определенным идентификатором то в result возвещается либо result_positive либо result_negative а в ключ result_data возвращается данные из элементов ввода диалога, если таковые были.

Поиск, сортировка и видимость узлов (клиент)

В мобильном клиенте NodaLogic есть встроенные механизмы поиска, сортировки и скрытия узлов, которые работают на стороне клиента и не требуют дополнительного кода или API.

Эти механизмы управляются через специальные служебные поля в _data узла.

Поиск по узлам

Поведение по умолчанию

Если в узле не задано специальное поле для поиска, приложение выполняет поиск:

по всем ключам словаря _data

поиск осуществляется по строковому представлению значений

Поле _search_index

Чтобы управлять поиском явно и повысить производительность, рекомендуется использовать поле:

_search_index

Если оно задано:

  • поиск выполняется только по этому полю

  • поиск становится быстрее и более контролируемым

Пример

self._data["_search_index"] = (
   self._data.get("number", "") + " " +
   self._data.get("customer_name", "")
)

_search_index должен быть обычной строкой

Сортировка узлов

Сортировка списков узлов также выполняется на стороне клиента.

Для этого используются специальные поля:

_sort_string — сортировка по возрастанию

_sort_string_desc — сортировка по убыванию

Пример сортировки

Сортировка по дате (по возрастанию):

self._data["_sort_string"] = self._data.get("plan_date")

Сортировка по дате (по убыванию):

self._data["_sort_string_desc"] = self._data.get("created_at")

Скрытие узлов

Узел можно скрыть из интерфейса, не удаляя его физически.

Для этого используется поле:

_hidden

Если установлено значение True:

узел не отображается в списках

узел остаётся доступным через код и API

Пример скрытия узла

self._data["_hidden"] = True