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