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
course_data = FileAttachment("assets/courses.csv").csv({typed: true})
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")
Code
// This updates the search to be "*" on pressing the "show all" button
{
  search_all;
  if (search_all > 0) {
    const search_element = viewof search_terms;
    search_element.value = "*";
    const reset_button = viewof reset_filters;
    reset_button.value += 1;
    reset_button.dispatchEvent(new Event("input", {bubbles: true}));
    search_element.dispatchEvent(new Event("input", {bubbles: true}));
  }
}
Code
// Performs filtering of the complete (`course_data`) data into the filtered (`course_filtered`) set.
course_filtered = {
  // 1: If the current search term is empty, and no filters are set, return featured courses
  if(
    !search_query &&
    !selected_audience &&
    (selected_resource_type ?? []).length === 0 &&
    (selected_time ?? []).length === 0 &&
    (selected_provider ?? []).length === 0
  ) {
    return course_data.filter(row => row.featured === "yes")
  }
  // 2: If the current search term is "*", then return everything:
  if (search_query === "*") {
    return course_data
  }
  // 3: There is another search term, so filter on it:
  return course_data.filter(row =>
      (!selected_audience || !row.audience || row.audience.includes(selected_audience)) &&
      ((selected_resource_type ?? []).length === 0 || (selected_resource_type ?? []).includes(row.learningResourceType)) &&
      ((selected_time ?? []).length === 0 || (selected_time ?? []).includes(row.approxTimeRequired)) &&
      ((selected_provider ?? []).length === 0 || (selected_provider ?? []).includes(row.provider)) &&
      (!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 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.id = "toggle-filters";
  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
viewof selected_audience = Inputs.select(audiences, {label: "Audience", value: null})
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 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