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 »