Forwarding Mouse Events Through Layers
Anyone who has worked with web apps has likely created a masking element at some point, and the great thing about a masking element is that it intercepts user interaction, letting us create a pseudo-modal user interface. The masking element enables us to mask the entire screen, bringing focus to a particular element, or just create a window like effect. This behavior is demonstrated in the ExtJS libraries Ext.Window when modal is set to true, among other places.
Yeah, so what?
Why do this?
A plugin that I recently wrote - DataDrop - used a textarea overlaying a grid to act as the receiver of dragged data from spreadsheet type programs. One of the limitations that I was not so happy about was the fact that this overlay could only exist in the 'empty' area of the grid, not over rows of data, headers, toolbars, etc. otherwise it would cause all of those elements below to become unresponsive to user input. If our textarea that receives the data were positioned over the entire grid area, our GridPanel could not be controlled using the mouse anymore.
How to fix this?
- The textarea (my masking element) that is positioned over the grid receives mouseover, mousemove, mousedown , mouseup and other events.
- The top masking layer is hidden for a moment, so we can figure out what element is below the mask at the event location.
- The event is re-fired - this is where the W3 DOM Event model and the simpler Microsoft equivalent come into play.
- Start the process again - ready for the next event.
What's element to fire upon?
Knowing the cursor position and event that occurred, the only obstacle now is determining what DOM element to fire the simulated event through. It turns out that finding which element is the topmost element at a certain document coordinate position is quite simple in modern browsers. The document.elementFromPoint method is supported in IE, Mozilla, Webkit and Opera.
So it is possible to capture the document coordinates of a mouse event fired on the masking element, momentarily hide that masking element, and then ask the document what is under that coordinate position before showing the mask again.
A new event can be then fired on the found element. Obviously there are some complexities such as firing mouseover and mouseout events when the found element is different from the last time, but that is the gist of the technique. To see it in action, check out the screencast or play with the live Grid DataDrop example or simple DOM element example yourself.
The Next Step
Having the data that is dropped into the grid automatically insert into the grids rows at the correct position is my next task for the ever-improving DataDrop plugin. Should be fun :/UPDATE (12-1-09): Seems that Mozilla has also noticed this issue.