Summary Using jquery.blockUI seems to hide / swallow / mask button click event.
Tech Stach
- backbone and marionette
- backbone.radio
- underscore
- jquery
- jquery.blockUI
(all latest version)
The App
The app consists of a text input and a button.
In terms of backbone/marionette terminology, there is
- a top view which has 2 regions
- container view which as the text input
- footer view which has the button
- The container view is backed by a model.
- The footer has a button, Clicking the button sends a backbone.radio event.
- This event is picked up in the top view.
When the user leaves the text input, an API (server / backend) is called. In the example a Promise / setTimeout is used to simulate the call.
In the example the button calls console.log.
Code
Here is the JSFiddle Example on JSFiddle and below the Javascript code
// ------------------------------------------------------------------
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);
});
Use
The user uses the app like so. The user
- changes the text input
- and directly clicks the button (without tabbing out of the field first!)
Expected Behavior
- the change to the text input triggers a change event.
- jquery.blockUI
- async call
- jquery unblockUI
- the click event to the button is executed
Actual Behavior
When the jquery.blockUI function is present the click event to the button is not executed. Commenting jquery.blockUI the button click event occurs, however before the await returns.
Questions
- What am I doing wrong?
- Why is the click event swallowed?


Your expectations are wrong. There is no implicit mechanism in JavaScript that serializes asynchronous events one after the other has completed. You (developer) are responsible for synchronization of asynchronous events.
Clickevent fires when a mousedown and mouseup event occur on the same element. And this is not your case. The order of events is as follows:mousedownon<button>changeon<input>; causes displaying overlay<div>viablockUImouseupon overlay<div>clickon closest common parent element of elements that triggeredmousedownandmouseup, which is<body>Technically it seems to be impossible to click the button after the input changed, because overlay is displayed before
mouseup, however there is one way. If you click and hold the mouse button while the overlay is being displayed and release afterwards, theclickevent will be triggered on the button, but this isn't something that you want anyway.Try to play with this snippet. It logs every
mousedown,mouseup,clickandchangeevent. It registers asynchronouschangeevent handler on<input>thet does nothing for the first second, then displays an overlay, then sleeps for 3 seconds and finally hides the overlay. You can observe various behaviours based on how long you kept the button mouse depressed.Change input text and quickly click the button
Change input text, click the button and hold for 1 sec, then release
Change input text, click the button and hold for 4 sec (until overlay disappears), then release
You will have pretty hard time getting around this, because in normal situations the
clickevent won't be fired on the button and therefore I'd suggest you to rethink your user interface. Why do you even block user interface when you use async/promises? It was invented to avoid blocking.