ransack_ui 1.1.0 → 1.2.0
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/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