I love getting questions from people that are using Customer Insights – Journeys. I have a lot of ideas but there are scenarios that I just never come up against myself. One was a question that asked if it might be possible to add a search box or filtering to an event registration form that has sessions. In that readers case, they had about 100 sessions that needed to be displayed. What a pain for the person trying to register, having to scroll through and read all of them just to find the right ones to pick. I accepted the challenge and built some options with search and filtering of sessions that works nicely with a keyword search, category drop down, date selector, time option, ability to reset all the filters, and a counter that shows how many sessions are found each time you filter along with the number of sessions registered for. The cherry on the cake is the ability to block someone from registering for another session on the exact same date and time. Let’s check it out!
The first thing I would suggest is consider the layout of the sessions. By default they will be a long list like this.
Simply add this in to your HTML in the <style> section near the top. Notice the grid-template-columns part? The repeat of 2 will show us two sessions per row. Make sure you also include the media only screen part which determines how it will look on a mobile. You should leave this as repeat 1 so that each session shows on a single row stacked on top of each other. I also reduced the gap between each one from 20px to 4px.
//Event Sessions div[data-editorblocktype="Sessions"] .eventSessions { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .eventSessions { font-size: 16px; line-height: 1.2; border: none; display: grid !important; grid-template-columns: repeat(2, 1fr); gap: 20px; } @media only screen and (max-width: 768px) { div[data-editorblocktype="Sessions"] .eventSessions { grid-template-columns: repeat(1, 1fr); /* one column */ gap: 4px; } .eventSessions { grid-template-columns: repeat(1, 1fr);/* one column */ gap: 4px; } }
This works great for my mine because I have two sessions on each date at the same time. Makes it nice and easy to review.
You could change that 2 to a 3 and get even more per row. You get the idea.
Let’s start of small on the form and work our way up to the final big all singing all dancing finished product. First, we could add in a keyword search box at the top. This will allow someone to type something that will filter the sessions based on the session title and/or session description. I also added in a counter that shows how many sessions have been found that match the keyword search.
If this is all you would want, great, you can copy the script below.
<script> document.addEventListener("d365mkt-afterformload", function () { const sessionsDiv = document.querySelector("div[data-editorblocktype="Sessions"]"); if (!sessionsDiv) return; // --- Create keyword search box --- const keywordBox = document.createElement("input"); keywordBox.type = "text"; keywordBox.placeholder = "Search sessions..."; keywordBox.style.width = "100%"; keywordBox.style.padding = "8px"; keywordBox.style.margin = "10px 0 5px 0"; keywordBox.style.boxSizing = "border-box"; // --- Create results counter --- const resultsCounter = document.createElement("div"); resultsCounter.style.margin = "0 0 10px 0"; resultsCounter.style.fontStyle = "italic"; resultsCounter.style.fontSize = "14px"; resultsCounter.textContent = ""; // Insert counter and search box above sessions sessionsDiv.insertAdjacentElement("beforebegin", resultsCounter); sessionsDiv.insertAdjacentElement("beforebegin", keywordBox); function filterSessions() { const term = keywordBox.value.toLowerCase(); const sessionDivs = document.querySelectorAll(".eventSession"); let visibleCount = 0; sessionDivs.forEach(session => { const descDiv = session.querySelector(".eventSessionDescription"); if (!descDiv) return; // Combine all visible personalization spans in the session for search let textToSearch = ""; descDiv.querySelectorAll("span.msdynmkt_personalization").forEach(span => { if (span.offsetParent !== null) { // only visible textToSearch += " " + span.textContent.toLowerCase(); } }); // Show/hide the session using !important to override CSS if (term === "" || textToSearch.includes(term)) { session.style.setProperty("display", "flex", "important"); visibleCount++; } else { session.style.setProperty("display", "none", "important"); } }); // Update counter text if (visibleCount === 0) { resultsCounter.textContent = "No sessions found."; } else if (visibleCount === 1) { resultsCounter.textContent = "1 session found."; } else { resultsCounter.textContent = `${visibleCount} sessions found.`; } } // --- Event listener --- keywordBox.addEventListener("input", filterSessions); // Initial run filterSessions(); }); </script>
I think we can do better though. What about a category? You might think this is impossible, but all we need to do is include the word Category: followed by whatever you want to use at the end of each Session Summary on the individual session record. The script can then extract the final part that comes after Category: and make a nice little drop down list for us. Beautiful! You could do something like that with Location or other filtering options you want to create.
If you want the option above, use the script below.
<script> document.addEventListener("d365mkt-afterformload", function () { const sessionsDiv = document.querySelector("div[data-editorblocktype="Sessions"]"); if (!sessionsDiv) return; const filterContainer = document.createElement("div"); filterContainer.style.display = "flex"; filterContainer.style.flexWrap = "wrap"; filterContainer.style.gap = "10px"; filterContainer.style.marginBottom = "10px"; // --- Keyword search --- const keywordBox = document.createElement("input"); keywordBox.type = "text"; keywordBox.placeholder = "Search sessions..."; keywordBox.style.flex = "1"; keywordBox.style.minWidth = "150px"; keywordBox.style.padding = "8px"; // --- Category dropdown --- const categorySelect = document.createElement("select"); categorySelect.style.flex = "1"; categorySelect.style.minWidth = "150px"; categorySelect.style.padding = "8px"; categorySelect.innerHTML = `<option value="">Filter by category</option>`; // --- Results counter --- const resultsCounter = document.createElement("div"); resultsCounter.style.margin = "0 0 10px 0"; resultsCounter.style.fontStyle = "italic"; resultsCounter.style.fontSize = "14px"; resultsCounter.textContent = ""; filterContainer.appendChild(keywordBox); filterContainer.appendChild(categorySelect); sessionsDiv.insertAdjacentElement("beforebegin", resultsCounter); sessionsDiv.insertAdjacentElement("beforebegin", filterContainer); const sessionDivs = document.querySelectorAll(".eventSession"); const categories = new Set(); // --- Collect unique categories --- sessionDivs.forEach(session => { const descDiv = session.querySelector(".eventSessionDescription"); if (!descDiv) return; const text = descDiv.textContent; const catMatch = text.match(/Category:s*(.+)/i); if (catMatch) categories.add(catMatch[1].trim()); }); // Populate category dropdown Array.from(categories).sort().forEach(cat => categorySelect.innerHTML += `<option value="${cat}">${cat}</option>` ); // --- Filter function --- function filterSessions() { const term = keywordBox.value.toLowerCase(); const selectedCategory = categorySelect.value.toLowerCase(); let visibleCount = 0; sessionDivs.forEach(session => { const descDiv = session.querySelector(".eventSessionDescription"); if (!descDiv) return; // Keyword search let textToSearch = ""; descDiv.querySelectorAll("span.msdynmkt_personalization").forEach(span => { if (span.offsetParent !== null) textToSearch += " " + span.textContent.toLowerCase(); }); // Category const catMatch = descDiv.textContent.match(/Category:s*(.+)/i); const categoryText = catMatch ? catMatch[1].trim().toLowerCase() : ""; const keywordMatch = term === "" || textToSearch.includes(term); const categoryMatch = selectedCategory === "" || categoryText === selectedCategory; if (keywordMatch && categoryMatch) { session.style.setProperty("display", "flex", "important"); visibleCount++; } else { session.style.setProperty("display", "none", "important"); } }); resultsCounter.textContent = visibleCount === 0 ? "No sessions found." : visibleCount === 1 ? "1 session found." : `${visibleCount} sessions found.`; } [keywordBox, categorySelect].forEach(el => { el.addEventListener("input", filterSessions); el.addEventListener("change", filterSessions); }); // Initial run filterSessions(); }); </script>
If your event spans across several days, it would be helpful to have a date and time picker right? This way someone can look for everything on a specific date and even time, or just filter by time and show everything across all days at that time. Lots of options
This one will also include the date and time filters. The calendar will only allow selection of dates when the event is actually held, and the calendar will default to start on the first date it finds in the list of all the sessions.
<script> document.addEventListener("d365mkt-afterformload", function () { const sessionsDiv = document.querySelector("div[data-editorblocktype="Sessions"]"); if (!sessionsDiv) return; const filterContainer = document.createElement("div"); filterContainer.style.display = "flex"; filterContainer.style.flexWrap = "wrap"; filterContainer.style.gap = "10px"; filterContainer.style.marginBottom = "10px"; // --- Keyword search --- const keywordBox = document.createElement("input"); keywordBox.type = "text"; keywordBox.placeholder = "Search sessions..."; keywordBox.style.flex = "1"; keywordBox.style.minWidth = "150px"; keywordBox.style.padding = "8px"; // --- Category dropdown --- const categorySelect = document.createElement("select"); categorySelect.style.flex = "1"; categorySelect.style.minWidth = "150px"; categorySelect.style.padding = "8px"; categorySelect.innerHTML = `<option value="">Filter by category</option>`; // --- Date picker --- const dateInput = document.createElement("input"); dateInput.type = "date"; dateInput.style.flex = "1"; dateInput.style.minWidth = "150px"; dateInput.style.padding = "8px"; // --- Time dropdown --- const timeSelect = document.createElement("select"); timeSelect.style.flex = "1"; timeSelect.style.minWidth = "150px"; timeSelect.style.padding = "8px"; timeSelect.innerHTML = `<option value="">Filter by time</option>`; // --- Results counter --- const resultsCounter = document.createElement("div"); resultsCounter.style.margin = "0 0 10px 0"; resultsCounter.style.fontStyle = "italic"; resultsCounter.style.fontSize = "14px"; resultsCounter.textContent = ""; filterContainer.appendChild(keywordBox); filterContainer.appendChild(categorySelect); filterContainer.appendChild(dateInput); filterContainer.appendChild(timeSelect); sessionsDiv.insertAdjacentElement("beforebegin", resultsCounter); sessionsDiv.insertAdjacentElement("beforebegin", filterContainer); const sessionDivs = document.querySelectorAll(".eventSession"); const categories = new Set(); const times = new Set(); const sessionDates = new Set(); // --- Helper to convert session date to YYYY-MM-DD --- function sessionDateToISO(dateStr) { const parts = dateStr.split("/"); return `${parts[2]}-${parts[1].padStart(2, "0")}-${parts[0].padStart(2, "0")}`; } // --- Helper to convert time to minutes for sorting --- function timeToNumber(timeStr) { const match = timeStr.match(/(d{1,2}):(d{2})s*([AP]M)/i); if (!match) return 0; let hours = parseInt(match[1], 10); const minutes = parseInt(match[2], 10); const ampm = match[3].toUpperCase(); if (ampm === "PM" && hours < 12) hours += 12; if (ampm === "AM" && hours === 12) hours = 0; return hours * 60 + minutes; } // --- Collect unique categories, times, and dates --- sessionDivs.forEach(session => { const descDiv = session.querySelector(".eventSessionDescription"); if (!descDiv) return; const text = descDiv.textContent; const catMatch = text.match(/Category:s*(.+)/i); if (catMatch) categories.add(catMatch[1].trim()); const dateMatch = text.match(/d{1,2}/d{1,2}/d{4}/); if (dateMatch) sessionDates.add(sessionDateToISO(dateMatch[0])); const timeMatch = text.match(/bd{1,2}:d{2}s?[AP]Mb/i); if (timeMatch) times.add(timeMatch[0].trim()); }); // Populate category dropdown Array.from(categories).sort().forEach(cat => categorySelect.innerHTML += `<option value="${cat}">${cat}</option>`); // Populate time dropdown sorted chronologically Array.from(times).sort((a, b) => timeToNumber(a) - timeToNumber(b)) .forEach(t => timeSelect.innerHTML += `<option value="${t}">${t}</option>`); // --- Set min/max and initial date for the calendar --- const sortedDates = Array.from(sessionDates).sort(); // ISO dates sort naturally if (sortedDates.length > 0) { dateInput.min = sortedDates[0]; dateInput.max = sortedDates[sortedDates.length - 1]; } // --- Filter function --- function filterSessions() { const term = keywordBox.value.toLowerCase(); const selectedCategory = categorySelect.value.toLowerCase(); const selectedDate = dateInput.value; // YYYY-MM-DD const selectedTime = timeSelect.value; let visibleCount = 0; sessionDivs.forEach(session => { const descDiv = session.querySelector(".eventSessionDescription"); if (!descDiv) return; // Keyword search let textToSearch = ""; descDiv.querySelectorAll("span.msdynmkt_personalization").forEach(span => { if (span.offsetParent !== null) textToSearch += " " + span.textContent.toLowerCase(); }); // Category const catMatch = descDiv.textContent.match(/Category:s*(.+)/i); const categoryText = catMatch ? catMatch[1].trim().toLowerCase() : ""; // Session date ISO const dateMatch = descDiv.textContent.match(/d{1,2}/d{1,2}/d{4}/); const sessionDateISO = dateMatch ? sessionDateToISO(dateMatch[0]) : ""; // Session time const timeMatch = descDiv.textContent.match(/bd{1,2}:d{2}s?[AP]Mb/i); const sessionTime = timeMatch ? timeMatch[0].trim() : ""; const keywordMatch = term === "" || textToSearch.includes(term); const categoryMatch = selectedCategory === "" || categoryText === selectedCategory; const dateMatchFilter = !selectedDate || sessionDateISO === selectedDate; const timeMatchFilter = selectedTime === "" || sessionTime === selectedTime; if (keywordMatch && categoryMatch && dateMatchFilter && timeMatchFilter) { session.style.setProperty("display", "flex", "important"); visibleCount++; } else { session.style.setProperty("display", "none", "important"); } }); resultsCounter.textContent = visibleCount === 0 ? "No sessions found." : visibleCount === 1 ? "1 session found." : `${visibleCount} sessions found.`; } [keywordBox, categorySelect, dateInput, timeSelect].forEach(el => { el.addEventListener("input", filterSessions); el.addEventListener("change", filterSessions); }); // Initial run filterSessions(); }); </script>
Now let’s tidy things up a bit AND give the person a way to see how many session they have selected already, and a nice button to reset all of the filters rather than needing to manually change them all one by one. The script has a lot of the styling in it (button colours, filter background colours etc) so just tweak it a bit if you want it to show differently.
<script> document.addEventListener("d365mkt-afterformload", function () { const sessionsDiv = document.querySelector("div[data-editorblocktype="Sessions"]"); if (!sessionsDiv) return; // --- Counters container --- const countersContainer = document.createElement("div"); countersContainer.style.display = "flex"; countersContainer.style.flexWrap = "wrap"; countersContainer.style.alignItems = "center"; countersContainer.style.gap = "20px"; countersContainer.style.marginBottom = "10px"; countersContainer.style.padding = "10px 12px"; countersContainer.style.background = "#f3f3f3"; countersContainer.style.border = "1px solid #ccc"; countersContainer.style.borderRadius = "8px"; countersContainer.style.boxShadow = "0 2px 5px rgba(0,0,0,0.05)"; sessionsDiv.insertAdjacentElement("beforebegin", countersContainer); // --- Visible sessions counter --- const resultsCounter = document.createElement("div"); resultsCounter.style.fontStyle = "italic"; resultsCounter.textContent = ""; countersContainer.appendChild(resultsCounter); // --- Selected sessions counter --- const selectedCounter = document.createElement("div"); selectedCounter.style.fontWeight = "bold"; selectedCounter.textContent = "0 sessions selected"; countersContainer.appendChild(selectedCounter); // --- Filter container --- const filterContainer = document.createElement("div"); filterContainer.style.display = "flex"; filterContainer.style.flexWrap = "wrap"; filterContainer.style.alignItems = "center"; filterContainer.style.gap = "10px"; filterContainer.style.marginBottom = "15px"; filterContainer.style.padding = "10px"; filterContainer.style.background = "#f9f9f9"; filterContainer.style.border = "1px solid #ccc"; filterContainer.style.borderRadius = "8px"; filterContainer.style.boxShadow = "0 2px 5px rgba(0,0,0,0.05)"; sessionsDiv.insertAdjacentElement("beforebegin", filterContainer); // --- Keyword search --- const keywordBox = document.createElement("input"); keywordBox.type = "text"; keywordBox.placeholder = "Search sessions..."; keywordBox.style.flex = "1"; keywordBox.style.minWidth = "150px"; keywordBox.style.padding = "8px 12px"; keywordBox.style.border = "1px solid #ccc"; keywordBox.style.borderRadius = "6px"; keywordBox.style.boxSizing = "border-box"; // --- Category dropdown --- const categorySelect = document.createElement("select"); categorySelect.style.flex = "1"; categorySelect.style.minWidth = "150px"; categorySelect.style.padding = "8px 12px"; categorySelect.style.border = "1px solid #ccc"; categorySelect.style.borderRadius = "6px"; categorySelect.innerHTML = `<option value="">All Categories</option>`; // --- Date picker --- const dateInput = document.createElement("input"); dateInput.type = "date"; dateInput.style.flex = "1"; dateInput.style.minWidth = "150px"; dateInput.style.padding = "8px 12px"; dateInput.style.border = "1px solid #ccc"; dateInput.style.borderRadius = "6px"; // --- Time dropdown --- const timeSelect = document.createElement("select"); timeSelect.style.flex = "1"; timeSelect.style.minWidth = "120px"; timeSelect.style.padding = "8px 12px"; timeSelect.style.border = "1px solid #ccc"; timeSelect.style.borderRadius = "6px"; timeSelect.innerHTML = `<option value="">All Times</option>`; // --- Reset button --- const resetButton = document.createElement("button"); resetButton.type = "button"; resetButton.textContent = "Reset Filters"; resetButton.style.padding = "8px 16px"; resetButton.style.border = "none"; resetButton.style.borderRadius = "6px"; resetButton.style.background = "#3c245c"; resetButton.style.color = "#fff"; resetButton.style.cursor = "pointer"; resetButton.style.flex = "0 0 auto"; filterContainer.appendChild(keywordBox); filterContainer.appendChild(categorySelect); filterContainer.appendChild(dateInput); filterContainer.appendChild(timeSelect); filterContainer.appendChild(resetButton); // --- Collect sessions --- const sessionDivs = document.querySelectorAll(".eventSession"); const categories = new Set(); const times = new Set(); const sessionDates = new Set(); function sessionDateToISO(dateStr) { const parts = dateStr.split("/"); return `${parts[2]}-${parts[1].padStart(2,"0")}-${parts[0].padStart(2,"0")}`; } function timeToNumber(timeStr) { const match = timeStr.match(/(d{1,2}):(d{2})s*([AP]M)/i); if (!match) return 0; let hours = parseInt(match[1],10); const minutes = parseInt(match[2],10); const ampm = match[3].toUpperCase(); if (ampm==="PM" && hours<12) hours+=12; if (ampm==="AM" && hours===12) hours=0; return hours*60+minutes; } sessionDivs.forEach(session => { const descDiv = session.querySelector(".eventSessionDescription"); if(!descDiv) return; const text = descDiv.textContent; // Categories const catMatch = text.match(/Category:s*(.+)/i); if(catMatch) categories.add(catMatch[1].trim()); // Dates const dateMatch = text.match(/d{1,2}/d{1,2}/d{4}/); if(dateMatch) sessionDates.add(sessionDateToISO(dateMatch[0])); // Times const timeMatch = text.match(/bd{1,2}:d{2}s?[AP]Mb/i); if(timeMatch) times.add(timeMatch[0].trim()); }); // Populate dropdowns Array.from(categories).sort().forEach(cat => categorySelect.innerHTML += `<option value="${cat}">${cat}</option>`); Array.from(times).sort((a,b)=>timeToNumber(a)-timeToNumber(b)).forEach(t => timeSelect.innerHTML += `<option value="${t}">${t}</option>`); // Calendar min/max const sortedDates = Array.from(sessionDates).sort(); if(sortedDates.length>0){ dateInput.min = sortedDates[0]; dateInput.max = sortedDates[sortedDates.length-1]; } // --- Filtering function --- function filterSessions(){ const term = keywordBox.value.toLowerCase(); const selectedCategory = categorySelect.value.toLowerCase(); const selectedDate = dateInput.value; const selectedTime = timeSelect.value; let visibleCount = 0; sessionDivs.forEach(session=>{ const descDiv = session.querySelector(".eventSessionDescription"); if(!descDiv) return; let textToSearch = ""; descDiv.querySelectorAll("span.msdynmkt_personalization").forEach(span=>{ if(span.offsetParent!==null) textToSearch += " "+span.textContent.toLowerCase(); }); const catMatch = descDiv.textContent.match(/Category:s*(.+)/i); const categoryText = catMatch ? catMatch[1].trim().toLowerCase() : ""; const dateMatch = descDiv.textContent.match(/d{1,2}/d{1,2}/d{4}/); const sessionDateISO = dateMatch ? sessionDateToISO(dateMatch[0]) : ""; const timeMatch = descDiv.textContent.match(/bd{1,2}:d{2}s?[AP]Mb/i); const sessionTime = timeMatch ? timeMatch[0].trim() : ""; const keywordMatch = term==="" || textToSearch.includes(term); const categoryMatch = selectedCategory==="" || categoryText===selectedCategory; const dateMatchFilter = !selectedDate || sessionDateISO===selectedDate; const timeMatchFilter = selectedTime==="" || sessionTime===selectedTime; if(keywordMatch && categoryMatch && dateMatchFilter && timeMatchFilter){ session.style.setProperty("display","flex","important"); visibleCount++; } else { session.style.setProperty("display","none","important"); } }); resultsCounter.textContent = visibleCount===0?"No sessions found.":visibleCount===1?"1 session found.":`${visibleCount} sessions found.`; updateSelectedCount(); } // --- Selected sessions counter --- function updateSelectedCount() { let selectedCount = 0; sessionDivs.forEach(session => { const checkbox = session.querySelector("input[type="checkbox"]"); if (checkbox && checkbox.checked) selectedCount++; }); selectedCounter.textContent = selectedCount===1?"1 session selected":`${selectedCount} sessions selected`; } // --- Event listeners --- [keywordBox, categorySelect, dateInput, timeSelect].forEach(el=>{ el.addEventListener("input",filterSessions); el.addEventListener("change",filterSessions); }); sessionDivs.forEach(session => { const checkbox = session.querySelector("input[type="checkbox"]"); if (checkbox) checkbox.addEventListener("change", updateSelectedCount); }); resetButton.addEventListener("click", ()=>{ keywordBox.value=""; categorySelect.value=""; dateInput.value=""; timeSelect.value=""; filterSessions(); }); // Initial run filterSessions(); }); </script>
Now we are at what I think is the nicest option but it really depends on when your sessions are. In my example there are sessions on the same date at the same time, so I don’t want someone registering for both in that case. This final script includes ALL of the filters, the number of sessions found, number of sessions selected, a nice reset filters button AND disables a session if a session at the same date/time is already selected. Beautiful!
<script> document.addEventListener("d365mkt-afterformload", function () { const sessionsDiv = document.querySelector("div[data-editorblocktype="Sessions"]"); if (!sessionsDiv) return; // --- Counters container --- const countersContainer = document.createElement("div"); countersContainer.style.display = "flex"; countersContainer.style.flexWrap = "wrap"; countersContainer.style.alignItems = "center"; countersContainer.style.gap = "20px"; countersContainer.style.marginBottom = "10px"; countersContainer.style.padding = "10px 12px"; countersContainer.style.background = "#f3f3f3"; countersContainer.style.border = "1px solid #ccc"; countersContainer.style.borderRadius = "8px"; countersContainer.style.boxShadow = "0 2px 5px rgba(0,0,0,0.05)"; sessionsDiv.insertAdjacentElement("beforebegin", countersContainer); // --- Visible sessions counter --- const resultsCounter = document.createElement("div"); resultsCounter.style.fontStyle = "italic"; resultsCounter.textContent = ""; countersContainer.appendChild(resultsCounter); // --- Selected sessions counter --- const selectedCounter = document.createElement("div"); selectedCounter.style.fontWeight = "bold"; selectedCounter.textContent = "0 sessions selected"; countersContainer.appendChild(selectedCounter); // --- Filter container --- const filterContainer = document.createElement("div"); filterContainer.style.display = "flex"; filterContainer.style.flexWrap = "wrap"; filterContainer.style.alignItems = "center"; filterContainer.style.gap = "10px"; filterContainer.style.marginBottom = "15px"; filterContainer.style.padding = "10px"; filterContainer.style.background = "#f9f9f9"; filterContainer.style.border = "1px solid #ccc"; filterContainer.style.borderRadius = "8px"; filterContainer.style.boxShadow = "0 2px 5px rgba(0,0,0,0.05)"; sessionsDiv.insertAdjacentElement("beforebegin", filterContainer); // --- Keyword search --- const keywordBox = document.createElement("input"); keywordBox.type = "text"; keywordBox.placeholder = "Search sessions..."; keywordBox.style.flex = "1"; keywordBox.style.minWidth = "150px"; keywordBox.style.padding = "8px 12px"; keywordBox.style.border = "1px solid #ccc"; keywordBox.style.borderRadius = "6px"; keywordBox.style.boxSizing = "border-box"; // --- Category dropdown --- const categorySelect = document.createElement("select"); categorySelect.style.flex = "1"; categorySelect.style.minWidth = "150px"; categorySelect.style.padding = "8px 12px"; categorySelect.style.border = "1px solid #ccc"; categorySelect.style.borderRadius = "6px"; categorySelect.innerHTML = `<option value="">All Categories</option>`; // --- Date picker --- const dateInput = document.createElement("input"); dateInput.type = "date"; dateInput.style.flex = "1"; dateInput.style.minWidth = "150px"; dateInput.style.padding = "8px 12px"; dateInput.style.border = "1px solid #ccc"; dateInput.style.borderRadius = "6px"; // --- Time dropdown --- const timeSelect = document.createElement("select"); timeSelect.style.flex = "1"; timeSelect.style.minWidth = "120px"; timeSelect.style.padding = "8px 12px"; timeSelect.style.border = "1px solid #ccc"; timeSelect.style.borderRadius = "6px"; timeSelect.innerHTML = `<option value="">All Times</option>`; // --- Reset button --- const resetButton = document.createElement("button"); resetButton.type = "button"; resetButton.textContent = "Reset Filters"; resetButton.style.padding = "8px 16px"; resetButton.style.border = "none"; resetButton.style.borderRadius = "6px"; resetButton.style.background = "#3c245c"; resetButton.style.color = "#fff"; resetButton.style.cursor = "pointer"; resetButton.style.flex = "0 0 auto"; filterContainer.appendChild(keywordBox); filterContainer.appendChild(categorySelect); filterContainer.appendChild(dateInput); filterContainer.appendChild(timeSelect); filterContainer.appendChild(resetButton); // --- Collect sessions --- const sessionDivs = document.querySelectorAll(".eventSession"); const categories = new Set(); const times = new Set(); const sessionDates = new Set(); function sessionDateToISO(dateStr) { const parts = dateStr.split("/"); return `${parts[2]}-${parts[1].padStart(2,"0")}-${parts[0].padStart(2,"0")}`; } function timeToNumber(timeStr) { const match = timeStr.match(/(d{1,2}):(d{2})s*([AP]M)/i); if (!match) return 0; let hours = parseInt(match[1],10); const minutes = parseInt(match[2],10); const ampm = match[3].toUpperCase(); if (ampm==="PM" && hours<12) hours+=12; if (ampm==="AM" && hours===12) hours=0; return hours*60+minutes; } sessionDivs.forEach(session => { const descDiv = session.querySelector(".eventSessionDescription"); if(!descDiv) return; const text = descDiv.textContent; // Categories const catMatch = text.match(/Category:s*(.+)/i); if(catMatch) categories.add(catMatch[1].trim()); // Dates const dateMatch = text.match(/d{1,2}/d{1,2}/d{4}/); if(dateMatch) sessionDates.add(sessionDateToISO(dateMatch[0])); // Times const timeMatch = text.match(/bd{1,2}:d{2}s?[AP]Mb/i); if(timeMatch) times.add(timeMatch[0].trim()); }); // Populate dropdowns Array.from(categories).sort().forEach(cat => categorySelect.innerHTML += `<option value="${cat}">${cat}</option>`); Array.from(times).sort((a,b)=>timeToNumber(a)-timeToNumber(b)).forEach(t => timeSelect.innerHTML += `<option value="${t}">${t}</option>`); // Calendar min/max const sortedDates = Array.from(sessionDates).sort(); if(sortedDates.length>0){ dateInput.min = sortedDates[0]; dateInput.max = sortedDates[sortedDates.length-1]; } // --- Filtering function --- function filterSessions(){ const term = keywordBox.value.toLowerCase(); const selectedCategory = categorySelect.value.toLowerCase(); const selectedDate = dateInput.value; const selectedTime = timeSelect.value; let visibleCount = 0; sessionDivs.forEach(session=>{ const descDiv = session.querySelector(".eventSessionDescription"); if(!descDiv) return; let textToSearch = ""; descDiv.querySelectorAll("span.msdynmkt_personalization").forEach(span=>{ if(span.offsetParent!==null) textToSearch += " "+span.textContent.toLowerCase(); }); const catMatch = descDiv.textContent.match(/Category:s*(.+)/i); const categoryText = catMatch ? catMatch[1].trim().toLowerCase() : ""; const dateMatch = descDiv.textContent.match(/d{1,2}/d{1,2}/d{4}/); const sessionDateISO = dateMatch ? sessionDateToISO(dateMatch[0]) : ""; const timeMatch = descDiv.textContent.match(/bd{1,2}:d{2}s?[AP]Mb/i); const sessionTime = timeMatch ? timeMatch[0].trim() : ""; const keywordMatch = term==="" || textToSearch.includes(term); const categoryMatch = selectedCategory==="" || categoryText===selectedCategory; const dateMatchFilter = !selectedDate || sessionDateISO===selectedDate; const timeMatchFilter = selectedTime==="" || sessionTime===selectedTime; if(keywordMatch && categoryMatch && dateMatchFilter && timeMatchFilter){ session.style.setProperty("display","flex","important"); visibleCount++; } else { session.style.setProperty("display","none","important"); } }); resultsCounter.textContent = visibleCount===0?"No sessions found.":visibleCount===1?"1 session found.":`${visibleCount} sessions found.`; updateSelectedCount(); enforceNoDoubleBooking(); } // --- Selected sessions counter --- function updateSelectedCount() { let selectedCount = 0; sessionDivs.forEach(session => { const checkbox = session.querySelector("input[type="checkbox"]"); if (checkbox && checkbox.checked) selectedCount++; }); selectedCounter.textContent = selectedCount===1?"1 session selected":`${selectedCount} sessions selected`; } // --- No double booking enforcement --- function enforceNoDoubleBooking() { const selectedDateTime = new Map(); sessionDivs.forEach(session => { const checkbox = session.querySelector("input[type="checkbox"]"); const descDiv = session.querySelector(".eventSessionDescription"); if (!checkbox || !descDiv) return; // Get date const dateMatch = descDiv.textContent.match(/d{1,2}/d{1,2}/d{4}/); const sessionDate = dateMatch ? sessionDateToISO(dateMatch[0]) : ""; // Get time const timeMatch = descDiv.textContent.match(/bd{1,2}:d{2}s?[AP]Mb/i); const sessionTime = timeMatch ? timeMatch[0].trim() : ""; if (checkbox.checked && sessionDate && sessionTime) { selectedDateTime.set(`${sessionDate} ${sessionTime}`, true); } }); sessionDivs.forEach(session => { const checkbox = session.querySelector("input[type="checkbox"]"); const descDiv = session.querySelector(".eventSessionDescription"); if (!checkbox || !descDiv) return; const dateMatch = descDiv.textContent.match(/d{1,2}/d{1,2}/d{4}/); const sessionDate = dateMatch ? sessionDateToISO(dateMatch[0]) : ""; const timeMatch = descDiv.textContent.match(/bd{1,2}:d{2}s?[AP]Mb/i); const sessionTime = timeMatch ? timeMatch[0].trim() : ""; const key = `${sessionDate} ${sessionTime}`; if (!checkbox.checked && selectedDateTime.has(key)) { checkbox.disabled = true; session.style.opacity = "0.5"; session.style.pointerEvents = "none"; session.style.background = "#e3e0f0"; session.style.transition = "background 0.3s, opacity 0.3s"; } else { checkbox.disabled = false; session.style.opacity = "1"; session.style.pointerEvents = "auto"; session.style.background = "#f3f3f3"; } }); } // --- Event listeners --- [keywordBox, categorySelect, dateInput, timeSelect].forEach(el=>{ el.addEventListener("input",filterSessions); el.addEventListener("change",filterSessions); }); sessionDivs.forEach(session => { const checkbox = session.querySelector("input[type="checkbox"]"); if (checkbox) checkbox.addEventListener("change", () => { updateSelectedCount(); enforceNoDoubleBooking(); }); }); resetButton.addEventListener("click", ()=>{ keywordBox.value=""; categorySelect.value=""; dateInput.value=""; timeSelect.value=""; filterSessions(); }); // Initial run filterSessions(); }); </script>
I hope these scripts help. Some things to keep in mind if you go down this path and want to add some search and filtering controls to your event registration with sessions form.
Original Post http://meganvwalker.com/search-and-filtering-of-sessions-on-event-form/