Проблема в том, что 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>