Abra el menú plegable de forma predeterminada según la identificación


8

Estoy creando un menú anidado y submenús y todo se ha hecho a partir de ahora. Ahora tengo la necesidad de hacer que este menú colapsable se abra de manera predeterminada en función de la identificación dada.

También puede echar un vistazo al fragmento de código de trabajo completo a continuación,

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>

Requisito:

Tengo un valor de ID almacenado en el const openMenuId = "3.1.1.1.1";componente principal (puede ver esta variable debajo de la loadMenuvariable de matriz).

Aunque hay varios submenús, esta identificación solo pertenecerá a la identificación de los niños del último nivel y, por lo tanto, tendría una casilla de verificación segura para que esa casilla de verificación deba marcarse y los menús hasta el nivel primario necesiten abrirse.

P.ej..,

Como openMenuId es "3.1.1.1.1"y, por lo tanto, está claro que el último nivel secundario del menú threeque Three - one - one - one - onedebe verificarse ya que el openMenuIdvalor de la casilla de verificación tiene una coincidencia aquí. Luego, los menús y submenús respectivos deben expandirse hasta el último nivel.

Esto es solo para el comportamiento predeterminado en la página visitada, por lo que después de que el usuario pueda colapsar y pueda marcar cualquier otra casilla de verificación en cualquier otro menú ... Pero mientras visite la página, tendré una identificación particular que debe abrirse por defecto y también debe marcarse en la casilla de verificación.

Por favor, ayúdenme a lograr el resultado de abrir el menú respectivo comparando la identificación pasada como accesorios y hacer que el menú respectivo esté marcado.

Luchando por mucho tiempo, así que por favor ayúdenme ... Muchas gracias de antemano ...

Respuestas:


3

¡Qué gran pregunta! Realmente disfruté encontrar una solución para este.

Como quería dar un estado inicial tanto al estado del menú como al estado de la casilla de verificación, creo que controlar el estado de ambos en el <Menu>nivel (¡o incluso más!) Es una buena idea. Esto no solo facilita la definición de un estado inicial de un padre, sino que también le otorga más flexibilidad si necesita un comportamiento de menú o casilla de verificación más complicado en el futuro.

Como la estructura de los menús es recursiva, creo que tener una estructura recursiva para el estado del menú funciona bastante bien. Antes de entrar en el código, aquí hay un pequeño GIF que, espero, ayude a explicar cómo se ve el estado:

Manifestación

El video muestra tres columnas del menú: el menú, el estado del menú como JSON y el estado de la casilla de verificación como JSON.  A medida que se hace clic en los menús y casillas de verificación, los estados se actualizan.

Aquí está el fragmento del patio de recreo:

Responder

Recorrido de código a continuación.

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>

Tutorial

Antes de comenzar, debo decir que me he tomado la libertad de cambiar parte del código para usar las funciones modernas de JavaScript, como la destrucción de objetos , la destrucción de matrices , el descanso y los valores predeterminados .

Creando el estado

Entonces. Dado que los ID de los elementos del menú son números delimitados por un punto, podemos aprovechar esto al construir el estado. El estado es esencialmente una estructura similar a un árbol, con cada submenú como hijo de su padre, y el nodo hoja ("último menú" o "menú más profundo") tiene el valor de {}si está expandido o undefinedno. Así es como se construye el estado inicial del menú:

<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)
  };
};

Desarmemos esto poco a poco.

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

Estas son solo funciones de conveniencia que definimos para que podamos cambiar fácilmente el valor que usamos para representar un nodo expandido y no expandido. También hace que el código sea un poco más legible en comparación con el uso literal {}o undefineden el código. Los valores expandidos y no expandidos también podrían ser truey false, lo que importa es que el nodo expandido es verdadero y el nodo no expandido es falso. Más sobre eso más tarde.

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

Estas funciones nos permiten alternar o expandir un menú específico en el estado del menú. El primer parámetro es el estado del menú en sí, el segundo es la ID de cadena de un menú (por ejemplo "3.1.1.1.1"), y el tercero es la función que reemplaza. Piense en esto como la función a la que pasa .map(). La funcionalidad de reemplazo está separada de la iteración real del árbol recursivo para que pueda implementar fácilmente más funcionalidad más adelante; por ejemplo, si desea que algún menú específico no se expanda, puede pasar una función que regrese 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);
};

Esta función es utilizada por las dos anteriores para proporcionar una interfaz más limpia. La ID está dividida aquí por los puntos ( .) que nos dan una matriz de las partes de ID. La siguiente función opera en esta matriz en lugar de la cadena de identificación directamente, porque de esa manera no necesitamos hacer .indexOf('.')travesuras.

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)
  };
};

