Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Plain JS Cascading selects of unknown depth

Wanted to make a generic cascading dropdown but am weak in recursion

The code is supposed to end up with

  • One select for items - clothes or gadgets - when a choice is made
    • One select with either Levis/Gucci or LG/Apple - when a choice is made
      • One select with either Levis jeans or jackets or Gucci shoes or dresses - when a choice is made
        • One select with Levis jeans sizes OR levis jacket sizes OR
        • One select with Gucci shoe sizes OR Gucci dress sizes

OR

      • One select with either LG TVs or phones or Apple Macbooks or iPhones - when a choice is made
        • One select with LG TV sizes OR LG Phone sizes OR
        • One select with Apple Macbook sizes OR Apple iPhone sizes

I lost my train of thoughts when I got to actually recurse - or perhaps filtering can be used?

I assume one could make a set of paths and then just show/hide depending on path

const selObject = {
  "-- Select Item --": {
    "Clothes": {
      "-- Select brands --": {
        "Levis": {
          "-- Select product --": {
            "Jeans": {
              "-- Select size --": [
                "38",
                "39",
                "40"
              ]
            },
            "Jackets": {
              "-- Select size --": [
                "41",
                "42",
                "43"
              ]
            }
          }
        }, // end Levis
        "Gucci": {
          "-- Select product --": {
            "Shoes": {
              "-- Select size --": [
                "45",
                "50",
                "55"
              ]
            },
            "Dresses": {
              "-- Select size --": [
                "8",
                "9",
                "10"
              ]
            }
          }
        } // end Gucci
      } // end brands  
    }, // End clothes
    "Gadgets": {
      "-- Select brands --": {
        "LG": {
          "-- Select product --": {
            "TVs": {
              "-- Select size --": [
                "38",
                "39",
                "40"
              ]
            },
            "Phones": {
              "-- Select size --": [
                "8",
                "9",
                "10"
              ]
            }
          }
        }, // end Levis
        "Apple": {
          "-- Select product --": {
            "Macbooks": {
              "-- Select size --": [
                "15",
                "17",
                "21"
              ]
            },
            "iPhones": {
              "-- Select size --": [
                "8",
                "9",
                "10"
              ]
            }
          }
        } // end Apple
      } // end brands
    } // end  Gadgets
  } // end items
} // end  

function createSel(obj) {
  Object.keys(obj).forEach(function(item) {
    if (typeof obj[item] == "object") {
      var list = obj[item];
      //console.log(item,typeof list);
      if (typeof list == "object") {
        if (list.length) {
          list.forEach(function(val) {
            console.log('<br/>'+val)
          })  
        }  
        else createSel(list)
      }
    } else {
      console.log("no", obj[item])
    }
  });
}
window.onload = function() {
  createSel(selObject)
}
<form name="myform" id="myForm">
  <div id="selContainer">
  </div>
</form>
like image 383
mplungjan Avatar asked Feb 11 '19 15:02

mplungjan


2 Answers

Doing this in React would be easier,. But for a plain JS solution the below might be what your after.

Basically all I'm doing is using recursion to create the components, and attach the events.

const selObject = {
  "-- Select Item --": {
    "Clothes": {
      "-- Select brands --": {
        "Levis": {
          "-- Select product --": {
            "Jeans": {
              "-- Select size --": [
                "38",
                "39",
                "40"
              ]
            },
            "Jackets": {
              "-- Select size --": [
                "41",
                "42",
                "43"
              ]
            }
          }
        }, // end Levis
        "Gucci": {
          "-- Select product --": {
            "Shoes": {
              "-- Select size --": [
                "45",
                "50",
                "55"
              ]
            },
            "Dresses": {
              "-- Select size --": [
                "8",
                "9",
                "10"
              ]
            }
          }
        } // end Gucci
      } // end brands  
    }, // End clothes
    "Gadgets": {
      "-- Select brands --": {
        "LG": {
          "-- Select product --": {
            "TVs": {
              "-- Select size --": [
                "38",
                "39",
                "40"
              ]
            },
            "Phones": {
              "-- Select size --": [
                "8",
                "9",
                "10"
              ]
            }
          }
        }, // end Levis
        "Apple": {
          "-- Select product --": {
            "Macbooks": {
              "-- Select size --": [
                "15",
                "17",
                "21"
              ]
            },
            "iPhones": {
              "-- Select size --": [
                "8",
                "9",
                "10"
              ]
            }
          }
        } // end Apple
      } // end brands
    } // end  Gadgets
  } // end items
} // end  


