Tabular Data

With searching, sorting, and pagination

100 results found

Name ↓ Stars Price Category
1597 Burger ★★★ $ Healthy
16 Box ★★★ $$ American (Traditional)
19 Box ★★★★★ $$ Sushi
273 King ★★ $ Ramen
353 Sushi ★★ $$ German
396 Diner ★★★★ $$ Senegalese
402 Burger ★★★★★ $$$ Ethiopean
53 Creamery ★★★★★ $ African
6033 Steakhouse ★★★ $ Pizza
68 Subs ★★★★ $$$ Mexican

Explanation

Sophsticated management of tabular data using declarative StimulusReflex attributes. No custom JavaScript was written for this demo.

  • No need for expensive third party libraries.
  • No need to send heavy JavaScript payloads to the client.
  • No need to build an API layer that returns JSON.
  • A little server-side HTML rendering and a Reflex is all it takes.

Code (116 LOC)

ERB (59 LOC)
app/views/tabulars/_demo.html.erb (44 LOC)
<div class="form-row mb-4">
<div class="col-6">
<input type="text" placeholder="Search..." class="form-control form-control-lg" value="<%= @query %>"
data-reflex="input->TabularReflex#search" data-reflex-root="#search-results">
</div>
</div>
<div id="search-results">
<p class="lead"><%= @pagy.count %> results found</p>
<% if @pagy.count > 0 %>
<table class="table table-striped table-bordered">
<thead class="bg-primary">
<tr>
<th style="width:25%">
<%= link_to "Name #{arrow :name}", "#", class: column_css(:name),
data: { reflex: "click->TabularReflex#order", column_name: :name, direction: direction } %>
</th>
<th style="width:25%">
<%= link_to "Stars #{arrow :stars}", "#", class: column_css(:stars),
data: { reflex: "click->TabularReflex#order", column_name: :stars, direction: direction } %>
</th>
<th style="width:25%">
<%= link_to "Price #{arrow :price}", "#", class: column_css(:price),
data: { reflex: "click->TabularReflex#order", column_name: :price, direction: direction } %>
</th>
<th style="width:25%">
<%= link_to "Category #{arrow :category}", "#", class: column_css(:category),
data: { reflex: "click->TabularReflex#order", column_name: :category, direction: direction } %>
</th>
</tr>
</thead>
<tbody>
<% @restaurants.each do |restaurant| %>
<tr>
<td><%= restaurant.name %></td>
<td><%= "★" * restaurant.stars %></td>
<td><%= "$" * restaurant.price %></td>
<td><%= restaurant.category %></td>
</tr>
<% end %>
</tbody>
</table>
<%= render "/tabulars/paginator" if @pagy.pages > 1 %>
<% end %>
</div>
app/views/tabulars/_paginator.html.erb (15 LOC)
<nav>
<ul class="pagination">
<li class="page-item"><a href="#" class="page-link" data-reflex="click->TabularReflex#paginate" data-page="<%= @pagy.prev %>"></a></li>
<% @pagy.series.each do |item| %>
<% if item == :gap %>
<li class="page-item disabled"><a class="page-link">...</a></li>
<% else %>
<li class="page-item <%= "active" if item.is_a?(String) %>">
<a href="#" class="page-link" data-reflex="click->TabularReflex#paginate" data-page="<%= item %>"><%= item %></a>
</li>
<% end %>
<% end %>
<li class="page-item"><a href="#" class="page-link" data-reflex="click->TabularReflex#paginate" data-page="<%= @pagy.next %>"></a></li>
</ul>
</nav>
Ruby (57 LOC)
app/helpers/tabulars_helper.rb (17 LOC)
module TabularsHelper
include Pagy::Frontend
def column_css(column_name)
return "text-light selected" if column_name.to_s == @order_by
"text-light"
end
def arrow(column_name)
return if column_name.to_s != @order_by
@direction == "desc" ? "↑" : "↓"
end
def direction
@direction == "asc" ? "desc" : "asc"
end
def pagy_get_params(params)
params.merge query: @query, order_by: @order_by, direction: @direction
end
end
app/models/restaurant.rb (7 LOC)
class Restaurant < ApplicationRecord
scope :search, ->(query) {
query = sanitize_sql_like(query)
where(arel_table[:name].matches("%#{query}%"))
.or(where(arel_table[:category].matches("%#{query}%")))
}
end
app/controllers/tabulars_controller.rb (21 LOC)
class TabularsController < ApplicationController
include Pagy::Backend
def show
@query = session[:query]
@order_by = permitted_column_name(session[:order_by])
@direction = permitted_direction(session[:direction])
@page = (session[:page] || 1).to_i
restaurants = Restaurant.order(@order_by => @direction)
restaurants = restaurants.search(@query) if @query.present?
pages = (restaurants.count / Pagy::VARS[:items].to_f).ceil
@page = 1 if @page > pages
@pagy, @restaurants = pagy(restaurants, page: @page)
end
private
def permitted_column_name(column_name)
%w[name stars price category].find { |permitted| column_name == permitted } || "name"
end
def permitted_direction(direction)
%w[asc desc].find { |permitted| direction == permitted } || "asc"
end
end
app/reflexes/tabular_reflex.rb (12 LOC)
class TabularReflex < ApplicationReflex
def search
session[:query] = element[:value].strip
end
def order
session[:order_by] = element.dataset["column-name"]
session[:direction] = element.dataset["direction"]
end
def paginate
session[:page] = element.dataset[:page].to_i
end
end