如何关注必填或无效字段?

How to focus on required or invalid field?

我的表格很长,在某些情况下用户可能会漏掉一些必填字段。如果他们一直滚动到 Save 按钮并单击以发送表单数据,他们将不会看到错误消息。我想知道是否有一种方法可以触发 on focus 方法,将用户带到表单中的第一个字段,即 requiredinvalid。这是我的表格示例:

var COMMON_FUNC = {};
$("#save").on("click", function() {
  var frmObject = $(this).closest("form"),
    frmDisabledFlds = frmObject.find(":input:disabled").prop("disabled", false),
    frmData = frmObject.serialize();
  frmDisabledFlds.prop("disabled", true);

  if (COMMON_FUNC.verifyFields("new-record")) {
    console.log('Send Form Data!');
  }
});

COMMON_FUNC.verifyFields = function(containerID, includeInvisible) {
  includeInvisible = includeInvisible || false;
  let isValid = true;
  const hdlMap = {
    'valueMissing': "This field is required",
    'patternMismatch': "This field is invalid",
    'tooLong': "This field is too long",
    'rangeOverflow': "This field is greater than allowed maximum",
    'rangeUnderflow': "This field is less than allowed minimum",
    'typeMismatch': "This field is mistyped"
  };

  const arrV = Object.keys(hdlMap);

  $("#" + containerID).find("input,textarea,select").each(function() {
    var curItem$ = $(this);
    var errMsg = [];
    var dispfld = curItem$.data("dispfld");

    if (includeInvisible || curItem$.is(":visible")) {
      if (curItem$[0].validity.valid) {
        curItem$.removeClass("is-invalid");
        return;
      }

      arrV.forEach(function(prop) {
        if (curItem$[0].validity[prop]) {
          if (prop === "patternMismatch" && dispfld) {
            errMsg.push(dispfld);
          } else {
            errMsg.push(hdlMap[prop]);
          }
        }
      });

      if (errMsg.length) {
        if (!curItem$.next().is(".invalid-feedback")) {
          curItem$.after('<div class="invalid-feedback"></div>');
        }
        curItem$.addClass("is-invalid").next().text(errMsg.join(' and '));
        isValid = false;
      } else {
        curItem$.removeClass("is-invalid");
      }
    }
  });

  return isValid;
};
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<div class="container">
  <div class="card">
    <div class="card-header">
      <h4>New Record</h4>
    </div>
    <div class="card-body">
      <form name="new-record" id="new-record" autocomplete="off">
        <div class="form-group">
          <label for="bldg_name">Building Name:</label>
          <input class="form-control" type="text" name="bldg_name" id="bldg_name" value="" maxlength="500" placeholder="Enter the building name" required>
        </div>

        <div class="row">
          <div class="col-12"><strong><u>Manager</u></strong></div>
        </div>

        <div class="form-row">
          <div class="form-group col-6">
            <label for="salutation">Salutation:</label>
            <select class="custom-select browser-default" name="salutation" id="salutation">
              <option value="">--Select Salutation--</option>
              <option value="1">Mrs</option>
              <option value="2">Ms</option>
              <option value="3">Miss</option>
              <option value="4">Mr</option>
            </select>
          </div>
          <div class="form-group col-6">
            <label for="title">Business Title:</label>
            <select class="custom-select browser-default" name="title" id="title">
              <option value="">--Select Title--</option>
              <option value="1">Region Manager</option>
              <option value="2">State Manager</option>
              <option value="3">Building Manager</option>
            </select>
          </div>
        </div>
        <div class="form-row">
          <div class="form-group col-6 required">
            <label for="fname">First Name:</label>
            <input class="form-control" type="text" name="fname" id="fname" value="" maxlength="20" placeholder="Enter First name" required>
          </div>

          <div class="form-group col-6 required">
            <label for="lname">Last Name:</label>
            <input class="form-control" type="text" name="lname" id="lname" value="" maxlength="30" placeholder="Enter Last name" required>
          </div>
        </div>
        <div class="form-group required">
          <label for="email">Email:</label>
          <input class="form-control email" type="email" name="email" id="email" maxlength="50" placeholder="Enter Email address" required>
        </div>
        <div class="form-group row">
          <div class="col-5 offset-2"><strong><u>Physical Address</u></strong></div>
          <div class="col-5"><strong><u>Mailing Address</u></strong></div>
        </div>
        <div class="form-group row required">
          <label for="address1" class="col-2 col-form-label">Address 1:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_address1" id="p_address1" value="" placeholder="Enter Physical Address 1" maxlength="40" required>
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_address1" id="m_address1" value="" placeholder="Enter Mailing Address 1" maxlength="40" required>
          </div>
        </div>
        <div class="form-group row">
          <label for="address2" class="col-2 col-form-label">Address 2:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_address2" id="p_address2" value="" placeholder="Enter Physical Address 2" maxlength="40">
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_address2" id="m_address2" value="" placeholder="Enter Mailing Address 2" maxlength="40">
          </div>
        </div>
        <div class="form-group row">
          <label for="address3" class="col-2 col-form-label">Address 3:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_address3" id="p_address3" value="" placeholder="Enter Physical Address 3" maxlength="40">
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_address3" id="m_address3" value="" placeholder="Enter Mailing Address 3" maxlength="40">
          </div>
        </div>
        <div class="form-group row">
          <label for="address4" class="col-2 col-form-label">Address 4:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_address4" id="p_address4" value="" placeholder="Enter Physical Address 4" maxlength="40">
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_address4" id="m_address4" value="" placeholder="Enter Mailing Address 4" maxlength="40">
          </div>
        </div>
        <div class="form-group row required">
          <label for="city" class="col-2 col-form-label">City:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_city" id="p_city" value="" placeholder="Enter City" maxlength="25" required>
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_city" id="m_city" value="" placeholder="Enter City" maxlength="25" required>
          </div>
        </div>
        <div class="form-group row required">
          <label for="state" class="col-2 col-form-label">State:</label>
          <div class="col-5">
            <select class="custom-select browser-default physical" name="p_state" id="p_state" required>
              <option value="">--Select State--</option>
              <option value="az">Arizona</option>
              <option value="ia">Iowa</option>
              <option value="mo">Missouri</option>
              <option value="ny">New York</option>
              <option value="va">Virginia</option>
            </select>
          </div>
          <div class="col-5">
            <select class="custom-select browser-default mailing" name="m_state" id="m_state" required>
              <option value="">--Select State--</option>
              <option value="az">Arizona</option>
              <option value="ia">Iowa</option>
              <option value="mo">Missouri</option>
              <option value="ny">New York</option>
              <option value="va">Virginia</option>
            </select>
          </div>
        </div>
        <div class="form-group row required">
          <label for="zip" class="col-2 col-form-label">Zip:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_zip" id="p_zip" value="" pattern="(\d{5}([\-]\d{4})?)" data-dispfld="The required format is: xxxxx or xxxxx-xxxx" placeholder="Enter Zip Code, formatted: 99999 or 99999-9999" maxlength="10" required>
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_zip" id="m_zip" value="" pattern="(\d{5}([\-]\d{4})?)" data-dispfld="The required format is: xxxxx or xxxxx-xxxx" placeholder="Enter Zip Code, formatted: 99999 or 99999-9999" maxlength="10" required>
          </div>
        </div>

        <div class="row">
          <div class="col-12 text-center">
            <button class="btn btn-outline-secondary" type="button" name="save" id="save">Save</button>
            <button class="btn btn-outline-secondary" type="button" name="cancel" id="cancel">Cancel</button>
          </div>
        </div>
      </form>
    </div>
  </div>
