Logo for the UKRI Digital Research Skills Catalyst Logo for the UKRI Digital Research Skills Catalyst
  • Home
  • Consult an Expert
  • News & Events
  • About
  • Contact

Welcome to the UKRI Digital Research Skills Catalyst

A national hub for researcher and innovator development.

Boost your digital research skills, accelerate your research impact—discover 80+ learning resources, events, and expert-led training in one central hub with the UKRI Digital Research Skills Catalyst. Find out more here.

Code
# This cell loads the CSV data directly from the Google sheet via its published CSV-specific URL.
# We need to load in Python, as OJS doesn't have a caching system.
# Once loaded, we "publish" the variable globally to OJS.
import pandas as pd
course_data_cached = pd.read_csv("https://docs.google.com/spreadsheets/d/e/2PACX-1vRSrvsfFfVwokza_WP9JIzd4Wfg6OKPBJcwelLTqYn1SgigZXnfcU6_apN5gWTMF79n4CRQFNOJ5w6M/pub?gid=1012757406&single=true&output=csv")
ojs_define(course_data_cached = course_data_cached)
Code
// This cell transposes the data presented from Python, as numpy & OJS expect data in opposite order
course_data = transpose(course_data_cached)
Code
function csv_column_set(ds, col, inject_null) {
  let all_values = ds.flatMap(d =>
    d[col]
      ? d[col].split(",").map(s => s.trim())
      : []
  );
  all_values = Array.from(new Set(all_values)).sort()
  if(inject_null) {
    all_values.unshift(null)
  }
  return all_values
}
audiences = csv_column_set(course_data, "audience", true);
access_modes = csv_column_set(course_data, "accessMode", false);
// education_levels = csv_column_set(course_data, "educationalLevel", false);
resource_types = csv_column_set(course_data, "learningResourceType", true);
providers = csv_column_set(course_data, "provider", true);
times = csv_column_set(course_data, "approxTimeRequired", true);
keywords = csv_column_set(course_data, "keywords", false);
Code
// Trim the search query
search_query = (search_terms ?? "").toLowerCase().trim()
Code
viewof search_terms = Inputs.text({placeholder: "Search Training Resources..."})
// viewof search_all = Inputs.button("Show All")
// viewof show_filters = Inputs.button("Show Filters")

html`
  <div style="
      display: flex; 
      flex-direction: row; 
      gap: 10px; 
      align-items: center; 
      width: 100%;
      margin-bottom: 20px;
  ">
  
    <div style="flex-grow: 1;">
      <input type="search" placeholder="Search courses..." style="width: 100%; padding: 8px;">
    </div>

    <button style="
        padding: 4px 10px; 
        font-size: 0.85rem; 
        height: fit-content;
        cursor: pointer;
    ">
      Reset
    </button>

    <button style="
        padding: 4px 10px; 
        font-size: 0.85rem; 
        height: fit-content;
        cursor: pointer;
    ">
      Filter
    </button>
    
  </div>
`
Code
viewof selected_audience = Inputs.select(audiences, {label: "Audience", value: null})
// viewof selected_educational_level = Inputs.checkbox(education_levels, {label: "Level", value: education_levels})
viewof selected_resource_type = Inputs.select(resource_types, {label: "Type", value: null})
viewof selected_time = Inputs.select(times, {label: "Time Required", value: null})
viewof selected_provider = Inputs.select(providers, {label: "Provider", value: null})
viewof reset_filters = Inputs.button("reset filters")
Code
// This cell implements a show/hide button for extra filters.
// This works via a JavaScript function that finds and toggles the div display style.
{
  const toggleLink = document.createElement("a");
  toggleLink.href = "#";
  toggleLink.textContent = "show filters";
  toggleLink.addEventListener("click", (event) => {
    event.preventDefault();
    const filter_div = document.getElementById("search-filters");
    if (filter_div.style.display === "none") {
      toggleLink.textContent = "hide filters";
      filter_div.style.display = "block"
    } else {
      toggleLink.textContent = "show filters";
      filter_div.style.display = "none"
    }
  });
  return toggleLink;
}
Code
// Performs filtering of the complete (`course_data`) data into the filtered (`course_filtered`) set.
// If there is no search, and nothing has been selected in the filters then we return everything.
course_filtered =
  (
    (!selected_audience) &&
    // (selected_educational_level ?? []).length === 0 &&
    (selected_resource_type ?? []).length === 0 &&
    (selected_time ?? []).length === 0 &&
    (selected_provider ?? []).length === 0 &&
    (!search_query)
  )
  ? course_data.filter(row => row.featured === "yes")
  : course_data.filter(row =>
      // audience
      (!selected_audience || !row.audience || row.audience.includes(selected_audience)) &&
      // educational level
      // ((selected_educational_level ?? []).length === 0 || (selected_educational_level ?? []).includes(row.educationalLevel)) &&
      // resource type
      ((selected_resource_type ?? []).length === 0 || (selected_resource_type ?? []).includes(row.learningResourceType)) &&
      // time
      ((selected_time ?? []).length === 0 || (selected_time ?? []).includes(row.approxTimeRequired)) &&
      // provider
      ((selected_provider ?? []).length === 0 || (selected_provider ?? []).includes(row.provider)) &&
      // search
      (!search_query ||
        row.headline.toLowerCase().includes(search_query) ||
        (row.description  && row.description.includes(search_query)) ||
        (row.projectFunding  && row.projectFunding.includes(search_query)) ||
        (row.identifier  && row.identifier.includes(search_query))
      )
    )
