Öffnen Sie das ausklappbare Menü standardmäßig basierend auf der ID


8

Ich erstelle ein verschachteltes Menü und Untermenüs und alles wurde ab sofort erledigt. Ich muss dieses zusammenklappbare Menü jetzt standardmäßig auf der Grundlage der angegebenen ID öffnen.

Sie können sich auch das vollständige Arbeitscode-Snippet unten ansehen.

const loadMenu = () => Promise.resolve([{id:"1",name:"One",children:[{id:"1.1",name:"One - one",children:[{id:"1.1.1",name:"One - one - one"},{id:"1.1.2",name:"One - one - two"},{id:"1.1.3",name:"One - one - three"}]}]},{id:"2",name:"Two",children:[{id:"2.1",name:"Two - one"}]},{id:"3",name:"Three",children:[{id:"3.1",name:"Three - one",children:[{id:"3.1.1",name:"Three - one - one",children:[{id:"3.1.1.1",name:"Three - one - one - one",children:[{id:"3.1.1.1.1",name:"Three - one - one - one - one"}]}]}]}]},{id:"4",name:"Four"},{id:"5",name:"Five",children:[{id:"5.1",name:"Five - one"},{id:"5.2",name:"Five - two"},{id:"5.3",name:"Five - three"},{id:"5.4",name:"Five - four"}]},{id:"6",name:"Six"}]);

const openMenuId = "3.1.1.1";

const {Component, Fragment} = React;
const {Button, Collapse, Input} = Reactstrap;

class Menu extends Component {
  constructor(props) {
    super(props);
    this.state = {menuItems: []};
  }

  render() {
    return <MenuItemContainer menuItems={this.state.menuItems} />;
  }

  componentDidMount() {
    loadMenu().then(menuItems => this.setState({menuItems}));
  }
}

function MenuItemContainer(props) {
  if (!props.menuItems.length) return null;
  
  const renderMenuItem = menuItem =>
    <li key={menuItem.id}><MenuItem {...menuItem} /></li>;
    
  return <ul>{props.menuItems.map(renderMenuItem)}</ul>;
}
MenuItemContainer.defaultProps = {menuItems: []};

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.state = {isOpen: false};
    this.toggle = this.toggle.bind(this);
  }

  render() {
    let isLastChild = this.props.children ? false : true;
    return (
      <Fragment>
        <Button onClick={this.toggle}>{this.props.name}</Button>
        <Fragment>
          {isLastChild ? <Input type="checkbox" value={this.props.id} /> : ''}
        </Fragment>
        <Collapse isOpen={this.state.isOpen}>
          <MenuItemContainer menuItems={this.props.children} />
        </Collapse>
      </Fragment>
    );
  }

  toggle() {
    this.setState(({isOpen}) => ({isOpen: !isOpen}));
  }
}

ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>

<div id="root"></div>

Anforderung:

Ich habe einen ID-Wert const openMenuId = "3.1.1.1.1";in der übergeordneten Komponente gespeichert (Sie können diese Variable unter der loadMenuArray-Variablen sehen).

Obwohl es mehrere Untermenüs gibt, gehört diese ID nur zur untergeordneten ID der letzten Ebene und verfügt daher mit Sicherheit über ein Kontrollkästchen, sodass das Kontrollkästchen aktiviert und die Menüs bis zur übergeordneten Ebene geöffnet werden müssen.

Z.B..,

Wie die openMenuId ist "3.1.1.1.1" und daher klar ist, dass die letzte Menüebene, threedie Three - one - one - one - oneals openMenuIdWert für und Kontrollkästchen aktiviert werden muss, hier übereinstimmt. Dann müssen die jeweiligen Menüs und Untermenüs bis zur letzten Ebene erweitert werden.

Dies gilt nur für das Standardverhalten auf der besuchten Seite, damit der Benutzer nach dem Zusammenbruch zurückkehren und alle anderen Kontrollkästchen in anderen Menüs aktivieren kann. Beim Besuch der Seite habe ich jedoch eine bestimmte ID, die standardmäßig geöffnet werden muss muss im Kontrollkästchen aktiviert sein ..

Bitte helfen Sie mir, das Ergebnis des Öffnens des jeweiligen Menüs zu erzielen, indem Sie die als Requisiten übermittelte ID vergleichen und das entsprechende Menü überprüfen.

