PDA

View Full Version : Sliding Tabs - Google Chrome Style - Ext.ux.SlidingTabPanel



scipio
14 Jan 2010, 3:36 AM
Hi All,

I finally got around to starting a blog and giving back to the community.

Ext.ux.SlidingTabPanel, Version 1.0 - 1/14/2010

This extension creates a standard TabPanel and alters it so that the user can re-order the tabs, similar to the Google Chrome Browser.


Works in IE 6+, FF, Webkit
Works in Ext JS 3.1, 3.0, 2.3 (these are all the versions of Ext JS I tested)


License: GPL v3... I wish I could release it under the MIT but I am not sure if I can comply with the FLOSS requirements.

You can demo it here. (http://jakeknerr.com/2009/01/14/sliding-tabs-in-an-extjs-tabpanel/)

Download the source at the demo site.

More extensions to come...

Animal
14 Jan 2010, 4:01 AM
That's very sexy!

Can you make it a plugin of TabPanel rather than a subclass?

Animal
14 Jan 2010, 4:07 AM
Can I suggest that demo code be droppable into an examples directory, on anyone's machnie so



<html>
<head>
<title>Chrome Style Sliding Tabs</title>
<link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="../../ext-all.js"></script>

<script type="text/javascript" src="slidingtabs.js"></script>
<script type="text/javascript" src="../ux/TabCloseMenu.js"></script>

<script type="text/javascript">

Ext.onReady(function(){

window.loaded = true;

var tabs = new Ext.ux.SlidingTabPanel({
renderTo:'tabs',
resizeTabs:true, // turn on tab resizing
tabWidth:135,
enableTabScroll:false,
width:645,
height:250,
slideDuration: .15,
defaults:{bodyStyle:'padding:10px;'},
plugins: new Ext.ux.TabCloseMenu()
});

// Tab generation code
var index = 0;
while(index < 4){
addTab();
}
function addTab(){
tabs.add({
title: 'New Tab ' + (++index),
html: 'Tab Body ' + (index) + '<br/><br/><div>Bogus Markup</div>',
closable:true
}).show();
}

new Ext.Button({
text: 'Add A New Tab',
handler: addTab
}).render(document.body, 'tabs');

});

</script>
</head>
<body>
<div id="tabs" style="margin:15px 0;"></div>
</body>
</html>

scipio
14 Jan 2010, 4:35 AM
Thanks Animal for the compliment and the suggestions.

I should look into creating a plugin version, makes sense.

veenvliet.morion
14 Jan 2010, 5:17 AM
Looking sharp!!!

One suggestion to make it more work like google chrome like explained here:
http://www.theinvisibl.com/news/2009/12/08/a-piece-with-a-lot-of-screenshots-about-the-close-tab-behaviour-in-google-chrome/

The tabs don't move (get bigger) when a tab is closed, but when the mouse moves outside the tabbar area.

makana
14 Jan 2010, 5:37 AM
whohoo! that rocks! \:D/
Thanks for sharing!! :D

mschwartz
14 Jan 2010, 7:03 AM
Quite awesome.

Extra credit if you go the whole 9 yards and make it so you can tear off the tab and drag it to your desktop!

(I kid, I kid)

scipio
14 Jan 2010, 2:21 PM
Thanks guys, the compliments are really appreciated.

veenvliet.morion - Thanks for the link, I'll check that out.

mschwartz - Could be done using adobe AIR, would be an immense pain in the arse though. Would be easier to design it so that a user could tear off a tab to a floated Ext.Panel in the browser. In fact, that is a good idea. Thanks.

mitchellsimoens
14 Jan 2010, 3:30 PM
mschwartz - Could be done using adobe AIR, would be an immense pain in the arse though. Would be easier to design it so that a user could tear off a tab to a floated Ext.Panel in the browser.

Using AIR, you could have a tabpanel at the top (or anywhere) and when you tear off a tab you could open another AIR window.

xiexueze
18 Jan 2010, 5:34 PM
That is cool !! thanks for sharing !!

I encounter some problems when i put tabpanel into portal ,the tabpane's DD come into collision with the portal's. I change the code like this:



handleMouseDown function:

if (this.clickValidator(e)) {
this.setStartPosition(); // Set the initial element position
this.b4MouseDown(e);
this.onMouseDown(e);
this.DDM.handleMouseDown(e, this);

this.tabpanel.onStripMouseDown(e);//active the click tab

this.DDM.stopEvent(e); // Must remove this event swallower for the tabpanel to work
}

endDrag function:

Ext.dd.DDM.useCache = true;
Ext.dd.DDM.mode = 0;//set the mode


hope this is userful

scipio
18 Jan 2010, 6:54 PM
handleMouseDown function:

if (this.clickValidator(e)) {
this.setStartPosition(); // Set the initial element position
this.b4MouseDown(e);
this.onMouseDown(e);
this.DDM.handleMouseDown(e, this);

this.tabpanel.onStripMouseDown(e);//active the click tab

this.DDM.stopEvent(e); // Must remove this event swallower for the tabpanel to work
}

endDrag function:

Ext.dd.DDM.useCache = true;
Ext.dd.DDM.mode = 0;//set the mode


hope this is userful

Thanks for the tip. I should have mentioned that for apps that have overlapping drag/drop implementations and/or mousedown listeners, users should expect to tweak the event handling. The event listeners timings can get a bit hairy due to the fact that the extension already has multiple mousedown listeners, so if you add any additional mousedown listeners on top of the 2 pre-existing ones, expect a little tweaking to get everything to play nice.

evant
18 Jan 2010, 7:06 PM
Very cool, nicely done.

meesham
18 Jan 2010, 11:49 PM
I really like this, thanks for you work. I've noticed one problem where if you only have 1 tab and you drag it it throws an error. It's on line 156, this.targetProxy is null in this case. I fix it I just put line 156 and 157 inside an if statement checking that this.targetProxy is an object.

ozum
19 Jan 2010, 2:11 AM
Very nice, thank you for sharing.

scipio
19 Jan 2010, 5:15 AM
I really like this, thanks for you work. I've noticed one problem where if you only have 1 tab and you drag it it throws an error. It's on line 156, this.targetProxy is null in this case. I fix it I just put line 156 and 157 inside an if statement checking that this.targetProxy is an object.

Thanks for the heads up. I added the fix.

NateWorcester
19 Jan 2010, 8:14 AM
Hi Scipio!

I wanted to say that i LOVE your sliding tabs component and I was curious if you had any plans to release it under a different license like BSD. I'd love to use it in an application i'm building but i can't use anything with GPL.

Thanks!
-Nate

scipio
19 Jan 2010, 3:02 PM
I was curious if you had any plans to release it under a different license like BSD. I'd love to use it in an application i'm building but i can't use anything with GPL.

Hi Nate, I would love to release the extension under a license other than the GPL, but I don't think I can and I'll explain why.

A. FLOSS Exception, or Open Source License Exception for Development License: Can I release the extension under an MIT, BSD, or other permissive license using ExtJS's FLOSS exception to the GPL?

No, because the FLOSS Exception states in section 2 (a): You are free to distribute an Extension licensed under one or more of the licenses listed below in section 5, as long as:, Your Extension does not contain any Code or modified Code from the Library.

The slidingTabs extension does invoke and override several methods of the Ext.TabPanel, and for at least 1 method, I directly copied some code in the Ext JS 'library' into my overrided method. Therefore, it contains modified code from the library.

Perhaps my understanding of 'modified code' is overly broad because it implies that virtually no one can use the FLOSS exception, but since the Ext JS team provides no examples or guidance on the matter, I must assume that I fall outside the exception.

B. Commercial License: If I had a commercial license for extJS, could I release the extension under a commercia license?

No, because the commercial license agreement states in section (4): You are explicitly not allowed to redistribute the Software or Modifications as part of any product that can be described as a development toolkit or library or is intended for use by software developers and not end-users.

My reading of this indicates to me that since my extension is a modification of the ExtJS library and is directed to developers instead of end-users, it is a prohibited use for a commercial license.

I would love to hear from the community and/or the ExtJS dev team as to why I am wrong, because I would like to release some extensions under a more permissive license and for some extensions I would like to sell a commercial license.

Please do not tell me to email licensing@extjs.com, I have tried that several times, and they have not answered my questions.

All feedback, however, is strongly encouraged.

NateWorcester
19 Jan 2010, 3:36 PM
You are free to distribute an Extension licensed under one or more of the licenses listed below in section 5, as long as:, Your Extension does not contain any Code or modified Code from the Library.

For the definition of modified code, see also this, taken from the Ext License FAQ:


The simple rule to follow is if you modify any functionality or file in an Ext product for a purpose other than configuration, you have created a modification. All modifications naturally are covered by the GPL v3 license. Additional information is available in the official GPL FAQ.
The following are examples of modifications:

* Modify Ext JavaScript, Java or CSS source file
* Extend Ext class or override any Ext functions or methods
* Modifying an Ext API

So if i understand this correctly:

You may release any extension under a permissive license so long as you are changing anything from Ext. Does that sound correct?

I'd like for the Ext team to give us an example of an extension that CAN be released under a permissive license. If you were to take the phrase "modify any functionality" literally then only extensions which don't do anything would be allowed.

However, It would seem that you could make the following changes to your code and it would be free of 'modified code':

1) Change it to a plugin instead of a tabpanel sub class
2) In the plugin, instead of overriding the original tab panel method, setup your init tab method as a sequence of the original method.

