от Алекс Л

Диаграмма Ганта — это действительно удобный инструмент управления проектами для координации сложных графиков для больших команд или нескольких команд. Хотя многие компании используют Excel для построения диаграмм Ганта, использование диаграммы Ганта на основе JavaScript имеет несколько преимуществ по сравнению с электронными таблицами, например, вы можете:

  • Легко интегрируйте свою диаграмму в существующие панели управления проектами.
  • Поделитесь своей диаграммой в Интернете.
  • Настройте диаграмму в соответствии с вашими потребностями.

Диаграммы Ганта на удивление сложны, поэтому, если вы хотите создать свою собственную диаграмму для производственного использования, это руководство покажет вам только первые шаги. Если вместо этого вы предпочитаете купить проверенную готовую версию, взгляните на нашу коммерческую Диаграмму Бринтума Ганта.

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

Базовая диаграмма Ганта, которую мы создадим, будет иметь следующие особенности:

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

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

Начиная

Мы создадим нашу диаграмму Ганта, используя только HTML, CSS и JavaScript.

CSS будет определен в файлах JavaScript. Мы будем использовать модули JavaScript, чтобы разделить код на отдельные модули, которые можно импортировать и экспортировать, поэтому для запуска кода нам понадобится локальный HTTP-сервер. Нам нужно это сделать, потому что модули JavaScript следуют политике одного и того же происхождения, что означает, что вы не можете импортировать модули из вашей файловой системы по умолчанию. Чтобы получить локальный сервер с живой перезагрузкой, вы можете установить пакет npm Live Server. Если вы используете VS Code, вы можете установить расширение Live Server.

Если вы хотите выполнить это руководство в браузере с минимальной настройкой, вы можете использовать Replit, интегрированную онлайн-среду разработки (IDE). Вы можете проверить их документацию на предмет как создать проект.

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

Структура папок

Все, что можно экспортировать с помощью оператора экспорта, считается компонентом. Файл index.html будет импортировать только один файл JavaScript, script.js, который является модулем. Это будет единственная точка входа в наше приложение. Компонент диаграммы Ганта импортируется в script.js.

Мы разобьем компонент Ганта на четыре отдельных компонента:

  • Основной модуль, ganttChart.js,
  • Модуль для HTML, htmlContent.js,
  • Модуль для CSS, style.js и
  • Модуль для некоторых служебных функций, utils.js.

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

Создайте следующие файлы и папки:

|__ index.html
|__ script.js
|__ components 
    |__ ganttChart
        |__ ganttChart.js
        |__ htmlContent.js
        |__ style.js
        |__ utils.js

Базовая структура приложения

Прежде чем мы создадим компонент диаграммы Ганта, давайте настроим приложение, которое будет отображать диаграмму Ганта. Добавьте следующие строки в файл index.html:

Атрибут type=module файла script.js, который мы встраиваем в наш HTML-документ, позволяет нам использовать функции модуля в нашем скрипте. В теге ‹body› есть единственный ‹div› с ролью «диаграммы Ганта». Мы используем это, чтобы определить элемент HTML, который будет использоваться для создания нашего компонента диаграммы Ганта.

Теперь добавьте следующие строки в файл script.js:

Мы импортируем GanttChart, которая будет функцией-конструктором, создающей нашу диаграмму Ганта. Как только HTML-контент загружен и проанализирован, мы создаем нашу диаграмму Ганта. При необходимости вы можете создать несколько экземпляров диаграммы Ганта.

Мы жестко кодируем объекты данных tasks и taskDurations для диаграммы Ганта. Вы можете получить эти данные из своего бэкэнда, используя HTTP-запрос. TaskDurations указывает продолжительность задачи. У них есть свойство задачи, указывающее задачу, с которой она связана, как указано в свойстве идентификатора задачи. У них также есть свойства даты начала и окончания, которые указывают продолжительность задачи. Это объекты JavaScript Date.

Для каждого HTML-элемента на нашей странице, играющего роль диаграммы Ганта, мы создаем диаграмму Ганта с помощью функции-конструктора GanttChart. Мы передаем объект элемента ganttChart DOM, а также данные задачи и ее продолжительности. Эта функция создает содержимое компонента диаграммы Ганта.

HTML-структура

