зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1256757 - Support AMD in tree modules. r=jryans
This commit is contained in:
Родитель
b28f45e73c
Коммит
298e20ef4f
|
@ -5,48 +5,51 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Shortcuts
|
||||
const { td, span } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
// Shortcuts
|
||||
const { td, span } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* Render the default cell used for toggle buttons
|
||||
*/
|
||||
var LabelCell = React.createClass({
|
||||
// See the TreeView component for details related
|
||||
// to the 'member' object.
|
||||
propTypes: {
|
||||
member: PropTypes.object.isRequired
|
||||
},
|
||||
/**
|
||||
* Render the default cell used for toggle buttons
|
||||
*/
|
||||
let LabelCell = React.createClass({
|
||||
// See the TreeView component for details related
|
||||
// to the 'member' object.
|
||||
propTypes: {
|
||||
member: PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
displayName: "LabelCell",
|
||||
displayName: "LabelCell",
|
||||
|
||||
render: function() {
|
||||
let member = this.props.member;
|
||||
let level = member.level || 0;
|
||||
render: function() {
|
||||
let member = this.props.member;
|
||||
let level = member.level || 0;
|
||||
|
||||
// Compute indentation dynamically. The deeper the item is
|
||||
// inside the hierarchy, the bigger is the left padding.
|
||||
let rowStyle = {
|
||||
"paddingLeft": (level * 16) + "px",
|
||||
};
|
||||
// Compute indentation dynamically. The deeper the item is
|
||||
// inside the hierarchy, the bigger is the left padding.
|
||||
let rowStyle = {
|
||||
"paddingLeft": (level * 16) + "px",
|
||||
};
|
||||
|
||||
return (
|
||||
td({
|
||||
className: "treeLabelCell",
|
||||
key: "default",
|
||||
style: rowStyle},
|
||||
span({ className: "treeIcon" }),
|
||||
span({ className: "treeLabel " + member.type + "Label" },
|
||||
member.name
|
||||
return (
|
||||
td({
|
||||
className: "treeLabelCell",
|
||||
key: "default",
|
||||
style: rowStyle},
|
||||
span({ className: "treeIcon" }),
|
||||
span({ className: "treeLabel " + member.type + "Label" },
|
||||
member.name
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
module.exports = LabelCell;
|
||||
// Exports from this module
|
||||
module.exports = LabelCell;
|
||||
});
|
||||
|
|
|
@ -5,83 +5,86 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Implementation of the default data provider. A provider is state less
|
||||
* object responsible for transformation data (usually a state) to
|
||||
* a structure that can be directly consumed by the tree-view component.
|
||||
*/
|
||||
var ObjectProvider = {
|
||||
getChildren: function(object) {
|
||||
let children = [];
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
/**
|
||||
* Implementation of the default data provider. A provider is state less
|
||||
* object responsible for transformation data (usually a state) to
|
||||
* a structure that can be directly consumed by the tree-view component.
|
||||
*/
|
||||
let ObjectProvider = {
|
||||
getChildren: function(object) {
|
||||
let children = [];
|
||||
|
||||
if (object instanceof ObjectProperty) {
|
||||
object = object.value;
|
||||
}
|
||||
|
||||
if (!object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof (object) == "string") {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (let prop in object) {
|
||||
try {
|
||||
children.push(new ObjectProperty(prop, object[prop]));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (object instanceof ObjectProperty) {
|
||||
object = object.value;
|
||||
}
|
||||
|
||||
if (!object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof (object) == "string") {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (let prop in object) {
|
||||
try {
|
||||
children.push(new ObjectProperty(prop, object[prop]));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
},
|
||||
|
||||
hasChildren: function(object) {
|
||||
if (object instanceof ObjectProperty) {
|
||||
object = object.value;
|
||||
}
|
||||
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof object == "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof object !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.keys(object).length > 1;
|
||||
},
|
||||
|
||||
getLabel: function(object) {
|
||||
return (object instanceof ObjectProperty) ?
|
||||
object.name : null;
|
||||
},
|
||||
|
||||
getValue: function(object) {
|
||||
return (object instanceof ObjectProperty) ?
|
||||
object.value : null;
|
||||
},
|
||||
|
||||
getKey: function(object) {
|
||||
return (object instanceof ObjectProperty) ?
|
||||
object.name : null;
|
||||
},
|
||||
|
||||
getType: function(object) {
|
||||
return (object instanceof ObjectProperty) ?
|
||||
typeof object.value : typeof object;
|
||||
}
|
||||
return children;
|
||||
},
|
||||
};
|
||||
|
||||
hasChildren: function(object) {
|
||||
if (object instanceof ObjectProperty) {
|
||||
object = object.value;
|
||||
}
|
||||
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof object == "string") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof object !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.keys(object).length > 1;
|
||||
},
|
||||
|
||||
getLabel: function(object) {
|
||||
return (object instanceof ObjectProperty) ?
|
||||
object.name : null;
|
||||
},
|
||||
|
||||
getValue: function(object) {
|
||||
return (object instanceof ObjectProperty) ?
|
||||
object.value : null;
|
||||
},
|
||||
|
||||
getKey: function(object) {
|
||||
return (object instanceof ObjectProperty) ?
|
||||
object.name : null;
|
||||
},
|
||||
|
||||
getType: function(object) {
|
||||
return (object instanceof ObjectProperty) ?
|
||||
typeof object.value : typeof object;
|
||||
function ObjectProperty(name, value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
};
|
||||
|
||||
function ObjectProperty(name, value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.ObjectProperty = ObjectProperty;
|
||||
exports.ObjectProvider = ObjectProvider;
|
||||
// Exports from this module
|
||||
exports.ObjectProperty = ObjectProperty;
|
||||
exports.ObjectProvider = ObjectProvider;
|
||||
});
|
||||
|
|
|
@ -5,92 +5,95 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Shortcuts
|
||||
const { td, span } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* This template represents a cell in TreeView row. It's rendered
|
||||
* using <td> element (the row is <tr> and the entire tree is <table>).
|
||||
*/
|
||||
var TreeCell = React.createClass({
|
||||
// See TreeView component for detailed property explanation.
|
||||
propTypes: {
|
||||
value: PropTypes.any,
|
||||
decorator: PropTypes.object,
|
||||
id: PropTypes.string.isRequired,
|
||||
member: PropTypes.object.isRequired,
|
||||
renderValue: PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
displayName: "TreeCell",
|
||||
// Shortcuts
|
||||
const { td, span } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* Optimize cell rendering. If value is the same do not render.
|
||||
* This template represents a cell in TreeView row. It's rendered
|
||||
* using <td> element (the row is <tr> and the entire tree is <table>).
|
||||
*/
|
||||
shouldComponentUpdate: function(nextProps) {
|
||||
return (this.props.value != nextProps.value);
|
||||
},
|
||||
let TreeCell = React.createClass({
|
||||
// See TreeView component for detailed property explanation.
|
||||
propTypes: {
|
||||
value: PropTypes.any,
|
||||
decorator: PropTypes.object,
|
||||
id: PropTypes.string.isRequired,
|
||||
member: PropTypes.object.isRequired,
|
||||
renderValue: PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
getCellClass: function(object, id) {
|
||||
let decorator = this.props.decorator;
|
||||
if (!decorator || !decorator.getCellClass) {
|
||||
return [];
|
||||
displayName: "TreeCell",
|
||||
|
||||
/**
|
||||
* Optimize cell rendering. If value is the same do not render.
|
||||
*/
|
||||
shouldComponentUpdate: function(nextProps) {
|
||||
return (this.props.value != nextProps.value);
|
||||
},
|
||||
|
||||
getCellClass: function(object, id) {
|
||||
let decorator = this.props.decorator;
|
||||
if (!decorator || !decorator.getCellClass) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Decorator can return a simple string or array of strings.
|
||||
let classNames = decorator.getCellClass(object, id);
|
||||
if (!classNames) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof classNames == "string") {
|
||||
classNames = [classNames];
|
||||
}
|
||||
|
||||
return classNames;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let member = this.props.member;
|
||||
let type = member.type || "";
|
||||
let id = this.props.id;
|
||||
let value = this.props.value;
|
||||
let decorator = this.props.decorator;
|
||||
|
||||
// Compute class name list for the <td> element.
|
||||
let classNames = this.getCellClass(member.object, id) || [];
|
||||
classNames.push("treeValueCell");
|
||||
classNames.push(type + "Cell");
|
||||
|
||||
// Render value using a default render function or custom
|
||||
// provided function from props or a decorator.
|
||||
let renderValue = this.props.renderValue || defaultRenderValue;
|
||||
if (decorator && decorator.renderValue) {
|
||||
renderValue = decorator.renderValue(member.object, id) || renderValue;
|
||||
}
|
||||
|
||||
let props = Object.assign({}, this.props, {
|
||||
object: value,
|
||||
});
|
||||
|
||||
// Render me!
|
||||
return (
|
||||
td({ className: classNames.join(" ") },
|
||||
span({}, renderValue(props))
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Decorator can return a simple string or array of strings.
|
||||
let classNames = decorator.getCellClass(object, id);
|
||||
if (!classNames) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof classNames == "string") {
|
||||
classNames = [classNames];
|
||||
}
|
||||
|
||||
return classNames;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let member = this.props.member;
|
||||
let type = member.type || "";
|
||||
let id = this.props.id;
|
||||
let value = this.props.value;
|
||||
let decorator = this.props.decorator;
|
||||
|
||||
// Compute class name list for the <td> element.
|
||||
let classNames = this.getCellClass(member.object, id) || [];
|
||||
classNames.push("treeValueCell");
|
||||
classNames.push(type + "Cell");
|
||||
|
||||
// Render value using a default render function or custom
|
||||
// provided function from props or a decorator.
|
||||
let renderValue = this.props.renderValue || defaultRenderValue;
|
||||
if (decorator && decorator.renderValue) {
|
||||
renderValue = decorator.renderValue(member.object, id) || renderValue;
|
||||
}
|
||||
|
||||
let props = Object.assign({}, this.props, {
|
||||
object: value,
|
||||
});
|
||||
|
||||
// Render me!
|
||||
// Default value rendering.
|
||||
let defaultRenderValue = props => {
|
||||
return (
|
||||
td({ className: classNames.join(" ") },
|
||||
span({}, renderValue(props))
|
||||
)
|
||||
props.object + ""
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
module.exports = TreeCell;
|
||||
});
|
||||
|
||||
// Default value rendering.
|
||||
var defaultRenderValue = props => {
|
||||
return (
|
||||
props.object + ""
|
||||
);
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
module.exports = TreeCell;
|
||||
|
|
|
@ -5,93 +5,96 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Shortcuts
|
||||
const { thead, tr, td, div } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
// Shortcuts
|
||||
const { thead, tr, td, div } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* This component is responsible for rendering tree header.
|
||||
* It's based on <thead> element.
|
||||
*/
|
||||
var TreeHeader = React.createClass({
|
||||
// See also TreeView component for detailed info about properties.
|
||||
propTypes: {
|
||||
// Custom tree decorator
|
||||
decorator: PropTypes.object,
|
||||
// True if the header should be visible
|
||||
header: PropTypes.bool,
|
||||
// Array with column definition
|
||||
columns: PropTypes.array
|
||||
},
|
||||
/**
|
||||
* This component is responsible for rendering tree header.
|
||||
* It's based on <thead> element.
|
||||
*/
|
||||
let TreeHeader = React.createClass({
|
||||
// See also TreeView component for detailed info about properties.
|
||||
propTypes: {
|
||||
// Custom tree decorator
|
||||
decorator: PropTypes.object,
|
||||
// True if the header should be visible
|
||||
header: PropTypes.bool,
|
||||
// Array with column definition
|
||||
columns: PropTypes.array
|
||||
},
|
||||
|
||||
displayName: "TreeHeader",
|
||||
displayName: "TreeHeader",
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
columns: [{
|
||||
id: "default"
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
getHeaderClass: function(colId) {
|
||||
let decorator = this.props.decorator;
|
||||
if (!decorator || !decorator.getHeaderClass) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Decorator can return a simple string or array of strings.
|
||||
let classNames = decorator.getHeaderClass(colId);
|
||||
if (!classNames) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof classNames == "string") {
|
||||
classNames = [classNames];
|
||||
}
|
||||
|
||||
return classNames;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let cells = [];
|
||||
let visible = this.props.header;
|
||||
|
||||
// Render the rest of the columns (if any)
|
||||
this.props.columns.forEach(col => {
|
||||
let cellStyle = {
|
||||
"width": col.width ? col.width : "",
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
columns: [{
|
||||
id: "default"
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
let classNames = [];
|
||||
|
||||
if (visible) {
|
||||
classNames = this.getHeaderClass(col.id);
|
||||
classNames.push("treeHeaderCell");
|
||||
getHeaderClass: function(colId) {
|
||||
let decorator = this.props.decorator;
|
||||
if (!decorator || !decorator.getHeaderClass) {
|
||||
return [];
|
||||
}
|
||||
|
||||
cells.push(
|
||||
td({
|
||||
className: classNames.join(" "),
|
||||
style: cellStyle,
|
||||
key: col.id},
|
||||
div({ className: visible ? "treeHeaderCellBox" : "" },
|
||||
visible ? col.title : ""
|
||||
// Decorator can return a simple string or array of strings.
|
||||
let classNames = decorator.getHeaderClass(colId);
|
||||
if (!classNames) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof classNames == "string") {
|
||||
classNames = [classNames];
|
||||
}
|
||||
|
||||
return classNames;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let cells = [];
|
||||
let visible = this.props.header;
|
||||
|
||||
// Render the rest of the columns (if any)
|
||||
this.props.columns.forEach(col => {
|
||||
let cellStyle = {
|
||||
"width": col.width ? col.width : "",
|
||||
};
|
||||
|
||||
let classNames = [];
|
||||
|
||||
if (visible) {
|
||||
classNames = this.getHeaderClass(col.id);
|
||||
classNames.push("treeHeaderCell");
|
||||
}
|
||||
|
||||
cells.push(
|
||||
td({
|
||||
className: classNames.join(" "),
|
||||
style: cellStyle,
|
||||
key: col.id},
|
||||
div({ className: visible ? "treeHeaderCellBox" : "" },
|
||||
visible ? col.title : ""
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
thead({}, tr({ className: visible ? "treeHeaderRow" : "" },
|
||||
cells
|
||||
))
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
thead({}, tr({ className: visible ? "treeHeaderRow" : "" },
|
||||
cells
|
||||
))
|
||||
);
|
||||
}
|
||||
// Exports from this module
|
||||
module.exports = TreeHeader;
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
module.exports = TreeHeader;
|
||||
|
|
|
@ -5,177 +5,180 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
||||
|
||||
// Tree
|
||||
const TreeCell = React.createFactory(require("./tree-cell"));
|
||||
const LabelCell = React.createFactory(require("./label-cell"));
|
||||
// Tree
|
||||
const TreeCell = React.createFactory(require("./tree-cell"));
|
||||
const LabelCell = React.createFactory(require("./label-cell"));
|
||||
|
||||
// Shortcuts
|
||||
const { tr } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* This template represents a node in TreeView component. It's rendered
|
||||
* using <tr> element (the entire tree is one big <table>).
|
||||
*/
|
||||
var TreeRow = React.createClass({
|
||||
// See TreeView component for more details about the props and
|
||||
// the 'member' object.
|
||||
propTypes: {
|
||||
member: PropTypes.shape({
|
||||
object: PropTypes.obSject,
|
||||
name: PropTypes.sring,
|
||||
type: PropTypes.string.isRequired,
|
||||
rowClass: PropTypes.string.isRequired,
|
||||
level: PropTypes.number.isRequired,
|
||||
hasChildren: PropTypes.bool,
|
||||
value: PropTypes.any,
|
||||
open: PropTypes.bool.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
}),
|
||||
decorator: PropTypes.object,
|
||||
renderCell: PropTypes.object,
|
||||
renderLabelCell: PropTypes.object,
|
||||
columns: PropTypes.array.isRequired,
|
||||
provider: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
displayName: "TreeRow",
|
||||
// Shortcuts
|
||||
const { tr } = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* Optimize row rendering. If props are the same do not render.
|
||||
* This makes the rendering a lot faster!
|
||||
* This template represents a node in TreeView component. It's rendered
|
||||
* using <tr> element (the entire tree is one big <table>).
|
||||
*/
|
||||
shouldComponentUpdate: function(nextProps) {
|
||||
let props = ["name", "open", "value", "loading"];
|
||||
for (let p in props) {
|
||||
if (nextProps.member[props[p]] != this.props.member[props[p]]) {
|
||||
return true;
|
||||
let TreeRow = React.createClass({
|
||||
// See TreeView component for more details about the props and
|
||||
// the 'member' object.
|
||||
propTypes: {
|
||||
member: PropTypes.shape({
|
||||
object: PropTypes.obSject,
|
||||
name: PropTypes.sring,
|
||||
type: PropTypes.string.isRequired,
|
||||
rowClass: PropTypes.string.isRequired,
|
||||
level: PropTypes.number.isRequired,
|
||||
hasChildren: PropTypes.bool,
|
||||
value: PropTypes.any,
|
||||
open: PropTypes.bool.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
}),
|
||||
decorator: PropTypes.object,
|
||||
renderCell: PropTypes.object,
|
||||
renderLabelCell: PropTypes.object,
|
||||
columns: PropTypes.array.isRequired,
|
||||
provider: PropTypes.object.isRequired,
|
||||
onClick: PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
displayName: "TreeRow",
|
||||
|
||||
/**
|
||||
* Optimize row rendering. If props are the same do not render.
|
||||
* This makes the rendering a lot faster!
|
||||
*/
|
||||
shouldComponentUpdate: function(nextProps) {
|
||||
let props = ["name", "open", "value", "loading"];
|
||||
for (let p in props) {
|
||||
if (nextProps.member[props[p]] != this.props.member[props[p]]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
return false;
|
||||
},
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// I don't like accessing the underlying DOM elements directly,
|
||||
// but this optimization makes the filtering so damn fast!
|
||||
// The row doesn't have to be re-rendered, all we really need
|
||||
// to do is toggling a class name.
|
||||
// The important part is that DOM elements don't need to be
|
||||
// re-created when they should appear again.
|
||||
if (nextProps.member.hidden != this.props.member.hidden) {
|
||||
let row = ReactDOM.findDOMNode(this);
|
||||
row.classList.toggle("hidden");
|
||||
}
|
||||
},
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// I don't like accessing the underlying DOM elements directly,
|
||||
// but this optimization makes the filtering so damn fast!
|
||||
// The row doesn't have to be re-rendered, all we really need
|
||||
// to do is toggling a class name.
|
||||
// The important part is that DOM elements don't need to be
|
||||
// re-created when they should appear again.
|
||||
if (nextProps.member.hidden != this.props.member.hidden) {
|
||||
let row = ReactDOM.findDOMNode(this);
|
||||
row.classList.toggle("hidden");
|
||||
}
|
||||
},
|
||||
|
||||
getRowClass: function(object) {
|
||||
let decorator = this.props.decorator;
|
||||
if (!decorator || !decorator.getRowClass) {
|
||||
return [];
|
||||
}
|
||||
getRowClass: function(object) {
|
||||
let decorator = this.props.decorator;
|
||||
if (!decorator || !decorator.getRowClass) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Decorator can return a simple string or array of strings.
|
||||
let classNames = decorator.getRowClass(object);
|
||||
if (!classNames) {
|
||||
return [];
|
||||
}
|
||||
// Decorator can return a simple string or array of strings.
|
||||
let classNames = decorator.getRowClass(object);
|
||||
if (!classNames) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof classNames == "string") {
|
||||
classNames = [classNames];
|
||||
}
|
||||
if (typeof classNames == "string") {
|
||||
classNames = [classNames];
|
||||
}
|
||||
|
||||
return classNames;
|
||||
},
|
||||
return classNames;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let member = this.props.member;
|
||||
let decorator = this.props.decorator;
|
||||
render: function() {
|
||||
let member = this.props.member;
|
||||
let decorator = this.props.decorator;
|
||||
|
||||
// Compute class name list for the <tr> element.
|
||||
let classNames = this.getRowClass(member.object) || [];
|
||||
classNames.push("treeRow");
|
||||
classNames.push(member.type + "Row");
|
||||
// Compute class name list for the <tr> element.
|
||||
let classNames = this.getRowClass(member.object) || [];
|
||||
classNames.push("treeRow");
|
||||
classNames.push(member.type + "Row");
|
||||
|
||||
if (member.hasChildren) {
|
||||
classNames.push("hasChildren");
|
||||
}
|
||||
if (member.hasChildren) {
|
||||
classNames.push("hasChildren");
|
||||
}
|
||||
|
||||
if (member.open) {
|
||||
classNames.push("opened");
|
||||
}
|
||||
if (member.open) {
|
||||
classNames.push("opened");
|
||||
}
|
||||
|
||||
if (member.loading) {
|
||||
classNames.push("loading");
|
||||
}
|
||||
if (member.loading) {
|
||||
classNames.push("loading");
|
||||
}
|
||||
|
||||
if (member.hidden) {
|
||||
classNames.push("hidden");
|
||||
}
|
||||
if (member.hidden) {
|
||||
classNames.push("hidden");
|
||||
}
|
||||
|
||||
// The label column (with toggle buttons) is usually
|
||||
// the first one, but there might be cases (like in
|
||||
// the Memory panel) where the toggling is done
|
||||
// in the last column.
|
||||
let cells = [];
|
||||
// The label column (with toggle buttons) is usually
|
||||
// the first one, but there might be cases (like in
|
||||
// the Memory panel) where the toggling is done
|
||||
// in the last column.
|
||||
let cells = [];
|
||||
|
||||
// Get components for rendering cells.
|
||||
let renderCell = this.props.renderCell || RenderCell;
|
||||
let renderLabelCell = this.props.renderLabelCell || RenderLabelCell;
|
||||
if (decorator && decorator.renderLabelCell) {
|
||||
renderLabelCell = decorator.renderLabelCell(member.object) ||
|
||||
renderLabelCell;
|
||||
}
|
||||
// Get components for rendering cells.
|
||||
let renderCell = this.props.renderCell || RenderCell;
|
||||
let renderLabelCell = this.props.renderLabelCell || RenderLabelCell;
|
||||
if (decorator && decorator.renderLabelCell) {
|
||||
renderLabelCell = decorator.renderLabelCell(member.object) ||
|
||||
renderLabelCell;
|
||||
}
|
||||
|
||||
// Render a cell for every column.
|
||||
this.props.columns.forEach(col => {
|
||||
let props = Object.assign({}, this.props, {
|
||||
key: col.id,
|
||||
id: col.id,
|
||||
value: this.props.provider.getValue(member.object, col.id)
|
||||
// Render a cell for every column.
|
||||
this.props.columns.forEach(col => {
|
||||
let props = Object.assign({}, this.props, {
|
||||
key: col.id,
|
||||
id: col.id,
|
||||
value: this.props.provider.getValue(member.object, col.id)
|
||||
});
|
||||
|
||||
if (decorator && decorator.renderCell) {
|
||||
renderCell = decorator.renderCell(member.object, col.id);
|
||||
}
|
||||
|
||||
let render = (col.id == "default") ? renderLabelCell : renderCell;
|
||||
|
||||
// Some cells don't have to be rendered. This happens when some
|
||||
// other cells span more columns. Note that the label cells contains
|
||||
// toggle buttons and should be usually there unless we are rendering
|
||||
// a simple non-expandable table.
|
||||
if (render) {
|
||||
cells.push(render(props));
|
||||
}
|
||||
});
|
||||
|
||||
if (decorator && decorator.renderCell) {
|
||||
renderCell = decorator.renderCell(member.object, col.id);
|
||||
}
|
||||
// Render tree row
|
||||
return (
|
||||
tr({
|
||||
className: classNames.join(" "),
|
||||
onClick: this.props.onClick},
|
||||
cells
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let render = (col.id == "default") ? renderLabelCell : renderCell;
|
||||
// Helpers
|
||||
|
||||
// Some cells don't have to be rendered. This happens when some
|
||||
// other cells span more columns. Note that the label cells contains
|
||||
// toggle buttons and should be usually there unless we are rendering
|
||||
// a simple non-expandable table.
|
||||
if (render) {
|
||||
cells.push(render(props));
|
||||
}
|
||||
});
|
||||
let RenderCell = props => {
|
||||
return TreeCell(props);
|
||||
};
|
||||
|
||||
// Render tree row
|
||||
return (
|
||||
tr({
|
||||
className: classNames.join(" "),
|
||||
onClick: this.props.onClick},
|
||||
cells
|
||||
)
|
||||
);
|
||||
}
|
||||
let RenderLabelCell = props => {
|
||||
return LabelCell(props);
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
module.exports = TreeRow;
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
var RenderCell = props => {
|
||||
return TreeCell(props);
|
||||
};
|
||||
|
||||
var RenderLabelCell = props => {
|
||||
return LabelCell(props);
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
module.exports = TreeRow;
|
||||
|
|
|
@ -5,344 +5,348 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// ReactJS
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
|
||||
// Reps
|
||||
const { ObjectProvider } = require("./object-provider");
|
||||
const TreeRow = React.createFactory(require("./tree-row"));
|
||||
const TreeHeader = React.createFactory(require("./tree-header"));
|
||||
// Reps
|
||||
const { ObjectProvider } = require("./object-provider");
|
||||
const TreeRow = React.createFactory(require("./tree-row"));
|
||||
const TreeHeader = React.createFactory(require("./tree-header"));
|
||||
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
// Shortcuts
|
||||
const DOM = React.DOM;
|
||||
const PropTypes = React.PropTypes;
|
||||
|
||||
/**
|
||||
* This component represents a tree view with expandable/collapsible nodes.
|
||||
* The tree is rendered using <table> element where every node is represented
|
||||
* by <tr> element. The tree is one big table where nodes (rows) are properly
|
||||
* indented from the left to mimic hierarchical structure of the data.
|
||||
*
|
||||
* The tree can have arbitrary number of columns and so, might be use
|
||||
* as an expandable tree-table UI widget as well. By default, there is
|
||||
* one column for node label and one for node value.
|
||||
*
|
||||
* The tree is maintaining its (presentation) state, which consists
|
||||
* from list of expanded nodes and list of columns.
|
||||
*
|
||||
* Complete data provider interface:
|
||||
* var TreeProvider = {
|
||||
* getChildren: function(object);
|
||||
* hasChildren: function(object);
|
||||
* getLabel: function(object, colId);
|
||||
* getValue: function(object, colId);
|
||||
* getKey: function(object);
|
||||
* getType: function(object);
|
||||
* }
|
||||
*
|
||||
* Complete tree decorator interface:
|
||||
* var TreeDecorator = {
|
||||
* getRowClass: function(object);
|
||||
* getCellClass: function(object, colId);
|
||||
* getHeaderClass: function(colId);
|
||||
* renderValue: function(object, colId);
|
||||
* renderRow: function(object);
|
||||
* renderCelL: function(object, colId);
|
||||
* renderLabelCell: function(object);
|
||||
* }
|
||||
*/
|
||||
var TreeView = React.createClass({
|
||||
// The only required property (not set by default) is the input data
|
||||
// object that is used to puputate the tree.
|
||||
propTypes: {
|
||||
// The input data object.
|
||||
object: PropTypes.any,
|
||||
className: PropTypes.string,
|
||||
// Data provider (see also the interface above)
|
||||
provider: PropTypes.shape({
|
||||
getChildren: PropTypes.func,
|
||||
hasChildren: PropTypes.func,
|
||||
getLabel: PropTypes.func,
|
||||
getValue: PropTypes.func,
|
||||
getKey: PropTypes.func,
|
||||
getType: PropTypes.func,
|
||||
}).isRequired,
|
||||
// Tree decorator (see also the interface above)
|
||||
decorator: PropTypes.shape({
|
||||
getRowClass: PropTypes.func,
|
||||
getCellClass: PropTypes.func,
|
||||
getHeaderClass: PropTypes.func,
|
||||
renderValue: PropTypes.func,
|
||||
/**
|
||||
* This component represents a tree view with expandable/collapsible nodes.
|
||||
* The tree is rendered using <table> element where every node is represented
|
||||
* by <tr> element. The tree is one big table where nodes (rows) are properly
|
||||
* indented from the left to mimic hierarchical structure of the data.
|
||||
*
|
||||
* The tree can have arbitrary number of columns and so, might be use
|
||||
* as an expandable tree-table UI widget as well. By default, there is
|
||||
* one column for node label and one for node value.
|
||||
*
|
||||
* The tree is maintaining its (presentation) state, which consists
|
||||
* from list of expanded nodes and list of columns.
|
||||
*
|
||||
* Complete data provider interface:
|
||||
* var TreeProvider = {
|
||||
* getChildren: function(object);
|
||||
* hasChildren: function(object);
|
||||
* getLabel: function(object, colId);
|
||||
* getValue: function(object, colId);
|
||||
* getKey: function(object);
|
||||
* getType: function(object);
|
||||
* }
|
||||
*
|
||||
* Complete tree decorator interface:
|
||||
* var TreeDecorator = {
|
||||
* getRowClass: function(object);
|
||||
* getCellClass: function(object, colId);
|
||||
* getHeaderClass: function(colId);
|
||||
* renderValue: function(object, colId);
|
||||
* renderRow: function(object);
|
||||
* renderCelL: function(object, colId);
|
||||
* renderLabelCell: function(object);
|
||||
* }
|
||||
*/
|
||||
let TreeView = React.createClass({
|
||||
// The only required property (not set by default) is the input data
|
||||
// object that is used to puputate the tree.
|
||||
propTypes: {
|
||||
// The input data object.
|
||||
object: PropTypes.any,
|
||||
className: PropTypes.string,
|
||||
// Data provider (see also the interface above)
|
||||
provider: PropTypes.shape({
|
||||
getChildren: PropTypes.func,
|
||||
hasChildren: PropTypes.func,
|
||||
getLabel: PropTypes.func,
|
||||
getValue: PropTypes.func,
|
||||
getKey: PropTypes.func,
|
||||
getType: PropTypes.func,
|
||||
}).isRequired,
|
||||
// Tree decorator (see also the interface above)
|
||||
decorator: PropTypes.shape({
|
||||
getRowClass: PropTypes.func,
|
||||
getCellClass: PropTypes.func,
|
||||
getHeaderClass: PropTypes.func,
|
||||
renderValue: PropTypes.func,
|
||||
renderRow: PropTypes.func,
|
||||
renderCelL: PropTypes.func,
|
||||
renderLabelCell: PropTypes.func,
|
||||
}),
|
||||
// Custom tree row (node) renderer
|
||||
renderRow: PropTypes.func,
|
||||
renderCelL: PropTypes.func,
|
||||
// Custom cell renderer
|
||||
renderCell: PropTypes.func,
|
||||
// Custom value renderef
|
||||
renderValue: PropTypes.func,
|
||||
// Custom tree label (including a toggle button) renderer
|
||||
renderLabelCell: PropTypes.func,
|
||||
}),
|
||||
// Custom tree row (node) renderer
|
||||
renderRow: PropTypes.func,
|
||||
// Custom cell renderer
|
||||
renderCell: PropTypes.func,
|
||||
// Custom value renderef
|
||||
renderValue: PropTypes.func,
|
||||
// Custom tree label (including a toggle button) renderer
|
||||
renderLabelCell: PropTypes.func,
|
||||
// Set of expanded nodes
|
||||
expandedNodes: PropTypes.object,
|
||||
// Custom filtering callback
|
||||
onFilter: PropTypes.func,
|
||||
// Custom sorting callback
|
||||
onSort: PropTypes.func,
|
||||
// A header is displayed if set to true
|
||||
header: PropTypes.bool,
|
||||
// Array of columns
|
||||
columns: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
width: PropTypes.string
|
||||
}))
|
||||
},
|
||||
// Set of expanded nodes
|
||||
expandedNodes: PropTypes.object,
|
||||
// Custom filtering callback
|
||||
onFilter: PropTypes.func,
|
||||
// Custom sorting callback
|
||||
onSort: PropTypes.func,
|
||||
// A header is displayed if set to true
|
||||
header: PropTypes.bool,
|
||||
// Array of columns
|
||||
columns: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
width: PropTypes.string
|
||||
}))
|
||||
},
|
||||
|
||||
displayName: "TreeView",
|
||||
displayName: "TreeView",
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
object: null,
|
||||
renderRow: null,
|
||||
provider: ObjectProvider,
|
||||
expandedNodes: new Set(),
|
||||
columns: []
|
||||
};
|
||||
},
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
object: null,
|
||||
renderRow: null,
|
||||
provider: ObjectProvider,
|
||||
expandedNodes: new Set(),
|
||||
columns: []
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
expandedNodes: this.props.expandedNodes,
|
||||
columns: ensureDefaultColumn(this.props.columns)
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
expandedNodes: this.props.expandedNodes,
|
||||
columns: ensureDefaultColumn(this.props.columns)
|
||||
};
|
||||
},
|
||||
|
||||
// Node expand/collapse
|
||||
// Node expand/collapse
|
||||
|
||||
toggle: function(nodePath) {
|
||||
let nodes = this.state.expandedNodes;
|
||||
if (this.isExpanded(nodePath)) {
|
||||
nodes.delete(nodePath);
|
||||
} else {
|
||||
nodes.add(nodePath);
|
||||
}
|
||||
|
||||
// Compute new state and update the tree.
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
expandedNodes: nodes
|
||||
}));
|
||||
},
|
||||
|
||||
isExpanded: function(nodePath) {
|
||||
return this.state.expandedNodes.has(nodePath);
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
|
||||
onClickRow: function(nodePath, event) {
|
||||
event.stopPropagation();
|
||||
this.toggle(nodePath);
|
||||
},
|
||||
|
||||
// Filtering & Sorting
|
||||
|
||||
/**
|
||||
* Filter out nodes that don't correspond to the current filter.
|
||||
* @return {Boolean} true if the node should be visible otherwise false.
|
||||
*/
|
||||
onFilter: function(object) {
|
||||
let onFilter = this.props.onFilter;
|
||||
return onFilter ? onFilter(object) : true;
|
||||
},
|
||||
|
||||
onSort: function(parent, children) {
|
||||
let onSort = this.props.onSort;
|
||||
return onSort ? onSort(parent, children) : children;
|
||||
},
|
||||
|
||||
// Members
|
||||
|
||||
/**
|
||||
* Return children node objects (so called 'members') for given
|
||||
* parent object.
|
||||
*/
|
||||
getMembers: function(parent, level, path) {
|
||||
// Strings don't have children. Note that 'long' strings are using
|
||||
// the expander icon (+/-) to display the entire original value,
|
||||
// but there are no child items.
|
||||
if (typeof parent == "string") {
|
||||
return [];
|
||||
}
|
||||
|
||||
let provider = this.props.provider;
|
||||
let children = provider.getChildren(parent) || [];
|
||||
|
||||
// If the return value is non-array, the children
|
||||
// are being loaded asynchronously.
|
||||
if (!Array.isArray(children)) {
|
||||
return children;
|
||||
}
|
||||
|
||||
children = this.onSort(parent, children) || children;
|
||||
|
||||
return children.map(child => {
|
||||
let key = provider.getKey(child);
|
||||
let nodePath = path + "/" + key;
|
||||
let type = provider.getType(child);
|
||||
let hasChildren = provider.hasChildren(child);
|
||||
|
||||
// Value with no column specified is used for optimization.
|
||||
// The row is re-rendered only if this value changes.
|
||||
// Value for actual column is get when a cell is rendered.
|
||||
let value = provider.getValue(child);
|
||||
|
||||
if (isLongString(value)) {
|
||||
hasChildren = true;
|
||||
toggle: function(nodePath) {
|
||||
let nodes = this.state.expandedNodes;
|
||||
if (this.isExpanded(nodePath)) {
|
||||
nodes.delete(nodePath);
|
||||
} else {
|
||||
nodes.add(nodePath);
|
||||
}
|
||||
|
||||
// Return value is a 'member' object containing meta-data about
|
||||
// tree node. It describes node label, value, type, etc.
|
||||
return {
|
||||
// An object associated with this node.
|
||||
object: child,
|
||||
// A label for the child node
|
||||
name: provider.getLabel(child),
|
||||
// Data type of the child node (used for CSS customization)
|
||||
type: type,
|
||||
// Class attribute computed from the type.
|
||||
rowClass: "treeRow-" + type,
|
||||
// Level of the child within the hierarchy (top == 0)
|
||||
level: level,
|
||||
// True if this node has children.
|
||||
hasChildren: hasChildren,
|
||||
// Value associated with this node (as provided by the data provider)
|
||||
value: value,
|
||||
// True if the node is expanded.
|
||||
open: this.isExpanded(nodePath),
|
||||
// Node path
|
||||
path: nodePath,
|
||||
// True if the node is hidden (used for filtering)
|
||||
hidden: !this.onFilter(child)
|
||||
};
|
||||
});
|
||||
},
|
||||
// Compute new state and update the tree.
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
expandedNodes: nodes
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
* Render tree rows/nodes.
|
||||
*/
|
||||
renderRows: function(parent, level = 0, path = "") {
|
||||
let rows = [];
|
||||
let decorator = this.props.decorator;
|
||||
let renderRow = this.props.renderRow || TreeRow;
|
||||
isExpanded: function(nodePath) {
|
||||
return this.state.expandedNodes.has(nodePath);
|
||||
},
|
||||
|
||||
// Get children for given parent node, iterate over them and render
|
||||
// a row for every one. Use row template (a component) from properties.
|
||||
// If the return value is non-array, the children are being loaded
|
||||
// asynchronously.
|
||||
let members = this.getMembers(parent, level, path);
|
||||
if (!Array.isArray(members)) {
|
||||
return members;
|
||||
}
|
||||
// Event Handlers
|
||||
|
||||
members.forEach(member => {
|
||||
if (decorator && decorator.renderRow) {
|
||||
renderRow = decorator.renderRow(member.object) || renderRow;
|
||||
onClickRow: function(nodePath, event) {
|
||||
event.stopPropagation();
|
||||
this.toggle(nodePath);
|
||||
},
|
||||
|
||||
// Filtering & Sorting
|
||||
|
||||
/**
|
||||
* Filter out nodes that don't correspond to the current filter.
|
||||
* @return {Boolean} true if the node should be visible otherwise false.
|
||||
*/
|
||||
onFilter: function(object) {
|
||||
let onFilter = this.props.onFilter;
|
||||
return onFilter ? onFilter(object) : true;
|
||||
},
|
||||
|
||||
onSort: function(parent, children) {
|
||||
let onSort = this.props.onSort;
|
||||
return onSort ? onSort(parent, children) : children;
|
||||
},
|
||||
|
||||
// Members
|
||||
|
||||
/**
|
||||
* Return children node objects (so called 'members') for given
|
||||
* parent object.
|
||||
*/
|
||||
getMembers: function(parent, level, path) {
|
||||
// Strings don't have children. Note that 'long' strings are using
|
||||
// the expander icon (+/-) to display the entire original value,
|
||||
// but there are no child items.
|
||||
if (typeof parent == "string") {
|
||||
return [];
|
||||
}
|
||||
|
||||
let provider = this.props.provider;
|
||||
let children = provider.getChildren(parent) || [];
|
||||
|
||||
// If the return value is non-array, the children
|
||||
// are being loaded asynchronously.
|
||||
if (!Array.isArray(children)) {
|
||||
return children;
|
||||
}
|
||||
|
||||
children = this.onSort(parent, children) || children;
|
||||
|
||||
return children.map(child => {
|
||||
let key = provider.getKey(child);
|
||||
let nodePath = path + "/" + key;
|
||||
let type = provider.getType(child);
|
||||
let hasChildren = provider.hasChildren(child);
|
||||
|
||||
// Value with no column specified is used for optimization.
|
||||
// The row is re-rendered only if this value changes.
|
||||
// Value for actual column is get when a cell is rendered.
|
||||
let value = provider.getValue(child);
|
||||
|
||||
if (isLongString(value)) {
|
||||
hasChildren = true;
|
||||
}
|
||||
|
||||
// Return value is a 'member' object containing meta-data about
|
||||
// tree node. It describes node label, value, type, etc.
|
||||
return {
|
||||
// An object associated with this node.
|
||||
object: child,
|
||||
// A label for the child node
|
||||
name: provider.getLabel(child),
|
||||
// Data type of the child node (used for CSS customization)
|
||||
type: type,
|
||||
// Class attribute computed from the type.
|
||||
rowClass: "treeRow-" + type,
|
||||
// Level of the child within the hierarchy (top == 0)
|
||||
level: level,
|
||||
// True if this node has children.
|
||||
hasChildren: hasChildren,
|
||||
// Value associated with this node (as provided by the data provider)
|
||||
value: value,
|
||||
// True if the node is expanded.
|
||||
open: this.isExpanded(nodePath),
|
||||
// Node path
|
||||
path: nodePath,
|
||||
// True if the node is hidden (used for filtering)
|
||||
hidden: !this.onFilter(child)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Render tree rows/nodes.
|
||||
*/
|
||||
renderRows: function(parent, level = 0, path = "") {
|
||||
let rows = [];
|
||||
let decorator = this.props.decorator;
|
||||
let renderRow = this.props.renderRow || TreeRow;
|
||||
|
||||
// Get children for given parent node, iterate over them and render
|
||||
// a row for every one. Use row template (a component) from properties.
|
||||
// If the return value is non-array, the children are being loaded
|
||||
// asynchronously.
|
||||
let members = this.getMembers(parent, level, path);
|
||||
if (!Array.isArray(members)) {
|
||||
return members;
|
||||
}
|
||||
|
||||
members.forEach(member => {
|
||||
if (decorator && decorator.renderRow) {
|
||||
renderRow = decorator.renderRow(member.object) || renderRow;
|
||||
}
|
||||
|
||||
let props = Object.assign({}, this.props, {
|
||||
key: member.path,
|
||||
member: member,
|
||||
columns: this.state.columns,
|
||||
onClick: this.onClickRow.bind(this, member.path)
|
||||
});
|
||||
|
||||
// Render single row.
|
||||
rows.push(renderRow(props));
|
||||
|
||||
// If a child node is expanded render its rows too.
|
||||
if (member.hasChildren && member.open) {
|
||||
let childRows = this.renderRows(member.object, level + 1,
|
||||
member.path);
|
||||
|
||||
// If children needs to be asynchronously fetched first,
|
||||
// set 'loading' property to the parent row. Otherwise
|
||||
// just append children rows to the array of all rows.
|
||||
if (!Array.isArray(childRows)) {
|
||||
let lastIndex = rows.length - 1;
|
||||
props.member.loading = true;
|
||||
rows[lastIndex] = React.cloneElement(rows[lastIndex], props);
|
||||
} else {
|
||||
rows = rows.concat(childRows);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return rows;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let root = this.props.object;
|
||||
let classNames = ["treeTable"];
|
||||
|
||||
// Use custom class name from props.
|
||||
let className = this.props.className;
|
||||
if (className) {
|
||||
classNames.push(...className.split(" "));
|
||||
}
|
||||
|
||||
// Alright, let's render all tree rows. The tree is one big <table>.
|
||||
let rows = this.renderRows(root, 0, "");
|
||||
|
||||
// This happens when the view needs to do initial asynchronous
|
||||
// fetch for the root object. The tree might provide a hook API
|
||||
// for rendering animated spinner (just like for tree nodes).
|
||||
if (!Array.isArray(rows)) {
|
||||
rows = [];
|
||||
}
|
||||
|
||||
let props = Object.assign({}, this.props, {
|
||||
key: member.path,
|
||||
member: member,
|
||||
columns: this.state.columns,
|
||||
onClick: this.onClickRow.bind(this, member.path)
|
||||
columns: this.state.columns
|
||||
});
|
||||
|
||||
// Render single row.
|
||||
rows.push(renderRow(props));
|
||||
|
||||
// If a child node is expanded render its rows too.
|
||||
if (member.hasChildren && member.open) {
|
||||
let childRows = this.renderRows(member.object, level + 1, member.path);
|
||||
|
||||
// If children needs to be asynchronously fetched first,
|
||||
// set 'loading' property to the parent row. Otherwise
|
||||
// just append children rows to the array of all rows.
|
||||
if (!Array.isArray(childRows)) {
|
||||
let lastIndex = rows.length - 1;
|
||||
props.member.loading = true;
|
||||
rows[lastIndex] = React.cloneElement(rows[lastIndex], props);
|
||||
} else {
|
||||
rows = rows.concat(childRows);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return rows;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let root = this.props.object;
|
||||
let classNames = ["treeTable"];
|
||||
|
||||
// Use custom class name from props.
|
||||
let className = this.props.className;
|
||||
if (className) {
|
||||
classNames.push(...className.split(" "));
|
||||
}
|
||||
|
||||
// Alright, let's render all tree rows. The tree is one big <table>.
|
||||
let rows = this.renderRows(root, 0, "");
|
||||
|
||||
// This happens when the view needs to do initial asynchronous
|
||||
// fetch for the root object. The tree might provide a hook API
|
||||
// for rendering animated spinner (just like for tree nodes).
|
||||
if (!Array.isArray(rows)) {
|
||||
rows = [];
|
||||
}
|
||||
|
||||
let props = Object.assign({}, this.props, {
|
||||
columns: this.state.columns
|
||||
});
|
||||
|
||||
return (
|
||||
DOM.table({
|
||||
className: classNames.join(" "),
|
||||
cellPadding: 0,
|
||||
cellSpacing: 0},
|
||||
TreeHeader(props),
|
||||
DOM.tbody({},
|
||||
rows
|
||||
return (
|
||||
DOM.table({
|
||||
className: classNames.join(" "),
|
||||
cellPadding: 0,
|
||||
cellSpacing: 0},
|
||||
TreeHeader(props),
|
||||
DOM.tbody({},
|
||||
rows
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* There should always be at least one column (the one with toggle buttons)
|
||||
* and this function ensures that it's true.
|
||||
*/
|
||||
function ensureDefaultColumn(columns) {
|
||||
if (!columns) {
|
||||
columns = [];
|
||||
}
|
||||
|
||||
let defaultColumn = columns.filter(col => col.id == "default");
|
||||
if (defaultColumn.length) {
|
||||
return columns;
|
||||
}
|
||||
|
||||
// The default column is usually the first one.
|
||||
return [{id: "default"}, ...columns];
|
||||
}
|
||||
|
||||
function isLongString(value) {
|
||||
return typeof value == "string" && value.length > 50;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
module.exports = TreeView;
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* There should always be at least one column (the one with toggle buttons)
|
||||
* and this function ensures that it's true.
|
||||
*/
|
||||
function ensureDefaultColumn(columns) {
|
||||
if (!columns) {
|
||||
columns = [];
|
||||
}
|
||||
|
||||
let defaultColumn = columns.filter(col => col.id == "default");
|
||||
if (defaultColumn.length) {
|
||||
return columns;
|
||||
}
|
||||
|
||||
// The default column is usually the first one.
|
||||
return [{id: "default"}, ...columns];
|
||||
}
|
||||
|
||||
function isLongString(value) {
|
||||
return typeof value == "string" && value.length > 50;
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
module.exports = TreeView;
|
||||
|
|
Загрузка…
Ссылка в новой задаче