You would still be "modifying functionality"

Thoughts?

mschwartz
20 Jan 2010, 9:22 AM
I cannot believe that the Ext license is so restrictive that you cannot even pay for a license and extend a class without making your whole product GPL.

NateWorcester
20 Jan 2010, 9:36 AM
I cannot believe that the Ext license is so restrictive that you cannot even pay for a license and extend a class without making your whole product GPL.

The restriction on modifications does not apply to your project if you have a license. However, you if you release your modifications, they must be released under GPL.

This doesn't make any sense to me. In order for me to use Ext itself in a commercial project, I have to pay for a license. So in order to use any extensions to Ext in my project, no matter what the license is, I will have already paid for my Ext license.

So the way I see it, Ext extensions should allow for any license except for one that requires the user to pay (you would need an Ext OEM license to sell extensions).

Say this plugin was released under a permissive license like MIT. There are two cases:
1) I use it an open source project. Ext doesn't get paid because I don't need to pay for an Ext license.
2) I use it in a commercial project. Ext gets paid since I would need an Ext commercial license to use the plugin in a commercial project.

It seems to me that the existing licensing model is too restrictive, even stifling to the community. If i build an extension and release it here, it can only be used in GPL software, even if i don't care who uses it!

Ext extensions could even be released under the Ext license (unless i have an OEM license): If you paid for Ext, you can use the extension in your commercial project. Otherwise, you can't! It could be that simple.

scipio
20 Jan 2010, 1:21 PM
I read on Ed Spencer's blog that Ext is planning on an Ext Marketplace in the future. My suspicion is that once the marketplace is up and running, commercial licenses to extensions will become available for developers.

Bottom line is that I am unsure of what is or isn't permissible with extensions, except that extensions can be released GPL.

Scorpie
21 Jan 2010, 6:36 AM
Simply beautiful.

