listpress 0.1.11 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3695595a23f6a3377c6248a0c9e01d9c6702e1a526567daebbb9d4724392f226
4
- data.tar.gz: 0fb82ad6524ed2a9c80d8cf6f8d78751c4b5c14f80adce353e127e37e5098573
3
+ metadata.gz: 44127d0ef0815c1b06377df68c5bc866a0f86a0577a26dc92e5e9a2fcd2f11ce
4
+ data.tar.gz: c84824e3c3ee06e60eaea668286cef834b15ce46cd8bdaa6edc4d0cb7d7c0771
5
5
  SHA512:
6
- metadata.gz: f3d0826640b1c3e4ba011a71ea41e0bd4203ffe98b730aca7535d48a9b0809e39d05ae4a374aab133ca295aba71d2d62c1ef20a8612bbfb452276b5a9a3e8982
7
- data.tar.gz: ad69433776dbf5fc5d008416277df82556bc2a9d628301c609eda730b7c139dd80be1715206520d221bbc49c1a979c1f906d945f77e61cbd9e257b70ff839582
6
+ metadata.gz: 3173e635d38f88c5b895cf5c6155cf5a5d13393f8b56b3540487e7eaa30e4c489144991c0952c160e8bd84e8ecc14c7f4aec6736641d58d944335b2408c548bd
7
+ data.tar.gz: 74cc9b038c9c67db10d63b13dd7f4a1657744416e04a8ff669a764c0df276b0f81f35c59d2326e64c2101210df851464a9c3e1998a6a97732741c6975ba37d4c
data/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Listpress
2
2
 
3
- Listing helper for Timepress projects
3
+ Listing helper for Timepress projects.
4
+
5
+ ## Requirements
6
+
7
+ - Listpress requires jQuery to run.
8
+ - Listpress styles are built on top of Bootstrap, but all templates and looks can be customized if you need to use something else.
4
9
 
5
10
  ## Installation
6
11
 
@@ -14,11 +19,23 @@ And then execute:
14
19
 
15
20
  $ bundle install
16
21
 
17
- Add include to your `application.js`
22
+ Add listpress to your `application.js`
23
+
24
+ import "listpress"
25
+
26
+ Add listpress to your `app/config/importmap.rb`
27
+
28
+ pin "listpress", to: "listpress.js" # from gem
29
+
30
+ And `assets/config/manifest.js`
31
+
32
+ //= link listpress.js
33
+
34
+ Listpress uses Rails importmaps to import jQuery, use `bin/importmap` to make jQuery available if you didn't do so yet.
18
35
 
19
- //= require listpress
36
+ bin/importmap pin jquery@3.5.1 --download
20
37
 
21
- And styles to your `application.scss`
38
+ And styles to your `application.scss`. Styles are built on top of Boostrap 5.
22
39
 
23
40
  @import 'listpress';
24
41
 
@@ -27,20 +44,31 @@ And styles to your `application.scss`
27
44
  In your view:
28
45
 
29
46
  ```erb
30
- <%= listing @messages, per_page: 100, default_sort: "date:desc", default_filter: { resolved: false }, table_options: { class: "compact"} do |l| %>
47
+ <%#
48
+ create listing for @messages
49
+ `name` is optional but required to differentiate between multiple listings on the same page if present
50
+ `per_page` activates paging
51
+ `default_sort` "name_of_column:desc" or "name_of_column:asc" - works only if column has sort enabled
52
+ `default_filter` { name_of_filter: "value of filter" }
53
+ %>
54
+ <%= listing @messages, name: :messages, per_page: 100, default_sort: "date:desc", default_filter: { resolved: false } do |l| %>
31
55
  <%# :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]} %>
56
+ <% l.filter :code, as: :select, collection: Message.distinct.order(:code).pluck(:code) %>
33
57
 
34
58
  <%# :resolved filter will call method "resolved" on collection passing filter value as argument, (instead of attribute :resolved) %>
35
59
  <% l.filter :resolved, as: :boolean, method: :resolved %>
36
60
 
37
- <%# will use search method %>
61
+ <%# custom filter - block returns filtered collection %>
62
+ <% l.filter(:code_ends_with, as: :text) {|collection, value| collection.where('code LIKE ?', "%#{value}")} %>
63
+
64
+ <%# will use search method on the collection %>
38
65
  <% l.filter :search, as: :search %>
39
66
 
40
- <%# set attributes for whole row %>
41
- <% l.row_attributes {|item| { class: item.red? "red" : "" }} %>
67
+ <%# set html attributes for whole item (for <tr> tag) %>
68
+ <% l.item_attributes {|item| { class: item.red? "red" : "" }} %>
42
69
 
43
70
  <%# add class "nowrap", allow sorting by :date, custom output specified with a block %>
71
+ <%# th_options sets html attributes for column header, td_options for column values %>
44
72
  <% 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
73
 
46
74
  <%# passes the output (even if given by block) through format_helper() %>
@@ -74,21 +102,51 @@ In your controller:
74
102
  end
75
103
  ```
76
104
 
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
105
  ## Overriding views
