当值被重新输入但未更改时,如何强制JS输入的“更改”事件?

问题描述 投票:0回答:2

我知道有很多类似的问题,但我还没有找到确切的情况。

使用 vanilla JS 有一个

input
控件,用户可以在其中键入内容。以日期为例(但可以是电话号码或任何其他具有固定格式的内容)。使用元素的
change
事件验证输入数据。因此,当用户完成(通过按
enter
或离开控件或提交等)时,会进行验证,如果出现问题,则会显示错误消息。

对于良好的用户体验,一旦用户开始再次输入(即尝试编辑错误),验证错误就会被清除。这是必要的,这样用户在输入数据时就不会因为尚未完成而混淆数据“无效”。当他完成输入时,数据会再次重新验证。我们没有进行实时验证,因为它看起来很混乱(“我输入的数据已经无效了吗?”)。

例如,输入未完成的日期,例如

12.12
(没有年份)将导致验证错误。当用户再次开始输入时,错误将被清除,直到用户完成为止。

现在考虑一个案例:

  1. 用户类型
    12.12
    ;
  2. enter
  3. 验证开始并导致错误;
  4. 用户清除输入并再次输入
    12.12
  5. enter
  6. 不会发生验证,因为
    input
    元素的值没有变化,因此没有
    change
    事件。

所以问题是,如何使

input
元素相信数据实际上已更改,以便在用户完成编辑时再次触发该事件?

不确定模拟

change
事件是否是一个好主意(例如,通过在
blur
keypress=enter
或类似的内容中手动调度它)。

我正在寻找类似

input
的“优化”标志之类的东西,当禁用时,将强制它调度
change
事件,而不管实际更改的值如何。或者类似
invalidateElementValue
的东西可以在元素的
input
事件内部调用。

javascript html forms validation html-input
2个回答
0
投票

使用多个事件进行主动验证

问题询问如何在更改后或用户按 ENTER 时验证输入,但这也会在编辑时抑制烦人的消息。

用户有时可能会发现这种即时反馈很有帮助。例如,查看选定日期有哪些座位可用或快速了解用户名是否可用。一般来说,用户在继续之前可能想知道的任何信息。

我们可以通过触发对 changekeydown 事件的验证来完成此工作。并且 input 事件可用于在编辑期间禁用验证。

虽然可以添加额外的调整,但效果很好。例如,只允许按下 Enter 时提交按钮提交表单。

所需的最低代码

  form.addEventListener('change', (e) => {
    validation(e);
  });

  form.addEventListener('keydown', (e) => {
    if (e.code === 'Enter') validation(e);
  })

  form.addEventListener('input', (e) => {
    e.target.setCustomValidity('');
  });

  function validation(e) {
     // if e.target.value invalid
     e.target.setCustomValidity('some message');
     e.target.reportValidity();
  }

当用户按 Enter 或 Tab 键进入下一个输入时,验证将显示浏览器生成的弹出窗口。

演示片段

document.querySelectorAll('form').forEach(form => {

  // Valdate input on change
  form.addEventListener('change', (e) => {
    if (e.target.classList.contains('date')) {
      dateValidation(e);
    }
  });

  // Validate input on Enter key
  form.addEventListener('keydown', (e) => {
    if (e.code === 'Enter') {
      if (e.target.classList.contains('date')) {
        dateValidation(e);
      }
      // Prevents non-submit buttons from triggering form submit
      if (e.target.type !== 'submit') {
        e.preventDefault();
      }
    }
  })

  // Clear to suppress messages while editing
  form.addEventListener('input', (e) => {
    e.target.setCustomValidity('');
  });

  // Simulate form submit
  form.addEventListener('submit', (e) => {
    e.preventDefault();
    console.log('form submitted');
  });


});


function dateValidation(e) {

  let date = new Date(e.target.value),
  min = new Date(e.target.getAttribute('min')), 
  max = new Date(e.target.getAttribute('max')), 
  msg = '';

  if (isNaN(date)) {
    msg = 'The date is invalid';
  } else if (!isNaN(min) && min > date) {
    msg = `Date minimum is ${min.toDateString()}`;
  } else if (!isNaN(max) && max < date) {
    msg = `Date maximum is ${max.toDateString()}`;
  }

  if (msg) {
    e.target.setCustomValidity(msg);
    e.target.reportValidity();
  }

}
label {
  font-family: sans-serif;
  display: block;
  margin-bottom: 0.5rem;
}
<form>
  <label>
    <input type="text" 
      name="arrive" 
      class="date" 
      min="2023-11-01T00:00:00" 
      max="2023-12-31T00:00:00" 
      required>
    Arrival Date
  </label>
  <label>
    <input type="date" 
      name="depart" 
      class="date" 
      max="2023-12-31T00:00:00" 
      required>
    Departure Date
  </label>
  <label>
    <input type="text" name="comment">
    Comments
  </label>
  <input type="submit">
</form>


0
投票

来自一些OP和我的上述评论......

良好的用户体验要么对(表单)数据提交进行验证,要么默默地对每个发生的

input
事件进行不令人厌烦的后台验证。关于验证,
change
事件几乎总是没有什么用处。 – 彼得·塞利格

