简单的 pug html 表单,让它在值更改时立即发送,而不是等待提交按钮

Simple pug html form, make it send immediately on change of value rather than wait for submit button

我有一个非常简单的 pug 文件:

for item in itemList

    form(method='post', action='/change')
        table
            tr
                td(width=100)
                td(width=200)
                    | #{item.name}
                    input(type='hidden', name='field' value=item.name)
                    input(type='hidden', name='style' value='doublevalue')
                td(width=100)
                    input(type='number', name='value' min=-20.0 max=80.00 step=0.01 value=+item.value)
                td(width=100)
                    input(type='submit', value='Update')

p end

如您所见,它会生成一些像这样的微不足道的 forms:

(每个表格是一个'line',这是一个简单的table。)

(在脚本方面,它只是从 MySQL table 中读取每个 'line',其中有 10 个左右。)

因此在 www 页面上,用户要么

那么用户必须

并发送 post.

很简单,我希望当用户

它立即发送一个提交-post。

如何实现?

(如果发送发生,任何时候用户在字段中键入内容,and/or,当用户单击小的向上和向下按钮时,就可以了。Either/both 很好。 )


可能相关:

我的哈巴狗文件(以及我所有的哈巴狗文件)在第 1 行有这样 复杂 行代码:

include TOP.pug

我有一个很棒的文件,叫做 TOP.pug:

html
    head
        style.
            html {
                font-family: sans-serif
            }
            td {
                font-family: monospace
            }
body

我有 javascript 的解决方案。

// check if there are input[type="number"] to prevent errors
if (document.querySelector('input[type="number"]')) {
  // add event for each of them
  document.querySelectorAll('input[type="number"]').forEach(function(el) {
    el.addEventListener('change', function (e) {
      // on change submit the parent (closest) form
      e.currentTarget.closest('form').submit()
    });
  });
}

实际上它很短,但如果你想支持 Internet Explorer,你还必须添加 polyfill 脚本。 Internet Explorer 不支持 closest() 下面我们教它的这段代码。

// polyfills for matches() and closest()
if (!Element.prototype.matches) 
  Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
if (!Element.prototype.closest) {
  Element.prototype.closest = function(s) {
    var el = this;
    do {
      if (el.matches(s)) return el;
      el = el.parentElement || el.parentNode;
    } while (el !== null && el.nodeType === 1);
    return null;
  };
}

Ajax 表单提交到 node.js

如果您对 ajax 解决方案感兴趣,我在下面放了一些代码只是为了让您大开眼界;-) 它应该可以立即运行,我在我的一个网站上使用了它。您可以使用 jQuery 并保存代码行,但我喜欢它纯粹。 (ajax 函数和 polyfills 是实用程序,所以将其粘贴到任何地方)

HTML(示例)

<form>  
  <input type="hidden" name="field" value="field1">
  <input type="hidden" name="style" value="style1">
  <input type="number" name="value">
  <input type="submit" value="update">
</form>
<form>  
  <input type="hidden" name="field" value="field2">
  <input type="hidden" name="style" value="style2">
  <input type="number" name="value">
  <input type="submit" value="update">
</form>

Javascript: 事件侦听器并准备 ajax 调用(注意回调)。

// check if there are forms to prevent errors
if (document.querySelector('form')) {
  // add submit event for each form
  document.querySelectorAll('form').forEach(function (el) {
    el.addEventListener('submit', function (e) {
      e.currentTarget.preventDefault();
      submitData(e.currentTarget);
    });
  });
}

// check if there are input[type="number"] to prevent errors
if (document.querySelector('input[type="number"]')) {
  // add change event for each of them
  document.querySelectorAll('input[type="number"]').forEach(function (el) {
    el.addEventListener('change', function (e) {
      submitData(e.currentTarget.closest('form'));
    });
  });
}

// collect form data and send it
function submitData(form) {
  // send data through (global) ajax function 
  ajax({
    url: '/change',
    method: 'POST',
    data: {
      field: form.querySelector('input[name="field"]').value,
      style: form.querySelector('input[name="style"]').value,
      value: form.querySelector('input[name="value"]').value,
    },
    // callback on success
    success: function (response) {
      // HERE COMES THE RESPONSE
      console.log(response);
      // error is defined in (node.js res.json({error: ...}))
      if (response.error) {
        // make something red
        form.style.border = '1px solid red';
      }
      if (!response.error) {
        // everything ok, make it green
        form.style.border = '1px solid green';
      }
      // remove above styling
      setTimeout(function () {
        form.style.border = 'none';
      }, 1000);
    },
    // callback on error
    error: function (error) {
      console.log('server error occurred: ' + error)
    }
  });
}

如前所述javascript utils(像库一样粘贴到任何地方)

// reusable ajax function
function ajax(obj) {
  let a = {};
  a.url = '';
  a.method = 'GET';
  a.data = null;
  a.dataString = '';
  a.async = true;

  a.postHeaders = [
    ['Content-type', 'application/x-www-form-urlencoded'],
    ['X-Requested-With', 'XMLHttpRequest']
  ];
  a.getHeaders = [
    ['X-Requested-With', 'XMLHttpRequest']
  ];

  a = Object.assign(a, obj);
  a.method = a.method.toUpperCase();

  if (typeof a.data === 'string')
    a.dataString = encodeURIComponent(a.data);
  else
    for (let item in a.data) a.dataString += item + '=' + encodeURIComponent(a.data[item]) + '&';

  let xhReq = new XMLHttpRequest();
  if (window.ActiveXObject) xhReq = new ActiveXObject('Microsoft.XMLHTTP');

  if (a.method == 'GET') {
    if (typeof a.data !== 'undefined' && a.data !== null) a.url = a.url + '?' + a.dataString;
    xhReq.open(a.method, a.url, a.async);
    for (let x = 0; x < a.getHeaders.length; x++) xhReq.setRequestHeader(a.getHeaders[x][0], a.getHeaders[x][1]);
    xhReq.send(null);
  }
  else {
    xhReq.open(a.method, a.url, a.async);
    for (let x = 0; x < a.postHeaders.length; x++) xhReq.setRequestHeader(a.postHeaders[x][0], a.postHeaders[x][1]);
    xhReq.send(a.dataString);
  }
  xhReq.onreadystatechange = function () {
    if (xhReq.readyState == 4) {
      let response;
      try {
        response = JSON.parse(xhReq.responseText)
      } catch (e) {
        response = xhReq.responseText;
      }
      //console.log(response);
      if (xhReq.status == 200) {
        obj.success(response);
      }
      else {
        obj.error(response);
      }
    }
  }
}

// (one more) polyfill for Object.assign 
if (typeof Object.assign !== 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, 'assign', {
    value: function assign(target, varArgs) {
      // .length of function is 2
      if (target === null || target === undefined) {
        throw new TypeError('Cannot convert undefined or null to object');
      }
      var to = Object(target);
      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];
        if (nextSource !== null && nextSource !== undefined) {
          for (var nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

// polyfills for matches() and closest()
if (!Element.prototype.matches)
  Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
if (!Element.prototype.closest) {
  Element.prototype.closest = function (s) {
    var el = this;
    do {
      if (el.matches(s)) return el;
      el = el.parentElement || el.parentNode;
    } while (el !== null && el.nodeType === 1);
    return null;
  };
}

node.js(例如快速路线)

// the route in node.js
app.post('/change', (req, res) => {
  // your logic here
  let field = req.body.field;
  let style = req.body.style;
  let value = req.body.value;
  // ...
  // response result
  res.json({
    databaseError: false, // or true
    additionalStuff: 'message, markup and other things ...',
  });
});