87
106
 
88
- You can copy and override these templates:
107
+ You can copy and override these templates to update the markup or add new filter types.
89
108
 
90
109
  ```
91
110
  app/views/shared/_listing.html.erb
92
111
  app/views/shared/_listing_filters.html.erb
93
112
  app/views/shared/_listing_table.html.erb
94
- ```
113
+ ```
114
+
115
+ ## In-line editing
116
+
117
+ For in-line editing SimpleForm gem is required. Then you can specify editable columns with `:edit` option like this:
118
+
119
+ ```erb
120
+ <%= listing @pages, per_page: 100, default_sort: "date:desc" do |l| %>
121
+ <%# produces: f.input :title %>
122
+ <% l.column :title, edit: true %>
123
+
124
+ <%# produces: f.input :published, as: :boolean %>
125
+ <% l.column :published, helper: :bool, edit: { as: :boolean } %>
126
+
127
+ <%# produces: f.association :author, as: :select, collection: User.all.pluck(:name, :id) %>
128
+ <% l.column :author, helper: :bool, edit: { association: true, as: :select, collection: User.all.pluck(:name, :id) } %>
129
+
130
+ <%# uses this column to place the editing form and "Save" and "Cancel" buttons %>
131
+ <% l.column edit: :actions do |p| %>
132
+ <%= link_to "Edit", edit_page_path(p) %>
133
+ <% end %>
134
+ <% end %>
135
+ ```
136
+
137
+ `edit: true` turns on editing with default SimpleForm `f.input` field for attribute of the same name as column.
138
+ If you specify `edit: hash`, the hash is passed as options to `f.input`.
139
+
140
+ If you need to use `f.association` field, use `edit: {associtation: true}`
141
+
142
+ Use `edit: :actions` to specify a column where the form and it's submit buttons should be placed. This should be specified exactly once.
143
+
144
+ ## Javascript events
145
+
146
+ - `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.
147
+ - `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
148
+ - `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.
149
+
150
+ ## I18n
151
+
152
+ Listpress defines I18n keys in `listpress` scope for `cs` and `en` languages.
@@ -0,0 +1,256 @@
1
+ import $ from "jquery"
2
+
3
+ export function updateUrlWithParams(url, params) {
4
+ var anchor, query, pos;
5
+
6
+ // split anchor from url
7
+ pos = url.indexOf('#');
8
+ if (pos > 0) {
9
+ anchor = url.substring(pos + 1);
10
+ url = url.substr(0, pos);
11
+ } else {
12
+ anchor = "";
13
+ }
14
+
15
+ // split query from url
16
+ pos = url.indexOf('?');
17
+ if (pos > 0) {
18
+ query = url.substring(pos + 1);
19
+ url = url.substr(0, pos);
20
+ } else {
21
+ query = "";
22
+ }
23
+
24
+ // parse query into an object
25
+ var parsed_query = {};
26
+ query.replace(/&*([^=&]+)=([^=&]*)/gi, function(str,key,value) {
27
+ key = decodeURIComponent(key.replace(/\+/g, ' '));
28
+ value = decodeURIComponent(value.replace(/\+/g, ' '));
29
+
30
+ if (!parsed_query.hasOwnProperty(key)) parsed_query[key] = [];
31
+ parsed_query[key].push(value);
32
+ });
33
+
34
+ // aggregate params by name
35
+ var parsed_params = {};
36
+ $.each(params, function (i, pair) {
37
+ if (!parsed_params.hasOwnProperty(pair[0])) parsed_params[pair[0]] = [];
38
+ parsed_params[pair[0]].push(pair[1]);
39
+ });
40
+
41
+ // update query with parsed_params
42
+ $.each(parsed_params, function (k, v) {
43
+ parsed_query[k] = v;
44
+ });
45
+
46
+ // create new URL
47
+ var sep = '?';
48
+ $.each(parsed_query, function (k, values) {
49
+ $.each(values, function (i, val) {
50
+ url += sep + encodeURIComponent(k) + '=' + encodeURIComponent(val);
51
+ sep = '&'
52
+ });
53
+ });
54
+
55
+ if (anchor !== '') {
56
+ url += '#' + anchor
57
+ }
58
+
59
+ return url;
60
+ }
61
+
62
+ $(document).ready(function () {
63
+ // timeout used so URL is not updated too often on typing
64
+ var pushStateTimeout = null;
65
+
66
+ // url that represents the last known state
67
+ var currentStateUrl = location.href;
68
+
69
+ // currently running AJAX
70
+ var currentXhr = null;
71
+
72
+ $('.listing').each(function () {
73
+ var $listing = $(this);
74
+ var $filters = $listing.find('.filters');
75
+ var $content = $listing.find('.listing-content');
76
+ var $pages = $listing.find('.pages');
77
+ var name = $listing.data('name');
78
+
79
+ // factory for success function for AJAX updates
80
+ var updateFn = function (delayedPush) {
81
+ return function (data) {
82
+ // update html
83
+ $content.html(data.content);
84
+ $pages.html(data.pages);
85
+
86
+ // update current state according to params sent from Rails
87
+ currentStateUrl = updateUrlWithParams(location.href, data.params);
88
+
89
+ // push state to history
90
+ if (pushStateTimeout) clearTimeout(pushStateTimeout);
91
+ if (delayedPush) {
92
+ pushStateTimeout = setTimeout(function () {history.pushState(location.href, "listing", currentStateUrl)}, 300);
93
+ } else {
94
+ history.pushState(location.href, "listing", currentStateUrl);
95
+ }
96
+
97
+ $listing.trigger('update.listpress', currentStateUrl);
98
+ $listing.removeClass('loading');
99
+ }
100
+ };
101
+
102
+ // makes AJAX request and handles the response
103
+ var doRequest = function (url, delayedPush) {
104
+ if (currentXhr) currentXhr.abort();
105
+
106
+ $listing.addClass('loading');
107
+
108
+ currentXhr = $.ajax(url, {
109
+ method: 'GET',
110
+ dataType: 'json',
111
+ success: updateFn(delayedPush),
112
+ error: function (xhr, status, error) {
113
+ if (status != "abort") {
114
+ console.log("AJAX failed:", xhr, status, error);
115
+ window.location = url;
116
+ }
117
+ }
118
+ });
119
+ };
120
+
121
+ // handle paging links
122
+ $pages.on('click.listpress', 'a[href]', function () {
123
+ doRequest(updateUrlWithParams(currentStateUrl, [["listing", name], [name + "[page]", $(this).data('page')]]));
124
+ return false;
125
+ });
126
+
127
+ // handle sorting links
128
+ $content.on('click.listpress', 'a.sort', function () {
129
+ doRequest(updateUrlWithParams(currentStateUrl, [["listing", name], [name + "[sort]", $(this).data('sort')]]));
130
+ return false;
131
+ });
132
+
133
+ // refresh - refresh a single item
134
+ // edit - load editing version of the item (with in-line editing form)
135
+ $content.on('refresh.listpress edit.listpress', '.listing-item', function (e, edit_data) {
136
+ var id = $(this).data('id');
137
+
138
+ var params = [["listing", name], ["listing_item_id", id]];
139
+ if (e.type == "edit") params.push(["listing_edit", "1"]);
140
+
141
+ var url = updateUrlWithParams(currentStateUrl, params);
142
+
143
+ $.ajax(url, {
144
+ method: 'GET',
145
+ dataType: 'json',
146
+ success: function (data) {
147
+ // replace item with an item loaded with ajax
148
+ var $item = $(data.content).find('.listing-item').eq(0);
149
+ $content.find('.listing-item[data-id=' + id + ']').replaceWith($item);
150
+
151
+ if (e.type == "edit") {
152
+ // activate form field in the same cell as has been clicked - correct index must be sent in edit_data
153
+ setTimeout(function () {
154
+ var $first_input = $item.find('.editing').eq(edit_data).find('input, select, textarea').filter(':visible').first();
155
+ $first_input.focus();
156
+ if ($first_input.is('[type=text]')) {
157
+ $first_input[0].select();
158
+ }
159
+ }, 0);
160
+ }
161
+ $listing.trigger('update.listpress', currentStateUrl);
162
+ },
163
+ error: function (xhr, status, error) {
164
+ if (status != "abort") {
165
+ console.log("AJAX failed:", xhr, status, error);
166
+ window.location = currentStateUrl;
167
+ }
168
+ }
169
+ });
170
+ });
171
+
172
+ // handle filters
173
+ var filterTimeout = null;
174
+
175
+ $filters.on('submit.listpress', 'form', function () {return false;}); // prevent manual form submit
176
+
177
+ var submitFilters = function () {
178
+ // collect form data as params
179
+ var data = [];
180
+ $.each($filters.find('form').serializeArray(), function (i, o) {
181
+ if (o.name.substr(0, name.length + 8) == name + '[filter]') {
182
+ data.push([o.name, o.value])
183
+ }
184
+ });
185
+ data.push(['listing', name]);
186
+ data.push([name + '[page]', 1]);
187
+
188
+ // send
189
+ doRequest(updateUrlWithParams(currentStateUrl, data), true);
190
+ };
191
+
192
+ $filters.on('input.listpress', 'input[type=text],textarea', function () {
193
+ if (filterTimeout) clearTimeout(filterTimeout);
194
+ filterTimeout = setTimeout(submitFilters, 500);
195
+ });
196
+ $filters.on('change.listpress', 'select', submitFilters);
197
+
198
+ // load edit form when .editable is clicked
199
+ $content.on('click.listpress', '.editable', function (e) {
200
+ if ($(e.target).is('input,a[href]')) return;
201
+
202
+ var $item = $(this).closest('.listing-item');
203
+ var cell_index = $item.find('.editable').index($(this));
204
+ $item.trigger('edit.listpress', cell_index);
205
+ });
206
+
207
+ // close form when cancel button is clicked
208
+ $content.on('click.listpress', '.editing-actions a.btn.cancel', function () {
209
+ $(this).closest('.listing-item').trigger('refresh.listpress');
210
+ return false;
211
+ });
212
+
213
+ // submit form on enter, close it on escape
214
+ $content.on("keydown.listpress", ".editing input,.editing select,.editing textarea", function (e) {
215
+ if (e.which == 13 && !$(this).is('textarea')) { // Enter
216
+ $(this.form).submit();
217
+ return false;
218
+ } else if (e.which == 27) { // ESC
219
+ $(this).closest('.listing-item').trigger('refresh.listpress');
220
+ return false;
221
+ }
222
+ });
223
+
224
+ // handle form submit
225
+ $content.on("submit.listpress", ".editing-actions form", function () {
226
+ var $form = $(this);
227
+ var actionUrl = this.action; // get absolute URL
228
+ var xhr = new XMLHttpRequest(); // we must pass this one to get access to its responseURL
229
+
230
+ $.ajax({
231
+ type: "POST",
232
+ url: actionUrl,
233
+ data: $form.serialize(),
234
+ xhr: function () {
235
+ return xhr;
236
+ },
237
+ success: function() {
238
+ // we expect redirect on successful submit
239
+ if (xhr.responseURL == actionUrl) { // not redirected = fail - submit the form without ajax so user can see what's wrong
240
+ $form.trigger("submit.not-listpress");
241
+ } else { // redirected = success - refresh the item
242
+ $form.closest('.listing-item').trigger('refresh.listpress');
243
+ }
244
+ }
245
+ });
246
+
247
+ return false;
248
+ });
249
+
250
+ });
251
+
252
+ // reload page when back button is pressed
253
+ $(window).on('popstate.listpress', function () {
254
+ location.reload();
255
+ });
256
+ });
@@ -1,13 +1,37 @@
1
1
  .listing {
2
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; }
3
+
4
+ .pages { @extend .mb-2;
5
+ .pagination { margin: 0;
6
+ .page-item { @extend .shadow-sm; }
7
+ }
8
+ .pagination-info { float: right; line-height: 1.5; padding: 0.5rem 0; }
9
+ .gap { display: inline-block; padding: $pagination-padding-y $pagination-padding-x; }
10
+ }
11
+
12
+ form { justify-content: right;
13
+ .col-form-label { @extend .me-1; }
6
14
  }