Теперь давайте опишем компонент диаграммы Ганта. Следующая диаграмма описывает компоновку диаграммы Ганта. Пронумерованные блоки представляют контейнеры ‹div› для каждой части диаграммы Ганта. Каждый из них имеет уникальный идентификатор.

Давайте добавим HTML, необходимый для нашего компонента диаграммы Ганта. Добавьте следующие строки в файл htmlContent.js:

Мы импортируем наши стили CSS в виде строки из style.js, которую мы определим в следующем разделе. Мы создаем строку HTML и сохраняем ее в переменной содержимого. Мы рассмотрим особенности HTML и CSS по мере изучения руководства. Мы преобразуем строку HTML в узлы DOM, создавая DocumentFragment, который хранится в переменной contentFragment. Этот фрагмент документа состоит из узлов DOM и отделен от дерева DOM документа. Он не будет отображаться на странице, пока мы не добавим его в DOM документа. Функция createHtmlContentFragment возвращает переменную contentFragment. Позже мы добавим этот фрагмент документа как дочерний к элементу DOM диаграммы Ганта, чтобы поместить его на страницу.

Добавление стилей с помощью CSS

Теперь давайте добавим базовый стиль для нашего компонента диаграммы Ганта. Добавьте следующие строки в файл styles.js:

Мы экспортируем функцию cssStyles из этого модуля. Он возвращает строку CSS, которую мы импортируем и добавляем к строке HTML в htmlContent.js. Переменная ROW_HEIGHT позволяет нам легко изменять высоту строк в диаграмме Ганта.

Добавление служебных функций

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

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

Создание компонента диаграммы Ганта

Теперь, когда у нас настроена базовая структура нашего приложения, мы можем приступить к созданию диаграммы Ганта. С этого момента мы будем работать только с файлом ganttChart.js. Добавьте в файл ganttChart.js следующее:

Импортируем все нужные нам функции из других модулей. Функция конструктора GanttChart принимает в качестве аргументов элемент DOM диаграммы Ганта, задачи и продолжительность задач. Мы также создаем некоторые переменные, которые будем использовать позже, в том числе переменную contentFragment, которая является фрагментом документа нашего содержимого HTML из htmlContent.js. Затем мы добавляем это к элементу ganttChartElement, чтобы он был добавлен в документ DOM.

Теперь давайте добавим параметры месяца и года для наших элементов «От» и «До» ‹select›. Добавьте следующие строки чуть выше ganttChartElement.appendChild(contentFragment);:

Мы используем циклы for для создания строковых массивов нужных нам тегов option. Затем мы находим их теги ‹select› во фрагменте документа, хранящемся в переменной contentFragment, и добавляем параметры к соответствующим тегам ‹select›. Если вы сейчас запустите свой локальный сервер, вы должны увидеть следующий базовый скелет нашего приложения в окне браузера:

Создание сетки диаграммы Ганта

Давайте создадим сетку диаграммы Ганта, которая будет отображать наши задачи, временную шкалу и продолжительность наших задач. На приведенной ниже диаграмме есть шесть пронумерованных стрелок, которые показывают порядок, в котором будут созданы столбцы, строки и элементы длительности задачи. Создание каждой части сетки будет выполняться с помощью функций. Эти шесть функций будут вызываться в функции createGrid, которую мы вскоре создадим.

Давайте теперь выберем задачи и временные контейнеры из фрагмента контента, создадим функцию createGrid и вызовем ее. Добавьте следующие строки чуть выше ganttChartElement.appendChild(contentFragment);:

Сначала мы получаем контейнеры задач и периодов времени из фрагмента документа, contentFragment. Также получаем элементы формы «добавить задачу» и «добавить продолжительность задачи», а также элемент «добавить продолжительность задачи» ‹select›. Мы будем использовать эти элементы позже.

Затем мы определяем функцию createGrid. В этой функции мы определяем период планирования на основе значений параметров «От» и «До» ‹выбрать›. Затем мы используем нашу функцию полезности monthDiff, чтобы определить, на сколько месяцев распространяется период планирования. Если дата «От» больше даты «По», функция monthDiff возвращает ноль.

