Есть ли лучший способ сделать это, чем использовать IIFE? - PullRequest
0 голосов
/ 08 мая 2020

Я пытаюсь создать шаблон для форм, который проверяет наличие настраиваемого параметра data-regex в элементе HTML, а затем создает из него регулярное выражение, которое будет использоваться для проверки ввода. Вот что у меня есть на данный момент.

var textInputs = document.getElementsByClassName("form__text");
for (var i = 0; i < textInputs.length; ++i) {
  if (textInputs[i].getElementsByTagName("input")[0].type == "email") {
    (function() {
      var pattern = /.*[a-zA-Z0-9]+.*@.*\.[a-zA-Z]+/;
      textInputs[i]
        .getElementsByTagName("input")[0]
        .addEventListener("input", function(e) {
          checkText(pattern, e);
        });
    })();
  } else if (textInputs[i].getElementsByTagName("input")[0].type == "text") {
    (function() {
      var patternStr = textInputs[i].getAttribute("data-regex");
      var pattern = patternStr ? new RegExp(patternStr) : null;
      textInputs[i]
        .getElementsByTagName("input")[0]
        .addEventListener("input", function(e) {
          checkText(pattern, e);
        });
    })();  
  }
}

function checkText(pattern, e) {
  if (pattern && e.target.value.search(pattern) == -1) {
    e.target.parentElement.classList.add("form__text--error");
  } else {
    e.target.parentElement.classList.remove("form__text--error");
  }
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.form {
  margin: 10px;
}
.form .form__text {
  position: relative;
  margin: 2rem 0 4rem 0;
  display: block;
}
.form .form__text__label {
  position: absolute;
  font-size: 1.4rem;
  padding: 10px;
  opacity: 0.5;
  top: 50%;
  left: 0;
  pointer-events: none;
  transition: all 0.2s ease-out;
  transform: translateY(-50%);
}
.form .form__text__error-label {
  color: red;
  opacity: 0;
  transition: all 0.2s ease-out;
  position: absolute;
  top: 110%;
  left: 0;
}
.form .form__text input[type=text], .form .form__text input[type=email] {
  padding: 10px;
  width: 100%;
  border: 1px solid #ccc;
  border-radius: 5px;
  transition: all 0.2s ease-out;
}
.form .form__text input[type=text]:focus ~ .form__text__label, .form .form__text input[type=text]:not(:placeholder-shown) ~ .form__text__label, .form .form__text input[type=email]:focus ~ .form__text__label, .form .form__text input[type=email]:not(:placeholder-shown) ~ .form__text__label {
  transform: translateX(-15px) translateY(-125%) scale(0.75);
  opacity: 1;
}
.form .form__text input[type=text]:focus, .form .form__text input[type=email]:focus {
  outline: none;
  background: rgba(122, 217, 255, 0.075);
}
.form .form__text--error .form__text__label {
  color: red;
}
.form .form__text--error .form__text__error-label {
  opacity: 1;
}
.form .form__text--error input[type=text], .form .form__text--error input[type=email] {
  border: 1px solid red;
}
.form .form__text--error input[type=text]:focus, .form .form__text--error input[type=email]:focus {
  background: rgba(255, 0, 0, 0.05);
}
<form class="form">
  <label class="form__text">
    <input type="email" id="email" name="email" placeholder=" " />
    <span class="form__text__label">Email</span>
    <span class="form__text__error-label">Invalid Email</label>
  </label>
  <label class="form__text" data-regex="[a-zA-z ]{4,}">
    <input type="text" id="name" name="name" placeholder=" " />
    <span class="form__text__label">Name</span>
    <span class="form__text__error-label">Invalid Name</span>
  </label>
  <label class="form__text">
    <input type="text" id="random" name="random" placeholder=" " />
    <span class="form__text__label">Random Fact</span>
  </label>
</form>

Мне пришлось обернуть два блока addEventListener внутри IIFE, потому что в противном случае переменная шаблона была бы перезаписана всякий раз, когда запускался обратный вызов. Мне любопытно, есть ли более чистый способ сделать это. Я предполагаю, что создание объектов регулярных выражений требует определенных затрат, поэтому я пытался создать их один раз за пределами обратных вызовов. Заранее спасибо.

1 Ответ

1 голос
/ 08 мая 2020

Проблема в том, что pattern будет общим именем переменной для всей функции. Затем, когда срабатывает прослушиватель событий, он выбирает последнюю версию pattern, а не текущую. Это классная c проблема .

Решение ES6

С введением let и const у вас могут быть переменные в области видимости блока, поэтому решение очень простое - замените любое var на let или const.

const textInputs = document.getElementsByClassName("form__text");
for (let i = 0; i < textInputs.length; ++i) {
  if (textInputs[i].getElementsByTagName("input")[0].type == "email") {
    const pattern = /.*[a-zA-Z0-9]+.*@.*\.[a-zA-Z]+/;
    textInputs[i]
      .getElementsByTagName("input")[0]
      .addEventListener("input", function(e) {
        checkText(pattern, e);
      });
  } else if (textInputs[i].getElementsByTagName("input")[0].type == "text") {
    const patternStr = textInputs[i].getAttribute("data-regex");
    const pattern = patternStr ? new RegExp(patternStr) : null;
    textInputs[i]
      .getElementsByTagName("input")[0]
      .addEventListener("input", function(e) {
        checkText(pattern, e);
      });
  }
}

function checkText(pattern, e) {
  if (pattern && e.target.value.search(pattern) == -1) {
    e.target.parentElement.classList.add("form__text--error");
  } else {
    e.target.parentElement.classList.remove("form__text--error");
  }
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.form {
  margin: 10px;
}
.form .form__text {
  position: relative;
  margin: 2rem 0 4rem 0;
  display: block;
}
.form .form__text__label {
  position: absolute;
  font-size: 1.4rem;
  padding: 10px;
  opacity: 0.5;
  top: 50%;
  left: 0;
  pointer-events: none;
  transition: all 0.2s ease-out;
  transform: translateY(-50%);
}
.form .form__text__error-label {
  color: red;
  opacity: 0;
  transition: all 0.2s ease-out;
  position: absolute;
  top: 110%;
  left: 0;
}
.form .form__text input[type=text], .form .form__text input[type=email] {
  padding: 10px;
  width: 100%;
  border: 1px solid #ccc;
  border-radius: 5px;
  transition: all 0.2s ease-out;
}
.form .form__text input[type=text]:focus ~ .form__text__label, .form .form__text input[type=text]:not(:placeholder-shown) ~ .form__text__label, .form .form__text input[type=email]:focus ~ .form__text__label, .form .form__text input[type=email]:not(:placeholder-shown) ~ .form__text__label {
  transform: translateX(-15px) translateY(-125%) scale(0.75);
  opacity: 1;
}
.form .form__text input[type=text]:focus, .form .form__text input[type=email]:focus {
  outline: none;
  background: rgba(122, 217, 255, 0.075);
}
.form .form__text--error .form__text__label {
  color: red;
}
.form .form__text--error .form__text__error-label {
  opacity: 1;
}
.form .form__text--error input[type=text], .form .form__text--error input[type=email] {
  border: 1px solid red;
}
.form .form__text--error input[type=text]:focus, .form .form__text--error input[type=email]:focus {
  background: rgba(255, 0, 0, 0.05);
}
<form class="form">
  <label class="form__text">
    <input type="email" id="email" name="email" placeholder=" " />
    <span class="form__text__label">Email</span>
    <span class="form__text__error-label">Invalid Email</label>
  </label>
  <label class="form__text" data-regex="[a-zA-z ]{4,}">
    <input type="text" id="name" name="name" placeholder=" " />
    <span class="form__text__label">Name</span>
    <span class="form__text__error-label">Invalid Name</span>
  </label>
  <label class="form__text">
    <input type="text" id="random" name="random" placeholder=" " />
    <span class="form__text__label">Random Fact</span>
  </label>
</form>

Вы можете использовать Babel для переноса кода с ES6 + на ES5, так что вы должны написать выше, но он будет автоматически переведен на более старый код который работает так же, как и новый.

ПРИМЕЧАНИЕ: let и const будут приняты IE11 , однако они будут вести себя точно так же, как var. IE11 делает их разрешенным только синтаксисом, но как псевдонимы var. Они не будут ограничены блоком.

Решение ES5

Если вам нужно использовать ES5, то это не такая уж большая проблема. Беда в том, что не хватает блоковых областей, есть только глобальные и функциональные. Итак, вам нужно будет захватить переменную внутри функциональной области. IIFE делает это, но выглядит немного некрасиво.

Curry checkText()

Вместо этого мы можем сделать checkText функцию более высокого порядка, которая curried - вместо принимая два параметра, сначала он принимает один параметр, а затем возвращает функцию, которая принимает второй параметр. Это выглядит так:

function checkText(pattern){
  return function(e) {
    if (pattern && e.target.value.search(pattern) == -1) {
      e.target.parentElement.classList.add("form__text--error");
    } else {
      e.target.parentElement.classList.remove("form__text--error");
    }
  }
}

Это мощная конструкция, поскольку она позволяет нам немедленно захватить переменную, если мы вызываем var returnedFunction = checkText(pattern) - теперь, даже если шаблон переменной изменяется во включающем контексте, returnedFunction по-прежнему будет удерживать предыдущий.

Заменить старый вызов

В качестве бонуса это позволяет нам избавиться от ненужного кода. Давайте пройдемся шаг за шагом для полного понимания - сначала это

.addEventListener("input", function(e) {
  checkText(pattern, e);
});

должно превратиться в

.addEventListener("input", function(e) {
  var returnedFunction = checkText(pattern);
  returnedFunction(e);
});

, потому что теперь мы должны передать параметры в две разные функции. На этом этапе все еще остается та же проблема, что и раньше . Сейчас это ничего не решает, но я хочу показать промежуточный шаг. Та же проблема - при срабатывании слушателя будет выполнено checkText(pattern), после чего pattern уже изменен.

Убедитесь, что checkText(pattern) захватывает правильное значение

Нам нужно убедиться, что он срабатывает до инициализации в прослушивателе событий:

var pattern = patternStr ? new RegExp(patternStr) : null;
var returnedFunction = checkText(pattern);
/* ...code... */
.addEventListener("input", function(e) {
  returnedFunction(e);
});

Когда мы помещаем его на тот же уровень, что и переменная pattern, так что сразу снаружи обратный вызов .addEventListener, он работает по назначению без использования IIFE. Мы захватили бы текущую переменную шаблона, и изменения в ней не повлияют на returnedFunction.

Упрощение абстракции

Однако теперь функция обратного вызова имеет вид function(e) { returnedFunction(e); } - функция, которая принимает параметр и вызывает функцию, передающую тот же параметр в качестве аргумента. Набрать <anonymous function>(e) - это , то же самое , что и returneFunction(e). Внешняя функция-оболочка и внутренняя returneFunction имеют одинаковую сигнатуру, и семантика их вызова с одним аргументом практически одинакова. Таким образом, обертка сейчас бесполезна. Мы можем удалить его и выполнить то, что лямбда-исчисление называет сокращением Eta, чтобы упростить абстракцию, таким образом, весь обратный вызов становится

var pattern = patternStr ? new RegExp(patternStr) : null;
var returnedFunction = checkText(pattern);
/* ...code... */
.addEventListener("input", returnedFunction);

Теперь прослушиватель событий равен returnedFunction. Когда он запускается, ему будет передан тот же аргумент, что и раньше.

Удалить дополнительную переменную

Наконец, последний шаг в упрощении - просто встроить вызов checkText(pattern). Здесь нам действительно не нужна дополнительная переменная. Легко просто избавиться от него и получить

.addEventListener("input", checkText(pattern));

Готово. Это было подробно описано, потому что я хотел показать процесс. На практике он просто преобразует функцию checkText() в каррированный вариант, а затем заменяет им обратный вызов. Надеюсь, эти шаги превратят его из каких-то странных инструкций, которые работают, в понимание, почему это делается.

Окончательный результат таков:

var textInputs = document.getElementsByClassName("form__text");
for (var i = 0; i < textInputs.length; ++i) {
  if (textInputs[i].getElementsByTagName("input")[0].type == "email") {
    var pattern = /.*[a-zA-Z0-9]+.*@.*\.[a-zA-Z]+/;
    textInputs[i]
      .getElementsByTagName("input")[0]
      .addEventListener("input", checkText(pattern));
  } else if (textInputs[i].getElementsByTagName("input")[0].type == "text") {
    var patternStr = textInputs[i].getAttribute("data-regex");
    var pattern = patternStr ? new RegExp(patternStr) : null;
    textInputs[i]
      .getElementsByTagName("input")[0]
      .addEventListener("input", checkText(pattern));
  }
}

function checkText(pattern){
  return function(e) {
    if (pattern && e.target.value.search(pattern) == -1) {
      e.target.parentElement.classList.add("form__text--error");
    } else {
      e.target.parentElement.classList.remove("form__text--error");
    }
  }
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.form {
  margin: 10px;
}
.form .form__text {
  position: relative;
  margin: 2rem 0 4rem 0;
  display: block;
}
.form .form__text__label {
  position: absolute;
  font-size: 1.4rem;
  padding: 10px;
  opacity: 0.5;
  top: 50%;
  left: 0;
  pointer-events: none;
  transition: all 0.2s ease-out;
  transform: translateY(-50%);
}
.form .form__text__error-label {
  color: red;
  opacity: 0;
  transition: all 0.2s ease-out;
  position: absolute;
  top: 110%;
  left: 0;
}
.form .form__text input[type=text], .form .form__text input[type=email] {
  padding: 10px;
  width: 100%;
  border: 1px solid #ccc;
  border-radius: 5px;
  transition: all 0.2s ease-out;
}
.form .form__text input[type=text]:focus ~ .form__text__label, .form .form__text input[type=text]:not(:placeholder-shown) ~ .form__text__label, .form .form__text input[type=email]:focus ~ .form__text__label, .form .form__text input[type=email]:not(:placeholder-shown) ~ .form__text__label {
  transform: translateX(-15px) translateY(-125%) scale(0.75);
  opacity: 1;
}
.form .form__text input[type=text]:focus, .form .form__text input[type=email]:focus {
  outline: none;
  background: rgba(122, 217, 255, 0.075);
}
.form .form__text--error .form__text__label {
  color: red;
}
.form .form__text--error .form__text__error-label {
  opacity: 1;
}
.form .form__text--error input[type=text], .form .form__text--error input[type=email] {
  border: 1px solid red;
}
.form .form__text--error input[type=text]:focus, .form .form__text--error input[type=email]:focus {
  background: rgba(255, 0, 0, 0.05);
}
<form class="form">
  <label class="form__text">
    <input type="email" id="email" name="email" placeholder=" " />
    <span class="form__text__label">Email</span>
    <span class="form__text__error-label">Invalid Email</label>
  </label>
  <label class="form__text" data-regex="[a-zA-z ]{4,}">
    <input type="text" id="name" name="name" placeholder=" " />
    <span class="form__text__label">Name</span>
    <span class="form__text__error-label">Invalid Name</span>
  </label>
  <label class="form__text">
    <input type="text" id="random" name="random" placeholder=" " />
    <span class="form__text__label">Random Fact</span>
  </label>
</form>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...