7
15
 
8
- form { float: right; clear: right;
9
- .input-field.inline { margin-top: -5px; margin-bottom: 0; }
16
+ .listing-wrapper { @extend .mb-2; position: relative;
17
+ .listing-content .listing-table { clear: both; margin-bottom: 0;
18
+ thead {
19
+ th { background-color: $primary; border: none; color: color-contrast($primary); font-weight: normal;
20
+ a { color: color-contrast($primary); font-weight: bold; }
21
+ }
22
+ }
23
+ tbody { border-top: none;
24
+ tr:last-child td { border-bottom: none; }
25
+ }
26
+ th, td { padding: 0.25rem 0.5rem; }
27
+ .list-empty td { height: 100px; line-height: 100px; text-align: center; vertical-align: middle; }
28
+ }
29
+ .spinner-border { display: none; }
30
+ }
31
+ &.loading {
32
+ .listing-wrapper {
33
+ .listing-content { opacity: 0.5; }
34
+ .spinner-border { display: block; position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; }
35
+ }
10
36
  }
11
- table { clear: both; }
12
- .list-empty td {height: 100px;line-height: 100px;text-align: center;vertical-align: middle;}
13
37
  }
@@ -3,16 +3,19 @@
3
3
  <%# render and capture filters %>
4
4
  <%= list.captures[:filters] = capture do %>