Ytorres
22 Jan 2010, 11:33 AM
Greats ux scipio ! beautifull

Could I suggest you to add some events ?

~line 14


Ext.ux.SlidingTabPanel = Ext.extend(Ext.TabPanel, {

initTab: function(item, index){
Ext.ux.SlidingTabPanel.superclass.initTab.call(this, item, index);

this.addEvents({
startDrag : true,
endDrag : true
});
......


~line 62


......
,startDrag: function(x, y) {

// Fire the startDrag event - panel & currentTab will be pass into it
this.tabpanel.fireEvent('startDrag', this.tabpanel, this.tabpanel.getActiveTab());

Ext.dd.DDM.useCache = false; // Disable caching of element location
Ext.dd.DDM.mode = 1; // Point mode
......


~line 173, at the end of endDrag method


......
Ext.dd.DDM.useCache = true;

// Fire the endDrag event - panel & currentTab will be pass into it
this.tabpanel.fireEvent('endDrag', this.tabpanel, this.tabpanel.getActiveTab());
......


Like this, we can monitor the drag process.

Best,
Yannick

Ytorres
24 Jan 2010, 1:00 AM
Another's suggestions,

Once the drag is done, the panel don't have the representation of this move (e.g. items elements are not in the same order as in the DOM).

So, when we want to now the current tab order, we get the order before the drag & drop.

I have added a new method to achieve this :



reorderTab: function() {

var tabsEl = this.tabpanel.header.child('ul').dom.children,
tabsId = [],
tabsOrigin = [];

for ( var i=0; i < tabsEl.length; i++ ) {
if( tabsEl[i].id.substr(0, this.tabpanel.id.length) == this.tabpanel.id ) {
tabsId.push( tabsEl[i].id.substr((this.tabpanel.id.length+2), tabsEl[i].id.length ) );
}
}

// Now, tabsId is the real list ordered of the tab's id
// We put this order into parent element

// We get the original reference of this tabs
for( var i=0; i < this.tabpanel.items.items.length; i++ ) {
tabsOrigin[this.tabpanel.items.items[i].id] = this.tabpanel.items.items[i];
}

for( var i=0; i < tabsId.length; i++ ) {
// order the keys
this.tabpanel.items.keys[i] = tabsId[i];
// order the elements
this.tabpanel.items.items[i] = tabsOrigin[tabsId[i]];
}

}


and the call of this new method at the end of endDrag method :



Ext.dd.DDM.useCache = true;

// Reorder all tabs to reflect this change
this.reorderTab();

// Fire the startDrag event
this.tabpanel.fireEvent('endDrag', this.tabpanel, this.tabpanel.getActiveTab());


To explain why I need this : Into each tab of my tabPanel, I have 2 buttons to go to the next and previous tab. Before this change, I can't know who is the next/previous tab after a tabs drag&drop. Now, I can ;)

Best,
Yannick

scipio
24 Jan 2010, 10:30 AM
Yannick,

Thanks for your suggestions, they are both good additions to the code.

I was surprised when I checked the API that the default dragDrop implementation doesn't have any drag events associated with it. Maybe the default DD should have events added to it.

cherbert
26 Jan 2010, 3:56 PM
Is it stateful? Your demo doesn't appear to be and this feature would be far easier than having to write database code to save the layout when the user resumes his or her session.

stever
26 Jan 2010, 9:30 PM
I read on Ed Spencer's blog that Ext is planning on an Ext Marketplace in the future. My suspicion is that once the marketplace is up and running, commercial licenses to extensions will become available for developers.

Bottom line is that I am unsure of what is or isn't permissible with extensions, except that extensions can be released GPL.

No, there is an explicit UX exception:


We want people building extensions, developer toolkits and frameworks, language packs and themes for Ext libraries to be able to publicly distribute them under less restrictive license terms despite the fact that version 3 of the GNU General Public License (the "GPL") may require them to be licensed under the GPL.

From http://www.extjs.com/products/ux-exception.php


It also mentions how you can use the MIT license...

PS: Nice work!

NateWorcester
27 Jan 2010, 8:20 AM
No, there is an explicit UX exception

Wow! That's exactly what I needed to read! It would make sense that there should be an exception for UXs for the exact reasons i stated above. Thanks for that link.

That being said, it would be easy to convert this into a plugin which contains no library code or modified library code.

scipio
28 Jan 2010, 8:09 AM
No, there is an explicit UX exception:



From http://www.extjs.com/products/ux-exception.php


It also mentions how you can use the MIT license...

PS: Nice work!

Read my earlier posts on this subject in the thread, specifically #17. I was quoting the exception you cite in that post but I don't think it is available to me because this extension uses modified code from the library. See section 2(a) of the FLOSS exception.

NateWorcester
28 Jan 2010, 8:21 AM
Is the only modified code what you have in the initTab method? Because you could easily refactor it like so:



Ext.ux.SlidingTabPanelPlugin = Ext.extend(Object, {

init: function(tabpanel){
tabpanel.initTab = tabpanel.initTab.createSequence(this.initTab,tabpanel);
}

,initTab: function(item, index){

var p = this.getTemplateArgs(item);
if(!this.slidingTabsID) this.slidingTabsID = Ext.id(); // Create a unique ID for this tabpanel
new Ext.ux.DDSlidingTab(p, this.slidingTabsID, {
tabpanel:this // Pass a reference to the tabpanel for each dragObject
});
}
});

scipio
29 Jan 2010, 9:55 AM
Is the only modified code what you have in the initTab method? Because you could easily refactor it like so:



Ext.ux.SlidingTabPanelPlugin = Ext.extend(Object, {

init: function(tabpanel){
tabpanel.initTab = tabpanel.initTab.createSequence(this.initTab,tabpanel);
}

,initTab: function(item, index){

var p = this.getTemplateArgs(item);
if(!this.slidingTabsID) this.slidingTabsID = Ext.id(); // Create a unique ID for this tabpanel
new Ext.ux.DDSlidingTab(p, this.slidingTabsID, {
tabpanel:this // Pass a reference to the tabpanel for each dragObject
});
}
});


Thanks Nate, I'll look into this weekend when the Wife's asleep. I think I tried refactoring the initTab method back when I built this (was over 1 year ago), but it kept screwing up the event handler for the tabPanel.

crenix
2 Feb 2010, 10:43 AM
Yea, MIT is a must. If not, then LGPL would do.

dawesi
5 Feb 2010, 5:42 PM
very cool!

uwolfer
10 Feb 2010, 12:57 AM
Using this SlidingTabPanel together with another drag'n'drop component seems not to work. In my case, there is also an Ext.ux.Portal in the same app. The sliding tabs continue working, but the portal does not work anymore after the first drag operation in the sliding tab. See the following error messages:


Uncaught TypeError: Cannot read property 'isNotifyTarget' of null
ext3/src/dd/DragSource.js:62
Uncaught TypeError: Cannot read property 'isNotifyTarget' of null
src/dd/DragSource.js:105

Any idea?

jay@moduscreate.com
10 Feb 2010, 5:18 AM
Thanks for doing this. You saved me time ;)

letssurf
10 Feb 2010, 6:33 AM
Using this SlidingTabPanel together with another drag'n'drop component seems not to work. In my case, there is also an Ext.ux.Portal in the same app. The sliding tabs continue working, but the portal does not work anymore after the first drag operation in the sliding tab. See the following error messages:


Uncaught TypeError: Cannot read property 'isNotifyTarget' of null
ext3/src/dd/DragSource.js:62
Uncaught TypeError: Cannot read property 'isNotifyTarget' of null
src/dd/DragSource.js:105

Any idea?

I had the same issue.

Putting

Ext.dd.DDM.mode = 0;
on line 185 seemed to fix it.

Not sure why though? lol

uwolfer
10 Feb 2010, 6:56 AM
Thanks a lot, letssurf. Fixed my issue.

scipio
10 Feb 2010, 10:42 AM
I had the same issue.

Putting

Ext.dd.DDM.mode = 0;
on line 185 seemed to fix it.

Not sure why though? lol

Hi guys, I wish I had listed this widget a long time ago because my memory of the internals is hazy, but I hit the docs and it appears that Ext.dd.DDM.mode determines if the dd targets interact via 'POINT' mode or 'INTERSECT' mode.

From the docs - Ext.dd.DragDropMgr

INTERSECT : int - In intersect mode, drag and drop interaction is defined by the overlap of two or more drag and drop objects.

POINT : int - In point mode, drag and drop interaction is defined by the location of the cursor during the drag/drop

I think I went with intersect mode to be more like google chrome, but if POINT mode is working for ya, then rock on.

realjax
11 Feb 2010, 2:56 AM
Very cool!


Is it stateful?

Good question. Is it ?

scipio
15 Feb 2010, 4:07 PM
Very cool!



Good question. Is it ?

No. Wouldn't be hard to add state though if it is important to your app. Save the tab order each time it changes in a cookie, database etc.

danigoldman
16 Feb 2010, 7:49 AM
So what's the verdict?

Could this extension be released under a non-GPL license?

xantus
15 Mar 2010, 8:30 PM
I'm in the middle of writing some other chrome like feature plugins for the tab panel, and I wanted to have everything just work as a plugin.

Soooo I cleaned up the code, and patched in reorderTabs.

I renamed it to Ext.ux.SllidingTabs as to not conflict with the previous version.

http://xant.us/files/slidingtabs.js

I've registered it as 'slidingtabs', so all you need is something like this:


var tabs = new Ext.TabPanel({
renderTo: 'tabs',
resizeTabs: true,
tabWidth: 135,
enableTabScroll: false,
width: 645,
height: 250,
slideDuration: .15,
plugins: [ 'slidingtabs', 'tabclosemenu' ]
});


Next up: Tear off tabs, and app tabs (just the icon)

joeri
16 Mar 2010, 12:36 AM
I couldn't immediately find someone else mentioning it in this thread, but the best aspect of chrome's tabs is the close behavior. The close behavior is designed so that when you close a tab the close button of the next tab ends up beneath your mouse cursor, even with variable width tabs. It's explained on the chromium blog: http://blog.chromium.org/2009/01/tabbed-browsing-in-google-chrome.html

This is why chrome's tabs work much "better" than those of other browsers. Maybe you could consider adding this behavior?

Ytorres
16 Mar 2010, 2:10 AM
Always about tabs, but it's a firefox's tabs idea now ;)

I always use it : close the tab on double-click on it !

Simple, but very usefull.

xantus
16 Mar 2010, 5:24 AM
I couldn't immediately find someone else mentioning it in this thread, but the best aspect of chrome's tabs is the close behavior. The close behavior is designed so that when you close a tab the close button of the next tab ends up beneath your mouse cursor, even with variable width tabs. It's explained on the chromium blog: http://blog.chromium.org/2009/01/tabbed-browsing-in-google-chrome.html

This is why chrome's tabs work much "better" than those of other browsers. Maybe you could consider adding this behavior?

This may have been mentioned...I still had this open from last night:
http://www.theinvisibl.com/news/2009/12/08/a-piece-with-a-lot-of-screenshots-about-the-close-tab-behaviour-in-google-chrome/

I'd like to do a plugin for this too.

xantus
16 Mar 2010, 5:54 AM
Always about tabs, but it's a firefox's tabs idea now ;)

I always use it : close the tab on double-click on it !

Simple, but very usefull.




// Copyright (c) 2010 David Davis - http://xant.us/
// License: MIT
Ext.ux.DblClickCloseTabs = Ext.extend( Object, {

init: function( panel ) {
this.panel = panel;
panel.initEvents = panel.initEvents.createSequence( this.initEvents, this );
},

initEvents: function() {
this.panel.mon(this.panel.strip, {
dblclick: this.onDblClick.createDelegate( this, [ this.panel ], 0 )
});
// cleanup
delete this.panel;
},

onDblClick: function(panel,e) {
panel.remove( panel.getActiveTab() );
}

});

Ext.preg( 'dblclickclosetabs', Ext.ux.DblClickCloseTabs );

Ytorres
16 Mar 2010, 11:01 AM
Great Xantus ^^

Perhaps you could post a new thread with this UX, it could be more easy for all to find it.

scipio
16 Mar 2010, 4:52 PM
Soooo I cleaned up the code, and patched in reorderTabs.

Next up: Tear off tabs, and app tabs (just the icon)

Hi Xanus, what part of the code did you cleanup?

I am working on getting the ability to tear off a tab and turn it into a floated panel. Actually, I finished it awhile back but have gotten so bogged down in Flex/AIR-land that I haven't had a chance to polish it because it was buggy in IE and had some terrible memory leakage.

xantus
16 Mar 2010, 9:04 PM
Hi Xanus, what part of the code did you cleanup?.

It's Xantus :) I spaced out the code so I can read it, and simplified some small parts. The most notable change is that it is a plugin now.


I am working on getting the ability to tear off a tab and turn it into a floated panel. Actually, I finished it awhile back but have gotten so bogged down in Flex/AIR-land that I haven't had a chance to polish it because it was buggy in IE and had some terrible memory leakage.

Cool, I got mine working tonight. I used the sliding tabs as a base. I'm cleaning it up, and adding the ability for windows to be dragged into the panel now.

Maybe we can work together and add in a few more chrome-like features?

App tabs (icon only tabs), maybe a menu with more options....

albeva
17 Mar 2010, 2:09 AM
I don't know if this is your intension or not but stating licence as GPL 3 it will prohibit use of your plugin in anything but open source software...

danigoldman
17 Mar 2010, 6:51 AM
scipio, have you had a chance to review the licensing? GPL definitely doesn't work for our commercial product. Any chance of changing it?

xantus
18 Mar 2010, 6:02 AM
scipio, have you had a chance to review the licensing? GPL definitely doesn't work for our commercial product. Any chance of changing it?

All he needs to do is change the initTabs section and make it a tabpanel plugin, and he can release it under MIT.

So how about it?

scipio
18 Mar 2010, 2:18 PM
All he needs to do is change the initTabs section and make it a tabpanel plugin, and he can release it under MIT.

So how about it?

Hi Xantus, could you explain why I need to change the initTabs method and make it a tabpanel plugin?

I have emailed licensing@extjs.com several times awhile back and have never gotten a straight answer on licensing so this is a rare opportunity to get a member of the dev team's thoughts on licensing.

xantus
18 Mar 2010, 2:29 PM
Hi Xantus, could you explain why I need to change the initTabs method and make it a tabpanel plugin?

I have emailed licensing@extjs.com several times awhile back and have never gotten a straight answer on licensing so this is a rare opportunity to get a member of the dev team's thoughts on licensing.

AFAIK, If you rewrite the initTabs part you copied (making it a plugin would be easier to do that) Your code would fall under the extensions exemption and you could release it as MIT.

You only need to change a few lines.

scipio
18 Mar 2010, 5:36 PM
AFAIK, If you rewrite the initTabs part you copied (making it a plugin would be easier to do that) Your code would fall under the extensions exemption and you could release it as MIT.

You only need to change a few lines.

I am surprised that an ExtJS dev team member is pressuring me to release my GPL extension as MIT.

Dani, I did say I wouldn't mind releasing as MIT, so I'll look into refactoring the code when I get a chance.

danigoldman
19 Mar 2010, 4:52 AM
Dani, I did say I wouldn't mind releasing as MIT, so I'll look into refactoring the code when I get a chance.

I appreciate it. Thanks.

wm003
19 Mar 2010, 6:10 AM
B) Great!
Thanks for this really cool piece of code.

