1

I'm building a PHP page with multiple tabs (Termijnen, Mandaten, and Admin). Each tab's content is pre-rendered by PHP inside hidden <div> elements. I then inject the content into a visible container (#tab-content) using jQuery when a tab is clicked.

Here's the structure of my HTML:

<div id="content-termijnen" style="display:none;">
    <div class="row g-3">
        <?php foreach($termijnen as $t): 
            $statusClass = match($t['status']) {
                'pending' => 'bg-warning text-dark',
                'betaald' => 'bg-success text-white',
                'achterstallig' => 'bg-danger text-white',
                default => 'bg-light text-dark',
            };

            $mandaatNummer = null;
            if (!empty($t['mandaat_id'])) {
                $stmt = $conn->prepare("SELECT mandaat_nummer FROM contributie_mandaten WHERE id = ?");
                $stmt->bind_param("i", $t['mandaat_id']);
                $stmt->execute();
                $stmt->bind_result($mandaatNummer);
                $stmt->fetch();
                $stmt->close();
            }
        ?>
        <div class="col-md-6 col-lg-4">
            <div class="card h-100 shadow-sm">
                <div class="card-body d-flex flex-column justify-content-between">
                    <div>
                        <h5 class="card-title">Termijn #<?= $t['id']; ?></h5>
                        <p class="card-text">
                            Bedrag: €<?= number_format($t['bedrag'], 2, ',', '.'); ?><br>
                            Vervaldatum: <?= date('d-m-Y', strtotime($t['vervaldatum'])); ?><br>
                            <?php if ($mandaatNummer): ?>
                                <span class="badge bg-success">Mandaat: #<?= htmlspecialchars($mandaatNummer); ?></span>
                            <?php else: ?>
                                <span class="badge bg-warning">Geen mandaat gekoppeld</span>
                            <?php endif; ?>
                        </p>
                        <span class="badge <?= $statusClass; ?>"><?= ucfirst($t['status']); ?></span>
                    </div>
                    <div class="mt-3 d-flex gap-2 flex-wrap">
                        <button class="btn btn-sm btn-primary btn-link-mandate" data-termijn-id="<?= $t['id']; ?>">Koppel Mandaat</button>
                        <button class="btn btn-sm btn-outline-primary btn-incasso" data-id="<?= $t['id']; ?>" data-bedrag="<?= number_format($t['bedrag'], 2, ',', '.'); ?>" data-vervaldatum="<?= date('d-m-Y', strtotime($t['vervaldatum'])); ?>">Genereer Incasso</button>
                        <button class="btn btn-sm btn-outline-secondary btn-invoice">Genereer Factuur</button>
                    </div>
                </div>
            </div>
        </div>
        <?php endforeach; ?>
    </div>
</div>

<div id="content-mandaten" style="display:none;">
    <div class="mb-3">
        <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createMandateModal">
            Nieuw Mandaat Aanmaken
        </button>
    </div>
    <div class="row g-3">
        <?php foreach($mandaten as $m): 
            $statusClass = match($m['status']) {
                'pending' => 'bg-warning text-dark',
                'actief' => 'bg-success text-white',
                'gepauzeerd' => 'bg-danger text-white',
                'ingetrokken'=> 'bg-danger text-white',
                default => 'bg-light text-dark',
            };
            $ibanPlain = decryptIban($m['iban'], $encKey, $authKey);
        ?>
        <div class="col-md-6 col-lg-4">
            <div class="card h-100 shadow-sm">
                <div class="card-body d-flex flex-column justify-content-between">
                    <div>
                        <h5 class="card-title">Mandaat #<?= $m['mandaat_nummer']; ?></h5>
                        <p class="card-text">IBAN: <?= htmlspecialchars($ibanPlain !== null ? $ibanPlain : 'Ongeldig'); ?></p>
                        <span class="badge <?= $statusClass; ?>"><?= ucfirst($m['status']); ?></span>
                    </div>
                    <div class="mt-3 d-flex gap-2 flex-wrap">
                        <button class="btn btn-sm btn-outline-primary btn-iban" data-id="<?= $m['id']; ?>">Wijzig IBAN</button>
                        <button class="btn btn-sm btn-primary btn-pause" data-id="<?= $m['id']; ?>">Pauzeer</button>
                        <button class="btn btn-sm btn-outline-primary">Intrekken</button>
                        <button class="btn btn-sm btn-success btn-link-term" data-mandaat-id="<?= $m['id']; ?>">Koppel aan Termijn</button>
                    </div>
                </div>
            </div>
        </div>
        <?php endforeach; ?>
    </div>
</div>

<div id="content-admin" style="display:none;">
    <div class="mb-4">
        <button class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#planModal">Nieuw Plan Aanmaken</button>
        <div class="row g-3">
            <?php foreach($plannen as $p): 
                $statusClass = match($p['status']){
                    'actief' => 'bg-success text-white',
                    'draft' => 'bg-warning text-dark',
                    default => 'bg-secondary text-white'
                };
            ?>
            <div class="col-md-6 col-lg-4">
                <div class="card h-100 shadow-sm">
                    <div class="card-body d-flex flex-column justify-content-between">
                        <div>
                            <h5 class="card-title"><?php echo $p['naam']; ?> €<?php echo number_format($p['bedrag'],2,',','.'); ?></h5>
                            <p class="card-text">Start: <?php echo date('d/m/Y', strtotime($p['startdatum'])); ?><?php if($p['einddatum']) echo ' - Eind: '.date('d/m/Y', strtotime($p['einddatum'])); ?></p>
                            <span class="badge <?php echo $statusClass; ?>"><?php echo ucfirst($p['status']); ?></span>
                        </div>
                        <div class="mt-3 d-flex gap-2">
                            <button class="btn btn-sm btn-outline-primary"><i data-feather="edit-2"></i> Bewerk</button>
                            <button class="btn btn-sm btn-outline-danger"><i data-feather="trash-2"></i> Verwijder</button>
                        </div>
                    </div>
                </div>
            </div>
            <?php endforeach; ?>
        </div>
    </div>
</div>

The tabs themselves are buttons:

<div class="button-group" role="group">
    <button class="btn btn-sm btn-outline-primary active" data-tab="termijnen">Termijnen</button>
    <button class="btn btn-sm btn-outline-primary" data-tab="mandaten">Mandaten</button>
    <button class="btn btn-sm btn-outline-secondary" data-tab="admin">Admin</button>
</div>

JavaScript to load tabs:

function loadTab(tab) {
    const content = $(`#content-${tab}`).html();
    $('#tab-content').html(content);

    if (window.feather) feather.replace(); // for icons
}

I also have search, pagination, and dynamic modals for editing IBANs, pausing mandates, linking mandates to termijnen, etc.

Problem

  • The hidden <div> for Termijnen exists ($('#content-termijnen').length === 1) and contains HTML, but is not visible in the #tab-content container when expected.
  • The hidden <div>s for Mandaten and Admin currently do not exist in the DOM at all when inspected with jQuery ($('#content-mandaten').length === 0).
  • Console logs show that the Termijnen HTML exists, but it does not render initially.
  • Mandaten only exists dynamically if a button is clicked that makes it active.
  • A simple <script>console.log('Hello world');</script> works, so scripts run, but the tab content injection does not appear as expected.
  • All libraries (jQuery, Bootstrap, Feather) are loaded, and no JS errors are present.

Question

  1. Why does content from hidden <div>s sometimes not render when injected into #tab-content, even though the <div> exists and has HTML?
  2. How can I reliably load all tab content immediately on page load or on tab click, so that the dynamic cards, modals, and icons work correctly without requiring extra user interaction?

I tried to render the hidden tab content <div>s (#content-termijnen, #content-mandaten, and #content-admin) into the main container #tab-content using jQuery when the page loads or when a tab button is clicked.

  • I verified that $('#content-termijnen').length === 1 and it contains HTML, so I expected it to appear inside #tab-content on page load.
  • I also verified that console.log('Hello World') in a separate <script> tag works, proving that scripts execute.
  • I expected the Termijnen tab to load automatically, and Mandaten/Admin tabs to populate when their respective buttons are clicked.

Actual results

  • Termijnen HTML exists in the DOM but does not appear in #tab-content, so the content is invisible.
  • Mandaten and Admins are 0 in length initially and do not appear at all.
  • No JavaScript errors occur, and libraries like jQuery and Feather are loaded initially.
2
  • 1
    Why are you not using Bootstrap's existing implementation of tabs, instead of cooking your own (which is not nearly as accessible)? getbootstrap.com/docs/5.3/components/navs-tabs/… Commented Oct 2 at 12:31
  • "JavaScript to load tabs:" - and where does this loadTab function actually get called? Commented Oct 2 at 12:34

1 Answer 1

1

PS: why not use Bootstrap's navs-tabs?


Removed all the irrelevant PHP from your code, and I've added the click handler (you have not provided in your example code) and the <div id="tab-content"></div> to inject contents into:

function loadTab(tab) {
  const content = $(`#content-${tab}`).html();
  $('#tab-content').html(content || "404");
  if (window.feather) feather.replace(); // for icons
}

$("[data-tab]").on("click", function() {
  const tab = $(this).data("tab");
  loadTab(tab);
});

// Init
loadTab("termijnen");
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<div class="button-group" role="group">
  <button class="btn btn-sm btn-outline-primary active" data-tab="termijnen">Termijnen</button>
  <button class="btn btn-sm btn-outline-primary" data-tab="mandaten">Mandaten</button>
  <button class="btn btn-sm btn-outline-secondary" data-tab="admin">Admin</button>
</div>

<div id="tab-content"></div>

<div id="content-termijnen" style="display:none;">
  <h1>termijnen content...</h1>
</div>

<div id="content-mandaten" style="display:none;">
  <h1>mandaten content...</h1>
</div>

<div id="content-admin" style="display:none;">
  <h1>admin content...</h1>
</div>

<script src="https://code.jquery.com/jquery-3.1.0.js"></script>

Also, instead of using .html() to populate the <div id="tab-content"></div> wrapper, why not just toggle the visibility of a specific section?

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.