react-panels
Version:
React.js panel widget with support for dynamic tabs, toolbars, buttons, floating windows and customizable themes
623 lines (515 loc) • 17 kB
JSX
/*
* react-panels
* https://github.com/Theadd/react-panels
*
* Copyright (c) 2015 R.Beltran https://github.com/Theadd
* Licensed under the MIT license.
*/
var Panel = React.createClass({
applyPreset: function (preset) {
var self = this,
defaultProps = {
"icon": "",
"title": "",
"theme": "default",
"bordered": false,
"opaque": false,
"raised": false,
"rounded": false,
"forceTabs": false,
"buttons": [],
/** Set panel title based on the title of the active tab. */
"getTitleFromActiveTab": false,
"displayTabTitles": true
};
Object.keys(defaultProps).forEach(function(prop) {
var value = defaultProps[prop];
if (typeof self.props[prop] !== "undefined") {
value = self.props[prop];
} else if (typeof preset[prop] !== "undefined") {
value = preset[prop];
}
self.props[prop] = value;
});
},
getInitialState: function () {
var self = this,
tabList = [],
defaultTabIndex = 0;
this.applyPreset(this.props.preset || {});
self._id = Panel._register(self);
self._childrenList = [];
React.Children.forEach(this.props.children, function(tab) {
var _tab = self._registerPanelContent(tab);
if (tab.props.active || false) {
defaultTabIndex = _tab.index;
}
tabList[_tab.index] = _tab;
});
return {
tabIndex: defaultTabIndex,
tabCount: self._childrenList.length,
tabList: tabList,
state: "default"
};
},
_getIcon: function () {
var icon = null;
if (this.props.icon) {
icon = (
<span className="rpanel-icon">
<i className={this.props.icon}></i>
</span>
);
}
return icon;
},
_getTabs: function () {
var self = this,
index = -1,
classes = "rpanel-tabs" + (((self.state.tabCount > 1) || (self.props.forceTabs || false)) ? "" : " hidden");
return (
<ul className={classes}>
{self._childrenList.map(function(id) {
++index;
if (id == null) return null;
var child = Panel._getPanelContent(id),
tab = self.state.tabList[index],
title = child.props.title || "",
icon = child.props.icon || false;
return (tab.state != "none") ? (
<PanelTab
title={title}
icon={icon}
showTitle={self.props.displayTabTitles}
index={index}
key={index}
selected={self.state.tabIndex}
state={tab.state}
_id={tab.id}
onClick={self.handleClickOnTab}
/>) : null;
})}
</ul>
);
},
_getBody: function () {
var self = this,
index = -1;
return (
<div className="rpanel-body">
{self._childrenList.map(function(id) {
++index;
if (id == null) return null;
var child = Panel._getPanelContent(id),
showToolbar = (['visible', 'locked'].indexOf(self.state.tabList[index].toolbar) != -1),
display = (index == self.state.tabIndex),
visibility = self.state.tabList[index].state;
Panel._setPanelContent(
<PanelContent {...child.props}
key={index}
_index={index}
showToolbar={showToolbar}
display={display}
visibility={visibility}
/>
);
return Panel._getPanelContent(id);
})}
</div>
);
},
_getButtons: function () {
var self = this,
buttons = null,
keyIndex = 0;
if (self.props.buttons.length) {
buttons = [];
for (var i = self.props.buttons.length; --i >= 0;) {
var button = self.props.buttons[i],
buttonIdentifier = "",
customButtonProps = {};
if (typeof button === "string" || ((typeof button === "object") && !React.isValidElement(button))) {
if (typeof button === "string") {
buttonIdentifier = button;
} else {
buttonIdentifier = Object.keys(button)[0];
customButtonProps = button[buttonIdentifier];
}
var predefinedButton = self._getButton(buttonIdentifier, keyIndex, customButtonProps);
if (predefinedButton || false) {
buttons.push(predefinedButton);
++keyIndex;
}
} else if (React.isValidElement(button)) {
var panelButton = (
<PanelButton {...button.props}
key={keyIndex}
tabIndex={keyIndex}
parent={this}
/>
);
buttons.push(panelButton);
++keyIndex;
}
}
}
return buttons;
},
handleClickOnTab: function (child) {
this.setActivePanelContent(child.props.index);
},
handleClickOnClose: function (event) {
event.preventDefault();
this.setState({state: "closed"});
},
handleClickOnCollapse: function (event) {
var newState = (this.state.state == "collapsed") ? "default" : "collapsed";
event.preventDefault();
this.setState({state: newState});
},
handleClickOnFullscreen: function (event) {
var newState = (this.state.state == "fullscreen") ? "default" : "fullscreen";
event.preventDefault();
this.setState({state: newState});
},
handleClickOnToggleToolbar: function (event) {
var panelContent = this.getActivePanelContent();
event.preventDefault();
if (panelContent) {
panelContent.toggleToolbar();
}
},
dragStart: function (e) {
this.panelBounds = {
startLeft: Number(this.props.left) || 80,
startTop: Number(this.props.top) || 100,
startPageX: e.pageX,
startPageY: e.pageY
};
try {
e.dataTransfer.setData('text/plain', "Panel");
} catch (err) { /* Fix for IE */ }
window.addEventListener('dragover', this.dragOver);
},
dragEnd: function() {
delete this.panelBounds;
window.removeEventListener('dragover', this.dragOver);
},
dragOver: function(e) {
if (this.panelBounds || false) {
var left = this.panelBounds.startLeft + (e.pageX - this.panelBounds.startPageX),
top = this.panelBounds.startTop + (e.pageY - this.panelBounds.startPageY);
this.setProps({ left: left, top: top });
}
},
render: function() {
var classes = this._getClasses(),
icon = this._getIcon(),
body = this._getBody(),
tabs = this._getTabs(),
buttons = this._getButtons(),
title = this.props.title + (
(this.props.getTitleFromActiveTab) ? this.state.tabList[this.state.tabIndex].title : ""
),
header = (this.props.draggable || false) ? (
<header
draggable="true"
onDragEnd={this.dragEnd}
onDragStart={this.dragStart}>
{icon}
<span className="rpanel-title">{title}</span>
{buttons}
{tabs}
</header>
) : (
<header>
{icon}
<span className="rpanel-title">{title}</span>
{buttons}
{tabs}
</header>
);
/*if (this._needsForceUpdate) {
this._needsForceUpdate = false;
this.forceUpdate();
//TODO: forceUpdate active PanelContent
}*/
var left = Number(this.props.left) || 80,
top = Number(this.props.top) || 100,
width = String(this.props.width || 800) + "px",
transform = (this.state.state == "fullscreen") ? "inherit" : "translate3d(" + left + "px, " + top + "px, 0)";
return ((this.props.floating || false) && (this.props.draggable)) ? (
<div className={classes} style={{
WebkitTransform: transform,
MozTransform: transform,
msTransform: transform,
transform: transform,
width: (this.state.state == "fullscreen") ? "100%" : width
}}>
{header}
{body}
</div>
) : (
<div className={classes}>
{header}
{body}
</div>
);
},
_getClasses: function () {
var classes = "rpanel " + this.props.theme;
if (this.props.rounded) {
classes += " rounded";
} else {
if (this.props.roundedTop) {
classes += " rounded-top";
}
if (this.props.roundedBottom) {
classes += " rounded-bottom";
}
}
if (this.props.rounded) {
if (this.props.rounded == "top") {
classes += " rounded-top";
} else if (this.props.rounded == "bottom") {
classes += " rounded-bottom";
} else {
classes += " rounded";
}
}
if (this.props.bordered) {
classes += " bordered";
}
if (this.props.raised) {
classes += " raised";
}
if (this.props.floating) {
classes += " floating";
}
if (this.state.state != "default") {
classes += " rpanel-state-" + this.state.state;
}
return classes;
},
_getButton: function (identifier, key, customProps) {
var preset = {};
switch (identifier) {
case "close":
preset = {"onClick": this.handleClickOnClose};
break;
case "collapse":
preset = {
"identifier": "collapse",
"icon": "fa fa-minus",
"title": "Toggle panel",
"active": function (button) {
return (button.props.parent.state.state == "collapsed");
},
"hiddenOnFullscreen": true,
"onClick": this.handleClickOnCollapse
};
break;
case "fullscreen":
preset = {
"identifier": "fullscreen",
"icon": "fa fa-expand",
"title": "Toggle fullscreen",
"active": function (button) {
return (button.props.parent.state.state == "fullscreen");
},
"onClick": this.handleClickOnFullscreen
};
break;
case "toggleToolbar":
preset = {
"identifier": "toggleToolbar",
"icon": "fa fa-pencil-square-o",
"title": "Toggle toolbar of active tab",
"active": function (button) {
return (["visible", "locked"].indexOf(button.props.parent.state.tabList[button.props.parent.state.tabIndex].toolbar) != -1);
},
"disabled": function (button) {
return (["locked", "none"].indexOf(button.props.parent.state.tabList[button.props.parent.state.tabIndex].toolbar) != -1);
},
"hiddenOnFullscreen": true,
"onClick": this.handleClickOnToggleToolbar
};
break;
case "custom":
break;
default:
throw new Error("Predefined button '" + identifier + "' not found.");
}
Object.keys(customProps || {}).forEach(function(prop) {
preset[prop] = customProps[prop];
});
return (Object.keys(preset).length) ?
(<PanelButton key={key} tabIndex={key} parent={this} preset={preset} />) : null;
},
_registerPanelContent: function (panelContent) {
var hasToolbar = (typeof panelContent.props.toolbar !== "undefined"),
toolbarState = panelContent.props.toolbarState || ((hasToolbar) ? "visible" : "none");
panelContent.props._panelId = this._id;
var id = Panel._setPanelContent(panelContent),
index = this._childrenList.push(id) - 1;
return {
index: index,
toolbar: toolbarState,
padding: Boolean(!(panelContent.props.noPadding || false)), //TODO: deprecated
state: "visible",
id: id
};
},
addPanelContent: function (panelContent, selected) {
var _state = this.state,
_tab = this._registerPanelContent(panelContent);
_state.tabList[_tab.index] = _tab;
_state.tabCount = this._childrenList.length;
if (selected || false) {
_state.tabIndex = _tab.index;
}
this.setState(_state);
},
removePanelContent: function (index, hideOnly) {
var _state = this.state,
switchToOtherTab,
newTabState = (hideOnly || false) ? "hidden" : "none",
i = 0;
if (typeof index === "undefined" || index == null) {
index = _state.tabIndex;
}
switchToOtherTab = (index == _state.tabIndex);
_state.tabList[index].state = newTabState;
var newTabIndex = _state.tabIndex;
if (switchToOtherTab) {
for (i = index + 1; i < _state.tabList.length; ++i) {
if (_state.tabList[i].state == "visible") {
newTabIndex = i;
break;
}
}
if (newTabIndex == index) {
for (i = index; --i >= 0;) {
if (_state.tabList[i].state == "visible") {
newTabIndex = i;
break;
}
}
}
}
_state.tabIndex = newTabIndex;
this.setState(_state);
},
restorePanelContent: function (index, selected) {
var _state = this.state,
newTabState = "visible";
_state.tabList[index].state = newTabState;
if (selected || (_state.tabList[_state.tabIndex].state != newTabState)) {
_state.tabIndex = index;
}
this.setState(_state);
},
getPanelContentAt: function (index) {
var id = this._childrenList[index];
return (id == null) ? false : Panel.getPanelContent(id);
},
setActivePanelContent: function (index) {
this.setState({tabIndex: index || 0});
},
getActivePanelContent: function () {
return this.getPanelContentAt(this.state.tabIndex);
},
getPanelContentList: function (visible, hidden, removed, activeOnly, childrenOnly) {
var self = this,
list = [];
Object.keys(Panel._panelContentObjectList).forEach(function(id) {
var index = self._childrenList.indexOf(Number(id));
if ((childrenOnly || false) && index == -1) return;
var panelContent = Panel._panelContentObjectList[id];
if ((activeOnly || false) && !panelContent.isActive()) return;
if (!(removed || false) && panelContent.isRemoved()) return;
if (!(hidden || false) && panelContent.isHidden()) return;
if (!(visible || false) && (!panelContent.isRemoved() && !panelContent.isHidden())) return;
list.push(panelContent);
});
return list;
},
_childrenList: [],
_id: null,
statics: {
_register: function (panel) {
return (Panel._panelList.push(panel) - 1);
},
getPanel: function (id) {
return Panel._panelList[id] || false;
},
_getPanelContent: function (id) {
return Panel._panelContentList[id] || false;
},
getPanelContent: function (id) {
return Panel._panelContentObjectList[id] || false;
},
_setPanelContent: function (panelContent) {
var id = null;
if (panelContent.props._id != null) {
id = Number(panelContent.props._id);
Panel._panelContentList[id] = (panelContent);
} else {
id = Panel._panelContentList.length;
Panel._panelContentList[id] = (
<PanelContent {...panelContent.props}
_id={id}
/>
);
}
return id;
},
_setPanelContentObject: function (panelContentObject) {
var id = Number(panelContentObject.getId());
Panel._panelContentObjectList[id] = panelContentObject;
return id;
},
_panelContentList: [],
_panelContentObjectList: [],
_panelList: []
}
});
var PanelTab = React.createClass({
//This is the tab button in header not the tab's content
getDefaultProps: function () {
return {
"icon": "",
"title": "",
"index": 0,
"selected": false,
"showTitle": true
};
},
handleClick: function (event) {
event.preventDefault();
this.props.onClick(this);
},
render: function() {
var icon = null,
title = (this.props.showTitle && this.props.title.length) ? (
<span className="rpanel-title">{this.props.title}</span>
) : (
<span className="rpanel-title hidden"></span>
),
classes = (this.props.index == this.props.selected) ? "rpanel-tab active" : "rpanel-tab";
classes += " " + this.props.state;
if (this.props.icon) {
icon = (
<span className="rpanel-icon">
<i className={this.props.icon}></i>
</span>
);
}
return (
<li className={classes} onClick={this.handleClick}>
<a href="#" title={this.props.title}>{icon} {title}</a>
</li>
);
}
});