refine-rails 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|