Фильтрация данных с DateRangepicker. Встроенные интервалы и навигация

В мире разработки часто возникает необходимость отфильтровывать большие объемы данных по различным временным интервалам. Это может быть неделя, год или любой другой период. Например в Webix Inventory App мы решили эту задачу с помощью компонентов DateRangePicker и Richselect. Как вы уже догадались, в этой статье мы создадим инструмент, который поможет отфильтровывать данные по определённым временным интервалам, а также переключаться между ними.

Вот, что должно получиться:

View code >>

Давайте приступать к работе!

Строим интерфейс

Наш инструмент включает в себя 3 контрола:

  • DateRangePicker для выбора дат
  • Richselect с заранее заданными интервалами
  • arrows для переключения интервалов вперёд/назад

Так как задействованы несколько контролов, хорошо бы поместить их в контейнер. Webix Toolbar отлично подойдёт. Создайте тулбар и внутри опишите каждый из контролов. Дейтпикер должен быть минимум 220px по ширине. Для удобства, укажите изначальный диапазон дат с помощью свойства value. Чтобы спрятать все лишние кнопки и иконки в календаре, добавьте свойство suggest и внутри его поля body скройте кнопки и иконки, как это показано ниже:

{
  view:"toolbar", css:"webix_dark", paddingX:12,
    cols:[
      // ...
      {
        view:"daterangepicker", id:"dates", width:220,
        value:{
          start:webix.Date.monthStart(new Date()),
          end:webix.Date.monthStart(webix.Date.add(new Date(), 1, "month"))
        },
        suggest:{
          view:"daterangesuggest",
          body:{
            button:false, icons:false // hiding buttons
          }
        }
      },
      // ...
    ],
  // конфиг
}

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

{
  view:"toolbar", css:"webix_dark", paddingX:12,
    cols:[
      { view:"icon", icon:"wxi-angle-left", id:"prev"},
      {/* конфигурация дейтпикера */},
      { view:"icon", icon:"wxi-angle-right", id:"next"},
      // ...
    ]
}

Вам также потребуется список с заранее заданными интервалами. В нашем демо это год, месяц, 12 месяцев и произвольный период. Со значениями первых трёх всё предельно ясно, однако последнему нужно добавить свойство $empty, чтобы при клике по этому варианту текущее значение ричселекта сбрасывалось. Создайте Richselect и передайте ему необходимые опции.

{
  view:"toolbar", css:"webix_dark", paddingX:12,
   cols:[
     // стрелки и дейтпикер
     {
       view:"richselect", width:120, value:"month",
       id:"dateMode", placeholder:"Custom",
       options:[
          { $empty:true, id:"$empty", value:"Custom" },
          { id:"month", value:"Month" },
          { id:"year", value:"Year" },
          { id:"12", value:"12 months" }
       ],
     }
   ]
}

К этому моменту у вас должен быть полностью готовый интерфейс.

Посмотреть код этого шага >>

Описываем логику

После того как вы создали интерфейс, самое время описать поведение контролов.

Настраиваем Richselect

Вы создали список с опциями и теперь нужно оживить его. Ричселект будет задавать интервал дат для дейтпикера (месяц, год или последние 12 месяцев). С помощью обработчика onChange вы можете следить за изменениями в ричселекте и передавать соответствующее значение в дейтпикер:

{
  view:"richselect",  
    // конфиг
    on:{
      onChange: function(value){
        setRange(value);
      }
    }
}

function setRange(value){
  if (value){
    let dates = {};
    switch (value){
      case "month":
      case "year":
        dates.start = webix.Date.monthStart(new Date());
        dates.end = webix.Date.monthStart(webix.Date.add(new Date(), 1, value));
        break;
      case "12":
        dates.start = webix.Date.monthStart(webix.Date.add(new Date(), -11, "month"));
        dates.end = webix.Date.monthStart(webix.Date.add(new Date(), 1, "month"));
    }
  }
}

Функция setRange принимает ID выбранной опции и в соответствии с ним меняет значение дейтпикера.

Посмотреть код этого шага >>

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

Добавляем произвольный вариант

В нашем демо за произвольный интервал отвечает дейтпикер. Когда пользователь выбирает диапазон дат, который не совпадает ни с одним заранее заданных, вам необходимо сбрасывать текущее значение ричселекта. Для этого добавьте обработчик onChange, который будет срабатывать всякий раз, когда пользователь будет выбирать диапазон.

const ranges = [
// число дней в миллисекундах
  31622400000, // 366
  31536000000, // 365
  2678400000, // 31
  2592000000, // 30
  2505600000, // 29
  2419200000 // 28
];



