Javascript:替换元素时事件属性丢失
Javascript: Event properties get lost on replacing an element
我正在尝试使用 vanilla js 创建一个 todoList 应用程序。到目前为止,我已经完成了标记、设计并添加了其他功能,例如将提交的任务添加到 ui、删除任务、将任务标记为已完成。
现在我坚持添加编辑功能。(当然还有其他事情要做,比如验证、实现 localStorage、添加提醒、使其响应等。)
要实现的目标:
- 用户应该可以点击编辑图标,它应该会变成保存图标,
- 点击编辑图标后,用户应该可以实时编辑文本
- 最后点击保存图标后,文本应该不再是可编辑的,图标应该变回编辑图标
一些东西我试过:
单击编辑图标后,
- 更改了
contentEditable=true
文本,因此我们可以实时编辑它(任务文本)。
- 将
editBtn
替换为 saveBtn
(新创建的元素)
但是在替换元素后我找不到恢复它的方法。当我尝试使用 eventTarget(我用来存储事件的目标 属性 的变量)访问它时,我没有得到任何东西。我也尝试用 document.querySelector('.save')
抓取它,但这只能按照文档流程工作。(我的意思是如果我们点击第二个按钮,在 dom 中第一个按钮会改变)
现在我想要 将 saveBtn 替换回 editBtn 和 将 contentEditable 改回 false 或 inherit
这是处理 ui 事件的函数:
static taskEvents(eventTarget) {
const targetClassName = eventTarget.classList
if(targetClassName.contains('complete')) {
targetClassName.toggle('statusIcon');
eventTarget.parentElement.nextElementSibling.classList.toggle('task');
}
else if(targetClassName.contains('edit')) {
// let textEditableStatus = eventTarget.parentElement.parentElement.previousElementSibling;
// textEditableStatus.contentEditable=true;
// const editBtn = eventTarget.parentElement
// const saveBtn = document.createElement('a');
// saveBtn.className = "btn";
// saveBtn.id = "saveBtn";
// saveBtn.innerHTML = '<i class="fas fa-save save"></i>';
// editBtn.replaceWith(saveBtn)
}
else if(targetClassName.contains('delete')) {
eventTarget.parentElement.parentElement.parentElement.remove();
}
}
完整代码:
class UI {
// dummy data; for now.
static displayTasks() {
const tasks = ['Take out trash', 'Do laundry', 'Visit part'];
tasks.forEach(task => UI.addTask(task));
}
static addTask(task) {
const tbody = document.querySelector('#tasks');
const taskRow = document.createElement('tr');
taskRow.className += 'task';
taskRow.innerHTML = `
<td><i class="far fa-check-circle complete"></i></td>
<td>${task}</td>
<td><a href="#" class="btn" id="editBtn"><i class="fas fa-edit edit"></i></a></td>
<td><a href="#" class="btn" id="deleteBtn"><i class="fas fa-trash delete"></i></a></td>
`;
tbody.appendChild(taskRow);
document.querySelector('#todoInput').value = '';
}
static taskEvents(eventTarget) {
const targetClassName = eventTarget.classList
if (targetClassName.contains('complete')) {
targetClassName.toggle('statusIcon');
eventTarget.parentElement.nextElementSibling.classList.toggle('task');
} else if (targetClassName.contains('edit')) {
// let textEditableStatus = eventTarget.parentElement.parentElement.previousElementSibling;
// textEditableStatus.contentEditable=true;
// const editBtn = eventTarget.parentElement
// const saveBtn = document.createElement('a');
// saveBtn.className = "btn";
// saveBtn.id = "saveBtn";
// saveBtn.innerHTML = '<i class="fas fa-save save"></i>';
// editBtn.replaceWith(saveBtn)
} else if (targetClassName.contains('delete')) {
eventTarget.parentElement.parentElement.parentElement.remove();
}
}
}
// Ui events
document.addEventListener('DOMContentLoaded', UI.displayTasks);
const tbody = document.querySelector('#tasks');
tbody.addEventListener('click', event => {
UI.taskEvents(event.target);
})
.statusIcon {
font-weight: bold;
color: rgb(48, 158, 81);
}
td.task {
opacity: .6;
text-decoration: line-through;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo List App</title>
<!-- Custom css -->
<link rel="stylesheet" href="style.css">
<!-- fontawesome script-->
<script src="https://kit.fontawesome.com/39350fd9df.js"></script>
</head>
<body>
<div class="main-container">
<div class="container">
<div class="input-group">
<input type="text" id="todoInput" placeholder="Enter new task...">
<a href="#" class="btn addBtn"><i class="fas fa-plus"></i></a>
</div>
<table class="taskLister">
<thead>
<tr>
<th>Status</th>
<th>Task</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody id="tasks"></tbody>
</table>
</div>
</div>
<!-- Custom script -->
<script src="app.js"></script>
</body>
</html>
A quick 注意:我用的是纯javascript
使用innerHTML
重写元素内容会破坏其上定义的处理程序。如果您将 event delegation, this problem will not occur, because the delegated handler uses the actual elements to determine which action is needed. Here's a minimal reproducable example 用于具有一个条目的 table。
Here's a rewritten version of your jsFiddle, and here's a (somewhat extended) stackblitz version of it.
document.addEventListener("click", handle);
function handle(evt) {
const origin = evt.target;
if (origin.dataset.edit) {
const entryEdit = origin.closest("tr").querySelector("td:nth-child(2)");
entryEdit.contentEditable = true;
return entryEdit.focus();
}
if (origin.dataset.save) {
const entry = origin.closest("tr");
const value = entry.querySelector("td:nth-child(2)")
if (value.contentEditable === "true") {
value.contentEditable = false;
return entry.querySelector("td:nth-child(5)").textContent = "saved!";
};
return entry.querySelector("td:nth-child(5)").textContent = "nothing to do";
}
if (origin.dataset.setstatus) {
const row = origin.closest("tr");
const nwStatus = origin.dataset.setstatus === "Todo" ? "Done" : "Todo";
row.dataset.status = nwStatus;
origin.dataset.setstatus = nwStatus;
row.querySelectorAll("td > button")
.forEach(btn =>
btn[nwStatus === "Done"
? 'setAttribute'
: 'removeAttribute']("disabled", true));
return row.querySelector("td:nth-child(5)").textContent = "";
}
}
body {
margin: 2rem;
font: 12px/15px normal verdana, arial;
}
th {
background: black;
color: white;
text-align: left;
padding: 2px;
}
td {
padding: 2px;
}
th:nth-child(5) {
min-width: 75px;
}
th:nth-child(2) {
min-width: 200px;
}
td[data-setstatus]:after {
content: attr(data-setstatus);
}
tr[data-status='Done'] td:nth-child(2) {
text-decoration: line-through;
}
<table>
<thead>
<tr>
<th>Status</th>
<th>Task</th>
<th>edit</th>
<th>save</th>
<th>result</th>
</tr>
</thead>
<tbody>
<tr data-status="Todo">
<td data-setstatus="Todo"></td>
<td>Hi, I am a task</td>
<td><button data-edit="1">edit</button></td>
<td><button data-save="1">save</button></td>
<td></td>
</tr>
</tbody>
</table>
我正在尝试使用 vanilla js 创建一个 todoList 应用程序。到目前为止,我已经完成了标记、设计并添加了其他功能,例如将提交的任务添加到 ui、删除任务、将任务标记为已完成。
现在我坚持添加编辑功能。(当然还有其他事情要做,比如验证、实现 localStorage、添加提醒、使其响应等。)
要实现的目标:
- 用户应该可以点击编辑图标,它应该会变成保存图标,
- 点击编辑图标后,用户应该可以实时编辑文本
- 最后点击保存图标后,文本应该不再是可编辑的,图标应该变回编辑图标
一些东西我试过: 单击编辑图标后,
- 更改了
contentEditable=true
文本,因此我们可以实时编辑它(任务文本)。 - 将
editBtn
替换为saveBtn
(新创建的元素)
但是在替换元素后我找不到恢复它的方法。当我尝试使用 eventTarget(我用来存储事件的目标 属性 的变量)访问它时,我没有得到任何东西。我也尝试用 document.querySelector('.save')
抓取它,但这只能按照文档流程工作。(我的意思是如果我们点击第二个按钮,在 dom 中第一个按钮会改变)
现在我想要 将 saveBtn 替换回 editBtn 和 将 contentEditable 改回 false 或 inherit
这是处理 ui 事件的函数:
static taskEvents(eventTarget) {
const targetClassName = eventTarget.classList
if(targetClassName.contains('complete')) {
targetClassName.toggle('statusIcon');
eventTarget.parentElement.nextElementSibling.classList.toggle('task');
}
else if(targetClassName.contains('edit')) {
// let textEditableStatus = eventTarget.parentElement.parentElement.previousElementSibling;
// textEditableStatus.contentEditable=true;
// const editBtn = eventTarget.parentElement
// const saveBtn = document.createElement('a');
// saveBtn.className = "btn";
// saveBtn.id = "saveBtn";
// saveBtn.innerHTML = '<i class="fas fa-save save"></i>';
// editBtn.replaceWith(saveBtn)
}
else if(targetClassName.contains('delete')) {
eventTarget.parentElement.parentElement.parentElement.remove();
}
}
完整代码:
class UI {
// dummy data; for now.
static displayTasks() {
const tasks = ['Take out trash', 'Do laundry', 'Visit part'];
tasks.forEach(task => UI.addTask(task));
}
static addTask(task) {
const tbody = document.querySelector('#tasks');
const taskRow = document.createElement('tr');
taskRow.className += 'task';
taskRow.innerHTML = `
<td><i class="far fa-check-circle complete"></i></td>
<td>${task}</td>
<td><a href="#" class="btn" id="editBtn"><i class="fas fa-edit edit"></i></a></td>
<td><a href="#" class="btn" id="deleteBtn"><i class="fas fa-trash delete"></i></a></td>
`;
tbody.appendChild(taskRow);
document.querySelector('#todoInput').value = '';
}
static taskEvents(eventTarget) {
const targetClassName = eventTarget.classList
if (targetClassName.contains('complete')) {
targetClassName.toggle('statusIcon');
eventTarget.parentElement.nextElementSibling.classList.toggle('task');
} else if (targetClassName.contains('edit')) {
// let textEditableStatus = eventTarget.parentElement.parentElement.previousElementSibling;
// textEditableStatus.contentEditable=true;
// const editBtn = eventTarget.parentElement
// const saveBtn = document.createElement('a');
// saveBtn.className = "btn";
// saveBtn.id = "saveBtn";
// saveBtn.innerHTML = '<i class="fas fa-save save"></i>';
// editBtn.replaceWith(saveBtn)
} else if (targetClassName.contains('delete')) {
eventTarget.parentElement.parentElement.parentElement.remove();
}
}
}
// Ui events
document.addEventListener('DOMContentLoaded', UI.displayTasks);
const tbody = document.querySelector('#tasks');
tbody.addEventListener('click', event => {
UI.taskEvents(event.target);
})
.statusIcon {
font-weight: bold;
color: rgb(48, 158, 81);
}
td.task {
opacity: .6;
text-decoration: line-through;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo List App</title>
<!-- Custom css -->
<link rel="stylesheet" href="style.css">
<!-- fontawesome script-->
<script src="https://kit.fontawesome.com/39350fd9df.js"></script>
</head>
<body>
<div class="main-container">
<div class="container">
<div class="input-group">
<input type="text" id="todoInput" placeholder="Enter new task...">
<a href="#" class="btn addBtn"><i class="fas fa-plus"></i></a>
</div>
<table class="taskLister">
<thead>
<tr>
<th>Status</th>
<th>Task</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody id="tasks"></tbody>
</table>
</div>
</div>
<!-- Custom script -->
<script src="app.js"></script>
</body>
</html>
A quick 注意:我用的是纯javascript
使用innerHTML
重写元素内容会破坏其上定义的处理程序。如果您将 event delegation, this problem will not occur, because the delegated handler uses the actual elements to determine which action is needed. Here's a minimal reproducable example 用于具有一个条目的 table。
Here's a rewritten version of your jsFiddle, and here's a (somewhat extended) stackblitz version of it.
document.addEventListener("click", handle);
function handle(evt) {
const origin = evt.target;
if (origin.dataset.edit) {
const entryEdit = origin.closest("tr").querySelector("td:nth-child(2)");
entryEdit.contentEditable = true;
return entryEdit.focus();
}
if (origin.dataset.save) {
const entry = origin.closest("tr");
const value = entry.querySelector("td:nth-child(2)")
if (value.contentEditable === "true") {
value.contentEditable = false;
return entry.querySelector("td:nth-child(5)").textContent = "saved!";
};
return entry.querySelector("td:nth-child(5)").textContent = "nothing to do";
}
if (origin.dataset.setstatus) {
const row = origin.closest("tr");
const nwStatus = origin.dataset.setstatus === "Todo" ? "Done" : "Todo";
row.dataset.status = nwStatus;
origin.dataset.setstatus = nwStatus;
row.querySelectorAll("td > button")
.forEach(btn =>
btn[nwStatus === "Done"
? 'setAttribute'
: 'removeAttribute']("disabled", true));
return row.querySelector("td:nth-child(5)").textContent = "";
}
}
body {
margin: 2rem;
font: 12px/15px normal verdana, arial;
}
th {
background: black;
color: white;
text-align: left;
padding: 2px;
}
td {
padding: 2px;
}
th:nth-child(5) {
min-width: 75px;
}
th:nth-child(2) {
min-width: 200px;
}
td[data-setstatus]:after {
content: attr(data-setstatus);
}
tr[data-status='Done'] td:nth-child(2) {
text-decoration: line-through;
}
<table>
<thead>
<tr>
<th>Status</th>
<th>Task</th>
<th>edit</th>
<th>save</th>
<th>result</th>
</tr>
</thead>
<tbody>
<tr data-status="Todo">
<td data-setstatus="Todo"></td>
<td>Hi, I am a task</td>
<td><button data-edit="1">edit</button></td>
<td><button data-save="1">save</button></td>
<td></td>
</tr>
</tbody>
</table>