问题
I have a <textarea>
and a <button>
inside of a <form>
. When submitted, I call e.preventDefault()
and submit the form via AJAX
. From there I return the query and PREPEND
at information inside of a <div>
at the top of this list.
Also, I have given each item the ability to be deleted, which is instant on the client side but also submits a form via AJAX
to be completely removed. This is sort of working.
I am able to:
- Have a blank screen (no items added), create one and delete it no problems
- Have blank screen, add two items, delete the NEWEST item with no problems but deleting the second item (which was the first, or oldest item) returns an error. It's trying to delete itself and the NEWEST item. So if I have three, it will delete itself and the newest, leaving item #2 all alone. This just gets worse the more items that are added.
What I need to do
- Have newly Prepended element inherit the event handler
- Only remove the item that is selected
Code Explanation
When the user loads the page, items that are stored in the database are immediately queried and added to the screen.
Go ahead and find const delPostFunc
in the first code example. This is an anonymous function that is called immediately, to ensure that any items that are initially added to the screen are assigned the click
event handler.
When a user submits a new item via submitPostBtn.addEventListener('click', e => {
, at the bottom of the first example, two calls are made. One to const submitPost
, AJAX
in the second example, and const returnNewestPost
, AJAX
in the second example. This returnNewestPost
call returns some DATA
from the database, which just so happens to be the newest item inserted, and then it PREPENDS
this item to the top of the list, displayPostWrapper.prepend(newPostDiv);
and finally calls the delPostFunc();
function in an attempt to reassign the event handler to newly inserted items. This is because innerHTML
removes any event handlers that are supposed to be on an element, or that is what I am lead to believe.
JavaScript
// DELETE POST VARIABLES
let deletePostBtn = document.querySelectorAll('button[name="delete_post"]');
const displayPostWrapper = document.querySelector('.col-8.pt-4');
let displayPostSection = document.querySelectorAll('.col-8.pt-4 .row');
let postID = document.querySelectorAll('#delete-post-id');
// SUBMIT POST VARIABLES
const submitPostBtn = document.querySelector('#submit-post-button');
const submitPostID = document.querySelector('#submit-post-id');
const submitPostContent = document.querySelector('#submit-post-content');
const submitPostName = document.querySelector('#submit-post-name');
// MAKING THE CALL TO DELETE THE POST
const delPostFunc = () => {
console.log(deletePostBtn);
deletePostBtn = document.querySelectorAll('button[name="delete_post"]');
console.log(deletePostBtn);
if (deletePostBtn) {
for (let i = 0; i < deletePostBtn.length; i++) {
deletePostBtn[i].addEventListener('click', e => {
e.preventDefault();
postID = document.querySelectorAll('#delete-post-id');
displayPostSection = document.querySelectorAll('.col-8.pt-4 .row');
console.log(postID[i].value);
// ${postID[i]} comes from `const postID` at the top
deletePostPromise('http://localhost/mouthblog/ajax/delete_post.ajax.php', `id=${postID[i].value}`);
console.log(deletePostBtn);
displayPostSection[i].remove();
console.log(deletePostBtn);
});
}
}
}
// CALL `delPostFunc()` FOR THE INITIAL `deletePostBtn` ON SCREEN
delPostFunc();
// MAKING CALL TO SUBMIT NEW POST
if (submitPostBtn) {
submitPostBtn.addEventListener('click', e => {
e.preventDefault();
// SUBMIT POST
submitPost('http://localhost/mouthblog/ajax/submit_post.ajax.php',
`id=${submitPostID.value}&name=${submitPostName.value}&content=${submitPostContent.value}`)
.then(() => {
// RETURN THAT SAME POST
returnNewestPost('http://localhost/mouthblog/api/newest_post.php')
.then(data => {
// INSERT POST INTO DOM
const newPostDiv = document.createElement('div');
newPostDiv.setAttribute('class', 'row');
newPostDiv.innerHTML = `
<article class="col-10 offset-1">
<h2 class="h2">${data.user_name}</h2>
<small>${data.date_created}</small>
<form action="//localhost/mouthblog/blog.php" method="POST">
<button class="btn btn-danger" name="delete_post" type="submit">DELETE</button>
<input id="delete-post-id" name="post_id" type="hidden" value="${data.id}">
</form>
<hr>
<p class="lead">${data.content}</p>
</article>
`;
console.log(`INSERTING ${data.id}`);
displayPostWrapper.prepend(newPostDiv);
console.log(`INSERT ${data.id} COMPLETE`);
// GIVE THE `newPostDiv`'s `delete button` THE CLICK EVENT HANDLER
console.log(`RUNNING delPostFunc()`);
delPostFunc(); // BOOM!
console.log(`delPostFunc() COMPLETE`);
});
});
});
}
These are the promises for the AJAX just incase
// GET REQUEST TO RETRIEVE EVERY POST
const get = (url) => {
return new Promise((resolve, reject) => {
const xhttp = new XMLHttpRequest();
xhttp.open('GET', url, true);
xhttp.onload = () => {
if (xhttp.status == 200) {
resolve(JSON.parse(xhttp.response));
} else {
reject(xhttp.statusText);
}
};
xhttp.onerror = () => {
reject(xhttp.statusText);
};
xhttp.send();
});
}
// DELETE SPECIFIC POST
const deletePostPromise = (url, postID) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = () => {
if (xhr.status == 200) {
console.log('if (xhr.status == 200)');
resolve();
} else {
reject(xhr.statusText);
}
};
xhr.onerror = () => {
reject(xhr.statusText);
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(postID);
});
}
// SUBMIT A NEW POST
const submitPost = (url, user_id, user_name, content) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = () => {
if (xhr.status == 200) {
console.log('resolving');
resolve();
} else {
reject(xhr.statusText);
}
};
xhr.onerror = () => {
reject(xhr.statusText);
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(user_id, user_name, content);
});
};
// RETURN THE NEWEST BLOG POST
const returnNewestPost = (url) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = () => {
if (xhr.status == 200) {
console.log('resolving');
resolve(JSON.parse(xhr.response));
} else {
reject(xhr.statusText);
}
};
xhr.onerror = () => {
reject(xhr.statusText);
};
xhr.send();
});
}
回答1:
The simplest answer to this question is to rewrite the script using Event Delegation.
Event delegation allows us to attach a single event listener, to a parent element, that will fire for all descendants matching a selector, whether those descendants exist now or are added in the future.
Compare the script from the OP and compare this one. The rewritten script has less code, less loops, less variables and is a lot easier to maintain and read through.
If you would like to compare specifics, event delegation starts on the line with if (displayPostWrapper && submitPostBtn) {
Re-written JS
const submitPostBtn = document.querySelector('#submit-post-button');
const submitPostID = document.querySelector('#submit-post-id');
const submitPostContent = document.querySelector('#submit-post-content');
const submitPostName = document.querySelector('#submit-post-name');
const displayPostWrapper = document.querySelector('.col-8.pt-4');
// GET REQUEST TO RETRIEVE EVERY POST
const get = (url) => {
return new Promise((resolve, reject) => {
const xhttp = new XMLHttpRequest();
xhttp.open('GET', url, true);
xhttp.onload = () => {
if (xhttp.status == 200) {
resolve(JSON.parse(xhttp.response));
} else {
reject(xhttp.statusText);
}
};
xhttp.onerror = () => {
reject(xhttp.statusText);
};
xhttp.send();
});
}
// DELETE SPECIFIC POST
const deletePostPromise = (url, postID) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = () => {
if (xhr.status == 200) {
console.log('if (xhr.status == 200)');
resolve();
} else {
reject(xhr.statusText);
}
};
xhr.onerror = () => {
reject(xhr.statusText);
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(postID);
});
}
// SUBMIT A NEW POST
const submitPost = (url, user_id, user_name, content) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onload = () => {
if (xhr.status == 200) {
console.log('resolving');
resolve();
} else {
reject(xhr.statusText);
}
};
xhr.onerror = () => {
reject(xhr.statusText);
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(user_id, user_name, content);
});
};
// RETURN THE NEWEST BLOG POST
const returnNewestPost = (url) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = () => {
if (xhr.status == 200) {
console.log('resolving');
resolve(JSON.parse(xhr.response));
} else {
reject(xhr.statusText);
}
};
xhr.onerror = () => {
reject(xhr.statusText);
};
xhr.send();
});
}
// MAKING THE CALL TO DELETE THE POST
if (displayPostWrapper && submitPostBtn) {
displayPostWrapper.addEventListener('click', e => {
if (e.target && e.target.nodeName == 'BUTTON') {
e.preventDefault();
const row = e.target.parentElement.parentElement.parentElement;
const form = e.target.parentElement;
const postID = e.target.parentElement.childNodes[3].value;
deletePostPromise('http://localhost/mouthblog/ajax/delete_post.ajax.php', `id=${postID}`);
row.remove();
} // if
}); // click event
submitPostBtn.addEventListener('click', e => {
e.preventDefault();
submitPost('http://localhost/mouthblog/ajax/submit_post.ajax.php',
`id=${submitPostID.value}&name=${submitPostName.value}&content=${submitPostContent.value}`)
.then(() => {
returnNewestPost('http://localhost/mouthblog/api/newest_post.php')
.then(data => {
console.log(data);
const newPost = document.createElement('div');
newPost.setAttribute('class', 'row');
newPost.innerHTML = `
<article class="col-10 offset-1">
<h2 class="h2">${data.user_name}</h2>
<small>${data.date_created}</small>
<form action="//localhost/mouthblog/blog.php" method="POST">
<button class="btn btn-danger" name="delete_post" type="submit">DELETE</button>
<input id="delete-post-id" name="post_id" type="hidden" value="${data.id}">
</form>
<hr>
<p class="lead">${data.content}</p>
</article>
`;
displayPostWrapper.prepend(newPost);
}) // then
}) // then
}); // click event
} // if
来源:https://stackoverflow.com/questions/48468289/javascript-prepend-element-to-dom-and-inherit-event-handler