5
5
  <% if list.filters.any? %>
6
- <%= render "shared/listing_filters", list: list %>
6
+ <%= render list.filters_view, list: list %>
7
7
  <% end %>
8
- <% end %>
8
+ <% end unless list.item_refresh? %>
9
9
  </div>
10
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 %>
11
+ <div class="listing-wrapper">
12
+ <div class="listing-content">
13
+ <%# render and capture table %>
14
+ <%= list.captures[:content] = capture do %>
15
+ <%= render list.listing_view, list: list %>
16
+ <% end %>
17
+ </div>
18
+ <div class="spinner-border"></div>
16
19
  </div>
17
20
 
18
21
  <div class="pages">
@@ -20,9 +23,8 @@
20
23
  <%= list.captures[:pages] = capture do %>
21
24
  <% if list.paginate? %>
22
25
  <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 %>
26
+ <%= will_paginate list.collection, renderer: Listpress::PaginateRenderer, param_name: list.page_param_name %>
24
27
  <% end %>
25
- <% end %>
28
+ <% end unless list.item_refresh? %>
26
29
  </div>
27
30
  </div>
28
-
@@ -1,7 +1,7 @@
1
- <%= form_with url: request.params, method: :get do |f| %>
1
+ <%= form_with url: request.params, method: :get, class: "row row-cols-auto g-0" do |f| %>
2
2
  <%# make hidden fields with all the GET params so they don't get lost on submit %>
