Feature #34420 » 0001-Improve-query-columns-selector-by-replacing-the-comb.patch
Gemfile | ||
---|---|---|
4 | 4 |
gem 'bundler', '>= 1.12.0' |
5 | 5 | |
6 | 6 |
gem 'rails', '5.2.4.4' |
7 |
gem "view_component", require: "view_component/engine" |
|
7 | 8 |
gem 'sprockets', '~> 3.7.2' if RUBY_VERSION < '2.5' |
8 | 9 |
gem 'rouge', '~> 3.26.0' |
9 | 10 |
gem 'request_store', '~> 1.5.0' |
app/components/select_component.html.erb | ||
---|---|---|
1 |
<div class="drdn <%= @css_classes %>"> |
|
2 |
<span class="drdn-trigger"> |
|
3 |
<span class="selected"><%= @selected_items_count %></span> out of <span class="total"><%= @selected_items_count + @available_items_count %></span> selected |
|
4 |
</span> |
|
5 |
<div class="drdn-content"> |
|
6 |
<div class="quick-search"> |
|
7 |
<%= search_field_tag('q', '', :id => nil, :class => 'autocomplete', :autocomplete => 'off') %> |
|
8 |
</div> |
|
9 |
<ul class="drdn-items"> |
|
10 |
<% @items.each do |c| %> |
|
11 |
<li> |
|
12 |
<div class="item-content"> |
|
13 |
<%= check_box_tag "#{@name}[]", c[1], c.last, id: "#{@name}_#{c[1]}" %> |
|
14 |
<%= label_tag "#{@name}_#{c[1]}", c.first, :class => 'label-item'%> |
|
15 |
</div> |
|
16 |
<% if @sortable %> |
|
17 |
<div class="item-handler"> |
|
18 |
<span class="icon-only icon-sort-handle sort-handle"></span> |
|
19 |
</div> |
|
20 |
<% end %> |
|
21 |
</li> |
|
22 |
<% end %> |
|
23 |
</ul> |
|
24 |
</div> |
|
25 |
</div> |
|
26 | ||
27 |
<%= javascript_tag do %> |
|
28 |
enableAutocomplete(document.querySelector(".query-columns")); |
|
29 |
<% end %> |
app/components/select_component.rb | ||
---|---|---|
1 |
# frozen_string_literal: true |
|
2 | ||
3 |
class SelectComponent < ViewComponent::Base |
|
4 |
def initialize( |
|
5 |
items:, |
|
6 |
name:, |
|
7 |
css_class: |
|
8 |
) |
|
9 | ||
10 |
@selected_items_count = items.count{|v| v[2] == true } |
|
11 |
@available_items_count = items.count - @selected_items_count |
|
12 |
@sortable = true |
|
13 |
@name = name |
|
14 |
@items = items |
|
15 |
@css_classes = css_classes(css_class) |
|
16 |
end |
|
17 | ||
18 |
private |
|
19 | ||
20 |
def css_classes(css_class) |
|
21 |
s = +'' |
|
22 |
s << css_class |
|
23 |
s << " dropdown-sortable" if @sortable |
|
24 |
s |
|
25 |
end |
|
26 |
end |
app/helpers/queries_helper.rb | ||
---|---|---|
131 | 131 |
reject(&:frozen?).collect {|column| [column.caption, column.name]} |
132 | 132 |
end |
133 | 133 | |
134 |
def query_options_for_multiselect(query) |
|
135 |
selected_columns = query_selected_inline_columns_options(query).map{|s| s.append(true)} |
|
136 |
available_columns = query_available_inline_columns_options(query).map{|s| s.append(false)} |
|
137 |
columns = selected_columns + available_columns |
|
138 |
columns |
|
139 |
end |
|
140 | ||
134 | 141 |
def render_query_columns_selection(query, options={}) |
135 | 142 |
tag_name = (options[:name] || 'c') + '[]' |
136 | 143 |
render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name} |
app/views/queries/_columns.html.erb | ||
---|---|---|
1 |
<% tag_id = tag_name.gsub(/[\[\]]+/, '_').sub(/_+$/, '') %> |
|
2 |
<% available_tag_id = "available_#{tag_id}" %> |
|
3 |
<% selected_tag_id = "selected_#{tag_id}" %> |
|
4 | ||
5 |
<span class="query-columns"> |
|
6 |
<span> |
|
7 |
<%= label_tag available_tag_id, l(:description_available_columns) %> |
|
8 |
<%= select_tag 'available_columns', |
|
9 |
options_for_select(query_available_inline_columns_options(query)), |
|
10 |
:id => available_tag_id, |
|
11 |
:multiple => true, :size => 10, |
|
12 |
:ondblclick => "moveOptions(this.form.#{available_tag_id}, this.form.#{selected_tag_id});" %> |
|
13 |
</span> |
|
14 |
<span class="buttons"> |
|
15 |
<input type="button" value="→" class="move-right" |
|
16 |
onclick="moveOptions(this.form.<%= available_tag_id %>, this.form.<%= selected_tag_id %>);" /> |
|
17 |
<input type="button" value="←" class="move-left" |
|
18 |
onclick="moveOptions(this.form.<%= selected_tag_id %>, this.form.<%= available_tag_id %>);" /> |
|
19 |
</span> |
|
20 |
<span> |
|
21 |
<%= label_tag selected_tag_id, l(:description_selected_columns) %> |
|
22 |
<%= select_tag tag_name, |
|
23 |
options_for_select(query_selected_inline_columns_options(query)), |
|
24 |
:id => selected_tag_id, |
|
25 |
:multiple => true, :size => 10, |
|
26 |
:ondblclick => "moveOptions(this.form.#{selected_tag_id}, this.form.#{available_tag_id});" %> |
|
27 |
</span> |
|
28 |
<span class="buttons"> |
|
29 |
<input type="button" value="⇈" onclick="moveOptionTop(this.form.<%= selected_tag_id %>);" /> |
|
30 |
<input type="button" value="↑" onclick="moveOptionUp(this.form.<%= selected_tag_id %>);" /> |
|
31 |
<input type="button" value="↓" onclick="moveOptionDown(this.form.<%= selected_tag_id %>);" /> |
|
32 |
<input type="button" value="⇊" onclick="moveOptionBottom(this.form.<%= selected_tag_id %>);" /> |
|
33 |
</span> |
|
34 |
</span> |
|
35 | ||
36 |
<%= javascript_tag do %> |
|
37 |
$(document).ready(function(){ |
|
38 |
$('.query-columns').closest('form').submit(function(){ |
|
39 |
$('#<%= selected_tag_id %> option:not(:disabled)').prop('selected', true); |
|
40 |
}); |
|
41 |
}); |
|
42 |
<% end %> |
|
1 |
<%= render SelectComponent.new( |
|
2 |
items: query_options_for_multiselect(query), |
|
3 |
name: "c", |
|
4 |
css_class: "query-columns", |
|
5 |
) %> |
public/javascripts/application.js | ||
---|---|---|
1157 | 1157 |
tribute.attach(element); |
1158 | 1158 |
} |
1159 | 1159 | |
1160 |
function enableAutocomplete(element) { |
|
1161 |
const items = $(element).find('.drdn-items') |
|
1162 | ||
1163 |
if (element.classList.contains('dropdown-sortable')) { |
|
1164 |
items.sortable(); |
|
1165 |
// ToDo: Replace disableSelection with CSS user-select |
|
1166 |
items.disableSelection(); |
|
1167 |
} |
|
1168 | ||
1169 |
element.querySelector(".drdn-items").addEventListener('click', function(event) { |
|
1170 |
if (event.target.tagName.toLowerCase() === 'input') { |
|
1171 |
const checkedEl = element.querySelectorAll('.item-content input:checked').length |
|
1172 |
element.querySelector('.drdn-trigger span.selected').innerText = checkedEl |
|
1173 |
} |
|
1174 |
}) |
|
1175 | ||
1176 |
element.querySelector("input.autocomplete").addEventListener('input', function(event) { |
|
1177 |
filterValues(element, event) |
|
1178 |
}) |
|
1179 | ||
1180 |
function filterValues(context, event) { |
|
1181 |
var input, filter, ul, li, a, i, txtValue; |
|
1182 |
input = event.currentTarget; |
|
1183 |
filter = input.value.toLowerCase(); |
|
1184 | ||
1185 |
ul = element.querySelector(".drdn-items"); |
|
1186 |
li = ul.getElementsByTagName("li"); |
|
1187 | ||
1188 |
for (i = 0; i < li.length; i++) { |
|
1189 |
a = li[i].querySelector("div"); |
|
1190 |
txtValue = a.textContent || a.innerText; |
|
1191 | ||
1192 |
if (txtValue.toLowerCase().indexOf(filter) > -1) { |
|
1193 |
li[i].style.display = ''; |
|
1194 |
} else { |
|
1195 |
li[i].style.display = 'none'; |
|
1196 |
} |
|
1197 |
} |
|
1198 |
} |
|
1199 |
} |
|
1160 | 1200 | |
1161 | 1201 |
$(document).ready(setupAjaxIndicator); |
1162 | 1202 |
$(document).ready(hideOnLoad); |
public/stylesheets/application.css | ||
---|---|---|
160 | 160 |
a#toggle-completed-versions {color:#999;} |
161 | 161 | |
162 | 162 |
/***** Dropdown *****/ |
163 |
.drdn {position:relative;} |
|
163 |
.drdn { |
|
164 |
position: relative; |
|
165 |
} |
|
166 |
.drdn ul { |
|
167 |
margin: 0; |
|
168 |
padding: 0; |
|
169 |
} |
|
170 |
.drdn li { |
|
171 |
list-style: none; |
|
172 |
display: flex; |
|
173 |
} |
|
174 |
.drdn li:first-child { |
|
175 |
margin-top: 4px; |
|
176 |
} |
|
177 |
.drdn li .item-content { |
|
178 |
display: flex; |
|
179 |
flex-grow: 1; |
|
180 |
line-height: 26px; |
|
181 |
} |
|
182 |
.drdn .label-item { |
|
183 |
width: 100%; |
|
184 |
} |
|
185 |
.drdn li .item-handler { |
|
186 |
display: none; |
|
187 |
} |
|
188 |
.drdn li:hover .item-handler { |
|
189 |
display: block; |
|
190 |
} |
|
191 |
.drdn li .item-handler span { |
|
192 |
height: 26px; |
|
193 |
} |
|
164 | 194 |
.drdn-trigger { |
165 | 195 |
box-sizing:border-box; |
166 | 196 |
overflow:hidden; |
... | ... | |
196 | 226 |
overflow:hidden; |
197 | 227 |
text-overflow: ellipsis; |
198 | 228 |
white-space:nowrap; |
199 |
padding:4px 8px; |
|
229 |
padding-left: 8px; |
|
230 |
padding-right: 8px; |
|
200 | 231 |
} |
201 | 232 |
.drdn-items>a:hover {text-decoration:none;} |
202 | 233 |
.drdn-items>*:focus {border:1px dotted #bbb;} |
... | ... | |
222 | 253 |
.contextual .drdn-items {padding:2px; min-width: 160px;} |
223 | 254 |
.contextual .drdn-items>a {padding: 5px 8px;} |
224 | 255 |
.contextual .drdn-items>a.icon {padding-left: 24px; background-position-x: 4px;} |
225 |
.contextual .drdn-items>a:hover {color:#2A5685; border:1px solid #628db6; background-color:#eef5fd; border-radius:3px;} |
|
256 |
.contextual .drdn-items>a:hover, .query-columns .drdn-items li:hover {color:#2A5685; border:1px solid #628db6; background-color:#eef5fd; border-radius:3px;}
|
|
226 | 257 | |
227 | 258 |
#project-jump.drdn {width:200px;display:inline-block;} |
228 |
#project-jump .drdn-trigger { |
|
259 |
#project-jump .drdn-trigger, .query-columns .drdn-trigger {
|
|
229 | 260 |
width:100%; |
230 | 261 |
height:24px; |
231 | 262 |
display:inline-block; |
... | ... | |
239 | 270 |
} |
240 | 271 |
#project-jump .drdn.expanded .drdn-trigger {background-image:url(../images/arrow_up.png);} |
241 | 272 |
#project-jump .drdn-content {width:280px;} |
242 |
#project-jump .drdn-items>* {color:#555 !important;} |
|
273 |
#project-jump .drdn-items>* {color:#555 !important; padding-top: 4px; padding-bottom: 8px;}
|
|
243 | 274 |
#project-jump .drdn-items>a:hover {background-color:#759FCF; color:#fff !important;} |
244 | 275 | |
245 | 276 |
/***** Tables *****/ |
... | ... | |
403 | 434 |
height:100%; |
404 | 435 |
vertical-align: middle; |
405 | 436 |
} |
406 |
.query-columns label { |
|
407 |
display:block; |
|
408 |
} |
|
409 | 437 |
.query-columns .buttons input[type=button] { |
410 | 438 |
width:35px; |
411 | 439 |
display:block; |
- « Previous
- 1
- 2
- 3
- 4
- Next »