为什么 jquery.blockUI 会吞下 onClick 事件?
Why does jquery.blockUI swallow onClick event?
总结使用jquery.blockUI好像隐藏/吞下/屏蔽按钮点击事件。
Tech Stach
- backbone 和 marionette
- backbone.radio
- 下划线
- jquery
- jquery.blockUI
(所有最新版本)
应用程序
该应用程序由一个文本输入和一个按钮组成。
用backbone/marionette术语来说,有
- 具有 2 个区域的顶视图
- 作为文本输入的容器视图
- 有按钮的页脚视图
- 容器视图由模型支持。
- 页脚有一个按钮,单击该按钮会发送一个 backbone.radio 事件。
- 此事件在顶部视图中被拾取。
当用户离开文本输入时,调用 API(服务器/后端)。在示例中,使用 Promise / setTimeout 来模拟调用。
在示例中,按钮调用 console.log。
代码
这是 JSFiddle Example on JSFiddle 和下面的 Javascript 代码
// ------------------------------------------------------------------
var Model = Backbone.Model.extend({
defaults: {
"SearchCriteria": {
"Min": { "value": "abc123", "ReadOnly": true }
}
},
async callBackend() {
//$.blockUI(); //<----- uncomment this and the button click is swallowed
await new Promise(resolve => setTimeout(resolve, 3000));
$.unblockUI();
}
});
// ------------------------------------------------------------------
// ------------------------------------------------------------------
var ContainerView = Marionette.View.extend({
template: _.template('<div><label>Container</label></div><div><input id = "min" name = "min" type = "text"/></div>'),
events: {
'change': 'onChangeData',
},
async onChangeData(data) {
console.log('start onChangeData');
await this.model.callBackend();
this.render();
console.log('end onChangeData');
}
});
// ------------------------------------------------------------------
// ------------------------------------------------------------------
var FooterView = Marionette.View.extend({
template: _.template('<div><button class="btn-footer-test">Footer</button></div>'),
events: {
"click .btn-footer-test": () => {
console.log('click test ...');
Backbone.Radio.channel("maske").trigger("select:test");
}
},
});
// ------------------------------------------------------------------
// ------------------------------------------------------------------
var TopView = Marionette.View.extend({
template: _.template("<div id='container'></div><div id='footer'></div>"),
regions: {
container: '#container',
footer: '#footer'
},
events: {
'change': 'onChangeData',
},
initialize() {
this.listenTo(Backbone.Radio.channel("maske"), "select:test", this.onTest, this);
},
onRender() {
this.showChildView('container', new ContainerView({
model: new Model()
}));
this.showChildView('footer', new FooterView());
},
onChangeData(data) {
},
onTest() {
//NOT called if jquery.blockUI present ******
console.log('onTest');
}
});
// ------------------------------------------------------------------
$(document).ready(function () {
console.log('Start');
const topView = new TopView();
topView.render();
$('body').append(topView.$el);
});
使用
用户是这样使用应用的。用户
- 更改文本输入
- 并直接点击按钮(无需先跳出字段!)
预期行为
- 对文本输入的更改会触发更改事件。
- jquery.blockUI
- 异步调用
- jquery unblockUI
- 执行按钮的点击事件
实际行为
当jquery.blockUI函数存在时,按钮的点击事件不会被执行。评论 jquery.blockUI 按钮单击事件发生,但是在等待 returns.
之前
问题
- 我做错了什么?
- 点击事件为什么会被吞掉?
What am I doing wrong?
你的期望是错误的。 JavaScript 中没有隐式机制可以一个接一个地序列化异步事件。您(开发者)负责异步事件的同步。
Why is the click event swallowed?
Click
事件触发 when a mousedown and mouseup event occur on the same element。这不是你的情况。事件顺序如下:
mousedown
在 <button>
change
在 <input>
上;通过 blockUI
导致显示叠加层 <div>
mouseup
在叠加 <div>
上
click
在触发 mousedown
和 mouseup
的元素的最近公共父元素上,即 <body>
从技术上讲,输入更改后点击按钮似乎是不可能的,因为覆盖显示在mouseup
之前,但是有一种方法。如果在显示叠加层时单击并按住鼠标按钮,然后松开,click
事件将在按钮上触发,但这不是您想要的。
尝试使用此代码段。它记录每个 mousedown
、mouseup
、click
和 change
事件。它在 <input>
上注册异步 change
事件处理程序,第一秒什么都不做,然后显示一个叠加层,然后休眠 3 秒,最后隐藏叠加层。您可以根据按下鼠标按钮的时间观察各种行为。
更改输入文本并快速单击按钮
button.mousedown
input.change
button.mouseup
button.click
更改输入文本,单击按钮并按住 1 秒,然后松开
button.mousedown
input.change
div.mouseup
body.click
更改输入文本,单击按钮并按住 4 秒(直到覆盖消失),然后松开
button.mousedown
input.change
button.mouseup
button.click
$(document).on('mousedown mouseup click change', e => {
console.log(e.target.tagName.toLowerCase() + '.' + e.type);
});
$('input').change(async _ => {
await new Promise(resolve => setTimeout(resolve, 1000));
const $div = $('<div style="position:fixed; left:0; top:0; right:0; bottom:0; background-color:rgba(0,0,0,0.5); z-index:999">').appendTo('body');
await new Promise(resolve => setTimeout(resolve, 3000));
$div.remove();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" placeholder="type something, then click the button" style="width:250px">
<button>Button</button>
您将很难解决这个问题,因为在正常情况下 click
事件不会在按钮上触发,因此我建议您重新考虑您的用户界面。 为什么在使用 async/promises 时甚至会屏蔽用户界面?发明它是为了避免阻塞。
总结使用jquery.blockUI好像隐藏/吞下/屏蔽按钮点击事件。
Tech Stach
- backbone 和 marionette
- backbone.radio
- 下划线
- jquery
- jquery.blockUI
(所有最新版本)
应用程序
该应用程序由一个文本输入和一个按钮组成。
用backbone/marionette术语来说,有
- 具有 2 个区域的顶视图
- 作为文本输入的容器视图
- 有按钮的页脚视图
- 容器视图由模型支持。
- 页脚有一个按钮,单击该按钮会发送一个 backbone.radio 事件。
- 此事件在顶部视图中被拾取。
当用户离开文本输入时,调用 API(服务器/后端)。在示例中,使用 Promise / setTimeout 来模拟调用。
在示例中,按钮调用 console.log。
代码
这是 JSFiddle Example on JSFiddle 和下面的 Javascript 代码
// ------------------------------------------------------------------
var Model = Backbone.Model.extend({
defaults: {
"SearchCriteria": {
"Min": { "value": "abc123", "ReadOnly": true }
}
},
async callBackend() {
//$.blockUI(); //<----- uncomment this and the button click is swallowed
await new Promise(resolve => setTimeout(resolve, 3000));
$.unblockUI();
}
});
// ------------------------------------------------------------------
// ------------------------------------------------------------------
var ContainerView = Marionette.View.extend({
template: _.template('<div><label>Container</label></div><div><input id = "min" name = "min" type = "text"/></div>'),
events: {
'change': 'onChangeData',
},
async onChangeData(data) {
console.log('start onChangeData');
await this.model.callBackend();
this.render();
console.log('end onChangeData');
}
});
// ------------------------------------------------------------------
// ------------------------------------------------------------------
var FooterView = Marionette.View.extend({
template: _.template('<div><button class="btn-footer-test">Footer</button></div>'),
events: {
"click .btn-footer-test": () => {
console.log('click test ...');
Backbone.Radio.channel("maske").trigger("select:test");
}
},
});
// ------------------------------------------------------------------
// ------------------------------------------------------------------
var TopView = Marionette.View.extend({
template: _.template("<div id='container'></div><div id='footer'></div>"),
regions: {
container: '#container',
footer: '#footer'
},
events: {
'change': 'onChangeData',
},
initialize() {
this.listenTo(Backbone.Radio.channel("maske"), "select:test", this.onTest, this);
},
onRender() {
this.showChildView('container', new ContainerView({
model: new Model()
}));
this.showChildView('footer', new FooterView());
},
onChangeData(data) {
},
onTest() {
//NOT called if jquery.blockUI present ******
console.log('onTest');
}
});
// ------------------------------------------------------------------
$(document).ready(function () {
console.log('Start');
const topView = new TopView();
topView.render();
$('body').append(topView.$el);
});
使用
用户是这样使用应用的。用户
- 更改文本输入
- 并直接点击按钮(无需先跳出字段!)
预期行为
- 对文本输入的更改会触发更改事件。
- jquery.blockUI
- 异步调用
- jquery unblockUI
- 执行按钮的点击事件
实际行为
当jquery.blockUI函数存在时,按钮的点击事件不会被执行。评论 jquery.blockUI 按钮单击事件发生,但是在等待 returns.
之前问题
- 我做错了什么?
- 点击事件为什么会被吞掉?
What am I doing wrong?
你的期望是错误的。 JavaScript 中没有隐式机制可以一个接一个地序列化异步事件。您(开发者)负责异步事件的同步。
Why is the click event swallowed?
Click
事件触发 when a mousedown and mouseup event occur on the same element。这不是你的情况。事件顺序如下:
mousedown
在<button>
change
在<input>
上;通过blockUI
导致显示叠加层 mouseup
在叠加<div>
上
click
在触发mousedown
和mouseup
的元素的最近公共父元素上,即<body>
<div>
从技术上讲,输入更改后点击按钮似乎是不可能的,因为覆盖显示在mouseup
之前,但是有一种方法。如果在显示叠加层时单击并按住鼠标按钮,然后松开,click
事件将在按钮上触发,但这不是您想要的。
尝试使用此代码段。它记录每个 mousedown
、mouseup
、click
和 change
事件。它在 <input>
上注册异步 change
事件处理程序,第一秒什么都不做,然后显示一个叠加层,然后休眠 3 秒,最后隐藏叠加层。您可以根据按下鼠标按钮的时间观察各种行为。
更改输入文本并快速单击按钮
button.mousedown
input.change
button.mouseup
button.click
更改输入文本,单击按钮并按住 1 秒,然后松开
button.mousedown
input.change
div.mouseup
body.click
更改输入文本,单击按钮并按住 4 秒(直到覆盖消失),然后松开
button.mousedown
input.change
button.mouseup
button.click
$(document).on('mousedown mouseup click change', e => {
console.log(e.target.tagName.toLowerCase() + '.' + e.type);
});
$('input').change(async _ => {
await new Promise(resolve => setTimeout(resolve, 1000));
const $div = $('<div style="position:fixed; left:0; top:0; right:0; bottom:0; background-color:rgba(0,0,0,0.5); z-index:999">').appendTo('body');
await new Promise(resolve => setTimeout(resolve, 3000));
$div.remove();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" placeholder="type something, then click the button" style="width:250px">
<button>Button</button>
您将很难解决这个问题,因为在正常情况下 click
事件不会在按钮上触发,因此我建议您重新考虑您的用户界面。 为什么在使用 async/promises 时甚至会屏蔽用户界面?发明它是为了避免阻塞。