Search code examples
javascriptcoffeescript

Form reloads page 2nd time with preventDefault when using custom DOM function


I have a login form, and when submitted it calls this function

login = (that) ->
  header =
    "X-CSRFToken": window.csrftoken

  $('body').toggleDOM('.loading', true)

  fermata.json("/login").post(header, that, (err, data) ->
    $('body').toggleDOM('.loading', false)

    if !data.success
      htAlert.error 'Incorrect username and/or password.'
  )

This will add a "loading spinner" while the API is being called, and then it's removed. This works fine the first time you submit the form, because I'm using preventDefault so the page doesn't reload. However on the 2nd try, the page will reload, and I don't know why it's behaving like that.

My DOM function to add an element to the body:

$.fn.toggleDOM = (domName, b) ->
  dom = domName[domName.search(/([\.\#])/)]
  newDom = domName.substring(1)

  if dom == '#'
    if b
      document.body.innerHTML += '<div id="' + newDom + '"></div>'
    else
      document.getElementById(newDom).remove()
  else
    if b
      document.body.innerHTML += '<div class="' + newDom + '"></div>'
    else
      document.getElementsByClassName(newDom)[0].remove()

The event for form submit:

$ ->
  trigger = document.querySelectorAll("[data-trigger]")

  trigger.forEach((i) ->
     i.addEventListener "submit", (e) ->
      e.preventDefault()
      # We have to pass the event instead of the element.
      switch i.attributes["data-trigger"].nodeValue
        when "[form/login]" then login(e)

HTML:

<form data-trigger="[form/login]" class="login">
  <input type="hidden" class="uuid hidden">
  <input placeholder="Username" class="username">
  <input placeholder="Password" type="password" class="password">
  <button type="submit">Login</button>
</form>

I need to know why it reloads the page on the 2nd try. If I remove the $('body').toggleDOM('.loading', true) it does not behave like that and works as intended.

Also, I'm not using jQuery, I'm using this small library: https://github.com/finom/balalaika


Solution

  • The problem lies in how you attach event listeners to your elements. What you're trying to do doesn't work in plain JavaScript.

    What you need to utilize is event delegation; it won't change much code wise but it'll change how it works.

    By removing your for loop and attaching the handler to the document instead, you'll get what you're after.

    document.addEventListener "submit", (e) ->
      e.preventDefault();
      switch e.target.attributes["data-trigger"].nodeValue
        when "[form/login]" then login(e)
    

    There is a great article that covers the how and why