3
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) %>
4
+ <%= f.hidden_field k, value: v, id: nil unless ['utf8', list.page_param_name].include?(k) %>
5
5
  <% end %>
6
6
 
7
7
  <%# reset page when changing filters %>
@@ -9,26 +9,54 @@
9
9
 
10
10
  <%# render filter fields %>
11
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 %>
12
+ <div class="col row row-cols-auto g-0 ps-2 mb-2 <%= "filter-#{filter[:name].to_s.parameterize}" %>">
13
+ <% field_name = "#{list.name}[filter][#{filter[:name]}]" %>
14
+ <% field_value = list.filter_value(filter[:name]) %>
15
+ <% case filter[:as] %>
16
+ <% when :boolean %>
17
+ <label class="col-form-label"><%= list.human_name(filter[:name]) %></label>
18
+ <div class="shadow-sm">
19
+ <%= f.select field_name, [[t('listpress.do_not_filter'), nil], [t('listpress.yes'), true], [t('listpress.no'), false]], { selected: field_value }, class: "form-select" %>
20
+ </div>
21
+ <% when :select %>
22
+ <label class="col-form-label"><%= list.human_name(filter[:name]) %></label>
23
+ <div class="shadow-sm">
24
+ <%= f.select field_name, [[t('listpress.do_not_filter'), ""]] + filter[:collection], { selected: field_value }, class: "form-select" %>
25
+ </div>
26
+ <% when :multiple %>
27
+ <% field_value = Array.wrap(field_value).collect {|s| s.split(',').collect(&:presence).compact }.sum([])%>
28
+ <label class="col-form-label"><%= list.human_name(filter[:name]) %></label>
29
+ <div class="shadow-sm">
30
+ <%= f.select field_name, [[t('listpress.do_not_filter'), ""]] + filter[:collection], { selected: field_value }, class: "form-select", multiple: true %>
31
+ </div>
32
+ <% when :grouped_select %>
33
+ <label class="col-form-label"><%= list.human_name(filter[:name]) %></label>
34
+ <div class="shadow-sm">
35
+ <%= f.select field_name, grouped_options_for_select(filter[:collection], field_value, prompt: t('listpress.do_not_filter')), {}, class: "form-control" %>
36
+ </div>
37
+ <% when :search %>
38
+ <div class="input-group shadow-sm">
39
+ <div class="input-group-text"><i class="fas fa-search"></i></div>
40
+ <%= f.text_field field_name, value: field_value, placeholder: t('listpress.search'), class: "form-control" %>
41
+ </div>
42
+ <% when :date %>
43
+ <label class="col-form-label"><%= list.human_name(filter[:name]) %></label>
44
+ <div class="shadow-sm">
45
+ <%= f.text_field field_name, value: field_value, class: "form-control date", size: 8 %>
46
+ </div>
47
+ <% when :text %>
48
+ <label class="col-form-label"><%= list.human_name(filter[:name]) %></label>
49
+ <div class="shadow-sm">
50
+ <%= f.text_field field_name, value: field_value, class: "form-control" %>
51
+ </div>
52
+ <% when :multiline %>
53
+ <label class="col-form-label"><%= list.human_name(filter[:name]) %></label>
54
+ <div class="shadow-sm">
55
+ <%= f.text_area field_name, value: field_value.presence, class: "form-control one-line", rows: 1, cols: 20 %>
56
+ </div>
57
+ <% else %>
58
+ <label class="ms-2 col-form-label">Unknown filter type <%= filter[:as] %></label>
59
+ <% end %>
60
+ </div>
33
61
  <% end %>
34
62
  <% end %>
@@ -1,37 +1,62 @@
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>
1
+ <div class="bg-white shadow table-responsive rounded">
2
+ <table class="table table-hover table-sm align-middle listing-table">
3
+ <% unless list.item_refresh? %>
4
+ <thead>
5
+ <tr>
6
+ <%# render column headers with sorting indicator and links %>
7
+ <% sort_attr, sort_dir = list.sort %>
8
+ <% list.columns.each do |col| %>
9
+ <%= content_tag :th, (col[:th_options] || {}).merge(class: col[:class]) do %>
10
+ <% if col[:sort] %>
11
+ <% sort = sort_attr == col[:attr] && sort_dir == :asc ? "#{col[:attr]}:desc" : "#{col[:attr]}:asc" %>
12
+ <%= link_to list.human_name(col[:attr]), request.params.deep_merge(list.name => {sort: sort}), class: "sort", "data-sort": sort %>
13
+ <% if col[:attr] == sort_attr %>
14
+ <span class="sort-direction"><%= sort_dir == :asc ? "▲" : "▼" %></span>
15
+ <% end %>
16
+ <% else %>
17
+ <%= list.human_name(col[:attr]) %>
18
+ <% end %>
13
19
  <% end %>
14
- <% else %>
15
- <%= list.human_name(col[:attr]) %>
16
20
  <% end %>
17
- <% end %>
21
+ </tr>
22
+ </thead>
18
23
  <% 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 %>