{
  view: “daterangepicker”,
  on:{
    onChange:function(nv){
      /*если интервал не совпадает с каким либо из предопределённых, указать “custom”
      не совершать никаких действий, пока пользователь не выберет конечную дату диапазона */

      if (nv.start && nv.end){
      // range in ms
        const range = webix.Date.datePart(nv.end) - webix.Date.datePart(nv.start);
        const r = ranges.indexOf(range);
        if (r === -1){
          $$("dateMode").setValue("");
        }            
        // другие диапазоны
    this.getPopup().hide();
      }
    }
  }
  // конфиг
}

Так как пользователь может выбрать месяц, год, и пр. вручную, вам необходимо проверять, не совпадает ли выбранное значение с каким-либо из заранее заданных интервалов (хранятся в массиве ranges в миллисекундах). Если совпадает, меняйте значение ричселекта на то, с которым совпало (год, месяц…), если нет — сбрасывайте значение ричселекта.

Посмотреть код этого шага >>

Переключаем интервалы с помощью стрелок

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

  // внутри конфига тулбара
  [
    { view:"icon", icon:"wxi-angle-left", id:"prev", click: function(){
      changeRange(-1);
    }},
    // …
    { view:"icon", icon:"wxi-angle-right", id:"next", click: function(){
      changeRange(1);
    }},
  ]

Функция принимает параметр (1 или -1) и переключает интервал вперёд или назад соответственно. Однако прежде всего необходимо заблокировать стрелки, пока пользователь не выберет начальную и конечную даты. Помимо этого вам необходимо скопировать даты, чтобы не менять оригинальный объект.

function changeRange(sign){
  const mode = $$("dateMode").getValue();
  const orDate = $$("dates").getValue();

  // не начинать работу, пока пользователь не выберет диапазон
  if (orDate.start && orDate.end){
    let dates = {
      start:webix.Date.copy(orDate.start),
      end:webix.Date.copy(orDate.end)
    };
  // дальнейшая логика
  }
}

Теперь остаётся вычислить дату в соответствии с текущей опцией в ричселекте (или её отсутствии) и переданного в функцию changeRange аргумента. Если ни один из интервалов не выбран (выбрана опция “Custom”), необходимо переключить интервал в соответствии с текущем значением дейтпикера. Всё, что вам нужно сделать — менять начальную и конечную даты по указанному интервалу. Поэтому имеет смысл воспользоваться методом add, который доступен в модуле webix.Date.

function changeRange(sign){
  if (orDate.start && orDate.end){
    // …

    // проверяем, соответствует ли выбранный интервал какому-либо из предопределённых
    if (mode){
      switch (mode){
        case "month":
        case "year":
          dates.start = webix.Date.add(dates.start, sign*1, mode);
          dates.end = webix.Date.add(dates.end, sign*1, mode);
          break;
        case "12":
          dates.start = webix.Date.monthStart(webix.Date.add(dates.start, sign*12, "month"));
          dates.end = webix.Date.monthStart(webix.Date.add(dates.end, sign*12, "month"));
      }
    } else {
      const period = webix.Date.datePart(dates.end) - webix.Date.datePart(dates.start);
      const days = period / (1000 * 60 * 60 * 24);
      dates.start = webix.Date.add(dates.start, sign*days, "day");
      dates.end = webix.Date.add(dates.end, sign*days, "day");
    }
  }
}

Выберите диапазон:

View code >>

Вот и всё! Теперь контрол во всеоружии и можно испытать его в действии, например, для фильтрации данных в таблице.

Используем контрол для фильтрации данных

Получившийся контрол можно использовать для фильтрации данных по интервалам дат. Давайте отфильтруем таблицу. Будем сравнивать поле элементов «start» с интервалом из дейтпикера.

{
 view: “daterangepicker”,
  on:{
    onChange:function(nv){
       /* если интервал не совпадает с каким либо из предопределённых, указать “custom”
       не совершать никаких действий, пока пользователь не выберет конечную дату диапазона */

      if (nv.start && nv.end){
        // ....

        // фильтруем таблицу
        $$("data").filter(function(obj){
          return obj.start >= nv.start && obj.start < nv.end;
        });
      }
    }
  }
}

View code >>

Подведём итоги

Совместив Daterange, Richselect и иконки мы получили небольшой, но полезный инструмент, который умеет выбирать какой-либо интервал дат, а также переключаться вперёд/назад по этому интервалу. Первоначально этот контрол был придуман для Inventory App (самое время оценить приложение, если вы ещё не сделали этого). Получившийся инструмент можно использовать для фильтрации больших объёмов данных по определённому интервалу дат.

А вы сталкивались с такой задачей? Как решали? Чем пользовались? Поделитесь с нами вашими мыслями в комментариях ниже.