5

With the impending removal of the showModalDialog API from various browsers, our company like many others who provide large scale enterprise web applications are now faced with a significant dilemma.

Whilst we have centralised the calls to showModalDialog down to 3 lines of code, we extensively rely on this code to provide feedback from modal user prompts (a quick search of the solution reveals around 2400 instances).

We could rip out showModalDialog fairly easily and replace it with a Javascript/css based alternative, that's not a problem. The issue we face is that all of the calling code will no longer be blocking e.g.

if(doConfirm(...)) {
   ...
} else {
   ...
} 

The above will simply fall through due to the introduction of a non-blocking alternative. We also cannot use the in-built blocking methods (alert, confirm) as the dialog buttons are customised in many cases and are also styled to fit in with our application.

Based on the above, are there any pragmatic workarounds/solutions that could be employed to avoid having to re-factor so much legacy previously blocking code?

Brett Postin
  • 11,215
  • 10
  • 60
  • 95
  • Possible duplicate of [Why is window.showModalDialog deprecated? What to use instead?](http://stackoverflow.com/q/20733962/1529630) – Oriol Aug 19 '14 at 18:32

3 Answers3

2

You can avoid using callback functions by using my showModalDialog polyfill, which pauses execution of subsequent statements until the modal is closed. It does so by using generators, promises and the yield keyword. It works in the newest Opera and Google Chrome.

niutech
  • 28,923
  • 15
  • 96
  • 106
  • 1
    This would be exactly the sort of thing I was looking for, however browser support is a real issue. We need IE10+, Chrome, Firefox and Safari 6+. – Brett Postin Aug 05 '14 at 08:00
  • You can use [Facebook Regenerator](http://facebook.github.io/regenerator/) if you need to support other browsers. – niutech Aug 05 '14 at 09:19
  • 1
    I have a sample [HERE](http://jsfiddle.net/poztin/nqkvcehn/6/) demonstrating a cross browser solution. However my original problem remains. The call to showModalDialog is indeed asynchronous, however the calling code does not respect this and continues execution. Am I doing something wrong? Is there a way to avoid having to rewrite every call to doDialog? – Brett Postin Aug 09 '14 at 13:33
  • 1
    @BrettPostin I made a clearer cross-browser [example using Regenerator](http://jsbin.com/wasafiva/1/). – niutech Aug 09 '14 at 22:54
  • And since spawn() returns a Promise, you should add `yield` before it in the outer function generator to wait for it to complete. See an [example](http://jsbin.com/xecareto/1/). – niutech Aug 09 '14 at 23:07
  • Really appreciate the help, but the Regenerator sample is broken. So are you saying changing all 2400 calls to doDialog is unavoidable as they too need to be marked as generator functions? Is there no way to encapsulate it such that the calling code doesn't need to be modified in any way? – Brett Postin Aug 10 '14 at 06:28
  • Sorry, JSBin fault. Here is the same [Regenerator example on JSFiddle](http://jsfiddle.net/93e29w98/1/embedded/result/), tested in Firefox, Opera nad Chrome. As for the encapsulation, I don't know any way other than converting them to Promises or using callback functions. – niutech Aug 11 '14 at 00:40
  • That's what I was afraid of. Thanks for your suggestion, it will certainly reduce the amount of work required for refactoring. – Brett Postin Aug 11 '14 at 07:59
  • @BrettPostin Especially for you I made a [fallback](https://github.com/niutech/showModalDialog) which no longer needs `yield`! – niutech Aug 19 '14 at 05:12
  • That's an interesting idea! However I can only see it working in the very simplest of cases. See [here](http://jsfiddle.net/poztin/hkrLvfu8/2/) for two scenarios where this approach wouldn't work. – Brett Postin Aug 19 '14 at 13:08
  • @BrettPostin You're right, the fallback cannot be nested inside functions. In your scenario, couldn't you set `doDialog = showModalDialog` and invoke it from the outermost function? – niutech Aug 21 '14 at 02:22
  • window.close(). not working...i need to workable that. or any alternative to close the dialog. – Md. Masud Iqbal Mar 18 '15 at 11:37
  • @niutech `Promise` is NOT supported in IE and therefore this example falls flat and is NOT cross-browser: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise . I wasted a lot of time on trying to make this work in IE and found a better solution through native jQuery, below. People say that IE natively supports `showModalDialog` - yes, true. But there are times it screws up or SharePoint injects parameters (`IsDlg=1` for example) you may not want. jQuery's is a far better solution, IMHO. – vapcguy Oct 19 '15 at 15:43
  • @vapcguy This polyfill is a fix for browsers where the native `showModalDialog` is not supported (i.e. Chrome 37+, not IE) – niutech Oct 28 '15 at 18:58
1

You won't get around using asynchronous, event-based code.

pragmatic workarounds to avoid having to re-factor the code manually?

You can try a javascript-to-javascript compiler that brings the await keyword to js. It should automatically transpile your code to an asynchronous version.

Disclaimer: I haven't used any of these

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

In jQuery's 1.11.4 version, there is a built-in <dialog> you can use, which also allows for capturing a callback parameter on close.

    $("#dialog").dialog({
        autoOpen: false,
        width: 500,
        height: 500,
        modal: true
        buttons: [
            {
                text: "Ok",
                click: function () {
                    $(this).dialog("close");
                }
            },
            {
                text: "Cancel",
                click: function () {
                    $(this).dialog("close");
                }
            }
        ]
    });

Your value could be captured in the callback functions from the button click events.

You could even add HTML to append your own 'x' button to the existing "Close" button and hide the original, so you could do whatever you wanted:

    $(document).ready(function () {
        var closeHtml = '<a href="#" id="dialog-close" style="position: absolute; top: 0; right: 4px; font-size: 20px; color: #000; text-decoration: none; outline: none;">&times;</a>';
        $("button[title='Close']").css('display', 'none').parent().append(closeHtml);

    });

then attach a click event to the dialog-close ID from the 'x' button:

    var url = 'https://www.cnn.com';

    // Link to open the dialog
    $("#dialog-link").click(function (event) {
        var dialog = $("#dialog");
        dialog.dialog("open");

        dialog.html('<iframe id="dialog-body" scrolling="yes" src="' + url + '" style="border: 0; width: 100%; height: 100%;"></iframe>');

        $("#dialog-close").on('click', function (e) {
            // ...do whatever you want here, then...
            $("button[title='Close']").click();
            //e.preventDefault();
            //dialog.close();
        });

        event.preventDefault();
    });

Only caveat, since this uses IFrame, it might not work if the security on the site prevents bringing in external sites to your own site, if you are using it in this way. Google, for example, prevents this use with their site.

This should be a cross-platform example - I've tested it in IE 11. "Polyfill", that I've seen others say is another way to do this, is NOT, and does NOT work in IE because it relies on Promise, which is not supported in IE, as it shows at the bottom of this page: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

vapcguy
  • 7,097
  • 1
  • 56
  • 52