PDA

View Full Version : [DEFER-86][3.??] Bug in masking (loadmask, modal windows, etc.)



mschwartz
12 May 2009, 9:06 AM
I've suggested this before, but it is really worth bringing up again before 3.0 becomes final.

My app opens a modal window. Click a button on that window, it opens a 2nd modal window, properly masking the 1st window. Click the OK button and it puts up a loadMask while doing an ajax request to the server. The loadmask appears behind the 2nd modal window (maybe behind the 1st, it's hard to tell), the "please wait, submitting" message with busy animated gif shows up on top of the 2nd modal window.

I think there's a flaw in the strategy how Ext manages z-index, and people are going to run into this kind of thing forever unless it's fixed.

The proper strategy, IMO, is to start out with a global z-index for everything of 0. Each window would be assigned a new z-index which is +3 from the previous z-index. Use z-index-2 to mask anything below the window. Use z-index-1 for the window. Use z-index to mask the window.

When you depth arrange windows, you reorder the z-indexes accordingly.

Always mask the window when it is not on top. This allows the window to contain an iframe, when you click on the window you click on the mask and the iframe doesn't eat the event; you activate the window (bring to front) and remove/hide the mask (when the mask is clicked on). Iframes are a big deal, since they are used by TinyMCE, FCKEditor, the Ext rich text editor (I think this is true), the ManagedIFrame extension, and likely others.

If you really want to do it right, when you resize a window (resizable), you enable the mask and resize the mask as the mouse moves. This lets you (mostly) resize a window with an iframe in it and the iframe won't gobble the mouseover/mousemove events if you move the mouse fast enough to get ahead of the rendering.

For these last two kinds of masks, it's be a div with transparency 100%, absolute position and size. Or a transparent 1x1 pixel gif (s.gif)...

mschwartz
12 May 2009, 9:17 AM
In addition, there's issues when you open a modal window and from there you open a 2nd not modal window. As soon as you start activating the two windows by clicking on one then the other, one is going to end up below the modal mask where you can't get at it to activate it again.

mjlecomte
4 Jun 2009, 12:50 PM
Thanks for your post. You would help facilitate addressing any issues if you posted a test case illustrating your concern.

mschwartz
8 Jun 2009, 12:11 PM
To reproduce:
1) Click "Open Modal" button.
2) Drag new modal window away so you can see the original.
3) Click "Open Modeless" button.
4) You can drag (as you should) modless 2 window.
5) Click on "Modal" window (Modal in title bar).
6) Modeless 2 goes behind the shim, it shouldn't.




Ext.onReady(function(){
new Ext.Viewport({
layout: 'fit',
items: [
{
html: ''
}
]
});

new Ext.Window({
title: 'Not Modal 1',
width: 400,
height: 200,
buttons: [
{
text: 'Open Modal',
handler: function() {
new Ext.Window({
title: 'modal',
modal: true,
width: 400,
height: 200,
buttons: [
{
text: 'Open Modeless',
handler: function() {
new Ext.Window({
title: 'modeless 2',
width: 400,
height: 200
}).show();
}
}
]
}).show();
}
}
]
}).show();
});

mschwartz
8 Jun 2009, 12:21 PM
To reproduce:

1) Click Open Modal button.
2) Drag modal window so you can see first window.
3) Click Open Modeless button
4) Click 'show loadMask' button

square box with 'Loading...' is on top of modeless 2 and modal windows, the shim is underneath both. BUG.

Click on modal window and modeless 2 goes behind the shim. The other BUG.




Ext.onReady(function(){
var loadMask = null;

new Ext.Viewport({
layout: 'fit',
items: [
{
html: ''
}
]
});

new Ext.Window({
title: 'Not Modal 1',
width: 400,
height: 200,
buttons: [
{
text: 'Open Modal',
handler: function() {
new Ext.Window({
title: 'modal',
modal: true,
width: 400,
height: 200,
buttons: [
{
text: 'Open Modeless',
handler: function() {
new Ext.Window({
title: 'modeless 2',
width: 400,
height: 200,
buttons: [
{
text: 'show loadMask',
handler: function() {
if (!loadMask) {
loadMask = new Ext.LoadMask(Ext.getBody(), 'Load Mask');
loadMask.show();
}
}
},
{
text: 'hide loadMask',
handler: function() {
if (loadMask) {
loadMask.hide();
loadMask.destroy();
loadMask = null;
}
}
}
]
}).show();
}
}
]
}).show();
}
}
]
}).show();
});

mschwartz
8 Jun 2009, 12:23 PM
My first post details how to fix it ;)

aconran
8 Jun 2009, 1:57 PM
We are aware of the potential issues with our technique of managing z-indices. This issue will not be addressed for Ext 3.0, as we want to make sure that any potential regression issues and larger bugs are covered

We will look into it post 3.0.

mschwartz
9 Jun 2009, 5:00 AM
Not really a problem for me, Aaron, though it kills certain kinds of UI interface designs.

I was asked to produce code to show the issue, and I did. It's now up to you guys, on your schedule.

While I'm discussing this, another feature to consider is what "modal" actually should mean. As is, it means "block everything in the browser window except the current window." It could mean, "block the current tab in the tabPanel only" or "block the window that opened this one."

System modal dialogs are not a good idea, IMO. A few years ago, a friend of mine spent 7 days rendering something on a Macintosh. He was under a tight deadline. Before the render finished, a system modal dialog popped up, "Disk is full: retry or abort" - if the dialog wasn't system modal, he could have gone to his desktop and erased or moved some files to make space for the render to finish.

Animal
9 Jun 2009, 5:23 AM
We are aware of the potential issues with our technique of managing z-indices. This issue will not be addressed for Ext 3.0, as we want to make sure that any potential regression issues and larger bugs are covered

We will look into it post 3.0.

Good to hear that, I think it's the correct thing.

I think the whole masking strategy of modal Windows needs rethinking.

Perhaps we can discuss it off line. I'm thinking that there only needs to be one mask element per Ext.WindowGroup, and it's the managing WindowGroup which aligns that single mask just behind the topmost Window in its stack.

mjlecomte
9 Jun 2009, 5:51 AM
Probably not specifically related, but I'll mention in case this thread is used for reference:
IIRC there's still the issue of being able to tab items behind the modal mask (forms, buttons, etc.). With more ARIA support coming maybe this will be further exposed.

aconran
9 Jun 2009, 6:01 AM
While I'm discussing this, another feature to consider is what "modal" actually should mean. As is, it means "block everything in the browser window except the current window." It could mean, "block the current tab in the tabPanel only" or "block the window that opened this one."

If you use renderTo the mask should appear only in that section.


var p = new Ext.Panel({
renderTo: Ext.getBody(),
width: 600,
height: 600,
html: 'Sample'
});
var win = new Ext.Window({
renderTo: p.body,
width: 200,
height: 200,
modal: true,
html: 'Modal local to panel'
});
win.show();




System modal dialogs are not a good idea, IMO. A few years ago, a friend of mine spent 7 days rendering something on a Macintosh. He was under a tight deadline. Before the render finished, a system modal dialog popped up, "Disk is full: retry or abort" - if the dialog wasn't system modal, he could have gone to his desktop and erased or moved some files to make space for the render to finish.

Ouch!

Animal
9 Jun 2009, 6:05 AM
Of course rendering Windows to elements adds another dimension to this.

That Window rendered into a Panel, and modal within that Panel, can never be "brought to font" by its WindowGroup manager above another Window which is rendered to the document.

Could be that if no manager is specified, a new Window is assigned to a WindowGroup for the element it gets rendered into.

Most of the time, this will be the general purpose document WindowGroup, so there won't be too much overhead.

mjlecomte
9 Jun 2009, 6:58 AM
renderTo also constrains to that div as well correct? If so, that should be configurable perhaps. Or, do you have to configure constrain anyway. I forget.

mschwartz
9 Jun 2009, 7:38 AM
Good to hear that, I think it's the correct thing.

I think the whole masking strategy of modal Windows needs rethinking.

Perhaps we can discuss it off line. I'm thinking that there only needs to be one mask element per Ext.WindowGroup, and it's the managing WindowGroup which aligns that single mask just behind the topmost Window in its stack.

That's a mistake.

Each inactive window should have a mask in front of it, when you click on the mask, it activates the window, brings it to front.

Such masks in front of any iframe will prevent the iframe from consuming the click event, which is what you want: 1st click brings window to front, 2nd click is in the window. Otherwise you might click on some link or button in the inactive window while trying to bring it to front. These masks would be transparent, not greyed out, though you should consider some decoration for inactive windows so they're visually inactive.

The cost isn't a big deal. Number of windows minus 1 shims. Activating a window enables one shim, disabled one shim. You do have to have the manager reorder the Z of the windows.

The modal mask should be just behind the window that is opened modal. If you open a 2nd non-modal window, it'll never go z behind that mask. It belongs to the window, not the window group. You can have a modal window open another modal window.

Maybe window frames should be z in front of their bodies, as well. If you have an iframe fit inside the window and then resize it (smaller), your mouse can move faster than the resize and thus over the iframe where the mouse moves get consumed. If you enable a full screen invisible mask over the iframe (window contents) but behind the window decorations, you can resize the decorations layer and the shim/mask would prevent the mosue moves from getting to the iframe.

Window Manager should have it's own Z, and manage everything from that Z up (never down). New window managers should keep themselves above any lower WindowManager's Z.

