COMBO LOOKUP NITRO UI

Категорії: дизайн та імплементація контрольних елементів NITRO, технічне завдання.

Покажемо на прикладі #comboLookup як створювати контрольні елементи у середовищі N2O NITRO. #comboLookup — це контрольний елемент для пошуку та відображення результатів в універсальних гетерогенних персистентних віддалених послідовностях, з порціонною видачею результатів та підтримкою персональних курсорів.

Дизайн

Скафолдінг контрольного елементу

Контрол стандартно постачається у 3—4 файлах:

— priv/css/comboLookup.css — CSS частина
— priv/js/comboLookup.js — JavaScript частина
— include/comboLookup.hrl — спільний код для Erlang та Elixir
— src/comboLookup.erl або lib/comboLookup.ex

Розробка компонент

Контрол структурується як: 1) поле вводу для пошуку по зовнішньому віддаленому сервісу; 2) контейнер для результатів пошуку; 3) елементи результатів пошуку (насправді спеціалізовані редактори вводу відповідних елементів списку результатів пошуку); 4) управляючі елементи: кнопка пошуку та кнопка вібірни наступної порції даних через серверний курсор пошуку.

Кожна компонента 1—4 повинна бути окремим DOM елементом, а елементи результатів пошуку (4) повинні мати свої Erlang або Elixir визначення, так як ці форми відображають безпосередньо дані пошукових фідів, і таким чином є обов'язковими.

Розробка протоколу

За основу пропонується взять наступний протокол управління контрольним елементом:

#comboInit{name=[],feed=[]}, де name — ім'я курсору. Серверний код ціього повідомлення повинен буде визначити по імені курсору певний feed в базі даних аби переконатися що курсор існує, для подальшого пошуку по ньому. В JavaScript частині контролу при відсилці comboInit повинен містити поточне збережене значення з localStorage, унікальне для всіх додатків на базі цього контрольного елементу.

#comboSearch{prefix=[]}, де prefix — це введені користувачем символи для повнотекстового або довільного пошуку по фіду на сервері. Серверний код цього повідомлення повинен виконати lookup по базі даних та пересунути курсор на кількіть елементів у первій видачі.

#comboNext{pos=[],count=[]}, повторний запит на видачу результатів попередньо виконаного пошуку, або просто видача даних з пересуненням курсору на кількість елементів виданій у цьому запиту.

Як і кожне повідомлення протоколу NITRO, результати обробки протоколу контрольних елементів повинні бути IO повідомленнями {io,Code,Data}, де Code — це JavaScript з відрендереними на сервері HTML та JavaScript частинами форми, які вбудовують результати видачі у контейнер (2). Тому у тілі функції протоколу контрольного елементу ми формуємо actions (які будуть скинуті в сокет протколом NITRO в повідомленні IO) за допомогою :nitro.wire, або іншої функції з побічними ефектами які генерують JavaScript в контексті Erlang процесу.

Імплементація

Визначення бізнес-об'єкту

Для можливості користуватися одночасно Erlang та Elixir, контрольний елемент на базі HTML5 (ELEMENT_BASE) повинен визначатися в HRL файлі.

$ cat include/comboLookup.hrl -record(comboInit, { name=[], feed=[]}). -record(comboSearch, { prefix=[]}). -record(comboNext, { pos=[], count=[]}). -record(comboLookup, { ?ELEMENT_BASE(nx_comboLookup), feed="/erp/people", reader=[], chunk=20 }).

Де "/erp/people" — адреса фіду на сервері (можна подивитися за допомогою :kvs.all)

Рендер, бінд та налаштування івентінгу

Оскільки контрольні елементи породжують HTML та JavaScript, рендер контрольного елементу по module полю — це саме те місце, де зазвичай підключаються шаблонізатори, як то: DTL, JSP, ASP, PHP. У нашому випадку ми рекурсивно використовуємо бібліотеку контрольних елментів NITRO, поки повний рендер починаючи з верхнього не буде завершено, або природній Erlang шаблонізатор з мовою Erlang (або Elixir).

$ cat src/nx_comboLookup.erl -module(nx_comboLookup). -include("comboLookup.hrl"). -include("erp.hrl"). -include_lib("nitro/include/nitro.hrl"). -include_lib("nitro/include/event.hrl"). -export([render_element/1,proc/1]). render_obj(#'Person'{cn=CommonName,type=Type}=Person) -> #panel{ body=[ #image { src = "default.png" }, #h1 { body = CommonName }, #p { body = Type } ] }. render_element(#comboLookup{}=Record) -> nitro:wire("console.log(\"Hello comboLookup!\");"), "<input id=\"comboContainer\" type=\"comboLookup\"></input>".

Імплементація протоколу

Завдання написати найбільш компактну імплементацію з індивідуальними курсорами для кожного екземпляру контрольного елементу на стороні клієнта.

proto(#comboInit{feed=Feed,name=Name}) -> kvs:save(kvs:reader(Feed)), n2o:session(Name,Feed), ok; proto(#comboSearch{prefix=Name}) -> Feed = n2o:session(Name), lists:map(fun (X) -> nitro:insert_top(comboContainer, render_obj(X)) end, lists:all(fun(#process{name=N}) -> N == Name end, kvs:all(Feed))), ok; proto(#comboNext{pos=Pos,count=Count}) -> Reader = kvs:reader(Feed), ok.

Підключення протоколу до модулю сторінки:

Позаяк протокол сторінки — це кінцева точка усіх клієнтських повідомлень NITRO протоколу, аби доставити повідомлення в модуль контрольного елементу потрібно підключити протокол comboLookup в модуль сторінки з відповідними форвардами.

$ cat src/container.erl event(#comboInit{} =X) -> nx_comboLookup:proto(X); event(#comboSearch{}=X) -> nx_comboLookup:proto(X); event(#comboNext{} =X) -> nx_comboLookup:proto(X).

Розробка клієнтської частини

Мінімальне публічне API контрольного елементу.

$ cat priv/js/comboLookup.js function direct(term) { ws.send(enc(tuple(atom('direct'),term))); } function comboId() { return localStorage.getItem("comboLookup-1218297")||'';}; function comboInit() { direct(tuple(atom('comboInit'),string(comboId()))); } function comboSearch() { direct(tuple(atom('comboSearch'),'Maxi')); } function comboNext() { direct(tuple(atom('comboNext'),1,1)); }

Специфікація на стилі

$ cat priv/css/comboLookup.css

Для самостійного опрацювання.

Додаток 1

Перелік функцій на стороні JavaScript для реалізаціі мінімального клієнта N2O NITRO протоколу:

token() — поточний токен сесії
qi(name) — DOM елемент по існуючому id
qs(name) — пошук DOM елементу
qn(name) — створення нового DOM елементу
is(x, num, name)
co(name) — отримання cookies (застаріло, використувуємо localStorage)
querySource(name) — отримання преформатованої змінної поля value
validateSources(list) — провалідувати поле value для списку елементів
N2O_start() — створення веб-сокет каналу
$bert — реалізація бінарного берт протоколу на JavaScript
$io — IO протокол (евалуатор та помилки)
$file — FTP протокол (трансфер великих файлів до 100ГБ)
$ws — вебсокет транспорт
transports — транспорти
protos — протоколи




NITRO — Загальна інформація про веб-фреймворк NITRO
N2O — Опис контейнера протоколів та сервісів N2O
N2O NITRO — Опис протоколу веб-фреймворку
BERT.JS — Опис бінарного протоколу