Skip to content

Commit c549560

Browse files
authored
Merge pull request #3017 from sbull/horizontal-scroll-list
Add config.horizontal_scroll_list to enable horizontal scrolling colu…
2 parents 6cde21d + 20c2fd7 commit c549560

8 files changed

Lines changed: 297 additions & 35 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
(->
2+
$ = jQuery
3+
4+
setFrozenColPositions = ->
5+
$listForm = $('#bulk_form')
6+
return unless $listForm.is('.ra-horizontal-scroll-list')
7+
$listForm.find('table tr').each (index, tr) ->
8+
firstPosition = 0
9+
$(tr).find('.ra-horizontal-scroll-frozen').each (idx, td) ->
10+
tdLeft = $(td).position().left
11+
firstPosition = tdLeft if idx == 0
12+
td.style.left = "#{tdLeft - firstPosition}px"
13+
14+
$(window).on('load', setFrozenColPositions) # Update after link icons load.
15+
$(document).on('rails_admin.dom_ready', setFrozenColPositions)
16+
)()

app/assets/javascripts/rails_admin/rails_admin.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@
1515
//= require 'rails_admin/ra.i18n'
1616
//= require 'rails_admin/bootstrap/bootstrap'
1717
//= require 'rails_admin/ra.widgets'
18+
//= require 'rails_admin/ra.horizontal-scroll-list'
1819
//= require 'rails_admin/ui'
1920
//= require 'rails_admin/custom/ui'
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.ra-horizontal-scroll-table {
2+
margin-bottom: 20px;
3+
overflow-x: auto;
4+
.table {
5+
margin-bottom: 0;
6+
}
7+
8+
.ra-horizontal-scroll-frozen {
9+
position: sticky;
10+
}
11+
12+
// Remove transparency on frozen cells.
13+
$table-bg-default: if($table-bg == transparent, if($body-bg == transparent, #fff, $body-bg), $table-bg) !default;
14+
.table-striped > tbody > tr:nth-child(even) > td, .table-striped > thead > tr > th {
15+
background-color: $table-bg-default;
16+
}
17+
$table-bg-header-sort: #e2eff6 !default;
18+
.table .ra-horizontal-scroll-frozen {
19+
&.headerSortUp, &.headerSortDown {
20+
background-color: $table-bg-header-sort;
21+
}
22+
}
23+
24+
// border-right isn't sticky
25+
.ra-horizontal-scroll-frozen-last {
26+
box-shadow: -1px 0 0 0 $table-border-color inset;
27+
padding-right: $table-condensed-cell-padding + 1px;
28+
}
29+
}

app/assets/stylesheets/rails_admin/rails_admin.scss.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
@import "rails_admin/bootstrap-datetimepicker-build";
5656
@import "rails_admin/ra.filtering-multiselect";
5757
@import "rails_admin/ra.widgets";
58+
@import "rails_admin/ra.horizontal-scroll-table";
5859
@import "rails_admin/jquery.colorpicker";
5960

6061

app/views/rails_admin/main/index.html.haml

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212
properties = @model_config.list.with(controller: self.controller, view: self, object: @abstract_model.model.new).visible_fields
1313
checkboxes = @model_config.list.checkboxes?
1414
# columns paginate
15-
sets = get_column_sets(properties)
16-
properties = sets[params[:set].to_i] || []
17-
other_left = ((params[:set].to_i - 1) >= 0) && sets[params[:set].to_i - 1].present?
18-
other_right = sets[params[:set].to_i + 1].present?
15+
horiz_scroll = @model_config.list.horizontal_scroll_list_calc
16+
unless horiz_scroll[:enabled]
17+
sets = get_column_sets(properties)
18+
properties = sets[params[:set].to_i] || []
19+
other_left = ((params[:set].to_i - 1) >= 0) && sets[params[:set].to_i - 1].present?
20+
other_right = sets[params[:set].to_i + 1].present?
21+
end
1922

2023
- content_for :contextual_tabs do
2124
- if checkboxes
@@ -68,43 +71,54 @@
6871
%li{class: "#{'active' if scope.to_s == params[:scope] || (params[:scope].blank? && index == 0)}"}
6972
%a{href: index_path(params.merge(scope: scope, page: nil)), class: 'pjax'}= I18n.t("admin.scopes.#{@abstract_model.to_param}.#{scope}", default: I18n.t("admin.scopes.#{scope}", default: scope.to_s.titleize))
7073

71-
= form_tag bulk_action_path(model_name: @abstract_model.to_param), method: :post, id: "bulk_form", class: "form" do
74+
= form_tag bulk_action_path(model_name: @abstract_model.to_param), method: :post, id: "bulk_form", class: ["form", horiz_scroll[:num_frozen_columns] > 0 ? "ra-horizontal-scroll-list" : nil].compact.join(' ') do
7275
= hidden_field_tag :bulk_action
7376
- if description.present?
7477
%p
7578
%strong= description
7679

77-
%table.table.table-condensed.table-striped
78-
%thead
79-
%tr
80-
- if checkboxes
81-
%th.shrink
82-
%input.toggle{type: "checkbox"}
83-
- if other_left
84-
%th.other.left.shrink= "..."
85-
- properties.each do |property|
86-
- selected = (sort == property.name.to_s)
87-
- if property.sortable
88-
- sort_location = index_path params.except('sort_reverse').except('page').merge(sort: property.name).merge(selected && sort_reverse != "true" ? {sort_reverse: "true"} : {})
89-
- sort_direction = (sort_reverse == 'true' ? "headerSortUp" : "headerSortDown" if selected)
90-
%th{class: "#{property.sortable && "header pjax" || nil} #{sort_direction if property.sortable && sort_direction} #{property.css_class} #{property.type_css_class}", :'data-href' => (property.sortable && sort_location), rel: "tooltip", title: "#{property.hint}"}= capitalize_first_letter(property.label)
91-
- if other_right
92-
%th.other.right.shrink= "..."
93-
%th.last.shrink
94-
%tbody
95-
- @objects.each do |object|
96-
%tr{class: "#{@abstract_model.param_key}_row #{@model_config.list.with(object: object).row_css_class}"}
80+
.table-wrapper{class: horiz_scroll[:enabled] && 'ra-horizontal-scroll-table'}
81+
%table.table.table-condensed.table-striped
82+
%thead
83+
%tr
84+
- horiz_scroll_i = horiz_scroll[:num_frozen_columns]
9785
- if checkboxes
98-
%td= check_box_tag "bulk_ids[]", object.id, false
99-
- if @other_left_link ||= other_left && index_path(params.except('set').merge(params[:set].to_i != 1 ? {set: (params[:set].to_i - 1)} : {}))
100-
%td.other.left= link_to "...", @other_left_link, class: 'pjax'
101-
- properties.map{ |property| property.bind(:object, object) }.each do |property|
102-
- value = property.pretty_value
103-
%td{class: "#{property.css_class} #{property.type_css_class}", title: strip_tags(value.to_s)}= value
104-
- if @other_right_link ||= other_right && index_path(params.merge(set: (params[:set].to_i + 1)))
105-
%td.other.right= link_to "...", @other_right_link, class: 'pjax'
106-
%td.last.links
107-
%ul.inline.list-inline= menu_for :member, @abstract_model, object, true
86+
%th.shrink{class: [(horiz_scroll_i -= 1) > -1 && 'ra-horizontal-scroll-frozen', horiz_scroll_i == 0 && 'ra-horizontal-scroll-frozen-last']}
87+
%input.toggle{type: "checkbox"}
88+
- if horiz_scroll[:enabled]
89+
%th.last.shrink{class: [(horiz_scroll_i -= 1) > -1 && 'ra-horizontal-scroll-frozen', horiz_scroll_i == 0 && 'ra-horizontal-scroll-frozen-last']}
90+
- elsif other_left
91+
%th.other.left.shrink= "..."
92+
- properties.each do |property|
93+
- selected = (sort == property.name.to_s)
94+
- if property.sortable
95+
- sort_location = index_path params.except('sort_reverse').except('page').merge(sort: property.name).merge(selected && sort_reverse != "true" ? {sort_reverse: "true"} : {})
96+
- sort_direction = (sort_reverse == 'true' ? "headerSortUp" : "headerSortDown" if selected)
97+
%th{class: [property.sortable && "header pjax", property.sortable && sort_direction, property.css_class, property.type_css_class, (horiz_scroll_i -= 1) > -1 && 'ra-horizontal-scroll-frozen', horiz_scroll_i == 0 && 'ra-horizontal-scroll-frozen-last'], :'data-href' => (property.sortable && sort_location), rel: "tooltip", title: "#{property.hint}"}= capitalize_first_letter(property.label)
98+
- unless horiz_scroll[:enabled]
99+
- if other_right
100+
%th.other.right.shrink= "..."
101+
%th.last.shrink
102+
%tbody
103+
- @objects.each do |object|
104+
- horiz_scroll_i = horiz_scroll[:num_frozen_columns]
105+
%tr{class: "#{@abstract_model.param_key}_row #{@model_config.list.with(object: object).row_css_class}"}
106+
- if checkboxes
107+
%td{class: [(horiz_scroll_i -= 1) > -1 && 'ra-horizontal-scroll-frozen', horiz_scroll_i == 0 && 'ra-horizontal-scroll-frozen-last']}= check_box_tag "bulk_ids[]", object.id, false
108+
- td_links = capture do
109+
%td.last.links{class: [(horiz_scroll_i -= 1) > -1 && 'ra-horizontal-scroll-frozen', horiz_scroll_i == 0 && 'ra-horizontal-scroll-frozen-last']}
110+
%ul.inline.list-inline= menu_for :member, @abstract_model, object, true
111+
- if horiz_scroll[:enabled]
112+
= td_links
113+
- elsif @other_left_link ||= other_left && index_path(params.except('set').merge(params[:set].to_i != 1 ? {set: (params[:set].to_i - 1)} : {}))
114+
%td.other.left= link_to "...", @other_left_link, class: 'pjax'
115+
- properties.map{ |property| property.bind(:object, object) }.each do |property|
116+
- value = property.pretty_value
117+
%td{class: [property.css_class, property.type_css_class, (horiz_scroll_i -= 1) > -1 && 'ra-horizontal-scroll-frozen', horiz_scroll_i == 0 && 'ra-horizontal-scroll-frozen-last' ], title: strip_tags(value.to_s)}= value
118+
- unless horiz_scroll[:enabled]
119+
- if @other_right_link ||= other_right && index_path(params.merge(set: (params[:set].to_i + 1)))
120+
%td.other.right= link_to "...", @other_right_link, class: 'pjax'
121+
= td_links
108122

109123
- if @model_config.list.limited_pagination
110124
.row

lib/rails_admin/config.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ class << self
5959
# Set the max width of columns in list view before a new set is created
6060
attr_accessor :total_columns_width
6161

62+
# Enable horizontal-scroll table in list view, ignore total_columns_width
63+
attr_accessor :horizontal_scroll_list
64+
6265
# set parent controller
6366
attr_accessor :parent_controller
6467

@@ -285,6 +288,7 @@ def reset
285288
@excluded_models = []
286289
@included_models = []
287290
@total_columns_width = 697
291+
@horizontal_scroll_list = nil
288292
@label_methods = [:name, :title]
289293
@main_app_name = proc { [Rails.application.engine_name.titleize.chomp(' Application'), 'Admin'] }
290294
@registry = {}

lib/rails_admin/config/sections/list.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,30 @@ class List < RailsAdmin::Config::Sections::Base
3939
register_instance_option :row_css_class do
4040
''
4141
end
42+
43+
register_instance_option :horizontal_scroll_list do
44+
nil
45+
end
46+
47+
def horizontal_scroll_list_calc
48+
global_config = RailsAdmin::Config.horizontal_scroll_list
49+
model_config = horizontal_scroll_list
50+
enabled = model_config == false ? false : (!!model_config || !!global_config)
51+
if enabled
52+
num_frozen = model_config[:num_frozen_columns] if model_config.is_a?(Hash)
53+
unless num_frozen
54+
num_frozen = global_config[:num_frozen_columns] if global_config.is_a?(Hash)
55+
num_frozen ||= 3 # by default, freeze checkboxes, links & first property (usually primary key / id?)
56+
num_frozen -= 1 unless checkboxes? # model config should be explicit about this, only adjust if using global config
57+
end
58+
else
59+
num_frozen = 0
60+
end
61+
{
62+
enabled: enabled,
63+
num_frozen_columns: num_frozen,
64+
}
65+
end
4266
end
4367
end
4468
end

spec/integration/config/list/rails_admin_config_list_spec.rb

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,4 +474,177 @@
474474
end
475475
end
476476
end
477+
478+
describe 'horizontal-scroll list option' do
479+
all_team_columns = ['', '', 'Id', 'Created at', 'Updated at', 'Division', 'Name', 'Logo url', 'Team Manager', 'Ballpark', 'Mascot', 'Founded', 'Wins', 'Losses', 'Win percentage', 'Revenue', 'Color', 'Custom field', 'Main Sponsor', 'Players', 'Some Fans', 'Comments']
480+
481+
it "displays all fields on one page when true" do
482+
RailsAdmin.config do |config|
483+
config.horizontal_scroll_list = true
484+
end
485+
FactoryGirl.create_list :team, 3
486+
visit index_path(model_name: 'team')
487+
cols = all('th').collect(&:text)
488+
expect(cols[0..4]).to eq(all_team_columns[0..4])
489+
expect(cols).to contain_exactly(*all_team_columns)
490+
expect(page).to have_selector('.table-wrapper.ra-horizontal-scroll-table')
491+
expect(page).to have_selector('.ra-horizontal-scroll-list')
492+
expect(all('.ra-horizontal-scroll-frozen').count).to eq(12)
493+
expect(all('th.ra-horizontal-scroll-frozen').count).to eq(3)
494+
expect(all('td.ra-horizontal-scroll-frozen').count).to eq(9)
495+
expect(all('.ra-horizontal-scroll-frozen-last').count).to eq(4)
496+
end
497+
498+
it "displays all fields with custom frozen columns" do
499+
RailsAdmin.config do |config|
500+
config.horizontal_scroll_list = {num_frozen_columns: 2}
501+
end
502+
FactoryGirl.create_list :team, 3
503+
visit index_path(model_name: 'team')
504+
cols = all('th').collect(&:text)
505+
expect(cols[0..4]).to eq(all_team_columns[0..4])
506+
expect(cols).to contain_exactly(*all_team_columns)
507+
expect(page).to have_selector('.table-wrapper.ra-horizontal-scroll-table')
508+
expect(page).to have_selector('.ra-horizontal-scroll-list')
509+
expect(all('.ra-horizontal-scroll-frozen').count).to eq(8)
510+
expect(all('th.ra-horizontal-scroll-frozen').count).to eq(2)
511+
expect(all('td.ra-horizontal-scroll-frozen').count).to eq(6)
512+
expect(all('.ra-horizontal-scroll-frozen-last').count).to eq(4)
513+
end
514+
515+
it "displays all fields with no checkboxes" do
516+
RailsAdmin.config do |config|
517+
config.horizontal_scroll_list = true
518+
end
519+
RailsAdmin.config Team do
520+
list do
521+
checkboxes false
522+
end
523+
end
524+
FactoryGirl.create_list :team, 3
525+
visit index_path(model_name: 'team')
526+
cols = all('th').collect(&:text)
527+
expect(cols[0..3]).to eq(all_team_columns[1..4])
528+
expect(cols).to contain_exactly(*all_team_columns[1..-1])
529+
expect(all('.ra-horizontal-scroll-frozen').count).to eq(8)
530+
expect(all('th.ra-horizontal-scroll-frozen').count).to eq(2)
531+
expect(all('td.ra-horizontal-scroll-frozen').count).to eq(6)
532+
expect(all('.ra-horizontal-scroll-frozen-last').count).to eq(4)
533+
end
534+
535+
it "displays all fields with no frozen columns" do
536+
RailsAdmin.config do |config|
537+
config.horizontal_scroll_list = {num_frozen_columns: 0}
538+
end
539+
FactoryGirl.create_list :team, 3
540+
visit index_path(model_name: 'team')
541+
cols = all('th').collect(&:text)
542+
expect(cols[0..4]).to eq(all_team_columns[0..4])
543+
expect(cols).to contain_exactly(*all_team_columns)
544+
expect(page).to have_selector('.table-wrapper.ra-horizontal-scroll-table')
545+
expect(page).not_to have_selector('.ra-horizontal-scroll-list')
546+
expect(all('.ra-horizontal-scroll-frozen').count).to eq(0)
547+
expect(all('.ra-horizontal-scroll-frozen-last').count).to eq(0)
548+
end
549+
550+
it "displays sets when not set" do
551+
visit index_path(model_name: 'team')
552+
expect(all('th').collect(&:text)).to eq ['', 'Id', 'Created at', 'Updated at', 'Division', 'Name', 'Logo url', '...', '']
553+
expect(page).to have_selector('.table-wrapper')
554+
expect(page).not_to have_selector('.table-wrapper.ra-horizontal-scroll-table')
555+
expect(page).not_to have_selector('.ra-horizontal-scroll-list')
556+
expect(all('.ra-horizontal-scroll-frozen').count).to eq(0)
557+
expect(all('.ra-horizontal-scroll-frozen-last').count).to eq(0)
558+
end
559+
560+
it "displays sets when global config is on but model config is off" do
561+
RailsAdmin.config do |config|
562+
config.horizontal_scroll_list = true
563+
end
564+
RailsAdmin.config Team do
565+
list do
566+
horizontal_scroll_list false
567+
end
568+
end
569+
visit index_path(model_name: 'team')
570+
expect(all('th').collect(&:text)).to eq ['', 'Id', 'Created at', 'Updated at', 'Division', 'Name', 'Logo url', '...', '']
571+
expect(page).to have_selector('.table-wrapper')
572+
expect(page).not_to have_selector('.table-wrapper.ra-horizontal-scroll-table')
573+
expect(page).not_to have_selector('.ra-horizontal-scroll-list')
574+
expect(all('.ra-horizontal-scroll-frozen').count).to eq(0)
575+
expect(all('.ra-horizontal-scroll-frozen-last').count).to eq(0)
576+
end
577+
578+
it "displays all fields when global config is off but model config is on" do
579+
RailsAdmin.config Team do
580+
list do
581+
horizontal_scroll_list true
582+
end
583+
end
584+
FactoryGirl.create_list :team, 3
585+
visit index_path(model_name: 'team')
586+
cols = all('th').collect(&:text)
587+
expect(cols[0..4]).to eq(all_team_columns[0..4])
588+
expect(cols).to contain_exactly(*all_team_columns)
589+
expect(page).to have_selector('.table-wrapper.ra-horizontal-scroll-table')
590+
expect(page).to have_selector('.ra-horizontal-scroll-list')
591+
expect(all('.ra-horizontal-scroll-frozen').count).to eq(12)
592+
expect(all('th.ra-horizontal-scroll-frozen').count).to eq(3)
593+
expect(all('td.ra-horizontal-scroll-frozen').count).to eq(9)
594+
expect(all('.ra-horizontal-scroll-frozen-last').count).to eq(4)
595+
end
596+
597+
it "displays all fields with custom model config settings" do
598+
RailsAdmin.config do |config|
599+
config.horizontal_scroll_list = true
600+
end
601+
RailsAdmin.config Team do
602+
list do
603+
horizontal_scroll_list(num_frozen_columns: 2)
604+
end
605+
end
606+
FactoryGirl.create_list :team, 3
607+
FactoryGirl.create_list :player, 3
608+
visit index_path(model_name: 'team')
609+
cols = all('th').collect(&:text)
610+
expect(cols[0..4]).to eq(all_team_columns[0..4])
611+
expect(cols).to contain_exactly(*all_team_columns)
612+
expect(page).to have_selector('.table-wrapper.ra-horizontal-scroll-table')
613+
expect(page).to have_selector('.ra-horizontal-scroll-list')
614+
expect(all('.ra-horizontal-scroll-frozen').count).to eq(8)
615+
expect(all('th.ra-horizontal-scroll-frozen').count).to eq(2)
616+
expect(all('td.ra-horizontal-scroll-frozen').count).to eq(6)
617+
expect(all('.ra-horizontal-scroll-frozen-last').count).to eq(4)
618+
visit index_path(model_name: 'player')
619+
expect(page).to have_selector('.table-wrapper.ra-horizontal-scroll-table')
620+
expect(page).to have_selector('.ra-horizontal-scroll-list')
621+
expect(all('.ra-horizontal-scroll-frozen').count).to eq(12)
622+
expect(all('th.ra-horizontal-scroll-frozen').count).to eq(3)
623+
expect(all('td.ra-horizontal-scroll-frozen').count).to eq(9)
624+
expect(all('.ra-horizontal-scroll-frozen-last').count).to eq(4)
625+
end
626+
627+
it "displays all fields with model config checkbox settings" do
628+
RailsAdmin.config do |config|
629+
config.horizontal_scroll_list = true
630+
end
631+
RailsAdmin.config Team do
632+
list do
633+
horizontal_scroll_list(num_frozen_columns: 3)
634+
checkboxes false
635+
end
636+
end
637+
FactoryGirl.create_list :team, 3
638+
visit index_path(model_name: 'team')
639+
cols = all('th').collect(&:text)
640+
expect(cols[0..3]).to eq(all_team_columns[1..4])
641+
expect(cols).to contain_exactly(*all_team_columns[1..-1])
642+
expect(page).to have_selector('.table-wrapper.ra-horizontal-scroll-table')
643+
expect(page).to have_selector('.ra-horizontal-scroll-list')
644+
expect(all('.ra-horizontal-scroll-frozen').count).to eq(12)
645+
expect(all('th.ra-horizontal-scroll-frozen').count).to eq(3)
646+
expect(all('td.ra-horizontal-scroll-frozen').count).to eq(9)
647+
expect(all('.ra-horizontal-scroll-frozen-last').count).to eq(4)
648+
end
649+
end
477650
end

0 commit comments

Comments
 (0)