wm003
19 Mar 2010, 7:03 AM
To make it work in Ext 2.3 the method getTemplateArgs from Ext 3 needs to be added



Ext.ux.SlidingTabPanel = Ext.extend(Ext.TabPanel, {

initTab: function(item, index){
Ext.ux.SlidingTabPanel.superclass.initTab.call(this, item, index);
var p = this.getTemplateArgs(item);
if(!this.slidingTabsID) this.slidingTabsID = Ext.id(); // Create a unique ID for this tabpanel
new Ext.ux.DDSlidingTab(p, this.slidingTabsID, {
tabpanel:this // Pass a reference to the tabpanel for each dragObject
});
},

//method does not exist in ext 2.3
getTemplateArgs : function(item) {
var cls = item.closable ? 'x-tab-strip-closable' : '';
if(item.disabled){
cls += ' x-item-disabled';
}
if(item.iconCls){
cls += ' x-tab-with-icon';
}
if(item.tabCls){
cls += ' ' + item.tabCls;
}

return {
id: this.id + this.idDelimiter + item.getItemId(),
text: item.title,
cls: cls,
iconCls: item.iconCls || ''
};
}


});

Ytorres
19 Mar 2010, 12:27 PM
Just add a check if the current tab is closable or not :





// Copyright (c) 2010 David Davis - http://xant.us/
// License: MIT
Ext.ux.DblClickCloseTabs = Ext.extend( Object, {

init: function( panel ) {
this.panel = panel;
panel.initEvents = panel.initEvents.createSequence( this.initEvents, this );
},

initEvents: function() {
this.panel.mon(this.panel.strip, {
dblclick: this.onDblClick.createDelegate( this, [ this.panel ], 0 )
});
// cleanup
delete this.panel;
},

onDblClick: function(panel,e) {
if( panel.getActiveTab().closable ) {
panel.remove( panel.getActiveTab() );
}
}

});