24
+ <tbody>
25
+ <% list.collection.each do |item| %>
26
+ <%= content_tag :tr, list.item_attributes_for(item) do %>
27
+ <%# render each item according to it's definition %>
28
+
29
+ <% form_html = nil %>
30
+ <% form = nil %>
31
+ <% if list.edit? %>
32
+ <% form_html = simple_form_for(item, namespace: [list.name.to_s.underscore, item.id].join("_")) do |f| %>
33
+ <% form = f %>
34
+ <%= f.button :submit, t('listpress.save') %>
35
+ <%= button_link t('listpress.cancel'), "#", class: "cancel", btn_class: "btn btn-secondary" %>
36
+ <% end %>
37
+ <% end %>
38
+
39
+ <% list.columns_with_td_options.each do |col, td_options| %>
40
+ <%= content_tag :td, td_options do %>
41
+ <% if list.edit? && col[:edit] %>
42
+ <% if col[:edit] == :actions %>
43
+ <%= form_html %>
44
+ <% else %>
45
+ <%= listing_input(form, col) %>
46
+ <% end %>
47
+ <% else %>
48
+ <% v = col[:proc] ? capture { col[:proc].call(item).to_s } : item.public_send(col[:attr]) %>
49
+ <%= col[:helper] ? send(col[:helper], v) : v %>
50
+ <% end %>
51
+ <% end %>
29
52
  <% end %>
30
53
  <% end %>
31
54
  <% 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 %>
55
+ <% if list.collection.empty? %>
56
+ <tr class="list-empty">
57
+ <td colspan="<%= list.columns.size %>"><%= t('listpress.no_results') %></td>
58
+ </tr>
59
+ <% end %>
60
+ </tbody>
61
+ </table>
62
+ </div>
@@ -9,6 +9,9 @@ module Listpress
9
9
 
10
10
  # apply filters to collection
11
11
  def filter(collection)
12
+ # skip filters if we need to retrieve a specific item
13
+ return collection.where(id: listing.params[:item_id]) if listing.params[:item_id]
14
+
12
15
  listing.filters.each do |filter|
13
16
  val = listing.filter_value(filter[:name])
14
17
 
@@ -52,7 +55,7 @@ module Listpress
52
55
  end
53
56
 
54
57
  def page(collection)
55
- if listing.paginate?
58
+ if listing.paginate? && !listing.item_refresh?
56
59
  collection = collection.paginate(page: listing.params[:page] || 1, per_page: listing.options[:per_page])
57
60
  end
58
61
 
@@ -10,9 +10,9 @@ module Listpress
10
10
  #
11
11
  # Options:
12
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
13
  # params: Use these params instead of parsing them from request
15
- # default_sort: Use this sort if none specified in params (eg: "id:asc")
14
+ #
15
+ # See Listing.new for other options
16
16
  #
17
17
  def listing(collection, options = {}, &block)
18
18
  options = options.dup
@@ -21,9 +21,12 @@ module Listpress
21
21
  pars = options.delete(:params) || params[name] || {}
22
22
 
23
23
  if @_listing_only.nil? || @_listing_only == name
24
+ pars = pars.merge(@_listing_control) if @_listing_control
25
+
24
26
  list = Listing.new(name, collection, options, pars)
25
27
  yield list
26
- output = render 'shared/listing', list: list
28
+
29
+ output = render list.view, list: list
27
30
 
28
31
  if controller.respond_to?(:listing_content)
29
32
  controller.listing_content[name] = list.captures
@@ -45,14 +48,20 @@ module Listpress
45
48
  respond_to do |format|
46
49
  format.html { render action }
47
50
  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)))
51
+ name = params[:listing].presence&.to_sym || :list
52
+ control = {
53
+ item_id: params[:listing_item_id].presence,
54
+ edit: params[:listing_edit].to_i == 1,
55
+ }
56
+
57
+ render_listing(action, name, control)
58
+ render json: listing_content[name].merge(params: Listing.flatten_params(request.GET.except(:listing, :listing_item_id, :listing_edit)))
50
59
  }
51
60
  end
52
61
  end
53
62
 
54
- def render_listing(action = :index, name = nil)
55
- list_only(name)
63
+ def render_listing(action = :index, name = nil, control = {})
64
+ list_only(name, control)
56
65
  render_to_string action: action, formats: [:html], layout: nil
57
66
  end
58
67
 
@@ -66,9 +75,25 @@ module Listpress
66
75
  @_listing_instances ||= {}
67
76
  end
68
77
 
69
- # Used in controller to limit listing rendering only for +name+
70
- def list_only(name)
78
+ # Used in controller to limit listing rendering only for +name+ listing and pass control options +control+
79
+ def list_only(name, control)
71
80
  @_listing_only = name
81
+ @_listing_control = control
82
+ end
83
+
84
+ # View helper to render input for using SimpleForm builder +form+ and column +col+
85
+ def listing_input(form, col)
86
+ options = col[:edit].is_a?(Hash) ? col[:edit].dup : {}
87
+ options = options.reverse_merge(label: false, grid_wrapper_html: {class: "col-sm-12 ms-0"})
88
+ options[:input_html] ||= {}
89
+ options[:input_html][:form] = form.id
90
+
91
+ if options.delete(:association)
92
+ form.association col[:attr], options
93
+ else
94
+ form.input col[:attr], options
95
+ end
72
96
  end
