Skip to main content

2 posts tagged with "jquery ui"

View All Tags

Rolling your own confirm mechanism using Promises and jQuery UI

It is said that a picture speaks a thousand words. So here's two:

That's right, we're here to talk about the confirm dialog. Or, more specifically, how we can make our own confirm dialog.

JavaScript in the browser has had the window.confirm method for the longest time. This method takes a string as an argument and displays it in the form of a dialog, giving the user the option to click on either an "OK" or a "Cancel" button. If the user clicks "OK" the method returns true, if the user clicks "Cancel" the method returns false.

window.confirm is wonderful in one way - it has a simple API which is easy to grok. But regardless of the browser, window.confirm is always as ugly as sin. Look at the first picture in this blog post; hideous. Or, put more dispassionately, it's not terribly configurable; want to change the button text? You can't. Want to change the styling of the dialog? You can't. You get the picture.

Making confirm 2.0#

jQuery UI's dialog has been around for a long time. I've been using it for a long time. But, if you look at the API, you'll see it works in a very different way to window.confirm - basically it's all about the callbacks. My intention was to create a mechanism which allowed me to prompt the user with jQuery UI's tried and tested dialog, but to expose it in a way that embraced the simplicity of the window.confirm API.

How to do this? Promises! To quote Martin Fowler (makes you look smart when you do that):

"In Javascript, promises are objects which represent the pending result of an asynchronous operation. You can use these to schedule further activity after the asynchronous operation has completed by supplying a callback."

When we show our dialog we are in asynchronous land; waiting for the user to click "OK" or "Cancel". When they do, we need to act on their response. So if our custom confirm dialog returns a promise of a boolean (true when the users click "OK", false otherwise) then that should be exactly what we need. I'm going to use Q for promises. (Nothing particularly special about Q - it's one of many Promises / A+ compliant implementations available.)

Here's my custom confirm dialog:

/**
* Show a "confirm" dialog to the user (using jQuery UI's dialog)
*
* @param {string} message The message to display to the user
* @param {string} okButtonText OPTIONAL - The OK button text, defaults to "Yes"
* @param {string} cancelButtonText OPTIONAL - The Cancel button text, defaults to "No"
* @param {string} title OPTIONAL - The title of the dialog box, defaults to "Confirm..."
* @returns {Q.Promise<boolean>} A promise of a boolean value
*/
function confirmDialog(message, okButtonText, cancelButtonText, title) {
okButtonText = okButtonText || "Yes";
cancelButtonText = cancelButtonText || "No";
title = title || "Confirm...";
var deferred = Q.defer();
$('<div title="' + title + '">' + message + '</div>').dialog({
modal: true,
buttons: [{
// The OK button
text: okButtonText,
click: function () {
// Resolve the promise as true indicating the user clicked "OK"
deferred.resolve(true);
$(this).dialog("close");
}
}, {
// The Cancel button
text: cancelButtonText,
click: function () {
$(this).dialog("close");
}
}],
close: function (event, ui) {
// Destroy the jQuery UI dialog and remove it from the DOM
$(this).dialog("destroy").remove();
// If the promise has not yet been resolved (eg the user clicked the close icon)
// then resolve the promise as false indicating the user did *not* click "OK"
if (deferred.promise.isPending()) {
deferred.resolve(false);
}
}
});
return deferred.promise;
}

What's happening here? Well first of all, if okButtonText, cancelButtonText or title have false-y values then they are initialised to defaults. Next, we create a deferred object with Q. Then we create our modal dialog using jQuery UI. There's a few things worth noting about this:

  • We're not dependent on the dialog markup being in our HTML from the off. We create a brand new element which gets added to the DOM when the dialog is created. (I draw attention to this as the jQuery UI dialog documentation doesn't mention that you can use this approach - and frankly I prefer it.)
  • The "OK" and "Cancel" buttons are initialised with the string values stored in okButtonText and cancelButtonText. So by default, "Yes" and "No".
  • If the user clicks the "OK" button then the promise is resolved with a value of true.
  • If the dialog closes and the promise has not been resolved then the promise is resolved with a value of false. This covers people clicking on the "Cancel" button as well as closing the dialog through other means.

Finally we return the promise from our deferred object.

Going from window.confirm to confirmDialog#

It's very simple to move from using window.confirm to confirmDialog. Take this example:

if (window.confirm("Are you sure?")) {
// Do something
}

Becomes:

confirmDialog("Are you sure?").then(function(confirmed) {
if (confirmed) {
// Do something
}
});

There's no more to it than that.

And finally a demo...#

With the JSFiddle below you can create your own custom dialogs and see the result of clicking on either the "OK" or "Cancel" buttons.

Making IE 10's clear field (X) button and jQuery UI autocomplete play nice

This morning when I logged on I was surprised to discover IE 10 had been installed onto my machine. I hadn't taken any action to trigger this myself and so I’m assuming that this was part of the general Windows Update mechanism. I know Microsoft had planned to push IE 10 out through this mechanism.

I was a little surprised that my work desktop had been upgraded without any notice. And I was initially rather concerned given that most of my users have IE 9 and now I didn't have a test harness on my development machine any more. (I've generally found that having the majority users browser on your own machine is a good idea.) However, I wasn't too concerned as I didn’t think it would makes much of a difference to my development experience. I say that because IE10, as far as I understand, is basically IE 9 + more advanced CSS 3 and extra HTML 5 features. The rendering of my existing content developed for the IE 9 target should look pixel for pixel identical in IE 10. That’s the theory anyway.

However, I have found one exception to this rule already. IE 10 provides clear field buttons in text boxes that look like this:

Unhappily I found these were clashing with our jQuery UI auto complete loading gif – looking like this:

I know; ugly isn't it? Happily I was able to resolve this with a CSS hack

fix which looks like this:

And now the jQuery UI autocomplete looks like we expect during the loading phase:

But happily when the autocomplete is not in the loading phase we still have access to the IE 10 clear field button. This works because the CSS selector above only applies to the ui-autocomplete-loading class (which is only applied to the textbox when the loading is taking place). So we still get to use this:

Which is nice.