Skip to content

Instantly share code, notes, and snippets.

@bzerangue
Last active January 14, 2025 17:50
Show Gist options
  • Save bzerangue/4f81130cce927b6b9ff980a493a978e6 to your computer and use it in GitHub Desktop.
Save bzerangue/4f81130cce927b6b9ff980a493a978e6 to your computer and use it in GitHub Desktop.
PocketPlatform - Dynamic Event List by Month with PocketPlatform Lists of Lists and Webviews and PHP

PocketPlatform - Dynamic Event List by Month with PocketPlatform Lists of Lists and Webviews and PHP

THE SCENARIO

We wanted to get away from the Events page with the long stream of reoccurring events and wanted to come up with a more organized way of structuring the events page of the app. Ever since the PocketPlatform team introduced the Lists of Lists feature, this allowed a Dynamic way of organizing our content in PocketPlatform. We focused on this being the way to go.


OUR INITIAL RESPONSE

Here is what we did to accomplish that.

  1. We created an All Events List Page.
  2. And then we created manually created subitems from that page (from the Items Tab)
    • we entered a page for each month with the following detail

Title: January 2024

Link URL:
[[APP_PREFIX]]events?title=January+2024&Event_Start_Date>=2024-01-01&Event_Start_Date<=2024-01-31&Event_End_Date>=2024-01-01

Start Date:
01/01/2024 12:00 AM CT

End Date:
02/01/2024 12:00 AM CT

And I would have manually entered each month this way.

So February would look like this...

Title: February 2024

Link URL:
[[APP_PREFIX]]events?title=February+2024&Event_Start_Date>=2024-02-01&Event_Start_Date<=2024-02-28&Event_End_Date>=2024-02-01

Start Date:
01/01/2024 12:00 AM CT

End Date:
03/01/2024 12:00 AM CT

As you see, there are some repetitive pieces of information with adjustments of dates. We had to manually enter each one.


THE PROBLEM WITH THAT RESPONSE

Though it added flexibility to our app, it added more manual labor to the process.

  • Note: In the New Year, we were caught up with our manual entries for 2025 and our app up "All Events List" was now blank.
  • This was not sustainable over the long run

PHP and PocketPlatform Webviews to the Rescue

If you have access to a web hosting environment that allows you to upload your own PHP, you can do the following.

You can copy and paste the following PHP (in the following file below).

IMPORTANT

You will need to replace one this on LINE 4 of the file below...

$churchAppPrefix = 'yourchurch';

Replace yourchurch with your church's app prefix. If you are unsure what that is, you can contact ACS/MP Support and they can give you that prefix.

Since this is a "Webview" on an external server the [[APP_PREFIX]] token WILL NOT work, since that is MP only and is only processed inside of MP. Basically what token does inside of MP is change [[APP_PREFIX]] to your yourchurch://

CHANGE IN THE PROCESS

  1. Update the All Events List (List of List Entry)
  2. Under Tools for that entry, I clicked on Link Generator and chose Web View and added the URL to the new PHP file that I uploaded to the server that is hosting my php file.
