Персональный вид
Всё, что человек меняет во внешнем виде элемента, запоминается одинаково:
порядок, ширина, видимость и закрепление колонок, сортировка, размер страницы,
ширина панели. Для этого во Framework есть одно хранилище — SF.Preferences.
Без сервера вид хранится в браузере, а на сайте с личными настройками (Larena)
— ещё и у пользователя на сервере, и применяется до первой отрисовки:
первый кадр с колонками данных уже стоит в раскладке человека.
Что считается видом
- Вид — раскладка, порядок, размеры, видимость, сортировка, плотность,
свёрнутость. Его хранит
SF.Preferences. - Не вид — данные и фильтры со значениями. Сохранённый набор фильтров — это именованное представление, которым делятся; оно хранится отдельно.
Ключ и значение
Вид хранится под ключом <компонент>/<экземпляр>: sf-table/cms.solutions,
sf-resizer/menu. Экземпляр — постоянное имя того, что показывается
(коллекция, меню); его компоненту передаёт атрибут, например preferences-key
у таблицы или storage-key у изменения ширины.
Без ключа ничего не запоминается: таблица-пример в документации остаётся
чистой.
Значение — обычный JSON-объект, которым владеет компонент; он читает только
известные ему поля и не перезаписывает значение более новой версии, которое не
понимает. Ключ, версию формата и поля значения компонент объявляет в
манифесте (persistence.view_preference, см. Манифест); они перечислены в
Поля значения. Значение таблицы под sf-table/<экземпляр>
складывается из полей, которые пишут таблица и представление данных.
Поля значения
Раздел собран из манифестов выпуска ui-106d5901f8c3 и пакетов и обновляется вместе с ними. Поля объявляет persistence.view_preference.
<sf-resizer>
Ключ sf-resizer/<storage-key>; версия формата — v: 1. В столбце «Для первой отрисовки» «да» значит, что сервер может положить поле в блок data-sf-preferences страницы.
| Поле | Тип | Пишет | Для первой отрисовки | Что в нём |
|---|---|---|---|---|
width |
integer |
sf-resizer |
да | Ширина, до которой человек дотянул панель, в пикселях CSS; при применении ограничивается min и max. |
<sf-table>
Ключ sf-table/<preferences-key>, общий для всех элементов этого вида — sf-table/*; версия формата — v: 1. В столбце «Для первой отрисовки» «да» значит, что сервер может положить поле в блок data-sf-preferences страницы.
| Поле | Тип | Пишет | Для первой отрисовки | Что в нём |
|---|---|---|---|---|
columnSettings |
object |
sf-table |
да | Раскладка колонок человека по ключу колонки: visible (логическое), width (длина CSS), order (число), pinned (start или end). Накладывается на настройки колонок хоста, поэтому колонка, которой в ней нет, остаётся как у хоста. |
widths |
object |
sf-table |
да | Ширины колонок, которые человек задал перетаскиванием, по ключу колонки, в виде "<N>px". Хранятся и без preferences-key, если у таблицы есть remember-widths, — тогда под собственным ключом таблицы. |
view |
object |
sf-table |
да | Вид таблицы: density (compact, normal, spacious), stripes, lines, singleLine, stickyHead. Под sf-table/* это вид всех таблиц человека; он читается под видом этой таблицы. |
sort |
array |
sf-data-view |
да | Сортировка, которую выбрал человек: [{key, direction}]. Пишет и читает представление данных, в котором стоит таблица, поэтому первый запрос уже приходит в этом порядке. |
pageSize |
integer |
sf-data-view |
да | Размер страницы, который выбрал человек. Пишет и читает представление данных, поэтому первый запрос уже просит этот размер. |
Где хранится
| Где | Когда | Как |
|---|---|---|
| Браузер | всегда | localStorage, ключ sf-pref:<ключ>; это ещё и кеш для первой отрисовки |
| Сервер, у пользователя | когда страница подключила хранилище хоста | SF.Preferences.useHost(adapter); значения хоста важнее браузерных |
Копия в браузере принадлежит браузеру, а не человеку: её видит каждый, кто
входит на сайт с этого браузера. Поэтому на сайте с личными настройками
сервер кладёт в страницу каждый ключ, который она читает, — и null для
ключа, которого у человека нет (см. Подключение хранилища хоста). Иначе
человек увидит вид предыдущего, а после «Сбросить» на другом устройстве —
старую копию из браузера.
Запись в браузер происходит сразу, на сервер — с задержкой 400 мс на ключ, а при уходе со страницы всё, что ждёт, отправляется немедленно. Если вид поменяли в другой вкладке, компоненты получают новое значение.
Компонент сам localStorage не трогает: это проверяет контракт Framework.
Подключение хранилища хоста
SF.Preferences есть сразу после выполнения core.js, поэтому хранилище
хоста подключают встроенным скриптом сразу после него, до того как Loader
загрузит Smart-элементы:
SF.Preferences.useHost({
save: (key, value) => fetch(`/admin/preferences/${encodeURIComponent(key)}`, {
method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(value), keepalive: true,
}),
remove: (key) => fetch(`/admin/preferences/${encodeURIComponent(key)}`, {method: 'DELETE', keepalive: true}),
load: (keys) => fetch(`/admin/preferences?keys=${keys.map(encodeURIComponent).join(',')}`).then((r) => r.json()),
});
save обязателен, remove и load — нет. load(keys) получает массив
ключей и возвращает обещание объекта {ключ: значение}; в примере ключи
идут в адрес через запятую, каждый закодирован encodeURIComponent. Ключ,
которого у человека нет, хост возвращает со значением null: ключ, которого
в ответе нет, остаётся с копией из браузера. keepalive: true нужен потому,
что при уходе со страницы отложенные записи отправляются из обработчика
pagehide, и без него браузер может оборвать запрос.
save получает только ключ и значение, без ревизии: побеждает последняя
запись. Если обещание save или remove отклонено, SF.Preferences пишет
предупреждение в консоль и запись не повторяет. Если вид человека меняют
с нескольких устройств и потерянная запись важна, защиту от конфликта
делает адаптер: хранит ревизию, которую сервер отдал со значением,
отправляет её с записью (например, в заголовке If-Match), а на отказ
сервера читает текущее значение, сливает его со своим и записывает снова.
Чтобы и первый заход с нового устройства был в виде человека, сервер кладёт его значения прямо в страницу — они читаются до первой отрисовки:
<script type="application/json" data-sf-preferences>
{"sf-table/cms.solutions": {"v": 1, "sort": [{"key": "updated", "direction": "desc"}], "pageSize": 50,
"columnSettings": {"code": {"order": 10, "visible": true, "pinned": false, "width": "120px"}}},
"sf-tabs/vm-record": null}
</script>
Сервер кладёт в блок каждый ключ, который читает страница. Если у человека
значения нет, ключ всё равно стоит в блоке со значением null, как
sf-tabs/vm-record выше: null стирает копию в браузере. Без него страница
возьмёт копию из браузера — вид человека, который входил с этого браузера
раньше, или раскладку, которую на другом устройстве уже сбросили.
Блок может стоять в любом месте страницы до подключения компонентов: он читается один раз, при первом чтении вида.
То же делает SF.Preferences.preload(values) из кода. Значения, которые
расходятся с браузерной копией, заменяют её, и компоненты получают новое
значение. Таблица накладывает раскладку из нового значения на колонки,
которые дал хост: колонка, которой в значении нет, возвращается к
настройкам хоста.
Чтение и запись
| Функция | Что делает |
|---|---|
read(key) |
значение под ключом: хоста, если он его дал, иначе браузера; сразу, без ожидания |
readMerged(...keys) |
несколько ключей от общего к частному, слитые по полям: sf-table/* (общие настройки таблиц человека) под sf-table/<экземпляр> |
write(key, value) |
записать вид; значение, равное уже хранимому, не записывается и хосту не отправляется |
remove(key) |
«Сбросить вид»: убрать из браузера и с сервера |
subscribe(key, fn) |
узнавать о новом значении — из другой вкладки, от хоста и после remove(key) на этой странице (со значением null); возвращает функцию отписки |
load(keys) |
запросить у хоста ключи, которых он ещё не дал (компонент появился позже) |
flush() |
отправить отложенные записи сейчас; работает сразу и ничего не возвращает |
Раскладка таблицы: предпочтение и слой порта
У <sf-data-view> раскладку колонок помнят два места, и у каждого своя роль:
- Предпочтение
sf-table/<preferences-key>— значение из таблицы выше. Оно читается до первого запроса: запрос несёт сортировку и размер страницы человека, а первый кадр с колонками данных стоит в его раскладке. Сортировка и размер страницы хранятся только здесь. - Личный слой порта —
data.column_settings.layers.personalв ответе приложения на запрос. При изменении колонок представление отправляет приложению намерениеsettings.save_personalс ревизией этого слоя, и приложение записывает раскладку у себя с проверкой ревизии.
Изменение колонок записывается в оба места сразу. На экране действуют
колонки из ответа приложения (data.columns с их порядком, шириной и
видимостью), а поверх них — предпочтение. Слои порта представление к таблице
само не применяет: ревизия слоя нужна ему для записи.
Поэтому приложение, которое хочет, чтобы раскладка человека была на любом
устройстве, делает одно из двух: подключает хранилище хоста и кладёт
значения в блок data-sf-preferences, или учитывает личный слой, когда
собирает data.columns. Ответ на запрос несёт личный слой с ревизией или
хотя бы target с ней: без ревизии запись колонок не отправляется
(см. Представление данных → Шаблоны и настройки колонок).
Уровни
Вид складывается из уровней, поздний уровень важнее по каждому полю:
- значение по умолчанию компонента;
- значение по умолчанию у хоста для экземпляра — то, что хост отдаёт в
своих данных: у представления данных это колонки
data.columnsс их порядком, шириной и видимостью; - общие предпочтения человека для всех таблиц — плотность, полосы, перенос
(
sf-table/*); - вид человека для этого экземпляра (
sf-table/<экземпляр>).
Сброс
Компонент, который хранит вид, предлагает «Сбросить вид» там, где лежат его
настройки. Кнопка «Сбросить» в настройках таблицы убирает из значения
раскладку колонок (columnSettings и widths), а сортировку, размер
страницы и view оставляет; в <sf-data-view> она ещё записывает пустую
раскладку {} в личный слой порта против его ревизии. Ширину панели сбрасывает
двойной щелчок по ручке.
SF.Preferences.remove(key) из кода страницы стирает значение целиком и в
браузере, и у хоста; открытая таблица сразу возвращается к колонкам, которые
дало приложение. Сортировку и размер страницы открытого представления
remove() не меняет: они вернутся к значениям по умолчанию после
перезагрузки. Кнопка страницы «Сбросить вид таблицы» поэтому делает три
шага:
SF.Preferences.remove('sf-table/cms.solutions');
view.resetColumnSettings();
view.changeQuery({sort: [], per_page: 25, page: 1});
Какие компоненты уже хранят вид
- Изменение ширины — ширина панели
под
sf-resizer/<storage-key>. - Представление данных — раскладка
таблицы, сортировка и размер страницы под
sf-table/<preferences-key>. - Меню администратора Larena —
свёрнутость и открытый раздел под
sf-admin-menu/main; ширины колонок навигации порта хранят его ручки подsf-resizer/admin-nav-firstиsf-resizer/admin-nav-second.
Вкладки с configurable вид не
хранят: порядок и скрытые вкладки приходят событием
sf-tabs-settings-change, и приложение сохраняет их само — у себя или через
SF.Preferences.write('sf-tabs/<ключ>', {v: 1, order, hidden}) — и
возвращает методом setSettings(). v — версия формата значения, как у
таблицы и панели.
Смотрите также
- Состояния и загрузка данных — граница состояния компонента.
- Правила взаимодействия компонентов — общее состояние человека только через
SF.Preferences.