La replaceNodefunción es la carne del asunto. Es una función recursiva que produce un nuevo árbol a partir del árbol de menús anterior, reemplazando el antiguo nodo de destino con la función de reemplazo provista. Si al árbol le faltan partes intermedias, por ejemplo, cuando el árbol está {}pero queremos reemplazar el nodo 3.1.1.1, crea los nodos principales en el medio. Algo así como mkdir -psi estás familiarizado con el comando.

Entonces ese es el estado del menú. El estado de la casilla de verificación ( checkedMenus) es básicamente un índice, siendo la clave la ID y el valor truesi se marca un elemento. Este estado no es recursivo, ya que no es necesario desmarcarlo o verificarlo de forma recursiva. Si decide que desea mostrar un indicador de que algo debajo de este elemento del menú está marcado, una solución fácil sería cambiar el estado de la casilla de verificación para que sea recursivo como el estado del menú.

Renderizando el árbol

El <Menu>componente transmite los estados a <MenuItemContainer>, que representa el <MenuItem>s.

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>;
}

El <MenuItemContainer>componente no es muy diferente del componente original. Sin <MenuItem>embargo, el componente se ve un poco diferente.

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);
  }
}

Aquí la parte crucial es la siguiente: openMenus={openMenus && openMenus[id]}. En lugar de pasar todo el estado del menú, solo pasamos el árbol de estado que contiene los elementos secundarios del elemento actual. Esto permite que el componente verifique muy fácilmente si debe estar abierto o colapsado, ¡solo verifique si su propia ID se encuentra en el objeto ( openMenus ? !!openMenus[id] : false)!

También cambié el botón de alternar para alternar la casilla de verificación en lugar del estado del menú si es el elemento más profundo del menú; si esto no es lo que está buscando, es bastante rápido volver a cambiar.

También hago uso de !!aquí para coaccionar {}y undefineddel estado del menú a trueo false. Es por eso que dije que solo importa si son verdaderas o falsas. Los reactstrapcomponentes parecían querer explícito trueo en falselugar de verdadero / falso, por eso está ahí.

Y finalmente:

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

Aquí pasamos el estado inicial a <Menu>. También initialOpenMenuIdpodría ser una matriz (o initialCheckedMenuIdspodría ser una sola cadena), pero esto se ajusta a las especificaciones de la pregunta.

Margen de mejora

La solución en este momento pasa por un montón de estado de todo el camino hacia abajo, tales como el onMenuToggley onCheckeddevoluciones de llamada, y el checkedMenusestado que no es recursivo. Estos podrían hacer uso del contexto de React .


1
Gracias por pasar mucho tiempo y dar una solución muy detallada.

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>


Los volcados de código no son respuestas útiles . Di lo que hiciste y cómo funciona.
TJ Crowder

@TJCrowder, tampoco puedo cerrar estos elementos de menú abiertos ... Y como dijiste, esta respuesta no tiene mucha información. Como se solicitó anteriormente, ¿podrías ayudarme a lograr el resultado?

Esta solución no resuelve mis problemas por completo, porque abre el último nivel en el menú tres, pero no puedo hacer que vuelva a colapsar y tampoco puedo hacer ninguna otra casilla de verificación para comprobarlo ... Pero agradezco su ayuda pero desafortunadamente no no funciona como se esperaba ..

0

Suponiendo que necesita abrir cierto menú solo al comienzo, puede configurar el MenuItemcomponente para que espere una propiedad booleana defaultOpeny usar eso para establecer la inicial isOpen.

Entonces todo lo que tenemos que hacer es establecer esta propiedad en menuItemscarga.

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}));
  }
}

Si necesita poder abrir un elemento de menú después del renderizado inicial, debe crear MenuItemun componente controlado.

es decir, levante el isOpenestado a padre y páselo al MenuItemcomponente como accesorio junto con la función de devolución de llamada que invocará al hacer clic pasando su id como argumento. La función de devolución de llamada en padre alternará la isOpenpropiedad del elemento con la identificación dada en su estado.


-1

simplemente agregue una clase .actveo cualquier otra que desee y modifíquela de acuerdo con su requerimiento y luego agregue scriptsi está usando normal jsentonces document.querySelector("youElementClassOrId").classList.toggle("idOrClassYouWantToToggle"). Me jope esto funcionará


No, esto no será lo que necesito ... Además, alternar y funcionar bien. Necesito comparar los valores y hacer que la casilla de verificación esté marcada y abrir los menús hasta el nivel principal desde el marcado ...
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.