Code
// This cell iterates through the the filtered course results, and builds the displayed output.
// If there are no selected results, we show a placeholder <div>.
// All filtered courses are displayed, sorted first by `featured` status then in the CSV file order.
{
  const search_results = document.getElementById("primary-search-results");
  const empty_results = document.getElementById("empty-primary-search-results");
  if (course_filtered.length === 0) {
    return html`
      <div class="course-data-empty">
        <p>No results to display</p>
      </div>
    `
  } else {
    return html`
      ${course_filtered.slice().sort((a, b) => {
        if(a.featured === "yes" && b.featured === "no") return -1;
        if(a.featured === "no" && b.featured === "yes") return 1;
        return 0;
      }).map((row, row_index) => html `
        <div class="${row.featured === "yes" ? "course-data featured" : "course-data"}" id="course_data_${row_index}">
          <h3>
              <a href=${row.url}>${row.headline}</a>
          </h3>
          <p>${row.description}</p>
          <input type="checkbox" class="toggle-state" id="toggle-state-${row_index}">
          <label for="toggle-state-${row_index}" class="toggle-button"></label>
          <div class="course-data-extras" id="course_data_extras_${row_index}">
            <table>
              <tr>
                <th scope="row">Pre-requisites</th>
                <td class="pre-reqs">${row.competencyRequired?.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/).map(prereq => html`<p>${prereq.trim().replace(/^"|"$/g, "")}</p>`)}</td>
              </tr>
              <tr>
                <th scope="row">Teaches</th>
                <td class="teaches">${row.teaches?.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/).map(teach_p => html`<p>${teach_p.trim().replace(/^"|"$/g, "")}</p>`)}</td>
              </tr>
              <tr>
                <th scope="row">Time required</th>
                <td class="required">${row.TimeRequired}</td>
              </tr>
              <tr>
                <th scope="row">Credit</th>
                <td class="credit">${md`${row.creditText}`}</td>
              </tr>
              <tr>
                <th scope="row">Provider</th>
                <td>${row.projectFunding}</td>
              </tr>
            </table>
          </div>
        </div>
    `)}`
  }
}
Code
// This cell fires whenever the "reset filters" button is pressed.
{
  reset_filters; // This updates whenever the reset button fires.
  function resetInput(view, value) {
    const el = view.querySelector("select, input");
    if (!el) return;
    el.value = value;
    view.value = value;
    el.dispatchEvent(new Event("input", { bubbles: true }));
  }
  resetInput(viewof selected_audience, null);
  // resetInput(viewof selected_educational_level, []);
  resetInput(viewof selected_resource_type, null);
  resetInput(viewof selected_time, null);
  resetInput(viewof selected_provider, null);
}
Code
// This cell displays a table of all `course_data` for debugging only.
Inputs.table(course_data)
  • contact

  • accessibility

  • privacy

  • cookies

  • funding