97
+
73
98
  end
74
99
  end
@@ -1,7 +1,23 @@
1
1
  module Listpress
2
+ # definition of a listing - made from DSL in a View
2
3
  class Listing
3
4
  attr_reader :name, :collection, :filters, :options, :columns, :captures, :params
4
5
 
6
+ # Args:
7
+ # name: name of the listing
8
+ # collection: collection of items to list - usually AR relation
9
+ # options: configuration of listing given as specified in template
10
+ # params: request params for this specific listing
11
+ #
12
+ # Options:
13
+ # per_page: Limit output to this many items and turn paging on
14
+ # default_sort: Use this sort if none specified in params (eg: "id:asc")
15
+ # default_filter: Hash with default values for filters
16
+ # resolver: Specify custom Resolver class - Resolver applies filters and sorting to collection - default Listpress::DefaultResolver
17
+ # view: Partial used to render the listing component, default 'shared/listing'
18
+ # listing_view: Partial used to show the listed items, default 'shared/listing_table'
19
+ # filters_view: Partial used to show filters, default 'shared/listing_filters'
20
+ #
5
21
  def initialize(name, collection, options = {}, params = {})
6
22
  @name = name
7
23
  @collection = collection
@@ -10,7 +26,7 @@ module Listpress
10
26
  @columns = []
11
27
  @filters = []
12
28
  @captures = {}
13
- @row_attributes = {}
29
+ @item_attributes = {}
14
30
  end
15
31
 
16
32
  # Defines a column
@@ -18,12 +34,22 @@ module Listpress
18
34
  # Usage:
19
35
  # <%= listing collection, options do |l| %>
20
36
  # <% l.column :id, class: "right-align", sort: true %>
21
- # <% l.column :name, sort: true, helper: :shorten %>
37
+ # <% l.column :name, sort: true, helper: :shorten, edit: true %>
22
38
  # <% l.column :special, th_options: { title: "Special column"}, td_options: { title: "With special cells" } %>
39
+ # <% l.column(:block) { |item| link_to item.name, edit_item_path(item) } %>
23
40
  #
24
41
  # Options:
25
- # class: Used as HTML class for this column's cells
26
- # sort: Allows sorting by this attribute
42
+ # - attr: First argument if given is attribute name - unless a block is specified, this attribute will be called on the model to get content of the cell
43
+ # - label: Label of the column, defaults to human_attribute_name of `attr`
44
+ # - class: Used as HTML class for this column's cells
45
+ # - sort: Allow sorting of this column
46
+ # - true to sort by the `attr` (must be an SQL column)
47
+ # - column name to sort by other SQL column
48
+ # - Arel.sql() to sort by arbitrary SQL
49
+ # - edit: true to activate in-line editing with SimpleForm, hash to pass options to SimpleForm `input` builder method. Use association: true to call SimpleForm `association` instead.
50
+ # - th_options: HTML attributes for column header
51
+ # - td_options: HTML attributes for column cells
52
+ # - helper: use this helper to format cell value - if cell value given by block, applies to block result
27
53
  def column(*args, &block)
28
54
  opts = args.extract_options!
29
55
  attr = args[0] || ""
@@ -31,22 +57,26 @@ module Listpress
31
57
  column = opts.merge(attr: attr)
32
58
  column[:proc] = block if block_given?
33
59
 
60
+ if column[:edit]
61
+ SimpleForm rescue raise("To use in-line editing simple_form gem is required.")
62
+ end
63
+
34
64
  @columns << column
35
65
  end
36
66
 
37
- # Defines attributes for each row (tr)
67
+ # Defines attributes for each item (tr).
38
68
  #
39
69
  # Usage:
40
70
  # <%= listing collection, options do |l| %>
41
- # <% l.row_attributes {|item| { class: item.red? "red" : "" }} %>
71
+ # <% l.item_attributes {|item| { class: item.red? ? "red" : "" }} %>
42
72
  # ...
43
- def row_attributes(*args, &block)
73
+ def item_attributes(*args, &block)
44
74
  if block_given?
45
- @row_attributes[:proc] = block
75
+ @item_attributes[:proc] = block
46
76
  elsif !args.empty?
47
- @row_attributes = args.extract_options!
77
+ @item_attributes = args.extract_options!
48
78
  else
49
- @row_attributes
79
+ @item_attributes
50
80
  end
51
81
  end
52
82
 
@@ -102,10 +132,52 @@ module Listpress
102
132
  options[:per_page]
103
133
  end
104
134
 
135
+ def item_refresh?
136
+ params[:item_id].present?
137
+ end
138
+
139
+ def edit?
140
+ params[:edit]
141
+ end
142
+
105
143
  def page_param_name
106
144
  "#{name}[page]"
107
145
  end
108
146
 