</div>

您可以 运行 此示例并在表单中输入除建筑物名称之外的所有信息。然后您可以单击“保存”按钮,您将无法在顶部看到错误消息。如果有人对如何处理此问题有建议,请告诉我。我愿意听到针对这种情况的不同解决方案。

您可以尝试像这样简单的操作(未经测试但应该有效):

document.getElementsByClassName('is-invalid')[0].scrollIntoView({ behavior: 'smooth' });

这会查找错误 class is-invalid 然后平滑滚动到找到的第一个

验证后添加此代码,如果发现错误。

DOM API 对元素具有 "focus" 功能,可以满足您的需求。

将所有有效元素推送到 invalidInputs 数组,然后您可以选择第一个无效元素并对该元素调用 .focus()。

var COMMON_FUNC = {};
$("#save").on("click", function() {
  var frmObject = $(this).closest("form"),
    frmDisabledFlds = frmObject.find(":input:disabled").prop("disabled", false),
    frmData = frmObject.serialize();
  frmDisabledFlds.prop("disabled", true);

  if (COMMON_FUNC.verifyFields("new-record")) {
    console.log('Send Form Data!');
  }
});

COMMON_FUNC.verifyFields = function(containerID, includeInvisible) {
  includeInvisible = includeInvisible || false;
  let isValid = true;
  const hdlMap = {
    'valueMissing': "This field is required",
    'patternMismatch': "This field is invalid",
    'tooLong': "This field is too long",
    'rangeOverflow': "This field is greater than allowed maximum",
    'rangeUnderflow': "This field is less than allowed minimum",
    'typeMismatch': "This field is mistyped"
  };

  const arrV = Object.keys(hdlMap);

  // Create an array for the invalid fields.
  const invalidInputs = [];

  $("#" + containerID).find("input,textarea,select").each(function() {
    var curItem$ = $(this);
    var errMsg = [];
    var dispfld = curItem$.data("dispfld");

    if (includeInvisible || curItem$.is(":visible")) {
      if (curItem$[0].validity.valid) {
        curItem$.removeClass("is-invalid");
        return;
      }

      arrV.forEach(function(prop) {
        if (curItem$[0].validity[prop]) {
          if (prop === "patternMismatch" && dispfld) {
            errMsg.push(dispfld);
          } else {
            errMsg.push(hdlMap[prop]);
          }
        }
      });

      if (errMsg.length) {
        if (!curItem$.next().is(".invalid-feedback")) {
          curItem$.after('<div class="invalid-feedback"></div>');
        }
        curItem$.addClass("is-invalid").next().text(errMsg.join(' and '));
        isValid = false;
        
        //Push the invalid inputs onto the invalidInputs array.
        invalidInputs.push(curItem$);
      } else {
        curItem$.removeClass("is-invalid");
      }
    }
  });
 
  // Focus on the first element that is invalid.
  invalidInputs[0].focus();

  return isValid;
};
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<div class="container">
  <div class="card">
    <div class="card-header">
      <h4>New Record</h4>
    </div>
    <div class="card-body">
      <form name="new-record" id="new-record" autocomplete="off">
        <div class="form-group">
          <label for="bldg_name">Building Name:</label>
          <input class="form-control" type="text" name="bldg_name" id="bldg_name" value="" maxlength="500" placeholder="Enter the building name" required>
        </div>

        <div class="row">
          <div class="col-12"><strong><u>Manager</u></strong></div>
        </div>

        <div class="form-row">
          <div class="form-group col-6">
            <label for="salutation">Salutation:</label>
            <select class="custom-select browser-default" name="salutation" id="salutation">
              <option value="">--Select Salutation--</option>
              <option value="1">Mrs</option>
              <option value="2">Ms</option>
              <option value="3">Miss</option>
              <option value="4">Mr</option>
            </select>
          </div>
          <div class="form-group col-6">
            <label for="title">Business Title:</label>
            <select class="custom-select browser-default" name="title" id="title">
              <option value="">--Select Title--</option>
              <option value="1">Region Manager</option>
              <option value="2">State Manager</option>
              <option value="3">Building Manager</option>
            </select>
          </div>
        </div>
        <div class="form-row">
          <div class="form-group col-6 required">
            <label for="fname">First Name:</label>
            <input class="form-control" type="text" name="fname" id="fname" value="" maxlength="20" placeholder="Enter First name" required>
          </div>

          <div class="form-group col-6 required">
            <label for="lname">Last Name:</label>
            <input class="form-control" type="text" name="lname" id="lname" value="" maxlength="30" placeholder="Enter Last name" required>
          </div>
        </div>
        <div class="form-group required">
          <label for="email">Email:</label>
          <input class="form-control email" type="email" name="email" id="email" maxlength="50" placeholder="Enter Email address" required>
        </div>
        <div class="form-group row">
          <div class="col-5 offset-2"><strong><u>Physical Address</u></strong></div>
          <div class="col-5"><strong><u>Mailing Address</u></strong></div>
        </div>
        <div class="form-group row required">
          <label for="address1" class="col-2 col-form-label">Address 1:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_address1" id="p_address1" value="" placeholder="Enter Physical Address 1" maxlength="40" required>
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_address1" id="m_address1" value="" placeholder="Enter Mailing Address 1" maxlength="40" required>
          </div>
        </div>
        <div class="form-group row">
          <label for="address2" class="col-2 col-form-label">Address 2:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_address2" id="p_address2" value="" placeholder="Enter Physical Address 2" maxlength="40">
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_address2" id="m_address2" value="" placeholder="Enter Mailing Address 2" maxlength="40">
          </div>
        </div>
        <div class="form-group row">
          <label for="address3" class="col-2 col-form-label">Address 3:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_address3" id="p_address3" value="" placeholder="Enter Physical Address 3" maxlength="40">
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_address3" id="m_address3" value="" placeholder="Enter Mailing Address 3" maxlength="40">
          </div>
        </div>
        <div class="form-group row">
          <label for="address4" class="col-2 col-form-label">Address 4:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_address4" id="p_address4" value="" placeholder="Enter Physical Address 4" maxlength="40">
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_address4" id="m_address4" value="" placeholder="Enter Mailing Address 4" maxlength="40">
          </div>
        </div>
        <div class="form-group row required">
          <label for="city" class="col-2 col-form-label">City:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_city" id="p_city" value="" placeholder="Enter City" maxlength="25" required>
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_city" id="m_city" value="" placeholder="Enter City" maxlength="25" required>
          </div>
        </div>
        <div class="form-group row required">
          <label for="state" class="col-2 col-form-label">State:</label>
          <div class="col-5">
            <select class="custom-select browser-default physical" name="p_state" id="p_state" required>
              <option value="">--Select State--</option>
              <option value="az">Arizona</option>
              <option value="ia">Iowa</option>
              <option value="mo">Missouri</option>
              <option value="ny">New York</option>
              <option value="va">Virginia</option>
            </select>
          </div>
          <div class="col-5">
            <select class="custom-select browser-default mailing" name="m_state" id="m_state" required>
              <option value="">--Select State--</option>
              <option value="az">Arizona</option>
              <option value="ia">Iowa</option>
              <option value="mo">Missouri</option>
              <option value="ny">New York</option>
              <option value="va">Virginia</option>
            </select>
          </div>
        </div>
        <div class="form-group row required">
          <label for="zip" class="col-2 col-form-label">Zip:</label>
          <div class="col-5">
            <input class="form-control physical" type="text" name="p_zip" id="p_zip" value="" pattern="(\d{5}([\-]\d{4})?)" data-dispfld="The required format is: xxxxx or xxxxx-xxxx" placeholder="Enter Zip Code, formatted: 99999 or 99999-9999" maxlength="10" required>
          </div>
          <div class="col-5">
            <input class="form-control mailing" type="text" name="m_zip" id="m_zip" value="" pattern="(\d{5}([\-]\d{4})?)" data-dispfld="The required format is: xxxxx or xxxxx-xxxx" placeholder="Enter Zip Code, formatted: 99999 or 99999-9999" maxlength="10" required>
          </div>
        </div>

        <div class="row">
          <div class="col-12 text-center">
            <button class="btn btn-outline-secondary" type="button" name="save" id="save">Save</button>
            <button class="btn btn-outline-secondary" type="button" name="cancel" id="cancel">Cancel</button>
          </div>
        </div>
      </form>
    </div>
  </div>
</div>