Also looking for a Ext4 port of this extension, or something that gives comparable functionality.
Printable View
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/showthre...835#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
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:
PHP Code:,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';
}
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!
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?
Example usage:Code: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();
}
});
Further info and working demo is here.Code: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
}
}
}
});