Каждый раз, когда вызывается createGrid, мы очищаем внутренний HTML-код containerTasks и containerTimePeriods. Мы делаем это для того, чтобы при обновлении задач или длительности задач и повторном создании сетки с помощью вызова createGrid старое содержимое, показывающее предыдущее состояние, удалялось. Затем мы вызываем шесть функций, необходимых для создания столбцов, строк и элементов продолжительности задачи. Функции пока закомментированы. Когда мы определяем каждую функцию, вы можете раскомментировать вызов функции, чтобы увидеть ее влияние на создание сетки. После определения функции createGrid мы вызываем ее для первоначального рендеринга.

Давайте определим шесть функций, вызываемых в createGrid, одну за другой.

Создание строк задач

Добавьте следующую функцию ниже ganttChartElement.appendChild(contentFragment);:

Сначала мы создаем три пустых строки. Затем мы создаем строку для каждой задачи. У каждой строки задачи есть два дочерних элемента: элемент ‹input› и кнопка удаления. Значением ввода является имя задачи. Здесь мы использовали ввод, чтобы его можно было легко редактировать. Также мы добавляем задачи в качестве опций для элемента «выбрать задачу» в форме «добавить задачу».

Теперь вы сможете видеть заполненные строки задач в своем приложении. Обязательно раскомментируйте вызов функции createTaskRows() в функции createGrid. Нам еще нужно сделать так, чтобы задача редактирования и удаления работала.

Добавление месяцев

Теперь добавим функцию два. Добавьте следующую функцию createMonthsRow ниже функции createTaskRows:

Обязательно раскомментируйте вызов функции createMonthRow() в функции createGrid.

Аргументы startMonth и numMonths определяют, сколько столбцов месяца будет и где месяцы начинаются. Количество столбцов является динамическим, поскольку пользователь может изменить период времени. Каждый раз, когда выбирается период времени, количество столбцов в контейнере containerTimePeriods определяется на основе количества месяцев в периоде времени. Мы используем свойство CSS grid-template-columns, чтобы столбец каждого месяца занимал одну часть ширины контейнера, чтобы контейнер был равномерно разделен на столбцы для каждого месяца.

Для каждого месяца мы создаем новую строку периода времени. Мы добавляем к нему элемент ‹span› как дочерний. Этот элемент span центрирован по вертикали в строке и содержит метку месяца и года.

Теперь вы сможете увидеть в своем приложении одну строку с текстом «Январь 2022». Однако изменение диапазона дат периода времени не приведет к обновлению месяцев. Нам нужно добавить прослушиватели событий для прослушивания изменений в диапазоне дат, чтобы мы могли динамически обновлять сетку.

Добавьте следующие прослушиватели событий ниже функции createMonthsRow:

Мы слушаем событие «change» в тегах ‹select› периода времени, чтобы проверить, изменились ли диапазоны года и месяца. Если они есть, мы заново создаем сетку, вызывая функцию createGrid.

Теперь вы сможете увидеть, как обновляются строки периода времени при изменении периода планирования. Для больших периодов времени в контейнере сетки есть горизонтальная прокрутка с идентификатором «gantt-grid-container__time». Если выбран недопустимый период времени, например, с января 2023 г. по январь 2022 г., будет отображаться только первый месяц даты «С».

Добавление дней месяца

Теперь добавим функцию три. Добавьте следующую функцию createDaysRow над обработчиками событий «change»:

Обязательно раскомментируйте вызов функции createDaysRow() в функции createGrid.

Эта функция перебирает количество месяцев, указанное в качестве аргумента, и создает элемент периода времени, timePeriodEl, для каждого месяца. Мы создаем объект Date для начального месяца, который сохраняется в переменной month. В каждой итерации по количеству месяцев мы увеличиваем дату переменной месяца на один месяц, используя метод setMonth объекта Date.

У нас есть вложенный цикл для создания дней каждого месяца. Количество дней в каждом месяце рассчитывается с помощью нашей служебной функции getDaysInMonth. Затем мы перебираем каждый день месяца и создаем тег ‹div› элемента дня с именем dayEl. Мы создаем элемент ‹span›, который отображает значение дня. Мы добавляем его как дочерний элемент элемента дня. Затем мы добавляем элемент дня в качестве дочернего элемента к элементу периода времени для данного месяца.

Если вы сейчас просмотрите свое приложение, то увидите, что ячейки дней месяца располагаются по центру по вертикали. Это связано с тем, что контейнер с идентификатором «gantt-grid-container__time» имеет свойство отображения, установленное на сетку. Это понадобится нам позже для контейнера перетаскивания, который мы добавим внутрь сетки. Как только мы добавим все строки, строки будут выровнены правильно.

Добавление дней недели

Добавим четвертую функцию. Добавьте следующую функцию createDaysOfTheWeekRow над прослушивателями событий «change»:

Обязательно раскомментируйте вызов функции createDaysOfTheWeekRow() в функции createGrid.

Эта функция похожа на предыдущую функцию createDaysRow с той разницей, что мы добавляем в элемент ‹span› дни недели вместо числа числа месяца. Мы используем нашу служебную функцию dayOfTheWeek для определения дня недели, который отображается в виде заглавной строки первой буквы его имени. Мы используем текущий год, месяц и день, чтобы вычислить, какой сегодня день недели. Вспомогательная функция dayOfTheWeek использует метод getDay Date, чтобы определить это.

Создание контейнера периода времени

Добавим пятую функцию. Добавьте следующую функцию createTaskRowsTimePeriods над прослушивателями событий «change»:

Обязательно раскомментируйте вызов функции createTaskRowsTimePeriods() в функции createGrid.

Эта функция создает элемент dayElContainer ‹div›, который будет зоной перетаскивания для перетаскиваемых элементов длительности задачи. Мы используем тот же CSS-стиль grid-template-columns, что и в функции createMonthsRow для контейнера containerTimePeriods. Это сделано для того, чтобы количество столбцов в контейнере определялось количеством месяцев в выбранном периоде времени.

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

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

Добавление длительности задач

Давайте добавим шестую и последнюю функцию, вызываемую в функции createGrid, которая добавит продолжительность задач в виде горизонтальных полос в нашу сетку. Добавьте следующую функцию addTaskDurations над обработчиками событий «change»:

Обязательно раскомментируйте вызов функции addTaskDurations() в функции createGrid.

Для каждой задачи taskDuration, переданной в наш компонент диаграммы Ганта, мы находим начальную позицию, где разместить продолжительность задачи в нашей сетке. Это хранится в переменной startCell. Мы используем нашу служебную функцию createFormattedDateFromDate для преобразования свойства start длительности каждой задачи, которое является объектом Date, в строку. Задача продолжительности задачи и дата начала используются для определения правильной начальной позиции в сетке путем нахождения ячейки с соответствующими атрибутами данных для продолжительности задачи. Если начальная ячейка найдена, мы вызываем функцию createTaskDurationEl, которая создаст элемент длительности задачи и добавит его в качестве дочернего элемента к ячейке в ее начальной позиции в сетке. Давайте теперь определим эту функцию. Добавьте createTaskDurationEl под объявлением функции addTaskDurations:

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

Мы создаем элемент длительности задачи и присваиваем ему класс «taskDuration» для стилей CSS. Он имеет z-индекс 1, так что он помещается поверх сетки. Его положение является абсолютным, а ячейка, на которую оно помещено, имеет относительное положение, так что продолжительность задачи помещается в его начальную ячейку.

Чтобы продолжительность задачи охватывала правильное количество ячеек, мы используем нашу служебную функцию dayDiff, чтобы определить, сколько дней длится наша задача. Мы используем это значение для расчета ширины элемента длительности задачи, установив его ширину равной 100 % от ширины ячейки, что равно одному дню, умноженному на количество дней. Функция возвращает дни, которые мы будем использовать при добавлении функции перетаскивания.

Делаем диаграмму Ганта функциональной

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

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

Давайте добавим функцию перетаскивания к длительности наших задач, чтобы мы могли изменять положение элементов длительности задачи в сетке. Добавьте следующие строки внутрь функции createTaskDurationEl, чуть выше startCell.appendChild(taskDurationEl); линия:

Первое, что мы делаем, — устанавливаем для свойства draggable объекта taskDurationEl значение true. Нам не нужно это свойство для работы нашего перетаскивания, но оно полезно для UX, поскольку создает «призрачное» изображение элемента длительности задачи, которое прикрепляется к курсору мыши во время его перетаскивания.

