9

I would like to display some card elements in HTML. I would like to get the variables of the card element from a javascript array.(Such as title etc..). The card element number will also depend on the Javascript array size. I have looked at other questions such as generating dynamic tables but as I have a long customized html code block, it doesn't work.

This is for a website that I am building. I already got the variables from an api end point with a HTTP get request but am unable to display as I wish to. I have looked at many similar questions but can't find the answer that I am looking for.

This is the script for getting the variables with the HTTP get request

<script>
const request = new XMLHttpRequest();

request.open('GET', 'api/seminars', true);
request.onload = function() {
  // Begin accessing JSON data here
  const data = JSON.parse(this.response);

  if (request.status >= 200 && request.status < 400) {
    data.forEach(resultArray => {
      document.getElementById('output').innerHTML = resultArray.name;
      document.getElementById('description').innerHTML =
        resultArray.description;
      document.getElementById('date').innerHTML = resultArray.date;
    });
  } else {
    console.log('error');
  }
};

request.send();
</script>

HTML CODE :

<div id="accordion">
    <div class="card">
        <div class="card-header" id="headingOne">
            <h5 class="mb-0">
                <button class="btn btn-link" data-toggle="collapse" data- target="#collapseOne" aria-expanded="true"
                    aria-controls="collapseOne">

                </button>
            </h5>
        </div>

        <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
            <div class="card-body">

                <h5 id="name"></h5>
                <p id="description"></p>
                <p id="description"></p>
                ...
            </div>
        </div>
    </div>
</div>

And I have continued html code from here on not relevant ..

If there are 3 objects in my array, I would like to create 3 different cards and display name,description.. attributes. But my code only creates one card and displays last object's attributes.

Silvio Biasiol
  • 856
  • 8
  • 14
Ilker Kadir Ozturk
  • 352
  • 1
  • 3
  • 11
  • 1
    Can you give an example of what the api is returning? – Silvio Biasiol Feb 15 '19 at 09:26
  • 1
    Since the number of cards depends on the length of the results array, you need to dynamically generate the html for each card. You need to have a container div in your html. Then dynamically generate the html for each card on your onload method and append that as a child of the container element. – Sanjucta Feb 15 '19 at 09:35
  • 1
    Api is returning name,description and date information about conferences. But It only creates one card element and displays last conference's information in the array. I would like to create different cards for all of the conferences and display their information in those different cards. – Ilker Kadir Ozturk Feb 15 '19 at 09:35
  • 1
    Can you provide an example ? – Ilker Kadir Ozturk Feb 15 '19 at 09:36

3 Answers3

10

You code never really "creates" elements based on your API call - It just updates (ie, overwrites) the existing dom elements by updating the innerHTML of fixed elements referenced by their IDs.

If my interpretation of your code is correct, you should be only seeing the LAST item in your API result. There are also some other weird stuff going on like duplicate IDs which Im guessing are typos

To fix this, create a new div.card-body for each item your API returns and append it to your container

const apiResult = [{
  title: "title1",
  description: "desc1",
  output: "out1"
}, {
  title: "title2",
  description: "desc2",
  output: "out2"
}, {
  title: "title3",
  description: "desc3",
  output: "out3"
}];


const container = document.getElementById('accordion');

apiResult.forEach((result, idx) => {
  // Create card element
  const card = document.createElement('div');
  card.classList = 'card-body';

  // Construct card content
  const content = `
    <div class="card">
    <div class="card-header" id="heading-${idx}">
      <h5 class="mb-0">
        <button class="btn btn-link" data-toggle="collapse" data-target="#collapse-${idx}" aria-expanded="true" aria-controls="collapse-${idx}">

                </button>
      </h5>
    </div>

    <div id="collapse-${idx}" class="collapse show" aria-labelledby="heading-${idx}" data-parent="#accordion">
      <div class="card-body">

        <h5>${result.title}</h5>
        <p>${result.description}</p>
        <p>${result.output}</p>
        ...
      </div>
    </div>
  </div>
  `;

  // Append newyly created card element to the container
  container.innerHTML += content;
})
.card {
padding: 1rem;
border: 1px solid black;
margin: 1rem;
}
<div id="accordion">
  
</div>

Note:

While this works, it's not very scalable. There are some great templating libraries out there with much more advanced interpolation features that you can consider if you have to do something like this in many places (and maintain it)

jsRender

Underscore Templates

Handlebars

