1

I have a search form with half a dozen inputs, which performs a GET action to execute the search. Since there are many input fields, but only a handful are likely to be filled by any given search, I don't want the empty fields to be included in the GET args on the search request.

Here's my form (in django template syntax):

  <form id="advanced-search-form" action="{% url 'search:advanced_search' %}" method="GET">
      <div class="left clearfix">
        <div class="input-field">
          <label for="given-name">First Name:</label>
          <input id="given-name" name="givenName" tabindex="1">
        </div>
        <div class="input-field">
          <label for="sn">Last Name:</label>
          <input id="sn" name="sn"tabindex="2">
        </div>
        <div class="input-field">
          <label for="tel-number">Extension:</label>
          <input type="tel" id="tel-number" name="telephoneNumber" tabindex="3">
        </div>
      </div>
      <div class="right clearfix">
        <div class="input-field">
          <label for="category">Category:</label>
          <select id="category" name="personType" tabindex="4">
            <option value="">Choose One:</option>
            {% for slug, type in person_types.items %}
              <option value="{{ slug }}">{{ type }}</option>
            {% endfor %}
          </select>
        </div>
        <div class="input-field">
          <label for="building">Building:</label>
          <input id="building" name="physicalDeliveryOfficeName" tabindex="5">
        </div>
        <div class="input-field">
          <label for="dept">Department:</label>
          <input id="dept" name="department" {{ input_attrs }} tabindex="6">
        </div>
        <div class="submit-button">
          <input type="submit" value="Submit Search" tabindex="7">
        </div>
      </div>
    </form>

My objective is for the URL that this form leads to after submission with just the givenName field filled to look like this:

https://domain/search/advanced_search?givenName=Ben

Instead of looking like this:

https://domain/search/advanced_search?givenName=Ben&sn=&telephoneNumber=&personType=&physicalDeliveryOfficeName=&department=

I've found ancient SO questions which answer this question as "Create a new <form> in Javascript, populate it with <input>s that have the values you want, and then call .submit() on the form". e.g. https://stackoverflow.com/a/13937065/464318

I'm sure that's still viable today, but it seems extremely clunky. Is there not a more modern way to do this, perhaps via the FormData API? I've looked and looked for an answer to this in the MDN docs and all across google, but I think I just don't know the right term for "non-Ajax javascript form submission".

I don't want to use XMLHttpRequest or Fetch, because I actually want the form submission to trigger a normal page load leading the user to the search results page.

