templates/js/library/month_range_slider/month_range_slider.js.twig line 1

Open in your IDE?
  1. class MonthRangeSlider {
  2.     options = {
  3.         onChange: null
  4.     };
  5.     constructor(containerId, startDateStr, initialStartMonth = null, initialEndMonth = null, options = null) {
  6.         this.containerId = containerId;
  7.         this.$container = $('#' + containerId);
  8.         if (options) {
  9.             this.options = { ...this.options, ...options };
  10.         }
  11.         // Парсим начальную дату
  12.         const startDateParts = startDateStr.split('-');
  13.         this.baseYear = parseInt(startDateParts[0]);
  14.         this.baseMonth = parseInt(startDateParts[1]) - 1; // 0-based
  15.         // Вычисляем общее количество месяцев от базовой даты до текущей даты + 12 месяцев
  16.         const now = new Date();
  17.         const currentYear = now.getFullYear();
  18.         const currentMonth = now.getMonth();
  19.         const baseDate = new Date(this.baseYear, this.baseMonth, 1);
  20.         const endDate = new Date(currentYear, currentMonth + 12, 1);
  21.         this.totalMonths = this.getMonthsDiff(baseDate, endDate);
  22.         // Инициализируем позиции слайдеров
  23.         if (initialStartMonth !== null && initialStartMonth !== undefined) {
  24.             this.startMonthIndex = initialStartMonth;
  25.         } else {
  26.             this.startMonthIndex = 0;
  27.         }
  28.         if (initialEndMonth !== null && initialEndMonth !== undefined) {
  29.             this.endMonthIndex = initialEndMonth;
  30.         } else {
  31.             this.endMonthIndex = Math.min(11, this.totalMonths - 1);
  32.         }
  33.         // Валидация
  34.         if (this.startMonthIndex < 0) this.startMonthIndex = 0;
  35.         if (this.endMonthIndex >= this.totalMonths) this.endMonthIndex = this.totalMonths - 1;
  36.         if (this.startMonthIndex > this.endMonthIndex) {
  37.             this.startMonthIndex = 0;
  38.             this.endMonthIndex = Math.min(11, this.totalMonths - 1);
  39.         }
  40.         this.isDragging = false;
  41.         this.activeThumb = null;
  42.         this.$trackContainer = this.$container.find('.month-range-track-container');
  43.         this.$track = this.$container.find('.month-range-track');
  44.         this.$selected = this.$container.find('.month-range-selected');
  45.         this.$thumbStart = this.$container.find('.month-range-thumb-start');
  46.         this.$thumbEnd = this.$container.find('.month-range-thumb-end');
  47.         this.$label = this.$container.find('.month-range-label');
  48.         this.$ticks = this.$container.find('.month-range-ticks');
  49.         this.initTicks();
  50.         this.initEvents();
  51.         this.updateUI();
  52.     }
  53.     getMonthsDiff(date1, date2) {
  54.         let months = (date2.getFullYear() - date1.getFullYear()) * 12;
  55.         months += date2.getMonth() - date1.getMonth();
  56.         return months + 1; // +1 чтобы включить оба месяца
  57.     }
  58.     getDateFromIndex(monthIndex) {
  59.         const totalMonths = this.baseMonth + monthIndex;
  60.         const year = this.baseYear + Math.floor(totalMonths / 12);
  61.         const month = totalMonths % 12;
  62.         return new Date(year, month, 1);
  63.     }
  64.     formatMonth(date) {
  65.         const months = [
  66.             'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
  67.             'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
  68.         ];
  69.         return months[date.getMonth()] + ' ' + date.getFullYear();
  70.     }
  71.     initTicks() {
  72.         // Очищаем существующие риски
  73.         this.$ticks.empty();
  74.         // Создаем риски только для первого месяца каждого года (январь)
  75.         for (let i = 0; i < this.totalMonths; i++) {
  76.             const date = this.getDateFromIndex(i);
  77.             const isYearStart = date.getMonth() === 0; // Январь
  78.             // Создаем риску только для января
  79.             if (isYearStart) {
  80.                 const percent = (i / (this.totalMonths - 1)) * 100;
  81.                 const $tick = $('<div></div>')
  82.                     .addClass('month-range-tick major')
  83.                     .attr('data-month-index', i)
  84.                     .css('left', percent + '%');
  85.                 // Добавляем подпись с годом
  86.                 const $label = $('<div></div>')
  87.                     .addClass('month-range-tick-label')
  88.                     .text(date.getFullYear());
  89.                 $tick.append($label);
  90.                 this.$ticks.append($tick);
  91.             }
  92.         }
  93.     }
  94.     initEvents() {
  95.         const self = this;
  96.         // Начало перетаскивания
  97.         this.$thumbStart.on('mousedown', function(e) {
  98.             e.preventDefault();
  99.             e.stopPropagation();
  100.             self.startDragging('start');
  101.         });
  102.         this.$thumbEnd.on('mousedown', function(e) {
  103.             e.preventDefault();
  104.             e.stopPropagation();
  105.             self.startDragging('end');
  106.         });
  107.         // Начало перетаскивания при клике на контейнер трека
  108.         this.$trackContainer.on('mousedown', function(e) {
  109.             e.preventDefault();
  110.             self.onTrackMouseDown(e);
  111.         });
  112.         // Перетаскивание
  113.         $(document).on('mousemove', function(e) {
  114.             if (self.isDragging) {
  115.                 self.onDrag(e);
  116.             }
  117.         });
  118.         // Конец перетаскивания
  119.         $(document).on('mouseup', function() {
  120.             if (self.isDragging) {
  121.                 self.stopDragging();
  122.             }
  123.         });
  124.         // Touch events для мобильных устройств
  125.         this.$thumbStart.on('touchstart', function(e) {
  126.             e.preventDefault();
  127.             e.stopPropagation();
  128.             self.startDragging('start');
  129.         });
  130.         this.$thumbEnd.on('touchstart', function(e) {
  131.             e.preventDefault();
  132.             e.stopPropagation();
  133.             self.startDragging('end');
  134.         });
  135.         this.$trackContainer.on('touchstart', function(e) {
  136.             e.preventDefault();
  137.             self.onTrackMouseDown(e.touches[0]);
  138.         });
  139.         $(document).on('touchmove', function(e) {
  140.             if (self.isDragging) {
  141.                 self.onDrag(e.touches ? e.touches[0] : e);
  142.             }
  143.         });
  144.         $(document).on('touchend', function() {
  145.             if (self.isDragging) {
  146.                 self.stopDragging();
  147.             }
  148.         });
  149.     }
  150.     startDragging(thumb) {
  151.         this.isDragging = true;
  152.         this.activeThumb = thumb;
  153.         $('body').css('user-select', 'none');
  154.         // Обновляем z-index для активного слайдера
  155.         if (thumb === 'start') {
  156.             this.$thumbStart.css('z-index', '3');
  157.             this.$thumbEnd.css('z-index', '2');
  158.         } else if (thumb === 'end') {
  159.             this.$thumbEnd.css('z-index', '3');
  160.             this.$thumbStart.css('z-index', '2');
  161.         }
  162.     }
  163.     stopDragging() {
  164.         this.isDragging = false;
  165.         this.activeThumb = null;
  166.         $('body').css('user-select', '');
  167.         if (this.options.onChange) {
  168.             this.options.onChange(this.startMonthIndex, this.endMonthIndex);
  169.         }
  170.     }
  171.     onDrag(e) {
  172.         const trackRect = this.$track[0].getBoundingClientRect();
  173.         const x = e.clientX - trackRect.left;
  174.         const percentage = Math.max(0, Math.min(1, x / trackRect.width));
  175.         const monthIndex = Math.round(percentage * (this.totalMonths - 1));
  176.         if (this.activeThumb === 'start') {
  177.             this.startMonthIndex = Math.min(monthIndex, this.endMonthIndex);
  178.         } else if (this.activeThumb === 'end') {
  179.             this.endMonthIndex = Math.max(monthIndex, this.startMonthIndex);
  180.         }
  181.         this.updateUI();
  182.     }
  183.     onTrackMouseDown(e) {
  184.         const trackRect = this.$track[0].getBoundingClientRect();
  185.         const x = e.clientX - trackRect.left;
  186.         const percentage = x / trackRect.width;
  187.         const monthIndex = Math.round(percentage * (this.totalMonths - 1));
  188.         // Определяем, какой ползунок ближе
  189.         const distToStart = Math.abs(monthIndex - this.startMonthIndex);
  190.         const distToEnd = Math.abs(monthIndex - this.endMonthIndex);
  191.         // Перемещаем ближайший слайдер к позиции клика и начинаем его перетаскивание
  192.         if (distToStart < distToEnd) {
  193.             this.startMonthIndex = Math.min(monthIndex, this.endMonthIndex);
  194.             this.activeThumb = 'start';
  195.             // Обновляем z-index для активного слайдера
  196.             this.$thumbStart.css('z-index', '3');
  197.             this.$thumbEnd.css('z-index', '2');
  198.         } else {
  199.             this.endMonthIndex = Math.max(monthIndex, this.startMonthIndex);
  200.             this.activeThumb = 'end';
  201.             // Обновляем z-index для активного слайдера
  202.             this.$thumbEnd.css('z-index', '3');
  203.             this.$thumbStart.css('z-index', '2');
  204.         }
  205.         this.updateUI();
  206.         // Начинаем перетаскивание выбранного слайдера
  207.         this.isDragging = true;
  208.         $('body').css('user-select', 'none');
  209.     }
  210.     updateUI() {
  211.         const startPercent = (this.startMonthIndex / (this.totalMonths - 1)) * 100;
  212.         const endPercent = (this.endMonthIndex / (this.totalMonths - 1)) * 100;
  213.         this.$thumbStart.css('left', startPercent + '%');
  214.         this.$thumbEnd.css('left', endPercent + '%');
  215.         this.$selected.css({
  216.             'left': startPercent + '%',
  217.             'width': (endPercent - startPercent) + '%'
  218.         });
  219.         // Обновляем цвета рисок
  220.         this.$ticks.find('.month-range-tick').each((index, tick) => {
  221.             const $tick = $(tick);
  222.             const monthIndex = parseInt($tick.attr('data-month-index'));
  223.             if (monthIndex >= this.startMonthIndex && monthIndex <= this.endMonthIndex) {
  224.                 $tick.addClass('selected');
  225.             } else {
  226.                 $tick.removeClass('selected');
  227.             }
  228.         });
  229.         const startDate = this.getDateFromIndex(this.startMonthIndex);
  230.         const endDate = this.getDateFromIndex(this.endMonthIndex);
  231.         const labelText = this.formatMonth(startDate) + ' - ' + this.formatMonth(endDate);
  232.         this.$label.text(labelText);
  233.     }
  234.     getStartMonth() {
  235.         return this.startMonthIndex;
  236.     }
  237.     getEndMonth() {
  238.         return this.endMonthIndex;
  239.     }
  240.     setRange(startMonth, endMonth) {
  241.         this.startMonthIndex = Math.max(0, Math.min(startMonth, this.totalMonths - 1));
  242.         this.endMonthIndex = Math.max(0, Math.min(endMonth, this.totalMonths - 1));
  243.         if (this.startMonthIndex > this.endMonthIndex) {
  244.             const temp = this.startMonthIndex;
  245.             this.startMonthIndex = this.endMonthIndex;
  246.             this.endMonthIndex = temp;
  247.         }
  248.         this.updateUI();
  249.     }
  250. }