0

In jQuery, the code is like this, and everything is happy as a lark:

$.post("bucket.php", { empty:"1" }, function(r) { ... });

The console in Firefox's dev tools says that "Form data" contains empty=1, and the PHP file gets what it wants. I don't really understand that syntax (what { } does and while empty isn't in quotes), but it works.

Now I need to do the same thing in plain Javascript. Here is my code:

function bucketEmpty() {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if(xhr.readyState == 4 && xhr.status == 200) {  //no error
      if (!isNaN(xhr.responseText)) {  // return string is a number (expecting 0)
        document.getElementsByClassName('bucketcount').innerHTML = xhr.responseText;
      } else {
        alert(xhr.responseText);
      }
    }
  };
  xhr.open('POST', 'bucket.php');
  xhr.send(???what goes here???);
}

For the parameter of xhr.send() I have tried the following, none of which work:

  • 'empty=1' (according to this answer): The console says it's in "Request payload", not "Form data".
  • {empty:"1"} or {"empty":"1"} : The console says "Request payload" contains "[object Object]".
  • '{"empty":"1"}' : The console says it's in a section called "JSON".

This would be easy with GET, but since it is changing something on the server, POST is more proper. What is the right syntax?


EDIT in response to Rayon and Atul Sharma:

Hmm, something else fundamental must be wrong. For diagnostics, I currently have this as the first line in bucket.php:

die("GET:\n".print_r($_POST,true)."\nPOST:\n".print_r($_POST,true));

Whether I use xhr.send(JSON.stringify({empty: '1'})) (Atul Sharma's answer) or xhr.send('{"empty":"1"}') (Rayon's comment), I get the result shown in this screenshot of the console and the output from the server: enter image description here

And I decided to test GET - I changed the open command to this:

xhr.open('GET', 'bucket.php?empty=1');

The console says Query String contains empty: "1", but on the server $_GET and $_POST are still empty! Does vanilla JS just hate me? This works fine in jQuery.


EDIT #2: It's just me (or my server)!