UI frameworks like Vue, React, Angular wrap templating with binding to data models and handle auto updating the DOM for you to make things like this easier. Worth investigating if you have a lot of dynamically updating parts on your webpage

Chirag Ravindra
  • 4,760
  • 1
  • 24
  • 35
  • 1
    Thanks for your answer but it is not populating different card objects. It writes all the information in one card. – Ilker Kadir Ozturk Feb 15 '19 at 10:43
  • 1
    @IlkerKadirOzturk understood. In that case, you were nearly there instead of using `=` use `+=` to retain the current contents of the element. I have updated my answer – Chirag Ravindra Feb 15 '19 at 10:46
  • 1
    I think the problem is that I don't have these card objects in the beginning but would like to create them depending on the size of the incoming array. Does this code populate card objects depending on the size ? I still am getting one card object. – Ilker Kadir Ozturk Feb 15 '19 at 11:16
  • 1
    My previous code did that. I edited it after your comment `Thanks for your answer but it is not populating different card objects. It writes all the information in one card.`. I assumed you meant you wanted it in one card. Reverting my answer – Chirag Ravindra Feb 15 '19 at 12:17
  • 1
    I have also updated the code to imitate your HTML markup - which you may need to tweak as needed – Chirag Ravindra Feb 15 '19 at 12:26
  • By the way, what is the use of the idx variable ? Does it accomplish giving the different card elements different id's ? – Ilker Kadir Ozturk Feb 15 '19 at 15:34
  • Exactly I;m using the index of the element in the array to generate a unique ID per item. I'm not sure what library/framework you are using but I think the ID references (for ex `data-target="#collapse-${idx}"` need to be correct for the accordion function to work. You can also use something on the result object instead which is unique to each element (`result.title`, perhaps?). – Chirag Ravindra Feb 15 '19 at 17:33
  • Is it possible to make a grid of these cards? they only come one below the other. It would be great if you could let me know – Roobesh Balaji Jun 08 '21 at 20:42
  • @RoobeshBalaji that should be a CSS change. It's out of the scope of this question but it's definietly possible using either CSS grids or flexbox :) This just generates the DOM elements - how you want to style them is up to you :) – Chirag Ravindra Jun 09 '21 at 15:43
  • @ChiragRavindra yeah thanks I understand, but I couldn't make it work, so I was hoping you had some idea that could make it work with the existing example. Thanks anyways! – Roobesh Balaji Jun 10 '21 at 16:42
  • @RoobeshBalaji I'd be happy to help - just not sure the comments section of this is the best place to do it. [Check this link out](https://www.quackit.com/css/grid/examples/css_grid_card_examples.cfm) and see if it helps. If not, feel free to ask a new question and tag me on it :) – Chirag Ravindra Jun 10 '21 at 17:35
3

Suppose you have already queried the API, a way of doing it could be:

// Supposing you already have queried the API and have your data
    let data = [
        {name: 'name0', description: 'description', date: 'XX/XX/XXXX'},
        {name: 'name1', description: 'description', date: 'XX/XX/XXXX'},
        {name: 'name2', description: 'description', date: 'XX/XX/XXXX'},
    ]

    data.forEach(res => {
        let card = document.createElement("div");

        let name = document.createTextNode('Name:' + res.name + ', ');
        card.appendChild(name);

        let description = document.createTextNode('Description:' + res.description + ', ');
        card.appendChild(description);

        let date = document.createTextNode('date:' + res.date);
        card.appendChild(date);

        let container = document.querySelector("#container");
        container.appendChild(card);
    });
<!-- At one point where you want to generate the cards you put this container -->
<div id="container"></div>

Yours was not working since you updated always the same elements instead of appending new ones :)

Silvio Biasiol
  • 856
  • 8
  • 14
2

You are overwriting the description's innerHTML with every repetition of your for loop.

Change this

document.getElementById('output').innerHTML = resultArray.name;
document.getElementById('description').innerHTML = resultArray.description;
document.getElementById('date').innerHTML = resultArray.date;

to this

var element = document.createElement('div'); // create new div

var name = document.createElement('h4');
name.innerHTML = resultArray.name;
element.appendChild(name);

var description = document.createElement('p');
description.innerHTML = resultArray.description;
element.appendChild(description);

var date = document.createElement('span');
date.innerHTML = resultArray.date;
element.appendChild(date);

document.body.appendChild(element);
Chirag Ravindra
  • 4,760
  • 1
  • 24
  • 35
Jasper Lichte
  • 500
  • 4
  • 11