refine-rails 2.9.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.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +413 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/refine_rails_manifest.js +0 -0
  5. data/app/assets/javascripts/refine-stimulus.esm.js +2 -0
  6. data/app/assets/javascripts/refine-stimulus.esm.js.map +1 -0
  7. data/app/assets/javascripts/refine-stimulus.js +2 -0
  8. data/app/assets/javascripts/refine-stimulus.js.map +1 -0
  9. data/app/assets/javascripts/refine-stimulus.modern.js +2 -0
  10. data/app/assets/javascripts/refine-stimulus.modern.js.map +1 -0
  11. data/app/assets/javascripts/refine-stimulus.umd.js +2 -0
  12. data/app/assets/javascripts/refine-stimulus.umd.js.map +1 -0
  13. data/app/assets/stylesheets/index.css +1873 -0
  14. data/app/assets/stylesheets/index.tailwind.css +1035 -0
  15. data/app/controllers/refine/blueprints_controller.rb +80 -0
  16. data/app/controllers/refine/filter_application_controller.rb +29 -0
  17. data/app/controllers/refine/inline/criteria_controller.rb +161 -0
  18. data/app/controllers/refine/inline/stored_filters_controller.rb +84 -0
  19. data/app/controllers/refine/stored_filters_controller.rb +69 -0
  20. data/app/javascript/controllers/index.js +66 -0
  21. data/app/javascript/controllers/refine/add-controller.js +42 -0
  22. data/app/javascript/controllers/refine/criterion-form-controller.js +31 -0
  23. data/app/javascript/controllers/refine/date-controller.js +113 -0
  24. data/app/javascript/controllers/refine/defaults-controller.js +32 -0
  25. data/app/javascript/controllers/refine/delete-controller.js +13 -0
  26. data/app/javascript/controllers/refine/filter-pills-controller.js +63 -0
  27. data/app/javascript/controllers/refine/form-controller.js +51 -0
  28. data/app/javascript/controllers/refine/inline-conditions-controller.js +33 -0
  29. data/app/javascript/controllers/refine/popup-controller.js +46 -0
  30. data/app/javascript/controllers/refine/search-filter-controller.js +50 -0
  31. data/app/javascript/controllers/refine/server-refresh-controller.js +43 -0
  32. data/app/javascript/controllers/refine/state-controller.js +220 -0
  33. data/app/javascript/controllers/refine/stored-filter-controller.js +23 -0
  34. data/app/javascript/controllers/refine/submit-form-controller.js +8 -0
  35. data/app/javascript/controllers/refine/toggle-controller.js +12 -0
  36. data/app/javascript/controllers/refine/turbo-stream-form-controller.js +24 -0
  37. data/app/javascript/controllers/refine/turbo-stream-link-controller.js +24 -0
  38. data/app/javascript/controllers/refine/update-controller.js +86 -0
  39. data/app/javascript/index.js +1 -0
  40. data/app/javascript/refine/helpers/index.js +77 -0
  41. data/app/models/refine/blueprints/blueprint.rb +58 -0
  42. data/app/models/refine/blueprints/blueprint_example.json +25 -0
  43. data/app/models/refine/conditions/boolean_condition.rb +112 -0
  44. data/app/models/refine/conditions/clause.rb +38 -0
  45. data/app/models/refine/conditions/clauses.rb +38 -0
  46. data/app/models/refine/conditions/condition.rb +285 -0
  47. data/app/models/refine/conditions/condition_error.rb +1 -0
  48. data/app/models/refine/conditions/date_condition.rb +464 -0
  49. data/app/models/refine/conditions/date_with_time_condition.rb +8 -0
  50. data/app/models/refine/conditions/errors/condition_clause_error.rb +7 -0
  51. data/app/models/refine/conditions/errors/criteria_limit_exceeded_error.rb +2 -0
  52. data/app/models/refine/conditions/errors/option_error.rb +2 -0
  53. data/app/models/refine/conditions/errors/relationship_error.rb +1 -0
  54. data/app/models/refine/conditions/filter_condition.rb +93 -0
  55. data/app/models/refine/conditions/has_clauses.rb +117 -0
  56. data/app/models/refine/conditions/has_meta.rb +10 -0
  57. data/app/models/refine/conditions/has_refinements.rb +156 -0
  58. data/app/models/refine/conditions/numeric_condition.rb +224 -0
  59. data/app/models/refine/conditions/option_condition.rb +260 -0
  60. data/app/models/refine/conditions/text_condition.rb +152 -0
  61. data/app/models/refine/conditions/uses_attributes.rb +168 -0
  62. data/app/models/refine/filter.rb +302 -0
  63. data/app/models/refine/filters/blueprint_editor.rb +102 -0
  64. data/app/models/refine/filters/builder.rb +59 -0
  65. data/app/models/refine/filters/criterion.rb +87 -0
  66. data/app/models/refine/filters/query.rb +82 -0
  67. data/app/models/refine/inline/criteria/input.rb +50 -0
  68. data/app/models/refine/inline/criteria/numeric_refinement.rb +13 -0
  69. data/app/models/refine/inline/criteria/option.rb +2 -0
  70. data/app/models/refine/inline/criterion.rb +141 -0
  71. data/app/models/refine/invalid_filter_error.rb +8 -0
  72. data/app/models/refine/stabilize.rb +29 -0
  73. data/app/models/refine/stabilizers/database_stabilizer.rb +21 -0
  74. data/app/models/refine/stabilizers/errors/url_stabilizer_error.rb +2 -0
  75. data/app/models/refine/stabilizers/url_encoded_stabilizer.rb +21 -0
  76. data/app/models/refine/stored_filter.rb +14 -0
  77. data/app/models/refine/tracks_pending_relationship_subqueries.rb +196 -0
  78. data/app/views/_filter_builder_dropdown.html.erb +63 -0
  79. data/app/views/_filter_pills.html.erb +40 -0
  80. data/app/views/_loading.html.erb +32 -0
  81. data/app/views/refine/blueprints/_add_and.html.erb +25 -0
  82. data/app/views/refine/blueprints/_add_group.html.erb +24 -0
  83. data/app/views/refine/blueprints/_clause_select.html.erb +24 -0
  84. data/app/views/refine/blueprints/_condition_select.html.erb +53 -0
  85. data/app/views/refine/blueprints/_criterion.html.erb +41 -0
  86. data/app/views/refine/blueprints/_criterion_errors.html.erb +7 -0
  87. data/app/views/refine/blueprints/_delete_criterion.html.erb +11 -0
  88. data/app/views/refine/blueprints/_group.html.erb +13 -0
  89. data/app/views/refine/blueprints/_query.html.erb +34 -0
  90. data/app/views/refine/blueprints/_stored_filters.html.erb +23 -0
  91. data/app/views/refine/blueprints/clauses/_date_condition.html.erb +80 -0
  92. data/app/views/refine/blueprints/clauses/_date_picker.html.erb +26 -0
  93. data/app/views/refine/blueprints/clauses/_filter_condition.html.erb +36 -0
  94. data/app/views/refine/blueprints/clauses/_numeric_condition.html.erb +35 -0
  95. data/app/views/refine/blueprints/clauses/_option_condition.html.erb +37 -0
  96. data/app/views/refine/blueprints/clauses/_text_condition.html.erb +13 -0
  97. data/app/views/refine/blueprints/create.turbo_stream.erb +22 -0
  98. data/app/views/refine/blueprints/new.html.erb +7 -0
  99. data/app/views/refine/blueprints/show.html.erb +4 -0
  100. data/app/views/refine/blueprints/show.turbo_stream.erb +22 -0
  101. data/app/views/refine/inline/criteria/_form_fields.html.erb +62 -0
  102. data/app/views/refine/inline/criteria/create.turbo_stream.erb +19 -0
  103. data/app/views/refine/inline/criteria/edit.turbo_stream.erb +26 -0
  104. data/app/views/refine/inline/criteria/index.html.erb +64 -0
  105. data/app/views/refine/inline/criteria/new.turbo_stream.erb +24 -0
  106. data/app/views/refine/inline/filters/_add_first_condition_button.html.erb +19 -0
  107. data/app/views/refine/inline/filters/_and_button.html.erb +26 -0
  108. data/app/views/refine/inline/filters/_criterion.html.erb +23 -0
  109. data/app/views/refine/inline/filters/_group.html.erb +13 -0
  110. data/app/views/refine/inline/filters/_load_button.html.erb +15 -0
  111. data/app/views/refine/inline/filters/_or_button.html.erb +26 -0
  112. data/app/views/refine/inline/filters/_popup.html.erb +26 -0
  113. data/app/views/refine/inline/filters/_save_button.html.erb +15 -0
  114. data/app/views/refine/inline/filters/_show.html.erb +40 -0
  115. data/app/views/refine/inline/inputs/_date_condition.html.erb +7 -0
  116. data/app/views/refine/inline/inputs/_date_condition_days.html.erb +18 -0
  117. data/app/views/refine/inline/inputs/_date_condition_range.html.erb +22 -0
  118. data/app/views/refine/inline/inputs/_date_condition_single.html.erb +9 -0
  119. data/app/views/refine/inline/inputs/_date_picker.html.erb +20 -0
  120. data/app/views/refine/inline/inputs/_numeric_condition.html.erb +23 -0
  121. data/app/views/refine/inline/inputs/_option_condition.html.erb +14 -0
  122. data/app/views/refine/inline/inputs/_text_condition.html.erb +8 -0
  123. data/app/views/refine/inline/stored_filters/find.turbo_stream.erb +19 -0
  124. data/app/views/refine/inline/stored_filters/index.html.erb +28 -0
  125. data/app/views/refine/inline/stored_filters/new.turbo_stream.erb +47 -0
  126. data/app/views/refine/stored_filters/create.turbo_stream.erb +2 -0
  127. data/app/views/refine/stored_filters/find.turbo_stream.erb +5 -0
  128. data/app/views/refine/stored_filters/index.html.erb +39 -0
  129. data/app/views/refine/stored_filters/new.html.erb +29 -0
  130. data/app/views/refine/stored_filters/show.html.erb +1 -0
  131. data/config/locales/en/dates.en.yml +29 -0
  132. data/config/locales/en/en.yml +20 -0
  133. data/config/locales/en/refine.en.yml +187 -0
  134. data/config/routes.rb +17 -0
  135. data/lib/generators/filter/filter_generator.rb +27 -0
  136. data/lib/generators/filter/templates/filter.rb.erb +20 -0
  137. data/lib/refine/rails/engine.rb +15 -0
  138. data/lib/refine/rails/version.rb +5 -0
  139. data/lib/refine/rails.rb +38 -0
  140. data/lib/tasks/refine/rails_tasks.rake +13 -0
  141. metadata +202 -0
