Как стать автором
Обновить

Как встроить ColorPicker в JavaScript Гант для изменения цвета задач

Open source *JavaScript *jQuery *HTML *Визуализация данных
Из песочницы


Привет, меня зовут Женя, и я просто еще один из обитателей JavaScript вселенной, который хочет поделиться с вами интересным опытом в frontend-разработке, а именно как кастомизировать диаграмму Ганта.


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


  • оптимальное распределение ресурсов и нагрузки между сотрудниками
  • мониторинг выполнения задач
  • оценка эффективности и временных рамок проекта

Во многих современных приложениях для управления проектами для решения таких задач используются диаграммы Ганта. И я взялся за реализацию ее функциональности в нашем приложении.


Хотя можно было бы использовать готовый софт, в нашем случае нужно было сильно кастомизировать Гант под нужды проекта. Альтернатива разработки диаграммы с нуля — это слишком накладное и времязатратное мероприятие. Посовещавшись с коллегами, мы решили, что лучше всего найти готовый компонент среди библиотек JavaScript и настроить его под наш проект.


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


  • простой для понимания интерфейс
  • богатые возможности кастомизации
  • ну и, само собой, хорошая производительность

Один из иностранных коллег уберег меня от долгих скитаний по просторам интернета в поисках необходимого виджета и подсказал JavaScript библиотеку DHTMLX Gantt. Ознакомившись с функциональными возможностями продукта, я решил, что стоит попробовать — библиотека удовлетворила наши требованиям по настройке диаграммы и оказалась простой в использовании.


Сама библиотека доступна в двух версиях — бесплатной и платной:


  • в бесплатной нет некоторых фич и есть GPL v2 лицензия;
  • а платная стоит от 700$ до 3000$ в зависимости от условий использования.

Для нашего проекта хватает фич бесплатной версии — ее мы и решили использовать.


Про выбор лицензии

Насколько я понял, загвоздка с GPL лицензиями и GPLv2 конкретно в том, что они требуют раскрывать исходный код (TLDRlegal). При этом не очень понятно, какой именно код веб приложения надо открывать — все приложение/только front-end/только код, который взаимодействует с библиотекой. Но так как наш проект мы делаем для внутренних нужд и не планируем никому распространять, как я понимаю, нас требование раскрытия кода не коснется. Если мы когда-нибудь решим продавать наше приложение другим предприятиям, тогда придется купить платную лицензию.


В этой статье я хочу поделиться с вами несколькими приемами работы с DHTMLX JavaScript Гантом. Я не стану расписывать все детали по настройке Ганта, но расскажу о наиболее интересных фишках, которые пригодились мне в работе.


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



В DHTMLX Gantt контент можно редактировать двумя способами:


  • вызвав форму редактирования (lightbox)
  • воспользовавшись встроенными редакторами в области таблицы (inline editors), когда пользователь может редактировать данные с помощью горячих клавиш

Второй вариант редактирования задач как раз оказался тем, что мне нужно. Используя DHTMLX Gantt, я без труда смог реализовать свой тип редактора для выбора цвета — color picker, позволяющий присваивать цвет задачам проекта. Далее я подробно расскажу о реализации этой функциональности.


Инициализация


Пример я покажу на простом HTML5 и JavaScript, чтобы не требовалась сборка и было максимально понятно и без лишних сложностей.


Для начала нужно инициализировать Гант на странице. Для этого нужно подключить JS и CSS файлы библиотеки из коробки, а затем создать контейнер, в котором инициализировать Гант (подробная инструкция тут):


<!DOCTYPE html>
<html>
<head>
   <script src="codebase/dhtmlxgantt.js"></script>
   <link href="codebase/dhtmlxgantt.css" rel="stylesheet">
</head>
<body>
    <div id="gantt_here" style='width:100vw; height:100vh;'></div>
    <script>
      window.addEventListener("DOMContentLoaded",() => {
       gantt.init("gantt_here");
      });
   </script>
</body>
</html>

Прямо в HTML файле или же отдельно в js файле, после инициализации Ганта, нужно добавить данные, на основании которых Гант построит диаграмму. Для примера я добавлю тут 1 проект (Открытие производства оборудования) и 2 вложенные в него задачи (Определение рынка сбыта и Определение маркетинговой стратегии). Тут также задаются даты начала задач, их длительность, порядок выполнения и прогресс (степень выполнения задач):