coredumperror
  • 8,471
  • 6
  • 33
  • 42
  • 2
    If the resulting request is a GET anyway then you could just [build the URL](https://stackoverflow.com/questions/111529/how-to-create-query-parameters-in-javascript) and redirect the user, rather than relying on building a form and submitting it. – David Aug 17 '23 at 20:05
  • 5
    Why? Just... use the form the way it was intended, by having an `action` attribute and a `method="POST"` so that your form performs a normal POST, with the form data as payload instead of a URL query arguments, or since this is a search, _just accept all those query arguments_? What problem are you trying to solve that makes you think this change is necessary? (Because this feels pretty close to an XY problem) – Mike 'Pomax' Kamermans Aug 17 '23 at 20:05
  • 2
    To be clear, you are saying you **don't** want to use AJAX, and you **don't** want to call `.submit()` on a `
    ` element?
    – Chris Barr Aug 17 '23 at 20:10
  • 2
    The clunky way is still the way. – Pointy Aug 17 '23 at 20:13
  • Calling `.submit()` on a `
    ` element seems like the only way for Javascript to perform a normal form submission. But Maybe I'm missing something? Maybe there's a mechanism similar to XMLHttpRequest, which does a non-ajax request which leads to another page, rather than staying on the same page? I'm not sure, which is why I'm asking.
    – coredumperror Aug 17 '23 at 22:30
  • @Mike'Pomax'Kamermans I'm trying to avoid jamming a bunch of blank and useless query args into the URL of the search results page, since my users often bookmark those. And because they bookmark the search results page, a POST doesn't work. It's also a non-data-changing request, so POST is not the right HTTP verb to use. – coredumperror Aug 17 '23 at 22:32
  • Okay, but: _why do you care whether they're useless or not_, this is how browser form posts work. It's not a beauty competition. Neither bookmarks, nor the browser, cares. – Mike 'Pomax' Kamermans Aug 18 '23 at 05:13
  • It doesn't matter why I care. I just do. – coredumperror Aug 24 '23 at 19:41

2 Answers2

2

I'm assuming the use case here is to address things like:

  • Searches need to be bookmarked/shared, necessitating a GET request
  • Search URLs should be relatively clean when shared, as most users are likely to only use one or two search fields of the many fields offered

If it's just a GET request then we don't really need a <form> (given the first use case concern above, many users won't be using one anyway). But a <form> is still very useful for quickly serializing the data. Once serialized however, you can use that data to simply build a URL and redirect the user.

For example:

const form = document.querySelector('#advanced-search-form');

form.addEventListener('submit', function (e) {
  // prevent form submit
  e.preventDefault();
  
  // serialize form data
  const formData = new FormData(form);
  const params = {};
  
  // filter empty values
  for (const val of formData) {
    if (val[1]) {
      params[val[0]] = val[1];
    }
  }
  
  // redirect instead of console log here
  // window.location.href = `${form.action}?${new URLSearchParams(params).toString()}`;
  console.log(`${form.action}?${new URLSearchParams(params).toString()}`);
});
<form id="advanced-search-form" action="/some/url" method="GET">
  <div class="left clearfix">
    <div class="input-field">
      <label for="given-name">First Name:</label>
      <input id="given-name" name="givenName" tabindex="1">
    </div>
    <div class="input-field">
      <label for="sn">Last Name:</label>
      <input id="sn" name="sn"tabindex="2">
    </div>
    <div class="input-field">
      <label for="tel-number">Extension:</label>
      <input type="tel" id="tel-number" name="telephoneNumber" tabindex="3">
    </div>
  </div>
  <div class="right clearfix">
    <div class="input-field">
      <label for="category">Category:</label>
      <select id="category" name="personType" tabindex="4">
        <option value="">Choose One:</option>
        <option value="0">test</option>
      </select>
    </div>
    <div class="input-field">
      <label for="building">Building:</label>
      <input id="building" name="physicalDeliveryOfficeName" tabindex="5">
    </div>
    <div class="input-field">
      <label for="dept">Department:</label>
      <input id="dept" name="department" tabindex="6">
    </div>
    <div class="submit-button">
      <input type="submit" value="Submit Search" tabindex="7">
    </div>
  </div>
</form>

Leaving the form in place as-is also allows clients to fall back on the less-ideal-but-still-functional standard <form> action (to include empty values in the URL) when JavaScript is unavailable.

David
  • 208,112
  • 36
  • 198
  • 279
  • This is exactly what I was looking for. Thanks for showing me the object-based method of building up the non-blank query args, too, as I'd been trying it with arrays, which don't work with `URLSearchParams`. – coredumperror Aug 17 '23 at 22:34
1

This should work:

document.querySelector("#advanced-search-form").addEventListener("submit", (e) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const query = new URLSearchParams(formData);

    for (const [key, value] of [...query]) {
        if (value === "") {
            query.delete(key);
        }
    }

    location.search = query.toString();
})
DallogFheir
  • 193
  • 1
  • 7
  • 1
    Right, I fixed it. – DallogFheir Aug 17 '23 at 20:45
  • I like the approach, and it looks like it does the job as requested. The answer could be improved by adding an explanation of what you did, and maybe also by [adding a runnable Stack Snippet](https://meta.stackoverflow.com/a/358993/1220550). – Peter B Aug 17 '23 at 22:09
  • I ultimately went with something similar to this after having a brainwave right after posting this question. But this is actually even more elegant than what I did. I like it! – coredumperror Aug 17 '23 at 22:28