Редактирование в гриде

Библиотека dhtmlxGantt предлагает два основных способа редактирования содержимого:

  • Через форму редактирования Lightbox
  • Используя встроенные редакторы непосредственно в области грида

Встроенное редактирование позволяет вносить изменения прямо в гриде. Вы можете создавать и обновлять задачи, устанавливать связи, задавать даты начала и окончания, а также изменять продолжительность с помощью встроенных редакторов.

Встроенное редактирование грида

Чтобы активировать встроенное редактирование, вам нужно:

  • Определить список конфигураций редакторов и использовать свойство map_to для связи редактора с определённой колонкой грида.
var textEditor = {type: "text", map_to: "text"};
var dateEditor = {type: "date", map_to: "start_date", min: new Date(2018, 0, 1), 
    max: new Date(2019, 0, 1)};
var durationEditor = {type: "number", map_to: "duration", min:0, max: 100};
  • Использовать свойство editor в конфигурации колонки для назначения нужного редактора колонке.
gantt.config.columns = [
    {name: "text", tree: true, width: '*', resize: true, editor: textEditor},
    {name: "start_date", align: "center", resize: true, editor: dateEditor},
    {name: "duration", align: "center", editor: durationEditor},
    {name: "add", width: 44}
];

Related sample:  Inline editing

Более подробная информация об API объекта inlineEditors доступна в статье Расширение Inline Editors.

Для практической демонстрации, посмотрите видео руководство по внедрению встроенного редактирования в гриде.

Типы редакторов

Встроенные редакторы определяются в объекте конфигурации editor_types. Библиотека предоставляет несколько предварительно настроенных редакторов:

  • text редактор: Используется для текстовых колонок, таких как имена задач.
  • number редактор: Обрабатывает числовые колонки, такие как продолжительность задачи или порядок.
  • duration редактор: Предназначен для колонок продолжительности. Работает, если установлено свойство map_to: "duration" и тип редактора "duration".
{ type: "duration", map_to: "duration", formatter: formatter }

Этот редактор удобен, когда нужно указать продолжительность, включающую как числовое значение, так и единицу измерения продолжительности, например, 5 дней. По умолчанию используется Duration Formatter, но вы можете изменить его настройки или использовать пользовательский форматтер.

  • date редактор: Используется для колонок дат, таких как даты начала и окончания задач.
  • select редактор: Позволяет выбрать вариант из предопределенного списка.
  • predecessor редактор: Позволяет установить связи предшественников задач. Использует WBS коды для установления связей.
var editors = {
    text: {type: "text", map_to: "text"},
    start_date: {type: "date", map_to: "start_date", min: new Date(2018, 0, 1), 
        max: new Date(2019, 0, 1)},
    end_date: {type: "date", map_to: "end_date", min: new Date(2018, 0, 1), 
        max: new Date(2019, 0, 1)},
    duration: {type: "number", map_to: "duration", min:0, max: 100},
    priority: {type:"select", map_to:"priority", options:gantt.serverList("priority")},
    predecessors: {type: "predecessor", map_to: "auto"}
};

Ограничения дат в редакторе дат

Начиная с версии 6.3, нет ограничений по умолчанию для минимальных и максимальных значений ввода в date встроенных редакторах. Если вы хотите, чтобы видимые даты на временной шкале ограничивали диапазон ввода, вы можете установить динамические значения min/max:

const dateEditor = {type: "date", map_to: "start_date", 
    min: function(taskId){
      return gantt.getState().min_date
    },
    max: function(taskId){
      return gantt.getState().max_date
    }
};

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

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

// инклюзивный редактор для конечных дат
var dateEditor = gantt.config.editor_types.date;
gantt.config.editor_types.end_date = gantt.mixin({
   set_value: function(value, id, column, node){
        var correctedValue = gantt.date.add(value, -1, "day");
        return dateEditor.set_value.apply(this, [correctedValue, id, column, node]);
   },
   get_value: function(id, column, node) {
        var selectedValue = dateEditor.get_value.apply(this, [id, column, node]);
        return gantt.date.add(selectedValue, 1, "day");
   },
}, dateEditor);
 
var textEditor = {type: "text", map_to: "text"};
var startDateEditor = {type: "date", map_to: "start_date"};
var endDateEditor = {type: "end_date", map_to: "end_date"};
var durationEditor = {type: "number", map_to: "duration", min:0, max: 100};
 
gantt.config.columns = [
    {name: "text", label: "Name", tree: true, width: 200, editor: textEditor, 
        resize: true},
    {name: "duration", label: "Duration", width:80, align: "center", 
        editor: durationEditor, resize: true},
    {name: "start_date", label: "Start", width:140, align: "center", 
        editor: startDateEditor, resize: true},
    {name: "end_date", label: "Finish", width:140, align: "center", 
        editor: endDateEditor, resize: true}
];
 
// Настройка шаблонов для отображения дат в инклюзивном формате
gantt.templates.task_end_date = function(date){
    return gantt.templates.task_date(new Date(date.valueOf() - 1)); 
};
 
