turbo_filter 0.0.1 → 1.0.1
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 +15 -0
- data/README.md +33 -2
- data/app/assets/javascripts/turbo_filter.js +362 -0
- data/app/views/turbo_filters/_filters.html.erb +31 -0
- data/config/locales/en.yml +32 -0
- data/lib/turbo_filter/engine.rb +4 -0
- data/lib/turbo_filter/turbo_filter_controller.rb +36 -0
- data/lib/turbo_filter/turbo_filter_helper.rb +28 -0
- data/lib/turbo_filter/turbo_filter_query.rb +353 -0
- data/lib/turbo_filter/version.rb +1 -1
- data/lib/turbo_filter.rb +10 -2
- metadata +12 -11
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YWVhNGFlM2E1YmIyYjEwN2EwNTZjYzE3NTA2OGU1M2U5Yzk3MDk2Mg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZmQwOTE5ZmY0ODdiY2QyZTYzMGQ4YTk1NmI2NmU3MzlhOTQ2MzQyNQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MGYyZmUwZjNkZTY5ODMxY2IzNmNiMGJhYjMyMzA4NDI3Nzg3ZDMyMjUzMzEy
|
10
|
+
OTdiZDgwYTBjOWExZDk4ZTUzYmVjYTMzNDhiZjAwZDM5OWY1NTA1N2FmOGQx
|
11
|
+
ZGJiYTVjNzFjYWUyOTYwZDgyNjBhYTlhYTE2NzhmZTI2YTkyNWQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZGYzNmY0NTkzNzQ5NTYyMDg4YjJiNjNlZWQxY2Q2MzMyNzRjYjU2MTI3MDc5
|
14
|
+
MTE5N2M4YjFlMDA2YzJmYTIwNmUzNTg1ZGY4YTNiZWQ1NmYxOTMxZTFhMzg3
|
15
|
+
ZTYyMWQ3OWE4YzlhMWMwYjUzZDZiOWE5YTUxMGE2YmY2ZDBmNjM=
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# TurboFilter
|
2
2
|
|
3
|
-
|
3
|
+
Filters your ActiveRecord table records.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -18,7 +18,38 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
## Usage
|
20
20
|
|
21
|
-
|
21
|
+
Add turbo_filter to application.js
|
22
|
+
|
23
|
+
//= require turbo_filter
|
24
|
+
|
25
|
+
In Controller:
|
26
|
+
|
27
|
+
class ArticlesController < ApplicationController
|
28
|
+
...
|
29
|
+
def index
|
30
|
+
retrieve_turbo_filter_query(Article)
|
31
|
+
@articles = Article.where(@turbo_filter_query.statement).paginate(page: params[:page], per_page: 100)
|
32
|
+
end
|
33
|
+
...
|
34
|
+
end
|
35
|
+
|
36
|
+
In Models: Add `to_s` method like below to `Article -> belongs_to` assocation classes.
|
37
|
+
|
38
|
+
class User < ActiveRecord::Base
|
39
|
+
...
|
40
|
+
def to_s
|
41
|
+
name
|
42
|
+
end
|
43
|
+
...
|
44
|
+
end
|
45
|
+
|
46
|
+
In Views: Add `turbo_filters` helper method to `app/views/articles/index.html.erb` view filters.
|
47
|
+
|
48
|
+
<%= turbo_filters %>
|
49
|
+
|
50
|
+
UI Compatibility: boostrap compatible.
|
51
|
+
Requires jQueryDatePicker, Turbo links enabled.
|
52
|
+
|
22
53
|
|
23
54
|
## Contributing
|
24
55
|
|
@@ -0,0 +1,362 @@
|
|
1
|
+
function checkAll(id, checked) {
|
2
|
+
$('#'+id).find('input[type=checkbox]:enabled').prop('checked', checked);
|
3
|
+
}
|
4
|
+
|
5
|
+
function toggleCheckboxesBySelector(selector) {
|
6
|
+
var all_checked = true;
|
7
|
+
$(selector).each(function(index) {
|
8
|
+
if (!$(this).is(':checked')) { all_checked = false; }
|
9
|
+
});
|
10
|
+
$(selector).prop('checked', !all_checked);
|
11
|
+
}
|
12
|
+
|
13
|
+
function showAndScrollTo(id, focus) {
|
14
|
+
$('#'+id).show();
|
15
|
+
if (focus !== null) {
|
16
|
+
$('#'+focus).focus();
|
17
|
+
}
|
18
|
+
$('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
|
19
|
+
}
|
20
|
+
|
21
|
+
function toggleRowGroup(el) {
|
22
|
+
var tr = $(el).parents('tr').first();
|
23
|
+
var n = tr.next();
|
24
|
+
tr.toggleClass('open');
|
25
|
+
while (n.length && !n.hasClass('group')) {
|
26
|
+
n.toggle();
|
27
|
+
n = n.next('tr');
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
function collapseAllRowGroups(el) {
|
32
|
+
var tbody = $(el).parents('tbody').first();
|
33
|
+
tbody.children('tr').each(function(index) {
|
34
|
+
if ($(this).hasClass('group')) {
|
35
|
+
$(this).removeClass('open');
|
36
|
+
} else {
|
37
|
+
$(this).hide();
|
38
|
+
}
|
39
|
+
});
|
40
|
+
}
|
41
|
+
|
42
|
+
function expandAllRowGroups(el) {
|
43
|
+
var tbody = $(el).parents('tbody').first();
|
44
|
+
tbody.children('tr').each(function(index) {
|
45
|
+
if ($(this).hasClass('group')) {
|
46
|
+
$(this).addClass('open');
|
47
|
+
} else {
|
48
|
+
$(this).show();
|
49
|
+
}
|
50
|
+
});
|
51
|
+
}
|
52
|
+
|
53
|
+
function toggleAllRowGroups(el) {
|
54
|
+
var tr = $(el).parents('tr').first();
|
55
|
+
if (tr.hasClass('open')) {
|
56
|
+
collapseAllRowGroups(el);
|
57
|
+
} else {
|
58
|
+
expandAllRowGroups(el);
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
function toggleFieldset(el) {
|
63
|
+
var fieldset = $(el).parents('fieldset').first();
|
64
|
+
fieldset.toggleClass('collapsed');
|
65
|
+
fieldset.children('div').toggle();
|
66
|
+
}
|
67
|
+
|
68
|
+
function hideFieldset(el) {
|
69
|
+
var fieldset = $(el).parents('fieldset').first();
|
70
|
+
fieldset.toggleClass('collapsed');
|
71
|
+
fieldset.children('div').hide();
|
72
|
+
}
|
73
|
+
|
74
|
+
function initFilters() {
|
75
|
+
$('#add_filter_select').change(function() {
|
76
|
+
addFilter($(this).val(), '', []);
|
77
|
+
});
|
78
|
+
$('#filters-table td.field input[type=checkbox]').each(function() {
|
79
|
+
toggleFilter($(this).val());
|
80
|
+
});
|
81
|
+
$('#filters-table').on('click', 'td.field input[type=checkbox]', function() {
|
82
|
+
toggleFilter($(this).val());
|
83
|
+
});
|
84
|
+
$('#filters-table').on('click', '.toggle-multiselect', function() {
|
85
|
+
toggleMultiSelect($(this).siblings('select'));
|
86
|
+
});
|
87
|
+
$('#filters-table').on('keypress', 'input[type=text]', function(e) {
|
88
|
+
if (e.keyCode == 13) submit_turbo_filter_query_form("turbo_filter_query_form");
|
89
|
+
});
|
90
|
+
}
|
91
|
+
|
92
|
+
function addFilter(field, operator, values) {
|
93
|
+
var fieldId = field.replace('.', '_');
|
94
|
+
var tr = $('#tr_'+fieldId);
|
95
|
+
if (tr.length > 0) {
|
96
|
+
tr.show();
|
97
|
+
} else {
|
98
|
+
buildFilterRow(field, operator, values);
|
99
|
+
}
|
100
|
+
$('#cb_'+fieldId).prop('checked', true);
|
101
|
+
toggleFilter(field);
|
102
|
+
$('#add_filter_select').val('').children('option').each(function() {
|
103
|
+
if ($(this).attr('value') == field) {
|
104
|
+
$(this).attr('disabled', true);
|
105
|
+
}
|
106
|
+
});
|
107
|
+
}
|
108
|
+
|
109
|
+
function buildFilterRow(field, operator, values) {
|
110
|
+
var fieldId = field.replace('.', '_');
|
111
|
+
var filterTable = $("#filters-table");
|
112
|
+
var filterOptions = availableFilters[field];
|
113
|
+
if (!filterOptions) return;
|
114
|
+
var operators = operatorByType[filterOptions['type']];
|
115
|
+
var filterValues = filterOptions['values'];
|
116
|
+
var datepickerOptions = {dateFormat: 'yy-mm-dd', showButtonPanel: true, changeMonth: true, changeYear: true};
|
117
|
+
var i, select;
|
118
|
+
|
119
|
+
var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
|
120
|
+
'<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' +
|
121
|
+
'<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
|
122
|
+
'<td class="values"></td>'
|
123
|
+
);
|
124
|
+
filterTable.append(tr);
|
125
|
+
|
126
|
+
select = tr.find('td.operator select');
|
127
|
+
for (i = 0; i < operators.length; i++) {
|
128
|
+
var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]);
|
129
|
+
if (operators[i] == operator) { option.attr('selected', true); }
|
130
|
+
select.append(option);
|
131
|
+
}
|
132
|
+
select.change(function(){ toggleOperator(field); });
|
133
|
+
|
134
|
+
switch (filterOptions['type']) {
|
135
|
+
case "list":
|
136
|
+
case "list_optional":
|
137
|
+
case "list_status":
|
138
|
+
case "list_subprojects":
|
139
|
+
tr.find('td.values').append(
|
140
|
+
'<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
|
141
|
+
' <span class="toggle-multiselect"> </span></span>'
|
142
|
+
);
|
143
|
+
select = tr.find('td.values select');
|
144
|
+
if (values.length > 1) { select.attr('multiple', true); }
|
145
|
+
for (i = 0; i < filterValues.length; i++) {
|
146
|
+
var filterValue = filterValues[i];
|
147
|
+
var option = $('<option>');
|
148
|
+
if ($.isArray(filterValue)) {
|
149
|
+
option.val(filterValue[1]).text(filterValue[0]);
|
150
|
+
if ($.inArray(filterValue[1], values) > -1) {option.attr('selected', true);}
|
151
|
+
} else {
|
152
|
+
option.val(filterValue).text(filterValue);
|
153
|
+
if ($.inArray(filterValue, values) > -1) {option.attr('selected', true);}
|
154
|
+
}
|
155
|
+
select.append(option);
|
156
|
+
}
|
157
|
+
break;
|
158
|
+
case "date":
|
159
|
+
case "date_past":
|
160
|
+
tr.find('td.values').append(
|
161
|
+
'<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' +
|
162
|
+
' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' +
|
163
|
+
' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>'
|
164
|
+
);
|
165
|
+
$('#values_'+fieldId+'_1').val(values[0]).datepicker(datepickerOptions);
|
166
|
+
$('#values_'+fieldId+'_2').val(values[1]).datepicker(datepickerOptions);
|
167
|
+
$('#values_'+fieldId).val(values[0]);
|
168
|
+
break;
|
169
|
+
case "string":
|
170
|
+
case "text":
|
171
|
+
tr.find('td.values').append(
|
172
|
+
'<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>'
|
173
|
+
);
|
174
|
+
$('#values_'+fieldId).val(values[0]);
|
175
|
+
break;
|
176
|
+
case "relation":
|
177
|
+
tr.find('td.values').append(
|
178
|
+
'<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' +
|
179
|
+
'<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>'
|
180
|
+
);
|
181
|
+
$('#values_'+fieldId).val(values[0]);
|
182
|
+
select = tr.find('td.values select');
|
183
|
+
for (i = 0; i < allProjects.length; i++) {
|
184
|
+
var filterValue = allProjects[i];
|
185
|
+
var option = $('<option>');
|
186
|
+
option.val(filterValue[1]).text(filterValue[0]);
|
187
|
+
if (values[0] == filterValue[1]) { option.attr('selected', true); }
|
188
|
+
select.append(option);
|
189
|
+
}
|
190
|
+
break;
|
191
|
+
case "integer":
|
192
|
+
case "float":
|
193
|
+
tr.find('td.values').append(
|
194
|
+
'<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="6" class="value" /></span>' +
|
195
|
+
' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="6" class="value" /></span>'
|
196
|
+
);
|
197
|
+
$('#values_'+fieldId+'_1').val(values[0]);
|
198
|
+
$('#values_'+fieldId+'_2').val(values[1]);
|
199
|
+
break;
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
function toggleFilter(field) {
|
204
|
+
var fieldId = field.replace('.', '_');
|
205
|
+
if ($('#cb_' + fieldId).is(':checked')) {
|
206
|
+
$("#operators_" + fieldId).show().removeAttr('disabled');
|
207
|
+
toggleOperator(field);
|
208
|
+
} else {
|
209
|
+
$("#operators_" + fieldId).hide().attr('disabled', true);
|
210
|
+
enableValues(field, []);
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
function enableValues(field, indexes) {
|
215
|
+
var fieldId = field.replace('.', '_');
|
216
|
+
$('#tr_'+fieldId+' td.values .value').each(function(index) {
|
217
|
+
if ($.inArray(index, indexes) >= 0) {
|
218
|
+
$(this).removeAttr('disabled');
|
219
|
+
$(this).parents('span').first().show();
|
220
|
+
} else {
|
221
|
+
$(this).val('');
|
222
|
+
$(this).attr('disabled', true);
|
223
|
+
$(this).parents('span').first().hide();
|
224
|
+
}
|
225
|
+
|
226
|
+
if ($(this).hasClass('group')) {
|
227
|
+
$(this).addClass('open');
|
228
|
+
} else {
|
229
|
+
$(this).show();
|
230
|
+
}
|
231
|
+
});
|
232
|
+
}
|
233
|
+
|
234
|
+
function toggleOperator(field) {
|
235
|
+
var fieldId = field.replace('.', '_');
|
236
|
+
var operator = $("#operators_" + fieldId);
|
237
|
+
switch (operator.val()) {
|
238
|
+
case "!*":
|
239
|
+
case "*":
|
240
|
+
case "t":
|
241
|
+
case "ld":
|
242
|
+
case "w":
|
243
|
+
case "lw":
|
244
|
+
case "l2w":
|
245
|
+
case "m":
|
246
|
+
case "lm":
|
247
|
+
case "y":
|
248
|
+
case "o":
|
249
|
+
case "c":
|
250
|
+
enableValues(field, []);
|
251
|
+
break;
|
252
|
+
case "><":
|
253
|
+
enableValues(field, [0,1]);
|
254
|
+
break;
|
255
|
+
case "<t+":
|
256
|
+
case ">t+":
|
257
|
+
case "><t+":
|
258
|
+
case "t+":
|
259
|
+
case ">t-":
|
260
|
+
case "<t-":
|
261
|
+
case "><t-":
|
262
|
+
case "t-":
|
263
|
+
enableValues(field, [2]);
|
264
|
+
break;
|
265
|
+
case "=p":
|
266
|
+
case "=!p":
|
267
|
+
case "!p":
|
268
|
+
enableValues(field, [1]);
|
269
|
+
break;
|
270
|
+
default:
|
271
|
+
enableValues(field, [0]);
|
272
|
+
break;
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
function toggleMultiSelect(el) {
|
277
|
+
if (el.attr('multiple')) {
|
278
|
+
el.removeAttr('multiple');
|
279
|
+
el.attr('size', 1);
|
280
|
+
} else {
|
281
|
+
el.attr('multiple', true);
|
282
|
+
if (el.children().length > 10)
|
283
|
+
el.attr('size', 10);
|
284
|
+
else
|
285
|
+
el.attr('size', 4);
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
function validate_turbo_filters(id) {
|
290
|
+
var arr = 0;
|
291
|
+
$(".value:visible").each(function(){
|
292
|
+
if(!$(this).val().trim()){
|
293
|
+
$(this).css({"border-color": "RED"});
|
294
|
+
$(this).attr('placeholder', "can't be empty.");
|
295
|
+
arr += 1;
|
296
|
+
}
|
297
|
+
});
|
298
|
+
return arr;
|
299
|
+
}
|
300
|
+
|
301
|
+
function submit_turbo_filter_query_form(id) {
|
302
|
+
if(validate_turbo_filters(id) === 0)
|
303
|
+
$('#'+id).submit();
|
304
|
+
}
|
305
|
+
|
306
|
+
function showModal(id, width) {
|
307
|
+
var el = $('#'+id).first();
|
308
|
+
if (el.length === 0 || el.is(':visible')) {return;}
|
309
|
+
var title = el.find('h3.title').text();
|
310
|
+
el.dialog({
|
311
|
+
width: width,
|
312
|
+
modal: true,
|
313
|
+
resizable: false,
|
314
|
+
dialogClass: 'modal',
|
315
|
+
title: title
|
316
|
+
});
|
317
|
+
el.find("input[type=text], input[type=submit]").first().focus();
|
318
|
+
}
|
319
|
+
|
320
|
+
function hideModal(el) {
|
321
|
+
var modal;
|
322
|
+
if (el) {
|
323
|
+
modal = $(el).parents('.ui-dialog-content');
|
324
|
+
} else {
|
325
|
+
modal = $('#ajax-modal');
|
326
|
+
}
|
327
|
+
modal.dialog("close");
|
328
|
+
}
|
329
|
+
|
330
|
+
function setupAjaxIndicator() {
|
331
|
+
$(document).bind('ajaxSend', function(event, xhr, settings) {
|
332
|
+
if ($('.ajax-loading').length === 0 && settings.contentType != 'application/octet-stream') {
|
333
|
+
$('#ajax-indicator').show();
|
334
|
+
}
|
335
|
+
});
|
336
|
+
$(document).bind('ajaxStop', function() {
|
337
|
+
$('#ajax-indicator').hide();
|
338
|
+
});
|
339
|
+
}
|
340
|
+
|
341
|
+
function hideOnLoad() {
|
342
|
+
$('.hol').hide();
|
343
|
+
}
|
344
|
+
|
345
|
+
function addFormObserversForDoubleSubmit() {
|
346
|
+
$('form[method=post]').each(function() {
|
347
|
+
if (!$(this).hasClass('multiple-submit')) {
|
348
|
+
$(this).submit(function(form_submission) {
|
349
|
+
if ($(form_submission.target).attr('data-submitted')) {
|
350
|
+
form_submission.preventDefault();
|
351
|
+
} else {
|
352
|
+
$(form_submission.target).attr('data-submitted', true);
|
353
|
+
}
|
354
|
+
});
|
355
|
+
}
|
356
|
+
});
|
357
|
+
}
|
358
|
+
|
359
|
+
// $(document).ready(setupAjaxIndicator);
|
360
|
+
// $(document).ready(hideOnLoad);
|
361
|
+
// $(document).ready(addFormObserversForDoubleSubmit);
|
362
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
var operatorLabels = <%= raw_json TurboFilter::TurboFilterQuery.operators_labels %>;
|
3
|
+
var operatorByType = <%= raw_json TurboFilter::TurboFilterQuery.operators_by_filter_type %>;
|
4
|
+
var availableFilters = <%= raw_json turbo_filter_query.available_filters_as_json %>;
|
5
|
+
var labelDayPlural = <%= raw_json t(:label_day_plural) %>;
|
6
|
+
$(document).on('ready page:load', function () {
|
7
|
+
initFilters();
|
8
|
+
<% turbo_filter_query.filters.each do |field, options| %>
|
9
|
+
addFilter("<%= field %>", <%= raw_json turbo_filter_query.operator_for(field) %>, <%= raw_json turbo_filter_query.values_for(field) %>);
|
10
|
+
<% end %>
|
11
|
+
});
|
12
|
+
</script>
|
13
|
+
<h2>Turbo Filters</h2>
|
14
|
+
<%= form_tag({ :controller => turbo_filter_query.instance_values["filters_for_class"].to_s.pluralize.downcase, :action => 'index' },
|
15
|
+
:method => :get, :id => 'turbo_filter_query_form') do %>
|
16
|
+
<table style="width:100%">
|
17
|
+
<tr>
|
18
|
+
<td>
|
19
|
+
<table id="filters-table"></table>
|
20
|
+
</td>
|
21
|
+
<td class="add-filter">
|
22
|
+
<%= label_tag('add_filter_select', t(:label_filter_add)) %>
|
23
|
+
<%= select_tag 'add_filter_select', filters_options_for_select(turbo_filter_query), :name => nil %>
|
24
|
+
</td>
|
25
|
+
</tr>
|
26
|
+
</table>
|
27
|
+
<%= hidden_field_tag 'f[]', '' %>
|
28
|
+
<%= link_to_function t(:apply), 'submit_turbo_filter_query_form("turbo_filter_query_form")', :class => 'btn btn-primary' %>
|
29
|
+
<%= link_to t(:clear), { :set_filter => 1 }, "data-no-turbolink" => true, :class => 'btn' %>
|
30
|
+
<% end -%>
|
31
|
+
<hr>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
en:
|
2
|
+
label_day_plural: Days
|
3
|
+
label_filter_add: Add Filter
|
4
|
+
label_filter_plural: Filters
|
5
|
+
label_equals: is
|
6
|
+
label_not_equals: is not
|
7
|
+
label_in_less_than: in less than
|
8
|
+
label_in_more_than: in more than
|
9
|
+
label_in_the_next_days: in the next
|
10
|
+
label_in_the_past_days: in the past
|
11
|
+
label_greater_or_equal: '>='
|
12
|
+
label_less_or_equal: '<='
|
13
|
+
label_between: between
|
14
|
+
label_in: in
|
15
|
+
label_today: today
|
16
|
+
label_all_time: all time
|
17
|
+
label_yesterday: yesterday
|
18
|
+
label_this_week: this week
|
19
|
+
label_last_week: last week
|
20
|
+
label_last_n_weeks: "last %{count} weeks"
|
21
|
+
label_last_n_days: "last %{count} days"
|
22
|
+
label_this_month: this month
|
23
|
+
label_last_month: last month
|
24
|
+
label_this_year: this year
|
25
|
+
label_date_range: Date range
|
26
|
+
label_less_than_ago: less than days ago
|
27
|
+
label_more_than_ago: more than days ago
|
28
|
+
label_ago: days ago
|
29
|
+
label_contains: contains
|
30
|
+
label_not_contains: doesn't contain
|
31
|
+
label_any: any
|
32
|
+
label_none: none
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module TurboFilter
|
2
|
+
module TurboFilterController
|
3
|
+
|
4
|
+
# Retrieve query from session or build a new query
|
5
|
+
def retrieve_turbo_filter_query(filters_for_class)
|
6
|
+
if params[:set_filter] || session[:query].nil?
|
7
|
+
# Give it a name, required to be valid
|
8
|
+
@turbo_filter_query = TurboFilter::TurboFilterQuery.new(filters_for_class)
|
9
|
+
build_turbo_filter_query_from_params
|
10
|
+
session[:query] = {:filters => @turbo_filter_query.filters}
|
11
|
+
else
|
12
|
+
# retrieve from session
|
13
|
+
@turbo_filter_query ||= TurboFilter::TurboFilterQuery.new(filters_for_class, session[:query][:filters])
|
14
|
+
build_turbo_filter_query_from_params
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def retrieve_turbo_filter_query_from_session
|
19
|
+
if session[:query]
|
20
|
+
@turbo_filter_query = TurboFilter::TurboFilterQuery.new(filters_for_class, session[:query][:filters])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_turbo_filter_query_from_params
|
25
|
+
if params[:f]
|
26
|
+
filters = if session[:query]
|
27
|
+
session[:query][:filters] = session[:query][:filters].select { |k,v| params[:f].reject(&:blank?).include?(k) }
|
28
|
+
else
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
@turbo_filter_query.filters = filters
|
32
|
+
@turbo_filter_query.add_filters(params[:f], params[:operators] || params[:op], params[:values] || params[:v])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module TurboFilter
|
2
|
+
module TurboFilterHelper
|
3
|
+
|
4
|
+
def turbo_filters
|
5
|
+
render :partial => 'turbo_filters/filters', :layout => false, :locals => {:turbo_filter_query => @turbo_filter_query}
|
6
|
+
end
|
7
|
+
|
8
|
+
def filters_options_for_select(query)
|
9
|
+
options_for_select(filters_options(query))
|
10
|
+
end
|
11
|
+
|
12
|
+
def filters_options(query)
|
13
|
+
options = [[]]
|
14
|
+
options += query.available_filters.map do |field, field_options|
|
15
|
+
[field_options[:name], field]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Helper to render JSON in views
|
20
|
+
def raw_json(arg)
|
21
|
+
arg.to_json.to_s.gsub('/', '\/').html_safe
|
22
|
+
end
|
23
|
+
|
24
|
+
def link_to_function(name, function, html_options={})
|
25
|
+
content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,353 @@
|
|
1
|
+
module TurboFilter
|
2
|
+
class TurboFilterQuery
|
3
|
+
|
4
|
+
class_attribute :filters
|
5
|
+
class_attribute :operators
|
6
|
+
self.operators = {
|
7
|
+
"=" => :label_equals,
|
8
|
+
"!" => :label_not_equals,
|
9
|
+
"!*" => :label_none,
|
10
|
+
"*" => :label_any,
|
11
|
+
">=" => :label_greater_or_equal,
|
12
|
+
"<=" => :label_less_or_equal,
|
13
|
+
"><" => :label_between,
|
14
|
+
"<t+" => :label_in_less_than,
|
15
|
+
">t+" => :label_in_more_than,
|
16
|
+
"><t+"=> :label_in_the_next_days,
|
17
|
+
"t+" => :label_in,
|
18
|
+
"t" => :label_today,
|
19
|
+
"ld" => :label_yesterday,
|
20
|
+
"w" => :label_this_week,
|
21
|
+
"lw" => :label_last_week,
|
22
|
+
"l2w" => [:label_last_n_weeks, {:count => 2}],
|
23
|
+
"m" => :label_this_month,
|
24
|
+
"lm" => :label_last_month,
|
25
|
+
"y" => :label_this_year,
|
26
|
+
">t-" => :label_less_than_ago,
|
27
|
+
"<t-" => :label_more_than_ago,
|
28
|
+
"><t-"=> :label_in_the_past_days,
|
29
|
+
"t-" => :label_ago,
|
30
|
+
"~" => :label_contains,
|
31
|
+
"!~" => :label_not_contains
|
32
|
+
}
|
33
|
+
|
34
|
+
class_attribute :operators_by_filter_type
|
35
|
+
self.operators_by_filter_type = {
|
36
|
+
:list => [ "=", "!" ],
|
37
|
+
:list_optional => [ "=", "!", "!*", "*" ],
|
38
|
+
:date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
|
39
|
+
:date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
|
40
|
+
:string => [ "=", "~", "!", "!~", "!*", "*" ],
|
41
|
+
:text => [ "~", "!~", "!*", "*" ],
|
42
|
+
:integer => [ "=", ">=", "<=", "><", "!*", "*" ],
|
43
|
+
:float => [ "=", ">=", "<=", "><", "!*", "*" ]
|
44
|
+
}
|
45
|
+
|
46
|
+
# Returns a hash of localized labels for all filter operators
|
47
|
+
def self.operators_labels
|
48
|
+
operators.inject({}) {|h, operator| h[operator.first] = I18n.t(*operator.last); h}
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(filters_for_class=nil,filters={})
|
52
|
+
@filters_for_class = filters_for_class
|
53
|
+
self.filters = filters
|
54
|
+
end
|
55
|
+
|
56
|
+
# Adds available filters
|
57
|
+
def initialize_available_filters
|
58
|
+
if @filters_for_class.is_a?(Class) && (@filters_for_class.superclass == ActiveRecord::Base)
|
59
|
+
associations = @filters_for_class.reflect_on_all_associations(:belongs_to)
|
60
|
+
association_foreign_keys = associations.map(&:foreign_key)
|
61
|
+
@filters_for_class.columns.each do |col|
|
62
|
+
case col.type
|
63
|
+
when :string
|
64
|
+
add_available_filter col.name, :type => :text
|
65
|
+
when :date
|
66
|
+
add_available_filter col.name, :type => :date
|
67
|
+
when :datetime
|
68
|
+
add_available_filter col.name, :type => :date_past
|
69
|
+
when :float
|
70
|
+
add_available_filter col.name, :type => :float
|
71
|
+
when :integer
|
72
|
+
if association_foreign_keys.include?(col.name)
|
73
|
+
association_class = associations.select { |a| a.foreign_key == col.name }.first.class_name.constantize
|
74
|
+
association_values = association_class.all.collect{|s| [s.to_s, s.id.to_s] }
|
75
|
+
add_available_filter col.name, :type => :list, :values => association_values
|
76
|
+
else
|
77
|
+
add_available_filter col.name, :type => :integer
|
78
|
+
end
|
79
|
+
when :boolean
|
80
|
+
add_available_filter col.name, :type => :list, :values => [["Yes","1"],["No", "0"]]
|
81
|
+
end # case
|
82
|
+
end # do
|
83
|
+
end
|
84
|
+
end
|
85
|
+
protected :initialize_available_filters
|
86
|
+
|
87
|
+
# Adds an available filter
|
88
|
+
def add_available_filter(field, options)
|
89
|
+
@available_filters ||= ActiveSupport::OrderedHash.new
|
90
|
+
@available_filters[field] = options
|
91
|
+
@available_filters
|
92
|
+
end
|
93
|
+
|
94
|
+
# Removes an available filter
|
95
|
+
def delete_available_filter(field)
|
96
|
+
if @available_filters
|
97
|
+
@available_filters.delete(field)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Return a hash of available filters
|
102
|
+
def available_filters
|
103
|
+
unless @available_filters
|
104
|
+
initialize_available_filters
|
105
|
+
@available_filters.to_a.each do |field, options|
|
106
|
+
options[:name] ||= I18n.t("field_#{field}".gsub(/_id$/, ''))
|
107
|
+
end
|
108
|
+
end
|
109
|
+
@available_filters
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_filter(field, operator, values=nil)
|
113
|
+
# values must be an array
|
114
|
+
return unless values.nil? || values.is_a?(Array)
|
115
|
+
# check if field is defined as an available filter
|
116
|
+
if available_filters.has_key? field
|
117
|
+
filter_options = available_filters[field]
|
118
|
+
filters[field] = {:operator => operator, :values => (values || [''])}
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def add_short_filter(field, expression)
|
123
|
+
return unless expression && available_filters.has_key?(field)
|
124
|
+
field_type = available_filters[field][:type]
|
125
|
+
operators_by_filter_type[field_type].sort.reverse.detect do |operator|
|
126
|
+
next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
|
127
|
+
values = $1
|
128
|
+
add_filter field, operator, values.present? ? values.split('|') : ['']
|
129
|
+
end || add_filter(field, '=', expression.split('|'))
|
130
|
+
end
|
131
|
+
|
132
|
+
# Add multiple filters using +add_filter+
|
133
|
+
def add_filters(fields, operators, values)
|
134
|
+
if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
|
135
|
+
fields.each do |field|
|
136
|
+
add_filter(field, operators[field], values && values[field])
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def has_filter?(field)
|
142
|
+
filters and filters[field]
|
143
|
+
end
|
144
|
+
|
145
|
+
def type_for(field)
|
146
|
+
available_filters[field][:type] if available_filters.has_key?(field)
|
147
|
+
end
|
148
|
+
|
149
|
+
def operator_for(field)
|
150
|
+
has_filter?(field) ? filters[field][:operator] : nil
|
151
|
+
end
|
152
|
+
|
153
|
+
def values_for(field)
|
154
|
+
has_filter?(field) ? filters[field][:values] : nil
|
155
|
+
end
|
156
|
+
|
157
|
+
def value_for(field, index=0)
|
158
|
+
(values_for(field) || [])[index]
|
159
|
+
end
|
160
|
+
|
161
|
+
def label_for(field)
|
162
|
+
label = available_filters[field][:name] if available_filters.has_key?(field)
|
163
|
+
label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns a representation of the available filters for JSON serialization
|
167
|
+
def available_filters_as_json
|
168
|
+
json = {}
|
169
|
+
available_filters.to_a.each do |field, options|
|
170
|
+
json[field] = options.slice(:type, :name, :values).stringify_keys
|
171
|
+
end
|
172
|
+
json
|
173
|
+
end
|
174
|
+
|
175
|
+
def statement
|
176
|
+
# filters clauses
|
177
|
+
filters_clauses = []
|
178
|
+
filters.each_key do |field|
|
179
|
+
v = values_for(field).clone
|
180
|
+
next unless v and !v.empty?
|
181
|
+
operator = operator_for(field)
|
182
|
+
|
183
|
+
filters_clauses << '(' + sql_for_field(field, operator, v, @filters_for_class.table_name, field) + ')'
|
184
|
+
end if filters
|
185
|
+
|
186
|
+
filters_clauses.reject!(&:blank?)
|
187
|
+
|
188
|
+
filters_clauses.any? ? filters_clauses.join(' AND ') : nil
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
# Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
|
193
|
+
def sql_for_field(field, operator, value, db_table, db_field)
|
194
|
+
sql = ''
|
195
|
+
case operator
|
196
|
+
when "="
|
197
|
+
if value.any?
|
198
|
+
case type_for(field)
|
199
|
+
when :date, :date_past
|
200
|
+
sql = date_clause(db_table, db_field, parse_date(value.first), parse_date(value.first))
|
201
|
+
when :integer
|
202
|
+
sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
|
203
|
+
when :float
|
204
|
+
sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
|
205
|
+
else
|
206
|
+
sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{@filters_for_class.connection.quote_string(val)}'"}.join(",") + ")"
|
207
|
+
end
|
208
|
+
else
|
209
|
+
# IN an empty set
|
210
|
+
sql = "1=0"
|
211
|
+
end
|
212
|
+
when "!"
|
213
|
+
if value.any?
|
214
|
+
sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{@filters_for_class.connection.quote_string(val)}'"}.join(",") + "))"
|
215
|
+
else
|
216
|
+
# NOT IN an empty set
|
217
|
+
sql = "1=1"
|
218
|
+
end
|
219
|
+
when "!*"
|
220
|
+
sql = "#{db_table}.#{db_field} IS NULL"
|
221
|
+
when "*"
|
222
|
+
sql = "#{db_table}.#{db_field} IS NOT NULL"
|
223
|
+
when ">="
|
224
|
+
if [:date, :date_past].include?(type_for(field))
|
225
|
+
sql = date_clause(db_table, db_field, parse_date(value.first), nil)
|
226
|
+
else
|
227
|
+
sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
|
228
|
+
end
|
229
|
+
when "<="
|
230
|
+
if [:date, :date_past].include?(type_for(field))
|
231
|
+
sql = date_clause(db_table, db_field, nil, parse_date(value.first))
|
232
|
+
else
|
233
|
+
sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
|
234
|
+
end
|
235
|
+
when "><"
|
236
|
+
if [:date, :date_past].include?(type_for(field))
|
237
|
+
sql = date_clause(db_table, db_field, parse_date(value[0]), parse_date(value[1]))
|
238
|
+
else
|
239
|
+
sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
|
240
|
+
end
|
241
|
+
when "><t-"
|
242
|
+
# between today - n days and today
|
243
|
+
sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
|
244
|
+
when ">t-"
|
245
|
+
# >= today - n days
|
246
|
+
sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil)
|
247
|
+
when "<t-"
|
248
|
+
# <= today - n days
|
249
|
+
sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
|
250
|
+
when "t-"
|
251
|
+
# = n days in past
|
252
|
+
sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
|
253
|
+
when "><t+"
|
254
|
+
# between today and today + n days
|
255
|
+
sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
|
256
|
+
when ">t+"
|
257
|
+
# >= today + n days
|
258
|
+
sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
|
259
|
+
when "<t+"
|
260
|
+
# <= today + n days
|
261
|
+
sql = relative_date_clause(db_table, db_field, nil, value.first.to_i)
|
262
|
+
when "t+"
|
263
|
+
# = today + n days
|
264
|
+
sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
|
265
|
+
when "t"
|
266
|
+
# = today
|
267
|
+
sql = relative_date_clause(db_table, db_field, 0, 0)
|
268
|
+
when "ld"
|
269
|
+
# = yesterday
|
270
|
+
sql = relative_date_clause(db_table, db_field, -1, -1)
|
271
|
+
when "w"
|
272
|
+
# = this week
|
273
|
+
first_day_of_week = l(:general_first_day_of_week).to_i
|
274
|
+
day_of_week = Date.today.cwday
|
275
|
+
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
|
276
|
+
sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
|
277
|
+
when "lw"
|
278
|
+
# = last week
|
279
|
+
first_day_of_week = l(:general_first_day_of_week).to_i
|
280
|
+
day_of_week = Date.today.cwday
|
281
|
+
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
|
282
|
+
sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1)
|
283
|
+
when "l2w"
|
284
|
+
# = last 2 weeks
|
285
|
+
first_day_of_week = l(:general_first_day_of_week).to_i
|
286
|
+
day_of_week = Date.today.cwday
|
287
|
+
days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
|
288
|
+
sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1)
|
289
|
+
when "m"
|
290
|
+
# = this month
|
291
|
+
date = Date.today
|
292
|
+
sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
|
293
|
+
when "lm"
|
294
|
+
# = last month
|
295
|
+
date = Date.today.prev_month
|
296
|
+
sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
|
297
|
+
when "y"
|
298
|
+
# = this year
|
299
|
+
date = Date.today
|
300
|
+
sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year)
|
301
|
+
when "~"
|
302
|
+
sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{@filters_for_class.connection.quote_string(value.first.to_s.downcase)}%'"
|
303
|
+
when "!~"
|
304
|
+
sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{@filters_for_class.connection.quote_string(value.first.to_s.downcase)}%'"
|
305
|
+
else
|
306
|
+
raise "Unknown query operator #{operator}"
|
307
|
+
end
|
308
|
+
|
309
|
+
return sql
|
310
|
+
end
|
311
|
+
|
312
|
+
# Returns a SQL clause for a date or datetime field.
|
313
|
+
def date_clause(table, field, from, to)
|
314
|
+
s = []
|
315
|
+
if from
|
316
|
+
if from.is_a?(Date)
|
317
|
+
from = Time.local(from.year, from.month, from.day).yesterday.end_of_day
|
318
|
+
else
|
319
|
+
from = from - 1 # second
|
320
|
+
end
|
321
|
+
if @filters_for_class.default_timezone == :utc
|
322
|
+
from = from.utc
|
323
|
+
end
|
324
|
+
s << ("#{table}.#{field} > '%s'" % [@filters_for_class.connection.quoted_date(from)])
|
325
|
+
end
|
326
|
+
if to
|
327
|
+
if to.is_a?(Date)
|
328
|
+
to = Time.local(to.year, to.month, to.day).end_of_day
|
329
|
+
end
|
330
|
+
if @filters_for_class.default_timezone == :utc
|
331
|
+
to = to.utc
|
332
|
+
end
|
333
|
+
s << ("#{table}.#{field} <= '%s'" % [@filters_for_class.connection.quoted_date(to)])
|
334
|
+
end
|
335
|
+
s.join(' AND ')
|
336
|
+
end
|
337
|
+
|
338
|
+
# Returns a SQL clause for a date or datetime field using relative dates.
|
339
|
+
def relative_date_clause(table, field, days_from, days_to)
|
340
|
+
date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
|
341
|
+
end
|
342
|
+
|
343
|
+
# Returns a Date or Time from the given filter value
|
344
|
+
def parse_date(arg)
|
345
|
+
if arg.to_s =~ /\A\d{4}-\d{2}-\d{2}T/
|
346
|
+
Time.parse(arg) rescue nil
|
347
|
+
else
|
348
|
+
Date.parse(arg) rescue nil
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
end
|
data/lib/turbo_filter/version.rb
CHANGED
data/lib/turbo_filter.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
|
-
require
|
1
|
+
require 'turbo_filter/engine'
|
2
|
+
require 'turbo_filter/version'
|
3
|
+
require 'turbo_filter/turbo_filter_controller'
|
4
|
+
require 'turbo_filter/turbo_filter_helper'
|
5
|
+
require 'turbo_filter/turbo_filter_query'
|
6
|
+
|
7
|
+
ActiveSupport.on_load(:action_view) do
|
8
|
+
::ActionView::Base.send :include, TurboFilter::TurboFilterHelper
|
9
|
+
end
|
10
|
+
::ActionController::Base.send :include, TurboFilter::TurboFilterController
|
2
11
|
|
3
12
|
module TurboFilter
|
4
|
-
# Your code goes here...
|
5
13
|
end
|
metadata
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbo_filter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Sandeep Kumar
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2016-01-18 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: bundler
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
17
|
- - ~>
|
20
18
|
- !ruby/object:Gem::Version
|
@@ -22,7 +20,6 @@ dependencies:
|
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
24
|
- - ~>
|
28
25
|
- !ruby/object:Gem::Version
|
@@ -30,7 +27,6 @@ dependencies:
|
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rake
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
31
|
- - ! '>='
|
36
32
|
- !ruby/object:Gem::Version
|
@@ -38,7 +34,6 @@ dependencies:
|
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
38
|
- - ! '>='
|
44
39
|
- !ruby/object:Gem::Version
|
@@ -55,33 +50,39 @@ files:
|
|
55
50
|
- LICENSE.txt
|
56
51
|
- README.md
|
57
52
|
- Rakefile
|
53
|
+
- app/assets/javascripts/turbo_filter.js
|
54
|
+
- app/views/turbo_filters/_filters.html.erb
|
55
|
+
- config/locales/en.yml
|
58
56
|
- lib/turbo_filter.rb
|
57
|
+
- lib/turbo_filter/engine.rb
|
58
|
+
- lib/turbo_filter/turbo_filter_controller.rb
|
59
|
+
- lib/turbo_filter/turbo_filter_helper.rb
|
60
|
+
- lib/turbo_filter/turbo_filter_query.rb
|
59
61
|
- lib/turbo_filter/version.rb
|
60
62
|
- turbo_filter.gemspec
|
61
63
|
homepage: ''
|
62
64
|
licenses:
|
63
65
|
- MIT
|
66
|
+
metadata: {}
|
64
67
|
post_install_message:
|
65
68
|
rdoc_options: []
|
66
69
|
require_paths:
|
67
70
|
- lib
|
68
71
|
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
-
none: false
|
70
72
|
requirements:
|
71
73
|
- - ! '>='
|
72
74
|
- !ruby/object:Gem::Version
|
73
75
|
version: '0'
|
74
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
-
none: false
|
76
77
|
requirements:
|
77
78
|
- - ! '>='
|
78
79
|
- !ruby/object:Gem::Version
|
79
80
|
version: '0'
|
80
81
|
requirements: []
|
81
82
|
rubyforge_project:
|
82
|
-
rubygems_version:
|
83
|
+
rubygems_version: 2.4.8
|
83
84
|
signing_key:
|
84
|
-
specification_version:
|
85
|
+
specification_version: 4
|
85
86
|
summary: Filter records
|
86
87
|
test_files: []
|
87
88
|
has_rdoc:
|