This is a completely blank WordPress page created to test whether O2O coursework catalogue would work on IVMF website, which was built on WordPress as well. This page has some stylesheets and plugins but the goal is to check whether the fundamental workflow would work. Eventually, the process would need to be tested on IVMF website for errors and conflicts.
How does this work?
- A data source somewhere on the cloud: I am using a gist.
- A template to render the data to desired output:
- The script pulls data from source into the hidden table
- The script iterates through all rows and turns each row into a card.
- The script iterates through all rows for agencies and keywords, and it populates the filter panel with distinct values of both. The template includes search functions already just waiting for the values.
What’s in the template?
A pre-designed card
<template id="temp-card2">
<div id="%CERT_CARD_ID%" class="col card-path">
<div class="card h-100">
<div class="card-body">
<h4 class="card-title">%TITLE% </h4>
<h6 class="card-subtitle mb-2 text-body-secondary">%SUBTITLE%</h6>
<p style="font-size: 0.9rem;" class="card-text">%CERT_DESC%</p>
<ul style="font-size: 0.85rem; line-height: 1.1rem;" class="list-inline">
<li>
<span><strong>Keywords: </strong></span> %CAREER%
</li>
</ul>
<a role="button" data-bs-toggle="modal" data-bs-target="%CERT_MODAL_ID_HASHTAG%" class="btn btn-sm btn-outline-primary"> View detail </a>
</div>
</div>
</div>
<div tabindex="-1" id="%CERT_MODAL_ID%" class="modal fade p-0" aria-labelledby="%CERT_MODAL_TITLE_ID%" aria-hidden="true">
<div class="modal-dialog modal-fullscreen-sm-down modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<ul role="tablist" id="myTab" class="nav nav-pills">
<li role="presentation" class="nav-item">
<button
type="button"
tabindex="-1"
role="tab"
id="%CERT_MODAL_NAV_DESC_ID%"
data-bs-toggle="tab"
data-bs-target="%CERT_MODAL_DESC_ID_HASHTAG%"
class="nav-link active"
aria-selected="false"
aria-controls="%CERT_MODAL_DESC_ID%"
>
Description
</button>
</li>
<li role="presentation" class="nav-item">
<button
type="button"
tabindex="-1"
role="tab"
id="%CERT_MODAL_NAV_TOPIC_ID%"
data-bs-toggle="tab"
data-bs-target="%CERT_MODAL_TOPIC_ID_HASHTAG%"
class="nav-link"
aria-selected="false"
aria-controls="%CERT_MODAL_TOPIC_ID%"
>
Topic
</button>
</li>
</ul>
</div>
<div class="modal-body">
<div class="tab-content">
<div role="tabpanel" id="%CERT_MODAL_DESC_ID%" class="tab-pane active" aria-labelledby="%CERT_MODAL_NAV_DESC_ID%">%CERT_DESC%</div>
<div role="tabpanel" id="%CERT_MODAL_TOPIC_ID%" class="tab-pane" aria-labelledby="%CERT_MODAL_NAV_TOPIC_ID%">%EXAM_TOPIC%</div>
</div>
</div>
<div class="modal-footer"><button type="button" data-bs-dismiss="modal" class="btn btn-secondary btn-sm">Close</button><button type="button" class="btn btn-primary btn-sm">Select</button></div>
</div>
</div>
</div>
</template>
An accordion (collapsible) filter panel
<div style="font-family: 'Overpass', sans-serif;" id="accordionExample" class="accordion">
<div class="accordion-item">
<h2 id="headingOne" class="accordion-header">
<button type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" class="accordion-button" aria-expanded="true" aria-controls="collapseOne">Narrow down your search</button>
</h2>
<div id="collapseOne" data-bs-parent="#accordionExample" class="accordion-collapse collapse show" aria-labelledby="headingOne">
<div id="filter-panel" class="accordion-body">
<h6><label for="dropdown-agency">Agency</label></h6>
<select id="dropdown-agency" class="form-select">
<option value=""></option>
</select>
<fieldset id="filter-pathways">
<legend></legend>
<h6>Pathways</h6>
</fieldset>
</div>
</div>
</div>
</div>
A (hidden) table
<table style="width: 90%; margin: auto;" id="pathTable" class="table table-striped nowrap">
<thead>
<tr>
<th id="pathTable-id" data-visible="false" data-data="id">id</th>
<th id="pathTable-title" data-priority="1" data-data="title">Title</th>
<th id="pathTable-certificate-link" data-searchable="false" data-data="certificate-link">Certificate Link</th>
<th id="pathTable-description" data-priority="2" data-data="description">Description</th>
<th id="pathTable-agency" data-data="agency">Agency</th>
<th id="pathTable-agency-link" data-searchable="false" data-data="agency-link">Agency Link</th>
<th id="pathTable-agency-contact" data-searchable="false" data-data="agency-contact">Agency Contact</th>
<th id="pathTable-eligibility" data-data="eligibility">Eligibility</th>
<th id="pathTable-exam" data-data="exam">Exam</th>
<th id="pathTable-pathways" data-visible="false" data-data="pathways">Pathways</th>
<th id="pathTable-exam-topic" data-visible="false" data-data="exam-topic">Topics</th>
</tr>
</thead>
</table>
Some scripts
var cardPath = '<li class="list-inline-item text-secondary card-path me-0 " style="margin-top: 0.25em;" value="%VAL%">%VAL%</li>';
var pathTable, visibleIds;
// Pull data and populate table
jQuery.getJSON(
"https://gist.githubusercontent.com/yuenhsu/d7da45122fce175f8a758efc4ce95a00/raw/0a8c70f8e1004c612c77a3bda0df2da72a02a1ce/o2o_coursework.json",
function (response) {
console.log("jQuery get success. Record count: " + response.length);
pathTable = jQuery("#pathTable").DataTable({
data: response,
info: false,
ordering: false,
paging: false,
autoWidth: true,
//searching: false,
responsive: true,
});
jQuery("#pathTable_filter").hide();
console.log("table initiated");
// Populate card view
var view = pathTable
.data()
.toArray()
.map((row) =>
jQuery("#temp-card2")
.html()
.replaceAll("%TITLE%", row["title"])
.replaceAll("%SUBTITLE%", row["agency"])
.replaceAll("%CERT_DESC%", row["description"])
.replaceAll("%CERT_CARD_ID%", row["id"])
.replaceAll("%CERT_MODAL_ID%", "modal_" + row["id"])
.replaceAll("%CERT_MODAL_ID_HASHTAG%", "#modal_" + row["id"])
.replaceAll("%CERT_MODAL_TITLE_ID%", "modalheader_" + row["id"])
.replace("%EXAM_TOPIC%", row["exam-topic"])
.replaceAll("%CERT_MODAL_DESC_ID%", "modaldesc_" + row["id"])
.replaceAll(
"%CERT_MODAL_DESC_ID_HASHTAG%",
"#modaldesc_" + row["id"],
)
.replaceAll("%CERT_MODAL_NAV_DESC_ID%", "modaldesclab_" + row["id"])
.replaceAll("%CERT_MODAL_TOPIC_ID%", "modaltopic_" + row["id"])
.replaceAll(
"%CERT_MODAL_TOPIC_ID_HASHTAG%",
"#modaltopic_" + row["id"],
)
.replaceAll(
"%CERT_MODAL_NAV_TOPIC_ID%",
"modaltopiclab_" + row["id"],
)
.replace(
"%CAREER%",
row["pathways"]
.filter((value, index, array) => array.indexOf(value) === index)
.sort()
.map((c) => cardPath.replaceAll("%VAL%", c))
.join(", "),
),
)
.join("");
jQuery("#pathCard").append(view);
// Hide table and show cards
jQuery("#pathTable").attr("hidden", "");
jQuery("#pathCard").removeAttr("hidden");
// Move Modal
jQuery("div.modal").detach().appendTo("body");
// Make button float
jQuery("#NextButton").css({
position: "fixed",
bottom: "2rem",
right: "2rem",
"box-shadow": "2px 2px 3px #999",
});
/*jQuery("#pathTable_wrapper").prepend('<button type="button" class="btn btn-outline-primary" id="btn-cardview2" > Card View </button>');
jQuery("#btn-cardview2").click(function () {
let thisTable = jQuery("#pathTable");
let thisCardDiv = jQuery("#pathCard");
if ((thisTable.attr("hidden") == null) && (thisCardDiv.attr("hidden"))) {
thisTable.attr("hidden", "");
thisCardDiv.removeAttr("hidden");
} else {
thisCardDiv.attr("hidden", "");
thisTable.removeAttr("hidden");
}
});*/
// Get values for filter
pathTable
.column("#pathTable-agency")
.data()
.unique()
.sort()
.each(function (item) {
jQuery("#dropdown-agency").append(
jQuery("<option>", {
value: item,
text: item,
}),
);
});
pathTable
.column("#pathTable-pathways")
.data()
.flatten()
.unique()
.sort()
.each(function (item) {
jQuery("#filter-pathways").append(
jQuery("<input>").attr({
type: "checkbox",
autocomplete: "off",
class: "btn-check btn-sm",
id: "btn-" + item.toLowerCase().replace(/[\W_]+/g, "-"),
value: item,
onclick:
"jQuery('#lab' + this.id).toggleClass('btn-outline-secondary').toggleClass('btn-primary'); this.toggleAttribute('checked');",
}),
jQuery("<label>")
.text(item)
.attr({
for: "btn-" + item.toLowerCase().replace(/[\W_]+/g, "-"),
id: "labbtn-" + item.toLowerCase().replace(/[\W_]+/g, "-"),
class: "btn btn-sm rounded-pill btn-outline-secondary",
}),
);
});
jQuery("#dropdown-agency").on("change", function () {
console.log("📍 Filter by agency: " + jQuery(this).val());
pathTable
.column("#pathTable-agency")
.search(jQuery("#dropdown-agency").val())
.draw();
visibleIds = getVisibleIds();
updateCardView(visibleIds);
});
jQuery("#filter-pathways>input").on("change", function () {
if (jQuery(this).is(":checked")) {
jQuery("li.card-path.text-secondary")
.filter(function () {
return jQuery("#filter-pathways>input[checked]")
.toArray()
.map((a) => a.value)
.includes(jQuery(this).attr("value"));
})
.toggleClass("text-secondary")
.toggleClass("text-primary")
.toggleClass("strong");
} else {
jQuery("li.card-path.text-primary")
.toggleClass("text-secondary")
.toggleClass("text-primary")
.toggleClass("strong");
}
let searchArray = jQuery("#filter-pathways>input[checked]")
.toArray()
.map((a) => a.value);
console.log("📍 Filter by pathway: " + searchArray.join(", "));
// Filter table
pathTable
.column("#pathTable-pathways")
.search(searchArray.join("|"), true, false)
.draw();
visibleIds = getVisibleIds();
updateCardView(visibleIds);
});
},
);
function getVisibleIds() {
let ids = pathTable
.rows({ filter: "applied" })
.data()
.toArray()
.map((a) => a.id);
console.log("Displaying " + ids.length + " rows: " + ids.join(", "));
return ids;
}
function updateCardView(inputIds) {
jQuery("div.card-path").each(function () {
if (inputIds.includes(jQuery(this).attr("id"))) {
jQuery(this).removeClass("d-none");
} else {
jQuery(this).addClass("d-none");
}
});
}
Result
| id | Title | Certificate Link | Description | Agency | Agency Link | Agency Contact | Eligibility | Exam | Pathways | Topics |
|---|