Затем мы добавляем два прослушивателя событий в taskDurationEl: «dragstart» и «dragend». Когда элемент длительности задачи перетаскивается, мы добавляем к нему класс «перетаскивание». Когда перетаскивание заканчивается, мы удаляем класс. Мы делаем это, чтобы стилизовать перетаскиваемый элемент длительности задачи. В нашем файле style.js мы дали элементам с классом «перетаскивания» непрозрачность 0,5. Если вы попытаетесь перетащить элемент продолжительности задачи и проверить вкладку «Элементы» в своих инструментах разработки, вы увидите, что класс «перетаскивания» добавляется, когда начинается перетаскивание, и удаляется, когда перетаскивание заканчивается.

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

Если вы попытаетесь перетащить продолжительность задачи, вы увидите, что курсор изменится на значок не разрешено. Это связано с тем, что перетаскивание элемента внутри другого элемента по умолчанию отключено. Подробнее об этом можно прочитать в следующей статье: Использование HTML5 Drag and Drop API. Чтобы удалить это поведение по умолчанию, добавьте следующие строки кода в только что добавленный прослушиватель событий dragend:

Это добавляет прослушиватель событий «dragover» к dayElContainer, который является нашей зоной перетаскивания. Это событие срабатывает постоянно, когда на него перетаскивают элемент. Мы предотвращаем поведение события по умолчанию с помощью метода preventDefault. Наш курсор теперь не будет отображать значок «не разрешено» в нашей зоне перетаскивания.

Приступим к работе с помощью перетаскивания. В функцию createTaskRowsTimePeriods добавьте следующую строку кода чуть выше timePeriodEl.appendChild(dayEl); линия:

Мы добавляем атрибут onDrop, чтобы определить обработчик события drop для каждой ячейки в зоне перетаскивания сетки. Функция onTaskDurationDrop вызывается, когда элемент помещается в ячейку. Давайте определим функцию onTaskDurationDrop. Добавьте следующие строки ниже функции createTaskDurationEl:

Мы определяем, на какую ячейку был переброшен элемент длительности задачи, обращаясь к целевому свойству события. Мы предотвращаем добавление длительности задачи к продолжительности другой задачи, проверяя, имеет ли targetCell атрибут draggable. Только длительности задач имеют этот атрибут.

Затем мы находим задачу в массиве длительности задач, используя идентификатор перетаскиваемого элемента длительности задачи. TaskDurationElDragged был определен в начале перетаскивания. Затем мы получаем имя продолжительности задачи и дату начала из ее атрибутов данных. Мы удаляем его из DOM и создаем новый элемент длительности задачи для новой позиции длительности задачи с помощью функции createTaskDurationEl.

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

Возможность удаления длительности задач

Теперь давайте сделаем длительность нашей задачи удаляемой, нажав кнопку удаления, когда длительность задачи находится в фокусе. Добавьте следующие строки в функцию createTaskDurationEl над startCell.appendChild(taskDurationEl); линия:

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

Наш прослушиватель событий «keydown» прослушивает, когда элемент длительности задачи находится в фокусе и нажата кнопка удаления. В этом случае мы вызываем функцию deleteTaskDuration. Давайте определим эту функцию ниже определения функции onTaskDurationDrop:

В этой функции мы определяем, какую задачу удалить, проверяя, какая продолжительность задачи была в фокусе, используя свойство target события. Затем мы удаляем его из DOM и обновляем длительность задачи, отфильтровывая продолжительность удаленной задачи из массива длительности задач.

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

Доводим до ума форму «Добавить продолжительность задачи»

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

Это прослушивает событие «отправить» в нашей форме «Добавить продолжительность задачи».

Давайте определим функцию обработчика событий. Добавьте следующую функцию handleAddTaskDurationForm под объявлением функции deleteTaskDuration:

Первое, что делает эта функция обработчика событий, — это предотвращает поведение по умолчанию «отправить событие». Это предотвращает отправку формы с помощью запроса GET, что может привести к обновлению страницы. Мы потеряем сделанные нами изменения, поскольку мы не сохраняем изменения данных.

Затем мы получаем новое имя задачи, дату начала и дату окончания из входных данных формы. Мы создаем временную метку, используя Date.now(), чтобы создать уникальный идентификатор для нашей новой продолжительности задачи. Затем мы используем эти значения для создания объекта taskDuration и добавляем его в массив данных продолжительности задач.

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

Делаем задачи редактируемыми

Входные данные задачи можно редактировать, но они не обновляют массив данных задач, а правки не обновляют параметры в «Какая задача?» ‹выбрать› элемент формы «Добавить продолжительность задачи».

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

