Skip to content

Instantly share code, notes, and snippets.

@pythonandchips
Created January 12, 2021 11:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pythonandchips/c4aac663145324b9755c24f2b6ef383c to your computer and use it in GitHub Desktop.
Save pythonandchips/c4aac663145324b9755c24f2b6ef383c to your computer and use it in GitHub Desktop.
View component data table
<div class="data-table">
<%= form_with url: search_path, method: :get do |f| %>
<div class="data-table_controls">
<div class="data-table_length">
<%= f.label "page_size" do %>
Show
<%= f.select "page_size", options_for_select([10, 25, 50, 100], @collection.per_page) %>
entries
<% end %>
</div>
<div class="data-table_search">
<%= f.label "search", "Search:" %>
<div class="input-group">
<%= f.text_field "search", value: params.fetch(:search, ""), type: "search" %>
<%= f.submit "Go", class: "btn btn-primary" %>
</div>
</div>
</div>
<div class="data-table_controls">
<div class="data-table_filter">
<%= f.hidden_field "filter", value: params.fetch(:filter, "all") %>
<% @filters.each do |key, text| %>
<%= link_to text, filter_link(key), class: filter_class(key) %>
<% end %>
</div>
</div>
<% end %>
<table class="data-table_content">
<thead>
<tr>
<% @columns.each do |column, config| %>
<th class="<%= ordering_class(column, config) %> ">
<% if config[:sort] %>
<%= link_to column.to_s.titleize, sort_link(column, config) %>
<% else %>
<span><%= column.to_s.titleize %></span>
<% end %>
</th>
<% end %>
<% if show_links? %>
<th>&nbsp;</th>
<% end %>
</tr>
</thead>
<tbody>
<% @collection.each do |item| %>
<tr>
<% @columns.each do |column, config| %>
<td><%= display_value(item, column, config) %></td>
<% end %>
<% if show_links? %>
<td>
<% @links.each do |name, format| %>
<%= link_to name.to_s.titleize, format.call(item) %>
<% end %>
</td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
<div class="data-table_footer">
<div class="data-table_summary">
<span>Showing <%= start_number %> to <%= end_number %> of <%= @collection.count %> entries</span>
</div>
<%= render(UI::Paging, collection: @collection, class_name: ["data-table_paging"]) %>
</div>
</div>
# frozen_string_literal: true
require "uri_address"
module UI
class DataTable < ActionView::Component::Base
def initialize(
columns:,
collection:,
sorting:,
filters:,
links:
)
@columns = columns
@collection = collection
@sorting = sorting
@links = links
@filters = filters
end
def display_value(item, column, config)
v = item.send(column)
formatting = config.fetch(:format, proc { |value| value })
formatting.call(v)
end
def show_links?
@links.length.present?
end
def start_number
[((@collection.current_page - 1) * @collection.per_page) + 1, @collection.count].min
end
def end_number
page_end = (start_number + @collection.per_page) - 1
[page_end, @collection.count].min
end
def ordering_class(column, config)
return "" unless config[:sort]
order_field = @collection.order_values.first.expr.name
order_direction = @collection.order_values.first.direction
classes = ["data-table_sorting"]
classes << order_direction.to_s if order_field.to_s == column.to_s
classes.join(" ")
end
def sort_link(column, _config)
uri = UriAddress.new(request_url)
uri.query_param("order", column)
uri.query_param("direction", sort_direction(column))
uri.to_s
end
def search_path
uri = UriAddress.new(request_url)
uri.query_param("filter", params.fetch(:filter, "all"))
uri.query_param("order", order_field)
uri.query_param("direction", order_direction)
uri.remove_query_param("page_no")
uri.to_s
end
def filter_link(key)
uri = UriAddress.new(request_url)
uri.query_param("filter", key)
uri.to_s
end
def filter_class(key)
classes = ["btn"]
classes << if key.to_s == params.fetch(:filter, "all")
"btn-primary"
else
"btn-outline"
end
classes.join(" ")
end
def sort_direction(column)
if column.to_s == order_field
order_direction.to_s == "desc" ? "asc" : "desc"
else
"desc"
end
end
def order_field
@collection.order_values.first.expr.name
end
def order_direction
@collection.order_values.first.direction
end
def request_url
@request_url ||= request.url
end
end
end
.data-table {
display: block;
width: 100%;
overflow-x: auto;
&_controls {
display: flex;
align-items: center;
}
&_search {
display: flex;
label {
margin: auto;
}
}
&_filter {
display: flex;
align-items: flex-end;
width: 100%;
justify-content: flex-end;
margin-top: 10px;
}
&_length {
flex: 1 0;
}
&_content {
width: 100%;
border: 1px solid #e6e6f2;
margin-top: 6px;
margin-bottom: 6px;
max-width: none;
border-collapse: separate;
border-spacing: 0;
tbody {
tr:nth-child(odd) {
background-color: rgba(230, 230, 242, .5);
}
}
th {
text-align: left;
font-weight: normal;
border: 1px solid #e6e6f2;
vertical-align: middle;
border-bottom: 2px solid #e6e6f2;
color: #3d405c;
font-family: 'Circular Std Medium';
a, span {
padding: 10px;
display: block;
position: relative;
}
}
td {
padding: 10px;
vertical-align: middle;
border: 1px solid #e6e6f2;
font-weight: normal;
}
}
&_sorting {
cursor: pointer;
position: relative;
&::before {
position: absolute;
bottom: 0.7em;
display: block;
opacity: 0.3;
right: 1em;
content: "\2191";
}
&::after {
position: absolute;
bottom: 0.7em;
display: block;
opacity: 0.3;
right: 0.5em;
content: "\2193";
}
&.asc {
&::before {
opacity: 1;
}
}
&.desc {
&::after {
opacity: 1;
}
}
}
&_footer {
display: flex;
}
&_summary {
flex: 1 0;
padding-top: 0.85em;
white-space: nowrap;
}
&_paging {
margin: 0;
white-space: nowrap;
text-align: right;
display: flex;
padding-left: 0;
list-style: none;
border-radius: .25rem;
margin: 2px 0;
white-space: nowrap;
justify-content: flex-end;
li {
a, span {
position: relative;
display: block;
padding: .5rem .75rem;
margin-right: 5px;
border: 1px solid #e6e6f2;
border-radius: 3px;
line-height: 1;
}
a.active, a:hover {
color: #fff;
background-color: #5969ff;
border-color: #5969ff;
}
}
}
}
<%= render(UI::Card, container_class: "alert-list") do %>
<%=
render(
UI::DataTable,
columns: {
timestamp: {
sort: true,
format: proc { |value| value.strftime("%Y-%m-%d %H:%M:%S") }
},
current_state: {
sort: true,
format: proc { |value| value.titleize }
},
name: { sort: true },
description: {}
},
filters: {
all: "All",
received: "Received",
awaiting_acknowledgement: "Awaiting Acknowledgement",
acknowledged: "Acknowledged",
resolved: "Resolved"
},
links: {
view: proc { |item| alerting_alert_path(item) }
},
collection: @alerts,
sorting: { field: :timestamp, direction: :asc },
)
%>
<% end %>
<% return if @total_pages == 1 %>
<ul class="<%= @class_name.join(" ") %>">
<% if previous_page %>
<li>
<%= link_to "Previous", paging_link(previous_page) %>
</li>
<% end %>
<% if more_pages_before %>
<li><span>...</span></li>
<% end %>
<% (paging_start..paging_end).each do |page| %>
<li>
<%= link_to page, paging_link(page), class: "#{ 'active' if page == current_page }" %>
</li>
<% end %>
<% if more_pages_after %>
<li><span>...</span></li>
<% end %>
<% if next_page %>
<li>
<%= link_to "Next", paging_link(next_page) %>
</li>
<% end %>
</ul>
# frozen_string_literal: true
require "uri_address"
module UI
class Paging < ActionView::Component::Base
attr_reader :current_page, :total_entries, :page_size, :previous_page, :next_page, :total_pages
def initialize(collection:, class_name: [], request_url: nil)
@current_page = collection.current_page.to_i
@page_size = collection.per_page
@total_pages = collection.total_pages
@total_entries = collection.count
@previous_page = collection.previous_page
@next_page = collection.next_page
@class_name = class_name
@request_url = request_url
end
def paging_start
return 1 if current_page < 4
[(current_page - 2), (@total_pages - 5)].min
end
def paging_end
return @total_pages if @total_pages < 5
return 5 if current_page <= 3
end_no = current_page + 2
return @total_pages if end_no > @total_pages
end_no
end
def more_pages_before
paging_start != 1
end
def more_pages_after
@total_pages != paging_end
end
def paging_link(page_no)
uri = UriAddress.new(request_url)
uri.query_param("page_no", page_no)
uri.to_s
end
def request_url
@request_url ||= request.url
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment