Числовое поле с форматированием

9 августа 2021 в 22:53
Аватар пользователя gease gease 0 0

Задача к Друпалу относится постольку, поскольку всё что мы делаем - это Друпал, так то это просто фронтенд. Итак, задача проще простого: сделать числовое поле с форматированием по тысячам, то есть группы по 3 цифры должны разделяться пробелом, например: 45 312 862. На удивление, на просторах интернета решений этой задачи не то что бы обнаружилось в избытке. Даже уважаемые калькуляторы типа xe.com форматируют ввод только при потере фокуса полем. В общем, пришлось писать самому.

В поисках совершенства требования были поставлены так:

  • форматироваться должно на лету, то есть прямо в поле ввода по ходу ввода
  • всё должно работать максимально естественно (удаление, редактирование, вставка)
  • в том числе и на мобильных устройствах
  • на сервер должно улетать число без всяких пробелов

На фронтенде получилось следующее: https://codepen.io/gease-the-vuer/pen/rNmoMBp

Сначала я пытался прикрутить input type="number", но у него оказалось огромное количество ограничений, в том числе невозможность определить позицию каретки (selectionEnd). Оказалось правильней использовать стандартный input type="text", заблокировать нажатия всех клавиш, кроме цифр, стрелок вперёд-назад и удаления, а также вставки Ctrl+V. После этого при каждом изменении поля (событие input) проводить довольно хитрую манипуляцию с содержимым поля, вставлять его обратно и ставить каретку на место. Плюс заблокировать любую вставку, содержащую запрещённые символы.

Это всё отлично работает на традиционной клавиатуре. Но на мобильных устройствах, во-первых, нет события keyDown, во-вторых, с точки зрения пользователя, будет выглядеть крайне противоестественно, если он пытается нажать кнопку, а она не работает. Там логика другая: нам нужно вызвать виртуальную клавиатуру, на которой нет букв, только цифры. И это обеспечивает атрибут inputmode="numeric". Про атрибут inputmode хорошо написано здесь. Да, у пользователя остаётся возможность ввести разную чушь, но это уже возложим на серверную валидацию.

Итого, в дополнение к упомянутому codepen'у с чисто Друпальной стороны нам остаётся сделать две вещи: убрать пробелы при отправке формы (а она в нашем конкретном случае отправляется AJAX'ом) и валидировать на сервере.

Разработчки ядра предусмотрели возможность менять данные формы при отправке AJAX'ом (core/misc/ajax.es6.js):

 
/**
   * Modify form values prior to form submission.
   *
   * @param {Array.<object>} formValues
   *   Processed form values.
   * @param {jQuery} element
   *   The form node as a jQuery object.
   * @param {object} options
   *   jQuery.ajax options.
   */

  Drupal.Ajax.prototype.beforeSubmit = function(formValues, element, options) {
    // This function is left empty to make it simple to override for modules
    // that wish to add functionality here.
  };

так что нам остаётся только воспользоваться предоставленной возможностью:

   
Drupal.Ajax.prototype.beforeSubmit = function (formValues, element, options) {    
        for (var i in formValues) {
            if (formValues[i].name === our_field_name) {
                formValues[i].value = formValues[i].value.replace(/\s/g, "");
            }
        }
    };

Что касается серверной валидации, всё ещё проще: добавляем к нужному элементу формы паттерн
<?php$form[$element]['#pattern'] = '\d+';?>