Lange kämpfen, also bitte helfen Sie mir .. Ein großes Dankeschön im Voraus ..

Antworten:


3

Was für eine tolle Frage! Ich habe es wirklich genossen, eine Lösung für dieses Problem zu finden.

Da Sie sowohl dem Menüstatus als auch dem Kontrollkästchenstatus einen Anfangsstatus geben wollten, halte ich es für <Menu>eine gute Idee , den Status beider auf der Ebene (oder sogar höher!) Zu steuern . Dies erleichtert nicht nur das Definieren eines Anfangszustands eines übergeordneten Elements, sondern bietet Ihnen auch mehr Flexibilität, wenn Sie in Zukunft ein komplizierteres Menü- oder Kontrollkästchenverhalten benötigen.

Da die Struktur der Menüs rekursiv ist, denke ich, dass eine rekursive Struktur für den Menüstatus ziemlich gut funktioniert. Bevor ich in den Code gehe, hier ein kurzes GIF, das hoffentlich erklärt, wie der Zustand aussieht:

Demo

Das Video zeigt drei Spalten des Menüs: das Menü, den Menüstatus als JSON und das Kontrollkästchen als JSON.  Wenn Menüs und Kontrollkästchen angeklickt werden, werden die Status aktualisiert.

Hier ist der Spielplatzausschnitt:

Antworten

Code-exemplarische Vorgehensweise unten.

const loadMenu = () =>
  Promise.resolve([
    {
      id: "1",
      name: "One",
      children: [
        {
          id: "1.1",
          name: "One - one",
          children: [
            { id: "1.1.1", name: "One - one - one" },
            { id: "1.1.2", name: "One - one - two" },
            { id: "1.1.3", name: "One - one - three" }
          ]
        }
      ]
    },
    { id: "2", name: "Two", children: [{ id: "2.1", name: "Two - one" }] },
    {
      id: "3",
      name: "Three",
      children: [
        {
          id: "3.1",
          name: "Three - one",
          children: [
            {
              id: "3.1.1",
              name: "Three - one - one",
              children: [
                {
                  id: "3.1.1.1",
                  name: "Three - one - one - one",
                  children: [
                    { id: "3.1.1.1.1", name: "Three - one - one - one - one" }
                  ]
                }
              ]
            }
          ]
        }
      ]
    },
    { id: "4", name: "Four" },
    {
      id: "5",
      name: "Five",
      children: [
        { id: "5.1", name: "Five - one" },
        { id: "5.2", name: "Five - two" },
        { id: "5.3", name: "Five - three" },
        { id: "5.4", name: "Five - four" }
      ]
    },
    { id: "6", name: "Six" }
  ]);

const { Component, Fragment } = React;
const { Button, Collapse, Input } = Reactstrap;

const replaceNode = (replacer, node, idPath, i) => {
  if (i <= idPath.length && !node) {
    // Not at target node yet, create nodes in between
    node = {};
  }
  if (i > idPath.length) {
    // Reached target node
    return replacer(node);
  }

  // Construct ID that matches this depth - depth meaning
  // the amount of dots in between the ID
  const id = idPath.slice(0, i).join(".");
  return {
    ...node,
    // Recurse
    [id]: replaceNode(replacer, node[id], idPath, i + 1)
  };
};

const replaceNodeById = (node, id, visitor) => {
  // Pass array of the id's parts instead of working on the string
  // directly - easy way to handle multi-number ID parts e.g. 3.1.15.32
  return replaceNode(visitor, node, id.split("."), 1);
};

const expandedNode = () => ({});
const unexpandedNode = () => undefined;

const toggleNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode =>
    oldNode ? unexpandedNode() : expandedNode()
  );
const expandNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode => expandedNode());

class Menu extends Component {
  constructor(props) {
    super(props);
    this.state = {
      menuItems: [],
      openMenus: {},
      checkedMenus: {}
    };
    this.handleMenuToggle = this.handleMenuToggle.bind(this);
    this.handleChecked = this.handleChecked.bind(this);
  }

