« In-the-Brain of Erik Doernenburg: Why Agile Teams FailThe Social Impact of the Web: Society, Government and the Internet »

Scriptaculous Draggable and Z-index

05/06/07

07:24:29 pm, by robertc Email , 723 words, 27119 views   English (UK)
Categories: Web Design

Scriptaculous Draggable and Z-index

I was building a prototype today using the Scriptaculous Draggable object. It was a fairly simple idea, a set of 'items' along the top of the page which could be dragged onto a selection of timeslots below, the idea being that by dragging the item to the timeslot you are 'booking' the item for that timeslot. Obviously in the real application this would have to link back to a database and do all sorts of complex stuff, but for now I just wanted to mockup how the UI would work.

I started off aiming to get the basic functionality working and not worrying too much about design. The Scriptaculous stuff is easy to use, I set up my items and timeslots as div elements and gave them a class name, then in my initialisation function used getElementsByClassName to get an array of elements to which I could apply the behaviour:

var els = $('available').getElementsByClassName('cat');
for (var i=0; i<els.length; i++) {
    new Draggable(els[i],{revert: true});
}

The droppable bit was a bit more complicated as I added event handler functions for onHover and onDrop:

var els = $('times').getElementsByClassName('slot');
for (var i=0; i<els.length; i++) {
    Droppables.add(els[i],{accept: 'cat', onDrop: catchDrop, onHover: showCatch } );
}

My catchDrop handler inserts an item into the booking slot, or increments the number of that item if one or more is already booked, though initially I just popped up an alert to confirm everything worked as expected. The showCatch handler changes the background colour of the element when a draggable is moved over it. I tried using Effect.Highlight initially, but it didn't interact well with the drag and drop stuff - if you tried to add an item to the bottom booking slot, all of the slots above became highlighted as well. So I settled for changing the background colour and used a brute force approach of removing the background colour from all the other elements before applying to the current one. Here is what I'd got so far.

With the basic functionality working I set about styling the page with CSS. I envisaged a 'toolbar' of items across the top with an 'Outlook style' collection of timeslots below. Since, in the actual application, there could be any number of items, I made the toolbar a fixed height and width and set it to scroll along the x axis:

#available {
    width: 100%;
    height: 64px;
    overflow-x: scroll;
    overflow-y: hidden;
}

This had an unfortunate side effect, however. Now when I dragged my items down onto the booking slots they were hidden behind the slots, and this looked a bit stupid. I did some experimenting with z-index for the various elements involved, but didn't have much joy - I got it sort of working in IE but that 'solution' hid all the booking slots in Firefox, so I resorted to Google. The first thing I learned was that Scriptaculous sets the z-index of the draggable element to 1000 - so clearly that shouldn't be the problem. Next, I wondered if the float: left I'd applied to the draggable elements somehow interfered with the the z-index, which led me to an article, with an excellent example, on the Mozilla developer site. This was useful information, and clearly floating does have some effect but it looked like it wasn't what was causing my problem. Finally I came across a CSS-d wiki article on z-index stacking context. Although I'm sure I haven't fully understood the article yet, this seemed to be the problem - the draggable elements were now in a different stacking context to the droppable elements, and this is why my z-index experiments weren't working. This got me to the root of the problem - if it worked fine before any CSS was involved, then something in my CSS was messing with the stacking context. Commenting the rules out one by one soon revealed the culprit - overflow-x creates a new stacking context (or at least, has an equivalent effect) in Firefox, whereas it seems IE's broken z-index handling was what allowed me to end up with something which worked there.

Rather than delve even deeper into the mysteries of z-index I chickened out and did the page without the overflow, it's only a mockup after all (which is also why it's only been tested in Firefox and IE7, before anyone starts complaining &amp;#58;&amp;#41; )


Tweet this!

Trackback address for this post

Trackback URL (right click and copy shortcut/link location)

11 comments

