tree-row.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. // Make this available to both AMD and CJS environments
  7. define(function (require, exports, module) {
  8. // ReactJS
  9. const React = require("devtools/client/shared/vendor/react");
  10. const ReactDOM = require("devtools/client/shared/vendor/react-dom");
  11. // Tree
  12. const TreeCell = React.createFactory(require("./tree-cell"));
  13. const LabelCell = React.createFactory(require("./label-cell"));
  14. // Shortcuts
  15. const { tr } = React.DOM;
  16. const PropTypes = React.PropTypes;
  17. /**
  18. * This template represents a node in TreeView component. It's rendered
  19. * using <tr> element (the entire tree is one big <table>).
  20. */
  21. let TreeRow = React.createClass({
  22. displayName: "TreeRow",
  23. // See TreeView component for more details about the props and
  24. // the 'member' object.
  25. propTypes: {
  26. member: PropTypes.shape({
  27. object: PropTypes.obSject,
  28. name: PropTypes.sring,
  29. type: PropTypes.string.isRequired,
  30. rowClass: PropTypes.string.isRequired,
  31. level: PropTypes.number.isRequired,
  32. hasChildren: PropTypes.bool,
  33. value: PropTypes.any,
  34. open: PropTypes.bool.isRequired,
  35. path: PropTypes.string.isRequired,
  36. hidden: PropTypes.bool,
  37. }),
  38. decorator: PropTypes.object,
  39. renderCell: PropTypes.object,
  40. renderLabelCell: PropTypes.object,
  41. columns: PropTypes.array.isRequired,
  42. provider: PropTypes.object.isRequired,
  43. onClick: PropTypes.func.isRequired
  44. },
  45. componentWillReceiveProps(nextProps) {
  46. // I don't like accessing the underlying DOM elements directly,
  47. // but this optimization makes the filtering so damn fast!
  48. // The row doesn't have to be re-rendered, all we really need
  49. // to do is toggling a class name.
  50. // The important part is that DOM elements don't need to be
  51. // re-created when they should appear again.
  52. if (nextProps.member.hidden != this.props.member.hidden) {
  53. let row = ReactDOM.findDOMNode(this);
  54. row.classList.toggle("hidden");
  55. }
  56. },
  57. /**
  58. * Optimize row rendering. If props are the same do not render.
  59. * This makes the rendering a lot faster!
  60. */
  61. shouldComponentUpdate: function (nextProps) {
  62. let props = ["name", "open", "value", "loading"];
  63. for (let p in props) {
  64. if (nextProps.member[props[p]] != this.props.member[props[p]]) {
  65. return true;
  66. }
  67. }
  68. return false;
  69. },
  70. getRowClass: function (object) {
  71. let decorator = this.props.decorator;
  72. if (!decorator || !decorator.getRowClass) {
  73. return [];
  74. }
  75. // Decorator can return a simple string or array of strings.
  76. let classNames = decorator.getRowClass(object);
  77. if (!classNames) {
  78. return [];
  79. }
  80. if (typeof classNames == "string") {
  81. classNames = [classNames];
  82. }
  83. return classNames;
  84. },
  85. render: function () {
  86. let member = this.props.member;
  87. let decorator = this.props.decorator;
  88. // Compute class name list for the <tr> element.
  89. let classNames = this.getRowClass(member.object) || [];
  90. classNames.push("treeRow");
  91. classNames.push(member.type + "Row");
  92. if (member.hasChildren) {
  93. classNames.push("hasChildren");
  94. }
  95. if (member.open) {
  96. classNames.push("opened");
  97. }
  98. if (member.loading) {
  99. classNames.push("loading");
  100. }
  101. if (member.hidden) {
  102. classNames.push("hidden");
  103. }
  104. // The label column (with toggle buttons) is usually
  105. // the first one, but there might be cases (like in
  106. // the Memory panel) where the toggling is done
  107. // in the last column.
  108. let cells = [];
  109. // Get components for rendering cells.
  110. let renderCell = this.props.renderCell || RenderCell;
  111. let renderLabelCell = this.props.renderLabelCell || RenderLabelCell;
  112. if (decorator && decorator.renderLabelCell) {
  113. renderLabelCell = decorator.renderLabelCell(member.object) ||
  114. renderLabelCell;
  115. }
  116. // Render a cell for every column.
  117. this.props.columns.forEach(col => {
  118. let props = Object.assign({}, this.props, {
  119. key: col.id,
  120. id: col.id,
  121. value: this.props.provider.getValue(member.object, col.id)
  122. });
  123. if (decorator && decorator.renderCell) {
  124. renderCell = decorator.renderCell(member.object, col.id);
  125. }
  126. let render = (col.id == "default") ? renderLabelCell : renderCell;
  127. // Some cells don't have to be rendered. This happens when some
  128. // other cells span more columns. Note that the label cells contains
  129. // toggle buttons and should be usually there unless we are rendering
  130. // a simple non-expandable table.
  131. if (render) {
  132. cells.push(render(props));
  133. }
  134. });
  135. // Render tree row
  136. return (
  137. tr({
  138. className: classNames.join(" "),
  139. onClick: this.props.onClick},
  140. cells
  141. )
  142. );
  143. }
  144. });
  145. // Helpers
  146. let RenderCell = props => {
  147. return TreeCell(props);
  148. };
  149. let RenderLabelCell = props => {
  150. return LabelCell(props);
  151. };
  152. // Exports from this module
  153. module.exports = TreeRow;
  154. });