After discovering that even my original simplest syntax works on jsfiddle (https://jsfiddle.net/3j42ztpf/), I wrote a WME test file pair for my own server. On both my local VM and my production VPS, it acts the same way - jQuery works but plain JS doesn't, regardless of whether the parameter is sent as text or JSON. There must be something different in the HTTP request between the JSFiddle test and the first link on my server test, but I can't spot it in devtools - the Request Payload is identical and the differences in the headers all seem logical. Try it yourself at: https://dev.kizunadb.com/testajax.html

The back-end file testajax.php only contains this:

<?php
if (empty($_POST['text'])) {
  die('What do you want me to say?');
} else {
  die ($_POST['text']);
}

So I really do have a more fundamental problem than just bad syntax. Can anyone spot the issue in their dev tools, or suggest something I should check on my server?

OsakaWebbie
  • 543
  • 1
  • 4
  • 17
  • I don't see anything wrong in `'{"empty":"1"}'` but server should be able to understand content-type. – Rayon Apr 17 '20 at 04:33
  • @Rayon: I updated my question - apparently there is some other bottleneck, because I tried GET, and it didn't work either! – OsakaWebbie Apr 17 '20 at 07:28
  • Did you pass `'Content-Type': 'application/json'`? – Rayon Apr 17 '20 at 10:34
  • Where would I put that? I don't see that in any of the XMLHttpRequest examples I found. It sounds more like something that would go in the server-side file for specifying the type of the content returned - I'm not using JSON for that at all (I only return a simple string, usually just a string of an integer). – OsakaWebbie Apr 17 '20 at 12:38
  • Set `xhr.setRequestHeader("Content-Type", "application/json");` after `xhr.open` – Rayon Apr 18 '20 at 01:34
  • I tried that, but there was no change. The entry under Request Headers changed from `Content-Type: text/plain;charset=UTF-8` to `Content-Type: application/json`, and under Request Parameters it still says `empty :"1"` is in the JSON category, but the server file still never sees anything in `$_POST`. – OsakaWebbie Apr 18 '20 at 05:59
  • Could you try `var_dump($_REQUEST)` – Rayon Apr 18 '20 at 11:40
  • That's functionally the same as what I was doing with `print_r($_GET) . print_r($_POST)`, but okay, I changed the opening line of bucket.php to `var_dump($_REQUEST);exit;` The text part of the output was: `/...[path].../bucket.php:7: array (size=0) empty` – OsakaWebbie Apr 19 '20 at 00:12
  • Could you share your code through some fiddle? I highly doubt there is some silly mistake. – Rayon Apr 19 '20 at 05:15
  • I thought I'd already provided all the relevant code, but now I've put it in CodePen: https://codepen.io/OsakaWebbie/pen/NWGrBjP But of course I can't test it there, because there is no server-side code spot. (My PHP is in the HTML spot just as a place to put it.) I'm currently running this on a local VM, but that shouldn't prevent it from working. – OsakaWebbie Apr 19 '20 at 08:00
  • I found out how to test AJAX with JSFiddle (apparently CodePen doesn't have this feature): https://jsfiddle.net/3j42ztpf/ As you can see, the very first syntax I tried in my code (`'var=value'`) works fine in JSFiddle. I can't see JSFiddle's backend code, but I'm sure it's extremely simple - something like `die $_POST['html'];`. This is SO baffling! – OsakaWebbie Apr 20 '20 at 02:31
  • I edited my question (Edit #2), but tl;dr = Can you see a functional difference between the HTTP requests of https://jsfiddle.net/3j42ztpf/ (works) and the first link on https://dev.kizunadb.com/testajax.html (fails)? – OsakaWebbie Apr 20 '20 at 04:02
  • 1
    You should pass an instance of [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects) to `.send(formDataInstanceHere)`. By the way you can just do like `xhr.onload = function(){ const obj = JSON.parse(xhr.responseText); }`. – StackSlave Apr 20 '20 at 04:03
  • 1
    @OsakaWebbie - I am not sure how php 7 works, it's been a long time I am away from it. Tried this example as you were having a hard time and could understand one thing, `$data = json_decode(file_get_contents('php://input'), true);` is some new way how we retrieve posted data. It worked for me. Let me know. – Rayon Apr 20 '20 at 08:44
  • @StackSlave Your idea did work, but creating a FormData instance just to send a single parameter seemed overkill, so I kept researching. As for your "by the way", my response is not JSON (normally just text of the number 0), so I don't see why I would parse it like that. Anyway, thanks for the help - it was part of what got me pointed in the right direction. – OsakaWebbie Apr 20 '20 at 14:26
  • 1
    @Rayon PHP7 isn't different from older versions in this regard (except that $HTTP_RAW_POST_DATA was removed). `php://input` isn't new at all, but I had never heard of it until you mentioned it! I don't want to use it as my final solution because it would need parsing to get what $_GET and $_POST already provide, but it helped me see what was happening, and including it in Google searches led me to pages with clues I needed. Thanks. – OsakaWebbie Apr 20 '20 at 14:28
  • @OsakaWebbie Cheers man! – Rayon Apr 20 '20 at 16:44

1 Answers1

0

While researching suggestions in the comments from @StackSlave and @Rayon, I learned more about Content-Type. It turns out that all I needed was to set the Content-Type to "application/x-www-form-urlencoded". All three people who tried to help (one of whom removed their answer later) focused on passing data back and forth as JSON, but that's really unnecessary in this case and just makes the code harder to read (in my opinion anyway). So here is the final version of the function, the main point being the last two lines (I also had to change how I was writing the returned number into the DOM - getElementsByClassName returns an HTMLCollection that must be handled correctly):

function bucketEmpty() {
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function() {
    if(xhr.readyState === 4 && xhr.status === 200) {  //no error
      if (!isNaN(xhr.responseText)) {  // return string is a number (expecting 0)
        var elements = document.getElementsByClassName('bucketcount');
        [].forEach.call(elements, function (el) { el.innerHTML = xhr.responseText; });
      } else {
        alert(xhr.responseText);
      }
    }
  };
  xhr.open('POST', 'bucket.php');
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  xhr.send('empty=1');
}

Particularly helpful SO answers and other pages were:

OsakaWebbie
  • 543
  • 1
  • 4
  • 17