147
+ def columns_with_td_options
148
+ columns.collect do |col|
149
+ td_options = col[:td_options].deep_dup || {}
150
+ td_options[:class] = Array.wrap(td_options[:class]) + Array.wrap(col[:class])
151
+ if col[:edit]
152
+ if col[:edit] == :actions
153
+ td_options[:class] << (edit? ? "editing-actions" : "editable-actions")
154
+ else
155
+ td_options[:class] << (edit? ? "editing" : "editable")
156
+ end
157
+ end
158
+
159
+ [col, td_options]
160
+ end
161
+ end
162
+
163
+ def item_attributes_for(item)
164
+ if item_attributes[:proc]
165
+ attributes = item_attributes[:proc].call(item)
166
+ else
167
+ attributes = item_attributes.deep_dup
168
+ end
169
+
170
+ if item.respond_to?(:id)
171
+ attributes[:data] ||= {}
172
+ attributes[:data][:id] = item.id
173
+ end
174
+
175
+ attributes[:class] = Array.wrap(attributes[:class])
176
+ attributes[:class] << 'listing-item'
177
+
178
+ attributes
179
+ end
180
+
109
181
  # returns representation of arbitrarily nested params as list of pairs [field_name, value], that
110
182
  # can be used in forms to recreate the params
111
183
  def self.flatten_params(params, level=0)
@@ -157,5 +229,20 @@ module Listpress
157
229
  nil
158
230
  end
159
231
  end
232
+
233
+ # partial used to render the whole component
234
+ def view
235
+ options[:view] || 'shared/listing'
236
+ end
237
+
238
+ # partial used to render filters
239
+ def filters_view
240
+ options[:filters_view] || 'shared/listing_filters'
241
+ end
242
+
243
+ # partial used to render the listing of items
244
+ def listing_view
245
+ options[:listing_view] || 'shared/listing_table'
246
+ end
160
247
  end
161
248
  end
@@ -5,3 +5,5 @@ cs:
5
5
  "yes": Ano
6
6
  "no": Ne
7
7
  search: Hledat
8
+ save: Uložit
9
+ cancel: Zrušit
@@ -5,3 +5,6 @@ en:
5
5
  "yes": Yes
6
6
  "no": No
7
7
  search: Search
8
+ save: Save
9
+ cancel: Cancel
10
+
@@ -2,7 +2,7 @@ require 'will_paginate'
2
2
  require 'will_paginate/view_helpers/action_view'
3
3
 
4
4
  module Listpress
5
- class BootstrapPaginateRenderer < WillPaginate::ActionView::LinkRenderer
5
+ class PaginateRenderer < WillPaginate::ActionView::LinkRenderer
6
6
  def html_container(html)
7
7
  tag(:nav, tag(:ul, html, class: "pagination"))
8
8
  end
@@ -1,3 +1,3 @@
1
1
  module Listpress
2
- VERSION = "0.1.11"
2
+ VERSION = "0.4.2"
3
3
  end
data/lib/listpress.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  require "listpress/version"
2
- require "listpress/materialize_paginate_renderer"
3
- require "listpress/bootstrap_paginate_renderer"
2
+ require "listpress/paginate_renderer"
4
3
 
5
- require "listpress/config"
6
4
  require "listpress/engine"
7
5
  require "listpress/default_resolver"
8
6
  require "listpress/listing"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: listpress
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Petr Sedivy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-08 00:00:00.000000000 Z
11
+ date: 2023-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -63,7 +63,7 @@ files:
63
63
  - Gemfile
64
64
  - README.md
65
65
  - Rakefile
66
- - app/assets/javascript/listpress.js
66
+ - app/assets/javascripts/listpress.js
67
67
  - app/assets/stylesheets/listpress.scss
68
68
  - app/views/shared/_listing.html.erb
69
69
  - app/views/shared/_listing_filters.html.erb
@@ -71,15 +71,13 @@ files:
71
71
  - bin/console
72
72
  - bin/setup
73
73
  - lib/listpress.rb
74
- - lib/listpress/bootstrap_paginate_renderer.rb
75
- - lib/listpress/config.rb
76
74
  - lib/listpress/default_resolver.rb
77
75
  - lib/listpress/engine.rb
78
76
  - lib/listpress/helper.rb
79
77
  - lib/listpress/listing.rb
80
78
  - lib/listpress/locale/cs.yml
81
79
  - lib/listpress/locale/en.yml
82
- - lib/listpress/materialize_paginate_renderer.rb
80
+ - lib/listpress/paginate_renderer.rb
83
81
  - lib/listpress/version.rb
84
82
  - listpress.gemspec
85
83
  homepage: https://git.timepress.cz/timepress/listpress
@@ -102,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
100
  - !ruby/object:Gem::Version
103
101
  version: '0'
104
102
  requirements: []
105
- rubygems_version: 3.0.3
103
+ rubygems_version: 3.0.8
106
104
  signing_key:
107
105
  specification_version: 4
108
106
  summary: Listing helper for Timepress projects
@@ -1,130 +0,0 @@
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
- });
@@ -1,10 +0,0 @@
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
@@ -1,36 +0,0 @@
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