David Selem
David Selem

Reputation: 121

Logic correction: I need to place 3 cards then a new row

I'm using pyton flask and bootstrap

I'm trying to show the user's projects in Cards, there can be several projects and I would like to have them on a .card-deck to keep them organized, like this this

I have managed to get the json data from the database and to show it on the web, but I'm struggling a little bit to make it look nice.

The first rows are shown like this, one above of the next one. But if I scroll down a little bit, I found out that it kind of worked

enter image description here

This is my js code

    $(function () {
      var contenidoBienvenida = document.getElementById('contenidoBienvenida'); //The empty div
      var proyectos = JSON.parse('{{ proyectos | safe}}'); // receives json from db
      var optionHTML = '';
      var contador = 0; // counter
  optionHTML == "<div id='card-deck' class='card-deck'>" // This is to wrap the following cards, notice i don't </div>
  contenidoBienvenida.innerHTML = optionHTML; //adds html
  optionHTML == ""
  for (const prop in proyectos) {
    optionHTML += "<div class='card bg-light mb-3'> <div class=' card-header'>" + proyectos[prop].nombreProyecto + "</div> <div class='card-body'> <p class='card-text text-truncate'>" + proyectos[prop].descripcionProyecto + "</p> <p class='card-text'><small class='text-muted'>Creado el " + proyectos[prop].fecha_registro + "</small></p> </div> </div>"; // Adds a new card, normal div closure here
    if (contador == 3) { //when counter reaches 3, it means 3 cards have been added, 

      optionHTML += "</div><div id='card-deck' class='card-deck'>  "; // Here i close the first card-deck and I open a new one, without </div> again
      contenidoBienvenida.innerHTML = optionHTML; //adds httml
      contador = 0; // resets the counter
    }
    contenidoBienvenida.innerHTML = optionHTML; //adds html
    contador = contador + 1;
    })

If I'm not wrong, this is the (desired) output of that JS code

<div id='card-deck' class='card-deck'> <!--the starter with no </div> -->
<div class="card bg-light mb-3">
   <div class=' card-header'>" + proyectos[prop].nombreProyecto + "</div>
   <div class="card-body">
      <p class="card-text text-truncate">" + proyectos[prop].descripcionProyecto + "</p>
      <p class="card-text"><small class="text-muted">Creado el " + proyectos[prop].fecha_registro + "</small></p>
   </div>
</div> <!--the 1st card, counter = 1 </div> --> 
<div class='card bg-light mb-3'>
   <div class=' card-header'>" + proyectos[prop].nombreProyecto + "</div>
   <div class='card-body'>
      <p class='card-text text-truncate'>" + proyectos[prop].descripcionProyecto + "</p>
      <p class='card-text'><small class='text-muted'>Creado el " + proyectos[prop].fecha_registro + "</small></p>
   </div>
</div>  <!--the 2nd card, counter = 2 </div> --> 
<div class='card bg-light mb-3'>
   <div class=' card-header'>" + proyectos[prop].nombreProyecto + "</div>
   <div class='card-body'>
      <p class='card-text text-truncate'>" + proyectos[prop].descripcionProyecto + "</p>
      <p class='card-text'><small class='text-muted'>Creado el " + proyectos[prop].fecha_registro + "</small></p>
   </div>
</div><!--the 3rd card, counter = 3 </div> --> 
</div> <!--Here closes the card-deck --> 
<div id='card-deck' class='card-deck'>  <!--It repeats again, no </div> --> 

This is the actual output ot the JS

enter image description here

This is the HTML code

<div id='mensajeBienvenida' class="container centered pt-5 pb-3">
    <div class="card cardBienvenida shadow-lg animated fadeInDown faster">
        <div class="card-header bg-warning">
        </div>
        <div class="card-body">
            <h4 id='tituloBienvenida' class="card-title lead pb-2"><em>Estos son tus proyectos, {{session.get('nombre')}}</em></h4>
            <div id='contenidoBienvenida'>

                        <!--Here goes the code -->

            </div>
        </div>
        <div class="card-footer text-muted text-center">Secretaría Académica</div>
    </div>
</div>

Thank you for reading.

Update: I solved it, the logic was indeed off, also I removed all ID's because they weren't needed

var contenidoBienvenida = document.getElementById('contenidoBienvenida'); //The empty div
      var proyectos = JSON.parse('{{ proyectos | safe}}'); // receives json from db
      var optionHTML = '';
      var contador = 0; // counter

      optionHTML += "<div class='card-deck'>" // This is to wrap the following cards, notice i don't </div>

      console.log(contenidoBienvenida.innerHTML)
      for (const prop in proyectos) {
        optionHTML += "<div class='card bg-light mb-3'> <div class=' card-header'>" + proyectos[prop].nombreProyecto + "</div> <div class='card-body'> <p class='card-text text-truncate'>" + proyectos[prop].descripcionProyecto + "</p> <p class='card-text'><small class='text-muted'>Creado el " + proyectos[prop].fecha_registro + "</small></p> </div> </div>"; // Adds a new card, normal div closure here
        console.log(optionHTML)
        contenidoBienvenida.innerHTML = optionHTML; //adds html
        contador = contador + 1;
        console.log(proyectos[prop].nombreProyecto)
        if (contador == 3) { //when counter reaches 3, it means 3 cards have been added, 
          console.log('New card-deck')
          optionHTML += "</div><div  class='card-deck'>  "; // Here i close the first card-deck and I open a new one, without </div> again
          contenidoBienvenida.innerHTML = optionHTML; //adds httml
          contador = 0; // resets the counter
        }
      }
    })