Comment from: Henry [Visitor] Email
This link to z-index stacking context stated that making the element positioned absolute has a nice effect. So to get the draggable dragging outside the overflow div, I made these 2 changes in dragdrop.js:

1) After "startDrag: function(event) {", add "this.element.style.position = 'absolute'; "

2) After "finishDrag: function(event, success) {", add "this.element.style.position = 'static'; "

Thanks for your article.
04/09/07 @ 09:20
Comment from: Anon [Visitor] Email
I assume you never found a solution to this?
13/05/08 @ 17:47
Comment from: robertc [Member] Email · http://www.boogdesign.com/
I've not gone back to it yet, though that may be happening in the next few weeks. Did you try Henry's solution?

Rob
14/05/08 @ 00:41
Comment from: John R [Visitor] Email · http://www.maxpower.plus.com
I've tried Henry's mod. It works - but has side effects which render it useless. In my case - it doesn't put the draggables in the right place once complete.

I tested in FF3 Beta 5
27/05/08 @ 14:37
Comment from: Andy [Visitor] Email · http://householder-yogi.net
Here's a possible solution (note - if you're manually cloning the dragged element to copy it into your Droppable, you'll need to set position:'relative' on the cloned element otherwise it will have position:'absolute').


new Draggable(asanaId,{revert:true
,onStart: function(drgObj,mouseEvent){
drgObj.element.style.position='absolute'
}
,onEnd: function(drgObj,mouseEvent) {
drgObj.element.style.position='relative'
}
});
06/07/08 @ 07:39
Comment from: Andy [Visitor] Email · http://householder-yogi.net
On addition - offset must be zeroed because of the position change to absolute...

,onStart: function(drgObj,mouseEvent){
drgObj.element.setStyle({'position':'absolute',width:'30%'})
drgObj.offset=[0,0]
}
06/07/08 @ 08:41
Comment from: Matt [Visitor] Email
Could be better written like:

$$('#available .cat').each(function(el) {
new Draggable(el,{revert: true});
});

etc.
14/10/08 @ 22:29
Comment from: Matthijs [Visitor] Email

After a long time of searching I gave up on searching a solution. I had searched every single site about the subject I could find with google. Then I thought of perhaps the solution lies within my CSS code. I placed my stylesheet back piece by piece until I found when the trouble started.

I came to the conclusion that position: relative; creates the problem when you use overflow: auto; and try to drag something to a other element than the container which has overflow: auto; on it.

That was the solution in my case. Maybe it will help other people who also got this problem too.

24/11/08 @ 18:45
Comment from: Michael Hodgins [Visitor] Email · http://www.copycatweb.co.uk

Hi

I couldn't get any of these suggestions to work but finally worked out this solution. Thought I'd post it here as this page ranks high in Google.

I added the following event handlers to the Draggable declaration (in my code, not draggable.js).

onStart: function(drgObj,mouseEvent) {
  if (Prototype.Browser.IE) {
    var element = drgObj.element;
    element.parentNode.removeChild(element);
    $(document.body).appendChild(element);
    element.setStyle({
      width: '230px'
    });
  }
},
onEnd: function(drgObj,mouseEvent) {
  if (Prototype.Browser.IE) {
    drgObj.element.setStyle({
      width: 'auto'
    });
  }
}

It bypasses IE's weird stacking order by moving the draggable element to the root node while it is being dragged (what isn't shown here is the event handler that is called when the element is dropped and inserts the draggable into the droparea but this didn't require any changes).

The width also has to be set (in my code at least) to stop the draggable element spanning the whole page.

I hope this helps someone.

Michael

04/03/09 @ 17:26
Comment from: robertc [Member] Email · http://www.boogdesign.com/
Thanks Michael. I added a PRE block to your code so the indentation is preserved.

Rob
04/03/09 @ 17:53
Comment from: Andres Garcia [Visitor] Email · http://www.dranes.com
I think this is the better solution, work for me at least

http://www.iterativedesigns.com/2007/11/9/dragdropextra-js

18/03/09 @ 14:44