![](http://habr.habrastorage.org/post_images/85c/2ee/db4/85c2eedb4fb9d7c4576841da27d8c59f.jpg)
При разработке проекта для транспортной компании, занимающейся пассажирскими перевозками, возникла задача реализовать свой аналог Google Calendar для встраивания внутрь системы.
В силу некоторых причин(глубокая интеграция в проект, связь с кучей разных сущностей, полный контроль над всеми частями кода и прочее), использовать решение от Google было нерационально со многих точек зрения.
Итак, условия задачи:
- Интерфейс должен быть максимально приближен к интерфейсу от Google (т.к. до этого использовали его)
- Нормальная реализация RFC 2445, его части касательно RRULE (паттернов повторения)
- Быстрая скорость просчета дат событий (в данном случае рейсов) и их рендер в браузере
- Максимальное использование существующих библиотек для уменьшения потраченного времени .
Если тема интересна или вам есть что сказать, т.к. работа еще ведется и этот пост затрагивает лишь малую часть — прошу под кат, буду рад осмысленным советам.
Первоначально, перед детализацией, решено было определить общую стратегию работы данного решения. Попытка использовать ruby и gem ice_cube провалилась, т.к. уже довольно большое количество кода было написано на php и часть проекта выносить в другой язык программирования не очень кошерно. Ну и проблемы с производительностью (или моя неопытность работы с RoR).
Как итог, после размышлений, родилось следующее:
- За визуализацию в виде календаря будет ответственен немного доработанный jQuery плагин FullCalendar
- Создание повторяющихся рейсов возложено на Scheduler.js из набора FuelUX
- Хранение в БД паттернов повторения будет реализовано в виде «FREQ=DAILY;INTERVAL=2;UNTIL=20130130T230000Z;» для уменьшения размера БД (т.к. если хранить каждый рейс отдельно и окончание повторений не назначено количество отдельных рейсов стремится в бесконечность)
- Конвертация паттернов повторения в набор дат рейсов будет реализовано на стороне клиента для разгрузки серверных мощностей
Первое, что необходимо будет реализовать — получение RRULE, конвертация в набор дат и рендер с помощью плагина FullCalendar. После непродолжительного поиска было найдено следующее решение для конвертации — rrule.js работающее как в браузере, так и в качестве приложения на node.js, что в дальнейшем предоставляет возможность перенести на сервер с клиента.
Примерный путь ясен — приступим. Предупреждаю сразу, разрабатывается прототип и код пишется исходя из скоростных показателей, а не качественных.
Допустим у нас имеется json массив RRULE правил с полями имя, длина и сам паттерн повторений. Его получение с бекенда мы опустим.
[{ "name": "Reccurence Event #1", "length": "120", "rrule": "DTSTART=20020201T083000Z;FREQ=WEEKLY;WKST=MO;BYDAY=WE,FR" }, { "name": "Reccurence Event #2", "length": "120", "rrule": "FREQ=MONTHLY;DTSTART=20000201T083000Z;WKST=MO;BYDAY=TU" }, { "name": "Reccurence Event #3", "length": "120", "rrule": "FREQ=DAILY;DTSTART=20000201T063000Z;WKST=MO;BYDAY=MO,FR" } ]
Инициализируем массивы и создаем из строк RRULE объекты плагина rrule.js:
var data = private_env.get_data(); var rules = new Array(); var occurs = new Array(); for (var k in data){ rules.push( { name: data[k].name, length: data[k].length, rrule: RRule.fromString(data[k].rrule) }); }
Получаем из наших объектов список дат для каждого рейса, где DATE_START и DATE_END соответствуют началу и концу прмоежутка, за который нам необходимо их получить:
for (var k in rules){ occurs.push( { name: rules[k].name, length: rules[k].length, occurs: rules[k]['rrule'].beetween(DATE_START,DATE_END) }); }
Очищаем календарь перед рендером:
$calendar.fullCalendar('removeEvents', function (event){ return true; });
И выводим наши рейсы на экран:
for (var k in occurs){ for (var i in occurs[k].occurs){ var event = { id: k, title: occurs[k].name, start: occurs[k].occurs[i], /* Не элегантно высчитываем конец рейса по длине в минутах, но пусть */ end: new Date(occurs[k].occurs[i].getTime()+(1000*60*occurs[k].length)), allDay: false }; /* Фикс renderEvent, чтобы не запускал рендер каждое добавление рейса, т.к. регрессия производительности */ $calendar.fullCalendar('renderEvent', event, 1, 0); } } /* Рендерим весь стек рейсов, отвечает последняя 1 за это */ this.fullCalendar('renderEvent',{allDay: false}, 0, 1);
Все вышенаписанное мы оборачиваем в одну функцию, к примеру render(DATE_START,DATE_END) и вызываем при событии viewRender плагина FullCalendar:
... viewRender: function(view, element){ $(private_env.env_self).service('render', 'between', view.visStart, view.visEnd); } ...
В данный момент у нас получилась примерно следующая картина:
Превращать пост в простыни из кода не вижу смысла, думаю общая идея ясна. Внешний вид и прочее доработать труда не составляет.
Пару слов о создании и редактировании.
Большое количество событий в FullCalendar позволяет нам реализовать функционал редактирования. Необходимый функционал:
- При клике открывается форма редактирования рейса
- При перетаскивании, ресайзе выдаем запрос на варианты действий. Редактирование отдельно взятого рейса, редактирование всей цепочки повторений, редактирование будущих повторений
Задача тривиальна и сводится к тому, чтобы навешать функций на события eventClick, eventDrop, eventResize. Кстати, у двух последних имеется возможность отмены действий:
... revertFunc(); ...
Единственный нюанс. При выборе «Изменять только отдельный рейс» мы режем «RRULE FREQ=MONTHLY;DTSTART=20000201T083000Z;WKST=MO;BYDAY=TU» на три разных паттерна.
То что было до рейса этого, сам текущий рейс и то, что будет после. Решается это изменением опций в правиле DTSTART и UNTIL.
Что касается создания рейсов — Scheduler.js может выдавать на выход строку повторений согласно RFC 2445, что и необходимо нам:
... $('#myScheduler').scheduler('value') ...
Дорабатывать данную солянку из плагинов еще предстоит довольно долго, но направление куда двигаться ясно.
И спасибо вам за внимание, если имеется критика или предложения по улучшению — прошу в студию и буду только рад.
ссылка на оригинал статьи http://habrahabr.ru/post/210808/
Добавить комментарий