Event Calendar filter customization

Hi @Max

Just a quick question - using this script is there anyway to combine this with the code we can use to specify a “host” on a given page to filter.

We have a LOT of teams with events on the calendar, and our “area of focus” from the photo I shared is designed to be for the public to find events, but we want even more granularity . this Tag functionality is great because they can then select it, but is there a way to pre-select the tag to display on a certain page?

Thanks!

2 Likes

Hi, @Matthew7 :wave:

Yes, it’s possible. Could you please share a link to the page where your widget is installed?

We’ll be happy to help you with it :slightly_smiling_face:

Hey @Max

I set up an example here:

Example: Professional Learning - Health Education | Department of Education

This is under “School Health,” but we don’t want to show all of the events, only the ones with the tag “Health & Physical Education” - How do we further filter this based on the tag?

(Then, is there a way to hide the tag box on that page as well? I would assume not since the JS is actually calling the box to appear so folks can filter). We don’t even care if people can actually SEE the tag filter, we just want it so we can display a sub-category of events on pages (even more specific than the host/area of focus).

Thanks!

1 Like

Thanks!

Your request is with our devs now. I’ll report back once I have their response :slightly_smiling_face:

@Matthew7 We’ve double-checked things and, unfortunately, it’s impossible to implement this customization.

The thing is that you are using the Slider layout via the dynamic layouts option, and the custom filters work with Grid and List layouts only.

Let me know if this clarifies the situation or if any further questions come up.

