0

I have a very simple pug file:

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

As you can see it produces a few trivial forms like this:

enter image description here

(Each form is one 'line' which is a simple table.)

(On the script side, it just reads each 'line' from a MySQL table, there are 10 or so of them.)

So on the www page, the user either

  • types in new number (say "8")

  • or clicks the small arrows (say Up, changing it to 7.2 in the example)

then the user must

  • click submit

and it sends the post.

Quite simply, I would like it to be that when the user

  • clicks a small arrows (say Up, changing it to 7.2 in the example)

it immediately sends a submit-post.

How do I achieve this?

(It would be fine if the send happens, any time the user types something in the field, and/or, when the user clicks the Small Up And Down Buttons. Either/both is fine.)


May be relevant:

My pug file (and all my pug files) have this sophisticated line of code as line 1:

include TOP.pug

And I have a marvellous file called TOP.pug:

html
    head
        style.
            html {
                font-family: sans-serif
            }
            td {
                font-family: monospace
            }
body
halfer
  • 19,824
  • 17
  • 99
  • 186
Fattie
  • 27,874
  • 70
  • 431
  • 719
  • Where are the small arrows in your template? – tom Sep 08 '19 at 13:17
  • @kmgt , you get those for free with type='number' in html forms. the issue is just to do the "submit" .. whenever the user changes the value – Fattie Sep 08 '19 at 20:40

1 Answers1

1

I have a solution with 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()
    });
  });
}

Actually it is short but if you want to support Internet Explorer you have to add the polyfill script too. Internet Explorer does not support closest() with this snippet below we teach it.

// 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 form submit to node.js

If you are interested in an ajax solution I put some code below just to blow your mind ;-) It should work instantly, I use it on one of my sites. You could use jQuery and save lines of code but I like it pure. (The ajax function and polyfills are utils so paste it anywhere)

HTML (example)

<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: event listener and prepare ajax call (note the callbacks).

// 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)
    }
  });
}

As told javascript utils (paste it anywhere like a library)

// 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;
  };
}

In node.js (e.g. express route)

// 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 ...',
  });
});
tom
  • 9,550
  • 6
  • 30
  • 49
  • 1
    (1) It have to be placed once. (2) If you want to avoid `closest` and the structure of the form won't change then you can hit the form with `e.parentNode.parentNode.parentNode.parentNode.submit();`. So you climb up the tree. – tom Sep 09 '19 at 10:17
  • 1
    I updated the answer `e.currentTarget.closest('form').submit()` is the thing (`currentTarget` = the current target for the event). – tom Sep 19 '19 at 15:01
  • 1
    I guess it's annoying when the form submits and the hole page is reloading. An ajax request behind the scenes would be better, right? – tom Sep 19 '19 at 15:16
  • Out of interest which backend language do you use? – tom Sep 19 '19 at 15:28
  • My backend is node.js. I use things like "passport". DB is MYSQL. Runs on AWS. (You can see some of my example code here! https://stackoverflow.com/a/57642380/294884 ) – Fattie Sep 19 '19 at 15:43
  • "You could use jQuery and save lines of code but I like it pure" Funny, I feel exactly the same way based on my limited knowledge – Fattie Sep 19 '19 at 17:37
  • 1
    The js has to be loaded after the markup or in `head` section. Otherwise it won't find html elements in DOM. Or wrap the js into `document.addEventListener("DOMContentLoaded", function(event) {});` [DOMContentLoaded](https://developer.mozilla.org/de/docs/Web/Events/DOMContentLoaded). Every bowser is same on that. – tom Sep 19 '19 at 18:03
  • It can't work with `e.preventDefault()`. It will stop the submit for sure. – tom Sep 19 '19 at 18:04
  • EVERYTHING WORKING PERFECTLY. – Fattie Sep 19 '19 at 18:10
  • Ah ok. You made it with ajax. Great. I thought you still hang on form submit. – tom Sep 19 '19 at 18:12
  • i simply added a new endpoint, /changelive (or I guess /changeajax would be a sensible name) and I use that with the ajax route! booyah ! – Fattie Sep 19 '19 at 18:18
  • 1
    To bring more light to the `function ajax(obj)`: you see the props beginning with `a.`(`a.method = 'GET';` and so on). They have default values e.g. `GET`. However you can overwrite them in the call e.g. `ajax({url: '/url', method: 'PUT', async: false, data: {}})`. Have fun! – tom Sep 19 '19 at 18:40
  • Wait wait. You are talking about `addEventListener('submit',...` right? This binds the `onsubmit` event. In case the user clicks the submit button, the form will be send, the ajax will be ignored. So the event handler is catching any type of submitting the form (by the way enter key is also possible to submit), stops it and let ajax do the request. – tom Sep 19 '19 at 19:22
  • Try `e.preventDefault()` or after `ajax` call just `return false`; – tom Sep 19 '19 at 19:39
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/199797/discussion-on-answer-by-kmgt-simple-pug-html-form-make-it-send-immediately-on-c). – Samuel Liew Sep 22 '19 at 09:15
  • here's a new expert question in the field, @kmgt !! stackoverflow.com/questions/59935561 – Fattie Jan 27 '20 at 17:04