Reputation: 121
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
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
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
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
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:
Parse a JSON string into an Array of Objects and convert date values into real Date objects.
Dynamically generate Bootstrap card components for each object of the Array of Objects (the first object being the exception, see demo).
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