Mobilizon in your website via the front-end¶
In this page, we propose some solutions to include Mobilizon events in your web site with simple HTML/Javascript codes. In this case, it's user browser who communicate with Mobilizon server.
With Flux ICS¶
Here is a small page to allow you to integrate, in the form of a calendar, the agenda of a Mobilizon group.
In this example, change the ics link of your group (see Flux ICS).
<html>
<head>
<meta charset='utf-8' />
<meta http-equiv="Content-Security-Policy" content="frame-src '*'">
<style>
html, body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1200px;
margin: 00px auto;
}
</style>
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.0/main.min.css' rel='stylesheet' />
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.0/main.min.js'></script>
<script src='https://github.com/mozilla-comm/ical.js/releases/download/v1.4.0/ical.js'></script>
<script src='https://cdn.jsdelivr.net/npm/@fullcalendar/icalendar@5.11.0/main.global.min.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: "dayGridMonth",
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
},
events: {
url: 'https://mobilizon.fr/@arnfai@hoplagenda.fr/feed/ics',
format: 'ics'
}
});
calendar.render();
});
</script>
</head>
<body>
<div id='calendar'></div>
</html>
With GraphQL API¶
Also, We can imbeded a calendar by using GraphQL API.
In this example, we can change Mobilizon URL and Mobilizon group to show.
Remarque:
<html>
<head>
<script src="fetchEvents.js"></script>
<link rel="stylesheet" href="https://mobilizon.fr/assets/main-oPjfdLFg.css"/>
</head>
<body onload="execute('https://mobilizon.fr/api', 'arnfai@hoplagenda.fr')">
<div id="events-list">
</div>
</body>
</html
var eventsPage = 1;
async function fetchAndRender(mobilizonUrl, groupName) {
const limit = 10;
const after = new Date(Date.now());
const dtFormat = new Intl.DateTimeFormat("fr-FR", {
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
timeZone: 'Europe/Paris',
});
const data = JSON.stringify({
operationName: "FetchGroupEvents",
query: `query FetchGroupEvents($name: String!, $afterDateTime: DateTime, $beforeDateTime: DateTime, $order: EventOrderBy, $orderDirection: SortDirection, $organisedEventsPage: Int, $organisedEventsLimit: Int) {
group(preferredUsername: $name) {
organizedEvents(
afterDatetime: $afterDateTime
beforeDatetime: $beforeDateTime
order: $order
orderDirection: $orderDirection
page: $organisedEventsPage
limit: $organisedEventsLimit
) {
elements {
id
url
onlineAddress
uuid
title
beginsOn
status
draft
description
options {
...EventOptions
__typename
}
participantStats {
participant
notApproved
__typename
}
physicalAddress {
...AdressFragment
__typename
}
picture {
url
id
__typename
}
__typename
}
total
__typename
}
...ActorFragment
__typename
}
}
fragment EventOptions on EventOptions {
maximumAttendeeCapacity
remainingAttendeeCapacity
showRemainingAttendeeCapacity
anonymousParticipation
showStartTime
showEndTime
timezone
offers {
price
priceCurrency
url
__typename
}
participationConditions {
title
content
url
__typename
}
attendees
program
commentModeration
showParticipationPrice
hideOrganizerWhenGroupEvent
isOnline
__typename
}
fragment ActorFragment on Actor {
id
avatar {
id
url
__typename
}
type
preferredUsername
name
domain
summary
url
__typename
}
fragment AdressFragment on Address {
id
description
geom
street
locality
postalCode
region
country
type
url
originId
timezone
pictureInfo {
url
author {
name
url
__typename
}
source {
name
url
__typename
}
__typename
}
__typename
}`,
variables: {
"name": groupName,
"beforeDateTime": null,
"afterDateTime": after,
"order": "BEGINS_ON",
"orderDirection": "ASC",
"organisedEventsPage": eventsPage,
"organisedEventsLimit": limit
}
});
const response = await fetch(
mobilizonUrl,
{
method: 'post',
body: data,
headers: {
'Content-Type': "application/json"
}
}
);
const mapMarker = `<svg fill="currentColor" class="material-design-icon__svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12,11.5A2.5,2.5 0 0,1 9.5,9A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 14.5,9A2.5,2.5 0 0,1 12,11.5M12,2A7,7 0 0,0 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9A7,7 0 0,0 12,2Z"><!----></path></svg>`;
const json = await response.json();
target = document.getElementById('events-list');
json.data.group.organizedEvents.elements.forEach((event) => {
// create the event listing tag
anchor = document.createElement("a");
anchor.setAttribute("class", "mbz-card snap-center dark:bg-mbz-purple sm:flex sm:items-start my-4");
anchor.setAttribute("href", event.url);
// create the image tag
image = document.createElement("img");
image.setAttribute("class", "transition-opacity duration-500 rounded-lg object-cover mx-auto h-full opacity-100");
if (event.picture != null) {
image.src = event.picture.url;
}
// create the title tag
title = document.createElement("h2");
title.setAttribute("class", "mt-0 mb-2 text-2xl line-clamp-3 font-bold text-violet-3 dark:text-white");
title.innerHTML = event.title;
// create the date and time tags
startTime = document.createElement("time");
startTime.setAttribute("datetime", event.beginsOn);
startTime.setAttribute("class", "text-gray-700 dark:text-white font-semibold hidden sm:block");
startdate = new Date(event.beginsOn);
startTime.innerHTML = dtFormat.format(startdate);
// create the location tags
loc = document.createElement("div");
loc.setAttribute("class", "truncate flex gap-1");
mm = document.createElement("span");
mm.innerHTML = mapMarker;
loc.appendChild(mm);
place = document.createElement("span");
place.innerHTML = event.physicalAddress.description + ", " + event.physicalAddress.locality;
loc.appendChild(place);
// create the description
desc = document.createElement("p");
desc.setAttribute("style", "margin-top: 1em;");
desc.innerHTML = event.description;
// put it all together
imgblk = document.createElement("div");
imgblk.setAttribute("class", "rounded-lg sm:w-full sm:max-w-[20rem]");
imgbox = document.createElement("div");
imgbox.setAttribute("class", "h-full w-full max-w-100 min-h-[10rem]");
imgblk.appendChild(imgbox);
imgbox.appendChild(image);
details = document.createElement("div");
details.setAttribute("class", "p-2 flex-auto sm:flex-1");
detbox = document.createElement("div");
detbox.setAttribute("class", "relative flex flex-col h-full");
details.appendChild(startTime);
details.appendChild(title);
details.appendChild(loc);
details.appendChild(desc);
anchor.appendChild(imgblk);
anchor.appendChild(details);
target.appendChild(anchor);
});
}
function execute(mobilizonUrl, groupName) {
window.addEventListener("load", fetchAndRender(mobilizonUrl, groupName));
// infinite scroll
document.addEventListener('DOMContentLoaded', function(e) {
document.addEventListener('scroll', function(e) {
let documentHeight = document.body.scrollHeight;
let currentScroll = window.scrollY + window.innerHeight;
// When the user is [modifier]px from the bottom, fire the event.
let modifier = 200;
if(currentScroll + modifier > documentHeight) {
eventsPage++;
fetchAndRender(mobilizonUrl, groupName);
}
})
})
}
Last update: March 28, 2025