I had a similar need. I had to make links for pre-filtered results. I found a solution with html parameters (adding “/?venue=“filter”” for example at the end of the html link and it’s handled by this stript but it shouldent be used as it is because I made it for my own needs…

<script type="text/javascript"> 
function decodeHtmlEntities(str) {
    var list = [
        ['"', /&quot;/g],
        ["'", /&apos;/g],
        ["'", /&#039;/g],
        ['&', /&amp;/g],
        ['<', /&lt;/g],
        ['>', /&gt;/g],
        ['¡', /&iexcl;/g],
        ['¢', /&cent;/g],
        ['£', /&pound;/g],
        ['¤', /&curren;/g],
        ['¥', /&yen;/g],
        ['¦', /&brvbar;/g],
        ['§', /&sect;/g],
        ['¨', /&uml;/g],
        ['©', /&copy;/g],
        ['ª', /&ordf;/g],
        ['«', /&laquo;/g],
        ['¬', /&not;/g],
        ['­', /&shy;/g],
        ['®', /&reg;/g],
        ['¯', /&macr;/g],
        ['°', /&deg;/g],
        ['±', /&plusmn;/g],
        ['²', /&sup2;/g],
        ['³', /&sup3;/g],
        ['´', /&acute;/g],
        ['µ', /&micro;/g],
        ['¶', /&para;/g],
        ['·', /&middot;/g],
        ['¸', /&cedil;/g],
        ['¹', /&sup1;/g],
        ['º', /&ordm;/g],
        ['»', /&raquo;/g],
        ['¼', /&frac14;/g],
        ['½', /&frac12;/g],
        ['¾', /&frac34;/g],
        ['¿', /&iquest;/g],
        ['×', /&times;/g],
        ['÷', /&divide;/g],
        ['À', /&Agrave;/g],
        ['Á', /&Aacute;/g],
        ['Â', /&Acirc;/g],
        ['Ã', /&Atilde;/g],
        ['Ä', /&Auml;/g],
        ['Å', /&Aring;/g],
        ['Æ', /&AElig;/g],
        ['Ç', /&Ccedil;/g],
        ['È', /&Egrave;/g],
        ['É', /&Eacute;/g],
        ['Ê', /&Ecirc;/g],
        ['Ë', /&Euml;/g],
        ['Ì', /&Igrave;/g],
        ['Í', /&Iacute;/g],
        ['Î', /&Icirc;/g],
        ['Ï', /&Iuml;/g],
        ['Ð', /&ETH;/g],
        ['Ñ', /&Ntilde;/g],
        ['Ò', /&Ograve;/g],
        ['Ó', /&Oacute;/g],
        ['Ô', /&Ocirc;/g],
        ['Õ', /&Otilde;/g],
        ['Ö', /&Ouml;/g],
        ['Ø', /&Oslash;/g],
        ['Ù', /&Ugrave;/g],
        ['Ú', /&Uacute;/g],
        ['Û', /&Ucirc;/g],
        ['Ü', /&Uuml;/g],
        ['Ý', /&Yacute;/g],
        ['Þ', /&THORN;/g],
        ['ß', /&szlig;/g],
        ['à', /&agrave;/g],
        ['á', /&aacute;/g],
        ['â', /&acirc;/g],
        ['ã', /&atilde;/g],
        ['ä', /&auml;/g],
        ['å', /&aring;/g],
        ['æ', /&aelig;/g],
        ['ç', /&ccedil;/g],
        ['è', /&egrave;/g],
        ['é', /&eacute;/g],
        ['ê', /&ecirc;/g],
        ['ë', /&euml;/g],
        ['ì', /&igrave;/g],
        ['í', /&iacute;/g],
        ['î', /&icirc;/g],
        ['ï', /&iuml;/g],
        ['ð', /&eth;/g],
        ['ñ', /&ntilde;/g],
        ['ò', /&ograve;/g],
        ['ó', /&oacute;/g],
        ['ô', /&ocirc;/g],
        ['õ', /&otilde;/g],
        ['ö', /&ouml;/g],
        ['ø', /&oslash;/g],
        ['ù', /&ugrave;/g],
        ['ú', /&uacute;/g],
        ['û', /&ucirc;/g],
        ['ü', /&uuml;/g],
        ['ý', /&yacute;/g],
        ['þ', /&thorn;/g],
        ['ÿ', /&yuml;/g]
    ];

    // Boucle à travers la liste et remplace les entités HTML par les caractères correspondants
    for (var i = 0; i < list.length; i++) {
        str = str.replace(list[i][1], list[i][0]);
    }

    return str;
}
const waitForElement = (selector, root = document) => new Promise(res => {
  let i = 0;

  const check = () => {
    const component = root.querySelector(selector);

    if (component) {
      res(component);
    } else if (i !== 50) {
      setTimeout(check, 100);
      i++;
    }
  };

  check();
});

document.addEventListener('DOMContentLoaded', function() {
    var urlParams = new URLSearchParams(window.location.search);
    var filterVenue = urlParams.get('venue');
    var filterTag = decodeHtmlEntities(decodeURIComponent(urlParams.get('tag')));

    console.log(filterVenue);
    console.log(filterTag);

    if (filterVenue) {
        var widget = document.querySelectorAll('.elfsight-app-4040ba81-1f6f-4048-9525-7e29f9cd68f7');
        
        for (let i = 0; i < widget.length; i++) {
            if (widget[i]) {
                widget[i].setAttribute('data-elfsight-app-filter-venue', filterVenue);
            } else {
                console.error("No widget found.");
            }
        }
    } else {
        // Si aucun paramètre de lieu, définir sur "Tous"
    }

    if (filterTag) {
        waitForElement("#es-custom-filter").then((myfilter) => {
            if (myfilter) {
                var options = myfilter.options;
                for (var j = 0; j < options.length; j++) {
                    if (options[j].text.toLowerCase() === filterTag.toLowerCase()) {
                        myfilter.selectedIndex = j;
                        var event = new Event('change');
                        myfilter.dispatchEvent(event);
                        break;
                    }
                }
            } else {
                console.error("No filter found.");
            }
        });
    } else {
        // Si aucun paramètre de tag, définir sur "Tous"
    }
});


</script>
1 Like

Hi there, @Diony_Betrisey :wave:

A huge thank you for your help!

I am really happy to hear that you’ve found a workaround for your widget. However, unfortunately, it won’t work for the Matthew’s widget as he is using Slider layout.

Anyway, thank you so much for your participation, that’s much appreciated!

How do you implement it with those layouts? I just changed it to grid, but that doesn’t help me default the correct tag either.

The issue with grid is that it runs forever, and our main calendar has 100’s of events (and we want to show them all), and if we have 20 events for a specific tag, it makes the page super long. I think I had asked in another thread if we could only load 3 events and then add the load more after 3 and I believe that was not possible?

1 Like

A post was split to a new topic: Reached Member limit for Event Calendar

The Load More button isn’t supported in the Grid layout, unfortunately. As for making the tag filter in your Grid Calendar selected by default, I’ll ask the devs and will get back to you after their response :slightly_smiling_face:

It seems that you’ve removed the widget from the page, but here is the solution from our devs:

const PRESELECTED_FILTER = "Health&Physical Education";

const DEFAULT_FILTER_LABEL = "TAGS";
const DEFAULT_FILTER_VALUE = "all";

const waitForElement = (selector, root = document) => new Promise(res => {
  let i = 0;

  const check = () => {
    const component = root.querySelector(selector);

    if (component) {
      res(component);
    } else if (i !== 50) {
      setTimeout(check, 100);
      i++;
    }
  };

  check();
});

waitForElement(".eapp-events-calendar-events-calendar-component").then((widget) => {
  const select = document.createElement('select');
  select.id = "es-custom-filter";
  select.classList.add('eapp-events-calendar-controls-item');
  select.classList.add('eapp-events-calendar-filter');
  select.classList.add('eapp-events-calendar-filter-current');
  
  // Add default option to the filter
  const defaultOption = document.createElement('option');
  defaultOption.value = DEFAULT_FILTER_VALUE;
  defaultOption.text = DEFAULT_FILTER_LABEL;
  select.appendChild(defaultOption);
  
  // Get all tags on the page
  const tags = [...widget.querySelectorAll('.eapp-events-calendar-tags-item')];
  
  // Add tags to the filter list
  const addedTags = [];
  const addTags = (tag) => {
      const trimmedTag = tag.innerText?.trim();
      if (!trimmedTag || addedTags.includes(trimmedTag)) {
        return;
      }
    
      const option = document.createElement('option');
      option.value = trimmedTag;
      option.text = trimmedTag;
      select.appendChild(option);
      
      addedTags.push(trimmedTag);
  };
  tags.forEach(addTags);
  
  // Place the filter after the other ones
  let filterContainer = widget.querySelector('.eapp-events-calendar-controls-component');
  if (!filterContainer) {
      let header = widget.querySelector(".eapp-events-calendar-events-calendar-header");
      if (!header) {
        header = document.createElement("div");
        header.classList.add("eapp-events-calendar-events-calendar-header");
        widget.prepend(header);
      }
      
      filterContainer = document.createElement("div");
      filterContainer.classList.add("eapp-events-calendar-controls-component");
      header.append(filterContainer);
  }
  filterContainer.appendChild(select);
  
  const filterEvents = (event) => {
    const selectedTag = select.value.toLowerCase();
  
    // Look if event has selected tag
    const eventTags = [...event.querySelectorAll(".eapp-events-calendar-tags-item")];
    const match = eventTags.some((tag) => tag.textContent?.toLowerCase() === selectedTag);
    
    // Show/Hide the events
    if (match || selectedTag === DEFAULT_FILTER_VALUE) {
    	event.style.display = 'flex';
    	
    	return;
    }
    
    event.style.display = 'none';
  };
  
  // Add event listener when filter selected
  const eventsContainer = widget.querySelector(".eapp-events-calendar-list-events, .eapp-events-calendar-grid-component");
  const callback = (mutationList) => mutationList.forEach(({ type, addedNodes }) => {
    if (type !== 'childList' || !addedNodes.length) {
      return;
    }

    addedNodes.forEach(filterEvents);
  });
  const observer = new MutationObserver(callback);
  
  const refilter = () => {
    observer.disconnect();
    
    const selectedTag = select.value.toLowerCase();
    
    // Met à jour le texte du filtre sélectionné
    if (selectedTag !== DEFAULT_FILTER_VALUE) {
      select.options[0].text = "All"; // Change the first option when a filter is selected
    } else {
      select.options[0].text = DEFAULT_FILTER_LABEL;
    }
    
    // Get all events on the page
    let events = [...widget.querySelectorAll("[class*='eapp-events-calendar-'][class$='-item-component']")];
    const isGrid = events?.[0].classList.contains("eapp-events-calendar-grid-item-component");
    if (isGrid) {
      events = events.map(event => event.parentNode);
    }
    events.forEach(filterEvents);
    
    observer.observe(eventsContainer, { childList: true });
  };
  select.addEventListener('change', refilter);
  document.addEventListener("click", (event) => {
    if (event.target.closest(".eapp-events-calendar-filter-item")) {
      refilter();
    }
  });
  
  if (PRESELECTED_FILTER) {
    select.value = PRESELECTED_FILTER;
    refilter();
  }
});

This script should be added to the Custom JS section on the Settings tab of your widget’s settings. Try it out and let me know if it worked :slightly_smiling_face:

And here is the code to hide the tag filter. It should be added to the Custom CSS field on the Appearance tab:

#es-custom-filter {
  display: none;
}

Hi @Max - Thanks for this.

This solution only works widget-wide though vs. in-page.

We use the widget on 10-15 pages, and we use the default ‘host’ filter to populate events that are only for certain teams, but those teams have sub-teams, which I was hoping to use the tags for. If I add this to the code, it will only work for 1 subteam across the entire widget (but we have a primary calendar page with ALL events).

I was looking for a solution similar to the way we can use pre-selected filters: How to make certain Event Calendar filter selected by default - Elfsight Help Center

  • Using the JS code, I can only utilize that for one. Is there a more dynamic way to get tags to be selected across different pages?

Thanks!

@Matthew7 Just a couple of questions to clear things up:

  1. Do you want to use the same tag filter as a default option for all pages?

  2. Do you use the same or different layouts on these pages?