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.
- checksums.yaml +7 -0
- data/README.md +413 -0
- data/Rakefile +8 -0
- data/app/assets/config/refine_rails_manifest.js +0 -0
- data/app/assets/javascripts/refine-stimulus.esm.js +2 -0
- data/app/assets/javascripts/refine-stimulus.esm.js.map +1 -0
- data/app/assets/javascripts/refine-stimulus.js +2 -0
- data/app/assets/javascripts/refine-stimulus.js.map +1 -0
- data/app/assets/javascripts/refine-stimulus.modern.js +2 -0
- data/app/assets/javascripts/refine-stimulus.modern.js.map +1 -0
- data/app/assets/javascripts/refine-stimulus.umd.js +2 -0
- data/app/assets/javascripts/refine-stimulus.umd.js.map +1 -0
- data/app/assets/stylesheets/index.css +1873 -0
- data/app/assets/stylesheets/index.tailwind.css +1035 -0
- data/app/controllers/refine/blueprints_controller.rb +80 -0
- data/app/controllers/refine/filter_application_controller.rb +29 -0
- data/app/controllers/refine/inline/criteria_controller.rb +161 -0
- data/app/controllers/refine/inline/stored_filters_controller.rb +84 -0
- data/app/controllers/refine/stored_filters_controller.rb +69 -0
- data/app/javascript/controllers/index.js +66 -0
- data/app/javascript/controllers/refine/add-controller.js +42 -0
- data/app/javascript/controllers/refine/criterion-form-controller.js +31 -0
- data/app/javascript/controllers/refine/date-controller.js +113 -0
- data/app/javascript/controllers/refine/defaults-controller.js +32 -0
- data/app/javascript/controllers/refine/delete-controller.js +13 -0
- data/app/javascript/controllers/refine/filter-pills-controller.js +63 -0
- data/app/javascript/controllers/refine/form-controller.js +51 -0
- data/app/javascript/controllers/refine/inline-conditions-controller.js +33 -0
- data/app/javascript/controllers/refine/popup-controller.js +46 -0
- data/app/javascript/controllers/refine/search-filter-controller.js +50 -0
- data/app/javascript/controllers/refine/server-refresh-controller.js +43 -0
- data/app/javascript/controllers/refine/state-controller.js +220 -0
- data/app/javascript/controllers/refine/stored-filter-controller.js +23 -0
- data/app/javascript/controllers/refine/submit-form-controller.js +8 -0
- data/app/javascript/controllers/refine/toggle-controller.js +12 -0
- data/app/javascript/controllers/refine/turbo-stream-form-controller.js +24 -0
- data/app/javascript/controllers/refine/turbo-stream-link-controller.js +24 -0
- data/app/javascript/controllers/refine/update-controller.js +86 -0
- data/app/javascript/index.js +1 -0
- data/app/javascript/refine/helpers/index.js +77 -0
- data/app/models/refine/blueprints/blueprint.rb +58 -0
- data/app/models/refine/blueprints/blueprint_example.json +25 -0
- data/app/models/refine/conditions/boolean_condition.rb +112 -0
- data/app/models/refine/conditions/clause.rb +38 -0
- data/app/models/refine/conditions/clauses.rb +38 -0
- data/app/models/refine/conditions/condition.rb +285 -0
- data/app/models/refine/conditions/condition_error.rb +1 -0
- data/app/models/refine/conditions/date_condition.rb +464 -0
- data/app/models/refine/conditions/date_with_time_condition.rb +8 -0
- data/app/models/refine/conditions/errors/condition_clause_error.rb +7 -0
- data/app/models/refine/conditions/errors/criteria_limit_exceeded_error.rb +2 -0
- data/app/models/refine/conditions/errors/option_error.rb +2 -0
- data/app/models/refine/conditions/errors/relationship_error.rb +1 -0
- data/app/models/refine/conditions/filter_condition.rb +93 -0
- data/app/models/refine/conditions/has_clauses.rb +117 -0
- data/app/models/refine/conditions/has_meta.rb +10 -0
- data/app/models/refine/conditions/has_refinements.rb +156 -0
- data/app/models/refine/conditions/numeric_condition.rb +224 -0
- data/app/models/refine/conditions/option_condition.rb +260 -0
- data/app/models/refine/conditions/text_condition.rb +152 -0
- data/app/models/refine/conditions/uses_attributes.rb +168 -0
- data/app/models/refine/filter.rb +302 -0
- data/app/models/refine/filters/blueprint_editor.rb +102 -0
- data/app/models/refine/filters/builder.rb +59 -0
- data/app/models/refine/filters/criterion.rb +87 -0
- data/app/models/refine/filters/query.rb +82 -0
- data/app/models/refine/inline/criteria/input.rb +50 -0
- data/app/models/refine/inline/criteria/numeric_refinement.rb +13 -0
- data/app/models/refine/inline/criteria/option.rb +2 -0
- data/app/models/refine/inline/criterion.rb +141 -0
- data/app/models/refine/invalid_filter_error.rb +8 -0
- data/app/models/refine/stabilize.rb +29 -0
- data/app/models/refine/stabilizers/database_stabilizer.rb +21 -0
- data/app/models/refine/stabilizers/errors/url_stabilizer_error.rb +2 -0
- data/app/models/refine/stabilizers/url_encoded_stabilizer.rb +21 -0
- data/app/models/refine/stored_filter.rb +14 -0
- data/app/models/refine/tracks_pending_relationship_subqueries.rb +196 -0
- data/app/views/_filter_builder_dropdown.html.erb +63 -0
- data/app/views/_filter_pills.html.erb +40 -0
- data/app/views/_loading.html.erb +32 -0
- data/app/views/refine/blueprints/_add_and.html.erb +25 -0
- data/app/views/refine/blueprints/_add_group.html.erb +24 -0
- data/app/views/refine/blueprints/_clause_select.html.erb +24 -0
- data/app/views/refine/blueprints/_condition_select.html.erb +53 -0
- data/app/views/refine/blueprints/_criterion.html.erb +41 -0
- data/app/views/refine/blueprints/_criterion_errors.html.erb +7 -0
- data/app/views/refine/blueprints/_delete_criterion.html.erb +11 -0
- data/app/views/refine/blueprints/_group.html.erb +13 -0
- data/app/views/refine/blueprints/_query.html.erb +34 -0
- data/app/views/refine/blueprints/_stored_filters.html.erb +23 -0
- data/app/views/refine/blueprints/clauses/_date_condition.html.erb +80 -0
- data/app/views/refine/blueprints/clauses/_date_picker.html.erb +26 -0
- data/app/views/refine/blueprints/clauses/_filter_condition.html.erb +36 -0
- data/app/views/refine/blueprints/clauses/_numeric_condition.html.erb +35 -0
- data/app/views/refine/blueprints/clauses/_option_condition.html.erb +37 -0
- data/app/views/refine/blueprints/clauses/_text_condition.html.erb +13 -0
- data/app/views/refine/blueprints/create.turbo_stream.erb +22 -0
- data/app/views/refine/blueprints/new.html.erb +7 -0
- data/app/views/refine/blueprints/show.html.erb +4 -0
- data/app/views/refine/blueprints/show.turbo_stream.erb +22 -0
- data/app/views/refine/inline/criteria/_form_fields.html.erb +62 -0
- data/app/views/refine/inline/criteria/create.turbo_stream.erb +19 -0
- data/app/views/refine/inline/criteria/edit.turbo_stream.erb +26 -0
- data/app/views/refine/inline/criteria/index.html.erb +64 -0
- data/app/views/refine/inline/criteria/new.turbo_stream.erb +24 -0
- data/app/views/refine/inline/filters/_add_first_condition_button.html.erb +19 -0
- data/app/views/refine/inline/filters/_and_button.html.erb +26 -0
- data/app/views/refine/inline/filters/_criterion.html.erb +23 -0
- data/app/views/refine/inline/filters/_group.html.erb +13 -0
- data/app/views/refine/inline/filters/_load_button.html.erb +15 -0
- data/app/views/refine/inline/filters/_or_button.html.erb +26 -0
- data/app/views/refine/inline/filters/_popup.html.erb +26 -0
- data/app/views/refine/inline/filters/_save_button.html.erb +15 -0
- data/app/views/refine/inline/filters/_show.html.erb +40 -0
- data/app/views/refine/inline/inputs/_date_condition.html.erb +7 -0
- data/app/views/refine/inline/inputs/_date_condition_days.html.erb +18 -0
- data/app/views/refine/inline/inputs/_date_condition_range.html.erb +22 -0
- data/app/views/refine/inline/inputs/_date_condition_single.html.erb +9 -0
- data/app/views/refine/inline/inputs/_date_picker.html.erb +20 -0
- data/app/views/refine/inline/inputs/_numeric_condition.html.erb +23 -0
- data/app/views/refine/inline/inputs/_option_condition.html.erb +14 -0
- data/app/views/refine/inline/inputs/_text_condition.html.erb +8 -0
- data/app/views/refine/inline/stored_filters/find.turbo_stream.erb +19 -0
- data/app/views/refine/inline/stored_filters/index.html.erb +28 -0
- data/app/views/refine/inline/stored_filters/new.turbo_stream.erb +47 -0
- data/app/views/refine/stored_filters/create.turbo_stream.erb +2 -0
- data/app/views/refine/stored_filters/find.turbo_stream.erb +5 -0
- data/app/views/refine/stored_filters/index.html.erb +39 -0
- data/app/views/refine/stored_filters/new.html.erb +29 -0
- data/app/views/refine/stored_filters/show.html.erb +1 -0
- data/config/locales/en/dates.en.yml +29 -0
- data/config/locales/en/en.yml +20 -0
- data/config/locales/en/refine.en.yml +187 -0
- data/config/routes.rb +17 -0
- data/lib/generators/filter/filter_generator.rb +27 -0
- data/lib/generators/filter/templates/filter.rb.erb +20 -0
- data/lib/refine/rails/engine.rb +15 -0
- data/lib/refine/rails/version.rb +5 -0
- data/lib/refine/rails.rb +38 -0
- data/lib/tasks/refine/rails_tasks.rake +13 -0
- metadata +202 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static values = {
|
|
5
|
+
criterionId: Number,
|
|
6
|
+
input: Object,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
this.state = this.getStateController()
|
|
11
|
+
|
|
12
|
+
this.state.updateInput(
|
|
13
|
+
this.criterionIdValue,
|
|
14
|
+
this.inputValue,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getStateController() {
|
|
19
|
+
let currentElement = this.element
|
|
20
|
+
|
|
21
|
+
while(currentElement !== document.body) {
|
|
22
|
+
const controller = this.application.getControllerForElementAndIdentifier(currentElement, 'refine--state')
|
|
23
|
+
if (controller) {
|
|
24
|
+
return controller
|
|
25
|
+
} else {
|
|
26
|
+
currentElement = currentElement.parentNode
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import ServerRefreshController from './server-refresh-controller';
|
|
2
|
+
|
|
3
|
+
export default class extends ServerRefreshController {
|
|
4
|
+
static values = {
|
|
5
|
+
criterionId: Number,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
criterion() {
|
|
9
|
+
const { state, criterionIdValue } = this;
|
|
10
|
+
state.deleteCriterion(criterionIdValue);
|
|
11
|
+
this.refreshFromServer()
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { FetchRequest } from '@rails/request.js'
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static values = {
|
|
6
|
+
submitUrl: String
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
const urlParams = new URLSearchParams(window.location.search)
|
|
11
|
+
this.existingParams = urlParams
|
|
12
|
+
this.existingParams.delete('stable_id')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
delete(event) {
|
|
16
|
+
const { criterionId } = event.currentTarget.dataset
|
|
17
|
+
var index = parseInt(criterionId)
|
|
18
|
+
this.stateController.deleteCriterion(index)
|
|
19
|
+
this.reloadPage()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async reloadPage() {
|
|
23
|
+
const {blueprint} = this.stateController
|
|
24
|
+
const request = new FetchRequest(
|
|
25
|
+
"POST",
|
|
26
|
+
this.submitUrlValue,
|
|
27
|
+
{
|
|
28
|
+
responseKind: "turbo-stream",
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
refine_filters_builder: {
|
|
31
|
+
filter_class: this.stateController.filterName,
|
|
32
|
+
blueprint_json: JSON.stringify(blueprint),
|
|
33
|
+
client_id: this.stateController.clientIdValue
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
await request.perform()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
redirectToStableId(stableId) {
|
|
42
|
+
const params = new URLSearchParams()
|
|
43
|
+
if (stableId) {
|
|
44
|
+
params.append('stable_id', stableId)
|
|
45
|
+
}
|
|
46
|
+
const allParams = new URLSearchParams({
|
|
47
|
+
...Object.fromEntries(this.existingParams),
|
|
48
|
+
...Object.fromEntries(params),
|
|
49
|
+
}).toString()
|
|
50
|
+
const url = `${window.location.pathname}?${allParams}`
|
|
51
|
+
|
|
52
|
+
history.pushState({}, document.title, url)
|
|
53
|
+
window.location.reload()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get stateController() {
|
|
57
|
+
return this.element.refineStateController
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get stabilizeFilterController() {
|
|
61
|
+
return this.element.stabilizeFilterController
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
connect() {
|
|
5
|
+
this.state = this.getStateController()
|
|
6
|
+
this.blueprintInput = this.addHiddenInput('blueprint')
|
|
7
|
+
this.addHiddenInput('filter', this.state.filterName)
|
|
8
|
+
this.addHiddenInput('form_id', this.state.formIdValue)
|
|
9
|
+
this.finishUpdate()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getStateController() {
|
|
13
|
+
let currentElement = this.element
|
|
14
|
+
|
|
15
|
+
while(currentElement !== document.body) {
|
|
16
|
+
const controller = this.application.getControllerForElementAndIdentifier(currentElement, 'refine--state')
|
|
17
|
+
if (controller) {
|
|
18
|
+
return controller
|
|
19
|
+
} else {
|
|
20
|
+
currentElement = currentElement.parentNode
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
addHiddenInput(name, initialValue) {
|
|
28
|
+
const input = document.createElement('input')
|
|
29
|
+
input.type = 'hidden'
|
|
30
|
+
input.name = name
|
|
31
|
+
input.value = initialValue || ''
|
|
32
|
+
this.element.appendChild(input)
|
|
33
|
+
return input
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// called on connect
|
|
37
|
+
finishUpdate() {
|
|
38
|
+
this.state.finishUpdate()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Call this on submit
|
|
42
|
+
startUpdate() {
|
|
43
|
+
this.blueprintInput.value = JSON.stringify(this.state.blueprint)
|
|
44
|
+
this.state.startUpdate()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
submitForm() {
|
|
48
|
+
this.startUpdate()
|
|
49
|
+
this.element.requestSubmit()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
|
|
5
|
+
static targets = ['condition', 'category']
|
|
6
|
+
|
|
7
|
+
filterConditions(event) {
|
|
8
|
+
const query = event.currentTarget.value.toLowerCase()
|
|
9
|
+
const visibleCategories = new Set()
|
|
10
|
+
|
|
11
|
+
// hide / show condition links that match the query and note which
|
|
12
|
+
// categories should be visible
|
|
13
|
+
this.conditionTargets.forEach(conditionNode => {
|
|
14
|
+
const conditionName = conditionNode.innerHTML.toLowerCase()
|
|
15
|
+
if (conditionName.includes(query)) {
|
|
16
|
+
conditionNode.hidden = false
|
|
17
|
+
visibleCategories.add(conditionNode.dataset.category)
|
|
18
|
+
} else {
|
|
19
|
+
conditionNode.hidden = true
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// hide / show category headers that have
|
|
24
|
+
this.categoryTargets.forEach(categoryNode => {
|
|
25
|
+
const categoryName = categoryNode.innerHTML
|
|
26
|
+
if (visibleCategories.has(categoryName)) {
|
|
27
|
+
categoryNode.hidden = false
|
|
28
|
+
} else {
|
|
29
|
+
categoryNode.hidden = true
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { useClickOutside } from 'stimulus-use'
|
|
3
|
+
|
|
4
|
+
// simple controller to hide/show the filter modal
|
|
5
|
+
export default class extends Controller {
|
|
6
|
+
static targets = ["frame"]
|
|
7
|
+
|
|
8
|
+
static values = {
|
|
9
|
+
src: String,
|
|
10
|
+
isOpen: {type: Boolean, default: false}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
connect() {
|
|
14
|
+
useClickOutside(this)
|
|
15
|
+
this.boundHandleKeyUp = this.handleKeyUp.bind(this)
|
|
16
|
+
document.addEventListener("keyup", this.boundHandleKeyUp)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
disconnect() {
|
|
20
|
+
document.removeEventListener("keyup", this.boundHandleKeyUp)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
show(event) {
|
|
24
|
+
event.preventDefault()
|
|
25
|
+
this.frameTarget.src = this.srcValue;
|
|
26
|
+
this.isOpenValue = true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
hide(event) {
|
|
30
|
+
if (this.isOpenValue) {
|
|
31
|
+
event?.preventDefault()
|
|
32
|
+
this.frameTarget.innerHTML = "";
|
|
33
|
+
this.isOpenValue = false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
clickOutside(event) {
|
|
38
|
+
this.hide(event)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
handleKeyUp(event) {
|
|
42
|
+
if (event.key === "Escape" || event.key === "Esc") {
|
|
43
|
+
this.hide(event)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { FetchRequest } from '@rails/request.js'
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static values = {
|
|
6
|
+
submitUrl: String
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
search(event) {
|
|
11
|
+
event.preventDefault()
|
|
12
|
+
this.submitFilter()
|
|
13
|
+
document.activeElement.blur()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async submitFilter() {
|
|
17
|
+
const {blueprint} = this.stateController
|
|
18
|
+
const request = new FetchRequest(
|
|
19
|
+
"POST",
|
|
20
|
+
this.submitUrlValue,
|
|
21
|
+
{
|
|
22
|
+
responseKind: "turbo-stream",
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
refine_filters_builder: {
|
|
25
|
+
filter_class: this.stateController.filterName,
|
|
26
|
+
blueprint_json: JSON.stringify(blueprint),
|
|
27
|
+
client_id: this.stateController.clientIdValue
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
await request.perform()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get stateController() {
|
|
36
|
+
return this
|
|
37
|
+
.element
|
|
38
|
+
.querySelector('[data-controller~="refine--state"]')
|
|
39
|
+
.refineStateController
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
loadResults({detail: {url}}) {
|
|
43
|
+
console.log("filter submit success")
|
|
44
|
+
if (window.Turbo) {
|
|
45
|
+
window.Turbo.visit(url)
|
|
46
|
+
} else {
|
|
47
|
+
window.location.href = url
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { FetchRequest } from '@rails/request.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// Base class for controllers that reload form content from the server
|
|
6
|
+
export default class extends Controller {
|
|
7
|
+
connect() {
|
|
8
|
+
this.state.finishUpdate()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get state() {
|
|
12
|
+
let currentElement = this.element
|
|
13
|
+
|
|
14
|
+
while(currentElement !== document.body) {
|
|
15
|
+
if (currentElement.matches('[data-controller~="refine--state"]'))
|
|
16
|
+
return this.application.getControllerForElementAndIdentifier(currentElement, 'refine--state')
|
|
17
|
+
else {
|
|
18
|
+
currentElement = currentElement.parentNode
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async refreshFromServer(options = {}) {
|
|
26
|
+
const { includeErrors } = options
|
|
27
|
+
this.state.startUpdate()
|
|
28
|
+
const request = new FetchRequest(
|
|
29
|
+
"GET",
|
|
30
|
+
this.state.refreshUrlValue,
|
|
31
|
+
{
|
|
32
|
+
responseKind: "turbo-stream",
|
|
33
|
+
query: {
|
|
34
|
+
"refine_filters_builder[filter_class]": this.state.filterName,
|
|
35
|
+
"refine_filters_builder[blueprint_json]": JSON.stringify(this.state.blueprint),
|
|
36
|
+
"refine_filters_builder[client_id]": this.state.clientIdValue,
|
|
37
|
+
include_errors: !!includeErrors
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
await request.perform()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { delegate, abnegate } from 'jquery-events-to-dom-events'
|
|
3
|
+
import { blueprintUpdatedEvent } from '../../refine/helpers'
|
|
4
|
+
import { isEqual } from 'lodash'
|
|
5
|
+
|
|
6
|
+
const criterion = (id, depth, condition) => {
|
|
7
|
+
const component = condition?.component
|
|
8
|
+
const meta = condition?.meta || { clauses: [], options: {}}
|
|
9
|
+
const refinements = condition?.refinements || []
|
|
10
|
+
const { clauses, options } = meta
|
|
11
|
+
let selected
|
|
12
|
+
if (component === 'option-condition') {
|
|
13
|
+
selected = options[0] ? [options[0].id] : []
|
|
14
|
+
} else {
|
|
15
|
+
selected = undefined
|
|
16
|
+
}
|
|
17
|
+
// Set newInput based on component
|
|
18
|
+
|
|
19
|
+
let newInput = {
|
|
20
|
+
clause: clauses[0]?.id,
|
|
21
|
+
selected: selected,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// If refinements are present, add to input array
|
|
25
|
+
refinements.forEach((refinement) => {
|
|
26
|
+
const { meta, component } = refinement
|
|
27
|
+
const { clauses, options } = meta
|
|
28
|
+
let selected
|
|
29
|
+
if (component === 'option-condition') {
|
|
30
|
+
selected = options[0] ? [options[0].id] : []
|
|
31
|
+
} else {
|
|
32
|
+
selected = undefined
|
|
33
|
+
}
|
|
34
|
+
newInput[refinement.id] = {
|
|
35
|
+
clause: clauses[0].id,
|
|
36
|
+
selected: selected,
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
depth,
|
|
42
|
+
type: 'criterion',
|
|
43
|
+
condition_id: id,
|
|
44
|
+
input: newInput,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const or = function (depth) {
|
|
49
|
+
depth = depth === undefined ? 0 : depth
|
|
50
|
+
return {
|
|
51
|
+
depth,
|
|
52
|
+
type: 'conjunction',
|
|
53
|
+
word: 'or',
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const and = function (depth) {
|
|
58
|
+
depth = depth === undefined ? 1 : depth
|
|
59
|
+
return {
|
|
60
|
+
depth,
|
|
61
|
+
type: 'conjunction',
|
|
62
|
+
word: 'and',
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export default class extends Controller {
|
|
66
|
+
static values = {
|
|
67
|
+
blueprint: Array,
|
|
68
|
+
conditions: Array,
|
|
69
|
+
className: String,
|
|
70
|
+
refreshUrl: String,
|
|
71
|
+
clientId: String,
|
|
72
|
+
validateBlueprintUrl: String,
|
|
73
|
+
defaultConditionId: String
|
|
74
|
+
}
|
|
75
|
+
static targets = ['loading']
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
connect() {
|
|
79
|
+
// for select2 jquery events and datepicker
|
|
80
|
+
this.element.refineStateController = this
|
|
81
|
+
this.changeDelegate = delegate('change', ['event', 'picker'])
|
|
82
|
+
this.blueprint = this.blueprintValue
|
|
83
|
+
this.conditions = this.conditionsValue
|
|
84
|
+
this.filterName = this.classNameValue
|
|
85
|
+
this.conditionsLookup = this.conditions.reduce((lookup, condition) => {
|
|
86
|
+
lookup[condition.id] = condition
|
|
87
|
+
return lookup
|
|
88
|
+
}, {})
|
|
89
|
+
this.loadingTimeout = null
|
|
90
|
+
blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
disconnect() {
|
|
94
|
+
abnegate('change', this.changeDelegate)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
startUpdate() {
|
|
98
|
+
if (this.loadingTimeout) {
|
|
99
|
+
window.clearTimeout(this.loadingTimeout)
|
|
100
|
+
}
|
|
101
|
+
// only show the loading overlay if it's taking a long time
|
|
102
|
+
// to render the updates
|
|
103
|
+
this.loadingTimeout = window.setTimeout(() => {
|
|
104
|
+
this.loadingTarget.classList.remove('hidden')
|
|
105
|
+
}, 1000)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
finishUpdate() {
|
|
109
|
+
if (this.loadingTimeout) {
|
|
110
|
+
window.clearTimeout(this.loadingTimeout)
|
|
111
|
+
}
|
|
112
|
+
this.loadingTarget.classList.add('hidden')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
conditionConfigFor(conditionId) {
|
|
116
|
+
return this.conditionsLookup[conditionId]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
addGroup() {
|
|
120
|
+
const { blueprint, conditions } = this
|
|
121
|
+
const condition = ( conditions.find(c => c.id == this.defaultConditionIdValue) || conditions[0] )
|
|
122
|
+
const { meta } = condition
|
|
123
|
+
|
|
124
|
+
if (this.blueprint.length > 0) {
|
|
125
|
+
this.blueprint.push(or())
|
|
126
|
+
}
|
|
127
|
+
this.blueprint.push(criterion(condition.id, 1, condition))
|
|
128
|
+
blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
addCriterion(previousCriterionId) {
|
|
132
|
+
const { blueprint, conditions } = this
|
|
133
|
+
const condition = ( conditions.find(c => c.id == this.defaultConditionIdValue) || conditions[0] )
|
|
134
|
+
const { meta } = condition
|
|
135
|
+
blueprint.splice(previousCriterionId + 1, 0, and(), criterion(condition.id, 1, condition))
|
|
136
|
+
blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
deleteCriterion(criterionId) {
|
|
140
|
+
/**
|
|
141
|
+
To support 'groups' there is some complicated logic for deleting criterion.
|
|
142
|
+
|
|
143
|
+
Imagine this simplified blueprint: [eq, and, sw, or, eq]
|
|
144
|
+
|
|
145
|
+
User clicks to delete the last eq. We also have to delete the preceding or
|
|
146
|
+
otherwise we're left with a hanging empty group
|
|
147
|
+
|
|
148
|
+
What if the user deletes the sw? We have to clean up the preceding and.
|
|
149
|
+
|
|
150
|
+
Imagine another scenario: [eq or sw and ew]
|
|
151
|
+
Now we delete the first eq but this time we need to clean up the or.
|
|
152
|
+
|
|
153
|
+
These conditionals cover these cases.
|
|
154
|
+
**/
|
|
155
|
+
const { blueprint } = this
|
|
156
|
+
const previous = blueprint[criterionId - 1]
|
|
157
|
+
const next = blueprint[criterionId + 1]
|
|
158
|
+
|
|
159
|
+
const nextIsOr = next && next.word === 'or'
|
|
160
|
+
const previousIsOr = previous && previous.word === 'or'
|
|
161
|
+
|
|
162
|
+
const nextIsRightParen = nextIsOr || !next
|
|
163
|
+
const previousIsLeftParen = previousIsOr || !previous
|
|
164
|
+
|
|
165
|
+
const isFirstInGroup = previousIsLeftParen && !nextIsRightParen
|
|
166
|
+
const isLastInGroup = previousIsLeftParen && nextIsRightParen
|
|
167
|
+
const isLastCriterion = !previous && !next
|
|
168
|
+
|
|
169
|
+
if (isLastCriterion) {
|
|
170
|
+
this.blueprint = []
|
|
171
|
+
} else if (isLastInGroup && previousIsOr) {
|
|
172
|
+
blueprint.splice(criterionId - 1, 2)
|
|
173
|
+
} else if (isLastInGroup && !previous) {
|
|
174
|
+
blueprint.splice(criterionId, 2)
|
|
175
|
+
} else if (isFirstInGroup) {
|
|
176
|
+
blueprint.splice(criterionId, 2)
|
|
177
|
+
} else {
|
|
178
|
+
blueprint.splice(criterionId - 1, 2)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
Updates a criterion in the blueprint
|
|
186
|
+
Returns true if an update was actually performed, or false if no-op
|
|
187
|
+
*/
|
|
188
|
+
replaceCriterion(criterionId, conditionId, condition) {
|
|
189
|
+
const criterionRow = this.blueprint[criterionId]
|
|
190
|
+
if (criterionRow.type !== 'criterion') {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`You can't call updateConditionId on a non-criterion type. Trying to update ${JSON.stringify(criterion)}`
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
const existingCriterion = this.blueprint[criterionId]
|
|
196
|
+
const newCriterion = criterion(conditionId, criterionRow.depth, condition)
|
|
197
|
+
if (isEqual(existingCriterion, newCriterion)) {
|
|
198
|
+
return false
|
|
199
|
+
} else {
|
|
200
|
+
this.blueprint[criterionId] = newCriterion
|
|
201
|
+
blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
|
|
202
|
+
return true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
updateInput(criterionId, input, inputId) {
|
|
207
|
+
// Input id is an array of hash keys that define the path for this input such as ["input", "date_refinement"]
|
|
208
|
+
const { blueprint } = this
|
|
209
|
+
const criterion = blueprint[criterionId]
|
|
210
|
+
inputId = inputId || 'input'
|
|
211
|
+
const blueprintPath = inputId.split(', ')
|
|
212
|
+
// If the inputId contains more than one element, add input at appropriate depth
|
|
213
|
+
if (blueprintPath.length > 1) {
|
|
214
|
+
criterion[blueprintPath[0]][blueprintPath[1]] = { ...criterion[blueprintPath[0]][blueprintPath[1]], ...input }
|
|
215
|
+
} else {
|
|
216
|
+
criterion[inputId] = { ...criterion[inputId], ...input }
|
|
217
|
+
}
|
|
218
|
+
blueprintUpdatedEvent(this.element, {blueprint: this.blueprint, formId: this.formIdValue})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { filterStoredEvent } from '../../refine/helpers'
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static targets = ['blueprintField']
|
|
6
|
+
static values = { formId: String, stateDomId: String }
|
|
7
|
+
|
|
8
|
+
connect() {
|
|
9
|
+
const stateController = document
|
|
10
|
+
.getElementById(this.stateDomIdValue)
|
|
11
|
+
.refineStateController
|
|
12
|
+
this.blueprintFieldTarget.value = JSON.stringify(stateController.blueprint)
|
|
13
|
+
console.log("connect", this.blueprintFieldTarget.value)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
updateBlueprintField(event) {
|
|
17
|
+
if (event.detail.formId != this.formIdValue) { return null }
|
|
18
|
+
const { detail } = event
|
|
19
|
+
const { blueprint } = detail
|
|
20
|
+
this.blueprintFieldTarget.value = JSON.stringify(blueprint)
|
|
21
|
+
console.log("update blueprint", this.blueprintFieldTarget.value)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// simple controller to hide/show the filter modal
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static targets = ["content"]
|
|
6
|
+
|
|
7
|
+
toggle(_event) {
|
|
8
|
+
this.contentTargets.forEach(node => {
|
|
9
|
+
node.toggleAttribute("hidden")
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { FetchRequest } from '@rails/request.js'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
attach to a form element to have it submit to a turbo-stream endpoint
|
|
6
|
+
|
|
7
|
+
<form action="/contacts" data-controller="refine--turbo-stream-form" data-action="submit->refine--turbo-stream-form#submit">
|
|
8
|
+
|
|
9
|
+
Turbo is supposed to handle this natively but we're seeing issues when the form is inside an iframe
|
|
10
|
+
*/
|
|
11
|
+
export default class extends Controller {
|
|
12
|
+
async submit(event) {
|
|
13
|
+
event.preventDefault()
|
|
14
|
+
const request = new FetchRequest(
|
|
15
|
+
(this.element.method || "POST"),
|
|
16
|
+
this.element.action,
|
|
17
|
+
{
|
|
18
|
+
responseKind: "turbo-stream",
|
|
19
|
+
body: new FormData(this.element)
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
await request.perform()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
import { FetchRequest } from '@rails/request.js'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
attach to a link element to have it request turbo stream responses
|
|
6
|
+
|
|
7
|
+
<a href="/contacts" data-controller="refine--turbo-stream-link" data-action="refine--turbo-stream-link#get">Click me</a>
|
|
8
|
+
|
|
9
|
+
Turbo is supposed to handle this natively with data-turbo-stream but we're
|
|
10
|
+
seeing issues using that attribute inside iframes
|
|
11
|
+
*/
|
|
12
|
+
export default class extends Controller {
|
|
13
|
+
async visit(event) {
|
|
14
|
+
event.preventDefault()
|
|
15
|
+
const request = new FetchRequest(
|
|
16
|
+
(this.element.dataset.turboMethod || "GET"),
|
|
17
|
+
this.element.href,
|
|
18
|
+
{
|
|
19
|
+
responseKind: "turbo-stream",
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
await request.perform()
|
|
23
|
+
}
|
|
24
|
+
}
|