diff --git a/_includes/current-projects-check.html b/_includes/current-projects-check.html
new file mode 100644
index 0000000000..171e41c8fc
--- /dev/null
+++ b/_includes/current-projects-check.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Have an idea?
+ Submit your pitch
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/js/current-projects-check.js b/assets/js/current-projects-check.js
new file mode 100644
index 0000000000..cea70578cb
--- /dev/null
+++ b/assets/js/current-projects-check.js
@@ -0,0 +1,555 @@
+---
+
+---
+
+// Do All Dom Manipulation After The DOM content is full loaded
+document.addEventListener("DOMContentLoaded",function(){
+ (function main(){
+
+ const projectData = retrieveProjectDataFromCollection();
+
+ const sortedProjectData = projectDataSorter(projectData);
+
+ // Insert Project Card Into The Dom
+ for(const item of sortedProjectData){
+ document.querySelector('.project-list').insertAdjacentHTML('beforeend', projectCardComponent(item.project))
+ }
+
+ // create filter dictionary from sorted project data
+ let filters = createFilter(sortedProjectData);
+
+ // Insert Checkbox Filter Into The Dom
+ for(let [filterName,filterValue] of Object.entries(filters)){
+ // Add displayed filter title, resolves issue of "program areas" not being valid html attribute name due to spacing
+ let filterTitle = "";
+ if(filterName === "programs"){
+ filterTitle = "program areas"
+ } else if(filterName === 'technologies') {
+ filterTitle = 'technologies'
+ filterValue.sort((a,b)=> {
+ a = a.toLowerCase()
+ b = b.toLowerCase()
+ if(a < b) return -1;
+ if(a > b) return 1;
+ return 0;
+ })
+ } else {
+ filterTitle = filterName
+ }
+ document.querySelector('.filter-list').insertAdjacentHTML( 'beforeend', dropDownFilterComponent( filterName,filterValue,filterTitle) );
+ }
+
+ document.querySelectorAll("input[type='checkbox']").forEach(item =>{
+ item.addEventListener('change', checkBoxEventHandler)
+ });
+
+ // Update UI on page load based on url parameters
+ updateUI()
+
+ // Event listener to Update UI on url parameter change
+ window.addEventListener('locationchange',updateUI)
+
+})()
+
+})
+
+/**
+ * Retieves project data from jekyll _projects collection using liquid and transforms it into a javascript object
+ * The function returns a javascript array of objects representing all the projects under the _projects directory
+*/
+function retrieveProjectDataFromCollection(){
+ // { "project": {"id":"/projects/311-data","relative_path":"_projects/311-data.md","excerpt"
+ {% assign projects = site.data.external.github-data %}
+ {% assign visible_projects = site.projects | where: "visible", "true" %}
+ let projects = JSON.parse(decodeURIComponent("{{ projects | jsonify | uri_escape }}"));
+ // const scriptTag = document.getElementById("projectScript");
+ // const projectId = scriptTag.getAttribute("projectId");
+ // Search for correct project
+ let projectLanguagesArr = [];
+ projects.forEach(project=> {
+ if(project.languages){
+ const projectLanguages = {
+ id: project.id,
+ languages: project.languages
+ };
+ projectLanguagesArr.push(projectLanguages);
+ }
+ })
+
+ let projectData = [{%- for project in visible_projects -%}
+ {
+ "project": {
+ 'id': "{{project.id | default: 0}}",
+ 'identification': {{project.identification | default: 0}},
+ "status": "{{ project.status }}"
+ {%- if project.image -%},
+ "image": '{{ project.image }}'
+ {%- endif -%}
+ {%- if project.alt -%},
+ "alt": `{{ project.alt }}`
+ {%- endif -%}
+ {%- if project.title -%},
+ "title": `{{ project.title }}`
+ {%- endif -%}
+ {%- if project.description -%},
+ "description": `{{ project.description }}`
+ {%- endif -%}
+ {%- if project.partner -%},
+ "partner": `{{ project.partner }}`
+ {%- endif -%}
+ {%- if project.tools -%},
+ "tools": `{{ project.tools }}`
+ {%- endif -%}
+ {%- if project.looking -%},
+ "looking": {{ project.looking | jsonify }}
+ {%- endif -%}
+ {%- if project.links -%},
+ "links": {{ project.links | jsonify }}
+ {%- endif -%}
+ {%- if project.technologies -%},
+ "technologies": {{ project.technologies | jsonify }}
+ {%- endif -%}
+ {%- if project.program-area -%},
+ "programAreas": {{ project.program-area | jsonify }}
+ {%- endif -%}
+ {%- if project.languages -%},
+ "languages": {{ project.languages }}
+ {%- endif -%}
+ }
+ }{%- unless forloop.last -%}, {% endunless %}
+ {%- endfor -%}]
+ projectData.forEach((data,i) => {
+ const { project } = data;
+ const matchingProject = projectLanguagesArr.find(x=> x.id === project.identification);
+ if(matchingProject) {
+ project.languages = matchingProject.languages
+ }
+ })
+ return projectData;
+}
+
+/**
+ * Given an input hehe of a project data array object as returned by the function `retrieveProjectDataFromCollection()`, this
+ * function sorts the project twice.
+ * 1. It sort all projects in the array alphabetically on their `status` value
+ * 2. It sort all project by title for each status type
+*/
+function projectDataSorter(projectdata){
+
+ const statusList = ["Active","Completed","On Hold"]
+ const sortedProjectContainer = [];
+
+ // Sort Project data by status alphabetically
+ projectdata.sort( (a,b) => (a.project.status > b.project.status) ? 1 : -1)
+
+ // Sort Project Data by title for each status type
+ for(const status of statusList){
+ let arr = projectdata.filter(function(item){
+ return item.project.status === status
+ }).sort( (a,b) => (a.project.title > b.project.title) ? 1 : -1);
+ sortedProjectContainer.push(...arr);
+ }
+
+ return sortedProjectContainer;
+}
+
+/**
+ * Given an array of project object as returned by ``retrieveProjectDataFromCollection()``
+ * Returns a filter object -> {filter_type1:[filter_value1,filter_value2], filter_type2:[filter_value1,filter_value2], ... }
+*/
+function createFilter(sortedProjectData){
+ return {
+ // 'looking': [ ... new Set( (sortedProjectData.map(item => item.project.looking ? item.project.looking.map(item => item.category) : '')).flat() ) ].filter(v=>v!='').sort(),
+ // ^ See issue #1997 for more info on why this is commented out
+
+ 'technologies': [...new Set(sortedProjectData.map(item => (item.project.technologies?.length > 0) ? [item.project.technologies].flat() : '').flat() ) ].filter(v=>v!='').sort(),
+ 'languages': [...new Set(sortedProjectData.map(item => (item.project.languages?.length > 0) ? [item.project.languages].flat() : '').flat() ) ].filter(v=>v!='').sort(),
+
+ }
+}
+
+/**
+ * Update the history state and the url parameters on checkbox changes
+*/
+function checkBoxEventHandler(){
+ let incomingFilterData = document.querySelectorAll("input[type='checkbox']");
+ let queryObj = {}
+ incomingFilterData.forEach(input => {
+ if(input.checked){
+ if(input.name in queryObj){
+ queryObj[input.name].push(input.id);
+ }
+ else{
+ queryObj[input.name] = [];
+ queryObj[input.name].push(input.id)
+ }
+ }
+ })
+
+ let queryString = Object.keys(queryObj).map(key => key + '=' + queryObj[key]).join('&').replaceAll(" ","+");
+
+ //Update URL parameters
+ window.history.replaceState(null, '', `?${queryString}`);
+}
+
+/**
+ * The updateUI function updates the ui based on the url parameters during the following events
+ * 1. URL parameter changes
+ * 2. Page is reloaded/refreshed
+*/
+function updateUI(){
+
+ //Get filter parameters from the url
+ const filterParams = Object.fromEntries(new URLSearchParams(window.location.search));
+
+ //Transform filterparam object values to arrays
+ Object.entries(filterParams).forEach( ([key,value]) => filterParams[key] = value.split(',') )
+
+ //If there are no entries in URL display clear all filter tags and display all cards
+ if(Object.keys(filterParams).length === 0 ){noUrlParameterUpdate(filterParams) };
+
+ // Filters listed in the url parameter are checked or unchecked based on filter params
+ updateCheckBoxState(filterParams);
+
+ // Update category counter based on filter params
+ updateCategoryCounter(filterParams)
+
+ // Card is shown/hidden based on filters listed in the url parameter
+ updateProjectCardDisplayState(filterParams);
+
+ // The function updates the frequency of each filter based on the cards that are displayed on the page.
+ updateFilterFrequency(filterParams);
+
+ // Updates the filter tags show on the page based on the url paramenter
+ updateFilterTagDisplayState(filterParams);
+
+ // Add onclick event handlers to filter tag buttons and a clear all button if filter-tag-button exists in the dom
+ attachEventListenerToFilterTags()
+
+}
+
+ /**
+ * Computes and return the frequency of each checkbox filter that are currently present in on the displayed cards on the page
+ */
+function updateFilterFrequency(){
+
+ const onPageFilters = []
+ // Push the filters present on the displayed cards on the page into an array.
+ document.querySelectorAll('.project-card[style*="display: list-item;"]').forEach(card => {
+ for(const [key,value] of Object.entries(card.dataset)){
+ value.split(",").map(item => {
+ onPageFilters.push(`${key}_${item}`)
+ });
+ }
+ });
+
+ const allFilters = []
+
+ document.querySelectorAll('input[type=checkbox]').forEach(checkbox => {
+ allFilters.push(`${checkbox.name}_${checkbox.id}`)
+ })
+
+ // Convert a 1 dimensional array into a key,value object. Where the array item becomes the key and the value is defaulted to 0
+ let filterFrequencyObject = allFilters.reduce((acc,curr)=> (acc[curr]=0,acc),{});
+
+
+ // Update values on the filterFrquencyObject if item in onPageFilter array exist as a key in this object.
+ for(const item of onPageFilters){
+ if(item in filterFrequencyObject){
+ filterFrequencyObject[item] += 1;
+ }
+ }
+
+ for(const [key,value] of Object.entries(filterFrequencyObject)){
+ document.querySelector(`label[for="${key.split("_")[1]}"]`).lastElementChild.innerHTML = ` (${value})`;
+ }
+
+}
+
+ /**
+ * Filters listed in the url parameter are checked or unchecked based on filter params
+ */
+function updateCheckBoxState(filterParams){
+ document.querySelectorAll("input[type='checkbox']").forEach(checkBox =>{
+ if(checkBox.name in filterParams){
+ let args = filterParams[checkBox.name]
+ args.includes(checkBox.id) ? checkBox.checked = true : checkBox.checked = false;
+ }
+ })
+}
+
+ /**
+ * Update category counter based on filter params
+ */
+function updateCategoryCounter(filterParams){
+ let container = []
+ for(const [key,value] of Object.entries(filterParams)){
+ container.push([`counter_${key}`,value.length]);
+ }
+
+ for(const [key,value] of container){
+ document.querySelector(`#${key}`).innerHTML = ` (${value})`;
+ }
+}
+ /**
+ * Card is shown/hidden based on filters listed in the url parameter
+ */
+function updateProjectCardDisplayState(filterParams){
+ document.querySelectorAll('.project-card').forEach(projectCard => {
+ const projectCardObj = {};
+ for(const key in filterParams){
+ projectCardObj[key] = projectCard.dataset[key].split(",");
+ }
+ const cardsToHideContainer = [];
+ for(const [key,value] of Object.entries(filterParams)){
+ let inUrl = value;
+ let inCard = projectCardObj[key];
+ if( ( inCard.filter(x => inUrl.includes(x)) ).length == 0 ){
+ cardsToHideContainer.push([key,projectCard.id]);
+ }
+ else{
+ projectCard.style.display = 'list-item' ;
+ }
+
+ }
+ cardsToHideContainer.map(item => document.getElementById(`${item[1]}`).style.display = 'none');
+
+ });
+}
+
+ /**
+ * Updates the filter tags show on the page based on the url paramenter
+ */
+function updateFilterTagDisplayState(filterParams){
+ // Clear all filter tags
+ document.querySelectorAll('.filter-tag').forEach(filterTag => filterTag.parentNode.removeChild(filterTag) );
+
+ //Filter tags display hide logic
+ for(const [key,value] of Object.entries(filterParams)){
+ value.forEach(item =>{
+ document.querySelector('.filter-tag-container').insertAdjacentHTML('afterbegin', filterTagComponent(key,item ) );
+
+ })
+
+ }
+}
+
+ /**
+ * Add onclick event handlers to filter tag buttons and a clear all button if filter-tag-button exists in the dom
+ */
+function attachEventListenerToFilterTags(){
+ if(document.querySelectorAll('.filter-tag').length > 0){
+
+ // Attach event handlers to button
+ document.querySelectorAll('.filter-tag').forEach(button => {
+ button.addEventListener('click',filterTagOnClickEventHandler)
+ })
+
+ // If there exist a filter-tag button on the page add a clear all button after the last filter tag button
+ if(!document.querySelector('.clear-filter-tags')){
+ document.querySelector('.filter-tag:last-of-type').insertAdjacentHTML('afterend',`Clear All `);
+
+ //Attach an event handler to the clear all button
+ document.querySelector('.clear-filter-tags').addEventListener('click',clearAllEventHandler);
+ }
+ }
+}
+
+/**
+ * If there are no url parameter
+ * 1. Display all cards
+ * 2. Clear all checkboxes
+ * 3. If there are filter tags, clear all filter tags
+ * 4. If there is a clear all, button remove Clear All Button
+*/
+function noUrlParameterUpdate(){
+ // Display all cards
+ document.querySelectorAll('.project-card').forEach(projectCard => { projectCard.style.display = 'list-item' });
+
+ // Clear all checkboxes
+ document.querySelectorAll("input[type='checkbox']").forEach(checkBox => {checkBox.checked = false});
+
+ // Clear all number of checkbox counters
+ document.querySelectorAll('.number-of-checked-boxes').forEach(checkBoxCounter => {checkBoxCounter.innerHTML = ''} );
+
+ // Clear all filter tags
+ document.querySelectorAll('.filter-tag') && document.querySelectorAll('.filter-tag').forEach(filterTag => filterTag.remove() );
+
+ // Remove Clear All Button
+ document.querySelector('.clear-filter-tags') && document.querySelector('.clear-filter-tags').remove();
+ return;
+}
+
+/**
+ * Filter Tag Button On Click Event Handler
+ * The function removes key:value from url parameter based on the filter-tag button clicked on
+*/
+function filterTagOnClickEventHandler(){
+
+ //Get filter parameters from the url
+ const filterParams = Object.fromEntries(new URLSearchParams(window.location.search));
+
+ //Transform filterparam object values to arrays
+ Object.entries(filterParams).forEach( ([key,value]) => filterParams[key] = value.split(',') )
+
+
+
+ let buttonClickedData = Object.fromEntries([ this.dataset.filter.split(",") ])
+
+ for(const [button_filtername,button_filtervalue] of Object.entries(buttonClickedData)){
+ if(filterParams[button_filtername].includes(button_filtervalue) ){
+ filterParams[button_filtername].splice( filterParams[button_filtername].indexOf(button_filtervalue), 1);
+ filterParams[button_filtername].length == 0 && delete filterParams[button_filtername];
+ }
+ }
+
+ // Prepare Query String
+ let queryString = Object.keys(filterParams).map(key => key + '=' + filterParams[key]).join('&').replaceAll(" ","+");
+
+ //Update URL parameters
+ window.history.replaceState(null, '', `?${queryString}`);
+}
+
+/**
+ * Clear All Button Event Handler
+ * The function clears all URL parmeter by setting the history to '/'
+*/
+function clearAllEventHandler(){
+ //Update URL parameters
+ window.history.replaceState(null, '', '/');
+}
+
+/**
+ * Takes a single project object and returns the html string representing the project card
+*/
+function projectCardComponent(project){
+return `
+
+
+
+
+
+
+
+
+
+
+
+
${ project.status }
+
+
+
${ project.title }
+
+
${ project.description }
+
+
+
Links:
+ ${project.links.map(item => `
${ item.name } `).join(", ")}
+
+
+ ${project.partner ?
+ `
+
+ Partner:
+ ${ project.partner }
+
+ `:""
+ }
+
+ ${project.tools ?
+ `
+
+ Tools:
+ ${ project.tools }
+
+ `:""
+ }
+
+ ${project.looking ? "" : ""
+ // `
+ //
+ //
Looking for:
+ // ${project.looking.map( role => `
${ role.skill }
`).join(", ")}
+ //
+ // `:""
+ // ^ See issue #1997 for more info on why this is commented out
+ }
+
+ ${project.languages?.length > 0 ?
+ `
+
+
Languages:
+ ${project.languages.map(language => `
${ language }
`).join(", ")}
+
+ `: ""
+ }
+
+ ${project.technologies ?
+ `
+
+
Technologies:
+ ${project.technologies.map(tech => `
${ tech }
`).join(", ")}
+
+ `:""
+ }
+
+ ${project.programAreas ?
+ `
+
+
Program Areas:
+ ${project.programAreas.map(programArea => `
${ programArea }
`).join(", ")}
+
+ `:""
+ }
+
+
+ `
+}
+
+/**
+ * Takes a filter category name and array of filter stirings and returns the html string representing a single filter component
+*/
+function dropDownFilterComponent(categoryName,filterArray,filterTitle){
+ return `
+
+
+ ${filterTitle}
+
+ ∟
+
+
+
+ `
+}
+
+/**
+ * Takes a name of a checkbox filter and the value of the check boxed filter
+ * and creates a html string representing a button
+*/
+
+function filterTagComponent(filterName,filterValue){
+ return `
+
+ ${filterName === "looking" ? "Role" : filterName}: ${filterValue}
+
+
`
+}
diff --git a/pages/projects-check.html b/pages/projects-check.html
new file mode 100644
index 0000000000..4af714bc5a
--- /dev/null
+++ b/pages/projects-check.html
@@ -0,0 +1,23 @@
+---
+layout: default
+title: Projects-Check
+permalink: /projects-check/
+---
+
+
+
+
+
+{%- include current-projects-check.html -%}