<?php
// CHURCH App Prefix -- replace the yourchurch text below with your Church App Prefix
$churchAppPrefix = 'yourchurch';
// Set the start date to today
$today = new DateTime('now', new DateTimeZone('America/Chicago'));
$start = $today;
$end = clone $start;
$end->modify("+2 years");
$months = [];
// Generate the array of months
while ($start < $end) {
$months[] = [
"year" => $start->format("Y"),
"month" => $start->format("F"),
"month_num" => $start->format("m"),
"days_in_month" => $start->format("t"),
];
$start->modify("+1 month");
}
// Group months by year
$groupedMonths = [];
foreach ($months as $month) {
$groupedMonths[$month["year"]][] = $month;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title>Event Dates</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
<style>
/* CSS Variables for Light Mode */
:root {
--text-color: #0D0D0D;
--bg-color: #FFFFFF;
--button-text-color: #F8F5F1;
--button-bg-color: #425563;
--accordian-color: #EFEFEF;
}
/* Dark Mode */
@media (prefers-color-scheme: dark) {
:root {
--text-color: #FFFFFF;
--bg-color: #0D0D0D;
--button-text-color: #1D1D1D;
--button-bg-color: #ABC8C7;
--accordian-color: #1D1D1D;
}
}
/* Box sizing rules */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Prevent font size inflation */
html {
-moz-text-size-adjust: none;
-webkit-text-size-adjust: none;
text-size-adjust: none;
}
/* Remove default margin in favour of better control in authored CSS */
body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
-webkit-margin-after: 0;
margin-block-end: 0;
}
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
ul[role="list"],
ol[role="list"] {
list-style: none;
}
/* Set core body defaults */
body {
min-height: 100vh;
line-height: 1.5;
}
/* Set shorter line heights on headings and interactive elements */
h1,
h2,
h3,
h4,
button,
input,
label {
line-height: 1.1;
}
/* Balance text wrapping on headings */
h1,
h2,
h3,
h4 {
text-wrap: balance;
}
/* A elements that don't have a class get default styles */
a:not([class]) {
-webkit-text-decoration-skip: ink;
text-decoration-skip-ink: auto;
color: currentColor;
}
/* Make images easier to work with */
img,
picture {
max-width: 100%;
display: block;
}
/* Inherit fonts for inputs and buttons */
input,
button,
textarea,
select {
font-family: inherit;
font-size: inherit;
}
/* Make sure textareas without a rows attribute are not tiny */
textarea:not([rows]) {
min-height: 10em;
}
/* Anything that has been anchored to should have extra scroll margin */
:target {
scroll-margin-block: 5ex;
}
/* General styles */
body {
margin-inline: 1em;
--gutter: 1em;
font-family: sans-serif;
color: var(--text-color);
background: var(--bg-color);
font-family: "Montserrat", serif;
font-size: 1.5rem;
}
header > h1 {
margin-block-start: 0;
margin-block-end: 1em;
font-size: 2.25rem;
font-weight: 800;
}
li {
width: 100%;
}
.cluster {
display: flex;
flex-direction: var(--cluster-direction, row);
flex-wrap: var(--cluster-wrap, wrap);
column-gap: var(--cluster-column-gap, var(--gutter, var(--space-s-m)));
row-gap: var(--cluster-row-gap, var(--gutter, var(--space-s-m)));
justify-content: var(--cluster-horizontal-alignment, flex-start);
align-items: var(--cluster-vertical-alignment, center);
}
.accordion {
max-width: 55rem;
margin: 0 auto;
}
.accordion-item {
background-color: var(--accordian-color);
color: var(--text-color);
margin-bottom: 5px;
border: 1px solid var(--accordian-color);
border-radius: 0.5em;
}
.accordion-header {
font-weight: 600;
padding: 10px 20px;
cursor: pointer;
position: relative;
}
.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
.accordion-content ul {
padding: 10px 20px;
margin: 0;
}
.accordion-item.active .accordion-content {
max-height: 9999px; /* Adjust based on content */
transition: max-height 0.5s ease-in;
}
.accordion-header::after {
content: '\25BC'; /* Down arrow */
float: right;
transform: rotate(0deg);
transition: transform 0.2s ease-out;
}
.accordion-item.active .accordion-header::after {
transform: rotate(180deg);
}
.button {
--button-color: var(--button-text-color);
--button-bg: var(--button-bg-color);
display: inline-flex;
align-items: center;
gap: 0.5em;
padding: 0.7em 1.2em;
background: var(--button-bg);
color: var(--button-color);
border-width: 3px;
border-style: solid;
border-color: var(--button-bg);
border-radius: 0.5em;
text-decoration: none;
font-weight: 700;
font-size: 1em;
letter-spacing: 0.05ch;
line-height: 1.1;
cursor: pointer;
width: 100%;
}
/* Hover/focus/active for buttons */
.button:hover {
background: var(--button-bg);
color: var(--button-color);
opacity: 0.9; /* Darken on hover */
}
.button:focus {
outline: 3px solid var(--button-bg);
outline-offset: 6px;
}
.button:active {
transform: scale(99%);
}
</style>
</head>
<body>
<div class="wrapper">
<header>
<h1>All Events</h1>
</header>
<div class="accordion">
<?php
// Current year for comparison
$currentYear = date("Y");
// Output grouped by year
foreach ($groupedMonths as $year => $monthsInYear) {
$activeClass = $year == $currentYear ? "active" : "";
echo '<div class="accordion-item ' . $activeClass . '">';
echo '<div class="accordion-header" onclick="toggleAccordion(this)">' .
$year .
"</div>";
echo '<div class="accordion-content">';
echo '<ul class="app-date-list cluster" role="list">';
foreach ($monthsInYear as $index => $month) {
$link = $churchAppPrefix . "://events?title=" . urlencode($month["month"] . " " . $month["year"]);
if ($index === 0) {
// Special handling for the current month
$link .= "&Event_Start_Date>=" . $month["year"] . "-" . $month["month_num"] . "-" . $today->format("d");
$link .= "&Event_Start_Date<=" . $month["year"] . "-" . $month["month_num"] . "-" . $month["days_in_month"];
$link .= "&Event_End_Date>=" . $month["year"] . "-" . $month["month_num"] . "-" . $today->format("d");
} else {
// For all other months or if not the current month
$link .= "&Event_Start_Date>=" . $month["year"] . "-" . $month["month_num"] . "-01";
$link .= "&Event_Start_Date<=" . $month["year"] . "-" . $month["month_num"] . "-" . $month["days_in_month"];
$link .= "&Event_End_Date>=" . $month["year"] . "-" . $month["month_num"] . "-01";
}
echo '<li><a href="' . $link . '" class="button">' . $month["month"] . " " . $month["year"] . "</a></li>";
}
echo "</ul></div></div>";
}
?>
</div>
</div>
<script>
function toggleAccordion(header) {
var item = header.parentElement;
var items = document.querySelectorAll('.accordion-item');
// Close all accordions
items.forEach(function(i) {
if (i !== item) {
i.classList.remove('active');
}
});
// Toggle the clicked accordion
item.classList.toggle('active');
}
</script>
</body>
</html>

Comments are disabled for this gist.