Listpress

Listing helper for Timepress projects.

Requirements

  • Listpress requires jQuery to run.
  • Listpress styles are built on top of Bootstrap, but all templates and looks can be customized if you need to use something else.

Installation

Add this line to your application's Gemfile:

gem 'listpress', :git => '[email protected]:timepress/listpress.git', :branch => "master"

And then execute:

$ bundle install

Add listpress to your application.js

import "listpress"

Add listpress to your app/config/importmap.rb

pin "listpress", to: "listpress.js" # from gem

And assets/config/manifest.js

//= link listpress.js

Listpress uses Rails importmaps to import jQuery, use bin/importmap to make jQuery available if you didn't do so yet.

bin/importmap pin [email protected] --download

And styles to your application.scss. Styles are built on top of Boostrap 5.

@import 'listpress';

Usage

In your view:

<%# 
    create listing for @messages
    `name` is optional but required to differentiate between multiple listings on the same page if present
    `per_page` activates paging
    `default_sort` "name_of_column:desc" or "name_of_column:asc" - works only if column has sort enabled
    `default_filter` { name_of_filter: "value of filter" }
%>
<%= listing @messages, name: :messages, per_page: 100, default_sort: "date:desc", default_filter: { resolved: false } do |l| %>
  <%# :code filter will use where(code: filter_value), because model has this attribute %>
  <% l.filter :code, as: :select, collection: Message.distinct.order(:code).pluck(:code) %>

  <%# :resolved filter will call method "resolved" on collection passing filter value as argument, (instead of attribute :resolved) %>
  <% l.filter :resolved, as: :boolean, method: :resolved %>

  <%# custom filter - block returns filtered collection %>
  <% l.filter(:code_ends_with, as: :text) {|collection, value| collection.where('code LIKE ?', "%#{value}")} %>

  <%# will use search method on the collection %>
  <% l.filter :search, as: :search %>

  <%# set html attributes for whole item (for <tr> tag) %>
  <% l.item_attributes {|item| { class: item.red? "red" : "" }} %>

  <%# add class "nowrap", allow sorting by :date, custom output specified with a block %>
  <%# th_options sets html attributes for column header, td_options for column values %>
  <% l.column(:date, class: "nowrap", sort: true, th_options: {title: "Wow such dates"}, td_options: {title: "Very short"}) {|m| lf m.date, format: :short} %>

  <%# passes the output (even if given by block) through format_helper() %>
  <% l.column(:number, class: "num", helper: :format_number) %>

  <%# custom field label and output %>
  <% l.column("!", class: "nowrap") do |m| %>
    <span title="Status" style="color: red"><%= m.status %></span>
  <% end %>

  <% sort by custom SQL %>
  <% l.column(:customer, sort: Arel.sql("customers.name")) {|m| m.customer.name} %>

  <% l.column(:subject, sort: true) do |m| %>
    <% if m.resolved? %>
      <%= link_to m.subject, ote_msg_path(m, type: 'dec') %>
    <% else %>
      <b><%= link_to m.subject, ote_msg_path(m, type: 'dec') %></b>
    <% end %>
  <% end %>
<% end %>

In your controller:

  def index
    @messages = Message.all  # listing gets the whole collection and it will apply sorting, filtering and paging on it's own

    respond_with_listing  # just as render :index, but with some tricks to manage AJAX requests
  end

Overriding views

You can copy and override these templates to update the markup or add new filter types.

app/views/shared/_listing.html.erb
app/views/shared/_listing_filters.html.erb
app/views/shared/_listing_table.html.erb

In-line editing

For in-line editing SimpleForm gem is required. Then you can specify editable columns with :edit option like this:

<%= listing @pages, per_page: 100, default_sort: "date:desc" do |l| %>
    <%# produces: f.input :title %>
    <% l.column :title, edit: true %>

    <%# produces: f.input :published, as: :boolean %>
    <% l.column :published, helper: :bool, edit: { as: :boolean } %>

    <%# produces: f.association :author, as: :select, collection: User.all.pluck(:name, :id) %>
    <% l.column :author, helper: :bool, edit: { association: true, as: :select, collection: User.all.pluck(:name, :id) } %>

    <%# uses this column to place the editing form and "Save" and "Cancel" buttons %>
    <% l.column edit: :actions do |p| %>
        <%= link_to "Edit", edit_page_path(p) %>
    <% end %>
<% end %>

edit: true turns on editing with default SimpleForm f.input field for attribute of the same name as column. If you specify edit: hash, the hash is passed as options to f.input.

If you need to use f.association field, use edit: {associtation: true}

Use edit: :actions to specify a column where the form and it's submit buttons should be placed. This should be specified exactly once.

Javascript events

  • update.listpress (data: url) - is triggered on .listing after the listing is updated with AJAX - you can listen to this to initialize active components in table or react to document location changes - current state url is passed as data.
  • refresh.listpress - trigger this on a listing item .listing-item to reload the item - causes AJAX request that returns only this item and overwrite item in listing - filters are ignored
  • edit.listpress (data: index) - trigger this on a listing item .listing-item to load an in-line editing form for this item. index is index of .editable (resp. .editing) cell that should be focused when loaded.

I18n

Listpress defines I18n keys in listpress scope for cs and en languages.