我正在从服务器端生成一个标签:
'<select class="form-data api command" data-command-name="GetDifficultyData" data-events="change" name="id" data-data="" onchange="this.attributes[\'data-data\']=\'data=\' + this.value">'.$difficultyOptions.'</select>'
我正在使用PHP这样做,但这与此问题无关。
但我有一个function
会在我的event
代码中添加select
,因为它已被标记为处理command
:
function initializeCommands(root) {
if (!root) root = document;
if (!radWindow) {
radWindow = document.getElementById("radwindow");
var closeRadWindow = function(event) {
radWindow.addClass("hide-popup");
};
radWindow.addEventListener("click", closeRadWindow);
var popupActionContainer = radWindow.querySelector(".popup-action-container");
popupActionContainer.addEventListener("click", function(event) {
if (!event) {
event = window.event;
}
event.cancelBubble = true;
if (event.stopPropagation) {
event.stopPropagation();
}
return false;
});
popupActionContainer.addEventListener("keyup", function(e) {
if ((e.code == 'Enter') && (e.target.tagName.toLowerCase() !== "textarea")) {
var mainButton = this.querySelector("span.api[data-form-name]");
mainButton ? mainButton.click() : closeRadWindow();
}
});
document.getElementsByTagName("body")[0].addEventListener("keyup", function(e) {
if (e.code == 'Escape') {
closeRadWindow();
}
});
popupActionContainer.querySelector(".icon-close").addEventListener("click", closeRadWindow);
}
if (!popupContent) {
popupContent = radWindow.querySelector("#popup-content");
}
var commandButtons = root.querySelectorAll(".api:not(.initialized)");
for (var commandIndex = 0; commandIndex < commandButtons.length; commandIndex++) {
var eventsToHandle = commandButtons[commandIndex].attributes["data-events"].value.split(" ");
for (var eventIndex = 0; eventIndex < eventsToHandle.length; eventIndex++) {
commandButtons[commandIndex].addEventListener(eventsToHandle[eventIndex], function(event) {
if (this.hasClass("confirm")) {
if (!confirm(this.hasAttribute("data-confirm-message") ? this.attributes["data-confirm-message"].value : "Biztos ebben?")) {
return;
}
}
if (this.hasClass("template")) {
var data = this.attributes["data-data"];
sendRequest("POST", "/template/" + this.attributes["data-template-name"].value, templateCallback, true, (data ? data.value : data));
} else if (this.hasClass("form")) {
var container = document.querySelector(this.attributes["data-container"].value);
var items = container.querySelectorAll(".form-data");
var data = [];
var props = [];
for (var index = 0; index < items.length; index++) {
var v = items[index].value;
if (items[index].tagName.toLowerCase() === "input") {
if (items[index].type === "checkbox") {
v = items[index].checked;
}
}
data.push(items[index].name + "=" + v);
props.push(items[index].name);
}
var recaptchaResponse = container.querySelectorAll("[name=g-recaptcha-response]");
if (recaptchaResponse.length > 0) {
data.push("g-recaptcha-response=" + recaptchaResponse[0].value);
props.push("g-recaptcha-response");
}
var errorPlaces = container.querySelectorAll('.error-text:not(.invisible)');
for (var errorIndex = 0; errorIndex < errorPlaces.length; errorIndex++) {
errorPlaces[errorIndex].addClass("invisible");
}
var validationResults = validate(items);
if (validationResults.length) {
for (var errorIndex = 0; errorIndex < validationResults.length; errorIndex++) {
var errorPlace = container.querySelector('.error-text[data-key="' + validationResults[errorIndex].key + '"]');
if (errorPlace) {
errorPlace.removeClass("invisible").innerText = validationResults[errorIndex].value;
}
}
} else {
sendRequest("POST", "/form/" + this.attributes["data-form-name"].value, function() {
if (this.readyState === 4) {
var r = JSON.parse(this.responseText);
for (var key in r) {
if (r[key] && (props.indexOf(key) >= 0)) {
var errorPlace = container.querySelector('.error-text[data-key="' + key + '"]');
if (errorPlace) {
errorPlace.removeClass("invisible").innerText = r[key];
}
}
}
eval(r.response);
}
}, true, data.join("&"));
}
} else if (this.hasClass("command")) {
var data = this.attributes["data-data"];
sendRequest("POST", "/command/" + this.attributes["data-command-name"].value, function() {
if (this.readyState === 4) {
var r = JSON.parse(this.responseText);
if (r.callback) {
eval(r.callback);
}
}
}, true, ((data && data.value) ? data.value : data));
}
});
}
commandButtons[commandIndex].addClass("initialized");
}
}
此function
在页面加载时运行,并且在某些情况下通过API生成弹出窗口内容(我目前的情况是弹出窗口)。总结一下情况:
onchange
在服务器端定义,因此从标签生命周期的开始就有效change
执行顺序的重要性在于{<1}}应该在事件之后执行以后添加到标记中,因为事件将发送onchange
选择标签到服务器(因此它将询问有关要编辑的实体的数据),发送当前id
非常重要。我当前的测试运行良好,id
正在onchange
事件之前执行,后来被添加到标记中,一切都很好。但是,我担心某些浏览器或未来版本可能不会以同样的方式发生。在这种情况下,我需要做一些进一步的工作,以保证首先执行change
,然后才执行onchange
事件。无论如何我可以做这项工作,但我想保持代码优雅,避免编写不必要的代码。所以,问题是:在我的情况下是否保证change
将首先执行,然后onchange
事件后来被定义为change
事件?
修改
我们可以使用此原型控制事件的顺序:
onchange
例如,如果我们访问https://stackoverflow.com/
并在控制台中运行原型代码并将此原型用作:
function CustomEventHandler() {
let events = new Map();
this.add = (tags, eventKeys, eventFunctions) => {
let isNew = false;
for (let tag of tags) {
if (!events.has(tag)) events.set(tag, new Map());
let outerItem = events.get(tag);
for (let eventKey of eventKeys) {
if (isNew = !outerItem.has(eventKey)) outerItem.set(eventKey, []);
let innerItem = outerItem.get(eventKey);
for (let eventFunction of eventFunctions) innerItem.push(eventFunction);
if (isNew) {
tag.addEventListener(eventKey, () => {this.execute(tag, eventKey)});
}
}
}
return this;
};
this.execute = (tag, eventKey) => {
if (events.has(tag)) {
let outerItem = events.get(tag);
if (outerItem.has(eventKey)) {
let innerItem = outerItem.get(eventKey);
for (let func of innerItem) func(tag, eventKey);
}
}
};
this.get = () => {return events};
}
然后我们可以看到事件以正确的顺序正确执行。我们可以添加自定义功能,例如向函数添加标识符并能够通过标识符删除函数,或者交换它们的顺序,但为了简单起见,我省略了这些功能的实现。在我们的例子中,全局范围用于简化。
答案 0 :(得分:5)
DOM Level 3(HTML5附带)现在按照附加顺序对事件进行排序。
以下是对规范的引用:
https://www.w3.org/TR/DOM-Level-3-Events/Overview.html#changes-DOMEvents2to3Changes-flow
<强> 11.1.1。对DOM Level 2事件流的更改
这个新规范引入了以下新概念 事件流程:
- 现在订购了事件监听器。在DOM Level 2中,事件排序 没有具体说明。
答案 1 :(得分:2)
您的问题可以归结为:“通过HTML属性关联的事件顺序与通过DOM对象属性关联的同一事件随着时间的推移会保持一致吗?” 。
答案是最有可能的,但不能保证。如果需要确定性,请不要使用任何一种技术,请使用DOM .addEventListener()
方法进行事件注册。就目前正在排序的事件而言,这是另一个答案实际上指的是什么。当你注册事件.addEventListener()
,对于特定事件,你可以放心的是,回调将在他们注册的顺序被调用。它不适用于HTML属性或DOM对象属性关联的事件。
答案 2 :(得分:1)
是的,事件处理程序的顺序也得到了保证,但不是很直接。
规范have a note指出:
仅当将事件处理程序的值设置为非null且尚未激活事件处理程序时,才进行事件侦听器注册。由于侦听器是按照注册时的顺序调用的,因此假设没有发生停用,特定事件类型的事件侦听器的顺序将始终为:
在事件处理程序的值首次设置为非null之前,向addEventListener()注册的事件侦听器
然后设置当前设置的回调(如果有)
,最后在第一次将事件处理程序的值设置为非null之后,向addEventListener()注册了事件侦听器。
这非常简洁,这意味着只有事件处理程序IDL(或属性解析)的第一个设置才能确定其顺序。后续的任何设置都将与第一次设置时具有相同的顺序,除非在两次设置之间将其禁用...
btn.addEventListener('click', e=>console.log('1'));
btn.onclick = e => console.log('2');
btn.addEventListener('click', e=>console.log('3'));
console.log('###test 1###');
btn.click(); //1, 2, 3
btn.addEventListener('click', e=>console.log('4'));
btn.onclick = e => console.log('should be 5 oO');
btn.addEventListener('click', e=>console.log('6'));
console.log('###test 2###');
btn.click(); //1, "should be 5 oO", 3, 4, 6
btn.addEventListener('click', e=>console.log('7'));
btn.onclick = null; // deactivate
btn.onclick = e => console.log('8 oO');
btn.addEventListener('click', e=>console.log('9'));
console.log('###test 4###');
btn.click(); //1, 3, 4, 6, 7, "8 oO", 9
<button id="btn">click me</button>
但是请注意,这只是规范的立足点,我们知道并非所有实现都始终遵循规范。