From 046d888de71a0de67b31764060f096d03a824f24 Mon Sep 17 00:00:00 2001 From: andrew nimmo Date: Mon, 17 Jun 2024 19:15:46 -0700 Subject: [PATCH] Refactor autocompleters to use query params --- app/classes/auto_complete.rb | 32 ++++---- app/classes/auto_complete/for_location.rb | 4 +- .../auto_complete/for_location_containing.rb | 8 +- app/controllers/autocompleters_controller.rb | 18 ++--- app/helpers/forms_helper.rb | 2 +- .../controllers/autocompleter_controller.js | 77 ++++++++++--------- config/routes.rb | 2 +- .../autocompleters_controller_test.rb | 41 +++++----- test/models/auto_complete_test.rb | 10 +-- 9 files changed, 101 insertions(+), 93 deletions(-) diff --git a/app/classes/auto_complete.rb b/app/classes/auto_complete.rb index d1bbab7d1e..03466d110d 100644 --- a/app/classes/auto_complete.rb +++ b/app/classes/auto_complete.rb @@ -9,10 +9,10 @@ # ################################################################################ -PUNCTUATION = '[ -\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]' - class AutoComplete - attr_accessor :string, :matches + attr_accessor :string, :matches, :all + + PUNCTUATION = '[ -\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]' def limit 1000 @@ -24,14 +24,18 @@ def self.subclass(type) raise("Invalid auto-complete type: #{type.inspect}") end - def initialize(string, _params = {}) - self.string = string.to_s.strip_squeeze + def initialize(params = {}) + self.string = params[:string].to_s.strip_squeeze + self.all = params[:all].present? end def matching_strings + # just use the first letter of the string to define the matches self.matches = rough_matches(string[0]) clean_matches - minimal_string = refine_matches + return matches if all + + minimal_string = refine_matches # defined in subclass truncate_matches [minimal_string] + matches # [[minimal_string, nil]] + matches @@ -39,14 +43,6 @@ def matching_strings private - def truncate_matches - return unless matches.length > limit - - matches.slice!(limit..-1) - matches.push("...") - # matches.push(["...", nil]) - end - def clean_matches matches.map! do |str| str.sub(/\s*[\r\n]\s*.*/m, "").sub(/\A\s+/, "").sub(/\s+\Z/, "") @@ -57,4 +53,12 @@ def clean_matches # end matches.uniq! end + + def truncate_matches + return unless matches.length > limit + + matches.slice!(limit..-1) + matches.push("...") + # matches.push(["...", nil]) + end end diff --git a/app/classes/auto_complete/for_location.rb b/app/classes/auto_complete/for_location.rb index ce08aa6c52..717765e0c8 100644 --- a/app/classes/auto_complete/for_location.rb +++ b/app/classes/auto_complete/for_location.rb @@ -10,8 +10,8 @@ class AutoComplete::ForLocation < AutoComplete::ByWord attr_accessor :reverse - def initialize(string, params) - super(string, params) + def initialize(params) + super self.reverse = (params[:format] == "scientific") end diff --git a/app/classes/auto_complete/for_location_containing.rb b/app/classes/auto_complete/for_location_containing.rb index b42b4ec1b9..fbda674c36 100644 --- a/app/classes/auto_complete/for_location_containing.rb +++ b/app/classes/auto_complete/for_location_containing.rb @@ -2,12 +2,12 @@ # Autocompleter for location names that encompass a given lat/lng. class AutoComplete::ForLocationContaining < AutoComplete::ByWord - attr_accessor :reverse + attr_accessor :reverse, :lat, :lng - include Mappable::BoxMethods + # include Mappable::BoxMethods - def initialize(string, params) - super(string, params) + def initialize(params) + super self.reverse = (params[:format] == "scientific") self.lat = params[:lat] self.lng = params[:lng] diff --git a/app/controllers/autocompleters_controller.rb b/app/controllers/autocompleters_controller.rb index 41b43f1483..7022c6015a 100644 --- a/app/controllers/autocompleters_controller.rb +++ b/app/controllers/autocompleters_controller.rb @@ -16,32 +16,32 @@ class AutocompletersController < ApplicationController # could add record ids. The first line of the returned results is the actual # (minimal) string used to match the records. If it had to truncate the list # of results, the last string is "...". - # type:: Type of string. - # id:: String user has entered. + # type:: Type of string. + # params[:string]:: String user has entered. def new @user = User.current = session_user - string = CGI.unescape(@id).strip_squeeze - if string.blank? + if params[:string].blank? && params[:all].blank? render(json: ActiveSupport::JSON.encode([])) else - render(json: ActiveSupport::JSON.encode(auto_complete_results(string))) + render(json: ActiveSupport::JSON.encode(auto_complete_results)) end end private - def auto_complete_results(string) + def auto_complete_results case @type - when "location" + when "location", "location_containing" params[:format] = @user&.location_format when "herbarium" params[:user_id] = @user&.id end - ::AutoComplete.subclass(@type).new(string, params).matching_strings + ::AutoComplete.subclass(@type).new(params).matching_strings end + # callback on `around_action` def catch_ajax_errors prepare_parameters yield @@ -53,8 +53,6 @@ def catch_ajax_errors def prepare_parameters @type = params[:type].to_s - @id = params[:id].to_s - @value = params[:value].to_s end def backtrace(exception) diff --git a/app/helpers/forms_helper.rb b/app/helpers/forms_helper.rb index 4bf834df41..6e57d8cace 100644 --- a/app/helpers/forms_helper.rb +++ b/app/helpers/forms_helper.rb @@ -165,7 +165,7 @@ def text_field_with_label(**args) # our autocompleter: use the browser standard autocomplete att "one-time-code" def autocompleter_field(**args) autocompleter_args = { - placeholder: :start_typing.l, autocomplete: "one-time-code", + placeholder: :start_typing.l, autocomplete: "off", data: { controller: :autocompleter, autocompleter_target: "input", autocomplete: args[:autocomplete], separator: args[:separator] } }.deep_merge(args.except(:autocomplete, :separator, :textarea)) diff --git a/app/javascript/controllers/autocompleter_controller.js b/app/javascript/controllers/autocompleter_controller.js index 264e346a47..0745bd36a6 100644 --- a/app/javascript/controllers/autocompleter_controller.js +++ b/app/javascript/controllers/autocompleter_controller.js @@ -14,7 +14,7 @@ const DEFAULT_OPTS = { // N = etc. COLLAPSE: 0, // where to request primer from - AJAX_URL: null, + AJAX_URL: "/autocompleters/new/", // how long to wait before sending AJAX request (seconds) REFRESH_DELAY: 0.10, // how long to wait before hiding pulldown (seconds) @@ -28,7 +28,7 @@ const DEFAULT_OPTS = { // amount to move cursor on page up and down PAGE_SIZE: 10, // max length of string to send via AJAX - MAX_REQUEST_LINK: 50, + MAX_STRING_LENGTH: 50, // Sub-match: starts finding new matches for the string *after the separator* // allowed separators (e.g. " OR ") SEPARATOR: null, @@ -45,38 +45,29 @@ const DEFAULT_OPTS = { // Allowed types of autocompleter. Sets some DEFAULT_OPTS from type const AUTOCOMPLETER_TYPES = { clade: { - AJAX_URL: "/autocompleters/new/clade/@", }, herbarium: { // params[:user_id] handled in controller - AJAX_URL: "/autocompleters/new/herbarium/@", UNORDERED: true }, location: { // params[:format] handled in controller - AJAX_URL: "/autocompleters/new/location/@", UNORDERED: true }, location_containing: { // params encoded from dataset - AJAX_URL: "/autocompleters/new/location_containing/@", ACT_LIKE_SELECT: true }, name: { - AJAX_URL: "/autocompleters/new/name/@", COLLAPSE: 1 }, project: { - AJAX_URL: "/autocompleters/new/project/@", UNORDERED: true }, region: { - AJAX_URL: "/autocompleters/new/location/@", UNORDERED: true }, species_list: { - AJAX_URL: "/autocompleters/new/species_list/@", UNORDERED: true }, user: { - AJAX_URL: "/autocompleters/new/user/@", UNORDERED: true } } @@ -108,6 +99,8 @@ const INTERNAL_OPTS = { // Connects to data-controller="autocomplete" export default class extends Controller { + // The select target is not the input element, but a