function fillDropdown(target, obj) {
  const sel = document.createElement("select");
  const sub = document.createElement("div");
  if (typeof obj !== "object") {
    sub.innerHTML = "<p>Thank you for your selection</p>";
    target.appendChild(sub);
    return;
  }
  target.appendChild(sel);
  target.appendChild(sub);
  const [title, value] = Object.entries(obj)[0];
  //add our title option
  const option1 = document.createElement("option");
  option1.innerText = title;
  sel.appendChild(option1);
  //now add the sub items
  const items = Object.entries(value);
  items.forEach(([k, v]) => {
    const option = document.createElement('option');
    option.innerText = k;
    sel.appendChild(option);
  });
  sel.addEventListener("change", () => {
    sub.innerHTML = "";
    if (sel.selectedIndex > 0) {
      const i = items[sel.selectedIndex - 1];    
      fillDropdown(sub, i[1]);
    }
  }); 
}


window.onload = function() {
  //createSel(selObject);
  fillDropdown(
    document.querySelector('#selContainer'),
    selObject
  );
}
select {
  display: block;
  width: 100%;
  padding: 10px;
}
<form name="myform" id="myForm">
  <div id="selContainer">
  </div>
</form>
like image 145
Keith Avatar answered Oct 13 '22 05:10

Keith


here's some other options you might want to consider :

using OptGroup :

const selObject = { "-- Select Item --": { Clothes: { "-- Select brands --": { Levis: { "-- Select product --": { Jeans: { "-- Select size --": ["38", "39", "40"] }, Jackets: { "-- Select size --": ["41", "42", "43"] } } }, Gucci: { "-- Select product --": { Shoes: { "-- Select size --": ["45", "50", "55"] }, Dresses: { "-- Select size --": ["8", "9", "10"] } } } } }, Gadgets: { "-- Select brands --": { LG: { "-- Select product --": { TVs: { "-- Select size --": ["38", "39", "40"] }, Phones: { "-- Select size --": ["8", "9", "10"] } } }, Apple: { "-- Select product --": { Macbooks: { "-- Select size --": ["15", "17", "21"] }, iPhones: { "-- Select size --": ["8", "9", "10"] } } } } } } };

const generateDropDown = (obj, indent) => {
  const spaces = Array(indent).fill('&nbsp;').join('');
  
  if (Array.isArray(Object.values(obj)[0])) {
    return Object.values(obj)[0].map(e => "<option>" + spaces + e + "</option>").join('');
  } else {
    return Object.values(obj).map(brand => {
      return Object.keys(brand).map(product => {
        //?
        return `<optgroup label="${spaces + product}"> ${generateDropDown(brand[product], indent + 4)} </optgroup>`;
      }).join('');
    });
  }
};


const list = generateDropDown(selObject, 0).join(' ');

document.querySelector('#dropDown').innerHTML = list;
<select id="dropDown">

</select>

using ul : ( more flexible to styling )

const selObject = { "-- Select Item --": { Clothes: { "-- Select brands --": { Levis: { "-- Select product --": { Jeans: { "-- Select size --": ["38", "39", "40"] }, Jackets: { "-- Select size --": ["41", "42", "43"] } } }, Gucci: { "-- Select product --": { Shoes: { "-- Select size --": ["45", "50", "55"] }, Dresses: { "-- Select size --": ["8", "9", "10"] } } } } }, Gadgets: { "-- Select brands --": { LG: { "-- Select product --": { TVs: { "-- Select size --": ["38", "39", "40"] }, Phones: { "-- Select size --": ["8", "9", "10"] } } }, Apple: { "-- Select product --": { Macbooks: { "-- Select size --": ["15", "17", "21"] }, iPhones: { "-- Select size --": ["8", "9", "10"] } } } } } } };

const generateDropDown = (obj, indent) => {  
  const values = Object.values(obj);
  if (Array.isArray(values[0])) {
    return values[0].map(e => `<li class="child">${e} </li>`).join(' ');
  } else {
    return values.map(brand => {
      return Object.keys(brand).map(product => {        
        return `<ul class="parent"> <li class="title">${product}</li> ${generateDropDown(brand[product], indent + 2)} </ul>`;
      }).join(' ');
    });
  }
};

const list = generateDropDown(selObject, 0).join(' ');

document.querySelector('#dropDown').innerHTML = list;

[...document.querySelectorAll('ul,li')].forEach(e => {	
	e.addEventListener('click', ev => {
  	ev.cancelBubble = true;    
    ev.target.classList.toggle('open');
  	// console.log(ev.target.innerText)
    // do some stuff when the element is clicked.    
  })
})
ul{
  padding: 0;
  margin: 0;
  list-style-type: none;
}

.parent, .child{
  padding-left: 15px;  
  display: none;
  cursor: pointer;
}

#dropDown > .parent {
  padding-left: 0;
  display: block;
  
}

.open ~ .parent{
  opacity: 1;
  display: block;
}

.open ~ .child{
  opacity: 1;
  display: block;
}

.title{
  font-weight: bold;
}

.title.open{
  color: red;
}
<div id="dropDown">

</div>
like image 29
Taki Avatar answered Oct 13 '22 06:10

Taki