listpress 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3695595a23f6a3377c6248a0c9e01d9c6702e1a526567daebbb9d4724392f226
4
+ data.tar.gz: 0fb82ad6524ed2a9c80d8cf6f8d78751c4b5c14f80adce353e127e37e5098573
5
+ SHA512:
6
+ metadata.gz: f3d0826640b1c3e4ba011a71ea41e0bd4203ffe98b730aca7535d48a9b0809e39d05ae4a374aab133ca295aba71d2d62c1ef20a8612bbfb452276b5a9a3e8982
7
+ data.tar.gz: ad69433776dbf5fc5d008416277df82556bc2a9d628301c609eda730b7c139dd80be1715206520d221bbc49c1a979c1f906d945f77e61cbd9e257b70ff839582
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in listpress.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Listpress
2
+
3
+ Listing helper for Timepress projects
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'listpress', :git => 'git@git.timepress.cz:timepress/listpress.git', :branch => "master"
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Add include to your `application.js`
18
+
19
+ //= require listpress
20
+
21
+ And styles to your `application.scss`
22
+
23
+ @import 'listpress';
24
+
25
+ ## Usage
26
+
27
+ In your view:
28
+
29
+ ```erb
30
+ <%= listing @messages, per_page: 100, default_sort: "date:desc", default_filter: { resolved: false }, table_options: { class: "compact"} do |l| %>
31
+ <%# :code filter will use where(code: filter_value), because model has this attribute %>
32
+ <% l.filter :code, as: :select, collection: Message.distinct.order(:code).pluck(:code).collect{|c| [c, c]} %>
33
+
34
+ <%# :resolved filter will call method "resolved" on collection passing filter value as argument, (instead of attribute :resolved) %>
35
+ <% l.filter :resolved, as: :boolean, method: :resolved %>
36
+
37
+ <%# will use search method %>
38
+ <% l.filter :search, as: :search %>
39
+
40
+ <%# set attributes for whole row %>
41
+ <% l.row_attributes {|item| { class: item.red? "red" : "" }} %>
42
+
43
+ <%# add class "nowrap", allow sorting by :date, custom output specified with a block %>
44
+ <% l.column(:date, class: "nowrap", sort: true, th_options: {title: "Wow such dates"}, td_options: {title: "Very short"}) {|m| lf m.date, format: :short} %>
45
+
46
+ <%# passes the output (even if given by block) through format_helper() %>
47
+ <% l.column(:number, class: "num", helper: :format_number) %>
48
+
49
+ <%# custom field label and output %>
50
+ <% l.column("!", class: "nowrap") do |m| %>
51
+ <span title="Status" style="color: red"><%= m.status %></span>
52
+ <% end %>
53
+
54
+ <% sort by custom SQL %>
55
+ <% l.column(:customer, sort: Arel.sql("customers.name")) {|m| m.customer.name} %>
56
+
57
+ <% l.column(:subject, sort: true) do |m| %>
58
+ <% if m.resolved? %>
59
+ <%= link_to m.subject, ote_msg_path(m, type: 'dec') %>
60
+ <% else %>
61
+ <b><%= link_to m.subject, ote_msg_path(m, type: 'dec') %></b>
62
+ <% end %>
63
+ <% end %>
64
+ <% end %>
65
+ ```
66
+
67
+ In your controller:
68
+
69
+ ```ruby
70
+ def index
71
+ @messages = Message.all # listing gets the whole collection and it will apply sorting, filtering and paging on it's own
72
+
73
+ respond_with_listing # just as render :index, but with some tricks to manage AJAX requests
74
+ end
75
+ ```
76
+
77
+ ### Config
78
+
79
+ You can change some setting in `Listpress::Config` class (for example in initializers).
80
+
81
+ ```ruby
82
+ Listpress::Config.color = "green" # affects class of paginate links
83
+ Listpress::Config.paginate_links_renderer = MaterializePaginateRenderer # affects how WillPaginate renders the paging links
84
+ ```
85
+
86
+ ## Overriding views
87
+
88
+ You can copy and override these templates:
89
+
90
+ ```
91
+ app/views/shared/_listing.html.erb
92
+ app/views/shared/_listing_filters.html.erb
93
+ app/views/shared/_listing_table.html.erb
94
+ ```
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,130 @@
1
+ $(document).ready(function () {
2
+ function updateUrlWithParams(url, params) {
3
+ var query;
4
+
5
+ // split query from url
6
+ var pos = url.indexOf('?');
7
+ if (pos > 0) {
8
+ query = url.substring(pos + 1);
9
+ url = url.substr(0, pos);
10
+ } else {
11
+ query = "";
12
+ }
13
+
14
+ // parse query into an object
15
+ var parsed_query = {};
16
+ query.replace(/&*([^=&]+)=([^=&]*)/gi, function(str,key,value) {parsed_query[decodeURIComponent(key.replace(/\+/g, ' '))] = decodeURIComponent(value.replace(/\+/g, ' '))});
17
+
18
+ // update query with params
19
+ $.each(params, function (i, param) {
20
+ parsed_query[param[0]] = param[1];
21
+ });
22
+
23
+ // create new URL
24
+ var sep = '?';
25
+ $.each(parsed_query, function (k, val) {
26
+ url += sep + encodeURIComponent(k) + '=' + encodeURIComponent(val);
27
+ sep = '&'
28
+ });
29
+
30
+ return url;
31
+ }
32
+
33
+ // timeout used so URL is not updated too often on typing
34
+ var pushStateTimeout = null;
35
+
36
+ // url that represents the last known state
37
+ var currentStateUrl = location.href;
38
+
39
+ // currently running AJAX
40
+ var currentXhr = null;
41
+
42
+ $('.listing').each(function () {
43
+ var $listing = $(this);
44
+ var $filters = $listing.find('.filters');
45
+ var $table = $listing.find('.table');
46
+ var $pages = $listing.find('.pages');
47
+ var name = $listing.data('name');
48
+
49
+ // factory for success function
50
+ var updateFn = function (delayedPush) {
51
+ return function (data) {
52
+ // update html
53
+ $table.html(data.table);
54
+ $pages.html(data.pages);
55
+
56
+ // update current state according to params sent from Rails
57
+ currentStateUrl = updateUrlWithParams(location.href, data.params);
58
+
59
+ // push state to history
60
+ if (pushStateTimeout) clearTimeout(pushStateTimeout);
61
+ if (delayedPush) {
62
+ pushStateTimeout = setTimeout(function () {history.pushState(location.href, "listing", currentStateUrl)}, 300);
63
+ } else {
64
+ history.pushState(location.href, "listing", currentStateUrl);
65
+ }
66
+
67
+ $listing.trigger('update', currentStateUrl);
68
+ }
69
+ };
70
+
71
+ // makes AJAX request and handles the response
72
+ var doRequest = function (url, delayedPush) {
73
+ if (currentXhr) currentXhr.abort();
74
+
75
+ currentXhr = $.ajax(url, {
76
+ method: 'GET',
77
+ dataType: 'json',
78
+ success: updateFn(delayedPush),
79
+ error: function (xhr, status, error) {
80
+ if (status != "abort") {
81
+ console.log("AJAX failed:", xhr, status, error);
82
+ window.location = url;
83
+ }
84
+ }
85
+ });
86
+ };
87
+
88
+ // handle paging links
89
+ $pages.on('click', 'a[href]', function () {
90
+ doRequest(updateUrlWithParams(currentStateUrl, [["listing", name], [name + "[page]", $(this).data('page')]]));
91
+ return false;
92
+ });
93
+
94
+ // handle sorting links
95
+ $table.on('click', 'a.sort', function () {
96
+ doRequest(updateUrlWithParams(currentStateUrl, [["listing", name], [name + "[sort]", $(this).data('sort')]]));
97
+ return false;
98
+ });
99
+
100
+ // handle filters
101
+ var filterTimeout = null;
102
+
103
+ $filters.on('submit', 'form', function () {return false;}); // prevent manual form submit
104
+
105
+ var submitFilters = function () {
106
+ // collect form data as params
107
+ var data = [];
108
+ $.each($filters.find('form').serializeArray(), function (i, o) {
109
+ if (o.name.substr(0, name.length + 8) == name + '[filter]') {
110
+ data.push([o.name, o.value])
111
+ }
112
+ });
113
+ data.push(['listing', name]);
114
+ data.push([name + '[page]', 1]);
115
+
116
+ // send
117
+ doRequest(updateUrlWithParams(currentStateUrl, data), true);
118
+ };
119
+
120
+ $filters.on('input', 'input[type=text],textarea', function () {
121
+ if (filterTimeout) clearTimeout(filterTimeout);
122
+ filterTimeout = setTimeout(submitFilters, 200);
123
+ });
124
+ $filters.on('change', 'select', submitFilters);
125
+ });
126
+
127
+ $(window).on('popstate', function (e) {
128
+ location.reload()
129
+ })
130
+ });
@@ -0,0 +1,13 @@
1
+ .listing {
2
+ .sort-direction { font-size: 0.8rem; position: relative; top: -2px; }
3
+ .pages { padding: 15px 0;
4
+ .pagination { margin: 0; }
5
+ .pagination-info { float: right; font-size: 1.1rem; line-height: 2.0; margin: 0px 0; }
6
+ }
7
+
8
+ form { float: right; clear: right;
9
+ .input-field.inline { margin-top: -5px; margin-bottom: 0; }
10
+ }
11
+ table { clear: both; }
12
+ .list-empty td {height: 100px;line-height: 100px;text-align: center;vertical-align: middle;}
13
+ }
@@ -0,0 +1,28 @@
1
+ <div class="listing <%= list.options[:class] %>" data-name="<%= list.name %>">
2
+ <div class="filters">
3
+ <%# render and capture filters %>
4
+ <%= list.captures[:filters] = capture do %>
5
+ <% if list.filters.any? %>
6
+ <%= render "shared/listing_filters", list: list %>
7
+ <% end %>
8
+ <% end %>
9
+ </div>
10
+
11
+ <div class="table">
12
+ <%# render and capture table %>
13
+ <%= list.captures[:table] = capture do %>
14
+ <%= render "shared/listing_table", list: list %>
15
+ <% end %>
16
+ </div>
17
+
18
+ <div class="pages">
19
+ <%# render and capture pages %>
20
+ <%= list.captures[:pages] = capture do %>
21
+ <% if list.paginate? %>
22
+ <div class="pagination-info"><%= page_entries_info list.collection, model: list.collection.model %></div>
23
+ <%= will_paginate list.collection, renderer: Listpress::Config.paginate_link_renderer, param_name: list.page_param_name %>
24
+ <% end %>
25
+ <% end %>
26
+ </div>
27
+ </div>
28
+
@@ -0,0 +1,34 @@
1
+ <%= form_with url: request.params, method: :get do |f| %>
2
+ <%# make hidden fields with all the GET params so they don't get lost on submit %>
3
+ <% list.flatten_params(request.GET).each do |k, v| %>
4
+ <%= f.hidden_field k, value: v unless ['utf8', list.page_param_name].include?(k) %>
5
+ <% end %>
6
+
7
+ <%# reset page when changing filters %>
8
+ <%= f.hidden_field list.page_param_name, value: 1 %>
9
+
10
+ <%# render filter fields %>
11
+ <% list.filters.each do |filter| %>
12
+ <% field_name = "#{list.name}[filter][#{filter[:name]}]" %>
13
+ <% field_value = list.filter_value(filter[:name]) %>
14
+ <% case filter[:as] %>
15
+ <% when :boolean %>
16
+ <div class="input-field inline">
17
+ <%= f.select field_name, [[t('listpress.do_not_filter'), nil], [t('listpress.yes'), true], [t('listpress.no'), false]], selected: field_value %>
18
+ <label><%= list.human_name(filter[:name]) %></label>
19
+ </div>
20
+ <% when :select %>
21
+ <div class="input-field inline">
22
+ <%= f.select field_name, [[t('listpress.do_not_filter'), ""]] + filter[:collection], selected: field_value %>
23
+ <label><%= list.human_name(filter[:name]) %></label>
24
+ </div>
25
+ <% when :search %>
26
+ <div class="input-field inline">
27
+ <i class="material-icons prefix">search</i>
28
+ <%= f.text_field field_name, value: field_value, placeholder: t('listpress.search') %>
29
+ </div>
30
+ <% else %>
31
+ Unknown filter type <%= filter[:as] %>
32
+ <% end %>
33
+ <% end %>
34
+ <% end %>
@@ -0,0 +1,37 @@
1
+ <%= content_tag :table, list.options[:table_options] do %>
2
+ <thead>
3
+ <tr>
4
+ <%# render column headers with sorting indicator and links %>
5
+ <% sort_attr, sort_dir = list.sort %>
6
+ <% list.columns.each do |col| %>
7
+ <%= content_tag :th, (col[:th_options] || {}).merge(class: col[:class]) do %>
8
+ <% if col[:sort] %>
9
+ <% sort = sort_attr == col[:attr] && sort_dir == :asc ? "#{col[:attr]}:desc" : "#{col[:attr]}:asc" %>
10
+ <%= link_to list.human_name(col[:attr]), request.params.deep_merge(list.name => {sort: sort}), class: "sort", "data-sort": sort %>
11
+ <% if col[:attr] == sort_attr %>
12
+ <span class="sort-direction"><%= sort_dir == :asc ? "▲" : "▼" %></span>
13
+ <% end %>
14
+ <% else %>
15
+ <%= list.human_name(col[:attr]) %>
16
+ <% end %>
17
+ <% end %>
18
+ <% end %>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ <% list.collection.each do |item| %>
23
+ <%= content_tag :tr, list.row_attributes[:proc]&.call(item) || list.row_attributes do %>
24
+ <%# render each row according to it's definition %>
25
+ <% list.columns.each do |col| %>
26
+ <%= content_tag :td, (col[:td_options] || {}).merge(class: col[:class]) do %>
27
+ <% v = col[:proc] ? capture { col[:proc].call(item).to_s } : item.public_send(col[:attr]) %>
28
+ <%= col[:helper] ? send(col[:helper], v) : v %>
29
+ <% end %>
30
+ <% end %>
31
+ <% end %>
32
+ <% end %>
33
+ <% if list.collection.empty? %>
34
+ <tr class="list-empty"><td colspan="<%= list.columns.size %>"><%= t('listpress.no_results') %></td></tr>
35
+ <% end %>
36
+ </tbody>
37
+ <% end %>
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "listpress"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,47 @@
1
+ require 'will_paginate'
2
+ require 'will_paginate/view_helpers/action_view'
3
+
4
+ module Listpress
5
+ class BootstrapPaginateRenderer < WillPaginate::ActionView::LinkRenderer
6
+ def html_container(html)
7
+ tag(:nav, tag(:ul, html, class: "pagination"))
8
+ end
9
+
10
+ def page_number(page)
11
+ if page == current_page
12
+ "<li class=\"page-item active\">" + link(page, page, rel: rel_value(page), "data-page": page) + "</li>"
13
+ else
14
+ "<li class=\"page-item\">" + link(page, page, rel: rel_value(page), "data-page": page) + "</li>"
15
+ end
16
+ end
17
+
18
+ def previous_page
19
+ num = @collection.current_page > 1 && @collection.current_page - 1
20
+ previous_or_next_page(num, "<i class=\"fas fa-chevron-left\"></i>")
21
+ end
22
+
23
+ def next_page
24
+ num = @collection.current_page < total_pages && @collection.current_page + 1
25
+ previous_or_next_page(num, "<i class=\"fas fa-chevron-right\"></i>")
26
+ end
27
+
28
+ def previous_or_next_page(page, text)
29
+ if page
30
+ "<li class=\"page-item\">" + link(text, page, "data-page": page) + "</li>"
31
+ else
32
+ "<li class=\"page-item disabled\"><span class=\"page-link\">" + text + "</span></li>"
33
+ end
34
+ end
35
+
36
+ def link(text, target, attributes = {})
37
+ if target.is_a?(Integer)
38
+ attributes[:rel] = rel_value(target)
39
+ target = url(target)
40
+ end
41
+ attributes[:href] = target
42
+ attributes[:class] = 'page-link'
43
+ tag(:a, text, attributes)
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,10 @@
1
+ module Listpress
2
+ class Config
3
+ @color = "green"
4
+ @paginate_link_renderer = MaterializePaginateRenderer
5
+
6
+ class << self
7
+ attr_accessor :color, :paginate_link_renderer
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,66 @@
1
+ module Listpress
2
+ class DefaultResolver
3
+ attr_reader :listing
4
+
5
+ def initialize(collection, listing)
6
+ @collection = collection
7
+ @listing = listing
8
+ end
9
+
10
+ # apply filters to collection
11
+ def filter(collection)
12
+ listing.filters.each do |filter|
13
+ val = listing.filter_value(filter[:name])
14
+
15
+ next if val.nil? || val == "" # beware of false (valid filter value)
16
+
17
+ if filter[:proc]
18
+ collection = filter[:proc].call(collection, val)
19
+ elsif filter[:method]
20
+ collection = collection.public_send(filter[:method], val)
21
+ elsif filter[:column]
22
+ collection = collection.where(filter[:column] => val)
23
+ else
24
+ if collection.respond_to?(filter[:name]) && !collection.model.column_names.include?(filter[:name].to_s)
25
+ collection = collection.public_send(filter[:name], val)
26
+ elsif
27
+ collection = collection.where(filter[:name] => val)
28
+ end
29
+ end
30
+ end
31
+
32
+ collection
33
+ end
34
+
35
+ # apply sort to collection
36
+ def sort(collection)
37
+ attr, dir = listing.sort
38
+ if attr && column = listing.columns.detect {|c| c[:attr] == attr}
39
+ case column[:sort]
40
+ when true
41
+ collection = collection.order(attr => dir)
42
+ when Symbol, String
43
+ collection = collection.order(column[:sort] => dir)
44
+ when Hash
45
+ collection = collection.order(column[:sort][dir] || (raise ArgumentError, "When using hash as a :sort option for a column, :asc and :desc keys are expected."))
46
+ else
47
+ # do not sort
48
+ end
49
+ end
50
+
51
+ collection
52
+ end
53
+
54
+ def page(collection)
55
+ if listing.paginate?
56
+ collection = collection.paginate(page: listing.params[:page] || 1, per_page: listing.options[:per_page])
57
+ end
58
+
59
+ collection
60
+ end
61
+
62
+ def collection
63
+ @_collection_cache ||= page(sort(filter(@collection)))
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,15 @@
1
+ module Listpress
2
+ class Engine < ::Rails::Engine
3
+ initializer 'listpress' do
4
+ ActiveSupport.on_load(:action_view) do
5
+ include Listpress::Helper
6
+ end
7
+
8
+ ActiveSupport.on_load :i18n do
9
+ I18n.load_path.concat(Dir[File.join(File.dirname(__FILE__), 'locale', '*.yml')])
10
+ end
11
+
12
+ ActionController::Base.send :include, Helper
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,74 @@
1
+ module Listpress
2
+ module Helper
3
+ # Used to define and render Listing.
4
+ #
5
+ # Usage:
6
+ # <%= listing collection, options do |l| %>
7
+ # <% l.column :id %>
8
+ # ...
9
+ # <% end %>
10
+ #
11
+ # Options:
12
+ # name: Identifies listing within a page - required for multiple listings on one page, (default: :list)
13
+ # per_page: Limit output to this many items and turn paging on
14
+ # params: Use these params instead of parsing them from request
15
+ # default_sort: Use this sort if none specified in params (eg: "id:asc")
16
+ #
17
+ def listing(collection, options = {}, &block)
18
+ options = options.dup
19
+
20
+ name = options.delete(:name)&.to_sym || :list
21
+ pars = options.delete(:params) || params[name] || {}
22
+
23
+ if @_listing_only.nil? || @_listing_only == name
24
+ list = Listing.new(name, collection, options, pars)
25
+ yield list
26
+ output = render 'shared/listing', list: list
27
+
28
+ if controller.respond_to?(:listing_content)
29
+ controller.listing_content[name] = list.captures
30
+ end
31
+ if controller.respond_to?(:listing_instances)
32
+ controller.listing_instances[name] = list
33
+ end
34
+
35
+ output
36
+ else
37
+ ""
38
+ end
39
+ end
40
+
41
+ # Use in controller's index action to automatically handle AJAX response for listing
42
+ #
43
+ # Responds as usually for HTML requests, renders only the selected listing for AJAX.
44
+ def respond_with_listing(action = :index)
45
+ respond_to do |format|
46
+ format.html { render action }
47
+ format.json {
48
+ render_listing(action, name = params[:listing].presence&.to_sym || :list)
49
+ render json: listing_content[name].merge(params: Listing.flatten_params(request.GET.except(:listing)))
50
+ }
51
+ end
52
+ end
53
+
54
+ def render_listing(action = :index, name = nil)
55
+ list_only(name)
56
+ render_to_string action: action, formats: [:html], layout: nil
57
+ end
58
+
59
+ # Used in controller to store rendered listing content for JSON response
60
+ def listing_content
61
+ @_listing_content ||= {}
62
+ end
63
+
64
+ # Used in controller to retrieve rendered listing instance
65
+ def listing_instances
66
+ @_listing_instances ||= {}
67
+ end
68
+
69
+ # Used in controller to limit listing rendering only for +name+
70
+ def list_only(name)
71
+ @_listing_only = name
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,161 @@
1
+ module Listpress
2
+ class Listing
3
+ attr_reader :name, :collection, :filters, :options, :columns, :captures, :params
4
+
5
+ def initialize(name, collection, options = {}, params = {})
6
+ @name = name
7
+ @collection = collection
8
+ @options = options
9
+ @params = params
10
+ @columns = []
11
+ @filters = []
12
+ @captures = {}
13
+ @row_attributes = {}
14
+ end
15
+
16
+ # Defines a column
17
+ #
18
+ # Usage:
19
+ # <%= listing collection, options do |l| %>
20
+ # <% l.column :id, class: "right-align", sort: true %>
21
+ # <% l.column :name, sort: true, helper: :shorten %>
22
+ # <% l.column :special, th_options: { title: "Special column"}, td_options: { title: "With special cells" } %>
23
+ #
24
+ # Options:
25
+ # class: Used as HTML class for this column's cells
26
+ # sort: Allows sorting by this attribute
27
+ def column(*args, &block)
28
+ opts = args.extract_options!
29
+ attr = args[0] || ""
30
+
31
+ column = opts.merge(attr: attr)
32
+ column[:proc] = block if block_given?
33
+
34
+ @columns << column
35
+ end
36
+
37
+ # Defines attributes for each row (tr)
38
+ #
39
+ # Usage:
40
+ # <%= listing collection, options do |l| %>
41
+ # <% l.row_attributes {|item| { class: item.red? "red" : "" }} %>
42
+ # ...
43
+ def row_attributes(*args, &block)
44
+ if block_given?
45
+ @row_attributes[:proc] = block
46
+ elsif !args.empty?
47
+ @row_attributes = args.extract_options!
48
+ else
49
+ @row_attributes
50
+ end
51
+ end
52
+
53
+ # Defines a filter
54
+ #
55
+ # Usage:
56
+ # <%= listing collection, options do |l| %>
57
+ # <% l.filter :search, as: :search %>
58
+ # <% l.filter :name, as: :text %>
59
+ # <% l.filter(:active, as: :boolean) {|collection, value| collection.where(active: value)} %>
60
+ # ...
61
+ # <% end %>
62
+ #
63
+ # Filter value is either used on collection method with the same name as the filter (ie. the first filter calls `collection.search(filter_value)`)
64
+ # or you can use block to alter your collection
65
+ #
66
+ def filter(*args, &block)
67
+ opts = args.extract_options!
68
+ name = args[0] || ""
69
+
70
+ filter = opts.merge(name: name)
71
+ filter[:proc] = block if block_given?
72
+
73
+ @filters << filter
74
+ end
75
+
76
+ # finds human_name (using I18n) for column +attr+
77
+ def human_name(attr)
78
+ return "" unless attr.present?
79
+
80
+ return attr if attr.is_a?(String)
81
+
82
+ if @collection.respond_to?(:model)
83
+ @collection.model.human_attribute_name(attr)
84
+ elsif @collection.respond_to?(:human_attribute_name)
85
+ @collection.human_attribute_name(attr)
86
+ else
87
+ attr.to_s.humanize
88
+ end
89
+ end
90
+
91
+ # return resolver object which decides how to filter, sort and page a collection according to listing params and options
92
+ def resolver
93
+ @resolver ||= (options[:resolver] || DefaultResolver).new(@collection, self)
94
+ end
95
+
96
+ # returns filtered, sorted and paged collection
97
+ def collection
98
+ resolver.collection
99
+ end
100
+
101
+ def paginate?
102
+ options[:per_page]
103
+ end
104
+
105
+ def page_param_name
106
+ "#{name}[page]"
107
+ end
108
+
109
+ # returns representation of arbitrarily nested params as list of pairs [field_name, value], that
110
+ # can be used in forms to recreate the params
111
+ def self.flatten_params(params, level=0)
112
+ out = []
113
+
114
+ case params
115
+ when Array
116
+ params.each do |v|
117
+ out += flatten_params(v, level+1).collect {|kk, vv| ['[]' + kk, vv]}
118
+ end
119
+ when Hash
120
+ params.each do |k, v|
121
+ k = level > 0 ? "[#{k}]" : k.to_s
122
+ out += flatten_params(v, level+1).collect {|kk, vv| [k + kk, vv]}
123
+ end
124
+ else
125
+ out << ['', params]
126
+ end
127
+
128
+ out
129
+ end
130
+
131
+ def flatten_params(*args)
132
+ self.class.flatten_params(*args)
133
+ end
134
+
135
+ # returns current sorting as [+attr+, +dir+] (both symbols) or nil if not set
136
+ def sort
137
+ s = params[:sort].presence || options.dig(:default_sort)
138
+
139
+ if s.present?
140
+ attr, dir = s.split(':', 2)
141
+
142
+ if %w(asc desc).include?(dir)
143
+ return attr.to_sym, dir.to_sym
144
+ end
145
+ end
146
+
147
+ nil
148
+ end
149
+
150
+ # return current value of filter +filter_name+
151
+ def filter_value(filter_name)
152
+ if params[:filter].respond_to?(:dig)
153
+ params[:filter].dig(filter_name)
154
+ elsif options[:default_filter]
155
+ options[:default_filter][filter_name]
156
+ else
157
+ nil
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,7 @@
1
+ cs:
2
+ listpress:
3
+ no_results: Nebyly nalezeny žádné výsledky.
4
+ do_not_filter: "- nefiltrovat -"
5
+ "yes": Ano
6
+ "no": Ne
7
+ search: Hledat
@@ -0,0 +1,7 @@
1
+ en:
2
+ listpress:
3
+ no_results: No results found.
4
+ do_not_filter: "- do not filter -"
5
+ "yes": Yes
6
+ "no": No
7
+ search: Search
@@ -0,0 +1,36 @@
1
+ require 'will_paginate'
2
+ require 'will_paginate/view_helpers/action_view'
3
+
4
+ module Listpress
5
+ class MaterializePaginateRenderer < WillPaginate::ActionView::LinkRenderer
6
+ def html_container(html)
7
+ tag(:ul, html, class: "pagination")
8
+ end
9
+
10
+ def page_number(page)
11
+ if page == current_page
12
+ "<li class=\"#{Config.color} active\">" + link(page, page, rel: rel_value(page), "data-page": page) + "</li>"
13
+ else
14
+ "<li class=\"waves-effect\">" + link(page, page, rel: rel_value(page), "data-page": page) + "</li>"
15
+ end
16
+ end
17
+
18
+ def previous_page
19
+ num = @collection.current_page > 1 && @collection.current_page - 1
20
+ previous_or_next_page(num, "<i class=\"material-icons\">chevron_left</i>")
21
+ end
22
+
23
+ def next_page
24
+ num = @collection.current_page < total_pages && @collection.current_page + 1
25
+ previous_or_next_page(num, "<i class=\"material-icons\">chevron_right</i>")
26
+ end
27
+
28
+ def previous_or_next_page(page, text)
29
+ if page
30
+ "<li class=\"waves-effect\">" + link(text, page, "data-page": page) + "</li>"
31
+ else
32
+ "<li class=\"disabled\"><a>" + text + "</a></li>"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module Listpress
2
+ VERSION = "0.1.11"
3
+ end
data/lib/listpress.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "listpress/version"
2
+ require "listpress/materialize_paginate_renderer"
3
+ require "listpress/bootstrap_paginate_renderer"
4
+
5
+ require "listpress/config"
6
+ require "listpress/engine"
7
+ require "listpress/default_resolver"
8
+ require "listpress/listing"
9
+ require "listpress/helper"
10
+
11
+ module Listpress
12
+
13
+ end
data/listpress.gemspec ADDED
@@ -0,0 +1,37 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "listpress/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "listpress"
8
+ spec.version = Listpress::VERSION
9
+ spec.authors = ["Petr Sedivy"]
10
+ spec.email = ["petr@timepress.cz"]
11
+
12
+ spec.summary = %q{Listing helper for Timepress projects}
13
+ spec.homepage = "https://git.timepress.cz/timepress/listpress"
14
+
15
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://git.timepress.cz/timepress/listpress"
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against " \
22
+ "public gem pushes."
23
+ end
24
+
25
+ # Specify which files should be added to the gem when it is released.
26
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
28
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ spec.add_development_dependency "bundler", "~> 1.17"
35
+ spec.add_development_dependency "rake", "~> 10.0"
36
+ spec.add_runtime_dependency "will_paginate", "~> 3.1"
37
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: listpress
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.11
5
+ platform: ruby
6
+ authors:
7
+ - Petr Sedivy
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-11-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: will_paginate
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ description:
56
+ email:
57
+ - petr@timepress.cz
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - README.md
65
+ - Rakefile
66
+ - app/assets/javascript/listpress.js
67
+ - app/assets/stylesheets/listpress.scss
68
+ - app/views/shared/_listing.html.erb
69
+ - app/views/shared/_listing_filters.html.erb
70
+ - app/views/shared/_listing_table.html.erb
71
+ - bin/console
72
+ - bin/setup
73
+ - lib/listpress.rb
74
+ - lib/listpress/bootstrap_paginate_renderer.rb
75
+ - lib/listpress/config.rb
76
+ - lib/listpress/default_resolver.rb
77
+ - lib/listpress/engine.rb
78
+ - lib/listpress/helper.rb
79
+ - lib/listpress/listing.rb
80
+ - lib/listpress/locale/cs.yml
81
+ - lib/listpress/locale/en.yml
82
+ - lib/listpress/materialize_paginate_renderer.rb
83
+ - lib/listpress/version.rb
84
+ - listpress.gemspec
85
+ homepage: https://git.timepress.cz/timepress/listpress
86
+ licenses: []
87
+ metadata:
88
+ homepage_uri: https://git.timepress.cz/timepress/listpress
89
+ source_code_uri: https://git.timepress.cz/timepress/listpress
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubygems_version: 3.0.3
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: Listing helper for Timepress projects
109
+ test_files: []