The event listener goes away?

I have a dropdown list which is tied to an event listener.

<script>
document.getElementById('material_type').addEventListener('change',postType);


    function postType(e){
      e.preventDefault();


      var xhr = new XMLHttpRequest();
      xhr.open('POST', 'material_type_results.php');
      xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');

      xhr.onload = function(){
        document.getElementById('materials').innerHTML = this.responseText;
      }

      xhr.send("Material_Type="+document.getElementById('material_type').value);
    }
    
</script>

You can see thats it tied from


Once I make a selection, it goes away and nothing seems to happen when a selection is made

When an element is removed then recreated, such as by using innerHTML, that destroys any event listeners that used to be on that element.

oh, dang Is there a better way than using…

        document.getElementById('materials').innerHTML = this.responseText;

There are a couple of viable solutions that are typically done here.

You could:

  • replace the event listener after updating that section of HTML code
  • move the event listener to a higher location in the DOM

Either of those are perfectly suitable, though I do typically prefer the second option.

So I can place the listener in the head and thats it?

<script>
document.getElementById('material_type').addEventListener('change',postType);
</script>
```
when I do that, I get

Uncaught TypeError: document.getElementById(...) is null, How can I refer ti it then? should I go with the first option?
```
<select class="form-control mr-5" id="material_type" name="material_type" onChange="postType">
```;

No, it does not go in the head. It stays just before the </body> element.

Instead of there being multiple events being on individual elements, a single event is used at a higher level, such as on the materials element, or on the document body.

When the event listener at a higher level is used, it needs to change to filter out other unwanted events. You can do that by using Node.contains() to check that the clicked element is inside of the materials section, before invoking the handler function for dealing with it.

#1: Please tell me you dont have 16,117 items in a select box.

#2: Why are we replacing the select box et al, and not just the list of filtered items? Pull data with the XHR request, you dont need to pull the HTML structure? Then you dont need either of the options; your page just… works?

Have you considered using json to populate a single view rather than trying to piece meal together views rendered server-side in javascript and orchestrate event listeners. If you are to populate the data on the front-end with json it would make for a more optimized, efficient user experience. Also if you ase a library like React or Vue this event listener problem would effectively be eliminated. Instead you would probably just end up with a single view that is populated differently based on selections being made. Rather than multiple views that look to be basically the same thing with the exception of different data.

Your approach has many disadvantages. You should not replace the whole select box and you should not receive a HTML DOM with Ajax request.
The backend should only deliver data not design.
So your material_type_results.php should return for example a json string containing only the options. for example

[
    {
        value: "iron",
       text: "Iron",
    },
   {
       value: "water",
      text: "Water"
    }
]

Now you can only fill the select with its options.

// get new types
let xhr = new XMLHttpRequest();
xhr.open('POST', 'material_type_results.php');
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onload = function()
{
    setNewOptions(JSON.parse(this.responseText));
}
xhr.send("Material_Type="+document.getElementById('material_type').value);

// set new options
function setNewOptions(options)
{
    const selectElement = document.getElementById('material_type');
    // first remove all old options
    let j = selectElement.options.length - 1;
    for(let = j; i >= 0; i--) {
        selectElement.remove(i);
    }
    options.forEach((option) =>
    {
        let opt = document.createElement('option');
        opt.value = option.value;
        opt.innerHTML = option.text;
        selectElement.appendChild(opt);
    }

in that case your event handler will always work and never deleted.

And of course it would be much more elegant to use fetch instead of XHR request.

When using the XMLHttpRequest() api It can return error messages or any warnings generated by php

Using the above when that happens is not probably what you want.
I use the fetch api as it returns a promise which is handy for detecting unwanted results.
I have abstracted server side requests as well as the php responses.
in php I use

    function respond($status, $_report, $data) {
        global $lastSQL, $report;
        if(strlen($lastSQL) > 0) {
            $report .= "Last sql: ".$lastSQL;
        }
        echo(json_encode(array('status' => $status, 'report' => $_report."\n".$report, 'data' => $data)));
        exit;
    }

The exception handlers also call respond()
In the js I use the following code

let _POST = async function(args) {
  let fd = new FormData();
  fd.append('family', localStorage.getItem('family'));
  for (const item in args) {
	fd.append(item, args[item]);
  }
  let response = await fetch(".php/editor.php", {method : 'POST', body : fd});
  let txt = await response.text();
  if(txt) {
	let reply = $responseObj(txt);
	if(reply.json) {
	  if(reply.json.status === "ok") {
		return reply.json.data;
	  } else {
		console.log("Non JSON res[ponse - " + txt);
		return false;
	  }
	} else {
	  console.log(txt);
	  return txt;
	}
  }
}

const $responseObj = txt => {
    let reply = {'str': undefined, 'report' : 'No Report', 'json' : undefined};
    try {
        reply.json = JSON.parse(txt);
    }
    catch (error) {
        reply.str = txt;
    }
	finally {
		return reply;
	}
}

It returns an object which can be data or an error/warning message and if it is a server connection error it is text
fetch is far more easy to use than XMLHttpRequest.
The report is used when things do not go as planned and so the report variable contains function and line nr. hints to aid in debugging.

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.