Библиотека Webix предлагает несколько способов редактирования табличных данных. Одни из самых распространённых практик — использование встроенных редакторов Datatable или же отдельных Webix Form. Однако вопрос остаётся открытым — как удачно подобрать инструменты и предоставить пользователю удобное и интуитивно-понятное решение?
Используем встроенный редактор
Представим, что у нас есть таблица с некоторыми данными. Чтобы обеспечить удобную коммуникацию с приложением, мы сделали её редактируемой, и пользователи могут вносить изменения прямо внутри таблицы.
На первый взгляд, всё работает отлично. Однако этот подход не может похвастаться эффективностью, т.к. при каждом сохранении данных в редакторе, таблица отправляет эти данные на сервер. Чтобы обойти такое поведение, мы можем редактировать весь ряд целиком с помощью метода editRow. В последнюю колонку добавим иконку, при клике по которой будет открываться редактор для всего ряда:
view:"datatable",
editable: true,
editaction: "custom",
columns:[
// данные столбцов
// иконка редактирования
{template: `<span class='webix_icon wxi-dots'></span>`}
]
onClick: {
"wxi-dots": function(ev, id) {
this.editRow(id);
}
}
}
Возможно, вы заметили, что после выбора опции из комбо-бокса, все редакторы закрываются. Дело в том, что Webix завершает текущее редактирование, если пользователь выбрал какую-либо опцию комбо. То же самое касается и кликов за пределами редактируемого ряда — редактирование также завершается. Но что, если нам необходимо начинать и завершать редактирование строго при клике по определённому контролу?
Редактирование с Webix Form
Простое и стабильное решение — использовать форму внутри модального окна, как в примере ниже:
Редактирование работает так, как нужно да и выглядит неплохо. Однако давайте пойдём ещё дальше и добавим форму прямо в редактируемый ряд.
Встроенный редактор с формой
Форма будет находиться внутри Webix Window и мы можем стилизовать её так, что она не будет отличатся от встроенного редактора Datatable.
Выглядит это следующим образом:
То, что нужно!
На что обратить внимание
Далее мы рассмотрим детали и неочевидные моменты нашего решения.
Таблица
В последнем столбце задайте темплейт для иконки, которая будет показывать текущий статус редактирования: “wxi-dots” если редактирование не активно и “wxi-check” — если редактор открыт.
view:"datatable",
id:"grid",
columns:[
// темплейт для иконки
{width:55, css:"edit", template:function(obj){
return `<span class='webix_icon wxi-${obj.edit?"check":"dots"}'></span>`;
}}
],
// другие свойства
}
Свойство edit у элемента данных показывает текущий статус редактирования и меняется с вызовом соответствующих функций editStart и editEnd, которые описаны ниже.
Окно и форма
При стилизации окна спрячьте в нём всё, что выдаёт в нём окно. Скройте хедер и уберите границы. Само окно необходимо поместить в контейнер таблицы, чтобы обеспечить его корректное позиционирование при скролле.
view:"window",
id:"editor",
height: 50,
container:$$("grid").$view,
head:false,
borderless:true,
// … другие свойства
}
Высота формы должна совпадать с высотой ряда (в нашем случае это 50px). Ширина контролов должна совпадать с шириной столбцов. В зависимости от типа данных добавьте соответствующие контролы для редактирования. Если какие-либо поля не нужно редактировать, используйте Webix labels, как показано в примере ниже:
body:{
view:"form",
padding:0, height:49, margin:0,
cols:[
// ярлыки для нередактируемых полей
{view:"label", css:"mylabel", width:40, name:"rank"},
{view:"combo", width:100, name:"catId", options:cats},
{view:"text", width:100, name:"votes"},
{view:"datepicker", width:120, name:"start", editable:true}
}
Процесс редактирования
Начало редактирования
В нашем случае редактирование начинается при клике по кнопке (иконка “wxi-dots”). Функция-стартер получает значения текущего ряда и записывает их в соответствующие поля формы. В момент редактирования иконка “wxi-dots” заменяется на “wxi-check”.
//…
const vals = grid.getItem(itemId);
form.setValues(vals);
// редактор открыт
vals.edit = true;
grid.refresh(itemId);
}
Важно расположить окно точно поверх ряда. Для этой цели используйте метод окна show. Обратите внимание, что свойства “х” и “y” рассчитываются с учётом смещения таблицы относительно document.body. Такой подход позволит корректно отображать редакторы таблицы вне зависимости от того, в какой лейаут она встроена.
const offset = webix.html.offset($$("grid").$view);
const xOffset = offset.x;
const yOffset = offset.y;
// ...
function showForm(id){
// позиция ряда
const node = grid.getItemNode(id);
if(node){
const pos = webix.html.offset(node);
editor.show({x:pos.x-xOffset, y:pos.y-yOffset-1});
}
// другие свойства
}
Функция showForm также вызывается после каждого скролла, что обеспечивает корректное позиционирование редактора при скролле содержимого таблицы.
view:"datatable",
on:{
onAfterScroll:function(){
if(itemId) showForm(itemId);
}
},
// другие свойства
}
Конец редактирования
При клике по иконке “wxi-check” форма валидирует свои значения и сохраняет их, если они соответствуют заданным правилам.
if(form.validate()){
const vals = form.getValues();
// редактирование завершено
vals.edit = false;
if(form.isDirty())
grid.updateItem(itemId, vals);
else {
grid.getItem(itemId).edit = false;
grid.refresh(itemId);
}
// ...
}
}
Вы можете проверить, внёс ли пользователь изменения с помощью метода isDirty. Если изменения внесены — вызываем метод updateItem, который отрисует новые данные в таблице и отправит их на сервер. Если же пользователь не вводил никаких данных, просто меняем иконку обратно на “wxi-dots” с помощью метода refresh.
Решение для горизонтального скролла
Если вам нужна таблица с горизонтальным скроллом, её необходимо обернуть в scrollView, а свойству scroll задать значение “X”. Это обеспечит корректное позиционирование и отрисовку редактора.
view:"datatable",
id:"grid",
rowHeight:50,
autowidth:true,
// other properties
}};
Необходимо также учитывать насколько пользователь проскроллил содержимое таблицы. С помощью метода getScrollState можно получить текущую позицию скролла и затем расположить окно, опираясь на это значение. В примере ниже текущая позиция скролла доступна как state.x.
const node = grid.getItemNode(id);
const pos = webix.html.offset(node);
const state = scrolls.getScrollState();
editor.show({
x:pos.x-xOffset+state.x,
y:pos.y-yOffset
});
}
Что дальше
А как вы решили бы эту задачу? Поделитесь мыслями по поводу нашего решения или предложите своё в комментариях ниже.