Как клонировать или повторно отправить события DOM?
Я ищу простой и абстрактный способ клонирования или повторной отправки только событий DOM. Я не заинтересован в клонировании DOM-узлов.
Я немного поэкспериментировал, прочитал спецификацию DOM Events и не нашел четкого ответа.
В идеале я ищу что-то такое простое, как:
handler = function(e){
document.getElementById("decoy").dispatchEvent(e)
}
document.getElementById("source").addEventListener("click", handler)
Этот пример кода, конечно, не работает. Есть исключение DOM, заявляющее, что событие в данный момент отправляется - очевидно.
Я бы хотел избежать ручного создания новых событий с document.createEvent()
, инициализируя их и отправляя их.
Есть ли простое решение для этого варианта использования?
3 ответа
Я знаю, вопрос старый, и ОП хотел избежать создания / инициализации подхода, но есть относительно простой способ дублировать события:
new_event = new MouseEvent(old_event.type, old_event)
Если вы хотите больше, чем просто события мыши, вы можете сделать что-то вроде этого:
new_event = new old_event.constructor(old_event.type, old_event)
И в оригинальном контексте:
handler = function(e) {
new_e = new e.constructor(e.type, e);
document.getElementById("decoy").dispatchEvent(new_e);
}
document.getElementById("source").addEventListener("click", handler);
(Для пользователей jQuery: вам может понадобиться e.originalEvent.constructor
вместо e.constructor
)
A Fix For Internet Explorer
Alexis posts a nice solution, but his solution will not work in Internet Explorer. The below solution will. Unfortunately, there is no system as consistent as event constructors in Internet Explorer, so the code bloat below is necessary.
var allModifiers = ["Alt","AltGraph","CapsLock","Control",
"Meta","NumLock","Scroll","Shift","Win"];
function redispatchEvent(original, newTargetId) {
if (typeof Event === "function") {
var eventCopy = new original.constructor(original.type, original);
} else {
// Internet Explorer
var eventType = original.constructor.name;
var eventCopy = document.createEvent(eventType);
if (original.getModifierState)
var modifiersList = allModifiers.filter(
original.getModifierState,
original
).join(" ");
if (eventType === "MouseEvent") original.initMouseEvent(
original.type, original.bubbles, original.cancelable,
original.view, original.detail, original.screenX, original.screenY,
original.clientX, original.clientY, original.ctrlKey,
original.altKey, original.shiftKey, original.metaKey,
original.button, original.relatedTarget
);
if (eventType === "DragEvent") original.initDragEvent(
original.type, original.bubbles, original.cancelable,
original.view, original.detail, original.screenX, original.screenY,
original.clientX, original.clientY, original.ctrlKey,
original.altKey, original.shiftKey, original.metaKey,
original.button, original.relatedTarget, original.dataTransfer
);
if (eventType === "WheelEvent") original.initWheelEvent(
original.detail, original.screenX, original.screenY,
original.clientX, original.clientY, original.button,
original.relatedTarget, modifiersList,
original.deltaX, original.deltaY, original.deltaZ, original.deltaMode
);
if (eventType === "PointerEvent") original.initPointerEvent(
original.type, original.bubbles, original.cancelable,
original.view, original.detail, original.screenX, original.screenY,
original.clientX, original.clientY, original.ctrlKey,
original.altKey, original.shiftKey, original.metaKey,
original.button, original.relatedTarget,
original.offsetX, original.offsetY, original.width, original.height,
original.pressure, original.rotation,
original.tiltX, original.tiltY,
original.pointerId, original.pointerType,
original.timeStamp, original.isPrimary
);
if (eventType === "TouchEvent") original.initTouchEvent(
original.type, original.bubbles, original.cancelable,
original.view, original.detail, original.screenX, original.screenY,
original.clientX, original.clientY, original.ctrlKey,
original.altKey, original.shiftKey, original.metaKey,
original.touches, original.targetTouches, original.changedTouches,
original.scale, original.rotation
);
if (eventType === "TextEvent") original.initTextEvent(
original.type, original.bubbles, original.cancelable,
original.view,
original.data, original.inputMethod, original.locale
);
if (eventType === "CompositionEvent") original.initTextEvent(
original.type, original.bubbles, original.cancelable,
original.view,
original.data, original.inputMethod, original.locale
);
if (eventType === "KeyboardEvent") original.initKeyboardEvent(
original.type, original.bubbles, original.cancelable,
original.view, original.char, original.key,
original.location, modifiersList, original.repeat
);
if (eventType === "InputEvent" || eventType === "UIEvent")
original.initUIEvent(
original.type, original.bubbles, original.cancelable,
original.view, original.detail
);
if (eventType === "FocusEvent") original.initFocusEvent(
original.type, original.bubbles, original.cancelable,
original.view, original.detail, original.relatedTarget
);
}
document.getElementById(newTargetId).dispatchEvent(eventCopy);
if (eventCopy.defaultPrevented) newTargetId.preventDefault();
}
<button onclick="redispatchEvent(arguments[0], '2nd')">Click Here</button>
<button id="2nd" onclick="console.log('Alternate clicked!')">Alternate Button</button>
A More General Solution
Depending on your needs, a much better solution than redispatching the original event might be synthetic event propagation. We create special ways to register event listeners that also expose these listeners to our code so that we can call them manually. Indeed, there is a getEventListeners
function that can be used to retrieve current event listeners. However, getEventListeners
is only supported by Chrome/Safari. Thus, I designed the following replacement. Although the code below looks way too big, the code below is mostly variable names, so it will be very small after minification.
/**@type{WeakMap}*/ var registeredListeners = new WeakMap();
hearEvent(document.getElementById("1st"), "click", function propagate(evt) {
fireEvent(document.getElementById("2nd"), evt, propagate);
});
hearEvent(document.getElementById("2nd"), "click", function(evt) {
console.log( evt.target.textContent );
});
/**
* @param{Element} target
* @param{string} name
* @param{function(Event=):(boolean|undefined)} handle
* @param{(Object<string,boolean>|boolean)=} options
* @return {undefined}
*/
function hearEvent(target, name, handle, options) {
target.addEventListener(name, handle, options);
var curArr = registeredListeners.get(target);
if (!curArr) registeredListeners.set(target, (curArr = []));
curArr.push([
"" + name,
handle,
typeof options=="object" ? !!options.capture : !!options,
target
]);
}
/**
* @param{Element} target
* @param{string} name
* @param{function(Event=):(boolean|undefined)} handle
* @param{(Object<string,boolean>|boolean)=} options
* @return {undefined}
*/
function muteEvent(target, name, handle, options) {
name += "";
target.removeEventListener(name, handle, options);
var capturing = typeof options=="object"?!!options.capture:!!options;
var curArr = registeredListeners.get(target);
if (curArr)
for (var i=(curArr.length|0)-1|0; i>=0; i=i-1|0)
if (curArr[i][0] === name && curArr[i][2] === capturing)
curArr.splice(i, 1);
if (!curArr.length) registeredListeners.delete(target);
}
/**
* @param{Element} target
* @param{Event} eventObject
* @param{Element=} caller
* @return {undefined}
*/
function fireEvent(target, eventObject, caller) {
var deffered = [], name = eventObject.type, curArr, listener;
var immediateStop = false, keepGoing = true, lastTarget;
var currentTarget = target, doesBubble = !!eventObject.bubbles;
var trueObject = Object.setPrototypeOf({
stopImmediatePropagation: function(){immediateStop = true},
stopPropagation: function(){keepGoing = false},
get target() {return target},
get currentTarget() {return currentTarget}
}, eventObject);
do {
if (curArr = registeredListeners.get(currentTarget))
for (var i=0; i<(curArr.length|0) && !immediateStop; i=i+1|0)
if (curArr[i][0] === name && curArr[i][1] !== caller) {
listener = curArr[i];
if (listener[2]) {
listener[1].call(trueObject, trueObject);
} else if (doesBubble || currentTarget === target) {
deffered.push( listener );
}
}
if (target.nodeType === 13) {
// for the ShadowDOMv2
deffered.push([ target ]);
currentTarget = target = currentTarget.host;
}
} while (keepGoing && (currentTarget = currentTarget.parentNode));
while (
(listener = deffered.pop()) &&
!immediateStop &&
(lastTarget === listener[3] || keepGoing)
)
if (listener.length === 1) {
// for the ShadowDOMv2
target = listener[0];
} else {
lastTarget = currentTarget = listener[3];
listener[1].call(trueObject, trueObject);
}
}
<button id="1st">Click Here</button>
<button id="2nd">Alternate Button</button>
Observe that, after minification, all this code fits neatly into a single kilobyte (prior to gzip).
var k=new WeakMap;m(document.getElementById("1st"),"click",function q(a){r(document.getElementById("2nd"),a,q)});m(document.getElementById("2nd"),"click",function(a){console.log(a.target.textContent)});function m(a,c,f,b){a.addEventListener(c,f,b);var d=k.get(a);d||k.set(a,d=[]);d.push([""+c,f,"object"==typeof b?!!b.capture:!!b,a])}
function r(a,c,f){var b=[],d=c.type,n=!1,p=!0,g=a,t=!!c.bubbles,l=Object.setPrototypeOf({stopImmediatePropagation:function(){n=!0},stopPropagation:function(){p=!1},get target(){return a},get currentTarget(){return g}},c);do{if(c=k.get(g))for(var h=0;h<(c.length|0)&&!n;h=h+1|0)if(c[h][0]===d&&c[h][1]!==f){var e=c[h];e[2]?e[1].call(l,l):(t||g===a)&&b.push(e)}13===a.nodeType&&(b.push([a]),g=a=g.host)}while(p&&(g=g.parentNode));for(;(e=b.pop())&&!n&&(u===e[3]||p);)if(1===e.length)a=e[0];else{var u=g=
e[3];e[1].call(l,l)}}function z(a,c,f,b){c+="";a.removeEventListener(c,f,b);f="object"==typeof b?!!b.capture:!!b;if(b=k.get(a))for(var d=(b.length|0)-1|0;0<=d;d=d-1|0)b[d][0]===c&&b[d][2]===f&&b.splice(d,1);b.length||k.delete(a)}
<button id="1st">Click Here</button>
<button id="2nd">Alternate Button</button>
Немного проще дублировать объект, который вы собираетесь сохранить, и обрабатывать только один класс событий:
В разделе «Дополнительный JS» демонстрируется клонирование объекта. Вместо копирования событий вы можете просто запускать события клонированного узла через прокси.