Skip to content

Repository files navigation

oscript-ui

UI-кит для веб-приложений на чистом OneScript: компоненты, каркас форм, дерево узлов экрана, токены темы, иконки и собственный базовый лист стилей. Классы кита — желуди ОСени, но зависимости на неё у библиотеки нет.

Зачем. Чтобы можно было декларативно рисовать веб-приложения на оскрипте.

Из коробки. Поставленный в чужой проект, кит выглядит прилично без единой строки CSS от потребителя. Всё, что проект хочет изменить — правило, файл статики, палитру, — он переопределяет штатно, не трогая библиотеку: своё правило вторым листом в каскаде, свой файл одноимённым объявлением, свою тему и акцент контрактом оформления.

Одна навигация. Инсталляция описывает свои дороги ОДИН раз — слотом МенюХаба в обрамлении, — а кит показывает их по ширине экрана. На широком шапка выглядит как обычно: знак, дороги, переключатель оформления и учётка справа; учётка же и открывает панель. На телефоне полоса ходов прячется, слева появляется бургер, и панель выезжает шторкой на всю высоту. Панель одна на обе ширины и содержимое несёт одно: учётка, дела с числами, дороги, разделы текущего экрана, тема и выход. Выглядит она по месту: на широком — компактным выпадающим меню у кнопки, на телефоне — шторкой с крестом. Открытие, закрытие, Esc и щелчок мимо делает движок браузера — скрипта в ките нет ни строки. Слот не заполнен — шапка остаётся прежней целиком, до последнего байта.

Меню строки и учётки. Шестерёнка в строке таблицы и кнопка-учётка — один компонент Меню, и панель у них ведёт себя одинаково, по тем же правилам, что и единое меню. На широком экране панель выпадает у самой кнопки, а места не хватит — отзеркаливается; пункты идут строками списка, опасный узнаётся красной подписью. На телефоне панель к кнопке не привязана: она встаёт листом у нижнего края окна, сверху подпись меню (чьи это действия), пункты под палец, страница под листом затемнена. Вид пунктов задаёт меню: ссылка, кнопка окна и форма из одной кнопки выглядят одинаково, какой бы вариант кнопки ни пришёл от вызывающего; форма с полями остаётся формой.

Таблица карточками. Кит.Таблица("карточками") на узком экране (до 720 px) печатает запись карточкой: шапки у неё нет, и ячейка сама несёт подпись своей колонки — приглушённой строкой над значением. Подписи нет у первой, заглавной ячейки, у ячейки с меню строки (на любой глубине) и у пустой; пустая ячейка на узком экране места не занимает. Колонка, чьи значения называют себя сами («занято 12 КБ из 100», плашка «активна»), от подписи отказывается явно — методом Кит.Колонка("…").БезПодписиВЗаписи(): подпись над таким значением была бы дублем. Угадывать это по виду значения кит не берётся — плашка бывает и состоянием, и названием. Узкий столбец (Кит.Ячейка("узкая")) в записи растягивается на всю ширину карточки.

Признак у имени. Шапка объекта (ШапкаОбъекта) принимает в модели поле Признаки — массив структур Иконка, Подпись. Каждый признак печатается значком сразу за именем, в той же строке заголовка, а не в ряду плашек: глиф из набора иконок размером с букву имени, по средней линии. Подпись обязательна: она печатается <title> значка (role="img") и служит сразу доступным именем и всплывающей подсказкой; признак без подписи отвергается исключением, с неизвестным глифом — не печатается. Значок держится за последний символ имени (класс objhead__tail, сам значок — objhead__mark) и на новую строку один не уходит; перенос внутри имени прежний. Без признаков шапка печатается как раньше, байт в байт.

Известный долг. Компонент «Меню» (меню строки и учётки) выдаёт идентификаторы панелей сквозным счётчиком процесса, поэтому страница со строчным меню от запроса к запросу отдаёт разную разметку — побайтовое сравнение ответов на ней не работает. Единого меню инсталляции это не касается: его идентификатор постоянен. Подробности — в комментарии у счётчика идентификаторов, src/Классы/компоненты/Меню.os, функция СледующийИдентификатор.

Установка

opm install oscript-ui

В packagedef приложения:

.ЗависитОт("oscript-ui", "0.8.0")

И подключаем:

#Использовать oscript-ui

Мини-пример

// Весь экран собирается через него
Кит = Поделка.НайтиЖелудь("Кит");

// Холст — прозрачная группировка. на холсте мы размещаем наши элементы
Холст = Кит.Холст();

// Заголовок страницы и пояснение под ним
Холст.Добавить(Кит.ЗаголовокСтраницы(Новый Структура("Заголовок, Пояснение",
	"Проекты", "Какое-то пояснение про проекты")));

// Поиск — готовая GET-форма: js нет, по кнопке запрос улетает на сервер.
Холст.Добавить(Кит.СтрокаПоиска(Новый Структура("Действие, Метка, Плейсхолдер, Кнопка, Иконка",
	"/проекты", "Поиск", "имя проекта", "Найти", "поиск")));

