Привет, Хабр! В свободное от работы время я занимаюсь разработкой своего проекта. На днях мне понадобилось разработать раздел с календарем и задачами, чтобы пользователи могли отслеживать свою деятельность. Увы, но полностью готовых решений я не нашел. API стандартного календаря Google не подходит, так как данные хочу хранить внутри контура проекта.

Спустя несколько часов поисков я наткнулся на плейлист разработчиков из Индии. В жизни все циклично — именно эти видео мне и помогли. Так я познакомился с классной open source-библиотекой FullCalendar, о которой расскажу в этой статье. Если вы уже начали составлять календари на 2025 год, добро пожаловать под кат!

Что за библиотека


FullCalendar — это open source-библиотека, которая позволяет создавать кастомные календари без лишних ручек по части бэкенда. Почти полностью фронтенд-решение, которое можно интегрировать без особых навыков. Есть версии как на чистом JavaScript, так и на React, Vue и старом добром Angular.


Главная страница FullCalendar.


Чем понравился FullCalendar


Есть два ограничения, которые я должен был учитывать при поиске решения. Первое: я не самый лучший фронтендер. Второе: мой проект построен на Django. Я убежден, что поиск решения — это всегда перебор разных вариантов, поэтому разводить лишний dependency hell из всяких React и Vue не хотелось.

Это open source


При интеграции вы получаете не какой-то iframe, а полноценный DOM-объект. Это значит, вы можете изменять CSS-стили для элементов календаря и обрабатывать события, связанные с ними. Это полезно, если нужно, например, показывать поп-апы по клику на конкретное задание.


Как я стилизовал стандартный шаблон под мобильный календарь.

Есть версия на нативном JavaScript


FullCalendar подкупил именно наличием версии из нативного JavaScript. Если не понравится, можно просто удалить link-импорты — и проект снова чистый. Но как это иногда бывает, любовь с первого взгляда превратилась в нечто большее.

Есть версия для бэкендеров с лапками


Нативный JavaScript — это хорошо. Но с учетом того, что и его я знаю так себе, не помешал бы Jquery. Разработчики FullCalendar предусмотрели и это. На каждый тип календаря, как я понял, есть две версии: расширенная (нативный JavaScript) и упрощенная (Jquery). К слову, версия с Jquery неплохая, для моего MVP подходит более чем.

Есть документация с песочницей


Очень порадовало, что на главном сайте библиотеки есть раздел Demos, в котором расположены самые популярные форматы календарей. Можно выбрать понравившийся, открыть его в Codepen и переиспользовать.


Раздел Demos с разными типами календарей.

Все — в JSON


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

$(function() {
    $('#calendar').fullCalendar({
        defaultView: 'agendaWeek',
        header: {
            left: 'prev,next today',
            center: 'title',
            right: 'agendaWeek,agendaDay'
        },

        // вот эта ссылка может быть размещена локально или в вашем S3
        events: 'https://fullcalendar.io/api/demo-feeds/events.json'
    })
});

Как сделать свой календарь


Рассмотрим реализацию календаря, на примере самого классического шаблона, в котором уже реализована механика, приближенная к Google Calendar.

Шаг 1. Прикрутите фронтенд


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

<!-- calendar.html -->
<!DOCTYPE html>
<html lang="en" >
<head>
    <meta charset="UTF-8">
    <title>Мой календарь</title>
    <link rel='stylesheet' href="./style.css">
    <!-- стили календаря рекомендую хранить локально -->        
    <link rel='stylesheet' href="./fullcalendar.min.css">
</head>
<body>
    <!-- тут что-то ваше –->

    <!-- вот этот блок понадобится, это контейнер для календаря –->
    <div id='calendar'></div>

    <!-- тут что-то ваше –->

    <script src='https://cdn.jsdelivr.net/npm/moment@2/min/moment.min.js'></script>
    <script src='https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js'></script>
    <script src='https://cdn.jsdelivr.net/npm/fullcalendar@3.10.5/dist/fullcalendar.min.js'></script>

    <!-- подгружаем главный скрипт, который все объединит –->
    <script src="./calendar_loader.js"></script>
</body>
</html>

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

// calendar_loader.js
$(function() {
 $('#calendar').fullCalendar({
   defaultView: 'agendaWeek',
   header: {
     left: 'prev,next today',
     center: 'title',
     right: 'agendaWeek,agendaDay'
   },
   // https://fullcalendar.io/docs/events-json-feed
   events: 'https://fullcalendar.io/api/demo-feeds/events.json'
 })
});

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


