6

I have the following array and I want to create an unordered list from it, but I am having trouble generating the unordered list in the proper format. I have searched similar questions but none of the existing solutions work for my problem.

var myArray = ['Value 1', ['Inner value 1', 'Inner value 2', 'Inner value 3', 'Inner value 4'], 'Value 2', 'Value 3', 'Value 4', 'Value 5', 'Value 6'];

Here is my JavaScript code:

function arrToUl(arr) {
  var div = document.getElementById('myList');
  var ul = document.createElement('ul');

  for (var i = 0; i < arr.length; i++) {

    if (arr[i] instanceof Array) {
      var list = arrToUl(arr[i]);
    } else {
      var li = document.createElement('li');
      li.appendChild(document.createTextNode(arr[i]));
      console.log(ul.appendChild(li));
    }
    div.appendChild(ul);
  }
}

arrToUl(myArray);

The above code is producing the following result:

<ul>
<li>Value 1</li>
<li>Inner Value 1</li>
<li>Inner Value 2</li>
<li>Inner Value 3</li>
<li>Inner Value 4</li>
<li>Value 2</li>
<li>Value 3</li>
<li>Value 4</li >
<li>Value 5</li >
<li>Value 6</li>

But the result should look like below:

<ul>
<li>Value 1
    <ul>
        <li>Inner Value 1</li>
        <li>Inner Value 2</li>
        <li>Inner Value 3</li>
        <li>Inner Value 4</li>
    </ul>
</li>
<li>Value 2</li>
<li>Value 3</li>
<li>Value 4</li>
<li>Value 5</li>
<li>Value 6</li>

Thank you for your help.

guest271314
  • 1
  • 15
  • 104
  • 177
EBamba
  • 131
  • 1
  • 2
  • 10
  • Q: What should the result be if myArray = ['Value 1', ['Inner value 1', 'Inner value 2', 'Inner value 3', 'Inner value 4'], ['Inner value 5', 'Inner value 6', 'Inner value 7', 'Inner value 8'], 'Value 2', 'Value 3', 'Value 4', 'Value 5', 'Value 6'] ? – Khalid T. Dec 27 '16 at 22:26
  • Using *instanceof* to detect an Array is unreliable, use [*Array.isArray*](http://ecma-international.org/ecma-262/7.0/index.html#sec-array.isarray) instead. – RobG Dec 27 '16 at 22:55

6 Answers6

9

You've appended all the <ul> elements to the myList <div>. To change that, I've added a new parameter to the arrToUl(root, arr) function.

The new parameter, root, determines who the created <ul> should be appended to, so if the function encounters a sub-array, it uses the previous list item as the root for the creation of the sub-list.

var myArray = ['Value 1', ['Inner value 1', 'Inner value 2', 'Inner value 3', 'Inner value 4'], 'Value 2', 'Value 3', 'Value 4', 'Value 5', 'Value 6'];

function arrToUl(root, arr) {
  var ul = document.createElement('ul');
  var li;
  
  root.appendChild(ul); // append the created ul to the root

  arr.forEach(function(item) {
    if (Array.isArray(item)) { // if it's an array
      arrToUl(li, item); // call arrToUl with the li as the root
      return;
    }
    
    li = document.createElement('li'); // create a new list item
    li.appendChild(document.createTextNode(item)); // append the text to the li
    ul.appendChild(li); // append the list item to the ul
  });
}

var div = document.getElementById('myList');

arrToUl(div, myArray);
<div id="myList"></div>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
1

Your function is called arrToUl, so it should just do that: convert the array to an ul.

Once you have the ul you can insert it wherever you want, but take that outside the function.

Then it all becomes clear.

function arrToUl(arr) {
  var ul = document.createElement('ul'), li;
  for (var i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      li.appendChild(arrToUl(arr[i]));
    } else {
      li = document.createElement('li');
      li.appendChild(document.createTextNode(arr[i]));
      ul.appendChild(li);
    }
  }
  return ul;
}
var myArray = ['Value 1', ['Inner value 1', 'Inner value 2', 'Inner value 3', 'Inner value 4'], 'Value 2', 'Value 3', 'Value 4', 'Value 5', 'Value 6'];
document.body.appendChild(arrToUl(myArray));
Oriol
  • 274,082
  • 63
  • 437
  • 513