  render() {
    const { menuItems, openMenus, checkedMenus } = this.state;

    return (
      <MenuItemContainer
        openMenus={openMenus}
        menuItems={menuItems}
        onMenuToggle={this.handleMenuToggle}
        checkedMenus={checkedMenus}
        onChecked={this.handleChecked}
      />
    );
  }

  componentDidMount() {
    const { initialOpenMenuId, initialCheckedMenuIds } = this.props;

    loadMenu().then(menuItems => {
      const initialMenuState = {};
      this.setState({
        menuItems,
        openMenus: expandNodeById(initialMenuState, initialOpenMenuId),
        checkedMenus: initialCheckedMenuIds.reduce(
          (acc, val) => ({ ...acc, [val]: true }),
          {}
        )
      });
    });
  }

  handleMenuToggle(toggledId) {
    this.setState(({ openMenus }) => ({
      openMenus: toggleNodeById(openMenus, toggledId)
    }));
  }

  handleChecked(toggledId) {
    this.setState(({ checkedMenus }) => ({
      checkedMenus: {
        ...checkedMenus,
        [toggledId]: checkedMenus[toggledId] ? unexpandedNode() : expandedNode()
      }
    }));
  }
}

function MenuItemContainer({
  openMenus,
  onMenuToggle,
  checkedMenus,
  onChecked,
  menuItems = []
}) {
  if (!menuItems.length) return null;

  const renderMenuItem = menuItem => (
    <li key={menuItem.id}>
      <MenuItem
        openMenus={openMenus}
        onMenuToggle={onMenuToggle}
        checkedMenus={checkedMenus}
        onChecked={onChecked}
        {...menuItem}
      />
    </li>
  );

  return <ul>{menuItems.map(renderMenuItem)}</ul>;
}

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.handleToggle = this.handleToggle.bind(this);
    this.handleChecked = this.handleChecked.bind(this);
  }

  render() {
    const {
      children,
      name,
      id,
      openMenus,
      onMenuToggle,
      checkedMenus,
      onChecked
    } = this.props;

    const isLastChild = !children;
    return (
      <Fragment>
        <Button onClick={isLastChild ? this.handleChecked : this.handleToggle}>
          {name}
        </Button>
        {isLastChild && (
          <Input
            addon
            type="checkbox"
            onChange={this.handleChecked}
            checked={!!checkedMenus[id]}
            value={id}
          />
        )}

        <Collapse isOpen={openMenus ? !!openMenus[id] : false}>
          <MenuItemContainer
            menuItems={children}
            // Pass down child menus' state
            openMenus={openMenus && openMenus[id]}
            onMenuToggle={onMenuToggle}
            checkedMenus={checkedMenus}
            onChecked={onChecked}
          />
        </Collapse>
      </Fragment>
    );
  }

  handleToggle() {
    this.props.onMenuToggle(this.props.id);
  }

  handleChecked() {
    this.props.onChecked(this.props.id);
  }
}

ReactDOM.render(
  <Menu initialOpenMenuId="3.1.1.1" initialCheckedMenuIds={["3.1.1.1.1"]} />,
  document.getElementById("root")
);
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>

<div id="root"></div>

Exemplarische Vorgehensweise

Bevor ich anfange, muss ich sagen, dass ich mir erlaubt habe, einen Teil des Codes zu ändern, um moderne JavaScript-Funktionen wie Objektzerstörung , Array-Destrukturierung , Ruhe- und Standardwerte zu verwenden .

Zustand schaffen

Damit. Da die IDs der Menüelemente durch einen Punkt begrenzte Zahlen sind, können wir dies bei der Erstellung des Status nutzen. Der Status ist im Wesentlichen eine baumartige Struktur, wobei jedes Untermenü ein untergeordnetes Element seines übergeordneten Menüs ist und der Blattknoten ("letztes Menü" oder "tiefstes Menü") den Wert hat, {}ob er erweitert ist oder undefinednicht. So wird der Ausgangszustand des Menüs aufgebaut:

<Menu initialOpenMenuId="3.1.1.1" initialCheckedMenuIds={["3.1.1.1.1"]} />

// ...

loadMenu().then(menuItems => {
  const initialMenuState = {};
  this.setState({
    menuItems,
    openMenus: expandNodeById(initialMenuState, initialOpenMenuId),
    checkedMenus: initialCheckedMenuIds.reduce(
      (acc, val) => ({ ...acc, [val]: true }),
      {}
    )
  });
});

