0

I'm trying to use AJAX (vanilla JS) to submit a form to a Laravel 5.5 controller that will search the Amazon products API.

The AJAX is submitting the correct keywords and category inputs and the request is passing a form request that requires both keywords and category, but the keywords and category are not available in the controller. I do get a placeholder response from the controller.

The request payload in Chrome's Network tab in Developer Tools shows the AJAX is sending the correct terms.

HTML form

<form method="POST" action="http://local.example.com/api/products/search" accept-charset="UTF-8" id="product-search-form">
    <input name="_token" type="hidden" value="qcXA3xVIoltX9tSpeJeWuKVa3alUkq1p5tuIlKJ5">
    <div class="form-group">
        <label for="keywords" class="required">Keywords</label> 
        <input id="keywords" placeholder="Enter keywords" aria-required name="keywords" type="text">
    </div>
    <div class="form-group ">
        <label for="category">Category</label> 
        <select id="category" name="category">
            <option selected="selected" value="">Choose a category...</option>
            <option value="All">All</option>
            <option value="Apparel">Apparel</option>
            <option value="Books">Books</option>
            <option value="DVD">DVD</option>
            <option value="Electronics">Electronics</option>
            <option value="Music">Music</option>
            <option value="Software">Software</option>
            <option value="SoftwareVideoGames">SoftwareVideoGames</option>
            <option value="Toys">Toys</option>
            <option value="VideoGames">VideoGames</option>
            <option value="UnboxVideo">Video on Demand</option>
        </select>
    </div>
    <div>
        <button id="search-products-submit" type="submit" class="button-primary">Search</button>
    </div>
</form>

JavaScript AJAX post request on submit

function getProducts(keywords, category) {
    let path = "api/products/search";

    let searchTerms = {
        keywords: keywords,
        category: category,
        _token: getCsrfToken(),
    };

    ajaxPostRequest(path, showProducts, searchTerms);
}

AJAX requests

function ajaxPostRequest(path, callback, formId) {
    return ajaxRequest("POST", path, callback, formId);
}

function ajaxRequest(method, path, callback, data = null) {
    let xhr = new XMLHttpRequest();
    let url = getFullUrl(path);

    addEventListeners(xhr);

    xhr.open(method, url, true);

    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            let parsedResponse = JSON.parse(xhr.responseText);
            callback(parsedResponse);
        }
    };

    setRequestHeaders(xhr, method);

    xhr.send(JSON.stringify(data));
}

AJAX request payload

{
    keywords: "aaa",
    category: "All", 
    _token: "qcXA3xVIoltX9tSpeJeWuKVa3alUkq1p5tuIlKJ5"
}

Form request

class SearchProductsFormRequest extends Request
{
    /**
     * Determine if the user is authorized to make this request
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request
     *
     * @return array
     */
    public function rules(): array
    {
        return [
            'keywords' => 'required',
            'category' => 'required'
        ];
    }
}

Controller

/**
 * @param SearchProductsFormRequest $request
 * @return JsonResponse
 */
public function create(SearchProductsFormRequest $request): JsonResponse
{
    if ($request->has('keywords')) {
        \Log::info('Got keywords');
    } else {
        \Log::info('Did not get keywords');
    }

    if ($request->has('keywords')) {
        \Log::info('Got category');
    } else {
        \Log::info('Did not get category');
    }

    \Log::info(print_r($request, true));
    \Log::info('keywords: ' . $request->input('keywords'));
    \Log::info('category: ' . $request->input('category'));
    return response()->json([
        'name' => 'Abigail',
        'state' => 'CA'
    ]);
}

Log output

[2018-04-08 19:26:15] local.INFO: Did not get keywords
[2018-04-08 19:26:15] local.INFO: Did not get category
[2018-04-08 19:26:15] local.INFO: keywords:
[2018-04-08 19:26:15] local.INFO: category:

console.log of server response (dummy data for testing)

{name: "Abigail", state: "CA"}

So the controller is getting called and responding, but can't read the input data.

setRequestHeaders()

function setRequestHeaders(xhr, method) {
    xhr.setRequestHeader('Accept', 'application/json');
    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    xhr.setRequestHeader("X-CSRF-TOKEN", getCsrfToken());

if (method === 'GET') {
    xhr.setRequestHeader('Content-Type', 'application/json');
} else {
    xhr.setRequestHeader('Content-Type', 'multipart/form-data');
}
kirkaracha
  • 742
  • 2
  • 14
  • 23
  • 1
    What does `setRequestHeaders()` do? – Phil Apr 10 '18 at 00:29
  • I added the setRequestHeaders() code – kirkaracha Apr 10 '18 at 02:10
  • So, due to your method being `POST`, you're setting the content-type as `multipart/form-data` yet you're posting `application/json` formatted data. There is a mismatch between what you're telling the server to expect and what you're actually sending. Have you read the [linked answer yet](https://stackoverflow.com/a/33298205/283366)? – Phil Apr 10 '18 at 02:13
  • FYI, `GET` requests do not need a content-type header set as there is no request payload / body. – Phil Apr 10 '18 at 02:13
  • I changed to application/json with the same results. xhr.setRequestHeader('Content-Type', 'application/json'); – kirkaracha Apr 10 '18 at 02:16
  • I'll ask you again... **Have you read the [linked answer](https://stackoverflow.com/a/33298205/283366) yet?** – Phil Apr 10 '18 at 02:22
  • Yes, this fixed it: $data = $request->json()->all(); $keywords = $data['keywords']; $category = $data['category']; Thanks for your help! – kirkaracha Apr 10 '18 at 02:25

1 Answers1

-1

Because you didnt pass that value to the controller so it logged the right output for you.

This was where you made your request and you explicitly made data = null in your code here:

function ajaxRequest(method, path, callback, data = 
// DATA IS NULL HERE
> null

) {
let xhr = new XMLHttpRequest();
let url = getFullUrl(path);

addEventListeners(xhr);

xhr.open(method, url, true);

xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        let parsedResponse = JSON.parse(xhr.responseText);
        callback(parsedResponse);
    }
};

setRequestHeaders(xhr, method);

xhr.send(JSON.stringify(
//            YOU ARE PASSING NULL VALUE HERE
> data

));
}

Pass the right parameters and you will be fine

Acetech
  • 398
  • 4
  • 16
  • 1
    `data = null` is a [default parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters) value. OP is setting data correctly in `ajaxPostRequest(path, showProducts, searchTerms)` – Phil Apr 10 '18 at 00:26
  • I get you, I am saying in your xhr.send(JSON.stringify(data)); You are sending just data which is null, send xhr.send("keywords=" + JSON.stringify(keywords) + ""); their too – Acetech Apr 10 '18 at 00:47
  • here xhttp.send("data=" + JSON.stringify(data) + "&keywords"+ keywords + "" + "&category"+ category + ""); – Acetech Apr 10 '18 at 00:50
  • 1
    No, OP is **not** sending `null`. They call `ajaxPostRequest(path, showProducts, searchTerms)` where `searchTerms` is an object. This is then stringified and sent as JSON. See https://stackoverflow.com/a/6418506/283366 – Phil Apr 10 '18 at 00:57