ransack_search_element 0.0.2.pre.alpha → 0.1.0.pre.alpha
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 +4 -4
- data/app/javascript/ransack-search-element/components/FilterDropdown.tsx +136 -15
- data/app/javascript/ransack-search-element/index.ts +1 -1
- data/app/javascript/ransack-search-element/utilities/registerNestedDropdown.ts +59 -57
- data/lib/ransack_search_element/version.rb +1 -1
- metadata +3 -4
- data/app/javascript/ransack-search-element/utilities/QueryBuilder.ts +0 -134
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd93e037848a8f700171aec719988067edbf515bbe1e512e4d27e66296d6f8b6
|
|
4
|
+
data.tar.gz: b6ef1f37b77de1ce0e78a07d2b16171c135a484596118c1c34aa611e75698d80
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9caa070a79c203d9f6b69a6ebdf4c9a9f8cef79d33549cf25325ba9895d8ac3b954efb875f227cb96a05483ca89fedde1396e38feb618d0221a9cad927633731
|
|
7
|
+
data.tar.gz: 1dc72e8b176fae901a389a77eb2b5fd1d4712e1f7c8a16cfd1a4d1d05bb8d1381b59a290fa05958ac1f4a51634cbc769ad0a099ed905bdb8d8a0a9d7b88f1088
|
|
@@ -1,12 +1,51 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import QueryBuilder, {
|
|
3
|
-
AttributeInfo,
|
|
4
|
-
Filter,
|
|
5
|
-
options,
|
|
6
|
-
} from "../utilities/QueryBuilder";
|
|
1
|
+
import React, { useState } from "react";
|
|
7
2
|
|
|
8
3
|
import FilterRow from "./FilterRow";
|
|
9
4
|
|
|
5
|
+
const options = [
|
|
6
|
+
["Contains", "cont"],
|
|
7
|
+
["Equals", "eq"],
|
|
8
|
+
["Less than", "lt"],
|
|
9
|
+
["Greater than", "gt"],
|
|
10
|
+
["Less than/equal", "lteq"],
|
|
11
|
+
["Greater than/equal", "gteq"],
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const options_values = options.map((v) => v[1]);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Recursively iterate through relations with matching prefixes.
|
|
18
|
+
* Attempts to get the accompanying AttributeInfo given a full query.
|
|
19
|
+
* @param full_query
|
|
20
|
+
* @param ass
|
|
21
|
+
*/
|
|
22
|
+
const get_association = (
|
|
23
|
+
full_query,
|
|
24
|
+
ass
|
|
25
|
+
): {
|
|
26
|
+
[association: string]: AttributeInfo;
|
|
27
|
+
} => {
|
|
28
|
+
if (ass instanceof Array) {
|
|
29
|
+
return ass.find((dict) => {
|
|
30
|
+
return full_query.includes(Object.keys(dict)[0]);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const key = Object.keys(ass).find((k) => full_query.includes(k));
|
|
35
|
+
|
|
36
|
+
if (typeof key === "undefined") return null;
|
|
37
|
+
|
|
38
|
+
const remainder = full_query.slice(full_query.indexOf(key) + key.length + 1);
|
|
39
|
+
|
|
40
|
+
return get_association(remainder, ass[key]);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type AttributeInfo = {
|
|
44
|
+
type: string;
|
|
45
|
+
label: string;
|
|
46
|
+
ass?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
10
49
|
export type FilterDropdownProps = {
|
|
11
50
|
columns: {
|
|
12
51
|
root: {
|
|
@@ -39,13 +78,85 @@ export default (props: FilterDropdownProps): JSX.Element => {
|
|
|
39
78
|
associations.map((v) => Object.keys(v)[0]).flat()
|
|
40
79
|
);
|
|
41
80
|
|
|
42
|
-
const queryBuilder = useMemo(() => {
|
|
43
|
-
return new QueryBuilder(colNames, associations);
|
|
44
|
-
}, [columns, queries]);
|
|
45
|
-
|
|
46
81
|
const [filters, setFilters] = useState<{
|
|
47
|
-
[key: string]:
|
|
48
|
-
|
|
82
|
+
[key: string]: {
|
|
83
|
+
label: string;
|
|
84
|
+
query: string;
|
|
85
|
+
type: string;
|
|
86
|
+
value: string | number;
|
|
87
|
+
ass?: string;
|
|
88
|
+
}[];
|
|
89
|
+
}>(
|
|
90
|
+
/**
|
|
91
|
+
* Here we initialize parameters from the query string,
|
|
92
|
+
* and do basic validation for options and attributes.
|
|
93
|
+
*/
|
|
94
|
+
(() => {
|
|
95
|
+
let s = queries || {};
|
|
96
|
+
|
|
97
|
+
let f = {};
|
|
98
|
+
|
|
99
|
+
if (Object.entries(s)?.length > 0) {
|
|
100
|
+
for (const [k, v] of Object.entries(s)) {
|
|
101
|
+
// sorting parameter
|
|
102
|
+
if (k === "s") continue;
|
|
103
|
+
|
|
104
|
+
const query = options_values
|
|
105
|
+
.filter((v) => k.includes(v))
|
|
106
|
+
.reduce((x, y) => (x.length > y.length ? x : y), "");
|
|
107
|
+
|
|
108
|
+
const attribute = possible_values
|
|
109
|
+
.filter((v) => k.includes(v))
|
|
110
|
+
.reduce((x, y) => (x.length > y.length ? x : y), "");
|
|
111
|
+
|
|
112
|
+
const asses = get_association(
|
|
113
|
+
k,
|
|
114
|
+
associations.reduce((acc, cur) => ({ ...acc, ...cur }), {})
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (asses) {
|
|
118
|
+
const full_ass_key = Object.keys(asses)[0];
|
|
119
|
+
const full_ass_values = Object.values(asses)[0];
|
|
120
|
+
|
|
121
|
+
const t = {};
|
|
122
|
+
t["query"] = query;
|
|
123
|
+
t["value"] = v;
|
|
124
|
+
t["type"] = full_ass_values?.type;
|
|
125
|
+
t["label"] = full_ass_values?.label;
|
|
126
|
+
t["ass"] = attribute;
|
|
127
|
+
|
|
128
|
+
if (query.length > 0 && attribute.length > 0) {
|
|
129
|
+
if (f[`${attribute}_${full_ass_key}`] instanceof Array) {
|
|
130
|
+
f[`${attribute}_${full_ass_key}`].push(t);
|
|
131
|
+
} else {
|
|
132
|
+
f[`${attribute}_${full_ass_key}`] = [];
|
|
133
|
+
f[`${attribute}_${full_ass_key}`].push(t);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
const t = {};
|
|
138
|
+
t["query"] = query;
|
|
139
|
+
t["value"] = v;
|
|
140
|
+
t["type"] = colNames[attribute]?.type;
|
|
141
|
+
t["label"] = colNames[attribute]?.label;
|
|
142
|
+
|
|
143
|
+
if (query.length > 0 && attribute.length > 0) {
|
|
144
|
+
if (f[`${attribute}`] instanceof Array) {
|
|
145
|
+
f[`${attribute}`].push(t);
|
|
146
|
+
} else {
|
|
147
|
+
f[`${attribute}`] = [];
|
|
148
|
+
f[`${attribute}`].push(t);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return f;
|
|
158
|
+
})()
|
|
159
|
+
);
|
|
49
160
|
|
|
50
161
|
/**
|
|
51
162
|
* The constructed query string to replace the current location when
|
|
@@ -61,7 +172,7 @@ export default (props: FilterDropdownProps): JSX.Element => {
|
|
|
61
172
|
.map(([k, v]) => {
|
|
62
173
|
return v
|
|
63
174
|
.map((x) => {
|
|
64
|
-
if (x["ass"]
|
|
175
|
+
if (x["ass"]) {
|
|
65
176
|
return (
|
|
66
177
|
`q[${x["ass"] + "_" + k + "_" + x["query"]}]` +
|
|
67
178
|
"=" +
|
|
@@ -121,7 +232,12 @@ export default (props: FilterDropdownProps): JSX.Element => {
|
|
|
121
232
|
onClick={(e) => {
|
|
122
233
|
e.preventDefault();
|
|
123
234
|
let copy = { ...filters };
|
|
124
|
-
const t = {
|
|
235
|
+
const t = {
|
|
236
|
+
query: "",
|
|
237
|
+
type: "",
|
|
238
|
+
label: "",
|
|
239
|
+
value: "",
|
|
240
|
+
};
|
|
125
241
|
t["query"] = "eq";
|
|
126
242
|
t["type"] = colNames[v]?.type || associations[v]?.type;
|
|
127
243
|
t["label"] = colNames[v]?.label || associations[v]?.label;
|
|
@@ -178,7 +294,12 @@ export default (props: FilterDropdownProps): JSX.Element => {
|
|
|
178
294
|
e.preventDefault();
|
|
179
295
|
let copy = { ...filters };
|
|
180
296
|
|
|
181
|
-
const t = {
|
|
297
|
+
const t = {
|
|
298
|
+
query: "",
|
|
299
|
+
type: "",
|
|
300
|
+
label: "",
|
|
301
|
+
value: "",
|
|
302
|
+
};
|
|
182
303
|
t["query"] = "eq";
|
|
183
304
|
t["type"] = values.type;
|
|
184
305
|
t["label"] = values.label;
|
|
@@ -1,65 +1,67 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as bootstrap from "bootstrap";
|
|
2
2
|
|
|
3
3
|
export default function (root: Document | HTMLElement) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
dd.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
root.querySelectorAll(".dropdown").forEach(function (dd) {
|
|
21
|
-
dd.addEventListener("hide.bs.dropdown", function (e) {
|
|
22
|
-
if (this.classList.contains(CLASS_NAME)) {
|
|
23
|
-
this.classList.remove(CLASS_NAME);
|
|
24
|
-
e.preventDefault();
|
|
25
|
-
}
|
|
26
|
-
if (
|
|
27
|
-
e.clickEvent &&
|
|
28
|
-
e.clickEvent
|
|
29
|
-
.composedPath()
|
|
30
|
-
.some(
|
|
31
|
-
(el) => el.classList && el.classList.contains("dropdown-toggle")
|
|
32
|
-
)
|
|
33
|
-
) {
|
|
34
|
-
e.preventDefault();
|
|
35
|
-
}
|
|
36
|
-
e.stopPropagation(); // do not need pop in multi level mode
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// for hover
|
|
41
|
-
function getDropdown(element) {
|
|
42
|
-
return Dropdown.getInstance(element) || new Dropdown(element);
|
|
43
|
-
}
|
|
4
|
+
(function ($bs) {
|
|
5
|
+
const CLASS_NAME = "has-child-dropdown-show";
|
|
6
|
+
$bs.Dropdown.prototype.toggle = (function (_orginal) {
|
|
7
|
+
return function () {
|
|
8
|
+
root.querySelectorAll("." + CLASS_NAME).forEach(function (e) {
|
|
9
|
+
e.classList.remove(CLASS_NAME);
|
|
10
|
+
});
|
|
11
|
+
let dd = this._element
|
|
12
|
+
.closest(".dropdown")
|
|
13
|
+
.parentNode.closest(".dropdown");
|
|
14
|
+
for (; dd && dd !== root; dd = dd.parentNode.closest(".dropdown")) {
|
|
15
|
+
dd.classList.add(CLASS_NAME);
|
|
16
|
+
}
|
|
17
|
+
return _orginal.call(this);
|
|
18
|
+
};
|
|
19
|
+
})($bs.Dropdown.prototype.toggle);
|
|
44
20
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
':scope>[data-bs-toggle="dropdown"]'
|
|
51
|
-
);
|
|
52
|
-
if (!toggle.classList.contains("show")) {
|
|
53
|
-
getDropdown(toggle).toggle();
|
|
21
|
+
root.querySelectorAll(".dropdown").forEach(function (dd) {
|
|
22
|
+
dd.addEventListener("hide.bs.dropdown", function (e) {
|
|
23
|
+
if (this.classList.contains(CLASS_NAME)) {
|
|
24
|
+
this.classList.remove(CLASS_NAME);
|
|
25
|
+
e.preventDefault();
|
|
54
26
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
27
|
+
if (
|
|
28
|
+
e.clickEvent &&
|
|
29
|
+
e.clickEvent
|
|
30
|
+
.composedPath()
|
|
31
|
+
.some(
|
|
32
|
+
(el) => el.classList && el.classList.contains("dropdown-toggle")
|
|
33
|
+
)
|
|
34
|
+
) {
|
|
35
|
+
e.preventDefault();
|
|
62
36
|
}
|
|
37
|
+
e.stopPropagation(); // do not need pop in multi level mode
|
|
63
38
|
});
|
|
64
39
|
});
|
|
40
|
+
|
|
41
|
+
// for hover
|
|
42
|
+
function getDropdown(element) {
|
|
43
|
+
return $bs.Dropdown.getInstance(element) || new $bs.Dropdown(element);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
root
|
|
47
|
+
.querySelectorAll(".dropdown-hover, .dropdown-hover-all .dropdown")
|
|
48
|
+
.forEach(function (dd) {
|
|
49
|
+
dd.addEventListener("mouseenter", function (e) {
|
|
50
|
+
let toggle = e.target.querySelector(
|
|
51
|
+
':scope>[data-bs-toggle="dropdown"]'
|
|
52
|
+
);
|
|
53
|
+
if (!toggle.classList.contains("show")) {
|
|
54
|
+
getDropdown(toggle).toggle();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
dd.addEventListener("mouseleave", function (e) {
|
|
58
|
+
let toggle = e.target.querySelector(
|
|
59
|
+
':scope>[data-bs-toggle="dropdown"]'
|
|
60
|
+
);
|
|
61
|
+
if (toggle.classList.contains("show")) {
|
|
62
|
+
getDropdown(toggle).toggle();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
})(bootstrap);
|
|
65
67
|
}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ransack_search_element
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.1.0.pre.alpha
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Arthur Dzieniszewski
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-08-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -42,7 +42,6 @@ files:
|
|
|
42
42
|
- app/javascript/ransack-search-element/components/FilterRow.tsx
|
|
43
43
|
- app/javascript/ransack-search-element/components/FormInput.tsx
|
|
44
44
|
- app/javascript/ransack-search-element/index.ts
|
|
45
|
-
- app/javascript/ransack-search-element/utilities/QueryBuilder.ts
|
|
46
45
|
- app/javascript/ransack-search-element/utilities/dateConverter.ts
|
|
47
46
|
- app/javascript/ransack-search-element/utilities/registerNestedDropdown.ts
|
|
48
47
|
- app/jobs/ransack_search_element/application_job.rb
|
|
@@ -77,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
77
76
|
- !ruby/object:Gem::Version
|
|
78
77
|
version: 1.3.1
|
|
79
78
|
requirements: []
|
|
80
|
-
rubygems_version: 3.2.
|
|
79
|
+
rubygems_version: 3.2.22
|
|
81
80
|
signing_key:
|
|
82
81
|
specification_version: 4
|
|
83
82
|
summary: Generic, advanced search element for Ransack.
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
export type Filter = {
|
|
2
|
-
[key: string]: {
|
|
3
|
-
label: string;
|
|
4
|
-
query: string;
|
|
5
|
-
type: string;
|
|
6
|
-
value: string | number;
|
|
7
|
-
ass?: string;
|
|
8
|
-
};
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type AttributeInfo = {
|
|
12
|
-
type: string;
|
|
13
|
-
label: string;
|
|
14
|
-
ass?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const options = [
|
|
18
|
-
["Contains", "cont"],
|
|
19
|
-
["Equals", "eq"],
|
|
20
|
-
["Less than", "lt"],
|
|
21
|
-
["Greater than", "gt"],
|
|
22
|
-
["Less than/equal", "lteq"],
|
|
23
|
-
["Greater than/equal", "gteq"],
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
const options_values = options.map((v) => v[1]);
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Recursively iterate through relations with matching prefixes.
|
|
30
|
-
* Attempts to get the accompanying AttributeInfo given a full query.
|
|
31
|
-
* @param full_query
|
|
32
|
-
* @param ass
|
|
33
|
-
*/
|
|
34
|
-
const get_association = (
|
|
35
|
-
full_query,
|
|
36
|
-
ass
|
|
37
|
-
): {
|
|
38
|
-
[association: string]: AttributeInfo;
|
|
39
|
-
} => {
|
|
40
|
-
if (ass instanceof Array) {
|
|
41
|
-
return ass.find((dict) => {
|
|
42
|
-
return full_query.includes(Object.keys(dict)[0]);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const key = Object.keys(ass).find((k) => full_query.includes(k));
|
|
47
|
-
|
|
48
|
-
if (typeof key === "undefined") return null;
|
|
49
|
-
|
|
50
|
-
const remainder = full_query.slice(full_query.indexOf(key) + key.length + 1);
|
|
51
|
-
|
|
52
|
-
return get_association(remainder, ass[key]);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export default class QueryBuilder {
|
|
56
|
-
attributes = {};
|
|
57
|
-
associations;
|
|
58
|
-
possible_values = [];
|
|
59
|
-
|
|
60
|
-
constructor(attributes, associations) {
|
|
61
|
-
this.attributes = attributes;
|
|
62
|
-
this.associations = associations;
|
|
63
|
-
|
|
64
|
-
this.possible_values = Object.keys(attributes).concat(
|
|
65
|
-
associations.map((v) => Object.keys(v)[0]).flat()
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
parseQueries(queries) {
|
|
70
|
-
let s = queries || {};
|
|
71
|
-
|
|
72
|
-
let f = {};
|
|
73
|
-
|
|
74
|
-
if (Object.entries(s)?.length > 0) {
|
|
75
|
-
for (const [k, v] of Object.entries(s)) {
|
|
76
|
-
// sorting parameter
|
|
77
|
-
if (k === "s") continue;
|
|
78
|
-
|
|
79
|
-
const query = options_values
|
|
80
|
-
.filter((v) => k.includes(v))
|
|
81
|
-
.reduce((x, y) => (x.length > y.length ? x : y), "");
|
|
82
|
-
|
|
83
|
-
const attribute = this.possible_values
|
|
84
|
-
.filter((v) => k.includes(v))
|
|
85
|
-
.reduce((x, y) => (x.length > y.length ? x : y), "");
|
|
86
|
-
|
|
87
|
-
const asses = get_association(
|
|
88
|
-
k,
|
|
89
|
-
this.associations.reduce((acc, cur) => ({ ...acc, ...cur }), {})
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
if (asses) {
|
|
93
|
-
const full_ass_key = Object.keys(asses)[0];
|
|
94
|
-
const full_ass_values = Object.values(asses)[0];
|
|
95
|
-
|
|
96
|
-
const t = {};
|
|
97
|
-
t["query"] = query;
|
|
98
|
-
t["value"] = v;
|
|
99
|
-
t["type"] = full_ass_values?.type;
|
|
100
|
-
t["label"] = full_ass_values?.label;
|
|
101
|
-
t["ass"] = attribute;
|
|
102
|
-
|
|
103
|
-
if (query.length > 0 && attribute.length > 0) {
|
|
104
|
-
if (f[`${attribute}_${full_ass_key}`] instanceof Array) {
|
|
105
|
-
f[`${attribute}_${full_ass_key}`].push(t);
|
|
106
|
-
} else {
|
|
107
|
-
f[`${attribute}_${full_ass_key}`] = [];
|
|
108
|
-
f[`${attribute}_${full_ass_key}`].push(t);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
const t = {};
|
|
113
|
-
t["query"] = query;
|
|
114
|
-
t["value"] = v;
|
|
115
|
-
t["type"] = this.attributes[attribute]?.type;
|
|
116
|
-
t["label"] = this.attributes[attribute]?.label;
|
|
117
|
-
|
|
118
|
-
if (query.length > 0 && attribute.length > 0) {
|
|
119
|
-
if (f[`${attribute}`] instanceof Array) {
|
|
120
|
-
f[`${attribute}`].push(t);
|
|
121
|
-
} else {
|
|
122
|
-
f[`${attribute}`] = [];
|
|
123
|
-
f[`${attribute}`].push(t);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
} else {
|
|
129
|
-
return {};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return f;
|
|
133
|
-
}
|
|
134
|
-
}
|