// ...

const expandedNode = () => ({});
const unexpandedNode = () => undefined;

const toggleNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode =>
    oldNode ? unexpandedNode() : expandedNode()
  );
const expandNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode => expandedNode());

const replaceNodeById = (node, id, visitor) => {
  // Pass array of the id's parts instead of working on the string
  // directly - easy way to handle multi-number ID parts e.g. 3.1.15.32
  return replaceNode(visitor, node, id.split("."), 1);
};

const replaceNode = (replacer, node, idPath, i) => {
  if (i <= idPath.length && !node) {
    // Not at target node yet, create nodes in between
    node = {};
  }
  if (i > idPath.length) {
    // Reached target node
    return replacer(node);
  }

  // Construct ID that matches this depth - depth meaning
  // the amount of dots in between the ID
  const id = idPath.slice(0, i).join(".");
  return {
    ...node,
    // Recurse
    [id]: replaceNode(replacer, node[id], idPath, i + 1)
  };
};

Nehmen wir das Stück für Stück auseinander.

const expandedNode = () => ({});
const unexpandedNode = () => undefined;

Dies sind nur Komfortfunktionen, die wir definieren, damit wir den Wert, den wir zur Darstellung eines erweiterten und nicht erweiterten Knotens verwenden, leicht ändern können. Es macht den Code auch ein bisschen lesbarer als nur die Verwendung von Literal {}oder undefinedim Code. Die erweiterten und nicht erweiterten Werte könnten genauso gut sein, trueund es kommt darauf an false, dass der erweiterte Knoten wahr und der nicht erweiterte Knoten falsch ist. Dazu später mehr.

const toggleNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode =>
    oldNode ? unexpandedNode() : expandedNode()
  );
const expandNodeById = (node, id) =>
  replaceNodeById(node, id, oldNode => expandedNode());

Mit diesen Funktionen können wir ein bestimmtes Menü im Menüstatus umschalten oder erweitern. Der erste Parameter ist der Menüstatus selbst, der zweite ist die Zeichenfolgen-ID eines Menüs (z. B. "3.1.1.1.1") und der dritte ist die Funktion, die das Ersetzen übernimmt. Stellen Sie sich das wie die Funktion vor, an die Sie übergeben .map(). Die Ersetzungsfunktionalität ist von der eigentlichen rekursiven Baumiteration getrennt, sodass Sie später problemlos weitere Funktionen implementieren können. Wenn Sie beispielsweise möchten, dass ein bestimmtes Menü nicht erweitert wird, können Sie einfach eine zurückgegebene Funktion übergeben unexpandedNode().

const replaceNodeById = (node, id, visitor) => {
  // Pass array of the id's parts instead of working on the string
  // directly - easy way to handle multi-number ID parts e.g. 3.1.15.32
  return replaceNode(visitor, node, id.split("."), 1);
};

Diese Funktion wird von den beiden vorherigen verwendet, um eine sauberere Oberfläche bereitzustellen. Die ID wird hier durch die Punkte ( .) geteilt, wodurch wir ein Array der ID-Teile erhalten. Die nächste Funktion arbeitet direkt mit diesem Array anstelle der ID-Zeichenfolge, da wir auf diese Weise keine .indexOf('.')Shenanigans ausführen müssen.

const replaceNode = (replacer, node, idPath, i) => {
  if (i <= idPath.length && !node) {
    // Not at target node yet, create nodes in between
    node = {};
  }
  if (i > idPath.length) {
    // Reached target node
    return replacer(node);
  }

  // Construct ID that matches this depth - depth meaning
  // the amount of dots in between the ID
  const id = idPath.slice(0, i).join(".");
  return {
    ...node,
    // Recurse
    [id]: replaceNode(replacer, node[id], idPath, i + 1)
  };
};

Die replaceNodeFunktion ist das Fleisch der Sache. Es ist eine rekursive Funktion, die aus dem alten Menübaum einen neuen Baum erzeugt und den alten Zielknoten durch die bereitgestellte Ersetzungsfunktion ersetzt. Wenn dem Baum Teile dazwischen fehlen, z. B. wenn der Baum ist, {}aber wir den Knoten ersetzen möchten 3.1.1.1, werden die übergeordneten Knoten dazwischen erstellt. Ein bisschen wie, mkdir -pwenn Sie mit dem Befehl vertraut sind.

