Bug 1328500 - Support input field in TreeView r?honza draft
authorRicky Chien <rchien@mozilla.com>
Wed, 04 Jan 2017 13:32:00 +0800
changeset 456630 dfe21ed4ccd63744b6351d03ea949ea63fd989f5
parent 455536 57ac9f63fc6953f4efeb0cc84a60192d3721251f
child 541295 384679d85a621316c6590f6c4e4451003ed89fad
push id40574
push userbmo:rchien@mozilla.com
push dateFri, 06 Jan 2017 02:12:05 +0000
reviewershonza
bugs1328500
milestone53.0a1
Bug 1328500 - Support input field in TreeView r?honza MozReview-Commit-ID: G80lLUK8CPT
devtools/client/shared/components/tree/tree-cell.js
devtools/client/shared/components/tree/tree-header.js
devtools/client/shared/components/tree/tree-view.css
devtools/client/themes/netmonitor.css
--- a/devtools/client/shared/components/tree/tree-cell.js
+++ b/devtools/client/shared/components/tree/tree-cell.js
@@ -5,42 +5,49 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // 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 { input, span, td } = 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>).
    */
   let TreeCell = React.createClass({
     displayName: "TreeCell",
 
     // 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
+      renderValue: PropTypes.func.isRequired,
+      enableInput: PropTypes.bool,
+    },
+
+    getInitialState: function () {
+      return {
+        inputEnabled: false,
+      };
     },
 
     /**
      * Optimize cell rendering. Rerender cell content only if
      * the value or expanded state changes.
      */
-    shouldComponentUpdate: function (nextProps) {
+    shouldComponentUpdate: function (nextProps, nextState) {
       return (this.props.value != nextProps.value) ||
-        (this.props.member.open != nextProps.member.open);
+        (this.state !== nextState);
     },
 
     getCellClass: function (object, id) {
       let decorator = this.props.decorator;
       if (!decorator || !decorator.getCellClass) {
         return [];
       }
 
@@ -52,43 +59,70 @@ define(function (require, exports, modul
 
       if (typeof classNames == "string") {
         classNames = [classNames];
       }
 
       return classNames;
     },
 
+    updateInputEnabled: function (evt) {
+      this.setState(Object.assign({}, this.state, {
+        inputEnabled: evt.target.nodeName !== "input",
+      }));
+    },
+
     render: function () {
-      let member = this.props.member;
+      let {
+        member,
+        id,
+        value,
+        decorator,
+        renderValue,
+        enableInput,
+      } = this.props;
       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;
+      renderValue = renderValue || defaultRenderValue;
       if (decorator && decorator.renderValue) {
         renderValue = decorator.renderValue(member.object, id) || renderValue;
       }
 
       let props = Object.assign({}, this.props, {
         object: value,
       });
 
+      let cellElement;
+      if (enableInput && this.state.inputEnabled && type !== "object") {
+        classNames.push("inputEnabled");
+        cellElement = input({
+          autoFocus: true,
+          onBlur: this.updateInputEnabled,
+          readOnly: true,
+          value,
+        });
+      } else {
+        cellElement = span({
+          onClick: (type !== "object") ? this.updateInputEnabled : null,
+        },
+          renderValue(props)
+        );
+      }
+
       // Render me!
       return (
         td({ className: classNames.join(" ") },
-          span({}, renderValue(props))
+          cellElement
         )
       );
     }
   });
 
   // Default value rendering.
   let defaultRenderValue = props => {
     return (
--- a/devtools/client/shared/components/tree/tree-header.js
+++ b/devtools/client/shared/components/tree/tree-header.js
@@ -75,19 +75,19 @@ define(function (require, exports, modul
           classNames.push("treeHeaderCell");
         }
 
         cells.push(
           td({
             className: classNames.join(" "),
             style: cellStyle,
             key: col.id},
-            div({ className: visible ? "treeHeaderCellBox" : "" },
-              visible ? col.title : ""
-            )
+            visible ? div({ className: "treeHeaderCellBox"},
+              col.title
+            ) : null,
           )
         );
       });
 
       return (
         thead({}, tr({ className: visible ? "treeHeaderRow" : "" },
           cells
         ))
--- a/devtools/client/shared/components/tree/tree-view.css
+++ b/devtools/client/shared/components/tree/tree-view.css
@@ -29,16 +29,35 @@
 }
 
 .treeTable .treeValueCell {
   padding: 2px 0;
   padding-inline-start: 5px;
   overflow: hidden;
 }
 
+.treeTable .treeValueCell.inputEnabled {
+  padding-top: 0;
+  padding-bottom: 0;
+}
+
+.treeTable .treeValueCell input {
+  width: 100%;
+  background: none;
+  border: none;
+  color: inherit;
+  margin-inline-end: 2px;
+}
+
+.treeTable .treeValueCell input:focus {
+  outline: none;
+  box-shadow: var(--theme-focus-box-shadow-textbox);
+  transition: all 0.2s ease-in-out;
+}
+
 .treeTable .treeLabel {
   cursor: default;
   overflow: hidden;
   padding-inline-start: 4px;
   white-space: nowrap;
   unicode-bidi: -moz-plaintext;
 }
 
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -1097,26 +1097,26 @@
   content: "";
 }
 
 /* Layout additional warning icon in tree value cell  */
 .security-info-value {
   display: flex;
 }
 
-.security-info-value .textbox-input {
+.treeTable .textbox-input {
   text-overflow: ellipsis;
   border: none;
   background: none;
   color: inherit;
   width: 100%;
   margin-inline-end: 2px;
 }
 
-.security-info-value .textbox-input:focus {
+.treeTable .textbox-input:focus {
   outline: 0;
   box-shadow: var(--theme-focus-box-shadow-textbox);
 }
 
 .treeTable .treeLabel {
   font-weight: 600;
 }