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 +4 -4
- data/README.md +78 -20
- data/app/assets/javascripts/listpress.js +256 -0
- data/app/assets/stylesheets/listpress.scss +31 -7
- data/app/views/shared/_listing.html.erb +12 -10
- data/app/views/shared/_listing_filters.html.erb +51 -23
- data/app/views/shared/_listing_table.html.erb +56 -31
- data/lib/listpress/default_resolver.rb +4 -1
- data/lib/listpress/helper.rb +34 -9
- data/lib/listpress/listing.rb +97 -10
- data/lib/listpress/locale/cs.yml +2 -0
- data/lib/listpress/locale/en.yml +3 -0
- data/lib/listpress/{bootstrap_paginate_renderer.rb → paginate_renderer.rb} +1 -1
- data/lib/listpress/version.rb +1 -1
- data/lib/listpress.rb +1 -3
- metadata +5 -7
- data/app/assets/javascript/listpress.js +0 -130
- data/lib/listpress/config.rb +0 -10
- data/lib/listpress/materialize_paginate_renderer.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44127d0ef0815c1b06377df68c5bc866a0f86a0577a26dc92e5e9a2fcd2f11ce
|
4
|
+
data.tar.gz: c84824e3c3ee06e60eaea668286cef834b15ce46cd8bdaa6edc4d0cb7d7c0771
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
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
|
-
|
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)
|
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
|
-
<%#
|
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
|
41
|
-
<% l.
|
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
|
-
|
4
|
-
|
5
|
-
.pagination
|
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
|
-
|
9
|
-
.
|
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
|
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="
|
12
|
-
|
13
|
-
|
14
|
-
<%=
|
15
|
-
|
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::
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
<%
|
16
|
-
|
17
|
-
|
18
|
-
<
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
<
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
2
|
-
<
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
<%
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
21
|
+
</tr>
|
22
|
+
</thead>
|
18
23
|
<% end %>
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
<%
|
28
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
|
data/lib/listpress/helper.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
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
|
-
|
49
|
-
|
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
|
data/lib/listpress/listing.rb
CHANGED
@@ -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
|
-
@
|
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
|
-
#
|
26
|
-
#
|
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
|
67
|
+
# Defines attributes for each item (tr).
|
38
68
|
#
|
39
69
|
# Usage:
|
40
70
|
# <%= listing collection, options do |l| %>
|
41
|
-
# <% l.
|
71
|
+
# <% l.item_attributes {|item| { class: item.red? ? "red" : "" }} %>
|
42
72
|
# ...
|
43
|
-
def
|
73
|
+
def item_attributes(*args, &block)
|
44
74
|
if block_given?
|
45
|
-
@
|
75
|
+
@item_attributes[:proc] = block
|
46
76
|
elsif !args.empty?
|
47
|
-
@
|
77
|
+
@item_attributes = args.extract_options!
|
48
78
|
else
|
49
|
-
@
|
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
|
data/lib/listpress/locale/cs.yml
CHANGED
data/lib/listpress/locale/en.yml
CHANGED
@@ -2,7 +2,7 @@ require 'will_paginate'
|
|
2
2
|
require 'will_paginate/view_helpers/action_view'
|
3
3
|
|
4
4
|
module Listpress
|
5
|
-
class
|
5
|
+
class PaginateRenderer < WillPaginate::ActionView::LinkRenderer
|
6
6
|
def html_container(html)
|
7
7
|
tag(:nav, tag(:ul, html, class: "pagination"))
|
8
8
|
end
|
data/lib/listpress/version.rb
CHANGED
data/lib/listpress.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
require "listpress/version"
|
2
|
-
require "listpress/
|
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.
|
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:
|
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/
|
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/
|
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.
|
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
|
-
});
|
data/lib/listpress/config.rb
DELETED
@@ -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
|