Das ist also der Menüzustand. Das Kontrollkästchen state ( checkedMenus) ist im Grunde nur ein Index, wobei der Schlüssel die ID und der Wert ist, truewenn ein Element aktiviert ist. Dieser Status ist nicht rekursiv, da sie nicht deaktiviert oder rekursiv überprüft werden müssen. Wenn Sie entscheiden, dass Sie einen Indikator anzeigen möchten, dass etwas unter diesem Menüpunkt aktiviert ist, besteht eine einfache Lösung darin, den Status des Kontrollkästchens so zu ändern, dass er wie der Menüstatus rekursiv ist.

Den Baum rendern

Die <Menu>Komponente gibt die Zustände an weiter <MenuItemContainer>, wodurch das <MenuItem>s gerendert wird .

function MenuItemContainer({
  openMenus,
  onMenuToggle,
  checkedMenus,
  onChecked,
  menuItems = []
}) {
  if (!menuItems.length) return null;

  const renderMenuItem = menuItem => (
    <li key={menuItem.id}>
      <MenuItem
        openMenus={openMenus}
        onMenuToggle={onMenuToggle}
        checkedMenus={checkedMenus}
        onChecked={onChecked}
        {...menuItem}
      />
    </li>
  );

  return <ul>{menuItems.map(renderMenuItem)}</ul>;
}

Die <MenuItemContainer>Komponente unterscheidet sich nicht sehr von der Originalkomponente. Die <MenuItem>Komponente sieht allerdings etwas anders aus.

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.handleToggle = this.handleToggle.bind(this);
    this.handleChecked = this.handleChecked.bind(this);
  }

  render() {
    const {
      children,
      name,
      id,
      openMenus,
      onMenuToggle,
      checkedMenus,
      onChecked
    } = this.props;

    const isLastChild = !children;
    return (
      <Fragment>
        <Button onClick={isLastChild ? this.handleChecked : this.handleToggle}>
          {name}
        </Button>
        {isLastChild && (
          <Input
            addon
            type="checkbox"
            onChange={this.handleChecked}
            checked={!!checkedMenus[id]}
            value={id}
          />
        )}

        <Collapse isOpen={openMenus ? !!openMenus[id] : false}>
          <MenuItemContainer
            menuItems={children}
            // Pass down child menus' state
            openMenus={openMenus && openMenus[id]}
            onMenuToggle={onMenuToggle}
            checkedMenus={checkedMenus}
            onChecked={onChecked}
          />
        </Collapse>
      </Fragment>
    );
  }

  handleToggle() {
    this.props.onMenuToggle(this.props.id);
  }

  handleChecked() {
    this.props.onChecked(this.props.id);
  }
}

Hier ist der entscheidende Teil : openMenus={openMenus && openMenus[id]}. Anstatt den gesamten Menüstatus weiterzugeben, geben wir nur den Statusbaum weiter, der die untergeordneten Elemente des aktuellen Elements enthält. Auf diese Weise kann die Komponente sehr einfach überprüfen, ob sie geöffnet oder reduziert sein sollte - überprüfen Sie einfach, ob ihre eigene ID vom Objekt gefunden wird ( openMenus ? !!openMenus[id] : false)!

Ich habe auch die Umschalttaste geändert, um das Kontrollkästchen anstelle des Menüstatus umzuschalten, wenn es das tiefste Element im Menü ist. Wenn dies nicht das ist, wonach Sie suchen, ist es ziemlich schnell, zurück zu wechseln.

Ich benutze !!hier auch, um {}und undefinedaus dem Menüzustand in trueoder zu zwingen false. Deshalb habe ich gesagt, es ist nur wichtig, ob sie wahr oder falsch sind. Die reactstrapKomponenten schienen explizit trueoder zu wollenfalse statt wahr / falsch , deshalb ist es da.

Und schlussendlich:

ReactDOM.render(
  <Menu initialOpenMenuId="3.1.1.1" initialCheckedMenuIds={["3.1.1.1.1"]} />,
  document.getElementById("root")
);

