/************** ***************************/
(function(){
/**
* Widget that contains several tabs and their content
* Options:
* - mode: "vertical","horizontal"
* - size
* - width,height
* - autoswitch: allow autoswitch (switch when mouse over)
* @class Tabs
* @constructor
*/
function Tabs( options, legacy )
{
if( legacy || (options && options.constructor === String) )
{
var id = options;
options = legacy || {};
options.id = id;
console.warn("LiteGUI.Dialog legacy parameter, use options as first parameter instead of id.");
}
options = options || {};
this.options = options;
var mode = this.mode = options.mode || "horizontal";
var root = document.createElement("DIV");
if(options.id)
root.id = options.id;
root.data = this;
root.className = "litetabs " + mode;
this.root = root;
this.root.tabs = this;
this.current_tab = null; //current tab array [id, tab, content]
if(mode == "horizontal")
{
if(options.size)
{
if(options.size == "full")
this.root.style.height = "100%";
else
this.root.style.height = options.size;
}
}
else if(mode == "vertical")
{
if(options.size)
{
if(options.size == "full")
this.root.style.width = "100%";
else
this.root.style.width = options.size;
}
}
if(options.width)
this.root.style.width = options.width.constructor === Number ? options.width.toFixed(0) + "px" : options.width;
if(options.height)
this.root.style.height = options.height.constructor === Number ? options.height.toFixed(0) + "px" : options.height;
//container of tab elements
var list = document.createElement("UL");
list.className = "wtabcontainer";
if(mode == "vertical")
list.style.width = LiteGUI.Tabs.tabs_width + "px";
else
list.style.height = LiteGUI.Tabs.tabs_height + "px";
//allows to use the wheel to see hidden tabs
list.addEventListener("wheel", onMouseWheel);
list.addEventListener("mousewheel", onMouseWheel);
function onMouseWheel(e){
if(e.deltaY)
list.scrollLeft += e.deltaY;
}
this.list = list;
this.root.appendChild(this.list);
this.tabs_root = list;
this.tabs = {};
this.tabs_by_index = [];
this.selected = null;
this.onchange = options.callback;
if(options.parent)
this.appendTo(options.parent);
}
Tabs.tabs_width = 64;
Tabs.tabs_height = 26;
Tabs.prototype.show = function()
{
this.root.style.display = "block";
}
Tabs.prototype.hide = function()
{
this.root.style.display = "none";
}
/**
* Returns the currently selected tab in the form of a tab object
* @method getCurrentTab
* @return {Object} the tab in the form of an object with {id,tab,content}
*/
Tabs.prototype.getCurrentTab = function()
{
if(!this.current_tab)
return null;
return this.tabs[ this.current_tab[0] ];
}
Tabs.prototype.getCurrentTabId = function()
{
return this.current_tab[0];
}
/**
* Returns the last tab pressed before this one. used to know from which tab we come
* @method getCurrentTab
* @return {Object} the tab in the form of an object with {id,tab,content}
*/
Tabs.prototype.getPreviousTab = function()
{
if(!this.previous_tab)
return null;
return this.tabs[ this.previous_tab[0] ];
}
Tabs.prototype.appendTo = function(parent, at_front)
{
if(at_front)
$(parent).prepend(this.root);
else
$(parent).append(this.root);
}
/**
* Returns a tab given its id
* @method getTab
* @param {String} id tab id
* @return {Object} the tab in the form of an object with {id,tab,content}
*/
Tabs.prototype.getTab = function(id)
{
return this.tabs[id];
}
/**
* Returns a tab given its index in the tabs list
* @method getTabByIndex
* @param {Number} index
* @return {Object} the tab in the form of an object with {id,tab,content}
*/
Tabs.prototype.getTabByIndex = function(index)
{
return this.tabs_by_index[index];
}
/**
* Returns how many tabs there is
* @method getNumOfTabs
* @return {number} number of tabs
*/
Tabs.prototype.getNumOfTabs = function()
{
var num = 0;
for(var i in this.tabs)
num++;
return num;
}
/**
* Returns the content HTML element of a tab
* @method getTabContent
* @param {String} id
* @return {HTMLEntity} content
*/
Tabs.prototype.getTabContent = function(id)
{
var tab = this.tabs[id];
if(tab)
return tab.content;
}
/**
* Returns the index of a tab (the position in the tabs list)
* @method getTabIndex
* @param {String} id
* @return {number} index
*/
Tabs.prototype.getTabIndex = function(id)
{
var tab = this.tabs[id];
if(!tab)
return -1;
for(var i = 0; i < this.list.childNodes.length; i++)
if( this.list.childNodes[i] == tab.tab )
return i;
return -1;
}
/**
* Create a new tab, where id is a unique identifier
* @method addTab
* @param {String} id could be null then a random id is generated
* @param {Object} options {
* title: tab text,
* callback: called when selected,
* callback_leave: callback when leaving,
* callback_context: on right click on tab
* callback_canopen: used to block if this tab can be opened or not (if it returns true then yes)
* content: HTML content,
* closable: if it can be closed (callback is onclose),
* tab_width: size of the tab,
* tab_className: classes for the tab element,
* id: content id,
* size: full means all,
* mode: "vertical" or "horizontal",
* button: if it is a button tab, not a selectable tab
* }
* @param {bool} skip_event prevent dispatching events
* @return {Object} an object containing { id, tab, content }
*/
Tabs.prototype.addTab = function( id, options, skip_event )
{
options = options || {};
if(typeof(options) == "function")
options = { callback: options };
var that = this;
if(id === undefined || id === null)
id = "rand_" + ((Math.random() * 1000000)|0);
//the tab element
var element = document.createElement("LI");
var safe_id = id.replace(/ /gi,"_");
element.className = "wtab wtab-" + safe_id + " ";
//if(options.selected) element.className += " selected";
element.dataset["id"] = id;
element.innerHTML = "<span class='tabtitle'>" + (options.title || id) + "</span>";
if(options.button)
element.className += "button ";
if(options.tab_className)
element.className += options.tab_className;
if(options.bigicon)
element.innerHTML = "<img class='tabbigicon' src='" + options.bigicon+"'/>" + element.innerHTML;
if(options.closable)
{
element.innerHTML += "<span class='tabclose'>" + LiteGUI.special_codes.close + "</span>";
element.querySelector("span.tabclose").addEventListener("click", function(e) {
that.removeTab(id);
e.preventDefault();
e.stopPropagation();
},true);
}
//WARNING: do not modify element.innerHTML or events will be lost
if( options.index !== undefined )
{
var after = this.list.childNodes[options.index];
if(after)
this.list.insertBefore(element,after);
else
this.list.appendChild(element);
}
else if( this.plus_tab )
this.list.insertBefore( element, this.plus_tab );
else
this.list.appendChild(element);
if(options.tab_width)
{
element.style.width = options.tab_width.constructor === Number ? ( options.tab_width.toFixed(0) + "px" ) : options.tab_width;
element.style.minWidth = "0";
}
if(this.options.autoswitch)
{
element.classList.add("autoswitch");
element.addEventListener("dragenter",function(e){
//console.log("Enter",this.dataset["id"]);
if(that._timeout_mouseover)
clearTimeout(that._timeout_mouseover);
that._timeout_mouseover = setTimeout((function(){
LiteGUI.trigger(this,"click");
that._timeout_mouseover = null;
}).bind(this),500);
});
element.addEventListener("dragleave",function(e){
//console.log("Leave",this.dataset["id"]);
if(that._timeout_mouseover)
{
clearTimeout(that._timeout_mouseover);
that._timeout_mouseover = null;
}
});
}
//the content of the tab
var content = document.createElement("div");
if(options.id)
content.id = options.id;
content.className = "wtabcontent " + "wtabcontent-" + safe_id + " " + (options.className || "");
content.dataset["id"] = id;
content.style.display = "none";
//adapt height
if(this.mode == "horizontal")
{
if(options.size)
{
content.style.overflow = "auto";
if(options.size == "full")
{
content.style.width = "100%";
content.style.height = "calc( 100% - "+LiteGUI.Tabs.tabs_height+"px )"; //minus title
content.style.height = "-moz-calc( 100% - "+LiteGUI.Tabs.tabs_height+"px )"; //minus title
content.style.height = "-webkit-calc( 100% - "+LiteGUI.Tabs.tabs_height+"px )"; //minus title
//content.style.height = "-webkit-calc( 90% )"; //minus title
}
else
content.style.height = options.size;
}
}
else if(this.mode == "vertical")
{
if(options.size)
{
content.style.overflow = "auto";
if(options.size == "full")
{
content.style.height = "100%";
content.style.width = "calc( 100% - "+LiteGUI.Tabs.tabs_width+"px )"; //minus title
content.style.width = "-moz-calc( 100% - "+LiteGUI.Tabs.tabs_width+"px )"; //minus title
content.style.width = "-webkit-calc( 100% - "+LiteGUI.Tabs.tabs_width+"px )"; //minus title
//content.style.height = "-webkit-calc( 90% )"; //minus title
}
else
content.style.width = options.size;
}
}
//overwrite
if(options.width !== undefined )
content.style.width = typeof(options.width) === "string" ? options.width : options.width + "px";
if(options.height !== undefined )
content.style.height = typeof(options.height) === "string" ? options.height : options.height + "px";
//add content
if(options.content)
{
if (typeof(options.content) == "string")
content.innerHTML = options.content;
else
content.appendChild(options.content);
}
this.root.appendChild(content);
//when clicked
if(!options.button)
element.addEventListener("click", Tabs.prototype.onTabClicked );
else
element.addEventListener("click", function(e){
var tab_id = this.dataset["id"];
if(options.callback)
options.callback( tab_id, e );
});
element.options = options;
element.tabs = this;
var title = element.querySelector("span.tabtitle");
//tab object
var tab_info = {
id: id,
tab: element,
content: content,
title: title,
add: function(v) { this.content.appendChild(v.root || v); },
setTitle: function( title ) { this.title.innerHTML = title; },
click: function(){ LiteGUI.trigger( this.tab, "click" ); },
destroy: function(){ that.removeTab(this.id) }
};
if(options.onclose)
tab_info.onclose = options.onclose;
this.tabs[ id ] = tab_info;
this.recomputeTabsByIndex();
//context menu
element.addEventListener("contextmenu", (function(e) {
if(e.button != 2) //right button
return false;
e.preventDefault();
if(options.callback_context)
options.callback_context.call(tab_info);
return false;
}).bind(this));
if ( options.selected == true || this.selected == null )
this.selectTab( id, options.skip_callbacks );
return tab_info;
}
Tabs.prototype.addPlusTab = function( callback )
{
if(this.plus_tab)
console.warn("There is already a plus tab created in this tab widget");
this.plus_tab = this.addTab( "plus_tab", { title: "+", tab_width: 20, button: true, callback: callback, skip_callbacks: true });
}
//this is tab
Tabs.prototype.onTabClicked = function(e)
{
//skip if already selected
if( this.classList.contains("selected") )
return;
if(!this.parentNode)
return; //this could happend if it gets removed while being clicked (not common)
var options = this.options;
var tabs = this.parentNode.parentNode.tabs;
if(!tabs)
throw("tabs not found");
var that = tabs;
//check if this tab is available
if(options.callback_canopen && options.callback_canopen() == false)
return;
//launch leaving current tab event
if( that.current_tab &&
that.current_tab[0] != tab_id &&
that.current_tab[2] &&
that.current_tab[2].callback_leave)
that.current_tab[2].callback_leave( that.current_tab[0], that.current_tab[1], that.current_tab[2] );
var tab_id = this.dataset["id"];
var tab_content = null;
//iterate tab labels
for(var i in that.tabs)
{
var tab_info = that.tabs[i];
if( i == tab_id )
{
tab_info.selected = true;
tab_info.content.style.display = "";
tab_content = tab_info.content;
}
else
{
delete tab_info.selected;
tab_info.content.style.display = "none";
}
}
$(that.list).find("li.wtab").removeClass("selected");
this.classList.add("selected");
//change tab
that.previous_tab = that.current_tab;
that.current_tab = [tab_id, tab_content, options];
if(e) //user clicked
{
//launch callback
if(options.callback)
options.callback(tab_id, tab_content,e);
$(that).trigger("wchange",[tab_id, tab_content]);
if(that.onchange)
that.onchange( tab_id, tab_content );
}
//change afterwards in case the user wants to know the previous one
that.selected = tab_id;
}
Tabs.prototype.selectTab = function( id, skip_events )
{
if(!id)
return;
if( id.constructor != String )
id = id.id; //in case id is the object referencing the tab
var tabs = this.list.querySelectorAll("li.wtab");
for(var i = 0; i < tabs.length; i++)
if( id == tabs[i].dataset["id"] )
{
this.onTabClicked.call( tabs[i], !skip_events );
break;
}
}
Tabs.prototype.setTabVisibility = function(id, v)
{
var tab = this.tabs[id];
if(!tab)
return;
tab.tab.style.display = v ? "none" : null;
tab.content.style.display = v ? "none" : null;
}
Tabs.prototype.recomputeTabsByIndex = function()
{
this.tabs_by_index = [];
for(var i in this.tabs)
{
var tab = this.tabs[i];
//compute index
var index = 0;
var child = tab.tab;
while( (child = child.previousSibling) != null )
index++;
this.tabs_by_index[index] = tab;
}
}
Tabs.prototype.removeTab = function(id)
{
var tab = this.tabs[id];
if(!tab)
return;
if(tab.onclose)
tab.onclose(tab);
if(tab.tab.parentNode)
tab.tab.parentNode.removeChild( tab.tab );
if(tab.content.parentNode)
tab.content.parentNode.removeChild( tab.content );
delete this.tabs[id];
this.recomputeTabsByIndex();
}
Tabs.prototype.removeAllTabs = function( keep_plus )
{
var tabs = [];
for(var i in this.tabs)
tabs.push( this.tabs[i] );
for(var i in tabs)
{
var tab = tabs[i];
if(tab == this.plus_tab && keep_plus)
continue;
if(tab.tab.parentNode)
tab.tab.parentNode.removeChild( tab.tab );
if(tab.content.parentNode)
tab.content.parentNode.removeChild( tab.content );
delete this.tabs[ tab.id ];
}
this.recomputeTabsByIndex();
}
Tabs.prototype.clear = function()
{
this.removeAllTabs();
}
Tabs.prototype.hideTab = function(id)
{
this.setTabVisibility(id, false);
}
Tabs.prototype.showTab = function(id)
{
this.setTabVisibility(id, true);
}
Tabs.prototype.transferTab = function(id, target_tabs, index)
{
var tab = this.tabs[id];
if(!tab)
return;
target_tabs.tabs[id] = tab;
if(index !== undefined)
target_tabs.list.insertBefore(tab.tab, target_tabs.list.childNodes[index]);
else
target_tabs.list.appendChild(tab.tab);
target_tabs.root.appendChild(tab.content);
delete this.tabs[id];
var newtab = null;
for(var i in this.tabs)
{
newtab = i;
break;
}
if(newtab)
this.selectTab(newtab);
tab.tab.classList.remove("selected");
target_tabs.selectTab(id);
}
Tabs.prototype.detachTab = function(id, on_complete, on_close )
{
var tab = this.tabs[id];
if(!tab)
return;
var index = this.getTabIndex( id );
//create window
var w = 800;
var h = 600;
var tab_window = window.open("","","width="+w+", height="+h+", location=no, status=no, menubar=no, titlebar=no, fullscreen=yes");
tab_window.document.write( "<head><title>"+id+"</title>" );
//transfer style
var styles = document.querySelectorAll("link[rel='stylesheet'],style");
for(var i = 0; i < styles.length; i++)
tab_window.document.write( styles[i].outerHTML );
tab_window.document.write( "</head><body></body>" );
tab_window.document.close();
var that = this;
//transfer content after a while so the window is propertly created
var newtabs = new LiteGUI.Tabs(null, this.options );
tab_window.tabs = newtabs;
//closing event
tab_window.onbeforeunload = function(){
newtabs.transferTab( id, that, index);
if(on_close)
on_close();
}
//move the content there
newtabs.list.style.height = "20px";
tab_window.document.body.appendChild(newtabs.root);
that.transferTab(id, newtabs);
newtabs.tabs[id].tab.classList.add("selected");
this.recomputeTabsByIndex();
if(on_complete)
on_complete();
return tab_window;
}
LiteGUI.Tabs = Tabs;
})();