Сохранение состояние окна в URL

Общие принципы

  1. Форма не должна содержать возможность прямого редактирования

  2. Редактирование данных должно осуществляться только через диалоговое (модальное) окно

  3. Изменения в форме (содержание полей ввода, кнопки и т.п.) могут только менять параметры отображения формы (фильтрация данных, отображение диалогов, скрытие/раскрытие кусков формы). Применение любых параметров возможно только через посредничество строки URL: пользователь меняет параметр → изменение отражается в URL → изменение применяется посредством считывания URL

Типовые исключения

  1. Объем сохраняемых параметров потенциально очень большой. Например, перечень строк выделенных чек-боксами в табличном гриде.

Общие функции, обеспечивающие сохранение состояния в URL

mapParamFromUrl(context: { [k: string]: any }, route: { [k: string]: any }, variableName: any, alias: string) {
    let paramName = variableName.name.split(/\W+|(?=[A-Z])|_/g).join("-").toLowerCase();
    if (alias) {
        paramName = alias + '-' + paramName;
    }
    switch (variableName.type) {
        case 's':
            context[variableName.name] = route[paramName];
            break;
        case 'n':
            context[variableName.name] = Number(route[paramName]);
            break;
        case 'b':
            context[variableName.name] = String(route[paramName]) === 'true';
            break;
        case 'a':
            context[variableName.name] = route[paramName];
            break;
        default:
            break;
    }
}

mapParamsFromUrl(context: { [k: string]: any }, route: { [k: string]: any }, variableNames: any, alias: string) {
    for (let variableName of variableNames) {
        if (alias) {
            this.mapParamFromUrl(context, route, variableName, alias);
        } else {
            this.mapParamFromUrl(context, route, variableName, alias);
        }
    }
}

trySetTimeout(f: any) {
    let last_timeout
    clearTimeout(last_timeout);
    last_timeout = setTimeout(() => {
        f()
    }, TIMER_INTERVAL);
}

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

Отслеживание изменений различных параметров
watch: {
  // Параметры пагинации и сортировки
  options: {
    handler() {
      this.setUrlByModelFilter();
      this.getDataFromApi(this.mapModelToUrl());
    },
    deep: true,
  },
  // Параметры фильтрации
  dates: {
    handler() {
      this.setUrlByModelFilter();
    },
    deep: true,
  },
  // Изменение маршрутизации страницы
  '$route': function (route) {
    this.onRouter(route);
  }
},
Изменение URL
setUrlByModelFilter() {
  this.$router.push({
    path: this.$route.path,
    query: {
      ...this.mapModelToUrl()
    },
  }, () => {
  });
},
setUrlByModelFilterWithTimeout() {
  this.$utils.trySetTimeout(this.setUrlByModelFilter);
},
Формирование параметров URL
mapModelToUrl() {
  let parameters: { [k: string]: any } = {};

  parameters['items-per-page'] = this.options.itemsPerPage ? this.options.itemsPerPage : 50;
  parameters['page'] = this.options.page ? this.options.page : 1;
  parameters['sort-by'] = this.options.sortBy[0] ? this.options.sortBy[0] : 'date_start';
  parameters['sort-desc'] = this.options.sortDesc[0] ? this.options.sortDesc[0] : false;

  if (this.dateFormatted && this.dateFormatted2 && this.dates.length === 2) {
    if ((this.dates[0] && this.dates[1]) && this.dates[0] <= this.dates[1]) {
      parameters['exam_date_start'] = this.parseDate(this.dateFormatted);
      parameters['exam_date_end'] = this.parseDate(this.dateFormatted2);
    } else {
      parameters['exam_date_start'] = this.parseDate(this.dateFormatted2);
      parameters['exam_date_end'] = this.parseDate(this.dateFormatted);
    }
  }

  return parameters;
},

mapUrlToModel(parameters: { [x: string]: any; }) {
  // todo: Функции установки сортировки о постраничного просмотра должны быть общими
  if (parameters['items-per-page'] && parameters['page']) {
    this.options.itemsPerPage = Number(parameters['items-per-page']);
    this.options.page = Number(parameters['page']);
  } else {
    this.options.itemsPerPage = 50;
    this.options.page = 1;
  }

  if (parameters['sort-by']) {
    this.options.sortBy = [parameters['sort-by']];
    this.options.sortDesc = [String((parameters['sort-desc'])) === 'true'];
  } else {
    this.options.sortBy = ['date_start'];
    this.options.sortDesc = [false];
  }

  // todo: Добавить значения по умолчанию в функцию мэппинга
  if (parameters['exam_date_start'] && parameters['exam_date_end']) {
    this.$utils.mapParamsFromUrl(this, parameters,
        [
          {name: 'exam_date_start', type: 'd'},
          {name: 'exam_date_end', type: 'd'},
        ], '');
    this.dates = [parameters['exam_date_start'], parameters['exam_date_end']];
    this.onDatePickerValueChanged();
  }
},
Вызов обработки изменения URL при первоначальном создании страницы
created() {
  this.onRouter(this.$route);
},
Обработка изменения URL
onRouter(route: { query: any; }) {
  this.mapUrlToModel(route.query);
  this.getDataFromApi(this.mapModelToUrl());
},