Hier übergeben wir den Ausgangszustand an <Menu>. Das initialOpenMenuIdkönnte auch ein Array sein (oder initialCheckedMenuIdseine einzelne Zeichenfolge), aber dies entspricht der Spezifikation der Frage.

Raum für Verbesserung

Die Lösung rechts geht jetzt viel Zustand nach unten den ganzen Weg hinunter, wie die onMenuToggleund onCheckedRückrufe, und der checkedMenusZustand , der nicht rekursiv ist. Diese könnten den Kontext von React nutzen .


1
Vielen Dank, dass Sie viel Zeit

0

const loadMenu = () => Promise.resolve([{id:"1",name:"One",children:[{id:"1.1",name:"One - one",children:[{id:"1.1.1",name:"One - one - one"},{id:"1.1.2",name:"One - one - two"},{id:"1.1.3",name:"One - one - three"}]}]},{id:"2",name:"Two",children:[{id:"2.1",name:"Two - one"}]},{id:"3",name:"Three",children:[{id:"3.1",name:"Three - one",children:[{id:"3.1.1",name:"Three - one - one",children:[{id:"3.1.1.1",name:"Three - one - one - one",children:[{id:"3.1.1.1.1",name:"Three - one - one - one - one"}]}]}]}]},{id:"4",name:"Four"},{id:"5",name:"Five",children:[{id:"5.1",name:"Five - one"},{id:"5.2",name:"Five - two"},{id:"5.3",name:"Five - three"},{id:"5.4",name:"Five - four"}]},{id:"6",name:"Six"}]);

const openMenuId = "3.1.1.1.1";

const {Component, Fragment} = React;
const {Button, Collapse, Input} = Reactstrap;

class Menu extends Component {
  constructor(props) {
    super(props);
    this.state = {menuItems: []};
  }

  render() {
    return <MenuItemContainer menuItems={this.state.menuItems} />;
  }

  componentDidMount() {
    loadMenu().then(menuItems => this.setState({menuItems}));
  }
}

function MenuItemContainer(props) {
  if (!props.menuItems.length) return null;
  
  const renderMenuItem = menuItem =>
    <li key={menuItem.id}><MenuItem {...menuItem} /></li>;
    
  return <ul>{props.menuItems.map(renderMenuItem)}</ul>;
}
MenuItemContainer.defaultProps = {menuItems: []};

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.state = {isOpen: false};
    this.toggle = this.toggle.bind(this);
  }

  render() {
    let isLastChild = this.props.children ? false : true;
    let {isOpen} = this.state;
    if(openMenuId.startsWith(this.props.id)){isOpen = true;}
    return (
      <Fragment>
        <Button onClick={this.toggle}>{this.props.name}</Button>
        <Fragment>
          {isLastChild ? <Input type="checkbox" checked={openMenuId === this.props.id} value={this.props.id} /> : ''}
        </Fragment>
        <Collapse isOpen={isOpen}>
          <MenuItemContainer menuItems={this.props.children} />
        </Collapse>
      </Fragment>
    );
  }

  toggle() {
    this.setState(({isOpen}) => ({isOpen: !isOpen}));
  }
}

ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>

<div id="root"></div>


Code-Dumps sind keine nützlichen Antworten. Sagen Sie, was Sie getan haben und wie es funktioniert.
TJ Crowder

@TJCrowder, ich kann diese geöffneten Menüpunkte auch nicht schließen. Und wie Sie sagten, hat diese Antwort nicht viele Informationen. Wie zuvor angefordert, können Sie mir bitte helfen, das Ergebnis zu erzielen?

Diese Lösung löst meine Probleme nicht vollständig, da sie die letzte Ebene in Menü drei öffnet, aber ich kann sie nicht wieder zusammenbrechen lassen und auch keine anderen Kontrollkästchen aktivieren, um sie zu aktivieren. Aber ich danke Ihnen für Ihre Hilfe, aber leider nicht funktioniert nicht wie erwartet vollwertig ..

0

Angenommen, Sie müssen ein bestimmtes Menü nur zu Beginn öffnen, können Sie die MenuItemKomponente so einstellen , dass sie eine boolesche Eigenschaft erwartet, defaultOpenund diese zum Festlegen der Initiale verwenden isOpen.