gantt.parse({
    data: [
      {
        id: 1, text: "Открытие производства оборудования", start_date: "01-05-2020", duration: 18, open: true
      },
      {
        id: 2, text: "Определение рынка сбыта", start_date: "02-05-2020", duration: 4, parent: 1
      },
      {
        id: 3, text: "Определение маркетинговой стратегии", start_date: "07-05-2020", duration: 5, parent: 1
      }
    ],
    links: [
      {id: 1, source: 1, target: 2, type: "1"},
      {id: 2, source: 2, target: 3, type: "0"}
    ]
  });
});


Это самая простая часть.


Добавление Inline Editors


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


Колонку можно сделать редактируемой, добавив к ней свойство 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}
];

Объект редактора должен иметь свойство type, которое соответствует нужному типу редактора, и свойство map_to, которое определяет свойство объекта задачи, в которое редактор будет сохранять значения. Например, так настраивается редактор для полей с текстом, датами и длительностями задач:


const textEditor = {type: "text", map_to: "text"};
const dateEditor = {type: "date", map_to: "start_date", min: new Date(2020, 0, 1), 
    max: new Date(2021, 0, 1)};
const durationEditor = {type: "number", map_to: "duration", min:0, max: 100};

Остальные настройки относятся к определенным типам редакторов.


Создаем Свой Собственный Редактор


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


Встроенного редактора для выбора цвета в библиотеке нет, поэтому вариантов у нас немного: использовать выпадающий список (select) или сделать новый редактор с селектором цвета (color picker). Я решил пойти по второму пути, то есть создать редактор-colorpicker.


Сначала я покажу на примере, как встроить простой HTML5 элемент формы input с типом color, чтобы выбирать цвет задачи.


Для создания собственного редактора нужно добавить новый объект редактора в конфигурацию Ганта. Я сделал это по шаблону из документации:


gantt.config.editor_types.custom_editor = {
  show: (id, column, config, placeholder) => {
    // called when input is displayed, put html markup of the editor into placeholder

    // and initialize your editor if needed:
        placeholder.innerHTML `<div><input type='text' name='${column.name}'></div>`;
  },

  hide: () => {
    // called when input is hidden 
    // destroy any complex editors or detach event listeners from here
  },

  set_value: (value, id, column, node) => {
    // set input value
  },

  get_value: (id, column, node) => {
    // return input value
  }, 

  is_changed: (value, id, column, node) => {
    // called before save/close. Return true if new value differs from the original one
    // returning true will trigger saving changes, returning false will skip saving 
  },

  is_valid: (value, id, column, node) => {
    // validate, changes will be discarded if the method returns false
    return true/false;
  },

  save: (id, column, node) => {
     // only for inputs with map_to:auto. complex save behavior goes here
  },

  focus: (node) => {                                                 
  }
}

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


  1. Первым делом я изменил название моего редактора и поменял функцию show, чтобы изменить тип вводимых данных на color:


    gantt.config.editor_types.color = {
      show: (id, column, config, placeholder) => {
        placeholder.innerHTML = `<div><input type='color' name='${column.name}'></div>`;
      },

  2. Метод hide мне не понадобился, поскольку элементу выбора цвета не требуется никаких деструкторов или постобработки после того, как он становится скрытым, поэтому я оставил его пустым:


    hide: () => {},

  3. Далее — методы set_value и get_value:


    set_value: (value, id, column, node) => {
      const input = node.querySelector("input");
      input.value = value   
    },
    get_value: (id, column, node) => {
      const input = node.querySelector("input");
      return input.value;
    },

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


  4. Следующая на очереди функция is_changed. Поскольку редакторы можно легко открывать и закрывать, я задал инициирование изменения данных только тогда, когда пользователь фактически меняет значение редактора:


    is_changed: (value, id, column, node) => {
      const input = node.querySelector("input");
      return input.value !== value;
    },

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


  5. Принцип работы метода is_valid полностью соответствует своему названию и, возвращая false, сообщает Ганту, что введенное значение недопустимо и его необходимо сбросить:


    is_valid: (value, id, column, node) => {
      const input = node.querySelector("input");
      return !!input.value;
    },

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


  7. Использование метода focus помогло поместить фокус окна браузера в редактор:


    focus: node => {
      const input = node.querySelector("input");
      input.focus();
    },

    В итоге я получил свой собственный редактор для выбора цвета задачи в Ганте.



Добавление редактора в Гант


Далее мне нужно было добавить этот редактор в мой Гантт.
Для этого я добавил новый столбец к конфигу таблицы и привязал к нему конфиг редактора. Свойство type редактора цвета должно соответствовать значению для редактора, которое я использовал выше (в моем случае type: “color”).


  const textEditor = {type: "text", map_to: "text"};
  const dateEditor = {type: "date", map_to: "start_date", min: new Date(2020, 0, 1), 
      max: new Date(2021, 0, 1)};
  const durationEditor = {type: "number", map_to: "duration", min:0, max: 100};
  const colorEditor = {type: "color", map_to: "color"};
  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: "color", align: "center", editor: colorEditor},
      {name: "add", width: 44}
  ];