@@ -0,0 +1,86 @@
1
+ import ServerRefreshController from './server-refresh-controller'
2
+ import { debounce } from 'lodash'
3
+
4
+ export default class extends ServerRefreshController {
5
+ static values = {
6
+ criterionId: Number,
7
+ }
8
+
9
+ initialize() {
10
+ this.updateBlueprint = debounce((event, value, inputKey) => {
11
+ this.value(event, value, inputKey)
12
+ }, 500)
13
+ }
14
+
15
+ refinedFilter(event) {
16
+ const { criterionIdValue, state } = this
17
+ const dataset = event.target.dataset
18
+ const inputId = dataset.inputId
19
+
20
+ state.updateInput(
21
+ criterionIdValue,
22
+ {
23
+ id: event.target.value,
24
+ },
25
+ inputId
26
+ )
27
+ this.refreshFromServer()
28
+ }
29
+
30
+ clause(event) {
31
+ const { criterionIdValue, state } = this
32
+ const dataset = event.target.dataset
33
+ const inputId = dataset.inputId
34
+ state.updateInput(
35
+ criterionIdValue,
36
+ {
37
+ clause: event.target.value,
38
+ },
39
+ inputId
40
+ )
41
+ this.refreshFromServer()
42
+ }
43
+
44
+ selected(event) {
45
+ const { target: select } = event
46
+ const options = Array.prototype.slice.call(select.options)
47
+ const selectedOptions = options.filter((option) => option.selected)
48
+ const selected = selectedOptions.map((option) => option.value)
49
+ this.value(event, selected, 'selected')
50
+ }
51
+
52
+ value(event, value, inputKey) {
53
+ const { criterionIdValue, state } = this
54
+ const dataset = event.target.dataset
55
+ const inputId = dataset.inputId
56
+ inputKey = inputKey || dataset.inputKey || 'value'
57
+ value = value || event.target.value
58
+ state.updateInput(
59
+ criterionIdValue,
60
+ {
61
+ [inputKey]: value,
62
+ },
63
+ inputId
64
+ )
65
+ }
66
+
67
+ condition(event) {
68
+ const { criterionIdValue, state } = this
69
+ const element = event.target
70
+ let newConditionId = element.value
71
+ if (!newConditionId) newConditionId = element.querySelector('select').value
72
+ const config = this.state.conditionConfigFor(newConditionId)
73
+ const updatePerformed = state.replaceCriterion(criterionIdValue, newConditionId, config)
74
+ if (updatePerformed) {
75
+ this.refreshFromServer()
76
+ }
77
+ }
78
+
79
+ // Prevent form submission when hitting enter in a text box
80
+ cancelEnter(event) {
81
+ if (event.code === "Enter") {
82
+ event.preventDefault()
83
+ event.stopPropagation()
84
+ }
85
+ }
86
+ }
@@ -0,0 +1 @@
1
+ export * from './controllers'
@@ -0,0 +1,77 @@
1
+ // Polyfill for custom events in IE9-11
2
+ // https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#polyfill
3
+ ;(function () {
4
+ if (typeof window.CustomEvent === 'function') return false
5
+
6
+ function CustomEvent(event, params) {
7
+ params = params || { bubbles: false, cancelable: false, detail: undefined }
8
+ var evt = document.createEvent('CustomEvent')
9
+ evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
10
+ return evt
11
+ }
12
+
13
+ CustomEvent.prototype = window.Event.prototype
14
+
15
+ window.CustomEvent = CustomEvent
16
+
17
+ // eslint expects a return here
18
+ return true
19
+ })()
20
+
21
+ export const filterStabilizedEvent = (element, stableId, filterName) => {
22
+ const event = new CustomEvent('filter-stabilized', {
23
+ bubbles: true,
24
+ cancelable: true,
25
+ detail: {
26
+ stableId,
27
+ filterName,
28
+ },
29
+ })
30
+ element.dispatchEvent(event)
31
+ }
32
+
33
+ export const filterUnstableEvent = (blueprint) => {
34
+ const event = new CustomEvent('filter-unstable', {
35
+ bubbles: true,
36
+ cancelable: true,
37
+ detail: {
38
+ blueprint,
39
+ },
40
+ })
41
+ window.dispatchEvent(event)
42
+ }
43
+
44
+ export const filterInvalidEvent = ({blueprint, errors}) => {
45
+ const event = new CustomEvent('filter-invalid', {
46
+ bubbles: true,
47
+ cancelable: true,
48
+ detail: {
49
+ blueprint,
50
+ errors,
51
+ },
52
+ })
53
+ window.dispatchEvent(event)
54
+ }
55
+
56
+ export const filterStoredEvent = (storedFilterId) => {
57
+ const event = new CustomEvent('filter-stored', {
58
+ bubbles: true,
59
+ cancelable: true,
60
+ detail: {
61
+ storedFilterId,
62
+ },
63
+ })
64
+ window.dispatchEvent(event)
65
+ }
66
+
67
+ export const blueprintUpdatedEvent = (element, {blueprint, formId}) => {
68
+ const event = new CustomEvent('blueprint-updated', {
69
+ bubbles: true,
70
+ cancelable: true,
71
+ detail: {
72
+ blueprint,
73
+ formId
74
+ },
75
+ })
76
+ element.dispatchEvent(event)
77
+ }
@@ -0,0 +1,58 @@
1
+ module Refine::Blueprints
2
+ class Blueprint
3
+ # DSL-based model for building filter blueprints
4
+ attr_reader :blueprint
5
+
6
+ def initialize
7
+ @blueprint = []
8
+ @depth = 0
9
+ end
10
+
11
+ def group(&block)
12
+ @depth = @depth += 1
13
+ instance_eval(&block)
14
+ @depth = @depth -= 1
15
+ self
16
+ end
17
+
18
+ def criterion(condition_id, input)
19
+ if !@blueprint.blank? && @blueprint.last[:type] == "criterion"
20
+ raise "Conjunction missing"
21
+ end
22
+
23
+ add({
24
+ depth: @depth,
25
+ type: "criterion",
26
+ condition_id: condition_id,
27
+ input: input,
28
+ })
29
+ self
30
+ end
31
+
32
+ def conjunction(word)
33
+ add({
34
+ depth: @depth,
35
+ type: "conjunction",
36
+ word: word
37
+ })
38
+ end
39
+
40
+ def and
41
+ conjunction("and")
42
+ self
43
+ end
44
+
45
+ def or
46
+ conjunction("or")
47
+ self
48
+ end
49
+
50
+ def add(item)
51
+ @blueprint.append(item)
52
+ end
53
+
54
+ def to_array
55
+ @blueprint
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ [
2
+ {
3
+ "depth": 0,
4
+ "type": "criterion",
5
+ "condition_id": "some_text_condition",
6
+ "input": {
7
+ "clause": "eq",
8
+ "value": "aaron"
9
+ }
10
+ },
11
+ {
12
+ "depth": 0,
13
+ "type": "conjunction",
14
+ "word": "and"
15
+ },
16
+ {
17
+ "depth": 0,
18
+ "type": "criterion",
19
+ "condition_id": "some_text_condition",
20
+ "input": {
21
+ "clause": "sw",
22
+ "value": "aa"
23
+ }
24
+ }
25
+ ]
@@ -0,0 +1,112 @@
1
+ module Refine::Conditions
2
+ class BooleanCondition < Condition
3
+ include HasClauses
4
+
5
+ CLAUSE_TRUE = Clauses::TRUE
6
+ CLAUSE_FALSE = Clauses::FALSE
7
+ CLAUSE_SET = Clauses::SET # non null
8
+ CLAUSE_NOT_SET = Clauses::NOT_SET # null
9
+
10
+ I18N_PREFIX = "refine.refine_blueprints.boolean_condition."
11
+
12
+ def component
13
+ "boolean-condition"
14
+ end
15
+
16
+ def human_readable(input)
17
+ current_clause = get_clause_by_id(input[:clause])
18
+ if input[:value]
19
+ "#{display} #{current_clause.display} #{input[:value]}"
20
+ else
21
+ "#{display} #{current_clause.display}"
22
+ end
23
+ end
24
+
25
+ def boot
26
+ @nulls_are = nil
27
+ hide_unknowns
28
+ end
29
+
30
+ def hide_unknowns
31
+ without_clauses([
32
+ CLAUSE_SET,
33
+ CLAUSE_NOT_SET,
34
+ ])
35
+ self
36
+ end
37
+
38
+ def nulls_are_true
39
+ @nulls_are = true
40
+ self
41
+ end
42
+
43
+ def nulls_are_false
44
+ @nulls_are = false
45
+ self
46
+ end
47
+
48
+ def nulls_are_unknown
49
+ @nulls_are = nil
50
+ self
51
+ end
52
+
53
+ def show_unknowns
54
+ with_clauses([
55
+ CLAUSE_SET,
56
+ CLAUSE_NOT_SET
57
+ ])
58
+ self
59
+ end
60
+
61
+ def clauses
62
+ [
63
+ Clause.new(CLAUSE_TRUE, I18n.t("#{I18N_PREFIX}is_true")),
64
+ Clause.new(CLAUSE_FALSE, I18n.t("#{I18N_PREFIX}is_false")),
65
+ Clause.new(CLAUSE_SET, I18n.t("#{I18N_PREFIX}is_set")),
66
+ Clause.new(CLAUSE_NOT_SET, I18n.t("#{I18N_PREFIX}is_not_set")),
67
+ ]
68
+ end
69
+
70
+ def apply_condition(_input, table, _inverse_clause)
71
+ case clause
72
+ when CLAUSE_SET
73
+ apply_clause_set(table)
74
+
75
+ when CLAUSE_NOT_SET
76
+ apply_clause_not_set(table)
77
+
78
+ when CLAUSE_TRUE
79
+ apply_clause_true(table)
80
+
81
+ when CLAUSE_FALSE
82
+ apply_clause_false(table)
83
+ end
84
+
85
+ # Apply a custom clause
86
+ end
87
+
88
+ def apply_clause_set(table)
89
+ table.grouping(table[:"#{attribute}"].not_eq(nil))
90
+ end
91
+
92
+ def apply_clause_not_set(table)
93
+ table.grouping(table[:"#{attribute}"].eq(nil))
94
+ end
95
+
96
+ def apply_clause_true(table)
97
+ apply_clause_bool(table, true)
98
+ end
99
+
100
+ def apply_clause_bool(table, bool)
101
+ if @nulls_are == bool
102
+ table.grouping(table[:"#{attribute}"].eq(bool).or(table[:"#{attribute}"].eq(nil)))
103
+ else
104
+ table.grouping(table[:"#{attribute}"].eq(bool))
105
+ end
106
+ end
107
+
108
+ def apply_clause_false(table)
109
+ apply_clause_bool(table, false)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,38 @@
1
+ module Refine::Conditions
2
+ class Clause
3
+ include HasMeta
4
+
5
+ attr_reader :id, :rules
6
+ attr_accessor :display
7
+
8
+ I18N_PREFIX = "refine.refine_blueprints.clause."
9
+
10
+ def initialize(id = nil, display = nil)
11
+ @id = id
12
+ @display = display || id.humanize(keep_id_suffix: true).titleize
13
+ @rules = {}
14
+ @messages
15
+ end
16
+
17
+ def with_rules(user_defined_hash)
18
+ @rules.merge!(user_defined_hash)
19
+ self
20
+ end
21
+
22
+ def requires_inputs(fields)
23
+ # Coerce field to an array
24
+ [*fields].each do |field|
25
+ @rules.merge!({"#{field}": I18n.t("#{I18N_PREFIX}required")})
26
+ end
27
+ self
28
+ end
29
+
30
+ def to_array
31
+ {
32
+ id: @id,
33
+ display: @display,
34
+ meta: meta
35
+ }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ module Refine::Conditions
2
+ class Clauses
3
+ EQUALS = "eq"
4
+ DOESNT_EQUAL = "dne"
5
+
6
+ SET = "st"
7
+ NOT_SET = "nst"
8
+
9
+ TRUE = "true"
10
+ FALSE = "false"
11
+
12
+ LESS_THAN_OR_EQUAL = "lte"
13
+ LESS_THAN = "lt"
14
+ BETWEEN = "btwn"
15
+ NOT_BETWEEN = "nbtwn"
16
+ GREATER_THAN = "gt"
17
+ GREATER_THAN_OR_EQUAL = "gte"
18
+
19
+ EXACTLY = "exct"
20
+
21
+ EXISTS = "exst"
22
+ DOESNT_EXIST = "dexst"
23
+
24
+ IN = "in"
25
+ NOT_IN = "nin"
26
+
27
+ CONTAINS = "cont"
28
+ DOESNT_CONTAIN = "dcont"
29
+
30
+ STARTS_WITH = "sw"
31
+ ENDS_WITH = "ew"
32
+
33
+ DOESNT_START_WITH = "dsw"
34
+ DOESNT_END_WITH = "dew"
35
+
36
+ FLIPPABLE = [NOT_IN, DOESNT_EQUAL].freeze
37
+ end
38
+ end