// Рисуем таблицу.
Таблица = Холст.Добавить(Кит.Таблица());
Таблица.Добавить(Кит.Колонка("Имя"));
Таблица.Добавить(Кит.Колонка("Состояние"));
Таблица.Добавить(Кит.Колонка("Обновлён"));
// Если в таблице строк нет - напишем так.
Таблица.Пусто().Добавить(Кит.Примечание("Ничего не найдено"));

// Какие-то данные для таблицы; состояние — сразу готовая модель плашки
Проекты = Новый Массив();
Проекты.Добавить(Новый Структура("Имя, Состояние, Обновлён",
	"альфа", Новый Структура("Текст, Вид", "открытый", "ok"), "вчера"));
Проекты.Добавить(Новый Структура("Имя, Состояние, Обновлён",
	"отчёт", Новый Структура("Текст, Вид", "устаревший", "warn"), "в марте"));

// Добавим записи таблицы
Для Каждого Проект Из Проекты Цикл
	Запись = Таблица.Добавить(Кит.Запись());
	Запись.Добавить(Кит.Ячейка()).Добавить(Кит.Код(Проект.Имя));
	Запись.Добавить(Кит.Ячейка()).Добавить(Кит.Плашка(Проект.Состояние));
	Запись.Добавить(Кит.Ячейка()).Добавить(Кит.Текст(Проект.Обновлён));
КонецЦикла;

// Под таблицей — пагинация.
Холст.Добавить(Кит.Пагинация(Новый Структура("Страница, ВсегоСтраниц, Основа",
	1, 3, "/проекты")));

// После построения - рендерим html.
HTML = Холст.Рендер();

С оформлением из коробки эта страница выглядит так:

Страница «Проекты», собранная китом

Все вы восхитительны и только что нарисовали свое веб-приложение.

Холст и Ничего

Два вызова возвращают одинаковый узел и печатаются одинаково — пустой строкой, пока в них ничего не положили. Разница видна только человеку, который читает код экрана, — и в ней весь смысл:

  • Кит.Холст() — корень куска экрана: его наполняют и печатают целиком;
  • Кит.Ничего() — место, где показывать нечего: его не наполняют, он и есть ответ.

Ложится Ничего() всюду, где принимают холст, — но не в обход правил вложения: внутрь записи кладут ячейку, внутрь таблицы — запись, внутрь списка — пункт.

Функция БлокЧерновика(Знач Статья)

	Если Статья.Черновик Тогда
		Возврат Кит.Ничего();   // на странице этого места просто не будет
	КонецЕсли;

	Блок = Кит.Холст();         // а здесь собираем кусок экрана
	Блок.Добавить(Кит.Заголовок(3)).Добавить(Кит.Текст("Как читать"));
	Блок.Добавить(Кит.Примечание(Статья.Пояснение, "описание"));
	Возврат Блок;

КонецФункции

На Возврат Кит.Ничего(); читатель останавливается: элемента здесь нет, идти больше некуда. На Возврат Кит.Холст(); он поднимается по функции вверх — искать, чем этот холст наполнили, — и не находит ничего.

Почему не «Пустота» и не «Пусто»: слово «пусто» кит уже трижды занял ВИДИМЫМ смыслом — компонент ПустоеСостояние, вид примечания «пусто» и Таблица.Пусто(). Все три что-то показывают человеку, и четвёртое имя того же корня значило бы прямо обратное.

Кнопка, которая отправляет форму на свой адрес