Ext.preg( 'dblclickclosetabs', Ext.ux.DblClickCloseTabs );

xantus
22 Mar 2010, 5:09 AM
I am surprised that an ExtJS dev team member is pressuring me to release my GPL extension as MIT.

Dani, I did say I wouldn't mind releasing as MIT, so I'll look into refactoring the code when I get a chance.

:) great. Looking forward to it

NateWorcester
31 Mar 2010, 6:41 AM
I am surprised that an ExtJS dev team member is pressuring me to release my GPL extension as MIT.

Dani, I did say I wouldn't mind releasing as MIT, so I'll look into refactoring the code when I get a chance.

I believe the changes I posted here (http://www.extjs.com/forum/showthread.php?p=431913#post431913) would satisfy all of the requirements for an MIT plugin.

garraS
23 Apr 2010, 11:18 AM
You are grox...simply like that.

mjmonserrat
19 May 2010, 1:04 AM
Help Guys, I'm having error "this.getTemplateArgs is not a function". My ext version is 3.2.1. Is there an issue with this version. Please hel. Thanks

wm003
19 May 2010, 2:29 AM
Does my fix from post #58 (5 posts above) help?

Sesshomurai
20 May 2010, 3:57 AM
Not sure if my previous post attempt made it. Trying again.

There is a bug after you reorder tabs, such that trying to reorder grid columns no long works.

wm003
24 May 2010, 9:04 PM
I encounter some problems when i put tabpanel into portal ,the tabpane's DD come into collision with the portal's. I change the code like this:



handleMouseDown function:

if (this.clickValidator(e)) {
this.setStartPosition(); // Set the initial element position
this.b4MouseDown(e);
this.onMouseDown(e);
this.DDM.handleMouseDown(e, this);

this.tabpanel.onStripMouseDown(e);//active the click tab

this.DDM.stopEvent(e); // Must remove this event swallower for the tabpanel to work
}

endDrag function:

Ext.dd.DDM.useCache = true;
Ext.dd.DDM.mode = 0;//set the mode


hope this is userful

Yes, indeed! Thank you.

wm003
24 May 2010, 9:18 PM
Another's suggestions,

Once the drag is done, the panel don't have the representation of this move (e.g. items elements are not in the same order as in the DOM).

So, when we want to now the current tab order, we get the order before the drag & drop.

I have added a new method to achieve this :

....

Thank you so much for this addition! Saved me a lot of time

sumit.madan
25 May 2010, 3:55 AM
I was getting javascript errors in the reorderTab() function and the tabpanel.items.items was getting replaced with all nulls. I didn't investigate it much and I rewrote it using the Ext.util.MixedCollection methods. Much simpler this way:

reorderTab: function() {
var tabElements = this.tabpanel.header.select('ul > li[id^=' + this.tabpanel.id + ']').elements;

var key, item;
for(var i = 0; i < tabElements.length; i++) {
key = tabElements[i].getAttribute('id').replace(this.tabpanel.id + '__', '');
item = this.tabpanel.items.key(key);
this.tabpanel.items.removeKey(key);
this.tabpanel.items.insert(i, item);
}
}EDIT - Another implementation using keySort():

reorderTab: function() {
var tabpanelId = this.tabpanel.id;
var tabElements = this.tabpanel.header.select('ul > li[id^=' + tabpanelId + ']').elements;
Ext.each(tabElements, function(item, index, tabElements) {
tabElements[index] = item.getAttribute('id').replace(tabpanelId + '__', '');
});
this.tabpanel.items.keySort('ASC', function(a, b) {
var idx1 = tabElements.indexOf(a);
var idx2 = tabElements.indexOf(b);
return idx1 > idx2 ? 1 : (idx1 < idx2 ? -1 : 0);
});
}

Sesshomurai
27 May 2010, 3:31 PM
Yes, indeed! Thank you.

The above fix (#66), also fixes the problem affecting dragndrop reordering of grid columns. Thank you.

Dumbledore
9 Aug 2010, 10:44 PM
Hi,

reorderTab will work only when the tabs are on top. I change this for me:


if (this.tabPosition == 'top'){
tabsEl = this.tabpanel.header.child( 'ul' ).dom.children;
} else {
tabsEl = this.tabpanel.footer.child( 'ul' ).dom.children;
}

wm003
9 Aug 2010, 11:03 PM
tabsEl is defined within Ext.ux.DDSlidingTab, so it's correctly:



...
,reorderTab: function() {
var tabsEl = (this.tabpanel.tabPosition=='top'?this.tabpanel.header.child('ul').dom.children:this.tabpanel.footer.child( 'ul' ).dom.children),
...

Dumbledore
10 Aug 2010, 4:41 AM
the proxy must also change. Below the complete source...

How can i get the new sort order?



/*
* By Jake Knerr - Copyright 2010 - supersonicecho@gmail.com
* Rewritten as a plugin by David Davis - http://xant.us/
*
* Version 2.0
*
* LICENSE
* GPL v3
*
*/

Ext.ux.SlidingTabs = Ext.extend( Object, {

init: function( panel ) {
panel.initTab = panel.initTab.createSequence( this.initTab, panel );
},

initTab: function( item, index ) {
if ( !this.slidingTabsID ) {
this.slidingTabsID = Ext.id();
}

new Ext.ux.DDSlidingTabs( this.getTemplateArgs( item ), this.slidingTabsID, {
tabpanel: this // Pass a reference to the tabpanel for each dragObject
});
}

});

Ext.preg( 'slidingtabs', Ext.ux.SlidingTabs );

// classes renamed so they wouldn't clash
Ext.ux.DDSlidingTabs = Ext.extend( Ext.dd.DDProxy, {

// Constructor
constructor: function() {
Ext.ux.DDSlidingTabs.superclass.constructor.apply( this, arguments );
this.setYConstraint( 0, 0, 0 ); // Lock the proxy to its initial Y coordinate

// Move the reference to the tab's tabpanel
this.tabpanel = this.config.tabpanel;
delete this.config.tabpanel;

// Set the slide duration
this.slideDuration = Ext.num( this.tabpanel.slideDuration, .1 );
},

// Pseudo Private Methods
handleMouseDown: function( e, oDD ) {
if ( ( this.primaryButtonOnly && e.button != 0 ) || this.isLocked() ) {
return;
}
this.DDM.refreshCache( this.groups );
var pt = new Ext.lib.Point( Ext.lib.Event.getPageX( e ), Ext.lib.Event.getPageY( e ) );
if ( !this.hasOuterHandles && !this.DDM.isOverTarget( pt, this ) ) {
} else {
if ( this.clickValidator( e ) ) {
this.setStartPosition(); // Set the initial element position
this.b4MouseDown( e );
this.onMouseDown( e );
this.DDM.handleMouseDown( e, this );
// this.DDM.stopEvent(e); // Must remove this event swallower for the tabpanel to work
}
}
},

startDrag: function( x, y ) {
Ext.dd.DDM.useCache = false; // Disable caching of element location
Ext.dd.DDM.mode = 1; // Point mode

this.proxyWrapper = Ext.get( this.getDragEl() ); // Grab a reference to the proxy element we are creating
this.proxyWrapper.update(); // Clear out the proxy's nodes
this.proxyWrapper.applyStyles( 'z-index:1001;border:0 none;' );
this.proxyWrapper.addClass( 'tab-proxy' );

// Use 2 nested divs to mimic the default tab styling
// You may need to customize the proxy to get it to look like your custom tabpanel if you use a bunch of custom css classes and styles
this.stripWrap = this.proxyWrapper.insertHtml( 'afterBegin', '<div class="x-tab-strip ' + ((this.tabpanel.tabPosition=='top') ? 'x-tab-strip-top ' : 'x-tab-strip-bottom ') + '"></div>', true );
this.dragEl = this.stripWrap.insertHtml( 'afterBegin','<div></div>', true );

this.tab = Ext.get( this.getEl() ); // Grab a reference to the tab being dragged
this.tab.applyStyles( 'visibility:hidden;' ); // Hide the tab being dragged

// Insert the html and css classes for the dragged tab into the proxy
this.dragEl.insertHtml( 'afterBegin', this.tab.dom.innerHTML, false );
this.dragEl.dom.className = this.tab.dom.className;

// Constrain the proxy drag in the X coordinate to the tabpanel
var panelWidth = this.tabpanel.el.getWidth();
var panelX = this.tabpanel.el.getX();
var tabX = this.tab.getX();
var tabWidth = this.tab.getWidth();
var left = tabX - panelX;
var right = panelX + panelWidth - tabX - tabWidth;
this.resetConstraints();
this.setXConstraint( left, right );
},

// credit: Ytorres http://www.extjs.com/forum/showthread.php?p=430305#post430305
onDragOver: function( e, targetArr ) {
e.stopEvent();

// Grab the tab the user has dragged the proxy over
var target = Ext.get( targetArr[ 0 ].id );
var targetWidth = target.getWidth();
var targetX = target.getX();
var targetMiddle = targetX + ( targetWidth / 2 );
var elX = this.tab.getX();
var dragX = this.proxyWrapper.getX();
var dragW = this.proxyWrapper.getWidth();
if ( dragX < targetX && ( ( dragX + dragW ) > targetMiddle ) ) {
if ( target.next() != this.tab ) {
target.applyStyles( 'visibility:hidden;' );
this.tab.insertAfter( target );
this.targetProxy = this.createSliderProxy( targetX, target );
if ( !this.targetProxy.hasActiveFx() ) {
this.animateSliderProxy( target, this.targetProxy, elX );
}
}
}
if ( dragX > targetX && ( dragX < targetMiddle ) ) {
if ( this.tab.next() != target ) {
target.applyStyles( 'visibility:hidden;' );
this.tab.insertBefore( target );
this.targetProxy = this.createSliderProxy( targetX, target );
if ( !this.targetProxy.hasActiveFx() ) {
this.animateSliderProxy( target, this.targetProxy, elX );
}
}
}
},

animateSliderProxy: function( target, targetProxy, elX ) {
targetProxy.shift({
x: elX,
easing: 'easeOut',
duration: this.slideDuration,
callback: function() {
targetProxy.remove();
target.applyStyles( 'visibility:visible;' );
},
scope:this
});
},

createSliderProxy: function( targetX, target ) {
var sliderWrapperEl = Ext.getBody().insertHtml( 'afterBegin', '<div class="tab-proxy" style="position:absolute;visibility:visible;z-index:999;left:' + targetX + 'px;"></div>', true);
sliderWrapperEl.stripWrapper = sliderWrapperEl.insertHtml( 'afterBegin', '<div class="x-tab-strip ' + ((this.tabpanel.tabPosition=='top') ? 'x-tab-strip-top ' : 'x-tab-strip-bottom ') + '"></div>', true );
sliderWrapperEl.dragEl = sliderWrapperEl.stripWrapper.insertHtml( 'afterBegin', '<div></div>', true );
sliderWrapperEl.dragEl.update( target.dom.innerHTML );
sliderWrapperEl.dragEl.dom.className = target.dom.className;
sliderWrapperEl.setTop( parseInt( target.getTop( false ) ) );
return sliderWrapperEl;
},

onDragDrop: function( e, targetId ) {
e.stopEvent();
},

endDrag: function( e ) {
this.proxyWrapper.applyStyles( 'visibility:visible;' );

// Animate the dragProxy to the proper position
this.proxyWrapper.shift({
x: this.tab.getX(),
easing: 'easeOut',
duration: this.slideDuration,
callback: function() {
this.proxyWrapper.applyStyles( 'visibility:hidden;' );
this.tab.applyStyles( 'visibility:visible;' );

// Cleanup
this.stripWrap.remove();
this.dragEl.remove();
if ( this.targetProxy ) {
this.targetProxy.stripWrapper.remove();
this.targetProxy.dragEl.remove();
}
},
scope:this
});

Ext.dd.DDM.useCache = true;

this.reorderTab();
},

reorderTab: function() {
var tabsEl = (this.tabpanel.tabPosition=='top'?this.tabpanel.header.child('ul').dom.children:this.tabpanel.footer.child( 'ul' ).dom.children),
tabsId = [],
tabsOrigin = [];

for ( var i = 0, len = tabsEl.length; i < len; i++ ) {
if ( tabsEl[ i ].id.substr( 0, this.tabpanel.id.length ) == this.tabpanel.id ) {
tabsId.push( tabsEl[ i ].id.substr( ( this.tabpanel.id.length + 2 ), tabsEl[ i ].id.length ) );
}
}

// Now, tabsId is the real list ordered of the tab's id
// We put this order into parent element

// We get the original reference of this tabs
for ( var i = 0, len = this.tabpanel.items.items.length; i < len; i++ ) {
tabsOrigin[ this.tabpanel.items.items[ i ].id ] = this.tabpanel.items.items[ i ];
}

for ( var i=0; i < tabsId.length; i++ ) {
// order the keys
this.tabpanel.items.keys[ i ] = tabsId[ i ];
// order the elements
this.tabpanel.items.items[ i ] = tabsOrigin[ tabsId[ i ] ];
}
}

});

wm003
10 Aug 2010, 9:42 AM
How can i get the new sort order?


The new order is stored in


this.tabpanel.items[.keys|.items]

Dumbledore
11 Aug 2010, 10:44 PM
nice, thanks!


it runs fine, but i need an event when the drag/drop is over... Perhaps we should add an fireEvent inside reorderTab?

wm003
11 Aug 2010, 11:04 PM
it runs fine, but i need an event when the drag/drop is over... Perhaps we should add an fireEvent inside reorderTab?
Yes, that would be the way to go, if you need such a feature

kof720551
9 Sep 2010, 4:11 PM
I use slidingtabs in Ext.Window,but show wrong.
How can i show it normal in Ext.Window?


<html>

<head>
<title>
Chrome Style Sliding Tabs
</title>
<link rel="stylesheet" type="text/css" href="http://extjs.cachefly.net/ext-3.2.1/resources/css/ext-all.css"
/>
<script type="text/javascript" src="http://extjs.cachefly.net/ext-3.2.1/adapter/ext/ext-base.js">
</script>
<script type="text/javascript" src="http://extjs.cachefly.net/ext-3.2.1/ext-all.js">
</script>
<script type="text/javascript" src="pluging_slidingtabs.js">
</script>
<script type="text/javascript" src="TabCloseMenu.js">
</script>
<style type="text/css">
body {}
</style>
<script type="text/javascript">
Ext.onReady(function() {

window.loaded = true;

var tabs = new Ext.TabPanel({
id: 'tabs1',
activeTab: 0,
resizeTabs: true,
// turn on tab resizing
slideDuration: .15,
defaults: {
bodyStyle: 'padding:10px;'
},
enableTabScroll: true,
layoutOnTabChange: true,
plugins: ['slidingtabs', 'tabclosemenu'],
defaults: {
autoScroll: true
},

items: [{
title: 'Bogus Tab',
closable: false
}]
});

// Tab generation code
var index = 0;
while (index < 4) {
addTab();
}

function addTab() {
tabs.add({
title: 'New Tab ' + (++index),
html: 'Tab Body ' + (index) + '<br/><br/><div>Bogus Markup</div>',
closable: true
}).show();
}

new Ext.Button({
text: 'Add A New Tab',
handler: addTab
});
var win = new Ext.Window({
items: [tabs]
});
win.show();
});
</script>
</head>

<body>
<div id="tabs" style="margin:15px 0;">
</div>
</body>

</html>

growler
22 Sep 2010, 2:43 PM
Hi. Thanks for plugin.

Was easy to include Ajax here.
Also I have found 1 bug:
If You scrolled page down(I have big page) - Your sorting jumps.
I meen function have no scroll corection.

Thanks.

PS: Sorry for my bad english. Im beginer in english.

shalmali
14 Mar 2011, 4:59 PM
Great feature! Thanks!

I used the code for the plugin that has been posted here. However my tab sliding is not as smooth as in the demo. I read through the code, but I haven't been able to see what causes the tab drag-drop to be as smooth as the demo.

Can anyone give me any leads as to what I need to modify to make this happen?

mjbcesar
27 May 2011, 6:29 AM
Has anyone ported this to ExtJs4? I tried but my knowledge of ExtJs isn't enough (yet) to do it myself...

fsiatama
9 Apr 2012, 12:57 PM
this solution worked perfectly