Фронтенд FullCalendar с тестовыми данными.

Шаг 2. Настраиваем отображение


В шаблоне выше я рекомендую хранить fullcalendar.min.css локально. Во-первых, он так будет быстрее загружаться. Во-вторых, мы можем его видоизменять. В этом ничего сложного нет: просто откройте инспектор кода и посмотрите, к каким классам принадлежат элементы вашего календаря. Нужно поменять цвет события? Посмотрите класс в инспекторе — и замените background-color в fullcalendar.min.css. Никакого rocket science.

Шаг 3. Продумайте логику


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


Схема хранения событий и подтягивания данных.

На самом деле, данные можно хранить любым способом. S3-хранилище мне нравится, потому что JSON-структуры сохраняются как объекты. Для меня это довольно удобно, т. к. в перспективе позволяет забыть о базах данных на десятки тысяч строк: пользователей и событий с каждым месяцем может быть все больше.

К тому же объектное хранилище выходит очень дешево, особенно если использовать холодный класс хранения. И последнее преимущество — возможность записи метаданных в объекты. В целом, это киллер-фича, которая позволяет записывать дополнительные сведения о событиях, не ограничиваясь стандартной структурой JSON для работы FullCalendar.

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

Шаг 4. Реализуйте логику


Рассмотрим на примере предложенной логики.

1. Создайте контейнер объектного хранилища S3. Это можно сделать, например, в облаке Selectel. Для этого перейдите в панель управления my.selectel.ru и откройте раздел Объектное хранилище. Нажмите Создать контейнер, введите его название, выберите тип и класс хранения. Для простоты эксперимента создадим публичный контейнер — не делайте так, если заботитесь о конфиденциальности событий.


Страница создания контейнера в панели управления.

2. Создайте сервисного пользователя в разделе Управление доступом → Сервисные пользователи, нажав на кнопку Добавить пользователя. После сгенерируйте для него S3-ключи — они понадобятся для подключения к объектному хранилищу из бэкенда вашего приложения. Так что сохраните их после получения.


Генерация S3-ключей для сервисного пользователя.

3. Подключитесь к контейнеру из своего приложения и создайте объект с JSON-структурой событий пользователя. Как это сделать с помощью S3-API, рассказали в документации.

# calendar_generator.py
# импорт boto3
import boto3

# ключи генерируем в разделе сервисных пользователей
# (замените на свои, эти — для примера)
access_key = "914866b003e94c569f7b5beb19cb0adf"
secret_key = "70232d4de75c4877916d0998db78dbd0"
# указываем типовой ендпоинт, название региона, access_key и aws_secret_access_key
s3 = boto3.client("s3", endpoint_url="https://s3.ru-1.storage.selcloud.ru",
                 region_name="ru-1", aws_access_key_id=access_key, aws_secret_access_key=secret_key,
                 verify=False)
# пользовательские данные (для примера)
users_calendars = {
   'user_1': {
       '09.12.24-13.12.24' : '[{"title":"Lunch","start":"2024-12-09T12:00:00+00:00"}]'
   }
}

# название контейнера до первой точки
# (можно посмотреть в панели управления)
bucket_name = 'fullcalendar'

# список пользователей и дат
users_list = list(users_calendars.keys())
dates_list = list(users_calendars["user_1"].keys())

# название объекта, в который мы запишем данные (наш json-файл)
# учтите: если хотите расположить внутри директории,
# пропишите ее на уровне названия объекта — она появится
key = f'{users_list[0]}/{dates_list[0]}.json'

#  данные, которые запишем внутрь объекта (тело объекта)
body = users_calendars[users_list[0]][dates_list[0]]

# загрузка объекта из строки
s3.put_object(Bucket=bucket_name, Key=key, Body=body)

Отлично! Мы подгрузили «откуда-то» данные о событиях пользователя user-1 на рабочей неделе с 9 по 13 декабря. Теперь при переходе в S3-хранилище вы увидите директорию и подгруженный объект:


4. Внутри fullcalendar.min.js замените адрес events на уникальную ссылку с событиями пользователя:

// calendar_loader.js
$(function() {
    ...
    // теперь здесь подгружаем ссылку на объект в s3
    // ссылку можно скопировать из панели управления
    events: 'https://f7f2013c-d34e-4560-99c6-5a60b6d5b291.selstorage.ru/user_1/09.12.24-13.12.24.json'
})
});

Готово — все события отображаются в календаре.


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

Комментарии (0)