@PeterSeliger 在输入过程中进行后台验证并不是一个好的用户体验,因为它会让用户感到困惑,因为他现在输入的数据已经无效。在这种情况下,您建议举办什么活动? – 卡斯博拉特·库马霍夫

正如我提议/所述,验证发生在(表单)数据

submit
或每个
input
事件,甚至两种事件类型。良好的用户体验取决于干扰用户期望的种类和方式。因此,为了在需要时以最有支持性且最不烦人的方式提供正确的信息,人们必须想出一些复杂的事件和数据处理。但这不会改变建议的事件类型。

function handleInvalidatedRepetition(validationOutput) {
  validationOutput.classList.add('warning');
  validationOutput.value = 'This value already has been invalidated before.'
}
function handleFailedValidation(validationRoot, control/*, validationOutput*/) {
  validationRoot.classList.add('validation-failed');
  // validationOutput.value = 'This is an invalid value.';
  control.blur();
}

function clearInvalidatedRepetition(control, validationOutput) {
  const invalidationsLookup = controlRegistry.get(control);
  if (
    invalidationsLookup && !invalidationsLookup.has(control.value) &&
    validationOutput.classList.contains('warning')
  ) {
    validationOutput.classList.remove('warning');

    validationOutput.value = '';
  }
}
function clearValidationStates({ currentTarget: control }) {
  const validationRoot = control.closest('label[data-validation]');
  const validationOutput = validationRoot.querySelector('output');

  const invalidationsLookup = controlRegistry.get(control);

  if (validationRoot.classList.contains('validation-failed')) {
    validationRoot.classList.remove('validation-failed');

    control.value = '';
  }
  clearInvalidatedRepetition(control, validationOutput);
}

function assureNoDotChainedNumbers(evtOrControl) {
  let result;

  const isEvent = ('currentTarget' in evtOrControl);
  const control = isEvent && evtOrControl.currentTarget || evtOrControl;

  const invalidationsLookup = controlRegistry.get(control);
  if (invalidationsLookup) {

    const { value } = control;
    const isValid = !(/\d*(?:\.\d+)+/g).test(value);

    const validationRoot = control.closest('label[data-validation]');
    const validationOutput = validationRoot.querySelector('output');

    clearInvalidatedRepetition(control, validationOutput);

    if (!isEvent) {

      if (!isValid) {
        invalidationsLookup.add(value);

        handleFailedValidation(validationRoot, control, validationOutput);
      }
      result = isValid;

    } else if (!isValid && invalidationsLookup.has(value)) {

      handleInvalidatedRepetition(validationOutput);
    }
  }
  return result;
}

function validateFormData(elmForm) {
  return [...elmForm.elements]
    .filter(control =>
      !(/^(?:fieldset|output)$/).test(control.tagName.toLowerCase())
    )
    .every(control => {
      const validationType =
        control.closest('label[data-validation]')?.dataset.validation ?? '';

      if (!controlRegistry.has(control)) {
        controlRegistry.set(control, new Set);
      }
      return validationLookup[validationType]?.(control) ?? true;
    });
}
function handleFormSubmit(evt) {
  const success = validateFormData(evt.currentTarget);

  if (!success) {
    evt.preventDefault();
  }
  return success;
}


const validationLookup = {
  'no-dot-chained-numbers': assureNoDotChainedNumbers,
};
const eventTypeLookup = {
  'input-text': 'input',
}
const controlRegistry = new WeakMap;


function main() {
  const elmForm = document.querySelector('form');

  [...elmForm.elements]
    .filter(control =>
      !(/^(?:fieldset|output)$/.test(control.tagName.toLowerCase()))
    )
    .forEach(control => {
      const controlName = control.tagName.toLowerCase();
      const controlType = control.type && `-${ control.type }` || '';

      const eventType =
        eventTypeLookup[`${ controlName }${ controlType }`] ?? '';

      const validationType =
        control.closest('label[data-validation]')?.dataset.validation ?? '';

      if (eventType && validationType) {
        // console.log({ eventType, validationType });

        control.addEventListener(
          eventType, validationLookup[validationType]
        );
      }
      control.addEventListener('focus', clearValidationStates);
    });

  elmForm.addEventListener('submit', handleFormSubmit);
}
main();
fieldset { padding: 16px; }
label { padding: 8px 12px 10px 12px; }
code { background-color: #eee; }
.validation-failed {
  outline: 1px dashed red;
  background-color: rgb(255 0 0 / 25%);
}
.warning {
  color: #ff9000;
}
<form>
  <fieldset>
    <legend>No dot chained numbers</legend>

    <label data-validation="no-dot-chained-numbers">
      <span class="label">No dot chained numbers</span>
      <input type="text" palceholder="No dot chained numbers" />
      <output></output>
    </label>

  </fieldset>
</form>

<ul>
  <li>E.g. do type <code>12.45</code>.</li>
  <li>Press <code>&lt;Enter&gt;</code>.</li>
  <li>Focus the <code>input</code> element again.</li>
  <li>Type e.g. another dot chained number sequence.</li>
  <li>
    Maybe repeat the above task sequence by pressing <code>&lt;Enter&gt;</code> again.
  </li>
  <li>Do type input <code>12.45</code> again.</li>
  <li>... Try other stuff; play around ...</li>
</ul>

© www.soinside.com 2019 - 2024. All rights reserved.