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.         startDate: null,
  5.         endDate: null,
  6.     };
  7.     containerId = null;
  8.     $container = null;
  9.     /** Левая граница шкалы */
  10.     minDate = null;
  11.     /** Правая граница шкалы */
  12.     maxDate = null;
  13.     /** Текущий выбранный старт */
  14.     startDate = null;
  15.     /** Текущий выбранный конец */
  16.     endDate = null;
  17.     isDragging = false;
  18.     activeThumb = null;
  19.     $trackContainer = null;
  20.     $track = null;
  21.     $selected = null;
  22.     $thumbStart = null;
  23.     $thumbEnd = null;
  24.     $label = null;
  25.     $ticks = null;
  26.     $innerTwoColorThumbStart = null;
  27.     $innerTwoColorThumbEnd = null;
  28.     _innerHideTimeoutStart = null;
  29.     _innerHideTimeoutEnd = null;
  30.     constructor(containerId, minDate, maxDate = null, options = null) {
  31.         this.containerId = containerId;
  32.         this.$container = $('#' + containerId);
  33.         if (options) {
  34.             this.options = { ...this.options, ...options };
  35.         }
  36.         this.minDate = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
  37.         if (maxDate instanceof Date) {
  38.             this.maxDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), 1);
  39.         } else {
  40.             const now = new Date();
  41.             this.maxDate = new Date(now.getFullYear(), now.getMonth() + 12, 1);
  42.         }
  43.         const startDate = this.options.startDate instanceof Date ? this.options.startDate : null;
  44.         const endDate = this.options.endDate instanceof Date ? this.options.endDate : null;
  45.         this.startDate = startDate
  46.             ? new Date(startDate.getFullYear(), startDate.getMonth(), 1)
  47.             : new Date(this.minDate);
  48.         this.endDate = endDate
  49.             ? new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0)
  50.             : new Date(this.minDate.getFullYear(), this.minDate.getMonth() + Math.min(11, this._totalMonths() - 1) + 1, 0);
  51.         this._clampDates();
  52.         this.isDragging = false;
  53.         this.activeThumb = null;
  54.         this.$trackContainer = this.$container.find('.month-range-track-container');
  55.         this.$track = this.$container.find('.month-range-track');
  56.         this.$selected = this.$container.find('.month-range-selected');
  57.         this.$thumbStart = this.$container.find('.month-range-thumb-start');
  58.         this.$thumbEnd = this.$container.find('.month-range-thumb-end');
  59.         this.$label = this.$container.find('.month-range-label');
  60.         this.$ticks = this.$container.find('.month-range-ticks');
  61.         this.$innerTwoColorThumbStart = this.$thumbStart.find('.month-range-inner-outer');
  62.         this.$innerTwoColorThumbEnd = this.$thumbEnd.find('.month-range-inner-outer');
  63.         this.initTicks();
  64.         this.initEvents();
  65.         this.updateUI();
  66.     }
  67.     _totalMonths() {
  68.         return (this.maxDate.getFullYear() - this.minDate.getFullYear()) * 12
  69.             + (this.maxDate.getMonth() - this.minDate.getMonth()) + 1;
  70.     }
  71.     _clampDates() {
  72.         if (this.startDate < this.minDate) this.startDate = new Date(this.minDate);
  73.         if (this.endDate > this.maxDate) this.endDate = new Date(this.maxDate);
  74.         if (this.startDate > this.endDate) {
  75.             this.startDate = new Date(this.minDate);
  76.             this.endDate = new Date(this.minDate.getFullYear(), this.minDate.getMonth() + Math.min(11, this._totalMonths() - 1) + 1, 0);
  77.         }
  78.     }
  79.     _dateToIndex(date) {
  80.         return (date.getFullYear() - this.minDate.getFullYear()) * 12
  81.             + (date.getMonth() - this.minDate.getMonth());
  82.     }
  83.     _indexToDate(index) {
  84.         return new Date(this.minDate.getFullYear(), this.minDate.getMonth() + index, 1);
  85.     }
  86.     formatMonth(date) {
  87.         const months = [
  88.             'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
  89.             'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
  90.         ];
  91.         return months[date.getMonth()] + ' ' + date.getFullYear();
  92.     }
  93.     initTicks() {
  94.         this.$ticks.empty();
  95.         const total = this._totalMonths();
  96.         for (let i = 0; i < total; i++) {
  97.             const date = this._indexToDate(i);
  98.             if (date.getMonth() !== 0) continue;
  99.             const percent = (i / (total - 1)) * 100;
  100.             const $tick = $('<div></div>')
  101.                 .addClass('month-range-tick major')
  102.                 .attr('data-month-index', i)
  103.                 .css('left', percent + '%');
  104.             const $label = $('<div></div>')
  105.                 .addClass('month-range-tick-label')
  106.                 .text(date.getFullYear());
  107.             $tick.append($label);
  108.             this.$ticks.append($tick);
  109.         }
  110.     }
  111.     initEvents() {
  112.         const self = this;
  113.         this.$thumbStart.on('mousedown', function(e) {
  114.             e.preventDefault();
  115.             e.stopPropagation();
  116.             self.startDragging('start');
  117.         });
  118.         this.$thumbEnd.on('mousedown', function(e) {
  119.             e.preventDefault();
  120.             e.stopPropagation();
  121.             self.startDragging('end');
  122.         });
  123.         this.$trackContainer.on('mousedown', function(e) {
  124.             e.preventDefault();
  125.             self.onTrackMouseDown(e);
  126.         });
  127.         $(document).on('mousemove', function(e) {
  128.             if (self.isDragging) {
  129.                 self.onDrag(e);
  130.             }
  131.         });
  132.         $(document).on('mouseup', function() {
  133.             if (self.isDragging) {
  134.                 self.stopDragging();
  135.             }
  136.         });
  137.         this.$thumbStart.on('touchstart', function(e) {
  138.             e.preventDefault();
  139.             e.stopPropagation();
  140.             self.startDragging('start');
  141.         });
  142.         this.$thumbEnd.on('touchstart', function(e) {
  143.             e.preventDefault();
  144.             e.stopPropagation();
  145.             self.startDragging('end');
  146.         });
  147.         this.$trackContainer.on('touchstart', function(e) {
  148.             e.preventDefault();
  149.             self.onTrackMouseDown(e.touches[0]);
  150.         });
  151.         $(document).on('touchmove', function(e) {
  152.             if (self.isDragging) {
  153.                 self.onDrag(e.touches ? e.touches[0] : e);
  154.             }
  155.         });
  156.         $(document).on('touchend', function() {
  157.             if (self.isDragging) {
  158.                 self.stopDragging();
  159.             }
  160.         });
  161.     }
  162.     startDragging(thumb) {
  163.         this.isDragging = true;
  164.         this.activeThumb = thumb;
  165.         $('body').css('user-select', 'none');
  166.         if (thumb === 'start') {
  167.             this.$thumbStart.css('z-index', '3');
  168.             this.$thumbEnd.css('z-index', '2');
  169.         } else {
  170.             this.$thumbEnd.css('z-index', '3');
  171.             this.$thumbStart.css('z-index', '2');
  172.         }
  173.     }
  174.     stopDragging() {
  175.         this.isDragging = false;
  176.         this.activeThumb = null;
  177.         $('body').css('user-select', '');
  178.         if (this.options.onChange) {
  179.             this.options.onChange(this.startDate, this.endDate);
  180.         }
  181.     }
  182.     _indexFromEvent(e) {
  183.         const trackRect = this.$track[0].getBoundingClientRect();
  184.         const x = e.clientX - trackRect.left;
  185.         const percentage = Math.max(0, Math.min(1, x / trackRect.width));
  186.         return Math.round(percentage * (this._totalMonths() - 1));
  187.     }
  188.     onDrag(e) {
  189.         const index = this._indexFromEvent(e);
  190.         if (this.activeThumb === 'start') {
  191.             const newStart = this._indexToDate(index);
  192.             this.startDate = newStart;
  193.             if (this.startDate > this.endDate) {
  194.                 this.endDate = new Date(this.startDate);
  195.             }
  196.         } else if (this.activeThumb === 'end') {
  197.             const d = this._indexToDate(index);
  198.             this.endDate = new Date(d.getFullYear(), d.getMonth() + 1, 0);
  199.             if (this.endDate < this.startDate) {
  200.                 this.startDate = new Date(this.endDate);
  201.             }
  202.         }
  203.         this.updateUI();
  204.     }
  205.     onTrackMouseDown(e) {
  206.         const index = this._indexFromEvent(e);
  207.         const startIndex = this._dateToIndex(this.startDate);
  208.         const endIndex = this._dateToIndex(this.endDate);
  209.         const distToStart = Math.abs(index - startIndex);
  210.         const distToEnd = Math.abs(index - endIndex);
  211.         if (distToStart < distToEnd) {
  212.             this.startDate = this._indexToDate(Math.min(index, endIndex));
  213.             this.activeThumb = 'start';
  214.             this.$thumbStart.css('z-index', '3');
  215.             this.$thumbEnd.css('z-index', '2');
  216.         } else {
  217.             const d = this._indexToDate(Math.max(index, startIndex));
  218.             this.endDate = new Date(d.getFullYear(), d.getMonth() + 1, 0);
  219.             this.activeThumb = 'end';
  220.             this.$thumbEnd.css('z-index', '3');
  221.             this.$thumbStart.css('z-index', '2');
  222.         }
  223.         this.updateUI();
  224.         this.isDragging = true;
  225.         $('body').css('user-select', 'none');
  226.     }
  227.     updateUI() {
  228.         const total = this._totalMonths();
  229.         const startIndex = this._dateToIndex(this.startDate);
  230.         const endIndex = this._dateToIndex(this.endDate);
  231.         const startPercent = (startIndex / (total - 1)) * 100;
  232.         const endPercent = (endIndex / (total - 1)) * 100;
  233.         this.$thumbStart.css('left', startPercent + '%');
  234.         this.$thumbEnd.css('left', endPercent + '%');
  235.         this.$selected.css({
  236.             'left': startPercent + '%',
  237.             'width': (endPercent - startPercent) + '%'
  238.         });
  239.         this.$ticks.find('.month-range-tick').each((index, tick) => {
  240.             const $tick = $(tick);
  241.             const i = parseInt($tick.attr('data-month-index'));
  242.             if (i >= startIndex && i <= endIndex) {
  243.                 $tick.addClass('selected');
  244.             } else {
  245.                 $tick.removeClass('selected');
  246.             }
  247.         });
  248.         this.$label.text(this.formatMonth(this.startDate) + ' - ' + this.formatMonth(this.endDate));
  249.         if (startIndex === endIndex) {
  250.             if (this.$innerTwoColorThumbStart) {
  251.                 clearTimeout(this._innerHideTimeoutStart);
  252.                 this.$innerTwoColorThumbStart.css('display', 'block');
  253.                 setTimeout(() => this.$innerTwoColorThumbStart.css('opacity', 1), 10);
  254.             }
  255.             if (this.$innerTwoColorThumbEnd) {
  256.                 clearTimeout(this._innerHideTimeoutEnd);
  257.                 this.$innerTwoColorThumbEnd.css('display', 'block');
  258.                 setTimeout(() => this.$innerTwoColorThumbEnd.css('opacity', 1), 10);
  259.             }
  260.         } else {
  261.             if (this.$innerTwoColorThumbStart) {
  262.                 this.$innerTwoColorThumbStart.css('opacity', 0);
  263.                 clearTimeout(this._innerHideTimeoutStart);
  264.                 this._innerHideTimeoutStart = setTimeout(() => {
  265.                     if (this.$innerTwoColorThumbStart) this.$innerTwoColorThumbStart.css('display', 'none');
  266.                 }, 180);
  267.             }
  268.             if (this.$innerTwoColorThumbEnd) {
  269.                 this.$innerTwoColorThumbEnd.css('opacity', 0);
  270.                 clearTimeout(this._innerHideTimeoutEnd);
  271.                 this._innerHideTimeoutEnd = setTimeout(() => {
  272.                     if (this.$innerTwoColorThumbEnd) this.$innerTwoColorThumbEnd.css('display', 'none');
  273.                 }, 180);
  274.             }
  275.         }
  276.     }
  277.     getStartDate() {
  278.         return this.startDate;
  279.     }
  280.     getEndDate() {
  281.         return this.endDate;
  282.     }
  283.     setRange(startDate, endDate) {
  284.         this.startDate = new Date(startDate.getFullYear(), startDate.getMonth(), 1);
  285.         this.endDate = new Date(endDate.getFullYear(), endDate.getMonth() + 1, 0);
  286.         if (this.startDate > this.endDate) {
  287.             const tmp = this.startDate;
  288.             this.startDate = this.endDate;
  289.             this.endDate = tmp;
  290.         }
  291.         this._clampDates();
  292.         this.updateUI();
  293.     }
  294. }