var gridDateToStr = gantt.date.date_to_str("%Y-%m-%d");
gantt.templates.grid_date_format = function(date, column){
    if(column === "end_date"){
        return gridDateToStr(new Date(date.valueOf() - 1)); 
    }else{
        return gridDateToStr(date); 
    }
}

Related sample:  Инклюзивный редактор конечной даты

Для получения более подробной информации, ознакомьтесь со статьей Отображение конечной даты задачи и инклюзивные конечные даты.

Форматирование значений редактора предшественников

Эта функция доступна только в PRO-версии.

Начиная с версии 6.3, Gantt поддерживает указание типов связей и значений задержки/опережения непосредственно из встроенного редактора. Чтобы включить эту возможность, используйте модуль Link Formatter и передайте экземпляр LinksFormatter редактору predecessor:

var formatter = gantt.ext.formatters.durationFormatter({
    enter: "day", 
    store: "day", 
    format: "auto"
});
var linksFormatter = gantt.ext.formatters.linkFormatter({durationFormatter: formatter});
 
var editors = {
    text: {type: "text", map_to: "text"},
    start_date: {type: "date", map_to: "start_date", 
                min: new Date(2018, 0, 1), max: new Date(2019, 0, 1)},
    end_date: {type: "date", map_to: "end_date", 
                min: new Date(2018, 0, 1), max: new Date(2019, 0, 1)},
    duration: {type: "duration", map_to: "duration", 
                min:0, max: 100, formatter: formatter},
    priority: {type: "select", map_to: "priority", 
                options:gantt.serverList("priority")},
    predecessors: {type: "predecessor", map_to: "auto", formatter: linksFormatter} };
 
gantt.config.columns = [
    {name: "wbs", label: "#", width: 60, align: "center", template: gantt.getWBSCode},
    {name: "text", label: "Name", tree: true, width: 200, editor: editors.text, 
        resize: true},
    {name: "start_date", label: "Start", width:80, align: "center", 
      editor: editors.start_date, resize: true},
    {name: "predecessors", label: "Predecessors",width:80, align: "left", 
      editor: editors.predecessors, resize: true, template: function(task){
            var links = task.$target;
            var labels = [];
            for(var i = 0; i < links.length; i++){
                var link = gantt.getLink(links[i]);
                labels.push(linksFormatter.format(link));             }
            return labels.join(", ")
        }},
    {name:"add"}
];

Related sample:  Inline editing - keyboard navigation mode

Пользовательский встроенный редактор

Если нужно, вы можете создать пользовательский встроенный редактор. Начните с определения нового объекта редактора:

gantt.config.editor_types.custom_editor = {
  show: function (id, column, config, placeholder) {
    var html = "<div><input type='text' name='" + column.name + "'></div>";
    placeholder.innerHTML = html;
  },
  hide: function () {},
  set_value: function (value, id, column, node) {},
  get_value: function (id, column, node) {},
  is_changed: function (value, id, column, node) {},
  is_valid: function (value, id, column, node) { return true; },
  save: function (id, column, node) {},
  focus: function (node) {}
}

Эта структура позволяет определить, как редактор ведет себя при отображении, скрытии или при установке, проверке или сохранении значений.

editor.save

Функция save полезна, когда вам нужно обновить несколько свойств задачи одновременно или когда вы хотите изменить объекты, отличные от задач. В таких случаях вы все равно можете использовать функцию get_value для встроенной проверки, но сам Gantt не будет применять значение редактора к задаче. Вместо этого он вызовет функцию save.

После вызова save вам нужно будет обработать входные значения и применить необходимые изменения к диаграмме Gantt с помощью пользовательского кода. После завершения функции save будет вызвано событие onSave. Однако Gantt не вызовет автоматически gantt.updateTask для обновленной строки.

Важно: Функция save будет выполнена только в том случае, если вы установили map_to:"auto" в конфигурации редактора:

var editors = {
    ...
    predecessors: {type: "predecessor", map_to: "auto"}
};

Хороший пример этого - встроенный редактор предшественников. Упрощенная версия его реализации представлена в примере ниже:

Related sample:  Встроенный редактор предшественников

Режимы встроенного редактирования

Базовое встроенное редактирование

Этот режим позволяет редактировать ячейки, щелкая по ним мышью, и перемещаться по ячейкам с помощью горячих клавиш:

  • Tab: Переместить фокус на следующий редактор
  • Shift+Tab: Переместить фокус назад на предыдущий редактор

Related sample:  Inline editing

Режим навигации с помощью клавиатуры

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

  • Enter: Открыть/закрыть редактор
  • Пробел: Открыть редактор
  • Стрелки: Навигация по ячейкам грида
  • Shift+Стрелка вправо: Создать отступ для задачи, чтобы она стала вложенной, превращая родительскую задачу в проект
  • Shift+Стрелка влево: Превратить проект обратно в простую задачу
  • Shift+Стрелка вверх: Свернуть ветвь задач
  • Shift+Стрелка вниз: Развернуть ветвь задач

Чтобы включить навигацию с помощью клавиатуры для редактирования, выполните следующие шаги:

  1. Активируйте плагин keyboard_navigation с помощью метода gantt.plugins:
gantt.plugins({
    keyboard_navigation: true
});
  1. Включите навигацию с помощью клавиатуры и навигацию по ячейкам:
gantt.config.keyboard_navigation = true;
gantt.config.keyboard_navigation_cells = true;

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

gantt.config.placeholder_task = true;

Чтобы автоматически фокусироваться на задаче-заполнителе после добавления новой, используйте эту конфигурацию:

gantt.config.placeholder_task = {
    focusOnCreate: true
};

Кроме того, вы можете включить автоматическое определение типа задачи:

gantt.config.auto_types = true;

Related sample:  Inline editing - keyboard navigation mode

Пользовательское встроенное редактирование

Вы можете настроить сопоставление клавиш, чтобы определить, как открываются редакторы, обрабатываются события, связанные с редактором (например, открытие и закрытие), и управляется логика редактирования. Это делается путем создания пользовательского объекта конфигурации и передачи его в конкретный метод:

var mapping = {
 init: function(inlineEditors){
  // Инициализация модуля InlineEditor
  // Добавление глобальных слушателей для событий редактирования
 },
 
 onShow: function(inlineEditors, node){
  // Действия, которые выполняются при отображении редактора
 },
 
 onHide: function(inlineEditors, node){
  // Действия по очистке при скрытии редактора
 }
};
 
gantt.ext.inlineEditors.setMapping(mapping);

Related sample:  Inline editing - Custom keyboard mapping

Пользовательское сопоставление для задач-заполнителей

Представьте два сценария при использовании навигации с клавиатуры, встроенных редакторов и задачи-заполнителя:

Сценарий 1: После ввода имени для новой задачи-заполнителя и нажатия Tab вы ожидаете, что следующая ячейка задачи откроется. Однако фокус перемещается на новую задачу-заполнитель ниже, не открывая редактор.

Сценарий 2: Если вы вводите имя для новой задачи-заполнителя и щелкаете на следующей ячейке, фокус перемещается на задачу-заполнитель вместо щелкнутой ячейки.

Вы можете решить эти проблемы, определив пользовательскую логику для обработки взаимодействий с мышью и клавиатурой с встроенным редактором. Вот пример:

Related sample:  Gantt. Пользовательское сопоставление для задачи-заполнителя

Валидация входных значений

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

Вот как ведет себя редактор с включенной валидацией:

  • Нажатие Escape закрывает редактор без сохранения изменений.
  • Нажатие Enter сохраняет и закрывает редактор, если значение действительно; в противном случае значение отбрасывается.
  • Нажатие Tab или щелчок по другой ячейке при редактировании сохраняет допустимые значения и перемещает фокус, в то время как недопустимые значения сбрасываются, а редактор закрывается.

Для получения более подробной информации о валидации на стороне клиента или сервера, ознакомьтесь со статьей Валидация.

Предотвращение закрытия редактора

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

function editAnotherCell(inlineEditors){
  var value = inlineEditors.getValue();
  if(confirm(`does '${value}' look ok to you?`)){
    inlineEditors.save();
  }
}
 
var mapping = {
  init: function(inlineEditors){
    gantt.attachEvent("onTaskClick", function (id, e) {
      var cell = inlineEditors.locateCell(e.target);
      if (cell && inlineEditors.getEditorConfig(cell.columnName)) {
        if (inlineEditors.isVisible()) editAnotherCell(inlineEditors)
        else inlineEditors.startEdit(cell.id, cell.columnName);
        return false;
      }
      return true;
    });
    gantt.attachEvent("onEmptyClick", function () {
      inlineEditors.hide();
      return true;
    });
  },
 
  onShow: function(inlineEditors, node){
 
    node.onkeydown = function (e) {
      e = e || window.event;
      if(e.defaultPrevented){
        return;
      }
 
      var keyboard = gantt.constants.KEY_CODES;
 
      var shouldPrevent = true;
      switch (e.keyCode) {
        case gantt.keys.edit_save:
          var value = inlineEditors.getValue();
          if(confirm(`does '${value}' look ok to you?`)){
            inlineEditors.save();
          }
 
          break;
        case gantt.keys.edit_cancel:
          inlineEditors.hide();
          break;
        case keyboard.TAB:
          if(e.shiftKey){
            if (inlineEditors.isVisible()) editAnotherCell(inlineEditors)
            else inlineEditors.editPrevCell(true);
          }else{
            if (inlineEditors.isVisible()) editAnotherCell(inlineEditors)
            else inlineEditors.editNextCell(true);
          }
          break;
        default:
          shouldPrevent = false;
          break;
      }
 
      if(shouldPrevent){
        e.preventDefault();
      }
    };
  },
 
  onHide: function(inlineEditors, node){}
};
 
gantt.ext.inlineEditors.setMapping(mapping);
 
gantt.init("gantt_here");

Related sample:  Пользовательское сопоставление клавиатуры

Открытие редактора одним кликом

В режиме одиночного выбора клик по задаче открывает встроенный редактор.

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

gantt.plugins({
  multiselect: true
});
 
...
 
gantt.config.inline_editors_multiselect_open = true;
К началу