Skip to content

Commit 2eae3a9

Browse files
committed
Add event propagation control. Bump version.
1 parent e6fb1ac commit 2eae3a9

File tree

6 files changed

+154
-43
lines changed

6 files changed

+154
-43
lines changed

NonBlock.es5.js

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@
5555
return el.classList.contains('nonblock');
5656
}
5757

58+
function isNotPropagating(el) {
59+
return el.classList.contains('nonblock-stoppropagation');
60+
}
61+
5862
function getCursor(el) {
5963
var style = window.getComputedStyle(el);
6064
return style.getPropertyValue('cursor');
@@ -76,15 +80,19 @@
7680
document.body.addEventListener('mouseenter', function (ev) {
7781
if (isNonBlocking(ev.target)) {
7882
nonBlockLastElem = ev.target;
79-
ev.stopPropagation();
83+
if (isNotPropagating(ev.target)) {
84+
ev.stopPropagation();
85+
}
8086
}
8187
}, true);
8288
document.body.addEventListener('mouseleave', function (ev) {
8389
if (isNonBlocking(ev.target)) {
8490
remCursor(ev.target);
8591
nonBlockLastElem = null;
8692
isSelectingText = false;
87-
ev.stopPropagation();
93+
if (isNotPropagating(ev.target)) {
94+
ev.stopPropagation();
95+
}
8896
}
8997
}, true);
9098
document.body.addEventListener('mousemove', function (ev) {
@@ -96,15 +104,19 @@
96104
window.getSelection().removeAllRanges();
97105
isSelectingText = true;
98106
}
99-
ev.stopPropagation();
107+
if (isNotPropagating(ev.target)) {
108+
ev.stopPropagation();
109+
}
100110
}
101111
}, true);
102112
document.body.addEventListener('mousedown', function (ev) {
103113
if (isNonBlocking(ev.target)) {
104114
ev.preventDefault();
105115
nonblockPass(ev.target, ev, 'onmousedown');
106116
isSelectingText = null;
107-
ev.stopPropagation();
117+
if (isNotPropagating(ev.target)) {
118+
ev.stopPropagation();
119+
}
108120
}
109121
}, true);
110122
document.body.addEventListener('mouseup', function (ev) {
@@ -115,24 +127,30 @@
115127
window.getSelection().removeAllRanges();
116128
}
117129
isSelectingText = false;
118-
ev.stopPropagation();
130+
if (isNotPropagating(ev.target)) {
131+
ev.stopPropagation();
132+
}
119133
}
120134
}, true);
121135
document.body.addEventListener('click', function (ev) {
122136
if (isNonBlocking(ev.target)) {
123137
nonblockPass(ev.target, ev, 'onclick');
124-
ev.stopPropagation();
138+
if (isNotPropagating(ev.target)) {
139+
ev.stopPropagation();
140+
}
125141
}
126142
}, true);
127143
document.body.addEventListener('dblclick', function (ev) {
128144
if (isNonBlocking(ev.target)) {
129145
nonblockPass(ev.target, ev, 'ondblclick');
130-
ev.stopPropagation();
146+
if (isNotPropagating(ev.target)) {
147+
ev.stopPropagation();
148+
}
131149
}
132150
}, true);
133151

