Logan
Logan

Reputation: 45

Adding items to a list from JSON array

I'm still new to JavaScript, so apologies if anything is unclear (or if this is really simple) - basically I am trying to create a list from a JSON array, which adds a number of list items to a list (where the exact number of list items to be added varies). For example, in the below - in the "roles" array there are three list items that need to be added for John Smith and four for Mary Taylor.

rolesData = [
{"name": "John Smith", 
 "title": "project manager", 
 "roles": 
 ["Planning and Defining Scope", "Activity Planning and Sequencing", "Resource Planning"],
}, 
 {"name": "Mary Taylor", 
 "title": "test analyst", 
 "roles": 
 ["design and develop tests for software and systems to detect faults", "analyse the defects and bugs to identify what is causing them", "track the success of the solutions", "keep software and systems documentation up to date"],
}, 

Here is the loop I use to add the list items. For the example above this works as intended for the second one as there are 4 items to be added and it loops through four times, but it will obviously add 4 items to the one that only requires 3 as well (the last one comes up as undefined) - what I'm struggling a little with is how to make it so that it loops through the number of times that corresponds to how many items there are in "roles" - e.g. if there are five items in "roles", it should add 5 list items. i.e. what should the condition be in the for loop: for (i = 0; ???????; i++)?

NB: 'num' is a random number that is generated, so if num is 0 it will get the data for John Smith etc.

function createList(num) {

...

var rolesList = document.createElement("ul");

for (i = 0; i < 3; i++){
    var roleItem = document.createElement("li");
    roleItem.innerHTML = rolesData[num].roles[i];
    rolesList.appendChild(roleItem);
}

Update: Having solved the issue of how to get the list to populate with the correct numbers on loading the page, I am now trying to add a functionality where by if you click on a button it will change the items in the list - this means that it may change from having 3 items to 4 items for example, and vice versa.

I have tried adapting the answer from @ggorlen (which worked great for creating the list) below but using getElementById rather than create element (when creating the list I assigned the id "featuredList" to the ul and "listItem" + count to the li's, so that for each new listItem that was created the id was listItem1, listItem2 etc. - I've checked this in console.log and appeared to be working correctly). I believe the issue is that if the initial list has only 3 items then there are only 3 listItem id's assigned, so if trying to change the list to one with 4 in it, there is no id for the fourth one to go to - so I guess my question is how would I do it so that if you are changing the elements, you can change it to a list with more or less items in it?

const createList = data => {
const list = document.getElementById("featuredList");
count = 1;
   data.forEach(e => {
     const listItem = document.getElementByID("listItem" + count);
     listItem.innerHTML = e;
     list.appendChild(listItem);
     count++;
  });
return list;
};

Upvotes: 0

Views: 466

Answers (2)

RobG
RobG

Reputation: 147503

Just an alternative using reduce and taking advantage of appendChild returning the appended element, so you can create and append in one go and get a reference to the appended node.

var rolesData = [
{"name": "John Smith", 
 "title": "project manager", 
 "roles": 
 ["Planning and Defining Scope", "Activity Planning and Sequencing", "Resource Planning"],
}, 
 {"name": "Mary Taylor", 
 "title": "test analyst", 
 "roles": 
 ["design and develop tests for software and systems to detect faults", "analyse the defects and bugs to identify what is causing them", "track the success of the solutions", "keep software and systems documentation up to date"],
}];

function createList(data) {
  var list = document.createElement('ul');
  list.appendChild(data.roles.reduce((frag, role) => {
    var item = frag.appendChild(document.createElement('li'));
    item.innerHTML = role;
    return frag;
  }, document.createDocumentFragment()));
  return list;
}

window.onload = function(){
  document.body.appendChild(createList(rolesData[Math.random()*rolesData.length|0]));
};

Upvotes: 1

ggorlen
ggorlen

Reputation: 57259

Your loop should not iterate up to a fixed literal number like 3, but instead to whatever the roles.length array property is. Even cleaner, use array.forEach, which calls a function on every element of the array and passes the current element into the function. I called it e for element in the below example.

Secondly, it's poor practice to access a global variable from a function context. This is an unnecessary dependency that breaks modularity. If your global scope changes, you may inadvertently break unrelated functions elsewhere in your code and it becomes difficult to reason about the program state. Pass all of your data into the function as parameters. In this case, you could skip the num parameter and simply pass in the data object itself.

Thirdly, try to keep functions as generic as possible. A createList function should create any <ul> list you wish, so giving the variables generic names and ignoring the .roles property on data (the caller can worry about this) enables maximum code reuse.

Lastly, I'm returning the node array back to the caller to allow flexibility in terms of what to do with it (in this case, appending it to the document body). The caller is taking a random decimal number between 0 and 1 using Math.random() and multiplying that by the size of the array. Since array indices are integers, the bitwise ~~ (not not) chops off the decimal portion of the number. This is roughly equivalent to Math.floor or | 0.

const rolesData = [{
    "name": "John Smith",
    "title": "project manager",
    "roles": ["Planning and Defining Scope", "Activity Planning and Sequencing", "Resource Planning"],
  },
  {
    "name": "Mary Taylor",
    "title": "test analyst",
    "roles": ["design and develop tests for software and systems to detect faults", "analyse the defects and bugs to identify what is causing them", "track the success of the solutions", "keep software and systems documentation up to date"],
  }
];

const createList = data => {
  const list = document.createElement("ul");

  data.forEach(e => {
    const listItem = document.createElement("li");
    listItem.innerHTML = e;
    list.appendChild(listItem);
  });
  
  return list;
};

document.body.appendChild(
  createList(rolesData[~~(Math.random()*rolesData.length)].roles)
);

If you prefer terseness, reduce (doc) is a good option:

const rolesData = [{
    "name": "John Smith",
    "title": "project manager",
    "roles": ["Planning and Defining Scope", "Activity Planning and Sequencing", "Resource Planning"],
  },
  {
    "name": "Mary Taylor",
    "title": "test analyst",
    "roles": ["design and develop tests for software and systems to detect faults", "analyse the defects and bugs to identify what is causing them", "track the success of the solutions", "keep software and systems documentation up to date"],
  }
];

const createList = data =>
  data.reduce((a, e) => {
    const item = document.createElement("li");
    item.innerHTML = e;
    a.appendChild(item);
    return a;
  }, document.createElement("ul")
);

document.body.appendChild(
  createList(rolesData[~~(Math.random()*rolesData.length)].roles)
);

Upvotes: 2

Related Questions