У одной формы бывает несколько кнопок, и не все отправляют её туда же, куда сама форма. АдресОтправки — адрес, на который кнопка отправки шлёт свою форму вместо адреса формы (атрибут formaction). Поля формы по-прежнему едут телом запроса, адрес называет только, куда их принять. Адрес — своего приложения, относительный (/…, ?…, #…); иначе — исключение. Пустой адрес атрибута не даёт, у кнопки, которая форму не отправляет (Тип = "обычная", Команда), и у ссылки-кнопки он не печатается.

ВНовойВкладке = Истина открывает ответ на отправку в новой вкладке (атрибут formtarget="_blank"): страница с заполненной формой остаётся как была, и введённое, включая поля, которые сервер обратно не печатает, не теряется. У кнопки, которая форму не отправляет, и у ссылки-кнопки атрибута нет.

// «Сохранить» отправляет форму на её адрес, «Предпросмотр» — ту же форму на другой
Форма.Добавить(Кит.Кнопка(Новый Структура("Подпись", "Сохранить")));
Форма.Добавить(Кит.Кнопка(Новый Структура("Подпись, Вариант, АдресОтправки, ВНовойВкладке",
	"Предпросмотр", "вторичная", "/статьи/черновик?вид=предпросмотр#текст", Истина)));

Карточка ссылки

Ссылку на страницу мессенджеры и соцсети показывают карточкой: заглавие, описание, картинка. Строят её по свойствам Open Graph в голове страницы. КаркасСтраницы.Страница принимает их пятым аргументом — массив структур Свойство, Содержание — и печатает в голове за заглавием вкладки объявлениями <meta property="…" content="…"> в порядке массива; свойство и содержание экранируются. Пара с пустым свойством или содержанием не печатается, без карточки голова прежняя, байт в байт. Адреса в карточке краулер ждёт абсолютными — их собирает страница, кит адресов не знает. Отдельное объявление собирает Кит.МетаСвойство(Свойство, Содержание).

Карточка = Новый Массив();
Карточка.Добавить(Новый Структура("Свойство, Содержание", "og:title", "Проекты"));
Карточка.Добавить(Новый Структура("Свойство, Содержание",
	"og:image", "https://пример.рф/проекты.png"));

HTML = Каркас.Страница("Проекты", Холст, Каркас.КонтекстСтраницы(Пользователь, Куки),
	Неопределено, Карточка);

Действия формы одним рядом

Действия записи собираются в один ряд — Кит.Ряд("действия"), — даже когда это разные формы: «Сохранить» своей формы и «Удалить», «Выключить» — формы из одной кнопки. Кнопка отправки, стоящая вне своей формы, называет её якорем: у формы — .Якорь("…") (атрибут id, его же даёт любой узел), у кнопки — поле Форма (атрибут form). Это обычный HTML: без скрипта, Enter в поле формы тоже отправляет её этой кнопкой. У кнопки, которая форму не отправляет, атрибута нет.

Правка = Кит.Форма("/рассылки/7", Токен, "столбик").Якорь("рассылка-7");
// … поля правки …
Действия = Кит.Ряд("действия");
Действия.Добавить(Кит.Кнопка(Новый Структура("Подпись, Вариант, Форма", "Сохранить", "основная", "рассылка-7")));
Действия.Добавить(ФормаУдаления); // своя форма из одной кнопки

Длинную форму делят разделы — Кит.Группа("Куда отправлять", "раздел"): подпись раздела печатается заголовком, раздел от раздела отделяет черта, вложенные группы внутри раздела — без рамок, их подпись выглядит подписью поля.

Сводка

Кит.Сводка(Модель) — запись списка одним взглядом: Название первым и крупно, Знаки (модели плашек) — состояние рядом с названием, Подпись (строка или массив частей через « · ») — под ним, Сведения — подписанные строки Подпись, Значение, Вид («warn» — предупреждение). Длинный перечень переносится в колонке значения, на узком экране подпись встаёт над значением. Строка с пустым значением не печатается. Разметка строчная, поэтому сводка ложится и в свёрнутую строку раскрывашки.

Сведения = Новый Массив();
Сведения.Добавить(Новый Структура("Подпись, Значение", "Темы", "Новые заказы, Отмены, Возвраты"));
Карточка = Кит.Раскрывашка();
Карточка.Шапка().Добавить(Кит.Сводка(Новый Структура("Название, Подпись, Сведения",
	"Вечерний отчёт", "почта · отдел продаж", Сведения)));

Дерево галок

Кит.ДеревоГалок(Модель) — выбор по дереву с трёхпозиционной галкой у ветки: Узлы — массив структур Имя, Значение, Подпись, Пояснение, Признаки, Состояние ("вкл" | "выкл" | "частично"), Отключён, Дети, Открыт, Примечание, Данные. Ветка сворачивается нативной раскрывашкой (шеврон слева от строки, в кнопку раскрытия ничего не вложено), отступ и линия показывают вложенность. Три состояния печатает сервер: «частично» уходит в форму отмеченной галкой, называет себя aria-checked="mixed" и рисуется знаком «—» без скрипта. Пересчёт на лету — дело скрипта страницы: дерево он находит по data-checktree, галку — по data-checktree-box, состояние читает и пишет в data-checktree-state (on | off | mixed), свои признаки галки приезжают полем Данные (имена — латиница, без data-).

«Частично» без скрипта — осознанное отступление от ARIA in HTML, которое не советует aria-checked на нативном флажке: свойство indeterminate ставит только скрипт, а другого способа сказать «частично» у разметки нет. Поэтому сервер печатает aria-checked="mixed" у отмеченной галки, и знак «—» рисуется по нему же. Снятая без скрипта такая галка выглядит пустой рамкой того же размера, что и обычная, но скринридер по-прежнему назовёт её «частично» — до перезагрузки страницы. Скрипт страницы, пересчитывая галку, снимает aria-checked и ставит indeterminate сам.

Витрина

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

oscript витрина.os

Команда кладёт рядом витрина.html, файл открывается браузером, сервера не нужно. Под каждым образцом — не только картинка, но и исходник, которым она нарисована: копируете кнопкой рядом с кодом, вставляете к себе — получаете ровно то, что видели. Разойтись коду и картинке негде: витрина не хранит описаний, она исполняет исходник и показывает результат.

Ключом --стили путь.css можно переопределить css своего проекта — и увидеть витрину в его оформлении.

Тесты

oneunit execute

Лицензия

MIT. Иконки — набор Phosphor, текст разрешения лежит рядом с ними в src/статика/иконки/phosphor-LICENSE.txt.

About

No description, website, or topics provided.

Resources

Stars

13 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages