PDA

View Full Version : Drag and drop tabs on TabPanel



divestoclimb
2 Jul 2010, 10:38 AM
I've seen other threads where users have requested this, but no implementations so I thought I'd offer up the one I've built.

This is implemented as a plugin to apply to the TabPanel(s) you want to support drag-and-dropping of tabs. The original intent of the plugin is to allow reordering of tabs within a single TabPanel, but I think it will also work to move tabs between different panels if the ddGroup config option on the two TabPanels is the same.

TabPanels automatically activate a tab when the mouse is pressed, not on mouse click. This causes the side effect that a tab always get activated before it can be dragged. I added a boolean option to this plugin, tabSwitchOnClick, to change the TabPanel's behavior to activate on click instead of mousedown if you prefer it.

If a tab is moved within the same TabPanel, the "add" and "remove" events are suppressed and a "tabmove" event is fired in their place, passing the old and new index of the tab that was moved as parameters. If a tab is dragged to a different TabPanel (untested), discrete "remove" and "add" events are fired on the appropriate panels.



Ext.ns("Ext.plugin");

Ext.plugin.DragDropTabs = (function() {
var TabProxy = Ext.extend(Ext.dd.StatusProxy, {
constructor: function(config) {
TabProxy.superclass.constructor.call(this, config);
// Custom class needed on the proxy so the tab can
// be dropped in and retain its styles
this.el.addClass("dd-tabpanel-proxy");
},
reset: function(config) {
TabProxy.superclass.reset.apply(this, arguments);
this.el.addClass("dd-tabpanel-proxy");
this.ghost.removeClass(["x-tab-strip-top","x-tab-strip-bottom"]);
}
}),
TabDragZone = Ext.extend(Ext.dd.DragZone, {
constructor: function(tp, config) {
this.tabpanel = tp;
this.ddGroup = tp.ddGroup;
this.proxy = new TabProxy();
TabDragZone.superclass.constructor.call(this, tp.strip, config);
},
getDragData: function(e) {
var t = this.tabpanel.findTargets(e);
if(t.el && ! t.close) {
return {
ddel: t.el,
tabpanel: this.tabpanel,
repairXY: Ext.fly(t.el).getXY(),
item: t.item,
index: this.tabpanel.items.indexOf(t.item)
}
}
return false;
},
onInitDrag: function() {
TabDragZone.superclass.onInitDrag.apply(this, arguments);
var strip = this.tabpanel.strip,
ghostClass;
// In order for the tab to appear properly within the
// ghost, we need to add the correct class from the
// tab strip
if(strip.hasClass("x-tab-strip-top")) {
ghostClass = "x-tab-strip-top";
} else if(strip.hasClass("x-tab-strip-bottom")) {
ghostClass = "x-tab-strip-bottom";
}
this.proxy.getGhost().addClass(ghostClass);
return true;
},
getRepairXY: function() {
return this.dragData.repairXY;
}
}),
TabDropTarget = Ext.extend(Ext.dd.DropTarget, {
constructor: function(tp, config) {
this.tabpanel = tp;
this.ddGroup = tp.ddGroup;
TabDropTarget.superclass.constructor.call(this, tp.strip, config);
},
notifyDrop: function(dd, e, data) {
var tp = this.tabpanel,
t = tp.findTargets(e),
i = tp.items.indexOf(t.item),
activeTab = tp.getActiveTab();
if(tp === data.tabpanel) {
tp.suspendEvents();
}
data.tabpanel.remove(data.item, false);
tp.insert(i, data.item);
tp.setActiveTab(activeTab);
if(tp === data.tabpanel) {
tp.resumeEvents();
tp.fireEvent("tabmove", tp, i, data.index);
}
return true;
}
}),
// Methods attached to the TabPanel
onTabPanelRender = function() {
this.dragZone = new TabDragZone(this);
this.dropTarget = new TabDropTarget(this);
},
onTabPanelInitEvents = function() {
this.mun(this.strip, "mousedown", this.onStripMouseDown, this);
this.mon(this.strip, "click", this.onStripMouseDown, this);
},
onTabPanelDestroy = function() {
Ext.destroy(this.dragZone, this.dropTarget);
if(this.dragZone) {
this.dragZone.destroy();
}
if(this.dropTarget) {
this.dropTarget.destroy();
}
};

return {
init: function(tp) {
Ext.applyIf(tp, {
ddGroup:"TabPanelDD",
tabSwitchOnClick: false
});
tp.addEvents("tabmove");
tp.afterMethod("onRender", onTabPanelRender);
if(tp.tabSwitchOnClick) {
tp.afterMethod("initEvents", onTabPanelInitEvents);
}
tp.afterMethod("onDestroy", onTabPanelDestroy);
}
};
})();


Plugin tested with Ext 3.2. It uses a couple undocumented methods like TabPanel.findTargets and Observable.afterMethod so there's no guarantee it won't break on other releases of Ext JS.

To make the tabs appear correctly within the StatusProxy, some stylesheet tweaks are needed:



.dd-tabpanel-proxy .x-dd-drop-icon {
/* Adjust based on the height of tabs in your theme */
top: 8px;
}

.dd-tabpanel-proxy li {
display: inline;
}

.dd-tabpanel-proxy span.x-tab-strip-text {
padding: 4px 0;
}
.dd-tabpanel-proxy a, .dd-tabpanel-proxy span, .dd-tabpanel-proxy em {
display: block;
}


In addition, make sure all styles that apply to ".x-tab-strip span.x-tab-strip-text" are also applied to ".x-tab-strip-text".

Tested so far on Firefox 3.6, IE8 and IE7.

wemerson.januario
28 Aug 2010, 9:46 PM
Demo and Usage. thanks .
very nice work

tobiu
29 Aug 2010, 11:20 AM
hi divestoclimb,

a demo to play with would be nice.
there are other implementations in the forum btw ;)
i ported one from ext2 to ext3 -> http://www.sencha.com/forum/showthread.php?92033

since my todo list is way to long and the available time to short, i can't compare both ux.


kind regards,
tobiu