PDA

View Full Version : [3.1.1]Ext.ux.panel.DDTabPanel



tobiu
13 Feb 2010, 3:30 PM
hi together,

there are quite a lot of changes, but i think it runs now fine with ext 3.1.1.
i had to remove:


onRemove : function(c){
// DragSource cleanup on removed tabs
Ext.destroy(c.ds.proxy, c.ds);


because this caused massive errors (afterwards ext.fly caused firebug-errors on each mouseMove...

please let me know, if this ux works fine for you too and definitely tell me if the changes cause memory leaks.

kind regards,
tobiu



Ext.namespace('Ext.ux.panel');

/**
* @class Ext.ux.panel.DDTabPanel
* @extends Ext.TabPanel
* @author
* Original by
* <a href="http://extjs.com/forum/member.php?u=22731">thommy</a> and
* <a href="http://extjs.com/forum/member.php?u=37284">rizjoj</a><br />
* Published and polished by: Mattias Buelens (<a href="http://extjs.com/forum/member.php?u=41421">Matti</a>)<br />
* With help from: <a href="http://extjs.com/forum/member.php?u=1459">mystix</a>
* Polished and debugged by: Tobias Uhlig (info@internetsachen.com) 04-25-2009
* Ported to Ext-3.1.1 by: Tobias Uhlig (info@internetsachen.com) 02-14-2010
* @license Licensed under the terms of the Open Source <a href="http://www.gnu.org/licenses/lgpl.html">LGPL 3.0 license</a>. Commercial use is permitted to the extent that the code/component(s) do NOT become part of another Open Source or Commercially licensed development library or toolkit without explicit permission.
* @version 2.0.0 (Feb 14, 2010)
*/
Ext.ux.panel.DDTabPanel = Ext.extend(Ext.TabPanel, {
/**
* @cfg {Number} arrowOffsetX The horizontal offset for the drop arrow indicator, in pixels (defaults to -9).
*/
arrowOffsetX: -9,
/**
* @cfg {Number} arrowOffsetY The vertical offset for the drop arrow indicator, in pixels (defaults to -8).
*/
arrowOffsetY: -8,

// Assign the drag and drop group id
/** @private */
initComponent: function(){
Ext.ux.panel.DDTabPanel.superclass.initComponent.call(this);
if(!this.ddGroupId) this.ddGroupId = 'dd-tabpanel-group-' + Ext.ux.panel.DDTabPanel.superclass.getId.call(this);
},

// Declare the tab panel as a drop target
/** @private */
afterRender: function(){
Ext.ux.panel.DDTabPanel.superclass.afterRender.call(this);
// Create a drop arrow indicator
this.arrow = Ext.DomHelper.append(
Ext.getBody(),
'<div class="dd-arrow-down"></div>',
true
);
this.arrow.hide();
// Create a drop target for this tab panel
var tabsDDGroup = this.ddGroupId;
this.dd = new Ext.ux.panel.DDTabPanel.DropTarget(this, {
ddGroup: tabsDDGroup
});

// needed for the onRemove-Listener
this.move = false;
},

// Init the drag source after (!) rendering the tab
/** @private */
initTab: function(tab, index){
Ext.ux.panel.DDTabPanel.superclass.initTab.call(this, tab, index);

var id = this.id + '__' + tab.id;
// Enable dragging on all tabs by default
Ext.applyIf(tab, { allowDrag: true });

// Extend the tab
Ext.apply(tab, {
// Make this tab a drag source
ds: new Ext.dd.DragSource(id, {
ddGroup : this.ddGroupId
,dropEl : tab
,dropElHeader : Ext.get(id, true)
,scroll : false

// Update the drag proxy ghost element
,onStartDrag : function(){
if(this.dropEl.iconCls){

var el = this.getProxy().getGhost().select(".x-tab-strip-text");
el.addClass('x-panel-inline-icon');

var proxyText = el.elements[0].innerHTML;
proxyText = Ext.util.Format.stripTags(proxyText);
el.elements[0].innerHTML = proxyText;

el.applyStyles({
paddingLeft: "20px"
});
}
}

// Activate this tab on mouse up
// (Fixes bug which prevents a tab from being activated by clicking it)
,onMouseUp: function(event){
if(this.dropEl.ownerCt.move){
if(!this.dropEl.disabled && this.dropEl.ownerCt.activeTab == null){
this.dropEl.ownerCt.setActiveTab(this.dropEl);
}
this.dropEl.ownerCt.move = false;
return;
}
if(!this.dropEl.isVisible() && !this.dropEl.disabled){
this.dropEl.show();
}
}
})
// Method to enable dragging
,enableTabDrag: function(){
this.allowDrag = true;
return this.ds.unlock();
}
// Method to disable dragging
,disableTabDrag: function(){
this.allowDrag = false;
return this.ds.lock();
}
});

// Initial dragging state
if(tab.allowDrag){
tab.enableTabDrag();
}else{
tab.disableTabDrag();
}
}

/** @private */
,onRemove : function(c){
var te = Ext.get(c.tabEl);
// check if the tabEl exists, it won't if the tab isn't rendered
if(te){
// DragSource cleanup on removed tabs
//Ext.destroy(c.ds.proxy, c.ds);
te.select('a').removeAllListeners();
Ext.destroy(te);
}

// ignore the remove-function of the TabPanel
Ext.TabPanel.superclass.onRemove.call(this, c);

this.stack.remove(c);
delete c.tabEl;
c.un('disable' ,this.onItemDisabled ,this);
c.un('enable' ,this.onItemEnabled ,this);
c.un('titlechange' ,this.onItemTitleChanged ,this);
c.un('iconchange' ,this.onItemIconChanged ,this);
c.un('beforeshow' ,this.onBeforeShowItem ,this);

// if this.move, the active tab stays the active one
if(c == this.activeTab){
if(!this.move){
var next = this.stack.next();
if(next){
this.setActiveTab(next);
}else if(this.items.getCount() > 0){
this.setActiveTab(0);
}else{
this.activeTab = null;
}
}
else {
this.activeTab = null;
}
}
if(!this.destroying){
this.delegateUpdates();
}
}


// DropTarget and arrow cleanup
/** @private */
,onDestroy: function(){
Ext.destroy(this.dd, this.arrow);
Ext.ux.panel.DDTabPanel.superclass.onDestroy.call(this);
}
});

// Ext.ux.panel.DDTabPanel.DropTarget
// Implements the drop behavior of the tab panel
/** @private */
Ext.ux.panel.DDTabPanel.DropTarget = Ext.extend(Ext.dd.DropTarget, {
constructor: function(tabpanel, config){
this.tabpanel = tabpanel;
// The drop target is the tab strip wrap
Ext.ux.panel.DDTabPanel.DropTarget.superclass.constructor.call(this, tabpanel.stripWrap, config);
}

,notifyOver: function(dd, e, data){
var tabs = this.tabpanel.items;
var last = tabs.length;

if(!e.within(this.getEl()) || dd.dropEl == this.tabpanel){
return 'x-dd-drop-nodrop';
}

var larrow = this.tabpanel.arrow;

// Getting the absolute Y coordinate of the tabpanel
var tabPanelTop = this.el.getY();

var left, prevTab, tab;
var eventPosX = e.getPageX();

for(var i = 0; i < last; i++){
prevTab = tab;
tab = tabs.itemAt(i);
// Is this tab target of the drop operation?
var tabEl = tab.ds.dropElHeader;
// Getting the absolute X coordinate of the tab
var tabLeft = tabEl.getX();
// Get the middle of the tab
var tabMiddle = tabLeft + tabEl.dom.clientWidth / 2;

if(eventPosX <= tabMiddle){
left = tabLeft;
break;
}
}

if(typeof left == 'undefined'){
var lastTab = tabs.itemAt(last - 1);
if(lastTab == dd.dropEl)return 'x-dd-drop-nodrop';
var dom = lastTab.ds.dropElHeader.dom;
left = (new Ext.Element(dom).getX() + dom.clientWidth) + 3;
}

else if(tab == dd.dropEl || prevTab == dd.dropEl){
this.tabpanel.arrow.hide();
return 'x-dd-drop-nodrop';
}

larrow.setTop(tabPanelTop + this.tabpanel.arrowOffsetY).setLeft(left + this.tabpanel.arrowOffsetX).show();

return 'x-dd-drop-ok';
}

,notifyDrop: function(dd, e, data){
this.tabpanel.arrow.hide();

// no parent into child
if(dd.dropEl == this.tabpanel){
return false;
}
var tabs = this.tabpanel.items;
var eventPosX = e.getPageX();

for(var i = 0; i < tabs.length; i++){
var tab = tabs.itemAt(i);
// Is this tab target of the drop operation?
var tabEl = tab.ds.dropElHeader;
// Getting the absolute X coordinate of the tab
var tabLeft = tabEl.getX();
// Get the middle of the tab
var tabMiddle = tabLeft + tabEl.dom.clientWidth / 2;
if(eventPosX <= tabMiddle) break;
}

// do not insert at the same location
if(tab == dd.dropEl || tabs.itemAt(i-1) == dd.dropEl){
return false;
}

dd.proxy.hide();

// if tab stays in the same tabPanel
if(dd.dropEl.ownerCt == this.tabpanel){
if(i > tabs.indexOf(dd.dropEl))i--;
}

this.tabpanel.move = true;
var dropEl = dd.dropEl.ownerCt.remove(dd.dropEl, false);

this.tabpanel.insert(i, dropEl);

return true;
}

,notifyOut: function(dd, e, data){
this.tabpanel.arrow.hide();
}
});

Ext.reg('ddtabpanel', Ext.ux.panel.DDTabPanel);

miti
24 Feb 2010, 6:42 PM
Thanks for the update. Seems everything works fine except I have to override and comment out a line for the DragSource - otherwise I will get an error sometime when the drop is done - probably because dragsource is not properly destroyed?


,afterRepair : function(){
if(Ext.enableFx){
//this.el.highlight(this.hlColor || "c3daf9");
}
this.dragging = false;
}

firebug trace:

this.dom is undefined
hasFxBlock()ext-all-debug.js (line 8611)
queueFx(Object { name="o"}, Object { name="fn"})ext-all-debug.js (line 8618)
highlight(Object { name="color"}, Object { name="o"})ext-all-debug.js (line 8135)
afterRepair()DDTabPanel.js (line 109)
afterRepair()ext-all-debug.js (line 30649)
afterFx(Object { name="o"})ext-all-debug.js (line 8692)
(?)()ext-all-debug.js (line 8446)
apply()ext-base.js (line 7)
apply()ext-base.js (line 7)
fire()ext-all-debug.js (line 2415)
apply()ext-base.js (line 7)
fire()ext-all-debug.js (line 2415)
apply(Object { name="r"}, Object { name="q"})ext-base.js (line 7)
apply(Object { name="s"})ext-base.js (line 7)
apply()ext-base.js (line 7)
[Break on this error] var q = getQueue(this.dom.id);

tobiu
25 Feb 2010, 3:09 AM
hi miti,

tested quite a while and that error never occured to me.
so, i need further information (or a small testcase) to track it down.

the function afterRepair only gets called onInvalidDrop, working fine for me (tabs moving back with animation).

if you need the override, i suggest a change to:


if(this.el)this.el.highlight(this.hlColor || "c3daf9");


i changed the "no parent into child" condition of notifyDrop a bit for nested layouts, so that it is not possible to add a parent of any level into a child:



var parentEl = this.tabpanel.findParentBy(function(p){
if(p.id == dd.dropEl.id)return true;
return false;
});

// no parent of any level into child
if(dd.dropEl == this.tabpanel || dd.dropEl == parentEl){
return false;
}


kind regards,
tobiu

miti
25 Feb 2010, 3:47 PM
Wow thanks for the quick reply. My case is a little complicated. The target panel is like a docking pane in eclipse that you can drag a tab to but will be hidden if no tab is docked. So I do some special code to handle it and maybe that cause problem. The weird thing is the problematic .afterRepair is called after a VALID drop.


if (this.el) this.el.highlight(this.hlColor || "c3daf9");

won't help and this.el is some dragProxy. but


if (this.el.dom) this.el.highlight(this.hlColor || "c3daf9");

fixed the problem. Obviously the dom is gone but the el is still there.

tobiu
25 Feb 2010, 3:56 PM
it might be related to the point of the top-posting,
i had to remove


Ext.destroy(c.ds.proxy, c.ds);


which was resposible for cleaning up.
i have more time next week and will try to find another solution (that line causes ext-fly to run amok...).


kind regards,
tobiu

adam.jimenez
2 Mar 2010, 12:47 AM
Hi, thanks for this update.

It has the same issue that the original DDtabpanel had. When you move a tab it removes it and re-inserts it. This is a problem for me as it triggers my beforeRemove handler.

my rewritten notifydrop resolves this - altho it probably won't work between different tab groups:


,notifyDrop: function(dd, e, data){
this.tabpanel.arrow.hide();
var tabs = this.tabpanel.items;

var last = tabs.length;
var eventPosX = e.getPageX();

var newPos = last;
// dd.dropEl.position = last * 2 + 1; // default: 'behind the rest'

var dragLeft=true;

for(var i = 0; i < last-1; i++){
var tab = tabs.itemAt(i);

if( dd.dropEl.id==tab.id ){
dragLeft=false;
}

// Is this tab target of the drop operation?
var tabEl = tab.ds.dropElHeader;
// Getting the absolute X coordinate of the tab
var tabLeft = tabEl.getX();
// Get the middle of the tab
var tabMiddle = tabLeft + tabEl.dom.clientWidth / 2;

var tabRight = tabLeft + tabEl.dom.clientWidth*1.5;

if( dragLeft ){
if(eventPosX <= tabMiddle){
break;
}
}else{
if(eventPosX <= tabRight){
break;
}
}
}

//reset positions just in case new tabs were added
tabs.each(function(tab, index){
// alert(tab.title);
tab.position = (index + 1) * 2;
});

// Insert the tab element at the new position
// dd.proxy.hide();
if( i+1==last ){
dd.el.dom.parentNode.insertBefore(dd.el.dom, dd.el.dom.parentNode.childNodes[i].nextSibling);

dd.dropEl.position=(last*2)+1;
}else{
if( dragLeft ){
dd.el.dom.parentNode.insertBefore(dd.el.dom, dd.el.dom.parentNode.childNodes[i]);

dd.dropEl.position=((i)*2)+1;
}else{
dd.el.dom.parentNode.insertBefore(dd.el.dom, dd.el.dom.parentNode.childNodes[i+1]);

dd.dropEl.position=((i+1)*2)+1;
}
}

// Sort tabs by their actual position
tabs.sort('ASC', function(a, b){
return a.position - b.position;
});
// Adjust tab position values
tabs.each(function(tab, index){
tab.position = (index + 1) * 2;
});

//var dropEl = dd.dropEl.ownerCt.remove(dd.dropEl, false);
//this.tabpanel.insert(i, dropEl);
//this.tabpanel.activate(dropEl);

return true;
}

adam.jimenez
2 Mar 2010, 1:21 AM
Shouldn't the tabs activate onmousedown?

I've added this to Ext.apply:



,onMouseDown: function(event) {
if (!this.dropEl.isVisible()) {
this.dropEl.show(); // simply call the tab's show() method to activate it
}
}


or make it a config option..

tobiu
2 Mar 2010, 2:24 AM
hi adam,

with tabs activating onMousedown, it is not possible to drag and drop tabs without activating them. so this is a needed tradeoff from my point of view. could you please color your other changes?

kind regards,
tobiu

adam.jimenez
2 Mar 2010, 4:46 AM
hi adam,

with tabs activating onMousedown, it is not possible to drag and drop tabs without activating them. so this is a needed tradeoff from my point of view. could you please color your other changes?

kind regards,
tobiu


Apps like firefox/ chrome have tabs that activate onMouseDown. it's what users are used to. It could be made into a config option to keep everyone happy. e.g. activateTabsOnMouseDown= true/ false.

WRT my changes - I didn't modify the new one. I copied the code that I rewrote from the original DDTabPanel.

uptodate
16 Apr 2010, 2:14 AM
In ExtJs 3.2.0 i can no change tabs

uptodate
19 Apr 2010, 8:06 AM
Here my ExJs 3.2.0 Fix. (In ExtJs i can`t change the Tabs)


Ext.namespace('Ext.ux.panel');

/**
* @class Ext.ux.panel.DDTabPanel
* @extends Ext.TabPanel
* @author
* Original by
* <a href="http://extjs.com/forum/member.php?u=22731">thommy</a> and
* <a href="http://extjs.com/forum/member.php?u=37284">rizjoj</a><br />
* Published and polished by: Mattias Buelens (<a href="http://extjs.com/forum/member.php?u=41421">Matti</a>)<br />
* With help from: <a href="http://extjs.com/forum/member.php?u=1459">mystix</a>
* Polished and debugged by: Tobias Uhlig (info@internetsachen.com) 04-25-2009
* Ported to Ext-3.1.1 by: Tobias Uhlig (info@internetsachen.com) 02-14-2010
* @license Licensed under the terms of the Open Source <a href="http://www.gnu.org/licenses/lgpl.html">LGPL 3.0 license</a>. Commercial use is permitted to the extent that the code/component(s) do NOT become part of another Open Source or Commercially licensed development library or toolkit without explicit permission.
* @version 2.0.0 (Feb 14, 2010)
*/
Ext.ux.panel.DDTabPanel = Ext.extend(Ext.TabPanel, {
/**
* @cfg {Number} arrowOffsetX The horizontal offset for the drop arrow indicator, in pixels (defaults to -9).
*/
arrowOffsetX: -9,
/**
* @cfg {Number} arrowOffsetY The vertical offset for the drop arrow indicator, in pixels (defaults to -8).
*/
arrowOffsetY: -8,

// Assign the drag and drop group id
/** @private */
initComponent: function(){
Ext.ux.panel.DDTabPanel.superclass.initComponent.call(this);
if(!this.ddGroupId) this.ddGroupId = 'dd-tabpanel-group-' + Ext.ux.panel.DDTabPanel.superclass.getId.call(this);
},

// Declare the tab panel as a drop target
/** @private */
afterRender: function(){
Ext.ux.panel.DDTabPanel.superclass.afterRender.call(this);
// Create a drop arrow indicator
this.arrow = Ext.DomHelper.append(
Ext.getBody(),
'<div class="dd-arrow-down"></div>',
true
);
this.arrow.hide();
// Create a drop target for this tab panel
var tabsDDGroup = this.ddGroupId;
this.dd = new Ext.ux.panel.DDTabPanel.DropTarget(this, {
ddGroup: tabsDDGroup
});

// needed for the onRemove-Listener
this.move = false;
},

// Init the drag source after (!) rendering the tab
/** @private */
initTab: function(tab, index){
Ext.ux.panel.DDTabPanel.superclass.initTab.call(this, tab, index);

var id = this.id + '__' + tab.id;
// Hotfix 3.2.0
Ext.fly(id).on('click', function(){tab.ownerCt.setActiveTab(tab.id);});
// Enable dragging on all tabs by default
Ext.applyIf(tab, { allowDrag: true });

// Extend the tab
Ext.apply(tab, {
// Make this tab a drag source
ds: new Ext.dd.DragSource(id, {
ddGroup : this.ddGroupId
,dropEl : tab
,dropElHeader : Ext.get(id, true)
,scroll : false

// Update the drag proxy ghost element
,onStartDrag : function(){
if(this.dropEl.iconCls){

var el = this.getProxy().getGhost().select(".x-tab-strip-text");
el.addClass('x-panel-inline-icon');

var proxyText = el.elements[0].innerHTML;
proxyText = Ext.util.Format.stripTags(proxyText);
el.elements[0].innerHTML = proxyText;

el.applyStyles({
paddingLeft: "20px"
});
}
}

// Activate this tab on mouse up
// (Fixes bug which prevents a tab from being activated by clicking it)
,onMouseUp: function(event){
if(this.dropEl.ownerCt.move){
if(!this.dropEl.disabled && this.dropEl.ownerCt.activeTab == null){
this.dropEl.ownerCt.setActiveTab(this.dropEl);
}
this.dropEl.ownerCt.move = false;
return;
}
if(!this.dropEl.isVisible() && !this.dropEl.disabled){
this.dropEl.show();
}
}
})
// Method to enable dragging
,enableTabDrag: function(){
this.allowDrag = true;
return this.ds.unlock();
}
// Method to disable dragging
,disableTabDrag: function(){
this.allowDrag = false;
return this.ds.lock();
}
});

// Initial dragging state
if(tab.allowDrag){
tab.enableTabDrag();
}else{
tab.disableTabDrag();
}
}

/** @private */
,onRemove : function(c){
var te = Ext.get(c.tabEl);
// check if the tabEl exists, it won't if the tab isn't rendered
if(te){
// DragSource cleanup on removed tabs
//Ext.destroy(c.ds.proxy, c.ds);
te.select('a').removeAllListeners();
Ext.destroy(te);
}

// ignore the remove-function of the TabPanel
Ext.TabPanel.superclass.onRemove.call(this, c);

this.stack.remove(c);
delete c.tabEl;
c.un('disable' ,this.onItemDisabled ,this);
c.un('enable' ,this.onItemEnabled ,this);
c.un('titlechange' ,this.onItemTitleChanged ,this);
c.un('iconchange' ,this.onItemIconChanged ,this);
c.un('beforeshow' ,this.onBeforeShowItem ,this);

// if this.move, the active tab stays the active one
if(c == this.activeTab){
if(!this.move){
var next = this.stack.next();
if(next){
this.setActiveTab(next);
}else if(this.items.getCount() > 0){
this.setActiveTab(0);
}else{
this.activeTab = null;
}
}
else {
this.activeTab = null;
}
}
if(!this.destroying){
this.delegateUpdates();
}
}


// DropTarget and arrow cleanup
/** @private */
,onDestroy: function(){
Ext.destroy(this.dd, this.arrow);
Ext.ux.panel.DDTabPanel.superclass.onDestroy.call(this);
}
});

// Ext.ux.panel.DDTabPanel.DropTarget
// Implements the drop behavior of the tab panel
/** @private */
Ext.ux.panel.DDTabPanel.DropTarget = Ext.extend(Ext.dd.DropTarget, {
constructor: function(tabpanel, config){
this.tabpanel = tabpanel;
// The drop target is the tab strip wrap
Ext.ux.panel.DDTabPanel.DropTarget.superclass.constructor.call(this, tabpanel.stripWrap, config);
}

,notifyOver: function(dd, e, data){
var tabs = this.tabpanel.items;
var last = tabs.length;

if(!e.within(this.getEl()) || dd.dropEl == this.tabpanel){
return 'x-dd-drop-nodrop';
}

var larrow = this.tabpanel.arrow;

// Getting the absolute Y coordinate of the tabpanel
var tabPanelTop = this.el.getY();

var left, prevTab, tab;
var eventPosX = e.getPageX();

for(var i = 0; i < last; i++){
prevTab = tab;
tab = tabs.itemAt(i);
// Is this tab target of the drop operation?
var tabEl = tab.ds.dropElHeader;
// Getting the absolute X coordinate of the tab
var tabLeft = tabEl.getX();
// Get the middle of the tab
var tabMiddle = tabLeft + tabEl.dom.clientWidth / 2;

if(eventPosX <= tabMiddle){
left = tabLeft;
break;
}
}

if(typeof left == 'undefined'){
var lastTab = tabs.itemAt(last - 1);
if(lastTab == dd.dropEl)return 'x-dd-drop-nodrop';
var dom = lastTab.ds.dropElHeader.dom;
left = (new Ext.Element(dom).getX() + dom.clientWidth) + 3;
}

else if(tab == dd.dropEl || prevTab == dd.dropEl){
this.tabpanel.arrow.hide();
return 'x-dd-drop-nodrop';
}

larrow.setTop(tabPanelTop + this.tabpanel.arrowOffsetY).setLeft(left + this.tabpanel.arrowOffsetX).show();

return 'x-dd-drop-ok';
}

,notifyDrop: function(dd, e, data){
this.tabpanel.arrow.hide();

var parentEl = this.tabpanel.findParentBy(function(p){
if(p.id == dd.dropEl.id)return true;
return false;
});

// no parent of any level into child
if(dd.dropEl == this.tabpanel || dd.dropEl == parentEl){
return false;
}
var tabs = this.tabpanel.items;
var eventPosX = e.getPageX();

for(var i = 0; i < tabs.length; i++){
var tab = tabs.itemAt(i);
// Is this tab target of the drop operation?
var tabEl = tab.ds.dropElHeader;
// Getting the absolute X coordinate of the tab
var tabLeft = tabEl.getX();
// Get the middle of the tab
var tabMiddle = tabLeft + tabEl.dom.clientWidth / 2;
if(eventPosX <= tabMiddle) break;
}

// do not insert at the same location
if(tab == dd.dropEl || tabs.itemAt(i-1) == dd.dropEl){
return false;
}

dd.proxy.hide();

// if tab stays in the same tabPanel
if(dd.dropEl.ownerCt == this.tabpanel){
if(i > tabs.indexOf(dd.dropEl))i--;
}

this.tabpanel.move = true;
var dropEl = dd.dropEl.ownerCt.remove(dd.dropEl, false);

this.tabpanel.insert(i, dropEl);

// Event drop
this.tabpanel.fireEvent('drop', this.tabpanel);
return true;
}

,notifyOut: function(dd, e, data){
this.tabpanel.arrow.hide();
}
});

Ext.reg('ddtabpanel', Ext.ux.panel.DDTabPanel);

Rob_mac
26 Apr 2010, 6:53 AM
I too have encountered the problem with not being able to activate a tab by clicking on it. Seems to only be an issue when the tab in question has not been previously activated? The bit of code that seemed to be causing the problem was the visibility check which was returning true even though the tab was not the activate tab. Fixed by removing the visibility check from the onMouseUp handler but may be a better way :


if(!this.dropEl.disabled){
this.dropEl.show();
}

tobiu
26 Apr 2010, 7:28 AM
Hi Rob_mac,



if(!this.dropEl.disabled){
this.dropEl.show();
}


was inserted for forbidding to activate disabled tabs. I sometimes add tabs with the config disabled:true. In that case, the tab-headers get a gray font-color and i definitely don't want those tabs to be able to get activated (like i would not want disabled buttons to get clicked). You can enable tabs calling myTab.enable().


Kind regards,
tobiu

Rob_mac
26 Apr 2010, 7:40 AM
Hi tobiu,

Thanks for your reply. Looking at my post I don't think I made myself clear. I don't have a problem with the disabled checking, it was the visibility check that I ended up removing. So the code used to be:



if(!this.dropEl.isVisible() && !this.dropEl.disabled){
this.dropEl.show();
}


And I had to remove/comment out the isVisible() check in order to be able to activate a tab. Either that or use the hot fix posted earlier which calls setActiveTab() in the click handler.

Kind Regards

Rob_mac
26 Apr 2010, 8:02 AM
Hi tobiu,

Just to let you know that adding the hidden: true config to my tabs seems to resolve the issue. The isVisible() check then correctly returns false for the unactivated tabs.

:)

tobiu
26 Apr 2010, 8:05 AM
hi helen,

in this case i got you completely wrong =)
it has been a while since i ported this ux over...

the check for


if(!this.dropEl.isVisible())


i think it was designed to not be able to activate a tab, that is currently already activated.
it should be no problem to remove this part, if it makes trouble (or replace it with another condition to check, if it is the active-tab).
somehow strange, that the ux is still working fine in my fieldmanager-app. at least with firefox and chrome.


kind regards,
tobiu

Rob_mac
26 Apr 2010, 8:11 AM
Great. Thanks again for your help tobiu.

Kind Regards
Helen

brombs
20 Aug 2010, 4:03 AM
The component is great! I allowed myself to be extended to cover additional event 'reordering'. Is called the 'drop' but has an additional parameter 'tab.'. This is a tab that has been postponed.

Here is source with 'reordering' event:



Ext.namespace('Ext.ux.panel');

/**
* @class Ext.ux.panel.DDTabPanel
* @extends Ext.TabPanel
* @author
* Original by
* <a href="http://extjs.com/forum/member.php?u=22731">thommy</a> and
* <a href="http://extjs.com/forum/member.php?u=37284">rizjoj</a><br />
* Published and polished by: Mattias Buelens (<a href="http://extjs.com/forum/member.php?u=41421">Matti</a>)<br />
* With help from: <a href="http://extjs.com/forum/member.php?u=1459">mystix</a>
* Polished and debugged by: Tobias Uhlig (info@internetsachen.com) 04-25-2009
* Ported to Ext-3.1.1 by: Tobias Uhlig (info@internetsachen.com) 02-14-2010
* @license Licensed under the terms of the Open Source <a href="http://www.gnu.org/licenses/lgpl.html">LGPL 3.0 license</a>. Commercial use is permitted to the extent that the code/component(s) do NOT become part of another Open Source or Commercially licensed development library or toolkit without explicit permission.
* @version 2.0.0 (Feb 14, 2010)
*/
Ext.ux.panel.DDTabPanel = Ext.extend(Ext.TabPanel, {
/**
* @cfg {Number} arrowOffsetX The horizontal offset for the drop arrow indicator, in pixels (defaults to -9).
*/
arrowOffsetX: -9,
/**
* @cfg {Number} arrowOffsetY The vertical offset for the drop arrow indicator, in pixels (defaults to -8).
*/
arrowOffsetY: -8,

// Assign the drag and drop group id
/** @private */
initComponent: function() {
Ext.ux.panel.DDTabPanel.superclass.initComponent.call(this);
this.addEvents('reorder');
if (!this.ddGroupId) this.ddGroupId = 'dd-tabpanel-group-' + Ext.ux.panel.DDTabPanel.superclass.getId.call(this);
},

// New Event fired after drop tab
reorder:function(tab){
this.fireEvent('reorder', this, tab);
},

// Declare the tab panel as a drop target
/** @private */
afterRender: function() {
Ext.ux.panel.DDTabPanel.superclass.afterRender.call(this);
// Create a drop arrow indicator
this.arrow = Ext.DomHelper.append(
Ext.getBody(),
'<div class="dd-arrow-down"></div>',
true
);
this.arrow.hide();
// Create a drop target for this tab panel
var tabsDDGroup = this.ddGroupId;
this.dd = new Ext.ux.panel.DDTabPanel.DropTarget(this, {
ddGroup: tabsDDGroup
});

// needed for the onRemove-Listener
this.move = false;
},

// Init the drag source after (!) rendering the tab
/** @private */
initTab: function(tab, index) {
Ext.ux.panel.DDTabPanel.superclass.initTab.call(this, tab, index);

var id = this.id + '__' + tab.id;
// Hotfix 3.2.0
Ext.fly(id).on('click', function() { tab.ownerCt.setActiveTab(tab.id); });
// Enable dragging on all tabs by default
Ext.applyIf(tab, { allowDrag: true });

// Extend the tab
Ext.apply(tab, {
// Make this tab a drag source
ds: new Ext.dd.DragSource(id, {
ddGroup: this.ddGroupId
, dropEl: tab
, dropElHeader: Ext.get(id, true)
, scroll: false

// Update the drag proxy ghost element
, onStartDrag: function() {
if (this.dropEl.iconCls) {

var el = this.getProxy().getGhost().select(".x-tab-strip-text");
el.addClass('x-panel-inline-icon');

var proxyText = el.elements[0].innerHTML;
proxyText = Ext.util.Format.stripTags(proxyText);
el.elements[0].innerHTML = proxyText;

el.applyStyles({
paddingLeft: "20px"
});
}
}

// Activate this tab on mouse up
// (Fixes bug which prevents a tab from being activated by clicking it)
, onMouseUp: function(event) {
if (this.dropEl.ownerCt.move) {
if (!this.dropEl.disabled && this.dropEl.ownerCt.activeTab == null) {
this.dropEl.ownerCt.setActiveTab(this.dropEl);
}
this.dropEl.ownerCt.move = false;
return;
}
if (!this.dropEl.isVisible() && !this.dropEl.disabled) {
this.dropEl.show();
}
}
})
// Method to enable dragging
, enableTabDrag: function() {
this.allowDrag = true;
return this.ds.unlock();
}
// Method to disable dragging
, disableTabDrag: function() {
this.allowDrag = false;
return this.ds.lock();
}
});

// Initial dragging state
if (tab.allowDrag) {
tab.enableTabDrag();
} else {
tab.disableTabDrag();
}
}

/** @private */
, onRemove: function(c) {
var te = Ext.get(c.tabEl);
// check if the tabEl exists, it won't if the tab isn't rendered
if (te) {
// DragSource cleanup on removed tabs
//Ext.destroy(c.ds.proxy, c.ds);
te.select('a').removeAllListeners();
Ext.destroy(te);
}

// ignore the remove-function of the TabPanel
Ext.TabPanel.superclass.onRemove.call(this, c);

this.stack.remove(c);
delete c.tabEl;
c.un('disable', this.onItemDisabled, this);
c.un('enable', this.onItemEnabled, this);
c.un('titlechange', this.onItemTitleChanged, this);
c.un('iconchange', this.onItemIconChanged, this);
c.un('beforeshow', this.onBeforeShowItem, this);

// if this.move, the active tab stays the active one
if (c == this.activeTab) {
if (!this.move) {
var next = this.stack.next();
if (next) {
this.setActiveTab(next);
} else if (this.items.getCount() > 0) {
this.setActiveTab(0);
} else {
this.activeTab = null;
}
}
else {
this.activeTab = null;
}
}
if (!this.destroying) {
this.delegateUpdates();
}
}


// DropTarget and arrow cleanup
/** @private */
, onDestroy: function() {
Ext.destroy(this.dd, this.arrow);
Ext.ux.panel.DDTabPanel.superclass.onDestroy.call(this);
}
});

// Ext.ux.panel.DDTabPanel.DropTarget
// Implements the drop behavior of the tab panel
/** @private */
Ext.ux.panel.DDTabPanel.DropTarget = Ext.extend(Ext.dd.DropTarget, {
constructor: function(tabpanel, config){
this.tabpanel = tabpanel;
// The drop target is the tab strip wrap
Ext.ux.panel.DDTabPanel.DropTarget.superclass.constructor.call(this, tabpanel.stripWrap, config);
}

,notifyOver: function(dd, e, data){
var tabs = this.tabpanel.items;
var last = tabs.length;

if(!e.within(this.getEl()) || dd.dropEl == this.tabpanel){
return 'x-dd-drop-nodrop';
}

var larrow = this.tabpanel.arrow;

// Getting the absolute Y coordinate of the tabpanel
var tabPanelTop = this.el.getY();

var left, prevTab, tab;
var eventPosX = e.getPageX();

for(var i = 0; i < last; i++){
prevTab = tab;
tab = tabs.itemAt(i);
// Is this tab target of the drop operation?
var tabEl = tab.ds.dropElHeader;
// Getting the absolute X coordinate of the tab
var tabLeft = tabEl.getX();
// Get the middle of the tab
var tabMiddle = tabLeft + tabEl.dom.clientWidth / 2;

if(eventPosX <= tabMiddle){
left = tabLeft;
break;
}
}

if(typeof left == 'undefined'){
var lastTab = tabs.itemAt(last - 1);
if(lastTab == dd.dropEl)return 'x-dd-drop-nodrop';
var dom = lastTab.ds.dropElHeader.dom;
left = (new Ext.Element(dom).getX() + dom.clientWidth) + 3;
}

else if(tab == dd.dropEl || prevTab == dd.dropEl){
this.tabpanel.arrow.hide();
return 'x-dd-drop-nodrop';
}

larrow.setTop(tabPanelTop + this.tabpanel.arrowOffsetY).setLeft(left + this.tabpanel.arrowOffsetX).show();

return 'x-dd-drop-ok';
}

,notifyDrop: function(dd, e, data){
this.tabpanel.arrow.hide();

// no parent into child
if(dd.dropEl == this.tabpanel){
return false;
}
var tabs = this.tabpanel.items;
var eventPosX = e.getPageX();

for(var i = 0; i < tabs.length; i++){
var tab = tabs.itemAt(i);
// Is this tab target of the drop operation?
var tabEl = tab.ds.dropElHeader;
// Getting the absolute X coordinate of the tab
var tabLeft = tabEl.getX();
// Get the middle of the tab
var tabMiddle = tabLeft + tabEl.dom.clientWidth / 2;
if(eventPosX <= tabMiddle) break;
}

// do not insert at the same location
if(tab == dd.dropEl || tabs.itemAt(i-1) == dd.dropEl){
return false;
}

dd.proxy.hide();

// if tab stays in the same tabPanel
if(dd.dropEl.ownerCt == this.tabpanel){
if(i > tabs.indexOf(dd.dropEl))i--;
}

this.tabpanel.move = true;
var dropEl = dd.dropEl.ownerCt.remove(dd.dropEl, false);

this.tabpanel.insert(i, dropEl);
// Event drop
this.tabpanel.fireEvent('drop', this.tabpanel);
// Fire event reorder
this.tabpanel.reorder(tabs.itemAt(i));

return true;
}

,notifyOut: function(dd, e, data){
this.tabpanel.arrow.hide();
}
});

Ext.reg('ddtabpanel', Ext.ux.panel.DDTabPanel);
I usage this event, when i want make somthing with dropped tab,
example:


...
'reorder': function(tabPanel, tab) {
tab.show();
}
...
best regards, brombs

adam.jimenez
6 May 2011, 2:21 AM
This plugin is great. Has anyone got it working in Ext4?

Cyberdust
6 Sep 2011, 5:03 AM
Hi, thx for this good ux

i'm looking for the CSS but can't find it since http://extjs-ux.org (http://extjs-ux.org/repo/authors/Matti/trunk/Ext/ux/panel/DDTabPanel/demo.html) is closed.

Does anyone can point me to it plz ?

Edit : i've found some http://lib.shiftcreate.com/dev/ddtabs.php

.dd-arrow-down.dd-arrow-down-invisible {
display: none;
visibility: hidden;
}

.dd-arrow-down {
background-image: url(../images/dd-arrow-down.gif);
display: block;
visibility: visible;
z-index: 20000;
position: absolute;
width: 16px;
height: 16px;
top: 0;
left: 0;
}

Edit 2 : i've also added the following to hide the bullet points; Unfortunatly it doesn't target this plugin specifically but all li tags inside a dd ghost.

.x-dd-drag-ghost li {
list-style: none outside none;
}

BulletzBill
21 Nov 2011, 6:08 AM
This plugin is great. Has anyone got it working in Ext4?
Also looking for a Ext4 port of this extension, or something that gives comparable functionality.

tobiu
22 Nov 2011, 9:16 AM
hello everyone,

i am completely busy with my new job at Sencha and my S-CIRCLES side project, so i see no chance for porting it over before the next Sourc{ SenchaDev Conference -> end of April.

Feel free to port it over by yourselves though!

brittongr
19 Dec 2011, 2:23 PM
hello everyone,

i am completely busy with my new job at Sencha and my S-CIRCLES side project, so i see no chance for porting it over before the next Sourc{ SenchaDev Conference -> end of April.

Feel free to port it over by yourselves though!

Hi tobiu, congrats for the job...

I'm posting here because many of you maybe faced this issue, http://www.sencha.com/forum/showthread.php?163161-Drag-amp-Drop-Components-between-tabs-not-working&p=694835#post694835 i'm trying to drag components from one tab to another but it doesn't working, i can't get the instance of DropTarget from the sencond tab to be notified when i drag over it a panel from another tab...

i hope somebody could give me a hint or tell why is not posible... thanks

alexw23
12 Nov 2012, 1:10 PM
My use case is DnD tabs between two DDTabPanels, where neither is guaranteed to have tabs. Doing so, however, presents the following when a tab is dropped on an empty DDTabPanel:

Uncaught TypeError: Cannot read property 'ds' of undefined
var dom = lastTab.ds.dropElHeader.dom;

This error is because of an assumption that at least 1 tab exists, which is reasonable for a single TabPanel.

A simple fix:



,notifyOver: function(dd, e, data){
var tabs = this.tabpanel.items;
var last = tabs.length;

if(!e.within(this.getEl()) || dd.dropEl == this.tabpanel){
return 'x-dd-drop-nodrop';
}

// Fix for DnD between two TabPanels, when the DropTarget has no tabs.
if(last === 0){
return 'x-dd-drop-ok';
}

Joyfulbob
20 Nov 2012, 12:19 PM
Hello,

This looks great; however, exactly what can you drag/drop into the tab?

I tried various things and nothing works.

Would be nice to see more of an explanation of what this ux does and/or print screens and/or demo.

Thanks for your efforts!

anupkshah
12 Jan 2013, 12:07 PM
Hi,

Thanks for this useful functionality. I created a refactored version to work as a plugin rather than a subclass, as that would be useful for me in many scenarios. Hope it is of use for others?



Ext.namespace('Ext.ux.panel');

/**
* @class Ext.ux.panel.DDTabPanel
* @extends Ext.TabPanel
* @author
* Original by
* <a href="http://extjs.com/forum/member.php?u=22731">thommy</a> and
* <a href="http://extjs.com/forum/member.php?u=37284">rizjoj</a><br />
* Published and polished by: Mattias Buelens (<a href="http://extjs.com/forum/member.php?u=41421">Matti</a>)<br />
* With help from: <a href="http://extjs.com/forum/member.php?u=1459">mystix</a>
* Polished and debugged by: Tobias Uhlig (info@internetsachen.com) 04-25-2009
* Ported to Ext-3.1.1 by: Tobias Uhlig (info@internetsachen.com) 02-14-2010
* Updated by <a href="http://www.sencha.com/forum/member.php?56442-brombs">brombs</a>
* to include reorder event
* Modified by <a href="http://www.onenaught.com">Anup Shah</a> to work as a plugin
* instead of subclass of TabPanel
* @license Licensed under the terms of the Open Source <a href="http://www.gnu.org/licenses/lgpl.html">LGPL 3.0 license</a>.
* Commercial use is permitted to the extent that the code/component(s) do NOT
* become part of another Open Source or Commercially licensed development library
* or toolkit without explicit permission.
* @version 2.0.1 (Jan 11, 2013)
*/
Ext.ux.panel.DraggableTabs = Ext.extend(Object, {
constructor: function (config) {
if (config) {
Ext.apply(this, config);
}
},

init: function(tp) {
if ((tp instanceof Ext.TabPanel) === false)
return;

// make these available onto the TabPanel as per original plugin, where used externally
tp.arrowOffsetX = this.arrowOffsetX;
tp.arrowOffsetY = this.arrowOffsetY;

tp.addEvents('reorder');

// TODO: check if ddGroupId can be left as a property of this plugin rather than on the TabPanel
if (!tp.ddGroupId) {
tp.ddGroupId = 'dd-tabpanel-group-' + tp.getId();
}

// New Event fired after drop tab. Is there a cleaner way to do this?
tp.reorder = this.reorder;
tp.oldinitTab = tp.initTab;
tp.initTab = this.initTab;
tp.onRemove = this.onRemove;

tp.on('afterrender', this.afterRender, this);

this.tabPanel = tp;
},

destroy: function () {
tp.un('afterrender', this.afterRender, this);
delete this.tabPanel;
Ext.destroy(this.dd, this.arrow);
},

/**
* @cfg {Number} arrowOffsetX The horizontal offset for the drop arrow indicator, in pixels (defaults to -9).
*/
arrowOffsetX: -9,
/**
* @cfg {Number} arrowOffsetY The vertical offset for the drop arrow indicator, in pixels (defaults to -8).
*/
arrowOffsetY: -8,

reorder: function(tab) {
this.fireEvent('reorder', this, tab);
},

// Declare the tab panel as a drop target
/** @private */
afterRender: function () {
// Create a drop arrow indicator
this.tabPanel.arrow = Ext.DomHelper.append(
Ext.getBody(),
'<div class="dd-arrow-down"></div>',
true
);
this.tabPanel.arrow.hide();
// Create a drop target for this tab panel
var tabsDDGroup = this.tabPanel.ddGroupId;
this.dd = new Ext.ux.panel.DraggableTabs.DropTarget(this, {
ddGroup: tabsDDGroup
});

// needed for the onRemove-Listener
this.move = false;
},

// Init the drag source after (!) rendering the tab
/** @private */
initTab: function (tab, index) {
this.oldinitTab(tab, index);

var id = this.id + '__' + tab.id;
// Hotfix 3.2.0
Ext.fly(id).on('click', function () { tab.ownerCt.setActiveTab(tab.id); });
// Enable dragging on all tabs by default
Ext.applyIf(tab, { allowDrag: true });

// Extend the tab
Ext.apply(tab, {
// Make this tab a drag source
ds: new Ext.dd.DragSource(id, {
ddGroup: this.ddGroupId
, dropEl: tab
, dropElHeader: Ext.get(id, true)
, scroll: false

// Update the drag proxy ghost element
, onStartDrag: function () {
if (this.dropEl.iconCls) {

var el = this.getProxy().getGhost().select(".x-tab-strip-text");
el.addClass('x-panel-inline-icon');

var proxyText = el.elements[0].innerHTML;
proxyText = Ext.util.Format.stripTags(proxyText);
el.elements[0].innerHTML = proxyText;

el.applyStyles({
paddingLeft: "20px"
});
}
}

// Activate this tab on mouse up
// (Fixes bug which prevents a tab from being activated by clicking it)
, onMouseUp: function (event) {
if (this.dropEl.ownerCt.move) {
if (!this.dropEl.disabled && this.dropEl.ownerCt.activeTab == null) {
this.dropEl.ownerCt.setActiveTab(this.dropEl);
}
this.dropEl.ownerCt.move = false;
return;
}
if (!this.dropEl.isVisible() && !this.dropEl.disabled) {
this.dropEl.show();
}
}
})
// Method to enable dragging
, enableTabDrag: function () {
this.allowDrag = true;
return this.ds.unlock();
}
// Method to disable dragging
, disableTabDrag: function () {
this.allowDrag = false;
return this.ds.lock();
}
});

// Initial dragging state
if (tab.allowDrag) {
tab.enableTabDrag();
} else {
tab.disableTabDrag();
}
}

/** @private */
, onRemove: function (c) {
var te = Ext.get(c.tabEl);
// check if the tabEl exists, it won't if the tab isn't rendered
if (te) {
// DragSource cleanup on removed tabs
//Ext.destroy(c.ds.proxy, c.ds);
te.select('a').removeAllListeners();
Ext.destroy(te);
}

// ignore the remove-function of the TabPanel
Ext.TabPanel.superclass.onRemove.call(this, c);

this.stack.remove(c);
delete c.tabEl;
c.un('disable', this.onItemDisabled, this);
c.un('enable', this.onItemEnabled, this);
c.un('titlechange', this.onItemTitleChanged, this);
c.un('iconchange', this.onItemIconChanged, this);
c.un('beforeshow', this.onBeforeShowItem, this);

// if this.move, the active tab stays the active one
if (c == this.activeTab) {
if (!this.move) {
var next = this.stack.next();
if (next) {
this.setActiveTab(next);
} else if (this.items.getCount() > 0) {
this.setActiveTab(0);
} else {
this.activeTab = null;
}
}
else {
this.activeTab = null;
}
}
if (!this.destroying) {
this.delegateUpdates();
}
}
});

Ext.preg('draggabletabs', Ext.ux.panel.DraggableTabs);

// Ext.ux.panel.DraggableTabs.DropTarget
// Implements the drop behavior of the tab panel
/** @private */
Ext.ux.panel.DraggableTabs.DropTarget = Ext.extend(Ext.dd.DropTarget, {
constructor: function (dd, config) {
this.tabpanel = dd.tabPanel;
// The drop target is the tab strip wrap
Ext.ux.panel.DraggableTabs.DropTarget.superclass.constructor.call(this, this.tabpanel.stripWrap, config);
}

, notifyOver: function (dd, e, data) {
var tabs = this.tabpanel.items;
var last = tabs.length;

if (!e.within(this.getEl()) || dd.dropEl == this.tabpanel) {
return 'x-dd-drop-nodrop';
}

var larrow = this.tabpanel.arrow;

// Getting the absolute Y coordinate of the tabpanel
var tabPanelTop = this.el.getY();

var left, prevTab, tab;
var eventPosX = e.getPageX();

for (var i = 0; i < last; i++) {
prevTab = tab;
tab = tabs.itemAt(i);
// Is this tab target of the drop operation?
var tabEl = tab.ds.dropElHeader;
// Getting the absolute X coordinate of the tab
var tabLeft = tabEl.getX();
// Get the middle of the tab
var tabMiddle = tabLeft + tabEl.dom.clientWidth / 2;

if (eventPosX <= tabMiddle) {
left = tabLeft;
break;
}
}

if (typeof left == 'undefined') {
var lastTab = tabs.itemAt(last - 1);
if (lastTab == dd.dropEl) return 'x-dd-drop-nodrop';
var dom = lastTab.ds.dropElHeader.dom;
left = (new Ext.Element(dom).getX() + dom.clientWidth) + 3;
}

else if (tab == dd.dropEl || prevTab == dd.dropEl) {
this.tabpanel.arrow.hide();
return 'x-dd-drop-nodrop';
}

larrow.setTop(tabPanelTop + this.tabpanel.arrowOffsetY).setLeft(left + this.tabpanel.arrowOffsetX).show();

return 'x-dd-drop-ok';
}

, notifyDrop: function (dd, e, data) {
this.tabpanel.arrow.hide();

// no parent into child
if (dd.dropEl == this.tabpanel) {
return false;
}
var tabs = this.tabpanel.items;
var eventPosX = e.getPageX();

for (var i = 0; i < tabs.length; i++) {
var tab = tabs.itemAt(i);
// Is this tab target of the drop operation?
var tabEl = tab.ds.dropElHeader;
// Getting the absolute X coordinate of the tab
var tabLeft = tabEl.getX();
// Get the middle of the tab
var tabMiddle = tabLeft + tabEl.dom.clientWidth / 2;
if (eventPosX <= tabMiddle) break;
}

// do not insert at the same location
if (tab == dd.dropEl || tabs.itemAt(i - 1) == dd.dropEl) {
return false;
}

dd.proxy.hide();

// if tab stays in the same tabPanel
if (dd.dropEl.ownerCt == this.tabpanel) {
if (i > tabs.indexOf(dd.dropEl)) i--;
}

this.tabpanel.move = true;
var dropEl = dd.dropEl.ownerCt.remove(dd.dropEl, false);

this.tabpanel.insert(i, dropEl);
// Event drop
this.tabpanel.fireEvent('drop', this.tabpanel);
// Fire event reorder
this.tabpanel.reorder(tabs.itemAt(i));

return true;
}

, notifyOut: function (dd, e, data) {
this.tabpanel.arrow.hide();
}
});


Example usage:



var tabPanel = new Ext.TabPanel({
plugins: new Ext.ux.panel.DraggableTabs(),
renderTo: document.body,
activeTab: 0,
width:300,
height:150,
items:[{
title: 'Tab 1',
html: "Tab 1 contents"
},{
title: 'Tab 2',
html: "Tab 2 contents"
},{
title: 'Tab 3',
html: "Tab 3 contents"
}],
listeners: {
reorder: {
fn: function (tabPanel, movedTab) {
// do something here
}
}
}
});


Further info and working demo is here (http://www.onenaught.com/posts/502/ext-js-tabpanel-plugin-for-draggable-tabs).