summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/related_assets_controller.rb18
-rw-r--r--public/javascripts/admin_search.js133
-rw-r--r--test/controllers/related_assets_controller_test.rb16
3 files changed, 91 insertions, 76 deletions
diff --git a/app/controllers/related_assets_controller.rb b/app/controllers/related_assets_controller.rb
index 906c02c..ae37d2f 100644
--- a/app/controllers/related_assets_controller.rb
+++ b/app/controllers/related_assets_controller.rb
@@ -3,17 +3,15 @@ class RelatedAssetsController < ApplicationController
3 before_action :find_node 3 before_action :find_node
4 4
5 def search 5 def search
6 term = params[:q].to_s.strip 6 term = params[:search_term].to_s.strip
7 if term.blank?
8 render json: []
9 return
10 end
11
12 attached_ids = @node.editable_page.related_assets.pluck(:asset_id) 7 attached_ids = @node.editable_page.related_assets.pluck(:asset_id)
13 results = Asset.images 8 scope = Asset.images.where.not(id: attached_ids)
14 .where("name ILIKE ?", "%#{term}%") 9
15 .where.not(id: attached_ids) 10 results = if term.present?
16 .limit(10) 11 scope.where("name ILIKE ?", "%#{term}%").limit(10)
12 else
13 scope.order(created_at: :desc).limit(5)
14 end
17 15
18 render json: results.map { |a| 16 render json: results.map { |a|
19 { id: a.id, name: a.name, thumb_url: a.upload.url(:thumb) } 17 { id: a.id, name: a.name, thumb_url: a.upload.url(:thumb) }
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
124dashboard_search = { 135dashboard_search = {
diff --git a/test/controllers/related_assets_controller_test.rb b/test/controllers/related_assets_controller_test.rb
index a3082c2..7f6d487 100644
--- a/test/controllers/related_assets_controller_test.rb
+++ b/test/controllers/related_assets_controller_test.rb
@@ -11,7 +11,7 @@ class RelatedAssetsControllerTest < ActionController::TestCase
11 Asset.create!(:name => "biometrics-poster", :upload_content_type => "image/png") 11 Asset.create!(:name => "biometrics-poster", :upload_content_type => "image/png")
12 Asset.create!(:name => "chaostreff-flyer", :upload_content_type => "image/png") 12 Asset.create!(:name => "chaostreff-flyer", :upload_content_type => "image/png")
13 13
14 get :search, params: { :node_id => node.id, :q => "biometrics" } 14 get :search, params: { :node_id => node.id, :search_term => "biometrics" }
15 15
16 json = JSON.parse(response.body) 16 json = JSON.parse(response.body)
17 names = json.map { |a| a["name"] } 17 names = json.map { |a| a["name"] }
@@ -20,13 +20,19 @@ class RelatedAssetsControllerTest < ActionController::TestCase
20 assert_not_includes names, "chaostreff-flyer" 20 assert_not_includes names, "chaostreff-flyer"
21 end 21 end
22 22
23 test "search returns an empty list for a blank term" do 23 test "search with a blank term returns the most recently created assets" do
24 Asset.delete_all
25 RelatedAsset.delete_all
24 login_as :quentin 26 login_as :quentin
25 node = Node.root.children.create!(:slug => "related_assets_blank_search_test") 27 node = Node.root.children.create!(:slug => "related_assets_recent_test")
26 28
27 get :search, params: { :node_id => node.id, :q => "" } 29 Asset.create!(:name => "older-photo", :upload_content_type => "image/png", :created_at => 2.days.ago)
30 Asset.create!(:name => "newer-photo", :upload_content_type => "image/png", :created_at => 1.hour.ago)
28 31
29 assert_equal [], JSON.parse(response.body) 32 get :search, params: { :node_id => node.id, :search_term => "" }
33
34 json = JSON.parse(response.body)
35 assert_equal ["newer-photo", "older-photo"], json.map { |a| a["name"] }
30 end 36 end
31 37
32 test "create attaches an asset to the node's editable page" do 38 test "create attaches an asset to the node's editable page" do