134152
// Fire a DOM event.
135-
var domEvent = function domEvent(elem, event, origEvent) {
153+
var domEvent = function domEvent(elem, event, origEvent, bubbles) {
136154
var eventObject = void 0;
137155
event = event.toLowerCase();
138156
if (document.createEvent && elem.dispatchEvent) {
@@ -143,13 +161,13 @@
143161
// probably a much better way to do it.
144162
elem.getBoundingClientRect();
145163
eventObject = document.createEvent("MouseEvents");
146-
eventObject.initMouseEvent(event, origEvent.bubbles, origEvent.cancelable, origEvent.view, origEvent.detail, origEvent.screenX, origEvent.screenY, origEvent.clientX, origEvent.clientY, origEvent.ctrlKey, origEvent.altKey, origEvent.shiftKey, origEvent.metaKey, origEvent.button, origEvent.relatedTarget);
164+
eventObject.initMouseEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable, origEvent.view, origEvent.detail, origEvent.screenX, origEvent.screenY, origEvent.clientX, origEvent.clientY, origEvent.ctrlKey, origEvent.altKey, origEvent.shiftKey, origEvent.metaKey, origEvent.button, origEvent.relatedTarget);
147165
} else if (event.match(regexUiEvents)) {
148166
eventObject = document.createEvent("UIEvents");
149-
eventObject.initUIEvent(event, origEvent.bubbles, origEvent.cancelable, origEvent.view, origEvent.detail);
167+
eventObject.initUIEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable, origEvent.view, origEvent.detail);
150168
} else if (event.match(regexHtmlEvents)) {
151169
eventObject = document.createEvent("HTMLEvents");
152-
eventObject.initEvent(event, origEvent.bubbles, origEvent.cancelable);
170+
eventObject.initEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable);
153171
}
154172
if (!eventObject) {
155173
return;
@@ -223,11 +241,17 @@
223241
if (!nonBlockLastElem || nonBlockLastElem !== elBelow) {
224242
if (nonBlockLastElem) {
225243
var lastElem = nonBlockLastElem;
226-
domEvent(lastElem, 'mouseleave', event);
227-
domEvent(lastElem, 'mouseout', event);
244+
if (!lastElem.contains(elBelow)) {
245+
domEvent(lastElem, 'mouseleave', event, false);
246+
}
247+
domEvent(lastElem, 'mouseout', event, true);
248+
if (!elBelow.contains(lastElem)) {
249+
domEvent(elBelow, 'mouseenter', event, false);
250+
}
251+
} else if (!elBelow.contains(elem)) {
252+
domEvent(elBelow, 'mouseenter', event, false);
228253
}
229-
domEvent(elBelow, 'mouseenter', event);
230-
domEvent(elBelow, 'mouseover', event);
254+
domEvent(elBelow, 'mouseover', event, true);
231255
}
232256
domEvent(elBelow, eventName, event);
233257
// Remember the latest element the mouse was over.

NonBlock.js

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@
8080
return el.classList.contains('nonblock');
8181
}
8282

83+
function isNotPropagating(el) {
84+
return el.classList.contains('nonblock-stoppropagation');
85+
}
86+
8387
function getCursor(el) {
8488
const style = window.getComputedStyle(el);
8589
return style.getPropertyValue('cursor');
@@ -101,14 +105,28 @@
101105
document.body.addEventListener('mouseenter', (ev) => {
102106
if (isNonBlocking(ev.target)) {
103107
nonBlockLastElem = ev.target;
104-
ev.stopPropagation();
108+
if (isNotPropagating(ev.target)) {
109+
ev.stopPropagation();
110+
}
105111
}
106112
}, true);
107113
document.body.addEventListener('mouseleave', (ev) => {
108114
if (isNonBlocking(ev.target)) {
109115
remCursor(ev.target);
110116
nonBlockLastElem = null;
111117
isSelectingText = false;
118+
if (isNotPropagating(ev.target)) {
119+
ev.stopPropagation();
120+
}
121+
}
122+
}, true);
123+
document.body.addEventListener('mouseover', (ev) => {
124+
if (isNonBlocking(ev.target) && isNotPropagating(ev.target)) {
125+
ev.stopPropagation();
126+
}
127+
}, true);
128+
document.body.addEventListener('mouseout', (ev) => {
129+
if (isNonBlocking(ev.target) && isNotPropagating(ev.target)) {
112130
ev.stopPropagation();
113131
}
114132
}, true);
@@ -121,15 +139,19 @@
121139
window.getSelection().removeAllRanges();
122140
isSelectingText = true;
123141
}
124-
ev.stopPropagation();
142+
if (isNotPropagating(ev.target)) {
143+
ev.stopPropagation();
144+
}
125145
}
126146
}, true);
127147
document.body.addEventListener('mousedown', (ev) => {
128148
if (isNonBlocking(ev.target)) {
129149
ev.preventDefault();
130150
nonblockPass(ev.target, ev, 'onmousedown');
131151
isSelectingText = null;
132-
ev.stopPropagation();
152+
if (isNotPropagating(ev.target)) {
153+
ev.stopPropagation();
154+
}
133155
}
134156
}, true);
135157
document.body.addEventListener('mouseup', (ev) => {
@@ -140,24 +162,30 @@
140162
window.getSelection().removeAllRanges();
141163
}
142164
isSelectingText = false;
143-
ev.stopPropagation();
165+
if (isNotPropagating(ev.target)) {
166+
ev.stopPropagation();
167+
}
144168
}
145169
}, true);
146170
document.body.addEventListener('click', (ev) => {
147171
if (isNonBlocking(ev.target)) {
148172
nonblockPass(ev.target, ev, 'onclick');
149-
ev.stopPropagation();
173+
if (isNotPropagating(ev.target)) {
174+
ev.stopPropagation();
175+
}
150176
}
151177
}, true);
152178
document.body.addEventListener('dblclick', (ev) => {
153179
if (isNonBlocking(ev.target)) {
154180
nonblockPass(ev.target, ev, 'ondblclick');
155-
ev.stopPropagation();
181+
if (isNotPropagating(ev.target)) {
182+
ev.stopPropagation();
183+
}
156184
}
157185
}, true);
158186

