listpress 0.1.11 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|