Dann müssen wir diese Eigenschaft nur noch beim menuItemsLaden festlegen .

import React from 'react'
import { Button, Collapse, Input } from 'reactstrap';
import 'bootstrap/dist/css/bootstrap.min.css';

const loadMenu = () => Promise.resolve([{id:"1",name:"One",children:[{id:"1.1",name:"One - one",children:[{id:"1.1.1",name:"One - one - one"},{id:"1.1.2",name:"One - one - two"},{id:"1.1.3",name:"One - one - three"}]}]},{id:"2",name:"Two",children:[{id:"2.1",name:"Two - one"}]},{id:"3",name:"Three",children:[{id:"3.1",name:"Three - one",children:[{id:"3.1.1",name:"Three - one - one",children:[{id:"3.1.1.1",name:"Three - one - one - one",children:[{id:"3.1.1.1.1",name:"Three - one - one - one - one"}]}]}]}]},{id:"4",name:"Four"},{id:"5",name:"Five",children:[{id:"5.1",name:"Five - one"},{id:"5.2",name:"Five - two"},{id:"5.3",name:"Five - three"},{id:"5.4",name:"Five - four"}]},{id:"6",name:"Six"}]);

const openMenuId = "3.1.1.1";

const {Component, Fragment} = React;

function setDefaultOpen(menuItems, openMenuId) {
  if(!menuItems) return
  const openMenuItem = menuItems.find(item => openMenuId.startsWith(item.id))
  if(!openMenuItem) return
  openMenuItem.defaultOpen = true
  setDefaultOpen(openMenuItem.children, openMenuId)
}

export default class Menu extends Component {
  constructor(props) {
    super(props);
    this.state = {menuItems: []};
  }

  render() {
    return <MenuItemContainer menuItems={this.state.menuItems} />;
  }

  componentDidMount() {
    loadMenu().then(menuItems => {
      setDefaultOpen(menuItems, openMenuId)
      this.setState({menuItems})
    });
  }
}

function MenuItemContainer(props) {
  if (!props.menuItems.length) return null;

  const renderMenuItem = menuItem =>
    <li key={menuItem.id}><MenuItem {...menuItem} /></li>;

  return <ul>{props.menuItems.map(renderMenuItem)}</ul>;
}
MenuItemContainer.defaultProps = {menuItems: []};

class MenuItem extends Component {
  constructor(props) {
    super(props);
    this.state = {isOpen: props.defaultOpen};
    this.toggle = this.toggle.bind(this);
  }

  render() {
    let isLastChild = this.props.children ? false : true;
    return (
      <Fragment>
        <Button onClick={this.toggle}>{this.props.name}</Button>
        <Fragment>
          {isLastChild ? <Input type="checkbox" value={this.props.id} /> : ''}
        </Fragment>
        <Collapse isOpen={this.state.isOpen}>
          <MenuItemContainer menuItems={this.props.children} />
        </Collapse>
      </Fragment>
    );
  }

  toggle() {
    this.setState(({isOpen}) => ({isOpen: !isOpen}));
  }
}

Wenn Sie nach dem ersten Rendern einen Menüeintrag öffnen können müssen, müssen Sie MenuItemeine kontrollierte Komponente erstellen.

Das heißt , Sie rufen den isOpenStatus an das übergeordnete Element auf und übergeben ihn MenuItemals Requisite zusammen mit der Rückruffunktion an die Komponente, die beim Klicken aufgerufen wird, wobei die ID als Argument übergeben wird. Die Rückruffunktion im übergeordneten isOpenElement schaltet die Eigenschaft des Elements mit der angegebenen ID in seinem Status um.


-1

nur eine Klasse hinzufügen , .actveoder jede andere Sie wollen und Stil es entsprechend Ihrem requirment und fügen Sie dann , scriptwenn Sie normale verwenden jsdann document.querySelector("youElementClassOrId").classList.toggle("idOrClassYouWantToToggle"). Ich denke, das wird funktionieren


Nein, das wird nicht das sein, was ich brauche. Außerdem muss ich umschalten und gut funktionieren. Ich muss mit Werten vergleichen und das Kontrollkästchen aktivieren und die Menüs bis zur übergeordneten Ebene von der aktivierten Ebene aus öffnen.
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.