Мы получаем идентификатор задачи для обновления из родительского узла целевого события. Целью будет задача ‹input›, у которой есть родительский элемент с атрибутом id, установленным на идентификатор задачи, который мы установили в функции createTaskRows. Затем мы удаляем задачу, которая была отредактирована, из массива данных задач и добавляем новую задачу с обновленным именем задачи, чтобы обновить задачи. Также мы сортируем задачи по ID, то есть по порядку их создания.

Чтобы вызвать updateTasks при изменении входного значения задачи, нам нужно добавить прослушиватель событий «change» к каждому входу задачи. Добавьте следующие строки в функцию createTaskRows ниже taskRowElInput.value = task.name; линия:

Массив данных задач будет обновляться при изменении ввода задачи.

Нам также необходимо обновить «Какую задачу?» ‹выбрать› элемент формы «Добавить продолжительность задачи». Добавьте следующий код в конец функции updateTasks:

Это обновит элемент taskSelect ‹select› с параметрами, отражающими обновленное состояние задач.

Делаем задачи удаляемыми

Теперь давайте заставим кнопки удаления задач работать. В функции createTaskRows ниже taskRowElDelBtn.innerText = «✕»; строку, добавьте следующую строку:

Это добавляет прослушиватель событий «щелчок» к каждой кнопке удаления задачи. Давайте добавим функцию deleteTask, которая вызывается при срабатывании события клика. Добавьте следующую функцию deleteTask ниже функции updateTasks:

Эта функция получает идентификатор задачи из родительского узла, как мы делали это для ‹input› в функции updateTasks. Затем мы отфильтровываем задачу для удаления из массива данных задач, чтобы обновить задачи. Мы также удаляем все длительности задач, связанные с удаленной задачей, отфильтровывая все длительности задач, идентификатор задачи которых совпадает с идентификатором удаленной задачи.

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

Делаем форму «Добавить задачу» функциональной

Чтобы сделать форму добавления задачи функциональной, мы сначала создадим прослушиватель событий «отправить». Добавьте следующую строку в функцию GanttChart внизу:

Нам нужно определить функцию handleAddTaskForm, которая вызывается при отправке формы «Добавить задачу». Добавьте следующую функцию handleAddTaskForm ниже определения функции handleAddTaskDurationForm:

Сначала мы предотвращаем поведение события отправить по умолчанию, а затем получаем новое значение имени задачи из формы. Мы используем метод массива уменьшить, чтобы определить новый идентификатор задачи. Значения идентификатора задачи — это числа, представляющие порядок, в котором они были созданы. Они отсортированы по идентификатору. Мы находим самый большой текущий идентификатор задачи, а затем добавляем к нему единицу, чтобы создать идентификатор для новой задачи.

Затем мы вызываем функцию createGrid для повторного рендеринга сетки, чтобы она отображала новую строку задачи.

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

Следующие шаги

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

  • Улучшить стиль. На показанном снимке экрана есть дополнительный CSS, не включенный в примеры выше, но который вы можете найти в репозитории GitHub.
  • Сделайте количество дней, в течение которых продолжительность задачи охватывает редактируемым.
  • Сделайте задачи переупорядочиваемыми.
  • Очистите данные о задаче и продолжительности задачи, поскольку установка данных, предоставляемых пользователем, с использованием innerHTML представляет собой угрозу безопасности.
  • Добавьте аргументы конфигурации или свойства, которые позволят пользователю легко изменять свойства компонента диаграммы Ганта, например стиль или тему.
  • Сохраняйте данные в базе данных.
  • Сделайте компонент веб-компонентом, чтобы у вас был хорошо инкапсулированный пользовательский HTML-элемент.

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

  • Планирование разрешения конфликтов.
  • Обработка разницы часовых поясов.
  • Группировка задач.
  • Дополнительные информационные этикетки.
  • Функции отмены и повтора.
  • Комплексная настройка задачи и длительности задачи.
  • Экспорт в Excel, PDF или в виде изображения.
  • Оптимизация производительности для больших наборов данных.
  • Интеграция с другими инструментами управления проектами, такими как доски задач.

Строить или покупать?

Эта статья дает вам отправную точку для построения собственной диаграммы Ганта. Если вместо этого вы хотите купить готовое проверенное решение, которое просто работает, взгляните на некоторые примеры нашей Диаграммы Бринта-Ганта.