Ext needs to, in general, keep track of the front-most Z always. When you want to block the UI with a loadMask or shim, it's that Z + 1.

mjlecomte
9 Jun 2009, 8:05 AM
Adding to related: there were some extensions, code posted for a config "alwaysOnTop".

Just mentioning in case that becomes easily included.

mschwartz
9 Jun 2009, 8:14 AM
Adding to related: there were some extensions, code posted for a config "alwaysOnTop".

Just mentioning in case that becomes easily included.

Always on top is considered when reordering Z. But what does always on top really mean? Can you have two windows always on top? Can you depth arrange those so either one can be on top of the other (in case you have two, for example)?

Animal
9 Jun 2009, 9:52 AM
That's a mistake.

Each inactive window should have a mask in front of it, when you click on the mask, it activates the window, brings it to front.

My idea is that all inactive Windows will have a mask in front of them. The mask will be in front of all inactive Windows, and only behind the topmost, active Window.

mschwartz
9 Jun 2009, 10:31 AM
My idea is that all inactive Windows will have a mask in front of them. The mask will be in front of all inactive Windows, and only behind the topmost, active Window.

You're going to need a stack or something to keep track of how to reposition/reorder Z the mask.

Consider open modal window -> open modal window again -> open modal window again, etc.

When you close the topmost, you have to move the mask's Z behind the 2nd window, etc.

Animal
9 Jun 2009, 11:41 AM
You're going to need a stack or something to keep track of how to reposition/reorder Z the mask.etc.

Which is what Ext.WindowGroup has and why it should manage the single mask for all the Windows it is managing.

mjlecomte
18 Jun 2009, 9:33 AM
Just an update: this issue appears to be still OPEN as of now, but the plan is to defer this until after the release of 3.0 final.

mschwartz
18 Jun 2009, 9:38 AM
My take is that it's going to be pretty involved coding to excise the wrong code and reimplement it correctly. At the same time, there's a significant chance they'll be less backwards compatibility. It's a good decision, but it does need to be addressed, IMO.

A use case for this stuff working right might be a modal dialog that has a floating toolbar window and a work window (sorta like Photoshop).

Obviously Desktop style applications require this to work right.

mystix
1 Jul 2009, 7:07 PM
copying a post i made in a related bug report for reference, and as an additional idea to throw into the zIndex-managedment mix:

or we could avoid the need for a zIndex manager by simply providing a getZIndex() method at the Component level so child Components can inspect their parents' zIndex when calculating their own. do you have a link to your FR? i'd like to throw this idea into the mix.

mjlecomte
1 Jul 2009, 8:32 PM
Another test case for this thread:
http://extjs.com/forum/showthread.php?p=351546#post351546

mschwartz
2 Jul 2009, 4:43 AM
copying a post i made in a related bug report for reference, and as an additional idea to throw into the zIndex-managedment mix:

You still need to manage zIndexes. Window #1 at Z=10, window #2 at Z=13 (always add 3!), window #3 at Z=16.

If you depth arrange so window #3 is behind the others, it's Z has to become 10, and window #2 Z has to become 16 and window #1 Z has to become 13. When you close a window, you have to adjust, too.

Why always add 3?

Window #2 is modal, so it puts up mask/shim at it's Z-1, which is guaranteed to be in front of window #1. Window #1 and window #2 are inactive, so they should have a fully transparent shim exactly their size at Z+1. If you click on the fully transparent shim, it should bring the window to the front.

The fully transparent shim will block events from going into HTML editor or ManagedIFrame, etc. At worst this should be a property setting, but I think you don't want to accidentally click on a link in miframe or button on the HTML editor when you're trying to bring a window to the front.

dbassett74
17 Jul 2009, 7:53 AM
Any word on this? I just discovered this bug. It is a big problem. Often times, when a panel is still loading in the background with a mask showing, the users wants to open a window. The load mask should still remain on the underlying panel, but the new window (either modal or modaless) should be on top of that mask, and may have its own mask. I don't think there should be a single mask as Animal suggested. I think that each component (Panel, Window, etc), should be able to manage its own mask and remain, but should not over lap other components. Here is a test case to illustrate this:


var pnl = new Ext.Panel({
renderTo: 'divBody'
, width: 600
, height: 400
, title: 'Panel'
});

var msk = new Ext.LoadMask(pnl.getEl(), { msg: 'Loading data in panel...' });
msk.show();

var win = new Ext.Window({
width: 500
, height: 400
, x: 10
, y: 10
});

win.show();

var msk = new Ext.LoadMask(win.getEl(), { msg: 'Loading data in window...' });
msk.show();
Any word on when this will be fixed? In the mean time, is there a workaround? This is very bad. Why can't the mask be a part of the actual component, rather than apparently being seperate from the panel. For example, why can't there be a load mask property of a component?