ransack_ui 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/app/assets/javascripts/ransack_ui_jquery/search_form.js.coffee.erb +213 -116
- data/app/views/ransack_ui/_search.html.erb +4 -3
- data/lib/ransack_ui/ransack_overrides/helpers/form_builder.rb +109 -25
- data/lib/ransack_ui/version.rb +1 -1
- data/lib/ransack_ui.rb +0 -1
- metadata +5 -18
- data/lib/core_ext/enumerable.rb +0 -3
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NzUxNDFiZWU1YTllYzkwMTNjZmU2ZTJmZmExOTVlMzI0OWQ4YzQ1Yg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NWM3YzRiZWVjMGZjZGY3NTQ2ODg2NzlhZmM2NzlhYzYyNDc0M2Y1Ng==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
OGVhZGIxMDMxZjc3YWQ3YzYwZDk2MjIyOTJiOTZjZmQ2NWQ3MGI5MTQxNTcy
|
10
|
+
YjMzNDYwM2U4MDE4NDA1ZDU5OGI0NGNkZGMwMGE2NGNiZGZkODdkZTdlZTc4
|
11
|
+
MTEzOGVlOWUzMjc1ODNjZDUzNDFlN2U2NGVkYjI2MzgxMDZiYmQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MDgxY2Y0YzllYTQyMDM1ZGM0NTZkNzI5YWExYTVjNGM2NDhmZDA4NmE4ZjVl
|
14
|
+
OTRiMzgzMjEzNjFjYTc1ZWY0MzAzYzIzZjQ0MGZhOGM4NDBjY2NiMGQ2MDVm
|
15
|
+
YjdjOTUzOTYxYTZkY2M4MGI0NjBhMjc4ODJhNjRlYTRhMjM5NGE=
|
@@ -22,61 +22,57 @@
|
|
22
22
|
# show spinner and disable the form when the search is underway
|
23
23
|
el.find("form input:submit").click $.proxy(@form_submit, this)
|
24
24
|
|
25
|
-
# Fire change event for any existing selects
|
26
|
-
|
25
|
+
# Fire change event for any existing attribute selects,
|
26
|
+
# set initialize to true so that existing queries are not cleared.
|
27
|
+
el.find(".filters select.ransack_attribute").each (i, el) =>
|
28
|
+
@attribute_changed({currentTarget: el}, true)
|
27
29
|
|
28
|
-
attribute_changed: (e) ->
|
30
|
+
attribute_changed: (e, initialize = false) ->
|
29
31
|
target = $(e.currentTarget)
|
30
|
-
|
31
|
-
column_type =
|
32
|
+
selected_attribute = target.find('option:selected')
|
33
|
+
column_type = selected_attribute.data('type')
|
32
34
|
|
33
35
|
base_id = target.attr('id').slice(0, -8)
|
34
36
|
predicate_select = @element.find("select##{base_id}p")
|
35
37
|
available = predicate_select.data['predicates']
|
36
38
|
query_input = $("input##{base_id}v_0_value")
|
39
|
+
multi_id = query_input.attr('id') + '_multi'
|
40
|
+
multi_input = @element.find("##{multi_id}")
|
41
|
+
query_select2_id = "#s2id_#{base_id}v_0_value"
|
42
|
+
query_select2 = @element.find(query_select2_id)
|
43
|
+
predicate_select2_id = "#s2id_#{base_id}p"
|
44
|
+
predicate_select2 = @element.find(predicate_select2_id)
|
37
45
|
|
38
46
|
# Initialize datepicker if column is date/datetime/time
|
39
47
|
$.proxy(@init_datetimepicker, this)(base_id)
|
40
48
|
|
49
|
+
# Clear input value unless this is the first run
|
50
|
+
unless initialize
|
51
|
+
query_input.val('')
|
52
|
+
|
53
|
+
# Destroy any query select2 inputs on attribute change and clear input
|
54
|
+
if query_select2.length
|
55
|
+
query_input.select2('destroy')
|
56
|
+
|
57
|
+
# Destroy any multi-inputs on attribute change and clear input
|
58
|
+
if multi_input.length
|
59
|
+
@destroy_multi_input(multi_input, selected_attribute.val())
|
60
|
+
|
41
61
|
# Handle association columns with AJAX autocomplete
|
42
|
-
if
|
43
|
-
controller = selected.data('controller')
|
62
|
+
if selected_attribute.data('ajax-url') and Select2?
|
44
63
|
@set_option_predicates(base_id, available, column_type)
|
45
64
|
|
46
|
-
# Set up Select2 for query input
|
47
|
-
query_input.val('')
|
48
|
-
query_input.select2
|
49
|
-
placeholder: "Search #{selected.data('ajax-entity')}"
|
50
|
-
minimumInputLength: 1
|
51
|
-
allowClear: true
|
52
|
-
ajax:
|
53
|
-
url: selected.data('ajax-url')
|
54
|
-
dataType: 'json'
|
55
|
-
type: selected.data('ajax-type')
|
56
|
-
data: (query, page) ->
|
57
|
-
obj = {}
|
58
|
-
obj[selected.data('ajax-key')] = query
|
59
|
-
obj
|
60
|
-
results: (data, page) ->
|
61
|
-
{results: $.map(data, (text, id) -> {id: id, text: text}) }
|
62
|
-
|
63
65
|
# Handle columns with options detected from validates :inclusion
|
64
|
-
else if
|
65
|
-
@set_option_predicates(base_id, available, column_type)
|
66
|
-
query_input.val('')
|
67
|
-
query_input.select2
|
68
|
-
data: selected.data('select-options')
|
69
|
-
placeholder: "Please select a #{selected.val()}"
|
70
|
-
allowClear: true
|
66
|
+
else if selected_attribute.data('select-options') and Select2?
|
67
|
+
@set_option_predicates(base_id, available, column_type, true)
|
71
68
|
|
72
69
|
# Handle regular columns
|
73
70
|
else
|
74
71
|
if Select2?
|
75
|
-
predicate_select2 = @element.find("#s2id_#{base_id}p")
|
76
72
|
predicate_select2.select2("enable")
|
77
73
|
|
78
74
|
# If Select2 is on query input, remove and set defaults
|
79
|
-
if
|
75
|
+
if query_select2.length > 0
|
80
76
|
query_input.select2('destroy')
|
81
77
|
query_input.val('')
|
82
78
|
previous_val = ''
|
@@ -109,8 +105,8 @@
|
|
109
105
|
if Select2?
|
110
106
|
predicate_select.select2('val', previous_val)
|
111
107
|
|
112
|
-
|
113
|
-
|
108
|
+
# Run predicate_changed callback
|
109
|
+
predicate_select.change()
|
114
110
|
|
115
111
|
return true
|
116
112
|
|
@@ -124,86 +120,111 @@
|
|
124
120
|
attribute_select = @element.find("select##{base_id}a_0_name")
|
125
121
|
selected_attribute = attribute_select.find('option:selected')
|
126
122
|
|
123
|
+
query_select2_id = "#s2id_#{base_id}v_0_value"
|
124
|
+
query_select2 = @element.find(query_select2_id)
|
125
|
+
query_select2_multi_id = "#s2id_#{base_id}v_0_value_multi"
|
126
|
+
query_select2_multi = @element.find(query_select2_multi_id)
|
127
|
+
|
128
|
+
no_query_predicates = ["true", "false", "blank", "present", "null", "not_null"]
|
129
|
+
|
127
130
|
# We need to use a dummy input to handle multiple terms
|
128
131
|
multi_id = query_input.attr('id') + '_multi'
|
129
132
|
multi_input = @element.find("##{multi_id}")
|
130
133
|
|
134
|
+
# If query was previously hidden, clear query input
|
135
|
+
if query_select2.length == 0 && multi_input.length == 0 && query_input.is(":hidden")
|
136
|
+
query_input.val('')
|
137
|
+
|
138
|
+
# Hide query input when not needed
|
139
|
+
if p in no_query_predicates
|
140
|
+
# If Select2 is on query input, remove and set defaults
|
141
|
+
if Select2? && query_select2.length
|
142
|
+
query_input.select2('destroy')
|
143
|
+
|
144
|
+
query_input.val("true")
|
145
|
+
query_input.hide()
|
146
|
+
query_input.parent().find('.ui-datepicker-trigger').hide()
|
147
|
+
|
131
148
|
if Select2?
|
132
149
|
# Turn query input into Select2 tag list when query accepts multiple values
|
133
150
|
if p in ["in", "not_in"] || p.match(/_(all|any)$/)
|
134
|
-
# If Select2 is on query input, remove and set defaults
|
135
|
-
if @element.find("#s2id_#{base_id}v_0_value").length
|
136
|
-
query_input.select2('destroy')
|
137
|
-
|
138
151
|
# Add dummy 'multi' input for select2 if not already added
|
139
|
-
if multi_input.length == 0
|
152
|
+
if multi_input.length == 0 && query_select2_multi.length == 0
|
140
153
|
# Set up multi-query input with fixed options, if present
|
141
|
-
query_input
|
142
|
-
|
143
|
-
|
154
|
+
@setup_multi_query_input(target, query_input, multi_id, selected_attribute)
|
155
|
+
|
156
|
+
# If Select2 is on query input, remove and set defaults
|
157
|
+
if query_select2.length
|
158
|
+
query_input.select2('destroy').hide()
|
144
159
|
|
145
160
|
return
|
146
161
|
|
147
|
-
else
|
162
|
+
else
|
163
|
+
# Otherwise, remove Select2 from multi-query input, and remove input.
|
148
164
|
if multi_input.length
|
149
|
-
#
|
150
|
-
multi_input.select2('
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
inputs = inputs.slice(1)
|
155
|
-
inputs.remove()
|
156
|
-
|
157
|
-
# If no fixed options, show query input
|
158
|
-
query_input.val('').show()
|
159
|
-
|
160
|
-
# Handle fixed options - set up Select2 for single values if not already present
|
161
|
-
if selected_attribute.data('select-options')
|
162
|
-
if (query_select2 = @element.find("#s2id_#{base_id}v_0_value")).length == 0
|
163
|
-
query_input.select2
|
164
|
-
data: selected_attribute.data('select-options')
|
165
|
-
placeholder: "Please select a #{selected_attribute.val()}"
|
166
|
-
allowClear: true
|
165
|
+
# Save label data from first value
|
166
|
+
if multi_input.select2('data') && multi_input.select2('data').length
|
167
|
+
multi_input_data = multi_input.select2('data').first()
|
168
|
+
Ransack.value_field_labels[selected_attribute.val()] ||= {}
|
169
|
+
Ransack.value_field_labels[selected_attribute.val()][multi_input_data.id] = multi_input_data.text
|
167
170
|
|
168
|
-
|
171
|
+
@destroy_multi_input(multi_input, selected_attribute.val())
|
169
172
|
|
173
|
+
if p not in no_query_predicates
|
174
|
+
query_input.show()
|
170
175
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
176
|
+
# Handle association columns with AJAX autocomplete
|
177
|
+
if selected_attribute.data('ajax-url')
|
178
|
+
if query_select2.length
|
179
|
+
query_input.hide()
|
180
|
+
else
|
181
|
+
@setup_select2_association(query_input, selected_attribute)
|
182
|
+
|
183
|
+
# Handle fixed options - set up Select2 for single values if not already present
|
184
|
+
if selected_attribute.data('select-options')
|
185
|
+
if query_select2.length
|
186
|
+
query_input.hide()
|
187
|
+
else
|
188
|
+
@setup_select2_options(query_input, selected_attribute)
|
177
189
|
|
178
|
-
query_input.val("true")
|
179
|
-
query_input.hide()
|
180
|
-
query_input.parent().find('.ui-datepicker-trigger').hide()
|
181
190
|
# Otherwise, reset query input and show datepicker trigger if present
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
191
|
+
if p not in no_query_predicates
|
192
|
+
return if selected_attribute.data('select-options')
|
193
|
+
|
194
|
+
# Don't show query input if ajax auto complete is present on selected attribute
|
195
|
+
unless p in ['eq', 'not_eq'] and selected_attribute.data('ajax-url')
|
196
|
+
unless query_input.is(":visible")
|
197
|
+
query_input.val('')
|
198
|
+
query_input.show()
|
199
|
+
query_input.parent().find('.ui-datepicker-trigger').show()
|
187
200
|
|
188
201
|
|
189
|
-
#
|
190
|
-
set_option_predicates: (base_id,
|
202
|
+
# Disables predicate choices and sets it to 'eq'
|
203
|
+
set_option_predicates: (base_id, available_predicates, column_type, include_number_predicates = false) ->
|
191
204
|
predicate_select = @element.find("select##{base_id}p")
|
205
|
+
previous_val = predicate_select.val()
|
192
206
|
|
193
207
|
# Remove all predicates, and add any supported predicates
|
194
208
|
predicate_select.find('option').each (i, o) -> $(o).remove()
|
195
209
|
|
196
|
-
|
210
|
+
allowed_predicates = $.merge([], Ransack.option_predicates)
|
211
|
+
|
212
|
+
# Include number predicates if the option was given.
|
213
|
+
# For example, a integer column will have fixed select options,
|
214
|
+
# but will also allow less than and greater than.
|
215
|
+
if column_type in ['integer', 'float', 'decimal'] && include_number_predicates
|
216
|
+
allowed_predicates = allowed_predicates.concat(Ransack.type_predicates[column_type] || [])
|
217
|
+
|
218
|
+
$.each available_predicates, (i, p) =>
|
197
219
|
[predicate, label] = [p[0], p[1]]
|
198
220
|
|
199
|
-
if predicate in
|
221
|
+
if predicate in allowed_predicates
|
200
222
|
# Get alternative predicate label depending on column type
|
201
223
|
label = @alt_predicate_label_or_default(predicate, column_type, label)
|
202
224
|
predicate_select.append $("<option value=#{predicate}>#{label}</option>")
|
203
225
|
|
204
|
-
if
|
205
|
-
|
206
|
-
predicate_select2.select2('val', 'eq')
|
226
|
+
# Select first predicate if current selection is invalid
|
227
|
+
predicate_select.select2('val', previous_val)
|
207
228
|
|
208
229
|
# Attempts to find a predicate translation for the specific column type,
|
209
230
|
# or returns the default label.
|
@@ -246,17 +267,20 @@
|
|
246
267
|
$.each values, (i, v) =>
|
247
268
|
@add_query_input(target, base_name, i + 1, v)
|
248
269
|
|
249
|
-
setup_multi_query_input: (predicate_el,
|
270
|
+
setup_multi_query_input: (predicate_el, query_input, multi_id, selected_attribute) ->
|
250
271
|
base_name = predicate_el.attr('name').slice(0, -3) + '[v]'
|
251
|
-
|
272
|
+
base_id = predicate_el.attr('id').slice(0, -1)
|
273
|
+
query_input.after(
|
252
274
|
$('<input class="ransack_query_multi" id="' + multi_id + '" ' +
|
253
|
-
'style="width:' + (
|
275
|
+
'style="width:' + (query_input.width() * 2) + 'px;" ' +
|
254
276
|
'data-base-name="' + base_name + '" />'))
|
255
277
|
|
278
|
+
query_select2_id = "#s2id_#{base_id}v_0_value"
|
279
|
+
query_select2 = @element.find(query_select2_id)
|
280
|
+
|
256
281
|
# Fetch all existing values
|
257
282
|
inputs = @element.find("input[name^=\"#{base_name}\"]")
|
258
|
-
values = $.map inputs, (el) ->
|
259
|
-
el.value
|
283
|
+
values = $.map inputs, (el) -> el.value
|
260
284
|
# Hide all query inputs
|
261
285
|
inputs.hide()
|
262
286
|
|
@@ -264,37 +288,102 @@
|
|
264
288
|
# Find newly created input and setup Select2
|
265
289
|
multi_query = @element.find('#' + multi_id)
|
266
290
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
(t) -> "Add a search term"
|
278
|
-
initSelection: (element, callback) ->
|
279
|
-
data = []
|
280
|
-
unless element.val().trim() == ""
|
281
|
-
$(element.val().split(",")).each ->
|
282
|
-
data.push {id: this, text: this}
|
283
|
-
callback(data)
|
291
|
+
# Handle association columns with AJAX autocomplete
|
292
|
+
if selected_attribute.data('ajax-url')
|
293
|
+
# Set label to single association label, if anything was selected
|
294
|
+
if query_select2.length && query_input.select2('data')
|
295
|
+
query_input_data = query_input.select2('data')
|
296
|
+
Ransack.value_field_labels[selected_attribute.val()] ||= {}
|
297
|
+
Ransack.value_field_labels[selected_attribute.val()][query_input_data.id] = query_input_data.text
|
298
|
+
|
299
|
+
@setup_select2_association(multi_query, selected_attribute, true)
|
300
|
+
|
284
301
|
else
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
302
|
+
if selected_attribute.data('select-options')
|
303
|
+
# Setup Select2 with fixed multiple options
|
304
|
+
@setup_select2_options(multi_query, selected_attribute, true)
|
305
|
+
|
306
|
+
else
|
307
|
+
# Setup Select2 with tagging support (can create options)
|
308
|
+
multi_query.select2
|
309
|
+
tags: []
|
310
|
+
tokenSeparators: [',']
|
311
|
+
formatNoMatches: (t) ->
|
312
|
+
"Add a search term"
|
291
313
|
|
292
314
|
multi_query.select2('val', values)
|
293
315
|
|
316
|
+
setup_select2_association: (query_input, selected_attribute, multiple = false) ->
|
317
|
+
selected_attribute_val = selected_attribute.val()
|
318
|
+
# Set up Select2 for query input
|
319
|
+
query_input.select2
|
320
|
+
placeholder: "Search #{selected_attribute.data('ajax-entity')}"
|
321
|
+
minimumInputLength: 1
|
322
|
+
allowClear: true
|
323
|
+
multiple: multiple
|
324
|
+
ajax:
|
325
|
+
url: selected_attribute.data('ajax-url')
|
326
|
+
dataType: 'json'
|
327
|
+
type: selected_attribute.data('ajax-type')
|
328
|
+
data: (query, page) ->
|
329
|
+
obj = {}
|
330
|
+
obj[selected_attribute.data('ajax-key')] = query
|
331
|
+
obj
|
332
|
+
results: (data, page) ->
|
333
|
+
{results: $.map(data, (text, id) -> {id: id, text: text}) }
|
334
|
+
initSelection: (element, callback) ->
|
335
|
+
data = []
|
336
|
+
unless element.val().trim() == ""
|
337
|
+
$(element.val().split(",")).each (i, val) ->
|
338
|
+
label = Ransack.value_field_labels[selected_attribute_val]?[val]
|
339
|
+
if label
|
340
|
+
data.push {id: val, text: label}
|
341
|
+
else
|
342
|
+
data.push {id: val, text: val}
|
343
|
+
if data.length
|
344
|
+
callback(multiple and data or data[0])
|
345
|
+
else
|
346
|
+
# If no label could be found, clear value
|
347
|
+
element.select2('val', '')
|
348
|
+
|
349
|
+
setup_select2_options: (query_input, selected_attribute, multiple = false) ->
|
350
|
+
query_input.select2
|
351
|
+
data: selected_attribute.data('select-options')
|
352
|
+
placeholder: "Please select a #{selected_attribute.text()}"
|
353
|
+
allowClear: true
|
354
|
+
multiple: multiple
|
355
|
+
tokenSeparators: [',']
|
356
|
+
formatNoMatches:
|
357
|
+
if selected_attribute.data('select-options')
|
358
|
+
(t) -> "No matches found."
|
359
|
+
else
|
360
|
+
(t) -> "Add a search term"
|
361
|
+
initSelection: (element, callback) ->
|
362
|
+
data = []
|
363
|
+
unless element.val().trim() == ""
|
364
|
+
$(element.val().split(",")).each (i, val) ->
|
365
|
+
selected_attribute.data('select-options').each (option, i) ->
|
366
|
+
if option.id == val
|
367
|
+
data.push {id: option.id, text: option.text}
|
368
|
+
return false # Break out of inner each loop
|
369
|
+
|
370
|
+
if data.length
|
371
|
+
callback(multiple and data or data[0])
|
372
|
+
else
|
373
|
+
element.select2('val', '')
|
374
|
+
|
294
375
|
add_query_input: (base_input, base_name, id, value) ->
|
295
376
|
base_input.after $('<input name="'+base_name+'['+id+'][value]" '+
|
296
377
|
'value="'+value+'" style="display:none;" />')
|
297
378
|
|
379
|
+
destroy_multi_input: (multi_input, selected_attribute_val) ->
|
380
|
+
multi_input.select2('destroy').remove()
|
381
|
+
# Also remove all extra inputs
|
382
|
+
base_name = multi_input.data('base-name')
|
383
|
+
inputs = @element.find("input[name^=\"#{base_name}\"]")
|
384
|
+
inputs = inputs.slice(1)
|
385
|
+
inputs.remove()
|
386
|
+
|
298
387
|
form_submit: (e) ->
|
299
388
|
@element.css({ opacity: 0.4 })
|
300
389
|
true
|
@@ -330,7 +419,7 @@
|
|
330
419
|
|
331
420
|
store_initial_predicates: (container) ->
|
332
421
|
# Store current predicates in data attribute
|
333
|
-
predicate_select = container.find('select.ransack_predicate
|
422
|
+
predicate_select = container.find('select.ransack_predicate').first()
|
334
423
|
unless predicate_select.data['predicates']
|
335
424
|
predicates = []
|
336
425
|
predicate_select.find('option').each (i, o) ->
|
@@ -349,8 +438,16 @@
|
|
349
438
|
placeholder: "Select a Field"
|
350
439
|
allowClear: true
|
351
440
|
formatSelection: (object, container) ->
|
441
|
+
# If initializing and element is not present,
|
442
|
+
# search for option element in original select tag
|
443
|
+
if !object.element
|
444
|
+
this.element.find('option').each (i, option) ->
|
445
|
+
if option.value == object.id
|
446
|
+
object.element = option
|
447
|
+
return false
|
448
|
+
|
352
449
|
# Return 'Model: field' unless column is on root model
|
353
|
-
if
|
450
|
+
if $(object.element).data('root-model')
|
354
451
|
object.text
|
355
452
|
else
|
356
453
|
group_label = $(object.element).parent().attr('label')
|
@@ -372,7 +469,7 @@
|
|
372
469
|
init_datetimepicker: (base_id) ->
|
373
470
|
if $.ui?.timepicker?
|
374
471
|
query_input = @element.find("input##{base_id}v_0_value")
|
375
|
-
|
472
|
+
selected_attribute = @element.find("select##{base_id}a_0_name option:selected")
|
376
473
|
|
377
474
|
# Clear any datepicker from query input first
|
378
475
|
query_input.datepicker('destroy')
|
@@ -388,7 +485,7 @@
|
|
388
485
|
onClose: (date) -> $(this).val(date)
|
389
486
|
|
390
487
|
# Show datepicker button for dates
|
391
|
-
switch
|
488
|
+
switch selected_attribute.data('type')
|
392
489
|
when "date"
|
393
490
|
query_input.datepicker(datepicker_options)
|
394
491
|
when "datetime"
|
@@ -1,9 +1,10 @@
|
|
1
1
|
<%= search_form_for @ransack_search, :url => (options[:url] || url_for(:action => :index)),
|
2
2
|
:html => {:method => :get, :class => "ransack_search"}, :remote => !!options[:remote] do |f| %>
|
3
3
|
|
4
|
-
|
4
|
+
<%= javascript_tag do %>
|
5
5
|
if (window.Ransack == null) { window.Ransack = {}; }
|
6
|
-
Ransack.alt_predicates_i18n =
|
6
|
+
Ransack.alt_predicates_i18n = <%= I18n.translate(:"ransack.predicates.alt", :default => {}).to_json.html_safe %>;
|
7
|
+
Ransack.value_field_labels = <%= f.labels_for_value_fields.to_json.html_safe %>
|
7
8
|
<% end %>
|
8
9
|
|
9
10
|
<div class="row">
|
@@ -21,7 +22,7 @@
|
|
21
22
|
<div class="pull-right">
|
22
23
|
<%= hidden_field_tag :distinct, '1' %>
|
23
24
|
<%= hidden_field_tag :page, '1' %>
|
24
|
-
<%= f.submit t('ransack.submit'), :class => 'btn btn-primary btn-large' %>
|
25
|
+
<%= f.submit t('ransack.submit'), :class => 'btn btn-primary btn-large submit-search' %>
|
25
26
|
</div>
|
26
27
|
</div>
|
27
28
|
</div>
|
@@ -3,6 +3,9 @@ require 'ransack/helpers/form_builder'
|
|
3
3
|
module Ransack
|
4
4
|
module Helpers
|
5
5
|
FormBuilder.class_eval do
|
6
|
+
cattr_accessor :cached_searchable_attributes_for_base
|
7
|
+
self.cached_searchable_attributes_for_base = {}
|
8
|
+
|
6
9
|
def attribute_select(options = {}, html_options = {})
|
7
10
|
raise ArgumentError, "attribute_select must be called inside a search FormBuilder!" unless object.respond_to?(:context)
|
8
11
|
options[:include_blank] = true unless options.has_key?(:include_blank)
|
@@ -58,6 +61,44 @@ module Ransack
|
|
58
61
|
end
|
59
62
|
end
|
60
63
|
|
64
|
+
def labels_for_value_fields
|
65
|
+
labels = {}
|
66
|
+
|
67
|
+
object.groupings.each do |grouping|
|
68
|
+
grouping.conditions.each do |condition|
|
69
|
+
condition.values.each do |value|
|
70
|
+
# If value is present, and the attribute is an association,
|
71
|
+
# load the selected record and include the record name as a data attribute
|
72
|
+
if value.value.present?
|
73
|
+
condition_attributes = condition.attributes
|
74
|
+
if condition_attributes.any?
|
75
|
+
attribute = condition_attributes.first.name
|
76
|
+
klass_name = foreign_klass_for_attribute(attribute)
|
77
|
+
|
78
|
+
if klass_name
|
79
|
+
klass = klass_name.constantize
|
80
|
+
|
81
|
+
value_object = klass.find_by_id(value.value)
|
82
|
+
if value_object
|
83
|
+
labels[attribute] ||= {}
|
84
|
+
|
85
|
+
if value_object.respond_to? :full_name
|
86
|
+
labels[attribute][value.value] = value_object.full_name
|
87
|
+
elsif value_object.respond_to? :name
|
88
|
+
labels[attribute][value.value] = value_object.name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
labels
|
99
|
+
end
|
100
|
+
|
101
|
+
|
61
102
|
def predicate_keys(options)
|
62
103
|
keys = options[:compounds] ? Predicate.names : Predicate.names.reject {|k| k.match(/_(any|all)$/)}
|
63
104
|
if only = options[:only]
|
@@ -101,9 +142,6 @@ module Ransack
|
|
101
142
|
|
102
143
|
def attribute_collection_for_base(base)
|
103
144
|
klass = object.context.traverse(base)
|
104
|
-
foreign_keys = klass.reflect_on_all_associations.select(&:belongs_to?).
|
105
|
-
map_to({}) {|r, h| h[r.foreign_key.to_sym] = r.class_name }
|
106
|
-
|
107
145
|
ajax_options = Ransack.options[:ajax_options] || {}
|
108
146
|
|
109
147
|
# Detect any inclusion validators to build list of options for a column
|
@@ -112,41 +150,36 @@ module Ransack
|
|
112
150
|
v.attributes.each do |a|
|
113
151
|
# Try to translate options from activerecord.attribute_options.<model>.<attribute>
|
114
152
|
hash[a.to_s] = v.send(:delimiter).each_with_object({}) do |o, options|
|
115
|
-
options[o] = I18n.translate("activerecord.attribute_options.#{klass.to_s.downcase}.#{a}.#{o}", :default => o)
|
153
|
+
options[o.to_s] = I18n.translate("activerecord.attribute_options.#{klass.to_s.downcase}.#{a}.#{o}", :default => o.to_s.titleize)
|
116
154
|
end
|
117
155
|
end
|
118
156
|
end
|
119
157
|
end
|
120
158
|
|
121
|
-
|
122
|
-
|
123
|
-
|
159
|
+
if klass.respond_to?(:ransack_column_select_options)
|
160
|
+
column_select_options.merge!(klass.ransack_column_select_options)
|
161
|
+
end
|
124
162
|
|
125
|
-
|
126
|
-
|
163
|
+
searchable_attributes_for_base(base).map do |attribute_data|
|
164
|
+
column = attribute_data[:column]
|
127
165
|
|
128
|
-
|
129
|
-
if c == 'id'
|
130
|
-
foreign_klass = object.context.traverse(base).model_name
|
131
|
-
# Check that model can autocomplete. If not, skip this id column.
|
132
|
-
next nil unless foreign_klass.constantize._ransack_can_autocomplete
|
133
|
-
attribute_label = I18n.translate(foreign_klass, :default => foreign_klass)
|
134
|
-
else
|
135
|
-
foreign_klass = foreign_keys[c.to_sym]
|
136
|
-
end
|
166
|
+
html_options = {}
|
137
167
|
|
138
168
|
# Add column type as data attribute
|
139
|
-
html_options
|
169
|
+
html_options[:'data-type'] = attribute_data[:type]
|
140
170
|
# Set 'base' attribute if attribute is on base model
|
141
171
|
html_options[:'data-root-model'] = true if base.blank?
|
172
|
+
|
142
173
|
# Set column options if detected from inclusion validator
|
143
|
-
if column_select_options[
|
174
|
+
if column_select_options[column]
|
144
175
|
# Format options as an array of hashes with id and text columns, for Select2
|
145
|
-
html_options[:'data-select-options'] = column_select_options[
|
176
|
+
html_options[:'data-select-options'] = column_select_options[column].map {|id, text|
|
146
177
|
{:id => id, :text => text}
|
147
178
|
}.to_json
|
148
179
|
end
|
149
180
|
|
181
|
+
foreign_klass = attribute_data[:foreign_klass]
|
182
|
+
|
150
183
|
if foreign_klass
|
151
184
|
# If field is a foreign key, set up 'data-ajax-*' attributes for auto-complete
|
152
185
|
controller = foreign_klass.tableize
|
@@ -161,15 +194,66 @@ module Ransack
|
|
161
194
|
end
|
162
195
|
|
163
196
|
[
|
164
|
-
|
165
|
-
attribute,
|
197
|
+
attribute_data[:label],
|
198
|
+
attribute_data[:attribute],
|
166
199
|
html_options
|
167
200
|
]
|
168
|
-
end
|
201
|
+
end
|
169
202
|
rescue UntraversableAssociationError => e
|
170
203
|
nil
|
171
204
|
end
|
172
|
-
end
|
173
205
|
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def searchable_attributes_for_base(base)
|
210
|
+
cache_prefix = object.context.klass.table_name
|
211
|
+
cache_key = base.blank? ? cache_prefix : [cache_prefix, base].join('_')
|
212
|
+
|
213
|
+
self.class.cached_searchable_attributes_for_base[cache_key] ||= object.context.searchable_attributes(base).map do |column, type|
|
214
|
+
klass = object.context.traverse(base)
|
215
|
+
foreign_keys = klass.reflect_on_all_associations.select(&:belongs_to?).
|
216
|
+
each_with_object({}) {|r, h| h[r.foreign_key.to_sym] = r.class_name }
|
217
|
+
|
218
|
+
# Don't show 'id' column for base model
|
219
|
+
next nil if base.blank? && column == 'id'
|
220
|
+
|
221
|
+
attribute = attr_from_base_and_column(base, column)
|
222
|
+
attribute_label = Translate.attribute(attribute, :context => object.context)
|
223
|
+
|
224
|
+
# Set model name as label for 'id' column on that model's table.
|
225
|
+
if column == 'id'
|
226
|
+
foreign_klass = object.context.traverse(base).model_name
|
227
|
+
# Check that model can autocomplete. If not, skip this id column.
|
228
|
+
next nil unless foreign_klass.constantize._ransack_can_autocomplete
|
229
|
+
attribute_label = I18n.translate(foreign_klass, :default => foreign_klass)
|
230
|
+
else
|
231
|
+
foreign_klass = foreign_keys[column.to_sym]
|
232
|
+
end
|
233
|
+
|
234
|
+
attribute_data = {
|
235
|
+
label: attribute_label,
|
236
|
+
type: type,
|
237
|
+
column: column,
|
238
|
+
attribute: attribute
|
239
|
+
}
|
240
|
+
attribute_data[:foreign_klass] = foreign_klass if foreign_klass
|
241
|
+
attribute_data
|
242
|
+
end.compact
|
243
|
+
end
|
244
|
+
|
245
|
+
def foreign_klass_for_attribute(attribute)
|
246
|
+
associations = object.context.klass.ransackable_associations
|
247
|
+
bases = [''] + association_array(associations)
|
248
|
+
|
249
|
+
bases.each do |base|
|
250
|
+
searchable_attributes_for_base(base).each do |attribute_data|
|
251
|
+
if attribute == attribute_data[:attribute]
|
252
|
+
return attribute_data[:foreign_klass]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
174
258
|
end
|
175
259
|
end
|
data/lib/ransack_ui/version.rb
CHANGED
data/lib/ransack_ui.rb
CHANGED
metadata
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ransack_ui
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Nathan Broadbent
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-12-12 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: ransack_chronic
|
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: :runtime
|
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: ransack
|
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: :runtime
|
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
|
@@ -69,7 +64,6 @@ files:
|
|
69
64
|
- app/views/ransack_ui/_search.html.erb
|
70
65
|
- app/views/ransack_ui/_sort_fields.html.erb
|
71
66
|
- config/locales/en.yml
|
72
|
-
- lib/core_ext/enumerable.rb
|
73
67
|
- lib/ransack_ui.rb
|
74
68
|
- lib/ransack_ui/adapters/active_record.rb
|
75
69
|
- lib/ransack_ui/adapters/active_record/base.rb
|
@@ -87,32 +81,25 @@ files:
|
|
87
81
|
homepage: https://github.com/ndbroadbent/ransack_ui
|
88
82
|
licenses:
|
89
83
|
- MIT
|
84
|
+
metadata: {}
|
90
85
|
post_install_message:
|
91
86
|
rdoc_options: []
|
92
87
|
require_paths:
|
93
88
|
- lib
|
94
89
|
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
-
none: false
|
96
90
|
requirements:
|
97
91
|
- - ! '>='
|
98
92
|
- !ruby/object:Gem::Version
|
99
93
|
version: '0'
|
100
|
-
segments:
|
101
|
-
- 0
|
102
|
-
hash: 3625052068884160403
|
103
94
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
-
none: false
|
105
95
|
requirements:
|
106
96
|
- - ! '>='
|
107
97
|
- !ruby/object:Gem::Version
|
108
98
|
version: '0'
|
109
|
-
segments:
|
110
|
-
- 0
|
111
|
-
hash: 3625052068884160403
|
112
99
|
requirements: []
|
113
100
|
rubyforge_project:
|
114
|
-
rubygems_version: 1.
|
101
|
+
rubygems_version: 2.1.11
|
115
102
|
signing_key:
|
116
|
-
specification_version:
|
103
|
+
specification_version: 4
|
117
104
|
summary: UI Builder for Ransack
|
118
105
|
test_files: []
|
data/lib/core_ext/enumerable.rb
DELETED