159187
// Fire a DOM event.
160-
const domEvent = (elem, event, origEvent) => {
188+
const domEvent = (elem, event, origEvent, bubbles) => {
161189
let eventObject;
162190
event = event.toLowerCase();
163191
if (document.createEvent && elem.dispatchEvent) {
@@ -170,7 +198,7 @@
170198
eventObject = document.createEvent("MouseEvents");
171199
eventObject.initMouseEvent(
172200
event,
173-
origEvent.bubbles,
201+
bubbles === undefined ? origEvent.bubbles : bubbles,
174202
origEvent.cancelable,
175203
origEvent.view,
176204
origEvent.detail,
@@ -187,10 +215,10 @@
187215
);
188216
} else if (event.match(regexUiEvents)) {
189217
eventObject = document.createEvent("UIEvents");
190-
eventObject.initUIEvent(event, origEvent.bubbles, origEvent.cancelable, origEvent.view, origEvent.detail);
218+
eventObject.initUIEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable, origEvent.view, origEvent.detail);
191219
} else if (event.match(regexHtmlEvents)) {
192220
eventObject = document.createEvent("HTMLEvents");
193-
eventObject.initEvent(event, origEvent.bubbles, origEvent.cancelable);
221+
eventObject.initEvent(event, bubbles === undefined ? origEvent.bubbles : bubbles, origEvent.cancelable);
194222
}
195223
if (!eventObject) {
196224
return
@@ -259,11 +287,17 @@
259287
if (!nonBlockLastElem || nonBlockLastElem !== elBelow) {
260288
if (nonBlockLastElem) {
261289
const lastElem = nonBlockLastElem;
262-
domEvent(lastElem, 'mouseleave', event);
263-
domEvent(lastElem, 'mouseout', event);
290+
if (!lastElem.contains(elBelow)) {
291+
domEvent(lastElem, 'mouseleave', event, false);
292+
}
293+
domEvent(lastElem, 'mouseout', event, true);
294+
if (!elBelow.contains(lastElem)) {
295+
domEvent(elBelow, 'mouseenter', event, false);
296+
}
297+
} else if (!elBelow.contains(elem)) {
298+
domEvent(elBelow, 'mouseenter', event, false);
264299
}
265-
domEvent(elBelow, 'mouseenter', event);
266-
domEvent(elBelow, 'mouseover', event);
300+
domEvent(elBelow, 'mouseover', event, true);
267301
}
268302
domEvent(elBelow, eventName, event);
269303
// Remember the latest element the mouse was over.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ npm install --save nonblockjs
2626

2727
Add the class `nonblock` to any element you want to make nonblocking.
2828

29+
Add the class `nonblock-stoppropagation` if you want NonBlock.js to stop event propagation for mouse events, effectively disguising it from its ancestors.
30+
2931
## Demos
3032

3133
https://sciactive.github.io/nonblockjs/

index.html

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@
2626
box-shadow: 0px 2px 30px 5px rgba(0,0,0,0.3);
2727
}
2828
.intheway.first {
29-
position: relative;
30-
top: -180px;
29+
position: absolute;
30+
top: 5px;
3131
left: 30px;
3232
height: 100px;
3333
width: 120px;
3434
}
35-
.intheway.second {
35+
.intheway.second, .intheway.third, .intheway.fourth {
3636
position: absolute;
3737
top: 4px;
3838
bottom: 4px;
@@ -51,16 +51,20 @@ <h1>NonBlock.js</h1>
5151
user hovers over them, and let the user select and interact with elements
5252
under them. Hover over the purple boxes to try it out.
5353
</p>
54-
<div class="linkcontainer first">
55-
This is a <a href="http://google.com" target="_blank">link</a>. Here is some
56-
text. Let's have more text. All the text in the world couldn't save you now,
57-
Mr. Bond. Ah, but that's where you're wrong. You see, my underpants have
58-
been equipped with several tiny lasers that will blind you while the speaker
59-
up my butt blasts disco music.
60-
</div>
61-
<div class="nonblock intheway first">
62-
There&apos;s a link under me. I am going to stay in the way of that link.
54+
<hr />
55+
<div style="position: relative;">
56+
<div class="linkcontainer first">
57+
This is a <a href="http://google.com" target="_blank">link</a>. Here is
58+
some text. Let's have more text. All the text in the world couldn't save
59+
you now, Mr. Bond. Ah, but that's where you're wrong. You see, my
60+
overalls have been equipped with several tiny lasers that will blind you
61+
while the speaker in my straw hat blasts disco music.
62+
</div>
63+
<div class="nonblock intheway first">
64+
There&apos;s a link under me. I am going to stay in the way of that link.
65+
</div>
6366
</div>
67+
<hr />
6468
<div>
6569
Try toggling nonblocking on this demo:
6670
<button onclick="document.querySelector('.intheway.second').classList.add('nonblock')">Enable NonBlock</button>
@@ -73,5 +77,52 @@ <h1>NonBlock.js</h1>
7377
button.
7478
</div>
7579
</div>
80+
<hr />
81+
<div>
82+
These demos show how event propagation works. The green box is listening for
83+
events and the purple box is a child.
84+
</div>
85+
<h4>
86+
class="nonblock"
87+
</h4>
88+
<div class="linkcontainer third">
89+
This is a <button type="button" onclick="alert('You clicked the button.')">button</button>.
90+
<div class="nonblock intheway third">
91+
There&apos;s a button under me. I am going to stay in the way of that
92+
button.
93+
</div>
94+
</div>
95+
<div style="display: inline-block; font-family: monospace; width: auto;">
96+
Event log:
97+
<div id="event-third" style="max-height: 250px; overflow: auto;"></div>
98+
</div>
99+
<h4>
100+
class="nonblock nonblock-stoppropagation"
101+
</h4>
102+
<div class="linkcontainer fourth">
103+
This is a <button type="button" onclick="alert('You clicked the button.')">button</button>.
104+
<div class="nonblock nonblock-stoppropagation intheway fourth">
105+
There&apos;s a button under me. I am going to stay in the way of that
106+
button.
107+
</div>
108+
</div>
109+
<div style="display: inline-block; font-family: monospace; width: auto;">
110+
Event log:
111+
<div id="event-fourth" style="max-height: 250px; overflow: auto;"></div>
112+
</div>
113+
<script type="text/javascript">
114+
function listenerDemo(demo, eventType) {
115+
document.querySelector('.linkcontainer.'+demo).addEventListener(eventType, function(event){
116+
var output = document.getElementById('event-'+demo);
117+
output.innerHTML += '<br />'+eventType+'<br />Target: '+event.target.tagName+' class="'+event.target.className+'"<br />';
118+
output.scrollTop = output.scrollHeight;
119+
});
120+
}
121+
var events = ['mouseenter', 'mouseover', 'mouseleave', 'mouseout', 'mousemove', 'mousedown', 'mouseup', 'click', 'dblclick'];
122+
for (var i in events) {
123+
listenerDemo('third', events[i]);
124+
listenerDemo('fourth', events[i]);
125+
}
126+
</script>
76127
</body>
77128
</html>

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nonblockjs",
3-
"version": "1.0.3",
3+
"version": "1.0.4",
44
"description": "Unobtrusive (click through) UI elements in JavaScript.",
55
"keywords": [
66
"non blocking",

0 commit comments

Comments
 (0)