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

от автора

Привет, меня зовут Женя, и я просто еще один из обитателей 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.

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

ссылка на оригинал статьи https://habr.com/ru/post/503128/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *