From 257cc46d3b113b62cd5560348ccd0555de850f4d Mon Sep 17 00:00:00 2001 From: Marius BALTEANU Date: Wed, 9 Dec 2020 09:06:41 +0200 Subject: [PATCH] Improve query columns selector by replacing the combo multi select with a custom multi select with sortable items --- Gemfile | 1 + app/components/select_component.html.erb | 29 +++++++++++++++ app/components/select_component.rb | 26 +++++++++++++ app/helpers/queries_helper.rb | 7 ++++ app/views/queries/_columns.html.erb | 47 +++--------------------- public/javascripts/application.js | 40 ++++++++++++++++++++ public/stylesheets/application.css | 44 ++++++++++++++++++---- 7 files changed, 144 insertions(+), 50 deletions(-) create mode 100644 app/components/select_component.html.erb create mode 100644 app/components/select_component.rb diff --git a/Gemfile b/Gemfile index 031fc9bdc..868667d52 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ ruby '>= 2.4.0', '< 2.8.0' gem 'bundler', '>= 1.12.0' gem 'rails', '5.2.4.4' +gem "view_component", require: "view_component/engine" gem 'sprockets', '~> 3.7.2' if RUBY_VERSION < '2.5' gem 'rouge', '~> 3.26.0' gem 'request_store', '~> 1.5.0' diff --git a/app/components/select_component.html.erb b/app/components/select_component.html.erb new file mode 100644 index 000000000..861d9499c --- /dev/null +++ b/app/components/select_component.html.erb @@ -0,0 +1,29 @@ +
+ + <%= @selected_items_count %> out of <%= @selected_items_count + @available_items_count %> selected + +
+ +
    + <% @items.each do |c| %> +
  • +
    + <%= check_box_tag "#{@name}[]", c[1], c.last, id: "#{@name}_#{c[1]}" %> + <%= label_tag "#{@name}_#{c[1]}", c.first, :class => 'label-item'%> +
    + <% if @sortable %> +
    + +
    + <% end %> +
  • + <% end %> +
