Различные способы редактирования табличных данных

Библиотека Webix предлагает несколько способов редактирования табличных данных. Одни из самых распространённых практик — использование встроенных редакторов Datatable или же отдельных Webix Form. Однако вопрос остаётся открытым — как удачно подобрать инструменты и предоставить пользователю удобное и интуитивно-понятное решение?

ПАРАМЕТРЫ ФАЙЛА  exploring_different techniques for data editing

Используем встроенный редактор

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

Попробуйте внести изменения в таблицу:

View code >>

На первый взгляд, всё работает отлично. Однако этот подход не может похвастаться эффективностью, т.к. при каждом сохранении данных в редакторе, таблица отправляет эти данные на сервер. Чтобы обойти такое поведение, мы можем редактировать весь ряд целиком с помощью метода 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);
     }
  }
}

Теперь можно редактировать сразу весь ряд:

View code >>

Возможно, вы заметили, что после выбора опции из комбо-бокса, все редакторы закрываются. Дело в том, что Webix завершает текущее редактирование, если пользователь выбрал какую-либо опцию комбо. То же самое касается и кликов за пределами редактируемого ряда — редактирование также завершается. Но что, если нам необходимо начинать и завершать редактирование строго при клике по определённому контролу?

Редактирование с Webix Form

Простое и стабильное решение — использовать форму внутри модального окна, как в примере ниже:

View code >>

Редактирование работает так, как нужно да и выглядит неплохо. Однако давайте пойдём ещё дальше и добавим форму прямо в редактируемый ряд.

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

Форма будет находиться внутри Webix Window и мы можем стилизовать её так, что она не будет отличатся от встроенного редактора Datatable.

Выглядит это следующим образом:

View code >>

То, что нужно!

На что обратить внимание

Далее мы рассмотрим детали и неочевидные моменты нашего решения.

Таблица

В последнем столбце задайте темплейт для иконки, которая будет показывать текущий статус редактирования: “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”.

function editStart(e, id){
 //…
  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” форма валидирует свои значения и сохраняет их, если они соответствуют заданным правилам.

function editEnd(){
  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:"scrollview", id:"scrolls", scroll:"x", body:{
  view:"datatable",
  id:"grid",
  rowHeight:50,
  autowidth:true,
// other properties
}};

Необходимо также учитывать насколько пользователь проскроллил содержимое таблицы. С помощью метода getScrollState можно получить текущую позицию скролла и затем расположить окно, опираясь на это значение. В примере ниже текущая позиция скролла доступна как state.x.

function showForm(id){
  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
  });
}

Datatable c горизонтальным скроллом:

View code >>

Что дальше

А как вы решили бы эту задачу? Поделитесь мыслями по поводу нашего решения или предложите своё в комментариях ниже.