listpress 0.1.11

Sign up to get free protection for your applications and to get access to all the features.
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: []