diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-07-13 03:48:28 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-07-13 03:48:28 +0200 |
| commit | 5f56fa867f073d8d1aecbfbba4a340ea414cfb0d (patch) | |
| tree | 5f39c0386b2e5819289e916ccdb67b987c2131fa /public | |
| parent | 70653b681d10917b77dced08f577446ced7568f1 (diff) | |
Add loadOnFocus to initSearchPicker; asset search shows recent by default
initSearchPicker's input handler has always short-circuited on an empty
term -- correct for every existing picker, none of which want results
appearing before anything's typed. The asset picker does want exactly
that (show the last few uploaded images without requiring a search
first), so this adds an opt-in loadOnFocus option instead of changing
the shared default: it fires once when the input gains focus with no
term yet, reusing the same request/render path a real search uses
rather than a separate code path. The AJAX call itself was pulled out
into a named runSearch function so both triggers could share it without
duplicating the success/render logic.
RelatedAssetsController#search now treats a blank term as "show the
most recently created, unattached images" (limit 5) rather than
returning nothing, matching the new client behavior. A real search
term still returns up to 10 name-matched results, unchanged.
Diffstat (limited to 'public')
| -rw-r--r-- | public/javascripts/admin_search.js | 133 |
1 files changed, 72 insertions, 61 deletions
diff --git a/public/javascripts/admin_search.js b/public/javascripts/admin_search.js index 0c768a3..7604bb7 100644 --- a/public/javascripts/admin_search.js +++ b/public/javascripts/admin_search.js | |||
| @@ -42,18 +42,76 @@ function initSearchPicker(options) { | |||
| 42 | var inputSelector = options.inputSelector; | 42 | var inputSelector = options.inputSelector; |
| 43 | var resultsSelector = options.resultsSelector; | 43 | var resultsSelector = options.resultsSelector; |
| 44 | var url = options.url || ADMIN_MENU_SEARCH_URL; | 44 | var url = options.url || ADMIN_MENU_SEARCH_URL; |
| 45 | var onSelect = options.onSelect; // omit for a real-navigation search like admin_search | 45 | var onSelect = options.onSelect; |
| 46 | var isActive = options.isActive; // optional guard, checked before every search | 46 | var isActive = options.isActive; |
| 47 | var resultsHeaderHtml = options.resultsHeaderHtml; // optional, prepended only when results exist | 47 | var resultsHeaderHtml = options.resultsHeaderHtml; |
| 48 | var renderResults = options.renderResults; // optional, replaces the default per-node rendering entirely | 48 | var renderResults = options.renderResults; |
| 49 | var loadOnFocus = options.loadOnFocus; // optional, fires an initial search on focus with no term typed yet | ||
| 49 | var requestId = 0; | 50 | var requestId = 0; |
| 50 | var timeout; | 51 | var timeout; |
| 52 | var results = $(resultsSelector); | ||
| 53 | |||
| 54 | function runSearch(term) { | ||
| 55 | var thisRequest = ++requestId; | ||
| 56 | $.ajax({ | ||
| 57 | type: "GET", | ||
| 58 | url: url, | ||
| 59 | data: "search_term=" + term, | ||
| 60 | dataType: "json", | ||
| 61 | success: function(data) { | ||
| 62 | if (thisRequest !== requestId) return; | ||
| 63 | results.empty(); | ||
| 64 | |||
| 65 | var found; | ||
| 66 | if (renderResults) { | ||
| 67 | found = renderResults(data, results, resultsHeaderHtml); | ||
| 68 | } else { | ||
| 69 | if (data.length && resultsHeaderHtml) { | ||
| 70 | results.append(resultsHeaderHtml); | ||
| 71 | } | ||
| 72 | found = false; | ||
| 73 | for (var i = 0; i < data.length; i++) { | ||
| 74 | (function(node) { | ||
| 75 | var link; | ||
| 76 | if (onSelect) { | ||
| 77 | link = $( | ||
| 78 | "<p><a href='#'>" + node.title + | ||
| 79 | "<span class='result_path'>" + node.unique_name + "</span></a></p>" | ||
| 80 | ); | ||
| 81 | link.find("a").bind("click", function() { | ||
| 82 | onSelect(node); | ||
| 83 | results.slideUp(); | ||
| 84 | results.empty(); | ||
| 85 | return false; | ||
| 86 | }); | ||
| 87 | } else { | ||
| 88 | link = $( | ||
| 89 | "<p><a href='" + node.node_path + "'>" + node.title + | ||
| 90 | "<span class='result_path'>" + node.unique_name + "</span></a></p>" | ||
| 91 | ); | ||
| 92 | } | ||
| 93 | results.append(link); | ||
| 94 | found = true; | ||
| 95 | })(data[i]); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | if (found) { | ||
| 100 | results.slideDown(); | ||
| 101 | } else { | ||
| 102 | results.slideUp(); | ||
| 103 | } | ||
| 104 | }, | ||
| 105 | error: function(xhr, status, error) { | ||
| 106 | console.log("Ajax error:", status, error, xhr.status, xhr.responseText); | ||
| 107 | } | ||
| 108 | }); | ||
| 109 | } | ||
| 51 | 110 | ||
| 52 | $(inputSelector).bind("input", function() { | 111 | $(inputSelector).bind("input", function() { |
| 53 | if (isActive && !isActive()) return; | 112 | if (isActive && !isActive()) return; |
| 54 | 113 | ||
| 55 | var term = $(this).val(); | 114 | var term = $(this).val(); |
| 56 | var results = $(resultsSelector); | ||
| 57 | clearTimeout(timeout); | 115 | clearTimeout(timeout); |
| 58 | 116 | ||
| 59 | if (!term) { | 117 | if (!term) { |
| @@ -62,63 +120,16 @@ function initSearchPicker(options) { | |||
| 62 | return; | 120 | return; |
| 63 | } | 121 | } |
| 64 | 122 | ||
| 65 | timeout = setTimeout(function() { | 123 | timeout = setTimeout(function() { runSearch(term); }, 250); |
| 66 | var thisRequest = ++requestId; | ||
| 67 | $.ajax({ | ||
| 68 | type: "GET", | ||
| 69 | url: url, | ||
| 70 | data: "search_term=" + term, | ||
| 71 | dataType: "json", | ||
| 72 | success: function(data) { | ||
| 73 | if (thisRequest !== requestId) return; | ||
| 74 | results.empty(); | ||
| 75 | |||
| 76 | var found; | ||
| 77 | if (renderResults) { | ||
| 78 | found = renderResults(data, results, resultsHeaderHtml); | ||
| 79 | } else { | ||
| 80 | if (data.length && resultsHeaderHtml) { | ||
| 81 | results.append(resultsHeaderHtml); | ||
| 82 | } | ||
| 83 | found = false; | ||
| 84 | for (var i = 0; i < data.length; i++) { | ||
| 85 | (function(node) { | ||
| 86 | var link; | ||
| 87 | if (onSelect) { | ||
| 88 | link = $( | ||
| 89 | "<p><a href='#'>" + node.title + | ||
| 90 | "<span class='result_path'>" + node.unique_name + "</span></a></p>" | ||
| 91 | ); | ||
| 92 | link.find("a").bind("click", function() { | ||
| 93 | onSelect(node); | ||
| 94 | results.slideUp(); | ||
| 95 | results.empty(); | ||
| 96 | return false; | ||
| 97 | }); | ||
| 98 | } else { | ||
| 99 | link = $( | ||
| 100 | "<p><a href='" + node.node_path + "'>" + node.title + | ||
| 101 | "<span class='result_path'>" + node.unique_name + "</span></a></p>" | ||
| 102 | ); | ||
| 103 | } | ||
| 104 | results.append(link); | ||
| 105 | found = true; | ||
| 106 | })(data[i]); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | if (found) { | ||
| 111 | results.slideDown(); | ||
| 112 | } else { | ||
| 113 | results.slideUp(); | ||
| 114 | } | ||
| 115 | }, | ||
| 116 | error: function(xhr, status, error) { | ||
| 117 | console.log("Ajax error:", status, error, xhr.status, xhr.responseText); | ||
| 118 | } | ||
| 119 | }); | ||
| 120 | }, 250); | ||
| 121 | }); | 124 | }); |
| 125 | |||
| 126 | if (loadOnFocus) { | ||
| 127 | $(inputSelector).bind("focus", function() { | ||
| 128 | if (isActive && !isActive()) return; | ||
| 129 | if ($(this).val()) return; // the input handler above already covers a real term | ||
| 130 | runSearch(""); | ||
| 131 | }); | ||
| 132 | } | ||
| 122 | } | 133 | } |
| 123 | 134 | ||
| 124 | dashboard_search = { | 135 | dashboard_search = { |
