Ext.ux.Portal = Ext.extend(Ext.Panel, {
    layout
: 'column',
    autoScroll
: true,
    cls
: 'x-portal',
    defaultType
: 'portalcolumn',
   
    initComponent
: function(){
       
Ext.ux.Portal.superclass.initComponent.call(this);
       
this.addEvents({
            validatedrop
:true,
            beforedragover
:true,
            dragover
:true,
            beforedrop
:true,
            drop
:true
       
});
   
},

    initEvents
: function(){
       
Ext.ux.Portal.superclass.initEvents.call(this);
       
this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig);
   
},
   
    beforeDestroy
: function() {
       
if(this.dd){
           
this.dd.unreg();
       
}
       
Ext.ux.Portal.superclass.beforeDestroy.call(this);
   
}
});

Ext.reg('portal', Ext.ux.Portal);


Ext.ux.Portal.DropZone = function(portal, cfg){
   
this.portal = portal;
   
Ext.dd.ScrollManager.register(portal.body);
   
Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);
    portal
.body.ddScrollConfig = this.ddScrollConfig;
};

Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, {
    ddScrollConfig
: {
        vthresh
: 50,
        hthresh
: -1,
        animate
: true,
        increment
: 200
   
},

    createEvent
: function(dd, e, data, col, c, pos){
       
return {
            portal
: this.portal,
            panel
: data.panel,
            columnIndex
: col,
            column
: c,
            position
: pos,
            data
: data,
            source
: dd,
            rawEvent
: e,
            status
: this.dropAllowed
       
};
   
},

    notifyOver
: function(dd, e, data){
       
var xy = e.getXY(), portal = this.portal, px = dd.proxy;

       
// case column widths
       
if(!this.grid){
           
this.grid = this.getGrid();
       
}

       
// handle case scroll where scrollbars appear during drag
       
var cw = portal.body.dom.clientWidth;
       
if(!this.lastCW){
           
this.lastCW = cw;
       
}else if(this.lastCW != cw){
           
this.lastCW = cw;
            portal
.doLayout();
           
this.grid = this.getGrid();
       
}

       
// determine column
       
var col = 0, xs = this.grid.columnX, cmatch = false;
       
for(var len = xs.length; col < len; col++){
           
if(xy[0] < (xs[col].x + xs[col].w)){
                cmatch
= true;
               
break;
           
}
       
}
       
// no match, fix last index
       
if(!cmatch){
            col
--;
       
}

       
// find insert position
       
var p, match = false, pos = 0,
            c
= portal.items.itemAt(col),
            items
= c.items.items, overSelf = false;

       
for(var len = items.length; pos < len; pos++){
            p
= items[pos];
           
var h = p.el.getHeight();
           
if(h === 0){
                overSelf
= true;
           
}
           
else if((p.el.getY()+(h/2)) > xy[1]){
                match
= true;
               
break;
           
}
       
}

        pos
= (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0);
       
var overEvent = this.createEvent(dd, e, data, col, c, pos);

       
if(portal.fireEvent('validatedrop', overEvent) !== false &&
           portal
.fireEvent('beforedragover', overEvent) !== false){

           
// make sure proxy width is fluid
            px
.getProxy().setWidth('auto');

           
if(p){
                px
.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);
           
}else{
                px
.moveProxy(c.el.dom, null);
           
}

           
this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false};
           
this.scrollPos = portal.body.getScroll();

            portal
.fireEvent('dragover', overEvent);

           
return overEvent.status;
       
}else{
           
return overEvent.status;
       
}

   
},

    notifyOut
: function(){
       
delete this.grid;
   
},

    notifyDrop
: function(dd, e, data){
       
delete this.grid;
       
if(!this.lastPos){
           
return;
       
}
       
var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p;

       
var dropEvent = this.createEvent(dd, e, data, col, c,
            pos
!== false ? pos : c.items.getCount());

       
if(this.portal.fireEvent('validatedrop', dropEvent) !== false &&
           
this.portal.fireEvent('beforedrop', dropEvent) !== false){

            dd
.proxy.getProxy().remove();
            dd
.panel.el.dom.parentNode.removeChild(dd.panel.el.dom);
           
           
if(pos !== false){
               
if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){
                    pos
++;
               
}
                c
.insert(pos, dd.panel);
           
}else{
                c
.add(dd.panel);
           
}
           
            c
.doLayout();

           
this.portal.fireEvent('drop', dropEvent);

           
// scroll position is lost on drop, fix it
           
var st = this.scrollPos.top;
           
if(st){
               
var d = this.portal.body.dom;
                setTimeout
(function(){
                    d
.scrollTop = st;
               
}, 10);
           
}

       
}
       
delete this.lastPos;
   
},

   
// internal cache of body and column coords
    getGrid
: function(){
       
var box = this.portal.bwrap.getBox();
        box
.columnX = [];
       
this.portal.items.each(function(c){
             box
.columnX.push({x: c.el.getX(), w: c.el.getWidth()});
       
});
       
return box;
   
},

   
// unregister the dropzone from ScrollManager
    unreg
: function() {
       
//Ext.dd.ScrollManager.unregister(this.portal.body);
       
Ext.ux.Portal.DropZone.superclass.unreg.call(this);
   
}
});