1

With vanilla JS (using ES6 syntax):

const myArray = [
  "Value 1",
  ["Inner value 1", "Inner value 2", "Inner value 3", "Inner value 4"],
  "Value 2",
  "Value 3",
  "Value 4",
  "Value 5",
  "Value 6",
];

const arrayToUL = (arr) => {
  const ul = document.createElement("ul");
  ul.append(
    ...arr.map((value) => {
      if (Array.isArray(value)) return arrayToUL(value);
      const li = document.createElement("li");
      li.textContent = value;
      return li;
    })
  );
  return ul;
};

document.body.appendChild(arrayToUL(myArray));

In ReactJS (takes an array as its children):

const List = ({ children }) => (
  <ul>
    {children.map((val) =>
      Array.isArray(val) ? List({ children: val }) : <li>{val}</li>
    )}
  </ul>
);

const myArray = [
  "Value 1",
  ["Inner value 1", "Inner value 2", "Inner value 3", "Inner value 4"],
  "Value 2",
  "Value 3",
  "Value 4",
  "Value 5",
  "Value 6",
];

const App = () => <List>{myArray}</List>;

ReactDOM.render(<App />, document.getElementById("root"));
<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>
<div id="root"></div>
0

You could save the last li and append to it with an extended function with a target DOM element.

function iter(target) {
    var ul = document.createElement('ul'),
        li;

    target.appendChild(ul);
    return function (a) {
        if (Array.isArray(a)) {
            if (!li) {
                li = document.createElement('li');
                ul.appendChild(li);
            }
            a.forEach(iter(li));
            return;
        }
        li = document.createElement('li');
        li.appendChild(document.createTextNode(a));
        ul.appendChild(li);
    };
}

var myArray = ['Value 1', ['Inner value 1', 'Inner value 2', 'Inner value 3', 'Inner value 4'], 'Value 2', 'Value 3', 'Value 4', 'Value 5', 'Value 6'];

myArray.forEach(iter(document.getElementById('myList')));
<div id="myList"></div>
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

Your recursive approach doesn't say anything about where it should append the new child. Maybe you should give, every time you get an instance of an array, the last element an unique id. And then you could give this id with your function paramers.

GUID Generator is a good way to make an unique id.

Community
  • 1
  • 1
Thomas Devoogdt
  • 816
  • 11
  • 16
0

I would propose a solution based on recursion, it seems to me clearer and less error-prone. It produces a slightly different result as it maps each array element to a node at the same level and I think this should be the output of creating a ul from an array.

var myArray = [
  'Value 1',
  ['Inner value 1', 'Inner value 2', 'Inner value 3', 'Inner value 4'],
  'Value 2',
  'Value 3',
  'Value 4',
  'Value 5',
  'Value 6'
];

function appendItems(arr, el) {
  if(!arr.length) return el;
  else {
      var head = arr.shift();
      var child = transform(head);
      var li = document.createElement('li');
      li.appendChild(child);
      el.appendChild(li);
      return appendItems(arr, el);
  }
}

function transform(item) {
  if(Array.isArray(item)) {
    var ul = document.createElement('ul');
    return appendItems(item, ul);
  } else {
    return document.createTextNode(item);
  }
}

var ul = transform(myArray);

If you would like to have the output you mentioned, it could be better to change the input format in order to match one iteration per node. Ex:

var arr = [
  {label: 'value1', subitems: ['Inner value 1', 'Inner value 2', 'Inner value 3', 'Inner value 4']}
];
Vlad Filimon
  • 534
  • 3
  • 5