Upvotes: 1

Views: 2253

Answers (1)

zer00ne
zer00ne

Reputation: 44033

That's great that you figured it out and I'm glad you came to the conclusion that ids aren't necessary (at least in this particular situation). The following demo will:

  1. Parse a JSON string into an Array of Objects and convert date values into real Date objects.

  2. Dynamically generate Bootstrap card components for each object of the Array of Objects (the first object being the exception, see demo).

  3. Programmatically adjust each .card-deck to hold a maximum of three .card as well as adjusting the last row (.card-deck) when there's one or two .card.

Since Bootstrap is loaded in your project, the majority of the script is jQuery. Further details are commented in demo.

/* A JSON string when once parsed will be a Array of Objects
---The object at index 0 has information about the student -- this
   will be used once and then removed from the array.
*/
let json=`[{"name":"Donnie Darko","desc":"student","date":"2016-02-029T13:11:32Z","class":"student"},{"name":"Vortex","desc":"A rip in time and space used to travel time","date":"2018-05-12T12:49:35Z","class":"project"},{"name":"Aircraft  Crashes","desc":"Early alert system that detects eminent plane crashes ","date":"2018-07-06T20:21:08Z","class":"project"},{"name":"Sleepwalking","desc":"Research on how to weaponize sleepwalking","date":"2018-11-15T00:32:41Z","class":"project"},{"name":"28 Day Plan","desc":"Do anything in 28 days","date":"2019-02-24T10:18:11Z","class":"project"},{"name":"Rabbit Costumes","desc":"Research and development to bulletproof rabbit costumes","date":"2019-03-02T04:41:26Z","class":"project"}]`;

/** bsCards(jsonString)
@ Params: See JSONTool() below.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//A - Pass a JSON string of an Array of Objects
//B - Then pass it on to JSONTool() which returns an Array of Objects 
//C - Extract the first object from the array
//D - This object has been removed from the array and it's name property is used to
      interpolate into a p.lead.
      - Note the brackets and o index that suffixes the jQuery Object $('.load')[0]. 
        This dereferences it into a Plain JavaScript Object. jQuery Objects and Plain 
        JavaScript Objects do not recognize either one's methods so in order to use 
        .insertAdjacentHTML().  
      - .insertAdjacentHTML() is like .innerHTML in that it parses htmlString into 
        real HTML. Although iAH() can't get HTML like .iH can but it can place 
        rendered in 4 positions and it inserts the HTML while .iH overwrites and 
        destroys everything.
//E - Iterate through the array with a for...of loop and .entries() method to 
      destructure it's result in order to gain easy access to [index, value].
//F - There are two Template Literals
      - deck is the container that holds three cards. It is generated every three iterations 
      - card is the .card generated on every loop. It interpolates three values 
        from the array.
//G - There is an extra property added called class which is used to assign either 
      .project or .invisible class. This is done so if there's any extra .cards at 
      the end the 'deck' -- it will justify the extra .card to the left without the
      width fully extending which IMO looks off.
*/      
function bsCards(jsonString) {//A
  let data = JSONTool(jsonString);//B
  let student = data.shift();//C
  $('.lead')[0].insertAdjacentHTML('beforeend', student.name);//D
  for (let [index, project] of data.entries()) {//E
    let deck = `<section class='card-deck deck'></section>`;//F
    let card = `<article class='card bg-light mb-3'><header class='card-header'>${project.name}</header> <section class='card-body'> <p class='card-text text-truncate'>${project.desc}</p></section><footer class='card-footer'><time class='card-text'><small class='text-muted'>Created ${project.date}</small></time></footer></article>`;
    if (index === 0 || index % 3 === 0) {
      $('.stack').append(deck);
      $('.deck').last().append(card).find('.card')
    .last().addClass(project.class);//G
    } else {  
      $('.deck').last().append(card).find('.card')
    .last().addClass(project.class);
    }
  }
}

/** JSONTool(jsonString)
@ Param: jsonString [String]: A string in JSON format
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Utility function that parses a JSON formatted string into an
  Array of Objects. 
- It detects date values by key and converts them into a real Date Object. 
- It will calculate how many placeholders the Array needs to have   a total number of .cards divisible by 3. Once determined, these   placeholder objects are pushed into the new array and then
  returns that new array.
*/
function JSONTool(jsonString) {
  let array = JSON.parse(jsonString, (key, value) => {
    return key === 'date' ? new Date(value).toLocaleString('en-US').split(', ') : value;
  });
  let count = array.length -1;
  let remaining = count % 3;
  if (remaining > 0) {
    for (let c = 0; c < 3 - remaining; c++) {
      let [date, time] = new Date().toLocaleString('en-US').split(', ');
  array.push({"name":"placeholder","desc":"placeholder","date":`${date}, ${time}`,"class":"invisible"});
    }
  }
  return array;
}

bsCards(json);
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css">

<main class='card-deck centered pt-5 pb-3'>
  <figure class="card shadow-lg animated fadeInDown">
    <figcaption class="card-header bg-warning">
      <h4 class='card-title mb-1 mt-0'>Projects</h4>
      <h5 class="card-subtitle text-muted mt-0"><em>Completed as well as works in progress</em></h5>
    </figcaption>
    <article class="card-body">
      <p class="card-text lead" style='font-style: italic'>Student: </p>
      <section class='container stack'>

      </section>
    </article>
    <footer class="card-footer text-muted text-center">Academic Secretariat</footer>
  </figure>
</main>

<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js'></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>

Upvotes: 1

Related Questions