Я использовал свойство color, поскольку Гант автоматически применит цвета из этого свойства. Теперь можно посмотреть, как все работает, если добавить к задачам значение цвета — например, color:"#FF0000":


{
    id: 2, text: "Определение рынка сбыта", start_date: "02-05-2020", duration: 4, parent: 1, color:"#FF0000"
},


В качестве последнего штриха, я сделал так, чтобы цвета внутри столбца “color” отображались красиво. Для этого я использовал шаблон:


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: "color", align: "center", label:"Color", editor: colorEditor, template:
       (task) => {
          return `<div class='task-color-cell' style='background:${task.color}'></div>`
        }
       },
      {name: "add", width: 44}
];

Я задал шаблон, который будет возвращать элемент контейнера div с указанным в стилях цветом фона. В файле css я добавил стили, чтобы красиво отображать цвет в контейнере:


 .task-color-cell{
    margin:10%;
    width:20px;
    height:20px;
    border:1px solid #cecece;
    display:inline-block;
    border-radius:20px;
}


Реализованный пример с кодом можно посмотреть по ссылке: https://plnkr.co/edit/yGWtLzoELPrhJV2K?preview


Использование готового Color Picker виджета в редакторе


Поскольку в DHTMLX Gantt изменять цвета можно только через селект, то я решил использовать более гибкий способ для обозначения задач цветом, а именно интегрировать в Гант плагин jquery под названием Spectrum.


Первым шагом я добавил файлы библиотеки в Гант:


<!DOCTYPE html>
<html>
  <head>
     <script src="https://docs.dhtmlx.com/gantt/codebase/dhtmlxgantt.js"></script>   
     <link rel="stylesheet"href="https://docs.dhtmlx.com/gantt/codebase/dhtmlxgantt.css">
     <script
    src="http://code.jquery.com/jquery-3.3.1.min.js"
    integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
    crossorigin="anonymous"></script>
     <script src="http://bgrins.github.io/spectrum/spectrum.js"></script>
     <link rel="stylesheet" href="http://bgrins.github.io/spectrum/spectrum.css">
     <script src="script.js"></script>
     <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div id="gantt_here"></div>
  </body>
</html>

После этого обновил контрол Ганта. Я задал переменную let editor, где будет храниться ссылка на наш редактор. Это необходимо, чтобы вызвать деструктор, когда элемент input будет скрыт.
Сначала я внес изменения в метод show. Когда он вызывается, необходимо инициализировать и отобразить виджет селектора цвета.


На этом месте возникла неожиданная сложность: если вызывать editor.spectrum("show") внутри метода show, редактор не появляется. Видимо, в момент вызова show placeholder-элемент еще не добавлен в документ и у него нет размеров и позиции. В итоге я просто добавил минимальный тайм-аут, чтобы запускать color picker уже после того, как метод завершился и placeholder висит над нужным местом в таблице.


document.addEventListener("DOMContentLoaded", function(event) {
  let editor;
  gantt.config.editor_types.color = {
    show: (id, column, config, placeholder) => {
          placeholder.innerHTML = `<div><input type='color' name='${column.name}'></div>`;

          editor = $(placeholder).find("input").spectrum({
            change:() => {
              gantt.ext.inlineEditors.save();
            }
          });
          setTimeout(() => {
            editor.spectrum("show");
          })
    }

Далее я определил метод “hide” — деструктор будет вызван, когда редактор будет закрыт:


hide: () => {
    if(editor){
      editor.spectrum("destroy");
      editor = null;
    }

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


 set_value: (value, id, column, node) => {
    editor.spectrum("set", value);
  },
  get_value: (id, column, node) => {
    return editor.spectrum("get").toHexString();
  },
  is_changed: function (value, id, column, node) {
    const newValue = this.get_value(id, column, node);
    return newValue !== value;
  },
  is_valid: function (value, id, column, node) {
    const newValue = this.get_value(id, column, node);
    return !!newValue;
  },
  focus:(node) => {
    editor.spectrum("show");
  }


После этого все должно работать как положено!


А это ссылка на мой пример со встроенным Color Picker.


Надеюсь, что эта статья поможет вам в настройке цвета задач в диаграмме Ганта.

Теги:
Хабы:
Всего голосов 7: ↑7 и ↓0 +7
Просмотры 1.9K
Комментарии Комментировать