+
+
+ +<%= javascript_tag do %> + enableAutocomplete(document.querySelector(".query-columns")); +<% end %> diff --git a/app/components/select_component.rb b/app/components/select_component.rb new file mode 100644 index 000000000..c353c14e4 --- /dev/null +++ b/app/components/select_component.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class SelectComponent < ViewComponent::Base + def initialize( + items:, + name:, + css_class: + ) + + @selected_items_count = items.count{|v| v[2] == true } + @available_items_count = items.count - @selected_items_count + @sortable = true + @name = name + @items = items + @css_classes = css_classes(css_class) + end + + private + + def css_classes(css_class) + s = +'' + s << css_class + s << " dropdown-sortable" if @sortable + s + end +end diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb index 0d16c6962..18b854da0 100644 --- a/app/helpers/queries_helper.rb +++ b/app/helpers/queries_helper.rb @@ -131,6 +131,13 @@ module QueriesHelper reject(&:frozen?).collect {|column| [column.caption, column.name]} end + def query_options_for_multiselect(query) + selected_columns = query_selected_inline_columns_options(query).map{|s| s.append(true)} + available_columns = query_available_inline_columns_options(query).map{|s| s.append(false)} + columns = selected_columns + available_columns + columns + end + def render_query_columns_selection(query, options={}) tag_name = (options[:name] || 'c') + '[]' render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name} diff --git a/app/views/queries/_columns.html.erb b/app/views/queries/_columns.html.erb index 18443de57..5c2d7d265 100644 --- a/app/views/queries/_columns.html.erb +++ b/app/views/queries/_columns.html.erb @@ -1,42 +1,5 @@ -<% tag_id = tag_name.gsub(/[\[\]]+/, '_').sub(/_+$/, '') %> -<% available_tag_id = "available_#{tag_id}" %> -<% selected_tag_id = "selected_#{tag_id}" %> - - - - <%= label_tag available_tag_id, l(:description_available_columns) %> - <%= select_tag 'available_columns', - options_for_select(query_available_inline_columns_options(query)), - :id => available_tag_id, - :multiple => true, :size => 10, - :ondblclick => "moveOptions(this.form.#{available_tag_id}, this.form.#{selected_tag_id});" %> - - - - - - - <%= label_tag selected_tag_id, l(:description_selected_columns) %> - <%= select_tag tag_name, - options_for_select(query_selected_inline_columns_options(query)), - :id => selected_tag_id, - :multiple => true, :size => 10, - :ondblclick => "moveOptions(this.form.#{selected_tag_id}, this.form.#{available_tag_id});" %> - - - - - - - - - -<%= javascript_tag do %> -$(document).ready(function(){ - $('.query-columns').closest('form').submit(function(){ - $('#<%= selected_tag_id %> option:not(:disabled)').prop('selected', true); - }); -}); -<% end %> +<%= render SelectComponent.new( + items: query_options_for_multiselect(query), + name: "c", + css_class: "query-columns", +) %> diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 770d5900c..cbcf97777 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -1157,6 +1157,46 @@ function inlineAutoComplete(element) { tribute.attach(element); } +function enableAutocomplete(element) { + const items = $(element).find('.drdn-items') + + if (element.classList.contains('dropdown-sortable')) { + items.sortable(); + // ToDo: Replace disableSelection with CSS user-select + items.disableSelection(); + } + + element.querySelector(".drdn-items").addEventListener('click', function(event) { + if (event.target.tagName.toLowerCase() === 'input') { + const checkedEl = element.querySelectorAll('.item-content input:checked').length + element.querySelector('.drdn-trigger span.selected').innerText = checkedEl + } + }) + + element.querySelector("input.autocomplete").addEventListener('input', function(event) { + filterValues(element, event) + }) + + function filterValues(context, event) { + var input, filter, ul, li, a, i, txtValue; + input = event.currentTarget; + filter = input.value.toLowerCase(); + + ul = element.querySelector(".drdn-items"); + li = ul.getElementsByTagName("li"); + + for (i = 0; i < li.length; i++) { + a = li[i].querySelector("div"); + txtValue = a.textContent || a.innerText; + + if (txtValue.toLowerCase().indexOf(filter) > -1) { + li[i].style.display = ''; + } else { + li[i].style.display = 'none'; + } + } + } +} $(document).ready(setupAjaxIndicator); $(document).ready(hideOnLoad); diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 1c2f76c15..8be8cf669 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -160,7 +160,37 @@ a.collapsible {padding-left: 12px; } a#toggle-completed-versions {color:#999;} /***** Dropdown *****/ -.drdn {position:relative;} +.drdn { + position: relative; +} +.drdn ul { + margin: 0; + padding: 0; +} +.drdn li { + list-style: none; + display: flex; +} +.drdn li:first-child { + margin-top: 4px; +} +.drdn li .item-content { + display: flex; + flex-grow: 1; + line-height: 26px; +} +.drdn .label-item { + width: 100%; +} +.drdn li .item-handler { + display: none; +} +.drdn li:hover .item-handler { + display: block; +} +.drdn li .item-handler span { + height: 26px; +} .drdn-trigger { box-sizing:border-box; overflow:hidden; @@ -196,7 +226,8 @@ div + .drdn-items {border-top:1px solid #ccc;} overflow:hidden; text-overflow: ellipsis; white-space:nowrap; - padding:4px 8px; + padding-left: 8px; + padding-right: 8px; } .drdn-items>a:hover {text-decoration:none;} .drdn-items>*:focus {border:1px dotted #bbb;} @@ -222,10 +253,10 @@ div + .drdn-items {border-top:1px solid #ccc;} .contextual .drdn-items {padding:2px; min-width: 160px;} .contextual .drdn-items>a {padding: 5px 8px;} .contextual .drdn-items>a.icon {padding-left: 24px; background-position-x: 4px;} -.contextual .drdn-items>a:hover {color:#2A5685; border:1px solid #628db6; background-color:#eef5fd; border-radius:3px;} +.contextual .drdn-items>a:hover, .query-columns .drdn-items li:hover {color:#2A5685; border:1px solid #628db6; background-color:#eef5fd; border-radius:3px;} #project-jump.drdn {width:200px;display:inline-block;} -#project-jump .drdn-trigger { +#project-jump .drdn-trigger, .query-columns .drdn-trigger { width:100%; height:24px; display:inline-block; @@ -239,7 +270,7 @@ div + .drdn-items {border-top:1px solid #ccc;} } #project-jump .drdn.expanded .drdn-trigger {background-image:url(../images/arrow_up.png);} #project-jump .drdn-content {width:280px;} -#project-jump .drdn-items>* {color:#555 !important;} +#project-jump .drdn-items>* {color:#555 !important; padding-top: 4px; padding-bottom: 8px;} #project-jump .drdn-items>a:hover {background-color:#759FCF; color:#fff !important;} /***** Tables *****/ @@ -403,9 +434,6 @@ div.table-list.boards .table-list-cell.name {width: 30%;} height:100%; vertical-align: middle; } -.query-columns label { - display:block; -} .query-columns .buttons input[type=button] { width:35px; display:block; -- 2.22.0