From ac479214fdef8fd0b0a9037be9cf70a8babb2c74 Mon Sep 17 00:00:00 2001 From: Kat Date: Fri, 26 Jul 2024 08:24:44 +0100 Subject: [PATCH] Add search controller --- app/controllers/users_controller.rb | 7 +++ app/frontend/controllers/index.js | 3 ++ app/frontend/controllers/search_controller.js | 47 +++++++++++++++++++ app/frontend/modules/search.js | 35 ++++++++++++++ app/views/filters/_select_filter.html.erb | 2 +- app/views/filters/assigned_to.html.erb | 1 - config/routes.rb | 4 ++ 7 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 app/frontend/controllers/search_controller.js diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 3fe3b1813..0e9b04758 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -32,6 +32,13 @@ class UsersController < ApplicationController end end + def search + users = User.search_by_name(params["query"]).limit(20) + x = {} + users.each { |user| x[user.id] = { value: user.name, hint: user.email } } + render json: x.to_json + end + def resend_invite @user.send_confirmation_instructions flash[:notice] = "Invitation sent to #{@user.email}" diff --git a/app/frontend/controllers/index.js b/app/frontend/controllers/index.js index ece539d15..ef29b99ca 100644 --- a/app/frontend/controllers/index.js +++ b/app/frontend/controllers/index.js @@ -13,6 +13,8 @@ import GovukfrontendController from './govukfrontend_controller.js' import NumericQuestionController from './numeric_question_controller.js' +import SearchController from './search_controller.js' + import FilterLayoutController from './filter_layout_controller.js' application.register('accessible-autocomplete', AccessibleAutocompleteController) application.register('conditional-filter', ConditionalFilterController) @@ -20,3 +22,4 @@ application.register('conditional-question', ConditionalQuestionController) application.register('govukfrontend', GovukfrontendController) application.register('numeric-question', NumericQuestionController) application.register('filter-layout', FilterLayoutController) +application.register('search', SearchController) diff --git a/app/frontend/controllers/search_controller.js b/app/frontend/controllers/search_controller.js new file mode 100644 index 000000000..621582c4e --- /dev/null +++ b/app/frontend/controllers/search_controller.js @@ -0,0 +1,47 @@ +import { Controller } from '@hotwired/stimulus' +import accessibleAutocomplete from 'accessible-autocomplete' +import 'accessible-autocomplete/dist/accessible-autocomplete.min.css' +import { searchSuggestion, fetchAndPopulateSearchResults } from '../modules/search' + +let hints = []; +const populateHint = (results) => { + console.log("populating") + hints = results +} + +export default class extends Controller { + connect () { + const selectEl = this.element + const selectOptions = Array.from(selectEl.options).filter(function (option, index, arr) { return option.value !== '' }) + + const matches = /^(\w+)\[(\w+)\]$/.exec(selectEl.name) + const rawFieldName = matches ? `${matches[1]}[${matches[2]}_raw]` : '' + + accessibleAutocomplete.enhanceSelectElement({ + defaultValue: '', + selectElement: selectEl, + minLength: 1, + source: (query, populateResults) => { + fetchAndPopulateSearchResults(query, populateResults, populateHint) + }, + autoselect: true, + placeholder: 'Start typing to search', + templates: { suggestion: (value) => searchSuggestion(value, hints) }, + name: rawFieldName, + onConfirm: (val) => { + const selectedOption = [].filter.call( + selectOptions, + (option) => (getSearchableName(option)) === val + )[0] + if (selectedOption) selectedOption.selected = true + } + }) + + const parentElement = selectEl.parentElement + const inputElement = parentElement.querySelector('[role=combobox]') + + inputElement.addEventListener('input', () => { + selectOptions.forEach((option) => { option.selected = false }) + }) + } +} diff --git a/app/frontend/modules/search.js b/app/frontend/modules/search.js index 71944746e..6913f0372 100644 --- a/app/frontend/modules/search.js +++ b/app/frontend/modules/search.js @@ -117,6 +117,21 @@ export const suggestion = (value, options) => { } } +export const searchSuggestion = (value, hints) => { + try { + const result = hints[value.toString()] + if (result) { + const html = result.append ? `${result.value} ${result.append}` : `${result.value}` + return result.hint ? `${html}
${result.hint}
` : html + } else { + return 'No results found' + } + } catch (error) { + console.error('Error fetching user option:', error) + return value + } +} + export const enhanceOption = (option) => { return { text: option.text, @@ -128,6 +143,26 @@ export const enhanceOption = (option) => { } } + +export const fetchAndPopulateSearchResults = async (query, populateResults, populateHint) => { + if (/\S/.test(query)) { + let results = await fetchUserOptions(query) + populateResults(Object.keys(results)) + populateHint(results) + } +} + +export const fetchUserOptions = async (query) => { + try { + const response = await fetch(`/users/search?query=${encodeURIComponent(query)}`) + const results = await response.json() + return results + } catch (error) { + console.error('Error fetching user options:', error) + return [] + } +} + export const getSearchableName = (option) => { return option.getAttribute('data-hint') ? option.text + ' ' + option.getAttribute('data-hint') : option.text } diff --git a/app/views/filters/_select_filter.html.erb b/app/views/filters/_select_filter.html.erb index e31326347..501c3394c 100644 --- a/app/views/filters/_select_filter.html.erb +++ b/app/views/filters/_select_filter.html.erb @@ -1,6 +1,6 @@ <%= f.govuk_select(category.to_sym, label: { text: label, hidden: secondary }, - "data-controller": "accessible-autocomplete conditional-filter") do %> + "data-controller": "search conditional-filter") do %> <% collection.each do |answer| %>