I am trying to render a HTML grid layout in a php file called gardenshop.php. Within the grid container should be grid items in the form of Bootstrap 5 cards that are created dynamically. The grid container (a ) has a class of .items-wrapper. Each card should consist of the following:
a)A container which is a of class of container mt-3 slides-container (if the card has more than one image) or container mt-3 (if the card has only one image).
b)A div nested within a) with a class of tree-card
c)Nested within b) are the image or images. I have a database of treeseeds and some entries have only one image treeImage1 while other treeseed types have jave more than one images with treeImage2 and treeImage3 not being NULL.
d)Below the images and also nested within b) is a another div for the cardBody with a class of “.card-body”
e)Nested within d) should be a element with a class of “.card-title” that will have the commonName of the treeseed.
f)Nested within d) but below e) there should a
element with text saying “Information:” and has a class of “.info-text”.
g)Below f) and nested within d) should be an
- unordered list with the list items (fields/columns/properties) for the treeseeds in the form of
- elements. I have a function called generateListItems() within a php file called shopconnect.php that I import into gardenshop.php.
On the MySQL side I am using Sequelize with a table called treeseeds and it has the following fields:
- id(primary key)
- commonName DataTypes.STRING (60)
- speciesName DataTypes.STRING (60)
- family DataTypes.STRING (60)
- seedcostInclVAT DataTypes.DOUBLE(4,2)
- quantityInStock DataTypes.INTEGER
- seedNumberPerPack DataTypes.INTEGER
- colour DataTypes.STRING
- hardiness DataTypes.STRING
- soilType DataTypes.STRING
- soilAcidity DataTypes.STRING
- description DataTypes.TEXT
- treeImage1 DataTypes.BLOB
- treeImage2 DataTypes.BLOB
- treeImage3 DataTypes.BLOB
Now referring back to g) the data for these columns for each treeseed will be listed a
I am using PDO (PHP Data Object) logic within a file called shopconnect.php to make a connection to my database and request the data back with MySQL select queries as usual.
//shopconnect.php
$host = "127.0.0.1";
$db = "database"
$user = "user";
$pass = "password";
$charset = "utf8mb4";
$datasourcestr = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($datasourcestr, $user, $pass, $options);
$sql1 = "SELECT * FROM suppliers";
$sql2 = "SELECT * FROM treeseeds";
$supplierNameData = $pdo->query($sql1)->fetchAll();
$treeSeedData = $pdo->query($sql2)->fetchAll();
$treeSuppliers = [];
function filterOutSuppliers($parentArr,$childDataArr,$candidateArr){
foreach($parentArr as $parentRow)
{
foreach($childDataArr as $childRow){
if(($parentRow["id"] <=> $childRow["supplierId"]) === 0 ){
array_push($candidateArr, $parentRow["supplierName"]);
}else{
continue;
}
}
}
return $candidateArr;
}
$treeSuppliers = filterOutSuppliers($supplierNameData,$treeSeedData,$treeSuppliers);
$supplierIds = [];
foreach($supplierNameData as $row1) {
array_push($supplierIds,$row1["id"]);
}
$supplierIdStrs = implode(',', $supplierIds);
$treeSeedQuery = "SELECT * FROM `treeseeds` WHERE supplierId in ($supplierIdStrs)";
$treeSeedDataOut = $pdo->query($treeSeedQuery);
$treeSeedArr = [];
do {
$treeSeedArr = $treeSeedDataOut->fetchAll();
} while ($treeSeedDataOut->nextRowset() && $treeSeedDataOut->columnCount());
$domDoc = new DOMDocument();
function generateTreeCard(){
$topicDiv = $GLOBALS['domDoc']->createElement('div');
$topicDiv->setAttribute('class','tree-card'); //tree-card div is to be appended to the container mt-3
return $topicDiv; //USED IN LOOP BELOW
}
function generateCardBody(){
$cardBody = $GLOBALS['domDoc']->createElement('div');
$cardBody->setAttribute('class','card-body');
return $cardBody; //DONE ALREADY APPENDED TO TREECARD BELOW IN THE LOOP
}
function addheaderH4Text($value){
$cardTitle = $GLOBALS['domDoc']->createElement('h4',$value);body
$cardTitle->setAttribute('class','card-title');//needs to be appended on to card body
return $cardTitle; //ALREADY DONE IN THE LOOP BELOW
}
function generateListElements($key,$value){
$resultItem = "";
switch($key){
case "commonName":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
case "speciesName":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
case "family":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
case "seedcostInclVAT":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
case "quantityInStock":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
case "seedNumberPerPack":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
case "colour":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
case "hardiness":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
case "soilType":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
case "soilAcidity":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
case "description":
$resultItem = $GLOBALS['domDoc']->createElement('li',"$key : $value");
break;
}
return $resultItem; //needs to be appended to the productInfoList variable <ul>
}
$outerContainer = $domDoc->createElement('div');
$outerContainer->setAttribute('class','items-wrapper');
$productInfoList = $domDoc->createElement('ul');
$listElementResult = "";
$imgCollectorArr = [];
$h4Text = "";
$cardBody = "";
$treeCard = "";
$treeImage1 = "";
$infoParagraph = "";
$cardContainer = "";
***ISSUE IS IN THE NESTED FOREACH LOOP HERE ****
foreach($treeSeedArr as $key => $value){
$infoParagraph = generateInfoText(); //Just to display "Information:"
foreach($value as $key1 => $value1){
echo $key1 . " has a value of " . $value1 . "<br/>"; //***TEST
if($key1 === 'commonName'){
$h4Text = addheaderH4Text($value1); //GENERATE h4 text line'
$cardBody = generateCardBody()->appendChild($h4Text)->appendChild($infoParagraph);
}
if($key1 !== 'id' && $key1 !== 'treeImage1' && $key1 !== 'treeImage2' && $key1 !== 'treeImage3'){
$listElementResult = generateListElements($key1,$value1); // the bullet pointed list of properties
$productInfoList->appendChild($listElementResult);//this is the <ul> tag
}
if($key1 === 'treeImage1' && !(is_null($value["treeImage1"]))){
if(array_key_exists("treeImage2",$value) && !(is_null($value["treeImage2"]))){
$imgCollectorArr["treeImage1"] = $value[$key1];
continue; //if a treeseed has more at least 2 images
}
$treeImage1 = generatetreeImage1($value1); //this is if a treeseed only has one image
$treeCard = generateTreeCard()->appendChild($treeImage1)->appendChild($cardBody);
}
if($key1 === 'treeImage2' && !(is_null($value["treeImage2"]))){
if(array_key_exists("treeImage3",$value) && !(is_null($value["treeImage3"]))){
$imgCollectorArr["treeImage2"] = $value[$key1];
continue; //if a treeseed has 3 images instead of 2
}
//design what happens when there are 2 images
$slideBox1 = $domDoc->createElement('div');
$slideBox1->setAttribute('class','mySlides fade');
$countText1 = $domDoc->createElement('div','1 / 2');
$countText1->setAttribute('class','numbertext');
$slideBox1->appendChild($countText1);
$firstImg = $domDoc->createElement('img');
$firstImg->setAttribute('style','width:100%');
$firstImg->setAttribute('src',$imgCollectorArr["treeImage1"]);
$slideBox1->appendChild($firstImg);
$slideBox2 = $domDoc->createElement('div');
$slideBox2->setAttribute('class','mySlides fade');
$countText2 = $domDoc->createElement('div','2 / 2');
$countText2->setAttribute('class','numbertext');
$slideBox2->appendChild($countText2);
$secondImg = $domDoc->createElement('img');
$secondImg->setAttribute('style','width:100%');
$secondImg->setAttribute('src',"$value1");
$slideBox2->appendChild($secondImg);
$treeCard->appendChild($slideBox1);
$treeCard->appendChild($slideBox2);
//attach on to treecard
$previousLink = $domDoc->createElement('a','❮');
$previousLink->setAttribute('class','prev');
$previousLInk->setAttribute('onclick','plusSlides(-1)');
$forwardLink = $domDoc->createElement('a','❯');
$forwardLink->setAttribute('class','next');
$forwardLInk->setAttribute('onclick','plusSlides(1)');
$treeCard->appendChild($previousLink);
$treeCard->appendChild($forwardLink);
//dots/circles
$dotCircleBox1 = $domDoc->createElement('div');
$dotCircleBox1->setAttribute('style','text-align:center');
$dotSpanA = $domDoc->createElement('span');
$dotSpanA->setAttribute('class','dot');
$dotSpanA->setAttribute('onclick','currentSlide(1)');
$dotSpanB = $domDoc->createElement('span');
$dotSpanB->setAttribute('class','dot');
$dotSpanB->setAttribute('onclick','currentSlide(2)');
$dotCircleBox1->appendChild($dotSpanA);
$dotCircleBox1->appendChild($dotSpanB);
$treeCard->appendChild($dotCircleBox1);
}
if($key1 === 'treeImage3' && !(is_null($value[$key1]))){
$slideBoxA = $domDoc->createElement('div');
$slideBoxA->setAttribute('class','mySlides fade');
$countTextA = $domDoc->createElement('div','1 / 3');
$countTextA->setAttribute('class','numbertext');
$slideBox1->appendChild($countTextA);
$firstImg = $domDoc->createElement('img');
$firstImg->setAttribute('style','width:100%');
$firstImg->setAttribute('src',$imgCollectorArr["treeImage1"]);
$slideBoxA->appendChild($firstImg);
$slideBoxB = $domDoc->createElement('div');
$slideBoxB->setAttribute('class','mySlides fade');
$countTextB = $domDoc->createElement('div','2 / 3');
$countTextB->setAttribute('class','numbertext');
$slideBoxB->appendChild($countTextB);
$secondImg = $domDoc->createElement('img');
$secondImg->setAttribute('style','width:100%');
$secondImg->setAttribute('src',$imgCollectorArr["treeImage2"]);
$slideBoxB->appendChild($secondImg);
$slideBoxC = $domDoc->createElement('div');
$slideBoxC->setAttribute('class','mySlides fade');
$countTextC = $domDoc->createElement('div','3 / 3');
$countTextC->setAttribute('class','numbertext');
$slideBoxC->appendChild($countTextB);
$thirdImg = $domDoc->createElement('img');
$thirdImg->setAttribute('style','width:100%');
$thirdImg->setAttribute('src',$value1);
$slideBoxC->appendChild($thirdImg);
$treeCard->appendChild($slideBoxA);
$treeCard->appendChild($slideBoxB);
$treeCard->appendChild($slideBoxC);
$previousLink = $domDoc->createElement('a','❮');
$previousLink->setAttribute('class','prev');
$previousLInk->setAttribute('onclick','plusSlides(-1)');
$forwardLink = $domDoc->createElement('a','❯');
$forwardLink->setAttribute('class','next');
$forwardLInk->setAttribute('onclick','plusSlides(1)');
$treeCard->appendChild($previousLink);
$treeCard->appendChild($forwardLink);
//dots/circles
$dotCircleBox2 = $domDoc->createElement('div');
$dotCircleBox2->setAttribute('style','text-align:center');
$dotSpan1 = $domDoc->createElement('span');
$dotSpan1->setAttribute('class','dot');
$dotSpan1->setAttribute('onclick','currentSlide(1)');
$dotSpan2 = $domDoc->createElement('span');
$dotSpan2->setAttribute('class','dot');
$dotSpan2->setAttribute('onclick','currentSlide(2)');
$dotSpan3 = $domDoc->createElement('span');
$dotSpan3->setAttribute('class','dot');
$dotSpan3->setAttribute('onclick','currentSlide(3)');
$dotCircleBox2->appendChild($dotSpan1);
$dotCircleBox2->appendChild($dotSpan2);
$dotCircleBox2->appendChild($dotSpan3); //FORGOT TO APPEND THESE??
$treeCard->appendChild($dotCircleBox2);
}
}
$cardBody->appendChild($productInfoList);
$treeCard->appendChild($cardBody);
//for the forward and backwards arrow heads
$cardContainer = $domDoc->createElement('div');//needs to be appended to items-wrapper
if(!(is_null($value["treeImage2"])) || !(is_null($value["treeImage3"]))){
$cardContainer->setAttribute('class','container mt-3 slides-container');
}else{
$cardContainer->setAttribute('class','container mt-3');
}
$cardContainer->appendChild($treeCard);
$outerContainer->appendChild($cardContainer);
}
$domDoc->appendChild($outerContainer);
$htmlString = $domDoc->saveHTML();
echo $htmlString;
So from the code above I am am getting the data back from database but like I said earlier I want the information presented in a html grid layout with bootstrap cards. The problems occur in the last part of the code with the nested foreach loop (see asterisks in code) and when I start used DOM elements. Essentially, I want to check on each iteration whether treeseed record has only one image or more. If a treeseed has more than one image then I want to render a bootstrap 5 card that has a slideshow where they user can click the dots/circles of navigation arrowheads to see the separate images. //***TEST is just a echo statement to test if I am getting any data back.
plusSlides() and currentSlide() are javascript functions in slidelogic.js and the classes “mySlides fade” are classes that are used in slidelogic.js. See the JavaScript file below.
//slidelogic.js
let slideIndex = 1;
showSlides(slideIndex);
//Next/previous controls
function plusSlides(slideNum){
showSlides(slideIndex += slideNum);
}
// Thumbnail image controls
function currentSlide(n) {
showSlides(slideIndex = n);
}
function showSlides(number){
let i;
let slides = document.getElementsByClassName("mySlides");
let dots = document.getElementsByClassName("dot");
if(number > slides.length){
slideIndex = 1;
}
if(number < 1){
slideIndex = slides.length;
}
for (i = 0; i < slides.length; i++) {
slides[i].style.display = "none";
}
for (i = 0; i < dots.length; i++) {
dots[i].className = dots[i].className.replace(" active", "");
}
slides[slideIndex-1].style.display = "block";
dots[slideIndex-1].className += " active";
}
function currentSlide(number){
displaySlides(slideIndex = number)
}
Then I import the JavaScript file above (slidelogic.js) and shopconnect.php into gardenshop.php
//gardenshop.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="creator">
<link rel="stylesheet" href="./gardenshop.css">
<link rel="icon" href="../tree_icon.png">
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
<header>
<div class="img-container"></div>
<section class="shop-header"><h1>SPROUTY'S GARDENING SHOP</h1></section>
<ul class="header-grp">
<li class="header-item"><a id="back-link" href="./homepage.html"><i id="header-arrow" class="fa-solid fa-arrow-left-long"></i>Back To Homepage</a></li>
<li class="header-item"><i class="fa-solid fa-cart-shopping"></i></li>
</ul>
</header>
<main>
<div class="f-container">
<div class="box-1">
<dl>
<dt class="inline-dt">Contains Stainless Steel</dt>: <input type="checkbox" class="filter-checkbox" id="tool_stainlessstl" name="tool_stainlessstl"/>
<dt class="inline-dt">FSC (Forest Stewardship Council) certified</dt>: <input type="checkbox" class="filter-checkbox" id="tool_fsc_cert" name="tool_fsc_cert"/>
<dt class="inline-dt">In Stock</dt>: <input type="checkbox" class="filter-checkbox" id="tool_stock" name="tool_stock"/>
</dl>
</div>
<div class="box-2">
<dl>
<dt class="inline-dt">In Stock</dt>: <input type="checkbox" class="filter-checkbox" id="giftcard_stock" name="giftcard_stock"/>
<dt class="inline-dt">Weight (grams)</dt>: <input type="range" min="0" max="50" id="giftcard_weight" name="giftcard_weight"/>
<dt class="inline-dt">Price</dt>: <input type="range" min="0" max="30" step="0.01" id="giftcard_price" name="giftcard_price"/>
<dt class="inline-dt">FSC (Forest Stewardship Council) certified</dt>: <input type="checkbox" class="filter-checkbox" id="giftcard_fsc_cert" name="giftcard_fsc_cert"/>
</dl>
</div>
<div class="box-3">
<dl>
<dt class="inline-dt">In Stock</dt>: <input type="checkbox" class="filter-checkbox" id="fertiliser_stock" name="fertiliser_stock"/>
<dt class="inline-dt">Litres (Capacity)</dt>: <input type="range" min="10" max="60" id="fertiliser_capacity" name="fertiliser_capacity"/>
<dt class="inline-dt">Price</dt>: <input type="range" min="0" max="20" step="0.01" id="fertiliser_price" name="fertiliser_price"/>
</dl>
</div>
<div class="box-4">
<dl>
<dt class="inline-dt">In Stock</dt>: <input type="checkbox" id="treeseed_stock" name="treeseed_stock"/>
<dt class="inline-dt">Soil Type</dt>: <select name="soiltypes" id="soiltypes">
<option value="chalk">chalk</option>
<option value="sand">sand</option>
<option value="loam">loam</option>
<option value="clay">clay</option>
</select>
<dt class="inline-dt">Soil Acidity</dt>: <select name="soilacidity" id="soilacidity">
<option value="acid">acid</option>
<option value="alkaline">alkaline</option>
<option value="neutral">neutral</option>
</select>
<dt class="inline-dt">Hardiness</dt>: <select name="hardiness" id="hardiness">
<option value="hardy">hardy</option>
<option value="tender">tender</option>
<option value="hardy/half-hardy">hardy/half-hardy</option>
</select>
<dt class="inline-dt">Price</dt>: <input type="range" min="0" max="4" step="0.01" id="treeseed_price" name="treeseed_price"/>
</dl>
</div>
<div class="box-5">
<dl>
<dt class="inline-dt">In Stock</dt>: <input type="checkbox" id="compost_stock" name="compost_stock"/>
<dt class="inline-dt">Is Biodegradable</dt>: <input type="checkbox" class="filter-checkbox" id="compost_degradable" name="compost_degradable"/>
<dt class="inline-dt">Litres (Capacity)</dt>: <span><input type="range" min="0" max="280" id="compost_capacity" name="compost_capacity"/>
<dt class="inline-dt">Price</dt>: <input type="range" min="0" max="20" step="0.01" id="compost_price" name="compost_price"/>
</dl>
</div>
</div>
<?php require_once './shopconnect.php'; ?>
<script type="text/javascript" src="./slidelogic.js"></script>
</main>
</body>
</html>
Finally hear is some CSS from gardenshop.css
//gardenshop.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.mySlides {
display: none;
}
[class~="container mt-3"]{
position: relative;
}
.prev,.next{
transition-duration: 0.6s;
transition-timing-function: ease;
cursor: pointer;
position: absolute;
top: 50%;
width: auto;
margin-top: -22px;
padding: 16px;
color: white;
font-weight: bold;
font-size: 18px;
border-radius: 0 3px 3px 0;
user-select: none;
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */
}
.next{
right: 0;
border-radius: 3px 0 0 3px;
}
.prev:hover, .next:hover {
background-color: rgba(0,0,0,0.8);
}
.numbertext {
color: #f2f2f2;
font-size: 12px;
padding: 8px 12px;
position: absolute;
top: 0;
}
.dot {
cursor: pointer;
height: 15px;
width: 15px;
margin: 0 2px;
background-color: #bbb;
border-radius: 50%;
display: inline-block;
transition-property: background-color;
transition-duration: 0.6s;
transition-timing-function: ease;
}
.active, .dot:hover {
background-color: #717171;
}
@keyframes fade{
from{opacity: 0.4};
to{opacity: 1};
}
.fade {
animation-name: fade;
animation-duration: 1.5s;
animation-direction: normal;
}
/*CSS for the product items dynamically made in PHP (shopconnect.php)*/
.items-wrapper{
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
grid-gap: 1rem;
}
I tried my code out above via XAMMP (render the php file gardenshop.php using the Apache on it)
I get the following output in the image below. I would like to know how to get the bootstrap 5 cards to render in a grid dynamically. Dynamically in the sense I don’t need to hard code the grid items; if I want to add more treeseed data to my database I can do that and then when the gardenshop.php webpage is loaded (going via localhost in my browser) it will render the correct number of grid items (cards) automatically.
Image of gardenshop.php when I go to localhost and the specific page using XAMPP](https://i.sstatic.net/0bk8E7lC.png)